Skip to main content

While redirecting individual worklist items is similar to the capabilities in K2 ‘03 from either the K2mng or workflow.management APIs, you have much more power and flexibility provided within SmartObjects to satisfy this need in a way that is auditted and tracked automatically in the "out of the box" reports.


 


The worklistitem.redirect(struser) method is only useful for reassigning a worklist item to an individual.  The link between group assigned activities, slots, and allocations would be completely confused if tried to redirect a single worklist item to a group from this API call.  If you want to redirect an activity to multiple people, or if you require a reasonably reportable and auditable trail during re-assignments or delegation, you should employe a redirection method that completes and re-instantiates the same activity with the new destination information.

 


Typically this can been accomplished by either:


1)      A loopback line that allows the activity to be finished resolving a certain action (ie. “Redirect”) to a line that is looped back to the same activity.  This way you can leverage the destination sets associated with the activity and make fresh use of slots when assigned the activity to a different group, list, roles etc within the organization.
(see loop-back line in the image below)


2)      A second activity customized for this particular group (ie. Redirecting a support ticket to the development team).  This allows for specialized actions, client and server events that apply only to the redirected recipient.


14629iD9459FB392ABFC53.png

The workflow (SupportRequests):


 


1)      Setup a workflow with an activity that can be handled by multiple different groups and user(s).  In my example here, it is called SupportRequestActions and I am planning on virtually anyone involved with the support process to be able to action this activity if they are assigned to it.


a.       For this example the SupportRequestActions needs at least the following actions:


                                                               i.      Complete


                                                             ii.      Redirect


b.      The “More Information” logic path is optional for the example


c.   Below is an image of the destinations related to this activity that are setup in the attached example.


2)      Two activities are available for the originator.


a.       SupporRequestOriginatorActions gives the originator the ability to provide additional information if needed.


b.      OriginatorReview allows the originator the accept or reject the solution provided


3)      The finish activity contains cleanup events and is the recipient of any .net API GotoActivity() method calls from the outside of the workflow that will “cancel” the workflow in its entirety.


13687i87652699F87AABDC.png

Activity Destination Sets in Focus (SupportRequestActions):


1)      AD-Group (View article specific to this type )


                           i.      Before the Activity begins:  Via form, populate process.datafield “SRActionsGroupName” programmatically based on user selection (user could directly pick a group or choose a support request type which resolves to a group)


                         ii.      Activity Destination Set:  Resolve names from AD smartobject that accepts GroupName as parameter from “SRActionsGroupName”


                        iii.      Activity Destination Rule: only computes true if “SRActionsGroupName” field is not blank:


2)      Custom Group (View article specific to this type)


                           i.      Before the Activity begins: 


1.       Via form, populate temporary records into a Smartbox Smartobject such as “CustomDestinations”, need at least (process instance ID, activity name for dest., user logon name)


2.        Populate process.datafield “SRActionsCustomDestinations” = true;


                         ii.      Resolve names from Smartbox Smartobject based on the ProcessInstanceID and Activity name


                        iii.      Dest. Rule only computes true if process.datafield “SRActionsUseCustomDestinations” == true


                       iv.      As activity completes:


1.       Cleanup the custom records from the destination using a fitered delete method.  Multi-records filtered deletes is an upcoming feature in SmartObjects, but using the code in the


3)      Individual User


                                                                           i.      Before the Activity begins:  Via form, populate a process.datafield “SRActionsSingleUser” with one logon name based on user selection


                                                                         ii.      Drop that field directly into the destination for that set


                                                                        iii.      Dest. Rule only computes true if process.datafield “SRActionsSingleUser” is not blank


4)      Failsafe or Default Destination


(Reference this article for a full description – this destination set is recommended and provides a catch-all destination set to handle worklist items that have been orphaned)


Below is an image of the controls that I'm using for the user to select which type of custom destination they want and to include information related to that destination:


1)  First there is a hidden field which is used to contain the users listed within the Custom Destination User List with a ; delimiter.
2) The lblStatus field should be used to let the user know that their form was submitted correctly or that there was an error
3) The Actions field is populated and read using the methods described by Bob in his "get / set" actions example.
4) The Destination Group and User text boxes will be used respectively if that type of destination is chosen from the "Select the type of destination to redirect to"
5) The Custom Destination User list contains:
- List Box to show the selected redirection users
- Add button to "add" the typed in user to the List and also to the hidden field
- I would recommend using an AD lookup that allows for searching in order to prevent unknown users from being added to the destination set.


16408i50CCB33D80DEAF6A.png

I've employed simplistic javascript in the example for so that based on the actions specified, the user is walked through the appropriate menus and lists for the destination they are choosing.

Note that I'm wrapping things in a panel within Visual Studio as this is an easy way to use non-control specific javascript across multiple controls.


Also note that, if the user does not have K2 based permissions to choose the "Redirect" action from the actions menu they won't be bothered by any of the assignment controls.

Here is the javascript for easy copying and pasting:

<script language="javascript" type="text/javascript">
// <!CDATA[
function load(){


    //hide the custom destination panels until user selection makes them visible
    document.all("pnlUser").style.display = "none";
    document.all("pnlGroup").style.display = "none";
    document.all("pnlCustom").style.display = "none";
    document.all("pnlDestinationType").style.display = "none";
   
    //clear out the Custom Destinations Listbox
    var select = document.all("lstRedirectCustomList");
    if (select.length > 0)
    {
        select.remove(select.length - 1);
    }
}


function ddlDestinationTypeSelectedIndexChanged() {
    switch (document.all("ddlDestinationType").value){
    case "none":
    document.all("pnlUser").style.display = "none";
    document.all("pnlGroup").style.display = "none";
    document.all("pnlCustom").style.display = "none";
    break;
    case "Group":
    document.all("pnlGroup").style.display = "inline";
    document.all("pnlUser").style.display = "none";
    document.all("pnlCustom").style.display = "none";
    break;
    case "User":
    document.all("pnlUser").style.display = "inline";
    document.all("pnlGroup").style.display = "none";
    document.all("pnlCustom").style.display = "none";
    break;
    case "Custom":
    document.all("pnlCustom").style.display = "inline";
    document.all("pnlUser").style.display = "none";
    document.all("pnlGroup").style.display = "none";
    break;
    }
}
function ddlActionsSelectedIndexChanged() {
    if (document.all("ddlActions").value == "Redirect"){
    document.all("pnlDestinationType").style.display = "inline";
    }
    else
    {
    document.all("pnlUser").style.display = "none";
    document.all("pnlGroup").style.display = "none";
    document.all("pnlCustom").style.display = "none";
    document.all("pnlDestinationType").style.display = "none";
    }
}
function btnAddOnClick(){
    var optn = document.createElement("OPTION");
    optn.text = document.all("txtUsername").value;
    optn.value = document.all("txtUsername").value;
    document.all("lstRedirectCustomList").options.add(optn);
    document.all("txtUsername").value = "";
}
          


function DoCustomPost(){
    for(i=0;i<document.all("lstRedirectCustomList").options.length;i++)
    {
        //document.all("lstRedirectCustomList").options.selected = true;
        document.all("hdnCustomList").value = document.all("hdnCustomList").value + document.all("lstRedirectCustomList").options.value + ";";
       
    }
    document.formse0].submit();
}


// ]]>
</script>


Here I am running all options out of the PageLoad event method.  Yes, this sort of if/else statement is a bit messy, but for this example it gets the point across - note the comments:


 protected void Page_Load(object sender, EventArgs e)
        {
            //assign jscript functions to the actions and destination type drop down lists
            ddlActions.Attributes.Add("OnChange", "ddlActionsSelectedIndexChanged()");
            ddlDestinationType.Attributes.Add("OnChange", "ddlDestinationTypeSelectedIndexChanged()");


            string strSN = string.Empty;


            // get the serial number
            if (Request"SN"] != null)
            {
                strSN = Requestu"SN"];
            }


             if (!IsPostBack && strSN != String.Empty)
                //Load the form
             {
                // open a K2 connection
                SourceCode.Workflow.Client.Connection oConn = new SourceCode.Workflow.Client.Connection();
                oConn.Open(m_strBPServer);


                // get this specific worklist item
                SourceCode.Workflow.Client.WorklistItem oWli = oConn.OpenWorklistItem(strSN);


                //Add one blank action to the actions drop-down
                ddlActions.Items.Add("");


                if (oWli != null)
                {
                    //// retrieve properties if desired
                    //lblProcess.Text = oWli.ProcessInstance.Name;
                    //lblActivity.Text = oWli.ActivityInstanceDestination.Name;
                    //lblFolio.Text = oWli.ProcessInstance.Folio;


                    // *** POPULATE THE DROPDOWNLIST WITH THE ACTIONS ***
                    foreach (Action oAct in oWli.Actions)
                    {
                        ddlActions.Items.Add(oAct.Name);
                    }
                }


                // close the connection
                oConn.Close();
            }
             else if (IsPostBack && strSN != String.Empty)
            //Form Submitted
            {
                    // open a K2 connection
                    SourceCode.Workflow.Client.Connection oConn = new SourceCode.Workflow.Client.Connection();
                    oConn.Open(m_strBPServer);


                    // get the worklist item
                    SourceCode.Workflow.Client.WorklistItem oWli = oConn.OpenWorklistItem(strSN);


                    if (oWli != null)
                    {
                        if (ddlActions.SelectedValue == "Redirect")
                        {
                            //cleanup previous destinations in case this is the 2nd redirect
                            oWli.ProcessInstance.DataFieldso"SRActionsSingleUserDestination"].Value = String.Empty;
                            oWli.ProcessInstance.DataFields"SRActionsGroupName"].Value = String.Empty;
                            oWli.ProcessInstance.DataFieldse"SRActionsCustomDestination"].Value = false;
                            switch (ddlDestinationType.SelectedValue)
                            {
                                case "User":
                                    oWli.ProcessInstance.DataFieldse"SRActionsSingleUserDestination"].Value = txtRedirectUser.Text;
                                    break;
                                case "Group":
                                    oWli.ProcessInstance.DataFields"SRActionsGroupName"].Value = txtRedirectGroup.Text;
                                    break;
                                case "Custom":
                                   
                                    string strCustomList = string.Empty;
                                    if (RequestS"hdnCustomList"] != null)
                                    {
                                        strCustomList = Requestd"hdnCustomList"];
                                    }
                                    if (strCustomList != string.Empty)
                                    {
                                       char chSeparator = Convert.ToChar(";");
                                     
                                       String ] arrDestinations =  strCustomList.Split(chSeparator);
                                       try
                                       {
                                           foreach (string strDestination in arrDestinations)
                                           {
                                               if (strDestination.Length > 0)
                                               {
                                                   //use the Example.CustomDestinationsClass to create the desination records
                                                   Example.CustomDestinationsClass.Methods.CreateDestination(
                                                       m_strSOConnCustomDestinations,
                                                       oWli.ActivityInstanceDestination.Name + oWli.ProcessInstance.ID.ToString(),
                                                       oWli.ProcessInstance.ID,
                                                       strDestination);
                                               }
                                           }
                                           oWli.ProcessInstance.DataFields"SRActionsCustomDestination"].Value = true;
                                       }
                                       catch
                                       {
                                           //if there is any errors writing the destinations, make sure the workflow will not use this destination set
                                           oWli.ProcessInstance.DataFields"SRActionsCustomDestination"].Value = false;
                                           lblStatus.Text = "There was an error creating the custom destination set.";
                                       }


                                    }
                                    break;
                            }
                        }


                        // *** SET THE APPROPRIATE ACTION AS SELECTED WITHIN THE COLLECTION ***
                        foreach (Action oAct in oWli.Actions)
                        {
                            if (oAct.Name == ddlActions.SelectedItem.Text)
                            {
                                oAct.Execute();
                                break;
                            }
                           
                        }
                       
                    }



                    // close the connection
                    oConn.Close();


                 //hide the submit button
                 pnlSubmit.Style.Add("display", "none");
                 pnlActions.Style.Add("display", "none");
                 lblStatus.Text = "The " + ddlActions.SelectedItem.Text + " action was completed successfully";
                
               
                   
               
            }


Reply