260228:1520 20260228:15:30 workflow update #4 Visual Builder
All checks were successful
Build and Deploy / deploy (push) Successful in 2m19s
All checks were successful
Build and Deploy / deploy (push) Successful in 2m19s
This commit is contained in:
@@ -63,82 +63,60 @@ interface VisualWorkflowBuilderProps {
|
|||||||
function parseDSL(dsl: string): { nodes: Node[], edges: Edge[] } {
|
function parseDSL(dsl: string): { nodes: Node[], edges: Edge[] } {
|
||||||
const nodes: Node[] = [];
|
const nodes: Node[] = [];
|
||||||
const edges: Edge[] = [];
|
const edges: Edge[] = [];
|
||||||
let yOffset = 100;
|
let yOffset = 50;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Simple line-based parser for the demo YAML structure
|
const parsedDsl = JSON.parse(dsl);
|
||||||
// name: Workflow
|
const states = parsedDsl.states || [];
|
||||||
// steps:
|
|
||||||
// - name: Step1 ...
|
|
||||||
|
|
||||||
const lines = dsl.split('\n');
|
states.forEach((state: { id: string, name: string, type: string, role?: string, transitions?: { event: string, to: string }[] }) => {
|
||||||
let currentStep: Record<string, string> | null = null;
|
const isCondition = state.type === 'CONDITION';
|
||||||
const steps: Record<string, string>[] = [];
|
const isStart = state.type === 'START';
|
||||||
|
const isEnd = state.type === 'END';
|
||||||
|
|
||||||
// Very basic parser logic (replace with js-yaml in production)
|
let nodeType = 'default';
|
||||||
let inSteps = false;
|
let style = { ...nodeStyle };
|
||||||
for (const line of lines) {
|
|
||||||
const trimmed = line.trim();
|
if (isStart) {
|
||||||
if (trimmed.startsWith('steps:')) {
|
nodeType = 'input';
|
||||||
inSteps = true;
|
style = { ...nodeStyle, background: '#10b981', color: 'white', border: 'none' };
|
||||||
continue;
|
} else if (isEnd) {
|
||||||
}
|
nodeType = 'output';
|
||||||
if (inSteps && trimmed.startsWith('- name:')) {
|
style = { ...nodeStyle, background: '#ef4444', color: 'white', border: 'none' };
|
||||||
if (currentStep) steps.push(currentStep);
|
} else if (isCondition) {
|
||||||
currentStep = { name: trimmed.replace('- name:', '').trim() };
|
style = conditionNodeStyle;
|
||||||
} else if (inSteps && currentStep && trimmed.startsWith('next:')) {
|
}
|
||||||
currentStep.next = trimmed.replace('next:', '').trim();
|
|
||||||
} else if (inSteps && currentStep && trimmed.startsWith('type:')) {
|
|
||||||
currentStep.type = trimmed.replace('type:', '').trim();
|
|
||||||
} else if (inSteps && currentStep && trimmed.startsWith('role:')) {
|
|
||||||
currentStep.role = trimmed.replace('role:', '').trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (currentStep) steps.push(currentStep);
|
|
||||||
|
|
||||||
// Generate Nodes
|
|
||||||
nodes.push({
|
nodes.push({
|
||||||
id: 'start',
|
id: state.id,
|
||||||
type: 'input',
|
type: nodeType,
|
||||||
data: { label: 'Start' },
|
data: {
|
||||||
position: { x: 250, y: 0 },
|
label: isStart || isEnd ? state.name : `${state.name}\n(${state.role || 'No Role'})`,
|
||||||
style: { ...nodeStyle, background: '#10b981', color: 'white', border: 'none' }
|
name: state.name,
|
||||||
});
|
role: state.role,
|
||||||
|
type: state.type || (isStart ? 'START' : isEnd ? 'END' : 'TASK')
|
||||||
steps.forEach((step) => {
|
},
|
||||||
const isCondition = step.type === 'CONDITION';
|
|
||||||
nodes.push({
|
|
||||||
id: step.name,
|
|
||||||
data: { label: `${step.name}\n(${step.role || 'No Role'})`, name: step.name, role: step.role, type: step.type }, // Store role in data
|
|
||||||
position: { x: 250, y: yOffset },
|
position: { x: 250, y: yOffset },
|
||||||
style: isCondition ? conditionNodeStyle : { ...nodeStyle }
|
style: style
|
||||||
});
|
|
||||||
yOffset += 100;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
nodes.push({
|
if (state.transitions) {
|
||||||
id: 'end',
|
state.transitions.forEach((trans: { event: string, to: string }) => {
|
||||||
type: 'output',
|
|
||||||
data: { label: 'End' },
|
|
||||||
position: { x: 250, y: yOffset },
|
|
||||||
style: { ...nodeStyle, background: '#ef4444', color: 'white', border: 'none' }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Generate Edges
|
|
||||||
edges.push({ id: 'e-start-first', source: 'start', target: steps[0]?.name || 'end', markerEnd: { type: MarkerType.ArrowClosed } });
|
|
||||||
|
|
||||||
steps.forEach((step, index) => {
|
|
||||||
const nextStep = step.next || (index + 1 < steps.length ? steps[index + 1].name : 'end');
|
|
||||||
edges.push({
|
edges.push({
|
||||||
id: `e-${step.name}-${nextStep}`,
|
id: `e-${state.id}-${trans.to}`,
|
||||||
source: step.name,
|
source: state.id,
|
||||||
target: nextStep,
|
target: trans.to,
|
||||||
|
label: trans.event,
|
||||||
markerEnd: { type: MarkerType.ArrowClosed }
|
markerEnd: { type: MarkerType.ArrowClosed }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
yOffset += 120;
|
||||||
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to parse DSL", e);
|
console.error("Failed to parse DSL as JSON", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { nodes, edges };
|
return { nodes, edges };
|
||||||
@@ -169,10 +147,12 @@ function VisualWorkflowBuilderContent({ initialNodes: propNodes, initialEdges: p
|
|||||||
|
|
||||||
const addNode = (type: string, label: string) => {
|
const addNode = (type: string, label: string) => {
|
||||||
const id = `${type}-${Date.now()}`;
|
const id = `${type}-${Date.now()}`;
|
||||||
|
const nodeType = type === 'condition' ? 'CONDITION' : type === 'end' ? 'END' : type === 'start' ? 'START' : 'TASK';
|
||||||
|
|
||||||
const newNode: Node = {
|
const newNode: Node = {
|
||||||
id,
|
id,
|
||||||
position: { x: Math.random() * 400 + 100, y: Math.random() * 300 + 100 },
|
position: { x: Math.random() * 400 + 100, y: Math.random() * 300 + 100 },
|
||||||
data: { label: label, name: label, role: 'User', type: type === 'condition' ? 'CONDITION' : 'APPROVAL' },
|
data: { label: label, name: label, role: 'User', type: nodeType },
|
||||||
style: { ...nodeStyle },
|
style: { ...nodeStyle },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -193,38 +173,26 @@ function VisualWorkflowBuilderContent({ initialNodes: propNodes, initialEdges: p
|
|||||||
onSave?.(nodes, edges);
|
onSave?.(nodes, edges);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mock DSL generation for demonstration
|
// Generate JSON DSL
|
||||||
const generateDSL = () => {
|
const generateDSL = () => {
|
||||||
const steps = nodes
|
const states = nodes.map(n => {
|
||||||
.filter(n => n.type !== 'input' && n.type !== 'output')
|
const outgoingEdges = edges.filter(e => e.source === n.id);
|
||||||
.map(n => ({
|
const transitions = outgoingEdges.map(e => ({
|
||||||
// name: n.data.label, // Removed duplicate
|
event: e.label || 'PROCEED',
|
||||||
// Actually, we should probably separate name and label display.
|
to: e.target
|
||||||
// For now, let's assume data.label IS the name, and we render it differently?
|
|
||||||
// Wait, ReactFlow Default Node renders 'label'.
|
|
||||||
// If I change label to "Name\nRole", then generateDSL will use "Name\nRole" as name.
|
|
||||||
// BAD.
|
|
||||||
// Fix: ReactFlow Node Component.
|
|
||||||
// custom Node?
|
|
||||||
// Quick fix: Keep label as Name. Render a CUSTOM NODE?
|
|
||||||
// Or just parsing: keep label as name.
|
|
||||||
// But user wants to SEE role.
|
|
||||||
// If I change label, I break name.
|
|
||||||
// Change: Use data.name for name, data.role for role.
|
|
||||||
// And label = `${name}\n(${role})`
|
|
||||||
// And here: use data.name if available, else label (cleaned).
|
|
||||||
name: n.data.name || n.data.label.split('\n')[0],
|
|
||||||
role: n.data.role,
|
|
||||||
type: n.data.type || 'APPROVAL', // Use stored type
|
|
||||||
next: edges.find(e => e.source === n.id)?.target || 'End'
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const dsl = `name: Visual Workflow
|
return {
|
||||||
steps:
|
id: n.id,
|
||||||
${steps.map(s => ` - name: ${s.name}
|
name: n.data.name || n.data.label.split('\n')[0],
|
||||||
role: ${s.role || 'User'}
|
type: n.data.type || 'TASK',
|
||||||
type: ${s.type}
|
role: n.data.role,
|
||||||
next: ${s.next}`).join('\n')}`;
|
transitions: transitions
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const dslObj = { states };
|
||||||
|
const dsl = JSON.stringify(dslObj, null, 2);
|
||||||
|
|
||||||
console.log("Generated DSL:", dsl);
|
console.log("Generated DSL:", dsl);
|
||||||
onDslChange?.(dsl);
|
onDslChange?.(dsl);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
361
specs/deploy-deploy-38.txt
Normal file
361
specs/deploy-deploy-38.txt
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user