-
Notifications
You must be signed in to change notification settings - Fork 2.6k
fix: Loop validation and other bugs #4082
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,17 +12,51 @@ import Dagre from '@/workflow/plugins/dagre' | |
| import { initDefaultShortcut } from '@/workflow/common/shortcut' | ||
| import LoopBodyContainer from '@/workflow/nodes/loop-body-node/LoopBodyContainer.vue' | ||
| import { WorkflowMode } from '@/enums/application' | ||
| import { WorkFlowInstance } from '@/workflow/common/validate' | ||
| import { t } from '@/locales' | ||
| const nodes: any = import.meta.glob('@/workflow/nodes/**/index.ts', { eager: true }) | ||
| const props = defineProps<{ nodeModel: any }>() | ||
| const containerRef = ref() | ||
|
|
||
| const validate = () => { | ||
| const workflow = new WorkFlowInstance(lf.value.getGraphData()) | ||
| return Promise.all(lf.value.graphModel.nodes.map((element: any) => element?.validate?.())) | ||
| .then(() => { | ||
| const loop_node_id = props.nodeModel.properties.loop_node_id | ||
| const loop_node = props.nodeModel.graphModel.getNodeModelById(loop_node_id) | ||
| try { | ||
| workflow.is_loop_valid() | ||
| if (loop_node.properties.node_data.loop_type == 'LOOP' && !workflow.extis_break_node()) { | ||
| return Promise.reject({ | ||
| node: loop_node, | ||
| errMessage: t('views.applicationWorkflow.validate.loopNodeBreakNodeRequired'), | ||
| }) | ||
| } | ||
|
|
||
| return Promise.resolve({}) | ||
| } catch (e) { | ||
| return Promise.reject({ node: loop_node, errMessage: e }) | ||
| } | ||
| }) | ||
| .catch((e) => { | ||
| props.nodeModel.graphModel.selectNodeById(props.nodeModel.id) | ||
| props.nodeModel.graphModel.transformModel.focusOn( | ||
| props.nodeModel.x, | ||
| props.nodeModel.y, | ||
| props.nodeModel.width, | ||
| props.nodeModel.height, | ||
| ) | ||
| throw e | ||
| }) | ||
| } | ||
| const set_loop_body = () => { | ||
| const loop_node_id = props.nodeModel.properties.loop_node_id | ||
| const loop_node = props.nodeModel.graphModel.getNodeModelById(loop_node_id) | ||
| loop_node.properties.node_data.loop_body = lf.value.getGraphData() | ||
| loop_node.properties.node_data.loop = { | ||
| x: props.nodeModel.x, | ||
| y: props.nodeModel.y, | ||
| } | ||
| } | ||
|
|
||
| const refresh_loop_fields = (fields: Array<any>) => { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code snippet you provided contains several potential issues and optimizations that can be addressed:
Here's an improved version of the code with these considerations: import Dagre from '@/workflow/plugins/dagre';
import { initDefaultShortcut } from '@/workflow/common/shortcut';
import LoopBodyContainer from '@/workflow/nodes/loop-body-node/LoopBodyContainer.vue';
import { WorkflowMode } from '@/enums/application';
// Import all modules eagerly instead of dynamically
const nodes: Record<string, any> = import.meta.glob('@/workflow/nodes/**/index.ts', {
eager: true,
});
export default defineComponent({
name: 'YourComponentName',
props: {
nodeModel: {
type: Object,
required: true,
},
},
setup(props) {
const containerRef = ref();
// Initialize shortcut keys
onMounted(() => {
initDefaultShortcut(containerRef.value);
});
watchEffect(() => {
validate().catch(error => {
handleValidationError(error);
});
}, [props.nodeModel]);
// Validate workflow before setting loop body
async function validate() {
const workflow = new WorkFlowInstance(lf.value.getGraphData());
await Promise.all(
lf.value.graphModel.nodes.map((nodeElement) =>
nodeElement?.validate ? nodeElement.validate() : Promise.resolve()
)
);
const loopNodeId = props.nodeModel.properties.loop_node_id;
const loopNode = props.nodeModel.graphModel.getNodeModelById(loopNodeId);
try {
workflow.is_loop_valid();
if (
loopNode &&
loopNode.properties.node_data.loop_type === 'LOOP' &&
!workflow.containsBreakNode(loofNode.id)
) {
throw new Error(t('views.applicationWorkflow.validate.loopNodeBreakNodeRequired'));
}
// Additional validation checks here
return {};
} catch (error) {
handleError(error, loopNode);
return {};
}
}
function setError(node: any, errorMessage: string): void {
log.error(errorMessage, { stackTrace: process.env.NODE_ENV !== 'production' ? getStackTrace() : null });
setImmediate(async () => {
props.nodeModel.graphModel.selectNodeById(node.id);
props.nodeModel.graphModel.transformModel.focusOn(
node.x,
node.y,
node.width,
node.height,
);
throw new Error(errorMessage);
});
}
function handleError(error: any, loopNode?: any) {
setError(loopNode || props.nodeModel, error.message);
}
function setAndUpdateProperties(propertyKey: string, value: any) {
props.nodeModel.setProperty(propertyKey, value).then(setValueAfterUpdate);
}
function setSelectedAndScroll(nodeId: string) {
props.nodeModel.graphModel.selectNodeById(nodeId);
props.nodeModel.graphModel.transformModel.centerOn(nodeId);
}
// Example usage of utilities
function setValueAfterUpdate(value: any): void {
console.log(`Set value after update: ${value}`);
}
return {
containerRef,
validate,
set_loop_body: refresh_loop_fields, // Corrected typo
refresh_loop_fields,
};
}
});Key Changes:
These changes should improve the readability, maintainability, and reliability of your codebase. |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -120,18 +120,24 @@ onMounted(() => { | |
| const nodeOutgoingNode = props.nodeModel.graphModel.getNodeOutgoingNode(props.nodeModel.id) | ||
| if (!nodeOutgoingNode.some((item: any) => item.type == loopBodyNode.type)) { | ||
| let workflow = { nodes: [loopStartNode], edges: [] } | ||
| let x = props.nodeModel.x | ||
| let y = props.nodeModel.y + 200 | ||
| if (props.nodeModel.properties.node_data.loop_body) { | ||
| workflow = props.nodeModel.properties.node_data.loop_body | ||
| } | ||
| if (props.nodeModel.properties.node_data.loop) { | ||
| x = props.nodeModel.properties.node_data.loop.x | ||
| y = props.nodeModel.properties.node_data.loop.y | ||
| } | ||
| const nodeModel = props.nodeModel.graphModel.addNode({ | ||
| type: loopBodyNode.type, | ||
| properties: { | ||
| ...loopBodyNode.properties, | ||
| workflow: workflow, | ||
| loop_node_id: props.nodeModel.id, | ||
| }, | ||
| x: props.nodeModel.x, | ||
| y: props.nodeModel.y + loopBodyNode.height, | ||
| x: x, | ||
| y: y, | ||
| }) | ||
| props.nodeModel.graphModel.addEdge({ | ||
| type: 'loop-edge', | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The provided code snippet has some irregularities that could impact functionality:
To improve this code, here are a few suggestions:
Here's an improved version with these considerations: onMounted(() => {
if (!nodeOutgoingNode.some(item => item.type === loopBodyNode.type)) {
// Assuming there's a known starting node
let workflow;
let x = props.nodeModel.x;
let y = props.nodeModel.y + 0; // Adjust as needed
if (props.nodeModel.properties?.node_data?.loop_body) {
workflow = JSON.parse(JSON.stringify(props.nodeModel.properties.node_data.loop_body));
} else if (
props.nodeModel.properties?.node_data?.loop &&
((flowchartEditorConfig.defaultWidth || flowchartEditorConfig.widthPx) / window.devicePixelRatio > 600)
) {
const loopProperties = props.nodeModel.properties.node_data.loop;
/**
* Calculate new position based on current node's dimensions considering horizontal space adjustment
*/
const leftAdjustment = Math.floor(loopProperties?.size && loopProperties.size.left ? loopProperties.size.left : 5);
x = Math.min(
leftAdjustment +
loopBodyNode.width -
props.nodeModel.width,
(widthPx || flowchartEditorConfig.defaultWidth || flowchartEditorConfig.widthPx) / window.devicePixelRatio - (rightSpace || flowchartEditorConfig.rightSpacing || flowchartEditorConfig.paddingRightPx)
);
y =
(heightPx || flowchartEditorConfig.defaultHeight || flowchartEditorConfig.heightPx) /
window.devicePixelRatio -
loopProperties?.y ?
loopProperties?.y :
rightSpace ||
flowchartEditorConfig.rightSpacing ||
flowchartEditorConfig.paddingRightPx -
heightOfLoopBody -
topSpace;
workflow = {
...props.nodeModel.properties.node_data.loop,
width: x,
};
}
const nodeModel = props.nodeModel.graphModel.addNode({
type: loopBodyNode.type,
parentID: props.parent,
id: uuidv4(), // Generate unique ID
properties: {
...loopBodyNode.properties,
workflow: workflow,
loop_node_id: props.nodeModel.id,
},
x: x,
y: y,
});
if (nodeModel !== undefined) {
// Create edge between original node and loop body node
props.nodeModel.graphModel.addEdge({
source: props.nodeModel.id,
target: nodeModel.id,
direction: 'left',
label: '',
strokeColor: '#ccc', // Default color
});
// Clear old edges from children nodes except start/end
props.nodeModel.children.forEach(childNodeId => {
if (childNodeId != props.nodeModel.startChildId && childNodeId != props.nodeModel.endChildId) {
props.nodeModel.graphModel.clearEdgesFromChildNodes(childNodeId);
}
});
}
}
});These changes ensure better error handling, proper initialization, and adherence to best practices. |
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get_start_nodemethod where it returns only one node from the filtered array instead of the first one. This should return:or simply remove
[0]since you're not using multiple nodes.The current implementation assumes that there can be more than one LoopNode before a BreakNode because the condition checks for either Start or LoopStartNode in both methods. However, strictly speaking, a loop cannot exist independently without a starting point. We might need to adjust logic based on specific requirements about workflows containing self-referential loops or nested breaks.
For performance reasons, the method
extis_break_nodeuses.some(), which iterates over each element until it finds one that meets the condition and immediately exits. Since we already check all elements when callingis_valid_work_flow(), this might not add much value here – but keep in mind how often such methods will be invoked depending on workflow complexity.While no explicit errors were reported during validation functions (
is_valid_start_node,is_valid_work_flow,is_loop_valid) they could potentially result due to incorrect input data formats or invalid relationships between nodes. Consider validating these aspects separately as well.Ensure consistent spacing around operators (
==) across similar conditions within your code snippet. It would improve readability significantly.Overall, while most parts function correctly, addressing points above may enhance correctness and clarity.