static string CreateActionLink(DisplayFormUserTask task) { var baseAddress = ConfigurationSettings.GetSiteConfigurationSection().SiteSettings.Address; var tenantID = RequestContext.TenantId; string tenantName; using (new AdministratorContext()) { tenantName = Entity.Get <Tenant>(tenantID, Tenant.Name_Field).Name; } string url; if (task.RecordToPresent != null) { url = string.Format(ActionLinkWithRecordFormat, baseAddress, tenantName, task.RecordToPresent.Id, task.Id); } else { url = string.Format(ActionLinkFormat, baseAddress, tenantName, task.Id); } if (task.FormToUse != null) { url += "&formId=" + task.FormToUse.Id; } return(url); }
private void ActOnTask(DisplayFormUserTask task, TransitionStart userResponse, List <Action> actions) { //TODO: Serialize the workflow runs like the trigger on save. var workflowRun = task.WorkflowRunForTask; if ((task.UserTaskIsComplete ?? false) && (workflowRun != null) && (workflowRun.WorkflowRunStatus_Enum == WorkflowRunState_Enumeration.WorkflowRunPaused)) { var resumeEvent = new UserCompletesTaskEvent { CompletionStateId = userResponse.Id, UserTaskId = task.Id }; var runId = workflowRun.Id; var runInThread = WorkflowRunContext.Current.RunTriggersInCurrentThread; Action act; if (Factory.FeatureSwitch.Get("longRunningWorkflow")) { act = () => ActionOnTask_new(runId, resumeEvent, runInThread); } else { act = () => ActionOnTask_old(runId, resumeEvent, runInThread); } WorkflowRunContext.Current.DeferAction(act); } }
// [Ignore("Failing on first run in test environment.")] public void TestWorkflowRunOnReverseRelationshipUpdate() { using (new WorkflowRunContext { RunTriggersInCurrentThread = true }) { var ageField = GetPersonField("Age"); var titleRel = GetTitleRel(); var myType = CreateMyType("Type_TestWorkflowRunOnUpdate"); Workflow wf = CreateTestWorkflow("TestWorkflowRunOnRelationshipUpdate " + DateTime.Now, "990"); var updateTrigger = new WfTriggerUserUpdatesResource { WorkflowToRun = wf, // ReSharper disable SpecifyACultureInStringConversionExplicitly Name = "Test" + DateTime.Now.ToString(), // ReSharper restore SpecifyACultureInStringConversionExplicitly TriggeringCondition_Enum = TriggeredOnEnum_Enumeration.TriggeredOnEnumUpdate, TriggerEnabled = true }; var task = new DisplayFormUserTask(); var relToUpdate = Entity.Get <Relationship>("core:recordToPresent"); updateTrigger.UpdatedRelationshipsToTriggerOn.Add(relToUpdate); // a reverse relationship updateTrigger.TriggeredOnType = myType; updateTrigger.Save(); ToDelete.Add(updateTrigger.Id); ToDelete.Add(wf.Id); EntityRef employee = CreateResource(myType); var savedEmployee = Entity.Get(employee); var age = savedEmployee.GetField <int?>(ageField); Assert.AreEqual(null, age, "Age has been not been set by workflow"); var rels = savedEmployee.GetRelationships(relToUpdate); rels.Add(task); using (new WorkflowRunContext(true)) using (var ctx = DatabaseContext.GetContext(true, preventPostSaveActionsPropagating: true)) { savedEmployee.Save(); ctx.CommitTransaction(); } savedEmployee = Entity.Get(employee); var age1 = savedEmployee.GetField <int?>(ageField); Assert.That(age1, Is.EqualTo(990), "The update has not occurred and has not triggered the workflow."); } }
/// <summary> /// Notify the user that there is a task waiting /// </summary> /// <param name="userAccount"></param> public static void NotifyUser(this DisplayFormUserTask task) { throw new NotImplementedException("This code needs to be refactored once a decision is made about the structure of email addresses has been made."); /* * /* * if (task.AssignedToUser != null) * { * string emailAddress = string.Empty; * var emailContacts = task.AssignedToUser.PersonHasEmailContact; * if (emailContacts != null) * { * var defaultEmailContact = emailContacts.FirstOrDefault(ec => ec.EmailContactIsDefault ?? false); * if (defaultEmailContact != null) * { * emailAddress = defaultEmailContact.Name; * } * } * * if (!String.IsNullOrEmpty(emailAddress)) * { * var subject = task.Name ?? DefaultSubject; * * var body = string.Format(MessageFormat, (task.Description), CreateActionLink(task)); //TODO: consider if this needs to be encoded * * var emailSettings = Entity.Get<TenantEmailSetting>("tenantEmailSettingsInstance", TenantEmailSetting.AllFields); * * if (emailSettings.TesApprovalsInbox != null && emailSettings.TesApprovalsInbox.UsesInboxProvider != null) * { * var mailBox = emailSettings.TesApprovalsInbox; * var provider = mailBox.UsesInboxProvider; * var tenantName = RequestContext.GetContext().Tenant.Name; * * var sentMessage = new SentEmailMessage() * { * EmTo = emailAddress, * EmFrom = mailBox.InboxEmailAddress, * EmSubject = subject, * EmBody = body, * EmIsHtml = true, * EmSentDate = DateTime.UtcNow, * SentFromInbox = mailBox, * RelatedTask = task.As<DisplayFormUserTask>(), * }; * * sentMessage.Save(); * * var inboxProviderHelper = provider.GetHelper(); * inboxProviderHelper.SendMessages(sentMessage.ToMailMessage().ToEnumerable(), tenantName, mailBox.Name); * } * } * } */ }
/// <summary> /// Process an approval /// </summary> /// <param name="taskId">The task to approve</param> /// <param name="selectedOption">the name of the transition that has been selected</param> /// <param name="preSaveAction">An optional presave action</param> public static void ProcessApproval(long taskId, string selectedOption, Action <DisplayFormUserTask> preSaveAction = null) { DisplayFormUserTask task = Entity.Get <DisplayFormUserTask>( taskId, true, DisplayFormUserTask.UserResponse_Field, BaseUserTask.UserTaskCompletedOn_Field, DisplayFormUserTask.RelatedMessages_Field); var transitions = task.WorkflowRunForTask.PendingActivity.ForwardTransitions; var selectedTransition = transitions.First( t => String.Equals(t.FromExitPoint.Name, selectedOption, StringComparison.OrdinalIgnoreCase)); ProcessApproval(task, selectedTransition, preSaveAction); }
/// <summary> /// Process an approval /// </summary> /// <param name="taskId">The task to approve</param> /// <param name="selectedOption">Teh selected transition</param> /// <param name="preSaveAction">An optional presave action</param> public static void ProcessApproval(DisplayFormUserTask task, TransitionStart selectedOption, Action <DisplayFormUserTask> preSaveAction = null) { task = task.AsWritable <DisplayFormUserTask>(); task.UserResponse = selectedOption; task.UserTaskCompletedOn = DateTime.UtcNow; // Leave this for the Before save hook, it's currently the only way to know what has changed. task.TaskStatus_Enum = TaskStatusEnum_Enumeration.TaskStatusCompleted; if (preSaveAction != null) { preSaveAction(task); } task.Save(); }
void UpdateLogEntry(DisplayFormUserTask userTask) { var logEntry = userTask.LogEntryForUserAction; var exitPoint = userTask.UserResponse.FromExitPoint; var actionSummary = !string.IsNullOrWhiteSpace(exitPoint.ExitPointActionSummary) ? exitPoint.ExitPointActionSummary: exitPoint.Name; var writable = logEntry.AsWritable <WorkflowUserActionLogEntry>(); writable.WuleCompletedDate = userTask.UserTaskCompletedOn; writable.WuleUserComment = userTask.TaskComment; writable.WuleActionSummary = actionSummary; writable.Description += string.Format(", Completed: {0}, Action: {1}", userTask.UserTaskCompletedOn, actionSummary); writable.Save(); }
void CreateLogEntry(IRunState context, DisplayFormUserTask userTask) { var actingPersonName = userTask.AssignedToUser != null ? userTask.AssignedToUser.Name : null; // Create the log entry context.Log(new WorkflowUserActionLogEntry { Name = ActivityInstance.Name, LogEventTime = DateTime.UtcNow, UserActionBeingLogged = userTask, WuleDueDate = userTask.UserTaskDueOn, ActingPersonReferencedInLog = userTask.AssignedToUser, ActingPersonName = actingPersonName, ObjectReferencedInLog = userTask.RecordToPresent, Description = string.Format("Assigned to: {0}, Due: {1}", actingPersonName ?? "", userTask.UserTaskDueOn) }); }
/// <summary> /// Generate a page to show on completing a task. /// </summary> /// <param name="select"></param> /// <returns></returns> public string GenerateCompletedPage(DisplayFormUserTask task) { var exitPoint = task.UserResponse.FromExitPoint; var sb = new StringBuilder(); GeneratePageStart(sb, task, "Completed:"); var display = exitPoint.ExitPointActionSummary ?? exitPoint.Name; sb.Append("<p>"); sb.Append(display); sb.Append("<p>"); GeneratePageEnd(sb); return(sb.ToString()); }
private static void GeneratePageStart(StringBuilder sb, DisplayFormUserTask task, string intro) { sb.Append(@" <html> <head> <meta name='viewport' content='width=device-width, initial-scale=1'> <link rel='stylesheet' type='text/css' href='assets/SoftwarePlatform.client.css' /> </head> <body style='font-size:100%'> "); sb.Append(intro); sb.Append("<p>"); sb.Append(HttpUtility.HtmlEncode(task.Name)); if (task.RecordToPresent != null) { sb.Append("<p>"); sb.Append(task.RecordToPresent.Name); } }
/// <summary> /// Given a token and the options to present, generate an appropriate html page /// </summary> /// <param name="task">The task to render</param> /// <param name="token">The task token</param> /// <returns></returns> public string GenerateSelectionPage(DisplayFormUserTask task) { var taskToken = task.DfutLinkToken; var options = task.AvailableTransitions.Select(t => t.FromExitPoint.Name); var sb = new StringBuilder(); GeneratePageStart(sb, task, "User action required for:"); sb.Append("<p><ul>"); foreach (var option in options) { sb.Append("<li>"); sb.Append(GenerateSelectionAnchor(taskToken, option)); sb.Append("</li>"); } GeneratePageEnd(sb); return(sb.ToString()); }
private const decimal DefaultTimeOutMins = 0; // no time out public override bool OnStart(IRunState context, ActivityInputs inputs) { var assignedTo = GetArgumentEntity <Person>(inputs, "inDisplayFormForUser"); var recordToPresent = GetArgumentEntity <UserResource>(inputs, "inDisplayFormResource"); var form = GetArgumentEntity <CustomEditForm>(inputs, "inDisplayFormForm"); var timeoutDays = GetArgumentValue <decimal>(inputs, "inDisplayFormTimeOut", DefaultTimeOutMins); var priority = GetArgumentEntity <EventEmailPriorityEnum>(inputs, "inDisplayFormPriority"); var activityInstanceAs = ActivityInstance.Cast <DisplayFormActivity>(); var percentageCompleted = GetArgumentValue <decimal?>(inputs, "inDisplayFormPercentageCompleted", null); var waitForNext = GetArgumentValue <bool>(inputs, "inDisplayFormWaitForNext", false); var recordHistory = GetArgumentValue <bool>(inputs, "inDisplayFormRecordHistory", false); var hideComment = GetArgumentValue <bool>(inputs, "inHideComment", false); var openInEditMode = GetArgumentValue <bool>(inputs, "inOpenInEditMode", false); priority = priority ?? Entity.Get <EventEmailPriorityEnum>(new EntityRef("core", "normalPriority")); var workflowRun = context.WorkflowRun; var dueDate = DateTime.UtcNow.AddDays((double)timeoutDays); var userTask = new DisplayFormUserTask { Name = ActivityInstance.Name ?? DefaultTitle, RecordToPresent = recordToPresent, FormToUse = form, AvailableTransitions = GetAvailableUserTransitions(), AssignedToUser = assignedTo, TaskPriority = priority, TaskStatus_Enum = TaskStatusEnum_Enumeration.TaskStatusNotStarted, PercentageCompleted = percentageCompleted, WaitForNextTask = waitForNext, UserTaskDueOn = dueDate, HideComment = hideComment, OpenInEditMode = openInEditMode, DfutLinkToken = CryptoHelper.GetRandomPrintableString(8) }; context.SetUserTask(userTask.Cast <BaseUserTask>()); if (recordHistory) { CreateLogEntry(context, userTask); } SetTimeoutIfNeeded(context, timeoutDays); var tenantSetting = Entity.Get <TenantGeneralSettings>(WellKnownAliases.CurrentTenant.TenantGeneralSettingsInstance); if (Factory.FeatureSwitch.Get("enableWfUserActionNotify")) { // // IN PROGRESS - Please leave // This code is in development and switched off until email and SMS approvals are required by PM. // Notifier notifier = null; // tenantSetting.UserActionNotifier; if (notifier != null) { // TODO: Format correctly for where it is being sent SMS email etc. Move the decision out of here and make the notfier decide on the type of message var generator = new HtmlGenerator(); string message = null; if (notifier.Is <EmailNotifier>()) // TODO: This is wrong, it should be somehow tied to the Router { var transitionOptions = userTask.AvailableTransitions.Select(t => t.FromExitPoint.Name).Where(n => !String.IsNullOrEmpty(n)); if (transitionOptions.Any()) { message = generator.GenerateSelectionPage(userTask); } } else if (notifier.Is <TwilioNotifier>()) { message = generator.GenerateSelectionPageUrl(userTask.DfutLinkToken); } if (message != null) { var userList = userTask.AssignedToUser.ToEnumerable(); var notification = new Notification { NMessage = message }; //TOOD: Add alternative text in the email with a link to the SMS page NotificationRouter.Instance.Send(notifier, notification, userList, false); } } } context.SetArgValue(ActivityInstance, GetArgumentKey("core:outDisplayFormUserTask"), userTask); context.SetArgValue(ActivityInstance, GetArgumentKey("core:dfaInternalKeepHistory"), recordHistory); return(false); }
//[Category( "ExtendedTests" )] public void TestWithWorkflowContext() { using (new WorkflowRunContext { RunTriggersInCurrentThread = true }) { Workflow myWorkflow = null; UserAccount myUser = null; Person myPerson = null; DisplayFormUserTask task1 = null; DisplayFormUserTask task2 = null; try { myWorkflow = Entity.Create <Workflow>() .AddDefaultExitPoint() .AddDisplayForm("Display Form 1", new string[] { "Exit1" }, null, null, null) .AddLog("First Log", "Log 1") .AddDisplayForm("Display Form 2", new string[] { "Exit1" }, null, null, null) .AddLog("Seond Log", "Log 2"); myWorkflow.Name = "UNNUMBERED_DianasSecurityProblem " + DateTime.Now; myWorkflow.WorkflowRunAsOwner = true; myWorkflow.Save(); ToDelete.Add(myWorkflow.Id); var myType = CreateType("CreateTriggerAddsAndRemovesTypeHook_type", UserResource.UserResource_Type); var myTrigger = CreateTrigger("CreateTriggerAddsAndRemovesTypeHook_trigger", myType, myWorkflow); myType.AsWritable(); myType.Name = "Creatable"; myType.IsOfType.Add(Resource.Resource_Type); myType.Save(); myPerson = Entity.Create <Person>(); myPerson.FirstName = "John"; myPerson.LastName = "Bob"; myUser = Entity.Create <UserAccount>(); myUser.Name = "Bob" + DateTime.Now; myUser.AccountHolder = myPerson; myUser.Save(); ToDelete.Add(myUser.Id); new AccessRuleFactory().AddAllowByQuery(myUser.As <Subject>(), Resource.Resource_Type.As <SecurableEntity>(), new EntityRef("core:create").ToEnumerable(), TestQueries.EntitiesWithName("Creatable").ToReport()); using (new SetUser(myUser)) using (new WorkflowRunContext(true)) { var e = Entity.Create(myType).As <Resource>(); e.Name = "MyName"; e.CreatedDate = DateTime.Now; e.CreatedBy = myUser; e.SecurityOwner = myUser; e.Save(); ToDelete.Add(e.Id); } Workflow wf; WorkflowRun run1; wf = Entity.Get <Workflow>(myWorkflow.Id); run1 = wf.RunningInstances.First(); Assert.That(run1, Is.Not.Null); task1 = run1.TaskWithinWorkflowRun.Select(t => t.As <DisplayFormUserTask>()).FirstOrDefault(t => t != null); Assert.That(task1, Is.Not.Null); task1.AssignedToUser.Should().NotBeNull(); task1.AssignedToUser.Id.Should().Be(myPerson.Id); using (new SetUser(myUser)) using (new WorkflowRunContext(true)) { var writableTask = task1.AsWritable <DisplayFormUserTask>(); writableTask.UserResponse = task1.AvailableTransitions.First(t => t.Name == "Exit1"); writableTask.TaskStatus_Enum = TaskStatusEnum_Enumeration.TaskStatusCompleted; writableTask.Save(); } Assert.IsTrue(WaitForWorkflowToStop(run1), "Workflow run should have completed."); wf = Entity.Get <Workflow>(wf.Id); var run2 = wf.RunningInstances.First(); Assert.That(run2, Is.Not.Null); task2 = run2.TaskWithinWorkflowRun.First(t => t.Id != task1.Id).As <DisplayFormUserTask>(); Assert.That(task2, Is.Not.Null); using (new SetUser(myUser)) using (new WorkflowRunContext(true)) { var writableTask = task2.AsWritable <DisplayFormUserTask>(); writableTask.UserResponse = writableTask.AvailableTransitions.First(t => t.Name == "Exit1"); writableTask.TaskStatus_Enum = TaskStatusEnum_Enumeration.TaskStatusCompleted; writableTask.Save(); } Assert.IsTrue(WaitForWorkflowToStop(run2), "Workflow run should have completed."); } finally { if (task1 != null) { ToDelete.Add(task1.Id); } if (task2 != null) { ToDelete.Add(task2.Id); } if (myWorkflow != null) { ToDelete.Add(myWorkflow.Id); } if (myUser != null) { ToDelete.Add(myUser.Id); } if (myPerson != null) { ToDelete.Add(myPerson.Id); } } } }
public void TestDisplayFormResumeWithPromptActivity_Bug_27060() { using (new WorkflowRunContext { RunTriggersInCurrentThread = true }) { Workflow wf = null; DisplayFormUserTask task = null; PromptUserTask p = null; try { WorkflowRun run, run2, run3; // Arrange var cheese = new EntityRef("test", "cheese"); var cheeses = Entity.GetInstancesOfType(cheese); // // IMPORTANT: the point is to NOT set a value on the variable for this test! // the run state serialization that occurs when pausing must realize that the variable is used // subsequently. // wf = Entity.Create <Workflow>() .AddDefaultExitPoint() .AddVariable <ResourceArgument>("myVar", null, Entity.Get <EntityType>(new EntityRef("test", "employee"))) //.AddEntityExpressionToVariable("myVar", new EntityRef("test", "aaJudeJacobs")) .AddInput <ResourceArgument>("myInput", Entity.Get <EntityType>(cheese)) .AddDisplayForm("Display Form", new[] { "Exit1", "Exit2" }, recordExpression: "[myInput]") .AddPromptUser("Prompt User") .AddLog("Log", "MyVar: {{myVar}}"); wf.Name = "!TestDisplayFormResumeWithVariables_Bug_23903_" + DateTime.Now; wf.Save(); // Act var wfInput = new Dictionary <string, object> { { "myInput", cheeses.First() } }; using (new WorkflowRunContext(true)) { run = RunWorkflow(wf, wfInput); } run.WorkflowRunStatus_Enum.Should().Be(WorkflowRunState_Enumeration.WorkflowRunPaused); run.PendingActivity.Should().NotBeNull(); run.PendingActivity.Name.Should().Be("Display Form"); run.TaskWithinWorkflowRun.Should().NotBeNull().And.NotBeEmpty().And.HaveCount(1); task = run.TaskWithinWorkflowRun.First().AsWritable <DisplayFormUserTask>(); task.Should().NotBeNull(); run.StateInfo.Count.Should().BeGreaterOrEqualTo(2); run.StateInfo.Select(si => si.StateInfoArgument.Name).Should().NotContain("myVar"); run.StateInfo.Select(si => si.StateInfoArgument.Name).Should().Contain("myInput"); using (new WorkflowRunContext(true)) { run2 = WorkflowRunner.Instance.ResumeWorkflow(run, new UserCompletesTaskEvent { CompletionStateId = task.AvailableTransitions.First(t => t.Name == "Exit1").Id, UserTaskId = task.Id }); } run2.WorkflowRunStatus_Enum.Should().Be(WorkflowRunState_Enumeration.WorkflowRunPaused); run2.PendingActivity.Should().NotBeNull(); run2.PendingActivity.Name.Should().Be("Prompt User"); run2.TaskWithinWorkflowRun.Should().NotBeNull().And.NotBeEmpty().And.HaveCount(1); p = run2.TaskWithinWorkflowRun.First().AsWritable <PromptUserTask>(); p.Should().NotBeNull(); p.PromptForTaskStateInfo.Should().NotBeNull().And.HaveCount(2); p.PromptForTaskStateInfo.Select(si => si.StateInfoArgument.Name).Should().Contain("myVar"); p.PromptForTaskStateInfo.Select(si => si.StateInfoArgument.Name).Should().Contain("myInput"); run2.StateInfo.Count.Should().BeGreaterOrEqualTo(3); run2.StateInfo.Select(si => si.StateInfoArgument.Name).Should().NotContain("myVar"); run2.StateInfo.Select(si => si.StateInfoArgument.Name).Should().Contain("myInput"); var value = p.PromptForTaskStateInfo.First(si => si.StateInfoArgument.Name == "myVar"); var param = value.StateInfoValue.AsWritable <ResourceArgument>(); param.ResourceParameterValue = Entity.Get <Resource>(new EntityRef("test", "aaJudeJacobs")); param.Save(); var param2 = Entity.Get <ResourceArgument>(param); param2.ResourceParameterValue.Should().NotBeNull(); param2.ResourceParameterValue.Name.Should().Be("Jude Jacobs"); using (new WorkflowRunContext(true)) { run3 = WorkflowRunner.Instance.ResumeWorkflow(run2, new PromptUserTaskCompletedEvent { UserTaskId = p.Id }); } // Assert run3.WorkflowRunStatus_Enum.Should().Be(WorkflowRunState_Enumeration.WorkflowRunCompleted); run3.RunLog.Should().NotBeNull().And.NotBeEmpty(); run3.RunLog.Where(l => l.Description == "MyVar: Jude Jacobs").Should().NotBeEmpty(); } finally { if (p != null) { ToDelete.Add(p.Id); } if (task != null) { ToDelete.Add(task.Id); } if (wf != null) { ToDelete.Add(wf.Id); } } } }