public IHttpActionResult GetNodePendingTasks(int id)
        {
            // id will be 0 when creating a new page - id is assigned after save
            if (id == 0)
            {
                return(Json(new
                {
                    settings = false,
                    noFlow = false
                }, ViewHelpers.CamelCase));
            }

            try
            {
                WorkflowTaskPoco currentTask = _tasksService.GetTasksByNodeId(id).FirstOrDefault();

                return(Json(new
                {
                    items = currentTask != null && currentTask.TaskStatus.In(TaskStatus.PendingApproval, TaskStatus.Rejected) ?
                            _tasksService.ConvertToWorkflowTaskList(new List <WorkflowTaskPoco> {
                        currentTask
                    }) :
                            new List <WorkflowTaskViewModel>()
                }, ViewHelpers.CamelCase));
            }
            catch (Exception ex)
            {
                string msg = Constants.ErrorGettingPendingTasksForNode.Replace("{id}", id.ToString());
                Log.Error(msg, ex);
                return(Content(HttpStatusCode.InternalServerError, ViewHelpers.ApiException(ex, msg)));
            }
        }
示例#2
0
        /// <summary>
        /// Create simple html markup for an inactive workflow task.
        /// </summary>
        /// <param name="taskInstance">The task instance.</param>
        /// <param name="index"></param>
        /// <returns>HTML markup describing an active task instance.</returns>
        public static string BuildTaskSummary(this WorkflowTaskPoco taskInstance)
        {
            var result = "";

            switch (taskInstance.Status)
            {
            case (int)TaskStatus.Approved:
            case (int)TaskStatus.Rejected:
            case (int)TaskStatus.Cancelled:

                if (taskInstance.CompletedDate != null)
                {
                    result += $"Stage {taskInstance.ApprovalStep + 1}: {taskInstance.StatusName} by {taskInstance.ActionedByUser.Name} on {taskInstance.CompletedDate.Value:dd/MM/yy}";
                }

                if (taskInstance.Comment.HasValue())
                {
                    result += $"<br/>&nbsp;&nbsp;Comment: <i>{taskInstance.Comment}</i>";
                }

                break;

            case (int)TaskStatus.NotRequired:

                result += $"Stage {taskInstance.ApprovalStep + 1}: Not required";

                break;
            }

            return(result);
        }
        /// <summary>
        /// Maps Users to the UserGroup property of a WorkflowTaskInstance
        /// </summary>
        /// <param name="task"></param>
        /// <param name="instance"></param>
        /// <param name="userGroup"></param>
        /// <returns></returns>
        public WorkflowInstancePoco MapIt(WorkflowInstancePoco instance, WorkflowTaskPoco task, UserGroupPoco userGroup)
        {
            if (instance == null)
            {
                return(_current);
            }

            if (userGroup.GroupId == task.GroupId)
            {
                task.UserGroup = userGroup;
            }

            if (_current != null && _current.Guid == instance.Guid)
            {
                if (_current.TaskInstances.All(t => t.ApprovalStep != task.ApprovalStep))
                {
                    _current.TaskInstances.Add(task);
                }
                return(null);
            }

            WorkflowInstancePoco prev = _current;

            _current = instance;
            _current.TaskInstances.Add(task);

            return(prev);
        }
示例#4
0
        public static EmailType?ProcessApproval(this WorkflowTaskPoco taskInstance, WorkflowAction action, int userId, string comment)
        {
            EmailType?emailAction = null;

            switch (action)
            {
            case WorkflowAction.Approve:
                if (taskInstance.TaskStatus != TaskStatus.NotRequired)
                {
                    taskInstance.Status = (int)TaskStatus.Approved;
                    emailAction         = EmailType.ApprovalRequest;
                }

                break;

            case WorkflowAction.Reject:
                taskInstance.Status = (int)TaskStatus.Rejected;
                emailAction         = EmailType.ApprovalRejection;

                break;
            }

            taskInstance.CompletedDate    = DateTime.Now;
            taskInstance.Comment          = comment;
            taskInstance.ActionedByUserId = userId;

            taskInstance.ActionedByAdmin = ActionedByAdmin(taskInstance, userId);

            return(emailAction);
        }
示例#5
0
        /// <summary>
        /// Set the appropriate properties to indicate the task has been cancelled
        /// </summary>
        /// <param name="taskInstance"></param>
        /// <param name="userId"></param>
        /// <param name="reason"></param>
        /// <param name="completedDate"></param>
        public static void Cancel(this WorkflowTaskPoco taskInstance, int userId, string reason, DateTime?completedDate)
        {
            taskInstance.Status           = (int)TaskStatus.Cancelled;
            taskInstance.ActionedByUserId = userId;
            taskInstance.Comment          = reason;
            taskInstance.CompletedDate    = completedDate;

            taskInstance.ActionedByAdmin = ActionedByAdmin(taskInstance, userId);
        }
示例#6
0
        /// <summary>
        /// Adds an approval task to this workflow instance, setting the approval step and instance guid
        /// </summary>
        /// <param name="instance"></param>
        public static WorkflowTaskPoco CreateApprovalTask(this WorkflowInstancePoco instance)
        {
            var taskInstance = new WorkflowTaskPoco(TaskType.Approve)
            {
                ApprovalStep         = instance.TaskInstances.Count(x => x.TaskStatus.In(TaskStatus.Approved, TaskStatus.NotRequired)),
                WorkflowInstanceGuid = instance.Guid
            };

            instance.TaskInstances.Add(taskInstance);

            return(taskInstance);
        }
示例#7
0
        public void Can_Get_Summary_String(TaskStatus status)
        {
            var taskInstance = new WorkflowTaskPoco
            {
                WorkflowInstanceGuid = Guid.NewGuid(),
                ApprovalStep         = 1,
                CreatedDate          = DateTime.Now.AddDays(-10),
                CompletedDate        = DateTime.Now,
                Comment          = Utility.RandomString(),
                Status           = (int)status,
                ActionedByUserId = 0
            };

            string summary = taskInstance.BuildTaskSummary(true);

            Assert.NotNull(summary);
        }
示例#8
0
        public void Can_Process_Task(WorkflowAction action, int userId, string comment, EmailType expected)
        {
            var taskInstance = new WorkflowTaskPoco
            {
                WorkflowInstanceGuid = Guid.NewGuid(),
                ApprovalStep         = 1,
                CreatedDate          = DateTime.Now,
                Status = (int)TaskStatus.PendingApproval
            };

            EmailType?emailType = taskInstance.ProcessApproval(action, userId, comment);

            Assert.Equal(expected, emailType.Value);

            Assert.Equal(comment, taskInstance.Comment);
            Assert.Equal(userId, taskInstance.ActionedByUserId);
        }
示例#9
0
        public void Can_Cancel_Task(int userId, string comment)
        {
            var taskInstance = new WorkflowTaskPoco
            {
                WorkflowInstanceGuid = Guid.NewGuid(),
                ApprovalStep         = 1,
                CreatedDate          = DateTime.Now.AddDays(-2),
                Status = (int)TaskStatus.PendingApproval
            };

            DateTime cancelledAt = DateTime.Now.AddDays(-1);

            taskInstance.Cancel(userId, comment, cancelledAt);

            Assert.Equal(comment, taskInstance.Comment);
            Assert.Equal(userId, taskInstance.ActionedByUserId);
            Assert.Equal(TaskStatus.Cancelled, taskInstance.TaskStatus);
        }
        public void Can_Update_Task_And_Raise_Event()
        {
            const string comment = "Comment has been updated";

            TasksService.Updated += (sender, args) =>
            {
                Assert.NotNull(args);
                Assert.IsAssignableFrom <WorkflowTaskPoco>(args.Task);
                Assert.Equal(comment, args.Task.Comment);
            };

            WorkflowTaskPoco task = Scaffold.Task();

            _service.InsertTask(task);

            task.Comment = comment;

            _service.UpdateTask(task);
        }
示例#11
0
        public async void Cannot_Validate_Request_When_Last_Task_Not_Pending()
        {
            Guid guid = Guid.NewGuid();

            const int userId = 446;
            const int nodeId = 3456;

            UserGroupPoco group = await AddGroupWithPermissionAndUser(userId, nodeId);

            // create a task on an instance
            WorkflowTaskPoco task = Scaffold.Task(guid, groupId: group.GroupId, status: (int)TaskStatus.NotRequired);

            _tasksService.InsertTask(task);
            _instancesService.InsertInstance(Scaffold.Instance(guid, 1, nodeId));

            bool isValid = await _previewService.Validate(nodeId, userId, task.Id, guid);

            Assert.False(isValid);
        }
示例#12
0
        public async void Can_Validate_Request()
        {
            Guid guid = Guid.NewGuid();

            const int userId = 11;
            const int nodeId = 1089;

            UserGroupPoco group = await AddGroupWithPermissionAndUser(userId, nodeId);

            // create a task on an instance
            WorkflowTaskPoco task = Scaffold.Task(guid, groupId: group.GroupId);

            _tasksService.InsertTask(task);
            _instancesService.InsertInstance(Scaffold.Instance(guid, 1, nodeId));

            // is valid when the user is in the group responsible for the task with the given id
            // and the task belongs to the given instance by guid
            // and both the task and instance are related to the given node id
            bool isValid = await _previewService.Validate(nodeId, userId, task.Id, guid);

            Assert.True(isValid);

            // invalid user id
            isValid = await _previewService.Validate(nodeId, 99, task.Id, guid);

            Assert.False(isValid);

            // invalid task id
            isValid = await _previewService.Validate(nodeId, userId, 11111, guid);

            Assert.False(isValid);

            // invalid guid
            isValid = await _previewService.Validate(nodeId, userId, task.Id, Guid.NewGuid());

            Assert.False(isValid);

            // invalid node id
            isValid = await _previewService.Validate(43535, userId, task.Id, guid);

            Assert.False(isValid);
        }
示例#13
0
        /// <summary>
        /// Delete from /app_plugins/workflow/preview
        /// </summary>
        /// <param name="nodeId"></param>
        /// <param name="userId"></param>
        /// <param name="taskId"></param>
        /// <param name="guid"></param>
        public async Task <bool> Validate(int nodeId, int userId, int taskId, Guid guid)
        {
            List <WorkflowTaskPoco> taskInstances = _tasksService.GetTasksByNodeId(nodeId);

            if (!taskInstances.Any() || taskInstances.Last().TaskStatus == TaskStatus.Cancelled)
            {
                return(false);
            }

            // only interested in last active task
            WorkflowTaskPoco activeTask = taskInstances.OrderBy(t => t.Id).LastOrDefault(t => t.TaskStatus.In(TaskStatus.PendingApproval, TaskStatus.Rejected));

            if (activeTask == null)
            {
                return(false);
            }

            UserGroupPoco group = await _groupService.GetPopulatedUserGroupAsync(activeTask.GroupId);

            // only valid if the task belongs to the current workflow, and the user is in the current group, and the task id is correct
            return(activeTask.WorkflowInstanceGuid == guid && group.Users.Any(u => u.UserId == userId) && activeTask.Id == taskId);
        }
示例#14
0
        public override void Up()
        {
            //Don't exeucte if the column is already there
            ColumnInfo[] columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray();

            if (columns.Any(x => x.TableName.InvariantEquals("WorkflowInstance") && x.ColumnName.InvariantEquals("CompletedDate")))
            {
                return;
            }

            // column doesn't exist, add it and populate the completed date for any existing instances

            Create.Column("CompletedDate").OnTable("WorkflowInstance").AsDateTime().Nullable();

            // once the column has been added, check for any instances where status is not active, find the last task, and set complete date to match
            // this only impacts on charting, but allows more complete history as instances didn't previously store a completion date

            List <WorkflowInstancePoco> instances = InstancesService.GetAll()
                                                    .Where(x => x.Status == (int)WorkflowStatus.Approved || x.Status == (int)WorkflowStatus.Cancelled)
                                                    .ToList();

            if (!instances.Any())
            {
                return;
            }

            foreach (WorkflowInstancePoco instance in instances)
            {
                if (!instance.TaskInstances.Any())
                {
                    continue;
                }

                WorkflowTaskPoco finalTask = instance.TaskInstances.OrderBy(x => x.Id).Last();

                instance.CompletedDate = finalTask.CompletedDate;
                Context.Database.Update(instance);
            }
        }
 /// <summary>
 ///
 /// </summary>
 /// <param name="poco"></param>
 public void UpdateTask(WorkflowTaskPoco poco)
 {
     _database.Update(poco);
 }
示例#16
0
 /// <summary>
 /// check if user is a member of the group, or is acting as an admin, then set flag
 /// If the usergroup doesn't exist, just return false - if that's the case, we have bigger problems...
 /// </summary>
 /// <param name="taskInstance"></param>
 /// <param name="userId"></param>
 /// <returns></returns>
 private static bool ActionedByAdmin(WorkflowTaskPoco taskInstance, int userId)
 {
     return(!taskInstance.UserGroup?.UsersSummary.Contains($"|{userId}|") ?? false);
 }
示例#17
0
 /// <summary>
 ///
 /// </summary>
 /// <param name="poco"></param>
 /// <returns></returns>
 public void UpdateTask(WorkflowTaskPoco poco)
 {
     _tasksRepo.UpdateTask(poco);
     Updated?.Invoke(this, new TaskEventArgs(poco));
 }
示例#18
0
 /// <summary>
 ///
 /// </summary>
 /// <param name="poco"></param>
 public void InsertTask(WorkflowTaskPoco poco)
 {
     _tasksRepo.InsertTask(poco);
     Created?.Invoke(this, new TaskEventArgs(poco));
 }
示例#19
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public WorkflowTaskViewModel GetTask(int id)
        {
            WorkflowTaskPoco task = _tasksRepo.Get(id);

            return(ConvertToWorkflowTaskList(task.AsEnumerableOfOne().ToList()).FirstOrDefault());
        }
示例#20
0
        /// <summary>
        /// Sends an email notification out for the workflow process
        /// </summary>
        /// <param name="instance"></param>
        /// <param name="emailType">the type of email to be sent</param>
        public async void Send(WorkflowInstancePoco instance, EmailType emailType)
        {
            WorkflowSettingsPoco settings = _settingsService.GetSettings();

            if (!settings.SendNotifications)
            {
                return;
            }

            if (!instance.TaskInstances.Any())
            {
                instance.TaskInstances = _tasksService.GetTasksWithGroupByInstanceGuid(instance.Guid);
            }

            if (!instance.TaskInstances.Any())
            {
                Log.Error($"Notifications not sent - no tasks exist for instance { instance.Id }");
                return;
            }

            WorkflowTaskPoco finalTask = null;

            try
            {
                string docTitle = instance.Node.Name;
                string docUrl   = UrlHelpers.GetFullyQualifiedContentEditorUrl(instance.NodeId);

                WorkflowTaskPoco[] flowTasks = instance.TaskInstances.OrderBy(t => t.ApprovalStep).ToArray();

                // always take get the emails for all previous users, sometimes they will be discarded later
                // easier to just grab em all, rather than doing so conditionally
                List <string> emailsForAllTaskUsers = new List <string>();

                // in the loop, also store the last task to a variable, and keep the populated group
                var taskIndex = 0;
                int taskCount = flowTasks.Length;

                foreach (WorkflowTaskPoco task in flowTasks)
                {
                    taskIndex += 1;

                    UserGroupPoco group = await _groupService.GetPopulatedUserGroupAsync(task.GroupId);

                    if (group == null)
                    {
                        continue;
                    }

                    emailsForAllTaskUsers.AddRange(group.PreferredEmailAddresses());
                    if (taskIndex != taskCount)
                    {
                        continue;
                    }

                    finalTask           = task;
                    finalTask.UserGroup = group;
                }

                if (finalTask == null)
                {
                    Log.Error("No valid task found for email notifications");
                    return;
                }

                List <string> to = new List <string>();

                var    body                = "";
                string typeDescription     = instance.WorkflowType.Description(instance.ScheduledDate);
                string typeDescriptionPast = instance.WorkflowType.DescriptionPastTense(instance.ScheduledDate);

                switch (emailType)
                {
                case EmailType.ApprovalRequest:
                    to   = finalTask.UserGroup.PreferredEmailAddresses();
                    body = string.Format(EmailApprovalRequestString,
                                         to.Count > 1 ? "Umbraco user" : finalTask.UserGroup.Name, docUrl, docTitle, instance.AuthorComment,
                                         instance.AuthorUser.Name, typeDescription, string.Empty);
                    break;

                case EmailType.ApprovalRejection:
                    to = emailsForAllTaskUsers;
                    to.Add(instance.AuthorUser.Email);
                    body = string.Format(EmailRejectedString,
                                         "Umbraco user", docUrl, docTitle, finalTask.Comment,
                                         finalTask.ActionedByUser.Name, typeDescription.ToLower());

                    break;

                case EmailType.ApprovedAndCompleted:
                    to = emailsForAllTaskUsers;
                    to.Add(instance.AuthorUser.Email);

                    //Notify web admins
                    to.Add(settings.Email);

                    if (instance.WorkflowType == WorkflowType.Publish)
                    {
                        IPublishedContent n = _utility.GetPublishedContent(instance.NodeId);
                        docUrl = UrlHelpers.GetFullyQualifiedSiteUrl(n.Url);
                    }

                    body = string.Format(EmailApprovedString,
                                         "Umbraco user", docUrl, docTitle,
                                         typeDescriptionPast.ToLower()) + "<br/>";

                    body += instance.BuildProcessSummary();

                    break;

                case EmailType.ApprovedAndCompletedForScheduler:
                    to = emailsForAllTaskUsers;
                    to.Add(instance.AuthorUser.Email);

                    body = string.Format(EmailApprovedString,
                                         "Umbraco user", docUrl, docTitle,
                                         typeDescriptionPast.ToLower()) + "<br/>";

                    body += instance.BuildProcessSummary();

                    break;

                case EmailType.WorkflowCancelled:
                    to = emailsForAllTaskUsers;

                    // include the initiator email
                    to.Add(instance.AuthorUser.Email);

                    body = string.Format(EmailCancelledString,
                                         "Umbraco user", typeDescription, docUrl, docTitle, finalTask.ActionedByUser.Name, finalTask.Comment);
                    break;

                case EmailType.SchedulerActionCancelled:
                    break;

                default:
                    throw new ArgumentOutOfRangeException(nameof(emailType), emailType, null);
                }

                if (!to.Any())
                {
                    return;
                }

                var client = new SmtpClient();
                var msg    = new MailMessage
                {
                    Subject    = $"{emailType.ToString().ToTitleCase()} - {instance.Node.Name} ({typeDescription})",
                    IsBodyHtml = true,
                };

                if (settings.Email.HasValue())
                {
                    msg.From = new MailAddress(settings.Email);
                }

                // if offline is permitted, email group members individually as we need the user id in the url
                if (emailType == EmailType.ApprovalRequest && finalTask.UserGroup.OfflineApproval)
                {
                    foreach (User2UserGroupPoco user in finalTask.UserGroup.Users)
                    {
                        string offlineString = string.Format(EmailOfflineApprovalString, settings.SiteUrl, instance.NodeId,
                                                             user.UserId, finalTask.Id, instance.Guid);

                        body = string.Format(EmailApprovalRequestString,
                                             user.User.Name, docUrl, docTitle, instance.AuthorComment,
                                             instance.AuthorUser.Name, typeDescription, offlineString);

                        msg.To.Clear();
                        msg.To.Add(user.User.Email);
                        msg.Body = string.Format(EmailBody, msg.Subject, body);

                        client.Send(msg);
                    }
                }
                else
                {
                    msg.To.Add(string.Join(",", to.Distinct()));
                    msg.Body = string.Format(EmailBody, msg.Subject, body);

                    client.Send(msg);
                }

                Log.Info($"Email notifications sent for task { finalTask.Id }, to { msg.To }");
            }
            catch (Exception e)
            {
                Log.Error($"Error sending notifications for task { finalTask.Id }", e);
            }
        }
示例#21
0
        /// <summary>
        /// Sends an email notification out for the workflow process
        /// </summary>
        /// <param name="instance"></param>
        /// <param name="emailType">the type of email to be sent</param>
        /// <param name="errorDetail"></param>
        public async Task <string> Send(WorkflowInstancePoco instance, EmailType emailType, string errorDetail = "")
        {
            var msg = new MailMessage();

            if (!_settings.SendNotifications)
            {
                return(null);
            }

            if (!instance.TaskInstances.Any())
            {
                instance.TaskInstances = _tasksService.GetTasksWithGroupByInstanceGuid(instance.Guid);
            }

            if (!instance.TaskInstances.Any())
            {
                Log.Error($"Notifications not sent - no tasks exist for instance { instance.Id }");
                return(null);
            }

            try
            {
                WorkflowTaskPoco[] flowTasks = instance.TaskInstances.OrderBy(t => t.ApprovalStep).ToArray();

                // always take get the emails for all previous users, sometimes they will be discarded later
                // easier to just grab em all, rather than doing so conditionally
                List <string> emailsForAllTaskUsers = new List <string>();

                // in the loop, also store the last task to a variable, and keep the populated group
                var taskIndex = 0;
                int taskCount = flowTasks.Length;

                foreach (WorkflowTaskPoco task in flowTasks)
                {
                    taskIndex += 1;

                    UserGroupPoco group = await _groupService.GetPopulatedUserGroupAsync(task.GroupId);

                    if (group == null)
                    {
                        continue;
                    }

                    emailsForAllTaskUsers.AddRange(group.PreferredEmailAddresses());
                    if (taskIndex != taskCount)
                    {
                        continue;
                    }

                    _finalTask           = task;
                    _finalTask.UserGroup = group;
                }

                if (_finalTask == null)
                {
                    Log.Error("No valid task found for email notifications");
                    return(null);
                }

                // populate list of recipients
                List <string> to = GetRecipients(emailType, instance, emailsForAllTaskUsers);
                if (!to.Any())
                {
                    return(null);
                }

                string body = GetBody(emailType, instance, out string typeDescription, errorDetail);

                var client = new SmtpClient();

                msg = new MailMessage
                {
                    Subject    = $"{emailType.ToString().ToTitleCase()} - {instance.Node.Name} ({typeDescription})",
                    IsBodyHtml = true,
                    Body       = string.Format(EmailBody, msg.Subject, body)
                };

                if (_settings.Email.HasValue())
                {
                    msg.From = new MailAddress(_settings.Email);
                }

                // if offline is permitted, email group members individually as we need the user id in the url
                if (emailType == EmailType.ApprovalRequest && _finalTask.UserGroup.OfflineApproval)
                {
                    string docTitle = instance.Node.Name;
                    string docUrl   = UrlHelpers.GetFullyQualifiedContentEditorUrl(instance.NodeId);

                    foreach (User2UserGroupPoco user in _finalTask.UserGroup.Users)
                    {
                        var msgBody = body + string.Format(EmailOfflineApprovalString, _settings.SiteUrl, instance.NodeId,
                                                           user.UserId, _finalTask.Id, instance.Guid);

                        msg.Body = string.Format(EmailBody, msg.Subject, msgBody);

                        msg.To.Clear();
                        msg.To.Add(user.User.Email);

                        client.Send(msg);
                    }
                }
                else
                {
                    msg.To.Add(string.Join(",", to.Distinct()));
                    client.Send(msg);
                }

                Log.Info($"Email notifications sent for task { _finalTask.Id }, to { msg.To }");
            }
            catch (Exception e)
            {
                Log.Error($"Error sending notifications for task { _finalTask.Id }", e);
            }

            return(msg.Body);
        }
 public TaskEventArgs(WorkflowTaskPoco task)
 {
     Task = task;
 }
 /// <summary>
 ///
 /// </summary>
 /// <param name="poco"></param>
 public void InsertTask(WorkflowTaskPoco poco)
 {
     _database.Insert(poco);
 }