- Post History
- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
on 06-26-2015 03:31 PM
Greetings! First off, our organization is currently on Eureka.
We have a change process that requires two separate groups of approvers who get the approval requests in parallel. Group one, essentially the CAB requires approval while group two a review board may reject but does not have approval powers and their response is not required (if they never reply the work flow will end once the timer expires). Things got trickier once it was decided that the reviewer group needed the ability to reject a change even after the CAB had approved it up to a time frame before the change start time. For this scenario, the approval coordinator would no longer work since we needed to change to move on to approved status but still allow a timer to run giving the review board an opportunity to reject. Once the reject was triggered, even if the CAB approval had not yet completed, we needed to roll back to the start of the work flow.
Of course with Rollback To, it only rolls back tasks in the transition path up to the 'To' point. This meant that if the CAB had not yet approved, and the change was rejected by the reviewers, the cab groups approval would still be running and they could potentially approve the rejected change. Also if the CAB had already approved, the problem was that once a rejected change was re-submitted, it would essentially auto-approve since the CAB's approval did not get rolled back.
In trying to find solutions, I took a look at the following which got me started:
While that would reset the workflow it just did not feel as graceful as a rollback. Also, I still had issues with handing existing approval records, especially when the workflow came back to the insertion which while tolerable just didn't seem clean to me.
Ultimately solution was to write a script that runs just prior to the rollback. This script marks the change as rejected (should the CAB have already completed their approval), loops through all of the approvals resetting them and finally so that the rollback works properly, then update any pending activity and add activity history records for them. The end result is that the entire workflow rolls back, not just the branch that triggered the rollback.
This code may still need some tweaking to make sure there aren't any other conflicts caused, but so far so good. If anyone has any feedback or improvement suggestions it would be much appreciated. I'm still feeling my way around. Thanks.
First here is the workflow we ended up with:
And here is the script I run in the run script step just prior to the rollback. Note that step 2 of this script is also run when approval is re-requested to make sure that all approval groups and users are back in a not yet requested state before the approval steps are re-triggered. Re running step 2 takes care of any late responses that may come in through email responses.
var approvalUtils = new WorkflowApprovalUtils();
// Step 1: Set the record the workflow is running against to rejected. This is
// necessary in case aparallel approval has previously approved the record.
current.setValue('approval','rejected');
// Step 2: Reset all approval records associated to the record being approved.
// Note, I also run this portion of the script when approval is re-requested just to make sure
// nothing has gone to an unwanted status due to email responses, etc.
workflow.info('Starting scripted resetting of approval records.');
var apGr = approvalUtils.getApprovalRecord( current.sys_id );
while( apGr.next() ){
var approval = new GlideRecord('sysapproval_approver');
approval.addQuery('approver', apGr.approver);
approval.addQuery('sysapproval', current.sys_id);
approval.query();
while (approval.next()) {
approval.iteration +=1;
var oldState = approval.state; // just for logging purposes
if(approval.state != 'rejected'){ approval.state = 'not requested'; }
if(approval.group > '') {
// If an approval group is referenced (ie this approval record was created by an approval group workflow activity), then we need to
// make sure that the group record is also set back to not yet requested because rollback to under certain circumstances can miss it.
if(approval.group.approval != 'not requested') {
workflow.info('Resetting approval group ' + approval.group.getDisplayValue() + ' back to not yet requested');
approval.group.approval = 'not requested';
}
}
approval.updateWithReferences();
workflow.debug('Approval record ' + approval.sys_id + ' for ' + approval.approver.getDisplayValue() + ' ' + oldState + ' => ' + approval.state);
}
}
// Step 3: Cancel any currently executing activites before we rollback (next step in workflow)
// making sure to also create history items in the current workflow context. This allows
// the rollback to work restart those tasks that were running.
workflow.info('Cancelling currently executing activities');
var wfActiveActivity = new GlideRecord('wf_executing');
wfActiveActivity.addQuery('context', activity.context);
wfActiveActivity.query();
while (wfActiveActivity.next()) {
wfActiveActivity.result = 'skipped';
wfActiveActivity.state = 'finished';
wfActiveActivity.update();
workflow.debug(wfActiveActivity.activity.getDisplayValue() + ' marked with result skipped, state finished in the wf_executing table.');
createHistory(wfActiveActivity);
}
// Function to create activity history for a given activity a.
function createHistory(a) {
// create activity history and transition
var h = new GlideRecord('wf_history');
h.initialize();
h.activity = a.activity;
h.activity_index = a.activity;
h.context = a.context;
h.due = a.due;
h.ended = new GlideDateTime();
h.fault_description = a.fault_description;
h.output = a.output;
h.parent = a.parent;
h.result = a.result;
h.started = a.started;
h.state = a.state;
h.workflow_version = a.workflow_version;
h.insert();
workflow.debug('Added record in wf_history for ' + a.activity.getDisplayValue() + ' so this new status change is recorded.');
}
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
I have revised the original script slightly. The query in step two was not narrow enough in scope originally so it was focused better and workflow logging has been added. Also step 2 has been updated to also reset associated workflow approval groups with an approval record. If exclusively using the Approval-User activity checking the approval group is not necessary but doesn't hurt anything.
We decided to use the approval-group activity because it attaches an approval group to the workflow context which is associated to the user group that was used to build the approval-group. What does this do for us? It allows us to in the list of approves include the group name that the user came from. By including the group name a change owner can identify who's approval they have to follow up with if their change is not yet approved and who is just a reviewer.
Note that pre-Fuji workflow.debug will need to be changed to workflow.info if you want those messages to go into the workflow log. With Fuji the ability to control the scope of workflow logging has been added so you can choose to have those messages included or not with the workflow properties.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Your approach and code was a life saver for me. Thank you so much
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Thanks, I'm happy to hear that it was a help! Our organization still uses this process today
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
This was very helpful. We have a new request/workflow buidl where this is perfect for. Thank you for the info