public Task <GraphNode <Key, FormNode> > Handle(GraphNode <Key, FormNode> node) { var nodeAsRouter = node.AssertType <SubTaskRouterFormNode>(); var subTaskItems = node.Neighbors.Where(GraphNodePredicates.IsSubTaskItemRouterNode); var preSubTaskNode = node.Neighbors.First(GraphNodePredicates.IsPreSubTaskNode); var stopKey = Key.ForPostSubTaskPage(nodeAsRouter.TaskId, nodeAsRouter.SubTaskId, nodeAsRouter.RepeatIndices); var hasUnansweredItems = false; var hasAnsweredItems = false; var traversal = new GraphTraversalBuilder <Key, FormNode>(TraversalType.BreadthFirstSearch, false) .WithOnNodeDequeuedHandler((props, output) => { if (props.Node.Key.Equals(stopKey)) { output.Skip = true; return; } if (props.Node.Value.IsDecisionNode) { return; } if (!props.Node.IsTaskQuestionPageNode(out var questionNode)) { return; } if (questionNode.PageStatus == PageStatus.Unanswered) { hasUnansweredItems = true; output.IsComplete = true; return; } hasAnsweredItems = true; }).Build(); foreach (var subTask in subTaskItems) { hasUnansweredItems = false; hasAnsweredItems = false; traversal.Run(subTask); if (hasUnansweredItems) { return(Task.FromResult(hasAnsweredItems ? subTask : preSubTaskNode)); } } return(Task.FromResult(node.Neighbors.First(GraphNodePredicates.IsPostSubTaskNode))); }
private bool IsTaskComplete(GraphNode <Key, FormNode> taskRouter) { taskRouter.AssertType <TaskRouterFormNode>(); // We assume all of our pages are complete and traverse each task item until we find a page which is incomplete var hasUnansweredItem = false; var traversal = new GraphTraversalBuilder <Key, FormNode>(TraversalType.BreadthFirstSearch, false) .WithOnNodeDequeuedHandler((node, output) => { if (!node.Node.IsTaskQuestionPageNode(out var taskQuestionPageFormNode)) { return; } if (taskQuestionPageFormNode.PageStatus == PageStatus.Answered) { return; } // If we found an unanswered page then we're finished hasUnansweredItem = true; output.IsComplete = true; }) .WithOnNodeDiscoveredHandler((props, output) => { // We don't need to queue past the summary if (props.Node.IsAnyTaskSummaryNode()) { output.ShouldQueue = false; } }) .Build(); var taskItemRouters = taskRouter.Neighbors.Where(GraphNodePredicates.IsTaskItemRouterNode); foreach (var taskItem in taskItemRouters) { traversal.Run(taskItem); if (hasUnansweredItem) { break; } } return(!hasUnansweredItem); }
public async Task TraversalTest() { var repo = new Mock <IFormRepository>(); var provider = new Mock <IStaticFormProvider>(); var service = new FormService(repo.Object, provider.Object); var form = await service.InitialiseForm("test", FormType.Test); var taskNodeToDebug = form.Nodes.FindByKey(Key.ForDecisionTaskRouter("last-task")); List <string> relationships = new List <string>(); var traversal = new GraphTraversalBuilder <Key, FormNode>(TraversalType.BreadthFirstSearch, true) .WithOnNodeDequeuedHandler((props, o) => { if (props.Node.Value is TaskListFormNode) { o.Skip = true; } }) .WithOnEdgeDiscoveredHandler((props, o) => { if (props.Cost == 1) { relationships.Add($"{GetFriendlyNodeNameForType(props.FromNode.Value)} {GetFriendlyNodeNameForType(props.ToNode.Value)}"); } if (props.Cost > 1) { o.ShouldQueue = false; } }) .Build(); var result = traversal.Run(taskNodeToDebug); var builder = new StringBuilder(); foreach (var r in relationships) { builder.Append(r); builder.AppendLine(); } var output = builder.ToString(); }
public async Task <string> DeleteSubTaskItem(FormType formType, string formKey, string subTaskNode, string subTaskItemNode) { var form = await _formRepository.GetForm(formKey); var subTaskRouterNode = form.Nodes.FindByKey(new Key(subTaskNode)); var subTaskRouter = subTaskRouterNode.AssertType <SubTaskRouterFormNode>(); var subTaskItemRouterNode = subTaskRouterNode.Neighbors.FindByKey(new Key(subTaskItemNode)); var subTaskItemRouter = subTaskItemRouterNode.AssertType <SubTaskItemRouterFormNode>(); var postSubTaskNode = subTaskRouterNode.Neighbors.FindByKey(Key.ForPostSubTaskPage(subTaskRouter.TaskId, subTaskRouter.SubTaskId, subTaskRouter.RepeatIndices)); var traversal = new GraphTraversalBuilder <Key, FormNode>(TraversalType.BreadthFirstSearch, true) .WithOnNodeDiscoveredHandler((props, output) => { // We do not want the PostSubTaskNode for our sub task or anything beyond it to be deleted so we prevent queueing it if (props.Node.Key.Equals(postSubTaskNode.Key)) { output.ShouldQueue = false; } }) .Build(); var traversalResult = traversal.Run(subTaskItemRouterNode); foreach (var(traversedNode, _) in traversalResult.TraversalOrder) { form.Remove(traversedNode); } subTaskRouter.RemoveSubTaskItem(subTaskItemRouter.RepeatIndex); var nextNodeId = postSubTaskNode.Key.Value; // If the last SubTask has been deleted we need to ensure an empty Item is added if (!subTaskRouter.TaskItemIds.Any()) { var subTask = _staticFormProvider.GetSubTask(formType, StaticKey.ForSubTask(subTaskRouter.TaskId, subTaskRouter.SubTaskId)); var nextId = subTaskRouter.AddSubTaskItem(); var preSubTaskNode = subTaskRouterNode.Neighbors.First(x => x.Value is PreSubTaskFormNode); var newNodes = AddSubTaskItem(form, subTaskRouter.TaskId, subTask, subTaskRouter.RepeatIndices, nextId, subTaskRouterNode, preSubTaskNode, postSubTaskNode); // Add any required edges for new nodes foreach (var(_, (node, nextKey)) in newNodes) { if (nextKey is null) { continue; } if (nextKey.Value == Key.EmptyKey().Value) { continue; } var(nextNode, _) = newNodes[nextKey]; form.AddDirectedEdge(node, nextNode); } // Navigate the user to the new sub task item instead of the PostSubTask page nextNodeId = preSubTaskNode.Key.Value; } await _formRepository.SaveForm(formKey, form); return(nextNodeId); }
public async Task <string> DeleteTaskItem(FormType formType, string formKey, string taskNodeId, string taskItemNodeId) { var form = await _formRepository.GetForm(formKey); var taskRouterNode = form.Nodes.FindByKey(new Key(taskNodeId)); var taskRouter = taskRouterNode.AssertType <TaskRouterFormNode>(); var taskItemRouterNode = taskRouterNode.Neighbors.FindByKey(new Key(taskItemNodeId)); var taskItemRouter = taskItemRouterNode.AssertType <TaskItemRouterFormNode>(); var taskItemSummaryNodeKey = Key.ForTaskSummary(taskItemRouter.TaskId, taskItemRouter.RepeatIndex); var postTaskNode = taskRouterNode.Neighbors.FindByKey(Key.ForPostTaskPage(taskItemRouter.TaskId)); var traversal = new GraphTraversalBuilder <Key, FormNode>(TraversalType.BreadthFirstSearch, true) .WithOnNodeDequeuedHandler((props, output) => { // Anything beyond the summary we do not need to delete so we skip visiting the node if (props.Node.Key.Equals(taskItemSummaryNodeKey)) { output.Skip = true; } }) .WithOnNodeDiscoveredHandler((props, output) => { // Prevent queuing the PostTaskNode as we do not want it included in the traversal if (props.Node.Key.Equals(postTaskNode.Key)) { output.ShouldQueue = false; } }) .Build(); var traversalResult = traversal.Run(taskItemRouterNode); foreach (var(traversedNode, _) in traversalResult.TraversalOrder) { form.Remove(traversedNode); } taskRouter.RemoveTaskItem(taskItemRouter.RepeatIndex); var nextNodeId = postTaskNode.Key.Value; // If the last Task has been deleted we need to ensure an empty Item is added if (!taskRouter.TaskItemIds.Any()) { var task = _staticFormProvider.GetTask(formType, StaticKey.ForTaskNode(taskRouter.TaskId)); var nextId = taskRouter.AddTaskItem(); var preTaskNode = taskRouterNode.Neighbors.First(x => x.Value is PreTaskFormNode || x.Value is PreTaskGhost); var newNodes = AddTaskItem(form, taskRouterNode, preTaskNode, postTaskNode, task, nextId); // Add any required edges for new nodes foreach (var(_, (node, nextKey)) in newNodes) { if (nextKey is null) { continue; } if (nextKey.Value == Key.EmptyKey().Value) { continue; } var(nextNode, _) = newNodes[nextKey]; form.AddDirectedEdge(node, nextNode); } // Navigate the user to the new task item instead of the PostTask page nextNodeId = preTaskNode.Key.Value; } await _formRepository.SaveForm(formKey, form); return(nextNodeId); }
public Task <GraphNode <Key, FormNode> > Handle(GraphNode <Key, FormNode> node) { node.AssertType <TaskRouterFormNode>(); var preTaskNode = node.Neighbors.First(GraphNodePredicates.IsAnyPreTaskNode); var postTaskNode = node.Neighbors.First(GraphNodePredicates.IsAnyPostTaskNode); var taskItemRouters = node.Neighbors.Where(GraphNodePredicates.IsTaskItemRouterNode) .OrderBy(x => x.IsTaskItemRouterNode(out var taskItem) ? taskItem.RepeatIndex : 0).ToList(); var hasAnsweredItems = false; GraphNode <Key, FormNode> summaryNode = null; var traversal = new GraphTraversalBuilder <Key, FormNode>(TraversalType.BreadthFirstSearch, true) .WithOnNodeDequeuedHandler((props, output) => { if (props.Node.Value.IsDecisionNode) { return; } if (props.Node.IsAnyTaskSummaryNode()) { summaryNode = props.Node; output.Skip = true; return; } if (!props.Node.IsTaskQuestionPageNode(out var taskPageNode)) { return; } if (taskPageNode.PageStatus == PageStatus.Unanswered) { output.IsComplete = true; return; } hasAnsweredItems = true; }) .WithOnEdgeDiscoveredHandler((props, output) => { if (props.Cost == 1) { return; } if (props.Cost == EdgeCosts.SubTaskToPostSubTaskCost) { if (props.FromNode.IsSubTaskRouterNode() && props.ToNode.IsPostSubTaskNode()) { output.ShouldQueue = false; } } }) .Build(); // Run the traversal on each of our TaskItems foreach (var router in taskItemRouters) { hasAnsweredItems = false; summaryNode = null; var result = traversal.Run(router); // Found an unanswered node if (result.Result == null) { continue; } // We had no answered questions and only one repeat (the user has never visited the task) if (!hasAnsweredItems && taskItemRouters.Count == 1) { return(Task.FromResult(preTaskNode)); } // The user has answered some questions but not all return(Task.FromResult(router)); } // All items were answered return(postTaskNode.Value.IsDecisionNode ? Task.FromResult(summaryNode) : Task.FromResult(postTaskNode)); }