Hi Everyone,
I am working on a QuickLink where it will be updating a custom object (add and remove) operations.
Now If the remove operation is performed - instead of directly removing the entry or selected data I want to create a workitem and if approved remove the data if not just end the flow without making any changes. Can anyone guide me with this issue.
below is the flow I have without the workitem creation
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE Workflow PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<Workflow created="1758885015689" explicitTransitions="true" id="c0a8380199851b45819985b770890036" modified="1758894454691" name="CustomObject_Add_Preventative_SOD_Flow_Updated" type="">
<Variable editable="true" name="identityModel"/>
<Variable name="workItemFormBasePath"/>
<Variable initializer="false" name="identityAttributesVisible"/>
<Variable editable="true" name="policyModel"/>
<Variable editable="true" name="removeSelection"/>
<Variable editable="true" name="userAction"/>
<Variable input="true" name="policyTemplates" output="true" type="List"/>
<Variable initializer="spadmin" input="true" name="fallbackApprover">
<Description>A String that specifies the name of the Identity that will
be assigned any approvals where the owner of the approver
can&#39;t be resolved. Example if the scheme is &quot;owner&quot; and the
application doesn&#39;t specify and owner.</Description>
</Variable>
<Variable input="true" name="workItemPriority">
<Description>String version of WorkItem.level that will be used
to set the priority of any generated approval
workitems.</Description>
</Variable>
<Variable input="true" name="approverElectronicSignature">
<Description>The name of the electronic signature object that should be used when workitems
are completed by the batch approver when performing batch operations.</Description>
</Variable>
<Variable initializer="serial" input="true" name="approvalMode">
<Description>A string that specifies how we should handle the approvals.
By default this is serial since most of these request with
the exception of manager transfers will have only one approver.
parallel
Approvals are processed concurrently and there must be consensus,
we wait for all approvers to approve. The first approver that
rejects terminates the entire approval.
parallelPoll
Approvals are processed concurrently but consensus is not required.
All approvals will be processed, we don&#39;t stop if there are any
rejections.
serial
Approvals are processed one at a time and there must be consensus.
The first approver that rejects terminates the entire approval.
serialPoll
Approvals are processed in order but consensus is not required.
All approvals will be processed, we don&#39;t stop if there are any
rejections. In effect we are &quot;taking a poll&quot; of the approvers.
any
Approvals are processed concurrently, the first approver to
respond makes the decision for the group.</Description>
</Variable>
<Variable name="approvalForm">
<Description>The generated form that is displayed to the user during the approval.</Description>
</Variable>
<Variable input="true" name="fallbackApprover">
<Description>A String that specifies the name of the Identity that will
be assigned any manual actions where the owner of the approver
can&#39;t be resolved.</Description>
</Variable>
<Variable input="true" name="approvalSet" output="true">
<Description>This variable includes all ApprovalItems that are part of
the request process and is updated during the AfterScript
of the approval process by assimilating the decisions
and comments from the Approvals copy of the ApprovalItem.</Description>
</Variable>
<Variable input="true" name="approvers">
<Description>Owners that should recieve the approval.</Description>
</Variable>
<Variable input="true" name="approvalScheme">
<Description>A String that specifies how approvals should be generated for
this workflow there are five built-in modes
none - disabled approvals
manager - The user&#39;s current manager will get approvals
newManager - The newly assigned manager will get approvals when
manager transfers occur. Otherwise the user&#39;s manager
current manager will be the approver.
securityOfficer - The identity in the securityOfficerName variable
will get approvals.</Description>
</Variable>
<Step icon="Start" name="Start" posX="50" posY="50">
<Transition to="Policy_Email_Selection dummy"/>
</Step>
<Step icon="Default" name="Policy_Email_Selection dummy" posX="160" posY="222">
<Approval name="Policy_Email_Selection_Form" owner="spadmin" return="policyModel,userAction" send="">
<Form name="Policy_Email_Selection_Form">
<Attributes>
<Map>
<entry key="hideIncompleteFields">
<value>
<Boolean></Boolean>
</value>
</entry>
<entry key="includeHiddenFields">
<value>
<Boolean></Boolean>
</value>
</entry>
<entry key="pageTitle" value="Policy_Email_Selection_Form"/>
</Map>
</Attributes>
<Section label="Existing Policies" name="ExistingPoliciesSection" type="text">
<Field>
<Script>
<Source>
import sailpoint.object.Custom;
import java.util.*;
System.out.println("Entered the table page");
StringBuilder sb = new StringBuilder("");
Custom customObj = context.getObjectByName(Custom.class, "New_PreventativeSOD_Policies");
sb.append("<html>");
sb.append("<div style='overflow:auto; max-height:400px;'>");
sb.append("<table width='100%' border='1' cellpadding='6' cellspacing='0' style='border-collapse:collapse;'>");
sb.append("<tr style='background-color:#024069; color:white;'>");
sb.append("<th style='border:1px solid black;'>Policy Name</th>");
sb.append("<th style='border:1px solid black;'>Email Template</th>");
sb.append("</tr>");
if (customObj != null) {
Map attrs = customObj.getAttributes();
if (attrs != null) {
Map policies = (Map) attrs.get("policies");
if (policies != null && !policies.isEmpty()) {
for (Object key : policies.keySet()) {
String policyName = key.toString();
Object emailTemplate = policies.get(key);
sb.append("<tr style='background-color:#FFFFFF; color:black;'>");
sb.append("<td style='border:1px solid black;'>").append(policyName).append("</td>");
sb.append("<td style='border:1px solid black;'>")
.append(emailTemplate != null ? emailTemplate.toString() : "")
.append("</td>");
sb.append("</tr>");
}
} else {
sb.append("<tr><td colspan='2'>No existing policies found.</td></tr>");
}
}
} else {
sb.append("<tr><td colspan='2'>No existing policies found.</td></tr>");
}
sb.append("</table>");
sb.append("</div>");
sb.append("</html>");
System.out.println("Exit the table page");
return sb.toString();
</Source>
</Script>
</Field>
</Section>
<Button action="next" label="Add" parameter="userAction" value="add"/>
<Button action="cancel" label="Cancel" parameter="userAction" value="cancel"/>
<Button action="next" label="Remove" parameter="userAction" value="remove"/>
</Form>
</Approval>
<Transition to="Policy_Email_Selection">
<Script>
<Source>
if (userAction != null && userAction.toString().equalsIgnoreCase("add")) {
System.out.println("userAction first: " + userAction);
return true;
}
</Source>
</Script>
</Transition>
<Transition to="Remove_CustomObject"/>
</Step>
<Step icon="Default" name="Policy_Email_Selection" posX="302" posY="68">
<Approval name="Policy_Email_Selection_Form_Add" owner="spadmin" return="policyModel,userAction" send="">
<Form name="Policy_Email_Selection_Form_Add">
<Attributes>
<Map>
<entry key="pageTitle" value="Policy_Email_Selection_Form_Add"/>
</Map>
</Attributes>
<Section name="Policy Details">
<Field displayName="Policy Name" name="policyName" required="true" type="string">
<AllowedValuesDefinition>
<Script>
<Source>
import sailpoint.object.Policy;
import sailpoint.object.Custom;
import java.util.*;
List policyNames = new ArrayList();
List policies = context.getObjects(Policy.class);
for (Policy p : policies) {
policyNames.add(p.getName());
}
Custom customObj = context.getObjectByName(Custom.class, "New_PreventativeSOD_Policies");
Set existingPolicies = new HashSet();
if (customObj != null) {
Map attributes = customObj.getAttributes();
if (attributes != null) {
Map policiesMap = (Map) attributes.get("policies");
if (policiesMap != null) {
existingPolicies.addAll(policiesMap.keySet());
}
}
}
policyNames.removeAll(existingPolicies);
Collections.sort(policyNames);
return policyNames;
</Source>
</Script>
</AllowedValuesDefinition>
</Field>
<Field displayName="Email Template" name="emailTemplate" required="true" type="string">
<AllowedValuesDefinition>
<Script>
<Source>
import sailpoint.object.EmailTemplate;
import java.util.*;
List emailTemplates = new ArrayList();
List templates = context.getObjects(EmailTemplate.class);
for (EmailTemplate t : templates) {
emailTemplates.add(t.getName());
}
emailTemplates.add("None of the above");
Collections.sort(emailTemplates);
return emailTemplates;
</Source>
</Script>
</AllowedValuesDefinition>
</Field>
</Section>
<Button action="next" label="Submit"/>
<Button action="cancel" label="Cancel" value="cancel"/>
</Form>
</Approval>
<Arg name="workItemFormBasePath" value="policyModel"/>
<Transition to="Create_CustomObject"/>
</Step>
<Step icon="Default" name="Create_CustomObject" posX="476" posY="47">
<Script>
<Source>
import sailpoint.object.Custom;
import java.util.*;
try {
String policyName = policyModel.get("policyName");
String emailTemplate = policyModel.get("emailTemplate");
Custom customObj = context.getObjectByName(Custom.class, "New_PreventativeSOD_Policies");
if (customObj == null) {
customObj = new Custom();
customObj.setName("New_PreventativeSOD_Policies");
}
Map attributes = customObj.getAttributes();
if (attributes == null) attributes = new HashMap();
Map policiesMap = (Map) attributes.get("policies");
if (policiesMap == null) policiesMap = new HashMap();
policiesMap.put(policyName, emailTemplate);
attributes.put("policies", policiesMap);
customObj.setAttributes(new sailpoint.object.Attributes(attributes));
context.saveObject(customObj);
context.commitTransaction();
return "Custom Object updated successfully with policy: " + policyName;
} catch (Exception e) {
return "Error: " + e.getMessage();
}
</Source>
</Script>
<Transition to="Audit_Log"/>
</Step>
<Step icon="Default" name="Remove_CustomObject" posX="302" posY="316">
<Approval name="Remove_Policy_Form" owner="spadmin" return="removeSelection,userAction" send="">
<Form name="Remove_Policy_Form">
<Attributes>
<Map>
<entry key="pageTitle" value="Remove_Policy_Form"/>
</Map>
</Attributes>
<Section name="Remove Policies">
<Field displayName="Select Policies to Remove" multi="true" name="removeSelection" required="true" type="string">
<Script>
<Source>
import sailpoint.object.Custom;
import java.util.*;
List policiesList = new ArrayList();
Custom customObj = context.getObjectByName(Custom.class, "New_PreventativeSOD_Policies");
if (customObj != null) {
Map attrs = customObj.getAttributes();
if (attrs != null) {
Map policies = (Map) attrs.get("policies");
if (policies != null) {
for (Object key : policies.keySet()) {
policiesList.add(key.toString());
}
}
}
}
return policiesList;
</Source>
</Script>
</Field>
</Section>
<Button action="next" label="Submit" value="submit"/>
<Button action="cancel" label="Cancel"/>
</Form>
</Approval>
<Transition to="Process_Remove"/>
</Step>
<Step icon="Default" name="Process_Remove" posX="577" posY="225">
<Script>
<Source>
import sailpoint.object.Custom;
import java.util.*;
System.out.println("Entered Process_Remove");
try {
List removeList = (List) removeSelection;
System.out.println("Requested removeList: " + removeList);
Custom customObj = context.getObjectByName(Custom.class, "New_PreventativeSOD_Policies");
if (customObj != null) {
Map attrs = customObj.getAttributes();
if (attrs != null) {
Map policies = (Map) attrs.get("policies");
if (policies != null) {
System.out.println("Original policies: " + policies.keySet());
Map selectedPolicies = new HashMap();
for (Object selectedKeyObj : removeList) {
String selectedKey = selectedKeyObj.toString().trim();
if (policies.containsKey(selectedKey)) {
selectedPolicies.put(selectedKey, policies.get(selectedKey));
System.out.println("Keeping selected policy: " + selectedKey);
} else {
System.out.println("Selected policy not found in custom object: " + selectedKey);
}
}
System.out.println("Final policies saved to custom object: " + selectedPolicies.keySet());
attrs.put("policies", selectedPolicies);
customObj.setAttributes(new sailpoint.object.Attributes(attrs));
context.saveObject(customObj);
context.commitTransaction();
}
}
}
return "Custom Object now contains only selected policies: " + removeList;
} catch (Exception e) {
System.out.println("Error in Process_Remove: " + e.getMessage());
e.printStackTrace();
return "Error removing: " + e.getMessage();
}
</Source>
</Script>
<Transition to="Audit_Log"/>
</Step>
<Step icon="Default" name="Audit_Log" posX="753" posY="140">
<Script>
<Source>
import sailpoint.object.AuditEvent;
import sailpoint.server.Auditor;
import java.text.SimpleDateFormat;
import java.util.Date;
System.out.println("Entered Audit_Log step");
try {
String action = "PreventativeSODPolicyUpdate";
String source = "SH Lifecycle Event - Rehire Workflow";
String target = "New_PreventativeSOD_Policies";
String actor = context.getUserName();
String arg1 = "User Action: " + (userAction != null ? userAction.toString() : "unknown");
String arg2 = (policyModel != null)
? "Policy Added: " + policyModel.get("policyName") + ", Template: " + policyModel.get("emailTemplate")
: (removeSelection != null ? "Policies Removed: " + removeSelection.toString() : "No details");
SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
String currentDate = formatter.format(new Date());
AuditEvent event = new AuditEvent();
event.setAction(action);
event.setSource(source);
event.setTarget(target);
event.setAttributeName("Operation Status");
event.setAttributeValue("Success");
event.setAttribute("Actor", actor);
event.setAttribute("Details1", arg1);
event.setAttribute("Details2", arg2);
event.setAttribute("Creation Date", currentDate);
Auditor.log(event);
context.commitTransaction();
System.out.println("Audit_Log step completed successfully");
} catch (Exception e) {
System.out.println("Error in Audit_Log step: " + e.getMessage());
e.printStackTrace();
}
System.out.println("Exited Audit_Log step");
</Source>
</Script>
<Transition to="Stop"/>
</Step>
<Step icon="Stop" name="Stop" posX="991" posY="135"/>
</Workflow>

