feat: add ABM pipeline contracts and validation
This commit is contained in:
@@ -3,7 +3,11 @@ import { Link, useLocation } from 'react-router-dom';
|
||||
import { LayoutGrid, Home, Edit3, Activity, ChevronDown, Globe } from 'lucide-react';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
|
||||
const ModuleSwitcher: React.FC = () => {
|
||||
interface ModuleSwitcherProps {
|
||||
variant?: 'light' | 'dark';
|
||||
}
|
||||
|
||||
const ModuleSwitcher: React.FC<ModuleSwitcherProps> = ({ variant = 'light' }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const location = useLocation();
|
||||
|
||||
@@ -20,11 +24,15 @@ const ModuleSwitcher: React.FC = () => {
|
||||
<div className="relative z-[100]">
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="flex items-center gap-2 px-3 py-1.5 bg-white hover:bg-slate-50 rounded-xl border border-slate-200 shadow-sm transition-all text-slate-800 active:scale-95"
|
||||
className={`flex items-center gap-2 px-3 py-1.5 rounded-xl border shadow-sm transition-all active:scale-95 ${
|
||||
variant === 'dark'
|
||||
? 'bg-blue-600 hover:bg-blue-500 border-blue-500 text-white shadow-blue-950/30'
|
||||
: 'bg-white hover:bg-slate-50 border-slate-200 text-slate-800'
|
||||
}`}
|
||||
>
|
||||
<LayoutGrid className="w-4 h-4 text-blue-600" />
|
||||
<LayoutGrid className={`w-4 h-4 ${variant === 'dark' ? 'text-white' : 'text-blue-600'}`} />
|
||||
<span className="text-sm font-black tracking-tight">{currentModule.name}</span>
|
||||
<ChevronDown className={`w-3 h-3 text-slate-400 transition-transform ${isOpen ? 'rotate-180' : ''}`} />
|
||||
<ChevronDown className={`w-3 h-3 transition-transform ${variant === 'dark' ? 'text-blue-100' : 'text-slate-400'} ${isOpen ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
|
||||
<AnimatePresence>
|
||||
|
||||
+1012
-103
File diff suppressed because it is too large
Load Diff
+101
-1
@@ -1,5 +1,16 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { createDefaultWorkflow, getBehaviorSectionsForNode, getNodeConnectionAnchor, getParametersForNode } from './workflowModel';
|
||||
import {
|
||||
buildRuntimeConfig,
|
||||
createDefaultWorkflow,
|
||||
ensureValidationChecklist,
|
||||
getBehaviorSectionsForNode,
|
||||
getInputOutputsForEdge,
|
||||
getInputOutputsForNode,
|
||||
getNodeConnectionAnchor,
|
||||
getNodeStatusFromValidation,
|
||||
getParametersForNode,
|
||||
getValidationsForNode,
|
||||
} from './workflowModel';
|
||||
|
||||
const baseWorkflow = createDefaultWorkflow();
|
||||
|
||||
@@ -10,6 +21,44 @@ assert.deepEqual(
|
||||
|
||||
assert.equal(baseWorkflow.edges.length, 1);
|
||||
|
||||
assert.equal(baseWorkflow.schedules.length, 3);
|
||||
assert.equal(baseWorkflow.schedules[0].id, 'S001');
|
||||
assert.equal(baseWorkflow.schedules[0].actorAgent, 'Environment');
|
||||
|
||||
assert.equal(baseWorkflow.associatedData.length, 3);
|
||||
assert.equal(baseWorkflow.associatedData[0].sourceTable, 'model_initial_state');
|
||||
|
||||
assert.equal(baseWorkflow.inputOutputs.length, 3);
|
||||
assert.equal(baseWorkflow.inputOutputs[0].snapshotPolicy, 'run_start');
|
||||
|
||||
assert.equal(baseWorkflow.validations.length, 3);
|
||||
assert.equal(baseWorkflow.validations[0].severity, 'error');
|
||||
|
||||
const runtimeConfig = buildRuntimeConfig(baseWorkflow);
|
||||
|
||||
assert.equal(runtimeConfig.runtime.version, 'workflow-runtime-v1');
|
||||
assert.equal(runtimeConfig.runtime.ioContracts.length, 3);
|
||||
assert.deepEqual(runtimeConfig.runtime.ioContracts[0], {
|
||||
id: 'IO001',
|
||||
direction: 'input',
|
||||
name: 'self.position',
|
||||
dataType: 'Coordinate',
|
||||
schema: '{ x: number, y: number }',
|
||||
source: {
|
||||
component: 'Grid Space',
|
||||
table: 'space_cell_map',
|
||||
},
|
||||
target: {
|
||||
component: 'Example Agent',
|
||||
table: '-',
|
||||
},
|
||||
required: true,
|
||||
defaultValue: null,
|
||||
snapshotPolicy: 'run_start',
|
||||
});
|
||||
assert.equal(runtimeConfig.runtime.schedule[0].id, 'S001');
|
||||
assert.equal(runtimeConfig.runtime.validation[0].severity, 'error');
|
||||
|
||||
const gridWorkflow = createDefaultWorkflow('grid');
|
||||
|
||||
assert.deepEqual(
|
||||
@@ -19,6 +68,57 @@ assert.deepEqual(
|
||||
|
||||
assert.equal(gridWorkflow.edges.length, 2);
|
||||
|
||||
const spaceToAgentContracts = getInputOutputsForEdge(gridWorkflow, 'e2');
|
||||
|
||||
assert.deepEqual(
|
||||
spaceToAgentContracts.map((row) => row.ioName),
|
||||
['self.position'],
|
||||
);
|
||||
|
||||
const agentContracts = getInputOutputsForNode(gridWorkflow, 'example-agent');
|
||||
|
||||
assert.deepEqual(
|
||||
agentContracts.map((row) => row.ioName),
|
||||
['self.position', 'neighbor.race', 'self.happiness'],
|
||||
);
|
||||
|
||||
assert.equal(getNodeStatusFromValidation(gridWorkflow, 'grid-space'), 'error');
|
||||
assert.equal(getNodeStatusFromValidation(gridWorkflow, 'example-agent'), 'ready');
|
||||
|
||||
const gridValidations = getValidationsForNode(gridWorkflow, 'grid-space');
|
||||
|
||||
assert.deepEqual(
|
||||
gridValidations.map((row) => row.id),
|
||||
['V001'],
|
||||
);
|
||||
|
||||
const generatedWorkflow = ensureValidationChecklist({
|
||||
...gridWorkflow,
|
||||
nodes: [
|
||||
...gridWorkflow.nodes,
|
||||
{
|
||||
id: 'ai-output',
|
||||
title: 'AI Output',
|
||||
subtitle: 'Generated report output',
|
||||
kind: 'output',
|
||||
x: 360,
|
||||
y: 120,
|
||||
status: 'ready',
|
||||
inputs: [{ id: 'report', label: 'report input', type: 'REPORT' }],
|
||||
outputs: [],
|
||||
config: [],
|
||||
},
|
||||
],
|
||||
validations: gridWorkflow.validations,
|
||||
});
|
||||
|
||||
const generatedNodeValidations = getValidationsForNode(generatedWorkflow, 'ai-output');
|
||||
|
||||
assert.deepEqual(
|
||||
generatedNodeValidations.map((row) => row.method),
|
||||
['node_config_present', 'io_contract_coverage'],
|
||||
);
|
||||
|
||||
const parameterRows = getParametersForNode(gridWorkflow, 'example-agent');
|
||||
|
||||
assert.equal(parameterRows.length, 3);
|
||||
|
||||
@@ -67,11 +67,82 @@ export interface BehaviorSection {
|
||||
items: Array<{ label: string; source: string; value: string }>;
|
||||
}
|
||||
|
||||
export interface ScheduleRow {
|
||||
id: string;
|
||||
order: string;
|
||||
phase: string;
|
||||
actorAgent: string;
|
||||
targetBehavior: string;
|
||||
trigger: string;
|
||||
executionMode: string;
|
||||
timeUnit: string;
|
||||
condition: string;
|
||||
repeatRule: string;
|
||||
dependsOn: string;
|
||||
writesTo: string;
|
||||
notes: string;
|
||||
}
|
||||
|
||||
export interface AssociatedDataRow {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
sourceType: string;
|
||||
sourceName: string;
|
||||
sourceTable: string;
|
||||
sourceColumn: string;
|
||||
ownerAgent: string;
|
||||
relatedVariable: string;
|
||||
usedByBehavior: string;
|
||||
updateFrequency: string;
|
||||
versionKey: string;
|
||||
qualityStatus: 'ok' | 'warning' | 'error';
|
||||
}
|
||||
|
||||
export interface InputOutputRow {
|
||||
id: string;
|
||||
componentType: string;
|
||||
componentId: string;
|
||||
componentName: string;
|
||||
direction: 'input' | 'output';
|
||||
ioName: string;
|
||||
ioDataType: string;
|
||||
schema: string;
|
||||
sourceComponent: string;
|
||||
sourceTable: string;
|
||||
targetComponent: string;
|
||||
targetTable: string;
|
||||
required: string;
|
||||
defaultValue: string;
|
||||
snapshotPolicy: string;
|
||||
}
|
||||
|
||||
export interface ValidationRow {
|
||||
id: string;
|
||||
targetType: string;
|
||||
targetId: string;
|
||||
targetName: string;
|
||||
validationType: string;
|
||||
method: string;
|
||||
metric: string;
|
||||
baselineData: string;
|
||||
threshold: string;
|
||||
frequency: string;
|
||||
resultField: string;
|
||||
severity: 'error' | 'warning' | 'info';
|
||||
status: 'ok' | 'warning' | 'error';
|
||||
notes: string;
|
||||
}
|
||||
|
||||
export interface WorkflowModel {
|
||||
nodes: WorkflowNode[];
|
||||
edges: WorkflowEdge[];
|
||||
parameters: WorkflowParameter[];
|
||||
behaviorSections: BehaviorSection[];
|
||||
schedules: ScheduleRow[];
|
||||
associatedData: AssociatedDataRow[];
|
||||
inputOutputs: InputOutputRow[];
|
||||
validations: ValidationRow[];
|
||||
}
|
||||
|
||||
export type InitialSpaceType = 'grid' | 'network' | 'continuous' | 'gis';
|
||||
@@ -308,6 +379,203 @@ export function createDefaultWorkflow(spaceType?: InitialSpaceType): WorkflowMod
|
||||
],
|
||||
},
|
||||
],
|
||||
schedules: [
|
||||
{
|
||||
id: 'S001',
|
||||
order: '1',
|
||||
phase: 'initialize',
|
||||
actorAgent: 'Environment',
|
||||
targetBehavior: 'create_context',
|
||||
trigger: 'model_start',
|
||||
executionMode: 'sync',
|
||||
timeUnit: 'tick',
|
||||
condition: 'always',
|
||||
repeatRule: 'once',
|
||||
dependsOn: '-',
|
||||
writesTo: 'ENV_CONTEXT',
|
||||
notes: 'Create shared model context',
|
||||
},
|
||||
{
|
||||
id: 'S002',
|
||||
order: '2',
|
||||
phase: 'initialize',
|
||||
actorAgent: 'Grid Space',
|
||||
targetBehavior: 'allocate_cells',
|
||||
trigger: 'space_ready',
|
||||
executionMode: 'sync',
|
||||
timeUnit: 'tick',
|
||||
condition: 'environment_ready',
|
||||
repeatRule: 'once',
|
||||
dependsOn: 'S001',
|
||||
writesTo: 'SPACE_CONTEXT',
|
||||
notes: 'Initialize spatial container',
|
||||
},
|
||||
{
|
||||
id: 'S003',
|
||||
order: '3',
|
||||
phase: 'daily',
|
||||
actorAgent: 'Example Agent',
|
||||
targetBehavior: 'on_sensing',
|
||||
trigger: 'every_tick',
|
||||
executionMode: 'sync',
|
||||
timeUnit: 'day',
|
||||
condition: 'neighbor_radius valid',
|
||||
repeatRule: 'every tick',
|
||||
dependsOn: 'S002',
|
||||
writesTo: 'happiness',
|
||||
notes: 'Observe neighbors and update agent state',
|
||||
},
|
||||
],
|
||||
associatedData: [
|
||||
{
|
||||
id: 'D001',
|
||||
name: 'agent_group_seed',
|
||||
type: 'parameter',
|
||||
sourceType: 'initialization',
|
||||
sourceName: 'default workflow',
|
||||
sourceTable: 'model_initial_state',
|
||||
sourceColumn: 'race',
|
||||
ownerAgent: 'Example Agent',
|
||||
relatedVariable: 'race',
|
||||
usedByBehavior: 'on_sensing',
|
||||
updateFrequency: 'model start',
|
||||
versionKey: 'workflow_v3',
|
||||
qualityStatus: 'ok',
|
||||
},
|
||||
{
|
||||
id: 'D002',
|
||||
name: 'grid_position_map',
|
||||
type: 'derived_data',
|
||||
sourceType: 'space connection',
|
||||
sourceName: 'Grid Space',
|
||||
sourceTable: 'space_cell_map',
|
||||
sourceColumn: 'x,y',
|
||||
ownerAgent: 'Example Agent',
|
||||
relatedVariable: 'position',
|
||||
usedByBehavior: 'on_sensing',
|
||||
updateFrequency: 'every tick',
|
||||
versionKey: 'space_config_v1',
|
||||
qualityStatus: 'ok',
|
||||
},
|
||||
{
|
||||
id: 'D003',
|
||||
name: 'neighbor_lookup_radius',
|
||||
type: 'space_config',
|
||||
sourceType: 'manual config',
|
||||
sourceName: 'Grid Space',
|
||||
sourceTable: 'space_parameters',
|
||||
sourceColumn: 'neighbor_radius',
|
||||
ownerAgent: 'Grid Space',
|
||||
relatedVariable: 'neighbor_radius',
|
||||
usedByBehavior: 'on_sensing',
|
||||
updateFrequency: 'model start',
|
||||
versionKey: 'missing',
|
||||
qualityStatus: 'error',
|
||||
},
|
||||
],
|
||||
inputOutputs: [
|
||||
{
|
||||
id: 'IO001',
|
||||
componentType: 'Behavior',
|
||||
componentId: 'on_sensing',
|
||||
componentName: 'on_sensing',
|
||||
direction: 'input',
|
||||
ioName: 'self.position',
|
||||
ioDataType: 'Coordinate',
|
||||
schema: '{ x: number, y: number }',
|
||||
sourceComponent: 'Grid Space',
|
||||
sourceTable: 'space_cell_map',
|
||||
targetComponent: 'Example Agent',
|
||||
targetTable: '-',
|
||||
required: 'true',
|
||||
defaultValue: '-',
|
||||
snapshotPolicy: 'run_start',
|
||||
},
|
||||
{
|
||||
id: 'IO002',
|
||||
componentType: 'Behavior',
|
||||
componentId: 'on_sensing',
|
||||
componentName: 'on_sensing',
|
||||
direction: 'input',
|
||||
ioName: 'neighbor.race',
|
||||
ioDataType: 'String',
|
||||
schema: 'A | B',
|
||||
sourceComponent: 'Observable pool',
|
||||
sourceTable: 'agent_state',
|
||||
targetComponent: 'Example Agent',
|
||||
targetTable: '-',
|
||||
required: 'true',
|
||||
defaultValue: '-',
|
||||
snapshotPolicy: 'each_tick',
|
||||
},
|
||||
{
|
||||
id: 'IO003',
|
||||
componentType: 'Behavior',
|
||||
componentId: 'on_sensing',
|
||||
componentName: 'on_sensing',
|
||||
direction: 'output',
|
||||
ioName: 'self.happiness',
|
||||
ioDataType: 'Boolean',
|
||||
schema: 'true | false',
|
||||
sourceComponent: 'on_sensing',
|
||||
sourceTable: '-',
|
||||
targetComponent: 'Example Agent',
|
||||
targetTable: 'agent_state',
|
||||
required: 'true',
|
||||
defaultValue: 'false',
|
||||
snapshotPolicy: 'each_tick',
|
||||
},
|
||||
],
|
||||
validations: [
|
||||
{
|
||||
id: 'V001',
|
||||
targetType: 'Variable',
|
||||
targetId: 's2',
|
||||
targetName: 'neighbor_radius',
|
||||
validationType: 'schema_check',
|
||||
method: 'required_value',
|
||||
metric: 'missing_count',
|
||||
baselineData: 'space_parameters.neighbor_radius',
|
||||
threshold: '= 0',
|
||||
frequency: 'model start',
|
||||
resultField: 'validation.required_missing',
|
||||
severity: 'error',
|
||||
status: 'error',
|
||||
notes: 'Required for on_sensing neighborhood lookup',
|
||||
},
|
||||
{
|
||||
id: 'V002',
|
||||
targetType: 'Behavior',
|
||||
targetId: 'on_sensing',
|
||||
targetName: 'on_sensing',
|
||||
validationType: 'rule_check',
|
||||
method: 'output_assignment',
|
||||
metric: 'assigned',
|
||||
baselineData: 'self.happiness',
|
||||
threshold: 'true',
|
||||
frequency: 'every tick',
|
||||
resultField: 'behavior.output_valid',
|
||||
severity: 'error',
|
||||
status: 'ok',
|
||||
notes: 'Behavior must write a Boolean result to the agent state',
|
||||
},
|
||||
{
|
||||
id: 'V003',
|
||||
targetType: 'Workflow',
|
||||
targetId: 'workflow_v3',
|
||||
targetName: 'workflow',
|
||||
validationType: 'reproducibility',
|
||||
method: 'input_snapshot',
|
||||
metric: 'coverage',
|
||||
baselineData: 'all required I/O rows',
|
||||
threshold: '100%',
|
||||
frequency: 'before run',
|
||||
resultField: 'run.snapshot_coverage',
|
||||
severity: 'warning',
|
||||
status: 'warning',
|
||||
notes: 'All required inputs should be captured for reproduction',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -319,6 +587,171 @@ export function getBehaviorSectionsForNode(workflow: WorkflowModel, nodeId: stri
|
||||
return workflow.behaviorSections.filter((section) => section.nodeId === nodeId);
|
||||
}
|
||||
|
||||
export function getInputOutputsForEdge(workflow: WorkflowModel, edgeId: string) {
|
||||
const edge = workflow.edges.find((workflowEdge) => workflowEdge.id === edgeId);
|
||||
if (!edge) return [];
|
||||
|
||||
const sourceNode = workflow.nodes.find((node) => node.id === edge.source);
|
||||
const targetNode = workflow.nodes.find((node) => node.id === edge.target);
|
||||
if (!sourceNode || !targetNode) return [];
|
||||
|
||||
return workflow.inputOutputs.filter((row) => (
|
||||
row.sourceComponent === sourceNode.title
|
||||
&& row.targetComponent === targetNode.title
|
||||
));
|
||||
}
|
||||
|
||||
export function getInputOutputsForNode(workflow: WorkflowModel, nodeId: string) {
|
||||
const node = workflow.nodes.find((workflowNode) => workflowNode.id === nodeId);
|
||||
if (!node) return [];
|
||||
|
||||
return workflow.inputOutputs.filter((row) => (
|
||||
row.sourceComponent === node.title
|
||||
|| row.targetComponent === node.title
|
||||
));
|
||||
}
|
||||
|
||||
function validationBelongsToNode(workflow: WorkflowModel, validation: ValidationRow, node: WorkflowNode) {
|
||||
if (validation.targetId === node.id || validation.targetName === node.title) return true;
|
||||
|
||||
if (validation.targetType === 'Variable') {
|
||||
return workflow.parameters.some((parameter) => (
|
||||
parameter.nodeId === node.id
|
||||
&& (parameter.id === validation.targetId || parameter.name === validation.targetName)
|
||||
));
|
||||
}
|
||||
|
||||
if (validation.targetType === 'Behavior') {
|
||||
return workflow.behaviorSections.some((section) => (
|
||||
section.nodeId === node.id
|
||||
&& (section.id === validation.targetId || section.title === validation.targetName || validation.targetId === 'on_sensing')
|
||||
));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getValidationsForNode(workflow: WorkflowModel, nodeId: string) {
|
||||
const node = workflow.nodes.find((workflowNode) => workflowNode.id === nodeId);
|
||||
if (!node) return [];
|
||||
|
||||
return workflow.validations.filter((validation) => validationBelongsToNode(workflow, validation, node));
|
||||
}
|
||||
|
||||
function createNodeValidationChecklist(workflow: WorkflowModel, node: WorkflowNode): ValidationRow[] {
|
||||
const hasIoContract = workflow.inputOutputs.some((row) => (
|
||||
row.sourceComponent === node.title
|
||||
|| row.targetComponent === node.title
|
||||
));
|
||||
|
||||
return [
|
||||
{
|
||||
id: `AUTO-${node.id}-CONFIG`,
|
||||
targetType: 'Node',
|
||||
targetId: node.id,
|
||||
targetName: node.title,
|
||||
validationType: 'schema_check',
|
||||
method: 'node_config_present',
|
||||
metric: 'config_items',
|
||||
baselineData: `${node.id}.config`,
|
||||
threshold: '> 0',
|
||||
frequency: 'before run',
|
||||
resultField: `validation.${node.id}.config_present`,
|
||||
severity: 'warning',
|
||||
status: node.config.length > 0 ? 'ok' : 'warning',
|
||||
notes: 'Generated checklist item: node should have explicit configuration before running.',
|
||||
},
|
||||
{
|
||||
id: `AUTO-${node.id}-IO`,
|
||||
targetType: 'Node',
|
||||
targetId: node.id,
|
||||
targetName: node.title,
|
||||
validationType: 'contract_check',
|
||||
method: 'io_contract_coverage',
|
||||
metric: 'io_contract_count',
|
||||
baselineData: 'inputOutputs',
|
||||
threshold: node.inputs.length || node.outputs.length ? '> 0' : '>= 0',
|
||||
frequency: 'before run',
|
||||
resultField: `validation.${node.id}.io_contract_coverage`,
|
||||
severity: 'warning',
|
||||
status: hasIoContract || (!node.inputs.length && !node.outputs.length) ? 'ok' : 'warning',
|
||||
notes: 'Generated checklist item: node ports should be represented by I/O contract rows.',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function ensureValidationChecklist(workflow: WorkflowModel): WorkflowModel {
|
||||
const existingIds = new Set(workflow.validations.map((validation) => validation.id));
|
||||
const generatedValidations = workflow.nodes.flatMap((node) => (
|
||||
createNodeValidationChecklist(workflow, node).filter((validation) => !existingIds.has(validation.id))
|
||||
));
|
||||
|
||||
if (!generatedValidations.length) return workflow;
|
||||
|
||||
return {
|
||||
...workflow,
|
||||
validations: [...workflow.validations, ...generatedValidations],
|
||||
};
|
||||
}
|
||||
|
||||
export function getNodeStatusFromValidation(workflow: WorkflowModel, nodeId: string): WorkflowNodeStatus {
|
||||
const node = workflow.nodes.find((workflowNode) => workflowNode.id === nodeId);
|
||||
if (!node) return 'ready';
|
||||
|
||||
const relatedValidations = getValidationsForNode(workflow, node.id);
|
||||
|
||||
if (relatedValidations.some((validation) => validation.status === 'error')) return 'error';
|
||||
if (relatedValidations.some((validation) => validation.status === 'warning')) return 'warning';
|
||||
|
||||
return node.status === 'success' ? 'success' : 'ready';
|
||||
}
|
||||
|
||||
export function buildRuntimeConfig(workflow: WorkflowModel) {
|
||||
const workflowWithChecklist = ensureValidationChecklist(workflow);
|
||||
|
||||
return {
|
||||
runtime: {
|
||||
version: 'workflow-runtime-v1',
|
||||
model: {
|
||||
nodes: workflowWithChecklist.nodes.map((node) => ({
|
||||
id: node.id,
|
||||
kind: node.kind,
|
||||
title: node.title,
|
||||
status: node.status,
|
||||
})),
|
||||
edges: workflowWithChecklist.edges.map((edge) => ({
|
||||
id: edge.id,
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
type: edge.type,
|
||||
label: edge.label,
|
||||
})),
|
||||
},
|
||||
ioContracts: workflowWithChecklist.inputOutputs.map((row) => ({
|
||||
id: row.id,
|
||||
direction: row.direction,
|
||||
name: row.ioName,
|
||||
dataType: row.ioDataType,
|
||||
schema: row.schema,
|
||||
source: {
|
||||
component: row.sourceComponent,
|
||||
table: row.sourceTable,
|
||||
},
|
||||
target: {
|
||||
component: row.targetComponent,
|
||||
table: row.targetTable,
|
||||
},
|
||||
required: row.required === 'true',
|
||||
defaultValue: row.defaultValue === '-' ? null : row.defaultValue,
|
||||
snapshotPolicy: row.snapshotPolicy,
|
||||
})),
|
||||
schedule: workflowWithChecklist.schedules,
|
||||
associatedData: workflowWithChecklist.associatedData,
|
||||
validation: workflowWithChecklist.validations,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const NODE_CARD_SIZE = {
|
||||
width: 176,
|
||||
height: 128,
|
||||
|
||||
Reference in New Issue
Block a user