diff --git a/src/modules/manager/workflowTemplates/components/CapabilityEditors/TaskCoreEditor.tsx b/src/modules/manager/workflowTemplates/components/CapabilityEditors/TaskCoreEditor.tsx index 652a5ca..e2dcfb0 100644 --- a/src/modules/manager/workflowTemplates/components/CapabilityEditors/TaskCoreEditor.tsx +++ b/src/modules/manager/workflowTemplates/components/CapabilityEditors/TaskCoreEditor.tsx @@ -8,6 +8,7 @@ import { renderTaskField } from "../taskEditorHelpers"; import { TaskValidationResult } from "../TasksEditor"; import { Namespaces } from "../../../../../i18n/i18n"; import { useTranslation } from "react-i18next"; +import { getAllDescendants } from "../workflowGraphUtils"; interface TaskCoreEditorProps { task: TaskDefinition; @@ -58,6 +59,11 @@ export const TaskCoreEditor: React.FC = ({ [allowedTasks, task.type, tTaskType], ); + useEffect(() => { + // Reset the guard when a new task is loaded + hasAssignedDefaultName.current = false; + }, [task.config.guid]); + useEffect(() => { if (!hasAssignedDefaultName.current && !task.config.name) { hasAssignedDefaultName.current = true; @@ -66,17 +72,27 @@ export const TaskCoreEditor: React.FC = ({ (t) => t.taskType === task.type, )?.displayName; - if (displayName) { - const newName = formatNewTaskName(allTasks); + const newConfig = { ...task.config }; - onChange({ - ...task, - config: { - ...task.config, - name: newName, - }, - }); + // Assign default name + if (displayName) { + newConfig.name = formatNewTaskName(allTasks); } + + // Assign default predecessor (the task immediately before this one) + const index = allTasks.findIndex( + (t) => t.config.guid === task.config.guid, + ); + + if (index > 0) { + const previousTask = allTasks[index - 1]; + newConfig.predecessors = [previousTask.config.guid as string]; + } + + onChange({ + ...task, + config: newConfig, + }); } }, [allTasks, allowedTasks, formatNewTaskName, onChange, task]); @@ -163,6 +179,21 @@ export const TaskCoreEditor: React.FC = ({ onValidate, ]); + const currentGuid = task.config.guid as string; + const descendants = getAllDescendants(currentGuid, allTasks); + + const allowedPredecessors = allTasks.filter((t) => { + const guid = t.config.guid as string; + + // Exclude self + if (guid === currentGuid) return false; + + // Exclude descendants (direct or indirect) + if (descendants.has(guid)) return false; + + return true; + }); + return (
{renderTaskField( @@ -193,12 +224,10 @@ export const TaskCoreEditor: React.FC = ({ "", 0, { - options: allTasks - .filter((t) => t.config.guid !== task.config.guid) - .map((t) => ({ - value: t.config.guid as string, - label: t.config.name as string, - })), + options: allowedPredecessors.map((t) => ({ + value: t.config.guid as string, + label: t.config.name as string, + })), }, )}
diff --git a/src/modules/manager/workflowTemplates/components/workflowGraphUtils.ts b/src/modules/manager/workflowTemplates/components/workflowGraphUtils.ts new file mode 100644 index 0000000..fd578d9 --- /dev/null +++ b/src/modules/manager/workflowTemplates/components/workflowGraphUtils.ts @@ -0,0 +1,27 @@ +import { TaskDefinition } from "../services/WorkflowTemplateService"; + +export const getAllDescendants = ( + guid: string, + tasks: TaskDefinition[], +): Set => { + const descendants = new Set(); + + const visit = (current: string) => { + for (const t of tasks) { + const preds = (t.config.predecessors as string[]) ?? []; + + // If t depends on current, it's a child + if ( + preds.includes(current) && + !descendants.has(t.config.guid as string) + ) { + const childGuid = t.config.guid as string; + descendants.add(childGuid); + visit(childGuid); // recursively find grandchildren + } + } + }; + + visit(guid); + return descendants; +};