Name
global.WorkflowModelManager
Description
The purpose of this Object is to provide a way to query the workflow model, to step backwards and forwards between specified wf_history items and to query the history for activity and transition specific information. This first pass provides an interface to support new UI tools being developed to assist in debugging a workflow. To acquire the executed history of the workflow activities var model = new WorkflowModelManager( myContextId ); var activities = model.getExecutedHistory(); To output the playback of the workflow use the following var model = new WorkflowModelManager( myContextId ); model.getExecutedHistory(); model.playBack(); At this time, the playBack is required to load the executed transitions. This will also play a role in walking backwards on a model to rollback to a specific activity. This is just iteration one in our sprint To see the details of the cached model var model = new WorkflowModelManager( myContextId ); model.getExecutedHistory(); model.dump();
Script
var WorkflowModelManager = Class.create();
WorkflowModelManager.prototype = {
initialize : function( /*context*/ context) {
this.wfContext = context; /** wf_context sys_id of the context associated with the workflow being played back **/
this.begin = ''; /** the ActivityHistoryRecord that begins the workflow, stored here to ease the start of playback **/
this.executedActivities = {}; /** hash of ActivityHistoryRecord javascript objects, hashed by sys_id of the wf_history record **/
this.executedTransitions = {}; /** hash of ExecutedTransition javascript objects, hashed by sys_id of the wf_transition_history record **/
this.executionOrder = []; /** array of ActivityHistoryRecord ordered by the activity_index that is set in the ActivityManager class **/
this.joinsByWfActivityId = {}; /** hash of ActivityHistoryRecords that are Join activities, used in playback. This hash is by the wf_activity.sys_id **/
this.joinsByHistoryRecordIndex = []; /** array of ActivityHistoryRecords that are Join activities, used in playback locate next appropriate join **/
this.executedPaths = [];
this.toActivityFound = false;
this.fromActivityFound = false;
this._grUtil=new GlideRecordUtil();
//for nested rollback fix -- STRY0127051
this.rollbackactivityIndex = 0; /** activity index of Roll BackTo activity which is in execution currently **/
},
//==========================================================================================
// BUILDING MODEL AND PLAYBACK LOGIC
//==========================================================================================
/**
* This list returns a fully initialized list of
* javascript object(s) ActivityHistoryRecord.
*
* Each instance reports the to/from activity
* as well as relevant wf_activity and activity definition
* details used in display of workflow.
*
* var model = new WorkflowModelManager('myContextId');
* var activities = model.getExecutedHistory();
*
*/
getExecutedHistory: function() {
this._getExecutedActivities();
this._getExecutedTransitions();
this.playBack();
return this.executionOrder;
},
/**
* The purpose of this function is to retrieve the history activity that
* is cached by the wf_history.sys_id provided in the argument.
**/
getActivityHistoryRecordById: function(haRecordSysId /** String wf_history.sys_id **/) {
haRecordSysId.isNil()
var haRecord = haRecordSysId.isNil() ? null : this.executedActivities[haRecordSysId];
return haRecord;
},
/**
* common validity test to make sure an operation won't error out
**/
_sanityCheck : function(haRecord /** ActivityHistoryRecord javascript object **/) {
var result = JSUtil.notNil(this.executionOrder) &&
this.executionOrder.length>0; //there is some executionOrder
//haRecord exists and its index makes sense for this executionOrder
result = result &&
JSUtil.notNil(haRecord) &&
Number(haRecord.index)>=0 &&
Number(haRecord.index)<this.executionOrder.length;
return result;
},
/**
* The purpose of this function is to retrieve the history activity that
* is cached by the wf_history.sys_id provided in the argument. It then
* calls to getPreviousByExecutedOrder() with the retrieved javascript object.
* see comments below.
*
* Sample use:
* var model = new WorkflowModelManager('contextId');
* model.getExecutedHistory();
* var current = model.getActivityHistoryRecordById('wf_history.sys_id');
* current.debugDump();
* var results = model.getPreviousByExecutedOrderId(current.sys_id);
* results.debugDump();
*
**/
getPreviousByExecutedOrderId: function(haRecordSysId /** String wf_history.sys_id **/) {
var haRecord = this.executedActivities[haRecordSysId];
return this.getPreviousByExecutedOrder( haRecord );
},
/**
* The purpose of this function is to retrieve the history activity that
* executed just previous to the one provided in the argument. The
* 'previous' status is based on the activity index reflecting the
* nearest prior activity in time and not necessarily the nearest
* prior activity with a valid transition to this activity. To
* get the nearest prior activity that transitioned to this haRecord
* passed in use getPreviousByTransition( haRecord )
*
* Note: in the process of assembling the execution order array, blanks are left
* in the array where history objects have been deleted. This is done to
* ensure the index in the array and the activity_index of the object remain in synch.
* For that reason, all objects coming out of the execution order array should be
* tested for nil() and not assumed to be the previous record based on activity_index or array index value alone.
*
**/
getPreviousByExecutedOrder: function(haRecord /** ActivityHistoryRecord javascript object **/) {
var previous = null;
if (this._sanityCheck(haRecord)) {
if (Number(haRecord.index)>0) { //not the first activity
for (var i = (Number(haRecord.index) - 1); i >= 0; i--) {
previous = this.executedActivities[this.executionOrder[i]];
if (previous != null && previous.index != null) {
break;
}
}
}
}
return previous;
},
/**
* The purpose of this function is to retrieve the history activity that
* is cached by the wf_history.sys_id provided in the argument and then
* call into getNextByExecutedOrder() with the retrieved javascript object.
* to find the previously executed activity (see comments below)
*
* Sample use:
* var model = new WorkflowModelManager('7b3e01573b130000dada82c09ccf3dcf');
* model.getExecutedHistory();
* var current = model.getActivityHistoryRecordById('d6681d573b130000dada82c09ccf3d10');
* current.debugDump();
* var results = model.getNextByExecutedOrderId(current.sys_id);
* results.debugDump();
*
**/
getNextByExecutedOrderId: function(haRecordSysId /** String wf_history.sys_id **/) {
var haRecord = this.executedActivities[haRecordSysId];
return this.getNextByExecutedOrder(haRecord);
},
/**
* The purpose of this function is to retrieve the history activity that
* executed just after to the one provided in the argument. The
* 'next' status is based on the activity index reflecting the
* nearest prior activity in time and not necessarily the nearest
* next wf_activity the provided activity transitioned to. To
* get the nearest next activity that transitioned to this haRecord
* passed in use getNextByTransitionOrder( haRecord )
*
* Note: in the process of assembling the execution order array, blanks are left
* in the array where history objects have been deletfsed. This is done to
* ensure the index in the array and the activity_index of the object remain in synch.
* For that reason, all objects coming out of the execution order array should be
* tested for nil() and not assumed to be the previous record based on index value alone.
*
* Note: in the process of assembling the execution order array, blanks are left
* in the array where history objects have been deleted. This is done to
* ensure the index in the array and the activity_index of the object remain in synch.
* For that reason, all objects coming out of the execution order array should be
* tested for nil() and not assumed to be the previous record based on activity_index or array index value alone.
*
*
**/
getNextByExecutedOrder: function(haRecord /** ActivityHistoryRecord javascript object **/) {
var next = null;
if (this._sanityCheck(haRecord)) {
for (var i = Number(haRecord.index) + 1; i < this.executionOrder.length; i++) {
next = this.executedActivities[this.executionOrder[i]];
if (next != null && next.index != null) {
break;
}
}
}
return next;
},
/**
* The purpose of this function is to retrieve the history activity that
* executed just after to the one identified by the sys_id provided in the argument.
*
* This function retreives the cached history record associated with the provided wf_history.sys_id
* and then calls getNextByTransition - see notes below for details
*
* The return values are based on what transitions came before the haRecord
* sumbitted and not not necessarily the activities that executed just prior
* to the haRecord in time. To get the activity that executed prior to this
* activity in time use getNextByExecutedOrder
*
* Sample use:
* var model = new WorkflowModelManager('7b3e01573b130000dada82c09ccf3dcf');
* model.getExecutedHistory();
* model.playBack();
*
* var current = model.getActivityHistoryRecordById('d6681d573b130000dada82c09ccf3d10');
* current.debugDump();
* var results = model.getNextByTransitionId(current.sys_id);
* gs.print('COMPLETED NEXT' + results.length );
* for( var i = 0; i < results.length; i++){
* results[i].debugDump();
* }
**/
getNextByTransitionId: function(haRecordSysId /** ActivityHistoryRecord sys_id **/) {
var haRecord = this.executedActivities[haRecordSysId];
return this.getNextByTransition(haRecord);
},
/**
* The purpose of this function is to retrieve the history activity that
* executed just after to the one provided in the argument. The
* 'next' status is based on the .to value of all the transitions that
* are associated with the activity represented in the haRecord.
*
* The return value is a collection of ActivityHistoryRecords that identify
* the argument haRecord.wfaId as their TO activity.
*
* The return values are based on what transitions came before the haRecord
* sumbitted and not not necessarily the activities that executed just prior
* to the haRecord in time. To get the activity that executed prior to this
* activity in time use getNextByExecutedOrder
*
*
**/
getNextByTransition: function(haRecord /** ActivityHistoryRecord javascript object **/) {
if (!this.executionOrder || !haRecord || haRecord.getTransitionCount() == 0) {
return null;
}
var next = [];
var count = haRecord.getTransitionCount();
for (var i = 0; i < count; i++) {
var nextActivity = this._getNextInstanceOfActivity(haRecord.index, haRecord.transitions[i].to);
if (nextActivity != null)
next.push(nextActivity);
}
return next;
},
/**
* The purpose of this function is to retrieve the history activit(y)ies that
* executed just prior to the one provided in the argument. The
* 'next' status is based on the wf_activity.sys_id associated with the activity
* represented in the haRecord existing as a TO in a transition associated with
* any ActivityHistoryRecords that come before the haRecord in the execution sequence
*
* The return value is a collection of ActivityHistoryRecords that identify
* the argument haRecord.wfaId as their TO activity.
*
* The return values are based on what transitions came before the haRecord
* sumbitted and not not necessarily the activities that executed just prior
* to the haRecord in time. To get the activity that executed prior to this
* activity in time use getPreviousByExecutedOrder
*
* see sample use below
*
**/
getPreviousByTransitionId: function(haRecordSysId /** ActivityHistoryRecord sys_id **/) {
var haRecord = this.executedActivities[haRecordSysId];
return this.getPreviousByTransition(haRecord);
},
/**
* The purpose of this function is to retrieve the history activit(y)ies that
* executed just prior to the one provided in the argument. The
* 'next' status is based on the wf_activity.sys_id associated with the activity
* represented in the haRecord existing as a TO in a transition associated with
* any ActivityHistoryRecords that come before the haRecord in the execution sequence
*
* (Differing from getAllTransitionedIntoActivity which will all return TO transitions that
* to the given haRecord in the execution sequence )
*
* The return value is a collection of ActivityHistoryRecords that identify
* the argument haRecord.wfaId as their TO activity.
*
* The return values are based on what transitions came before the haRecord
* sumbitted and not not necessarily the activities that executed just prior
* to the haRecord in time. To get the activity that executed prior to this
* activity in time use getPreviousByExecutedOrder
* sample use
* var model = new WorkflowModelManager('7b3e01573b130000dada82c09ccf3dcf');
* model.getExecutedHistory();
* var current = model.getActivityHistoryRecordById('d6681d573b130000dada82c09ccf3d10');
* current.debugDump();
* model.playBack();
*
* var results = model.getPreviousByTransitionId(current.sys_id);
* gs.print('COMPLETED PREVIOUS' + results.length );
* for( var i = 0; i < results.length; i++){
* gs.print('PRINTING ' + results[i].sys_id );
* results[i].debugDump();
* }
*
**/
getPreviousByTransition: function(haRecord /** ActivityHistoryRecord javascript object **/) {
if (!this.executionOrder ||
!haRecord ||
haRecord.index > this.executionOrder.length ||
(Number(haRecord.index) - 1 < 0)) {
return null;
}
var previousList = [];
for (var i = Number(haRecord.index) - 1; i >= 0; i--) {
var ahdPrevious = this.executedActivities[this.executionOrder[i]];
// Nested RollBack Fix --PRB583844
if (ahdPrevious != null) {
ahdPrevious.rollingBackBy = this.rollbackactivityIndex;
}
if (ahdPrevious != null &&
!ahdPrevious.isRolledBack() &&
ahdPrevious.isIdADestination( haRecord.wfaId ) &&
!this._isWFActivityInList( previousList, ahdPrevious.wfaId)) {
previousList.push(ahdPrevious);
/**
* -- G Turnstile(3) ^ - End
* | |
* A - B
* - D (join) - E task - F script
* - C
* A B C D E F G E F G E F G End
*
* when going back and haRecord is E Task with inputs from is G Turnstile,
* then we can break and not continue to search for previous tasks as
* (in good form) there should be only one entry to a loop.
*
*
*/
if (ahdPrevious.isTurnstile() || ahdPrevious.isARollback() ) {
break;
}
}
}
/**
* Join may not be have been satisfied prior to rollback, but it may have been
* and if so, could have one of it's TO: activities ahead of it, instead of behind
* it. So in that case, look ahead in time until join is satisfied or execution path
* ends ==== doesJoinContainActivity
* Note: this was added as part of STRY0028839 (FDT)
**/
if (haRecord.isJoin() && (previousList.length < haRecord.getJoinExpectedTransitionCount()) ) {
for (var i = Number(haRecord.index); i < this.executionOrder.length; i++) {
var ahdJoinTo = this.executedActivities[this.executionOrder[i]];
if (ahdJoinTo != null &&
!ahdJoinTo.isRolledBack() &&
ahdJoinTo.isIdADestination( haRecord.wfaId ) &&
!this._isWFActivityInList( previousList, ahdJoinTo.wfaId)) {
previousList.push(ahdJoinTo);
// if this is all there could have been for a single execution then stop now
if (previousList.length == haRecord.getJoinExpectedTransitionCount() )
break;
}
}
}
return previousList;
},
/**
* The purpose of this function is to retrieve the history activit(y)ies that
* executed and transitioned into the one represented by the sys_id in the argument. The
* 'next' status is based on the wf_activity.sys_id associated with the activity
* represented in the haRecord existing as a TO in a transition associated with
* any ActivityHistoryRecords that executed in the workflow's history (Differing
* from getPreviousByTransition which will only return TO transitions that
* come before the haRecord in the execution sequence (by time) )
*
* The return value is a collection of ActivityHistoryRecords that identify
* the argument haRecord.wfaId as their TO activity.
**/
getAllTransitionedIntoActivityId: function( haRecordSysId /** ActivityHistoryRecord sys_id **/) {
var haRecord = this.executedActivities[haRecordSysId];
return this.getAllTransitionedIntoActivity(haRecord);
},
/**
* The purpose of this function is to retrieve the history activit(y)ies that
* executed and transitioned into the one provided in the argument. The
* 'next' status is based on the wf_activity.sys_id associated with the activity
* represented in the haRecord existing as a TO in a transition associated with
* any ActivityHistoryRecords that executed in the workflow's history (Differing
* from getPreviousByTransition which will only return TO transitions that
* come before the haRecord in the execution sequence (by time) )
*
* The return value is a collection of ActivityHistoryRecords that identify
* the argument haRecord.wfaId as their TO activity.
*
* The return values are based on all transitions in the executed history collection that transition
* To get the activity that executed prior to this activity in time use getPreviousByExecutedOrder
*
* sample use
*
* var model = new WorkflowModelManager('a143585c3b001000dada82c09ccf3d44');
* model.getExecutedHistory();
* var activity = model.begin;
* gs.print('activity: ' + activity.wfaName + ', transitions: ' + activity.transitions.length);
* while( activity != null){
* gs.print('activity: ' + activity.wfaName + ', transitions: ' + activity.transitions.length);
* var parents = model.getAllTransitionedIntoActivity(activity);
* for( var i = 0; i < parents.length; i++ ){
* gs.print(' --------------- parent activity: ' + parents[i].wfaName );
* }
*
* activity = model.getNextByExecutedOrder( activity );
* }
*
**/
getAllTransitionedIntoActivity: function(haRecord /** ActivityHistoryRecord javascript object **/) {
var toTransitionList = [];
if (!this.executionOrder || !haRecord) {
return toTransitionList;
}
for (var i = 0; i < this.executionOrder.length; i++) {
var ahdToRecord = this.executedActivities[this.executionOrder[i]];
if (ahdToRecord != null &&
ahdToRecord.isIdADestination( haRecord.wfaId ) &&
!this._isWFActivityInList( toTransitionList, ahdToRecord.wfaId)) {
toTransitionList.push(ahdToRecord);
}
}
return toTransitionList;
},
/**
* the purpose of this funciton is to return the list
* of wf_history.sys_ids of all activities that successfully
* executed and were not rolled back or skipped up to the moment the
* function was called.
*
* sample use
* var model = new WorkflowModelManager('ee3e0a053b101000dada82c09ccf3d7c');
* model.getExecutedHistory();
* var finals = model.getFinalExecutedActivityIdList();
* gs.print(' EXECUTION PATH IDs --------------- : ' + finals.length);
*
* for ( var x = 0; x < finals.length; x++ ) {
* gs.print(finals[x] );
* }
*
*
**/
getFinalExecutedActivityIdList: function() {
var executedIds = [];
var executedActivities = this.getFinalExecutedActivityList();
if (executedActivities.isNil())
return executedIds;
for (var i=0; i < executedActivities.length; i++) {
if (executedIds[i].isNil())
continue;
executedIds.push(executedActivities[i].sys_id);
}
return executedIds;
},
/**
* the purpose of this funciton is to return the list
* of wf_history activities of all activities that successfully
* executed and were not rolled back or skipped up to the moment the
* function was called.
*
* sample use
* var model = new WorkflowModelManager('ee3e0a053b101000dada82c09ccf3d7c');
* model.getExecutedHistory();
* var finals = model.getFinalExecutedActivityList();
* gs.print(' EXECUTION PATH ACTIVITIES --------------- : ' + finals.length);
*
* for ( var x = 0; x < finals.length; x++ ) {
* gs.print(finals[x].index + ' - ' + finals[x].wfaName );
* }
*
*
**/
getFinalExecutedActivityList: function() {
var executedCompletes = [];
for (var i=0; i < this.executionOrder.length; i++) {
if ( this.executionOrder[i].isNil() )
continue;
var activity = this.executedActivities[this.executionOrder[i]];
if (activity != null && !activity.isRolledBack() )
executedCompletes.push(activity);
}
return executedCompletes;
},
/**
* Queries the wf_history table by context and retrieves all the
* activities executed in the workflow given by the context
* set in the construction of this object.
*
* This function will produce a list of executed activities in the
* exact order each activity passed through the server side
* ActivityManager.java using the new activity_index to force the order
* coming out of the database.
*
* On its this call will not give the full picture, it needs to load
* and map the transitions.
*
* to get the fully initialized list of executed activities use the
* following code snippet
*
* var model = new WorkflowModelManager('myContextId');
* var activities = model.getExecutedHistory();
*
**/
_getExecutedActivities: function() {
var grA=this._queryExecutedActivities(this.wfContext);
while (grA.next()) {
if (!this.executedActivities[grA.sys_id.toString()]) {
var activityHistory = new ActivityHistoryRecord(grA);
this.executedActivities[ grA.sys_id.toString() ] = activityHistory;
if (grA.activity.activity_definition.js_class_name == 'Begin')
this.begin = activityHistory;
// activities are deleted on Joins, so there could be a break in sequence
while (this.executionOrder.length != activityHistory.index &&
this.executionOrder.length < activityHistory.index ) {
this.executionOrder.push('');
}
this.executionOrder.push(activityHistory.sys_id.toString());
if (activityHistory.isJoin()) {
if (!this.joinsByWfActivityId[activityHistory.wfaId])
this._defineJoinParameters( activityHistory );
activityHistory.addJoinFromActivityIds(this.joinsByWfActivityId[activityHistory.wfaId]);
/** count on being pushed into array in order **/
this.joinsByHistoryRecordIndex.push(activityHistory);
}
}
}
},
_queryExecutedActivities : function(contextId) {
var grA = new GlideRecord('wf_history');
grA.addQuery('context', this.wfContext);
grA.addNotNullQuery('activity_index');
grA.orderBy('activity_index');
grA.query();
return grA;
},
/**
* The purpose of this function is to get the get the sys_ids of the
* wf_activity records that will transition to the ActivityHistoryRecord
* passed in as the argument, assuming that the toActivity has tested to be a Join
* activity.
*
* This functions hashes the Join activity by it's wf_activity.sys_id so that
* it can be accessed during playback.
*
* This function is called during the build up of the cached model, prior to
* playback.
**/
_defineJoinParameters: function(toActivity /** ActivityHistoryRecord javascript object **/) {
var joinIds = [];
var grJT = new GlideRecord('wf_transition');
grJT.addQuery('to', toActivity.wfaId);
grJT.query();
while (grJT.next()) {
joinIds.push(grJT.from.toString());
}
/* store join in name:value pair where name = the wf_activity.sys_id of the Join activity
* and the value is an array of all the sys_ids of wf_activities that are expected to arrive
* at the Join activity in the execution of the workflow
*/
this.joinsByWfActivityId[toActivity.wfaId] = joinIds;
},
/**
* The purpose of this function is to initialize the hash
* of ExecutedTransition javascript objects that contain
* the relevant transitions executed during a workflow
* and to map the transitions to the wf_history record
* that the transition executed FROM. This mapping is
* originally set in the workflow's ActivitManager class
* on the serverside.
*
*
**/
_getExecutedTransitions: function() {
var grT = new GlideRecord('wf_transition_history');
grT.addQuery('context', this.wfContext);
grT.query();
while (grT.next()) {
if (!this.executedTransitions[grT.sys_id.toString()]) {
var transition = new ExecutedTransition( grT );
this.executedTransitions[grT.sys_id.toString()] = transition;
this.executedActivities[ grT.from_activity_history.toString() ].addTransition(transition);
}
}
},
/**
* Starting with begin, and assuming that getExecutedHistory() has been previously called,
* this function will playback the execution of a workflow using the activity_index
* of the wf_history object (set in the workflow ActivityManager) to ensure that the order
* of playback is the order that the activities passed through the workflow engine. This
* is a more reliable execution order than relying on the precision timestamp out of the database.
*
* var model = new WorkflowModelManager('myContextId');
* var activities = model.getExecutedHistory();
* model.playBack();
*
* NOTE: this is used to replay the workflow at this time to fill in executed transitions from timeline.
* this required for rollback to a specific activity. For now all it does is walk the execution and
* print it out, but it will define execution paths as part of the rollback activities.
**/
playBack: function() {
this._log('============================================================================================================');
this._log('WORK FLOW PLAY BACK ');
if (this.begin.isNil())
return;
/** starting with Begin, recurse through cached model to playback execution of workflow **/
this._findExecutionPath(this.begin);
},
/**
* Beginning with the activity passed in, this function traverses the actvities indicated
* by the transitions in each activity and progresses to the end End. Of the workflow.
*
* This is called from rollback and assumes the following initializing functions have
* already been successfully called.
*
* var model = new WorkflowModelManager('myContextId');
* var activities = model.getExecutedHistory();
*
* This function collects the IDs of all activities from the point of the start activity
*
*/
_findLegOfExecutionPath: function(startActivity /** ActivityHistoryRecord **/,
activityTransitionPath) {
for (var i = 0; i < startActivity.getTransitionCount(); i++) {
var nextHistoryRecord = ( this.joinsByWfActivityId[startActivity.transitions[i].to] ) ?
this._findWhereActivityJoinedByWfActivityId(startActivity.transitions[i].from ) :
this._getNextInstanceOfActivity( startActivity.index, startActivity.transitions[i].to );
if (nextHistoryRecord) {
activityTransitionPath.push(nextHistoryRecord);
/** recurse from current record **/
this._findLegOfExecutionPath( nextHistoryRecord, activityTransitionPath );
}
}
},
/**
* Beginning with the activity passed in, this function traverses the actvities indicated
* by the transitions in each activity and progresses to the end End. Of the workflow.
*
* This is called from playback and is assumes the following initializing functions have
* already been successfully called.
*
* var model = new WorkflowModelManager('myContextId');
* var activities = model.getExecutedHistory();
*
*/
_findExecutionPath: function(startActivity /** ActivityHistoryRecord **/) {
for (var i = 0; i < startActivity.getTransitionCount(); i++) {
var nextHistoryRecord = ( this.joinsByWfActivityId[startActivity.transitions[i].to] ) ?
this._findClosestJoinToWfActivityId(startActivity.transitions[i].from ) :
this._getNextInstanceOfActivity( startActivity.index, startActivity.transitions[i].to );
if (nextHistoryRecord) {
if (nextHistoryRecord.isJoin()) {
if (!nextHistoryRecord.isJoinSatisfied())
continue;
}
/** gs.print('BEGIN ==== FROM - TO');
startActivity.debugDump();
nextHistoryRecord.debugDump();
gs.print('=======================');
gs.print(''); **/
/** recurse from current record **/
this._findExecutionPath( nextHistoryRecord );
}
}
},
/**
* In the instances of loops and rollbacks, it is possible for the same
* wf_activity.sys_id to be identified as a TO or a FROM activity.
*
* This functions uses the activity_index(ed) execution history to
* identify the specific instance of the wf_activity given the
* current index of activity being output in a playback.
*
* While not explicitly called in the following example, the
* function is a called from inside the playBack() call below
* and does require the first two initialization functions to have
* successfully run
*
* var model = new WorkflowModelManager('myContextId');
* var activities = model.getExecutedHistory();
* activities.playBack(); ==> calls _getNextInstanceOfActivity in it's recursion.
*
**/
_getNextInstanceOfActivity: function(currentIndex, nextActivityId) {
var nextHistoryRecord = null;
for (var i=currentIndex; i < this.executionOrder.length; i++) {
var activity = this.executedActivities[this.executionOrder[i]];
if (activity.isNil())
continue;
if (activity.wfaId.isNil())
continue;
if (activity.wfaId == nextActivityId) {
nextHistoryRecord = activity;
break;
}
}
return nextHistoryRecord;
},
/**
* When building the cache of an executed(ing)'s workflow history, the
* function _defineJoinParameters() caches all the instances Joins executed in the workflow
* in the order in which they were executed and caches it by the wf_history.sys_id of
* the Join history record.
*
* In the process of progressing through a workflow that has a join that could have been
* rolled back or that may occur in a loop and using the cache described above.
* This function looks through the activities cached as Joins and submits the id
* of the from activity. If it finds the join that
*
*
**/
_findWhereActivityJoinedByWfActivityId: function(fromActivityId /** wf_activity sys_id **/) {
var selectedJoinActivity = null;
for (var i = 0; i < this.joinsByHistoryRecordIndex.length; i++) {
if (this.joinsByHistoryRecordIndex[i].isJoinSatisfied())
continue;
if (this.joinsByHistoryRecordIndex[i].isJoinWaitingForActivity(fromActivityId)) {
this.joinsByHistoryRecordIndex[i].addArrivedActivityToJoin(fromActivityId);
selectedJoinActivity = this.joinsByHistoryRecordIndex[i];
break;
}
}
return selectedJoinActivity;
},
/**
* When building the cache of an executed(ing)'s workflow history, the
* function _defineJoinParameters() caches all the instances Joins executed in the workflow
* in the order in which they were executed and caches it by the wf_history.sys_id of
* the Join history record.
*
* In the process of progressing through a workflow that has a join that could have been
* rolled back or that may occur in a loop and using the cache described above.
* This function looks through the activities cached as Joins and submits the id
* of the from activity. If the Join is waiting for that sys_id, then it is added
* and the state of the join is adjusted. If there are multiple Joins or multiple instances
* of the same Join due to loop or rollback, the join that is closest in time, that is
* not satisfied when the fromActivity passes through is assumed to be the correct Join
*
*
**/
_findClosestJoinToWfActivityId: function(fromActivityId /** wf_activity sys_id **/) {
var selectedJoinActivity = null;
for (var i = 0; i < this.joinsByHistoryRecordIndex.length; i++) {
if (this.joinsByHistoryRecordIndex[i].doesJoinContainActivity(fromActivityId)) {
selectedJoinActivity = this.joinsByHistoryRecordIndex[i];
break;
}
}
return selectedJoinActivity;
},
//================================================================================================================================================
//ROLLBACK LOGIC - Rolls back from a specific activity to a specific activity instead of rolling all the way back to the last rolled back activity
//================================================================================================================================================
rollbackTransitionHistory: function(fromHistoryRecord /** wf_history.sys_id **/,
toMostRecentWfaId /** wf_activity.sys_id **/,
rollBackactivityIndex /** index of the wf_executing rollback **/) {
this._log('============================================================================================================');
this._log('ROLLING BACK TRANSITIONS BEGIN FROM INDEX ' + rollBackactivityIndex);
var toIds = this._getRollbackToActivityIds( toMostRecentWfaId );
var rollBackActivities = [];
var rollBackTransitions = [];
var rollbackConditionCriteria = [];
//fix for Nested RollBack -- PRB583844 (SYT)
this.rollbackactivityIndex = rollBackactivityIndex;
if (toIds != null) {
for (var i = 0; i < toIds.length; i++) {
this._rollHistoryBackFromTo( fromHistoryRecord, toIds[i], rollBackActivities, rollBackTransitions );
}
this._updateTransitionRecords( rollBackTransitions, rollBackactivityIndex );
this._updateWfHistoryRecords( rollBackActivities, rollBackactivityIndex );
rollbackConditionCriteria = this._buildRollbackQueryCondition(rollBackActivities);
this._updateExecuting( rollBackActivities, rollBackactivityIndex, rollbackConditionCriteria );
this._resetApprovalsAndTasks( rollBackActivities, rollbackConditionCriteria );
this._log('============================================================================================================');
this._log('ROLLING BACK TRANSITIONS - COMPLETE ' );
}
},
_updateWfHistoryRecords: function(rollBackActivities, rollBackactivityIndex ) {
this._log('UPDATING HISTORY RECORDS');
var activityIds = [];
for (var i = 0; i < rollBackActivities.length; i++) {
//To fix nested rollback issue STRY0127051 (SYT)
/** Need to consider only history records with no rolledBackBy value in it. */
if(rollBackActivities[i].rolledBackBy == 0 || rollBackActivities[i].rolledBackBy.isNil()) {
activityIds.push( rollBackActivities[i].sys_id );
rollBackActivities[i].rolledBackBy = rollBackactivityIndex;
}
}
var gr = new GlideRecord('wf_history');
gr.addQuery('sys_id', "IN", activityIds);
gr.query();
while (gr.next()) {
gr.setValue('rolled_back_by', rollBackactivityIndex);
gr.setValue('state', 'restart');
gr.update();
}
},
/**
* This was added as part of STRY0028839 to have a common place to
* build the "IN"A criteria on for getting executing and glide records
* by activity definition id
*
*/
_buildRollbackQueryCondition: function( rollBackActivities ) {
var activityIds = [];
for (var i = 0; i < rollBackActivities.length; i++) {
activityIds.push(rollBackActivities[i].wfaId);
}
return activityIds;
},
_updateExecuting: function( rollBackActivities, rollBackactivityIndex, activityIds ) {
this._log('UPDATING EXECUTING RECORDS');
this._cancelExecuting(activityIds, rollBackactivityIndex);
},
_updateTransitionRecords: function(rollBackTransitions, rollBackactivityIndex) {
var transitionIds = [];
for (var i = 0; i < rollBackTransitions.length; i++) {
transitionIds.push(rollBackTransitions[i].sys_id);
rollBackTransitions[i].rolledBackBy = rollBackactivityIndex;
}
var gr = new GlideRecord('wf_transition_history');
gr.addQuery('sys_id', "IN", transitionIds);
gr.query();
while (gr.next()) {
gr.setValue('rolled_back', true);
gr.setValue('rolled_back_by', rollBackactivityIndex);
gr.update();
}
},
_getRollbackToActivityIds: function( wfaId /** wf_activity.sys_id **/){
var toActivityIds = [];
var grT = new GlideRecord('wf_transition');
grT.addQuery('from', wfaId);
grT.query();
while (grT.next()) {
toActivityIds.push( grT.to.toString() );
}
return toActivityIds;
},
_rollHistoryBackFromTo: function( fromActivityId /** wf_history_id **/,
toWfActivityId /** wf_activity_id **/,
rollBackActivities,
rollBackTransitions ) {
this._log('ESTABLISHING ROLLBACK PATH - rollHistoryBackFromTo');
var fromActivity = this.executedActivities[fromActivityId]
if (fromActivity != null) {
rollBackActivities.push(fromActivity);
if (fromActivity.isParent())
this._addChildrenToRollback(fromActivity, rollBackActivities);
this._collectRollBackActivities(fromActivity, toWfActivityId, rollBackActivities);
this._collectRollBackTransitions(rollBackActivities, rollBackTransitions);
}
},
_collectRollBackTransitions: function(rollBackActivities, rollBackTransitions) {
this._log('COLLECTING ELIGIBLE TRANSITIONS');
for (var i = 0; i < rollBackActivities.length; i++) {
for (var t = 0; t < rollBackActivities[i].transitions.length; t++) {
rollBackTransitions.push(rollBackActivities[i].transitions[t]);
}
}
},
_getIndentLevel: function() {
if (!this.level)
this.level = 0;
this.level++;
var indent = '-';
for (var i=0; i < this.level; i++ ) {
indent = indent + '-';
}
return indent;
},
/**
* Starting from the the result of _findRollbackToDestination
*
*/
_collectRollBackActivities: function(fromActivity, toWfActivityId, rollBackActivities) {
// find the nearest instance of the toWfActivity look back through transitions
var foundActivity = [];
this._findRollbackToDestination(fromActivity, toWfActivityId, foundActivity);
// if not found return
if (foundActivity.length == 0) {
return;
}
var activityTransitionPath = [];
activityTransitionPath.push(foundActivity[0]);
this._findLegOfExecutionPath(foundActivity[0], activityTransitionPath);
if (activityTransitionPath) {
for (var i=0; i < activityTransitionPath.length; i++) {
this.fromActivityFound = activityTransitionPath[i].wfaId.toString() == this.fromActivity.toString();
if (!activityTransitionPath[i].isRolledBack()) {
this._addToRolledBackList(activityTransitionPath[i], rollBackActivities);
if (activityTransitionPath[i].isParent()) {
this._addChildrenToRollback(activityTransitionPath[i], rollBackActivities);
}
}
// this was modified as part of STRY0028839 to stop looking in history if we have walked
// back far enough to find the TO activity of the Rollback Transition arrow
if (this.fromActivityFound) {
break;
}
}
}
},
/** Walks back through all the transitions that have happened between the Rollback To activity
* and the destination, to find the nearest instance of the To: activity in the execution path
* relative to the current instance of Rollback To: activity.
*
* Added for STRY0028839 (FDT)
* returns null if activity was not found in rollback path
*/
_findRollbackToDestination: function(fromActivity, toWfActivityId, foundActivity) {
var level = this._getIndentLevel();
this._log(level);
this._log( level + ' LOCATING ROLLBACK TO ACTIVITY FOR ' + fromActivity.wfaName + ' AT INDEX ' + fromActivity.index + ' toActivity Found ' + this.toActivityFound);
if (this.toActivityFound)
return;
var previousActivities = this.getPreviousByTransition(fromActivity);
if (previousActivities != null ) {
for (var i=0; i < previousActivities.length; i++) {
this.toActivityFound = previousActivities[i].wfaId.toString() == toWfActivityId.toString();
// this was modified as part of STRY0028839 to stop looking in history if we have walked
// back far enough to find the TO activity of the Rollback Transition arrow
if (this.toActivityFound) {
foundActivity.push(previousActivities[i]);
return;
}
this._findRollbackToDestination(previousActivities[i], toWfActivityId, foundActivity);
}
}
// activity was not found in rollback path
return null;
},
_addToRolledBackList: function(hraRecord, rollBackActivities) {
var alreadyAdded = false;
for (var i = 0; i < rollBackActivities.length; i++) {
alreadyAdded = hraRecord.sys_id == rollBackActivities[i].sys_id;
if (alreadyAdded)
break;
}
if (!alreadyAdded)
rollBackActivities.push(hraRecord);
},
/**
* get the sys_id of the wf_history record for the rollback activity that is associated with hraSys_id.
* Associated meaning "was rolled back by" or "is the rollback itself".
* In other words, you can pass it either any record that got rolled back, or the rollback itself, and it will give you the rollback sys_id.
*/
getRollBack : function(hraSys_id /** wf_history.sys_id **/) {
var hraRecord = this.executedActivities[hraSys_id];
var index=hraRecord.index;
/**
* Fix for Nested Rollback STRY0127051 (SYT)
*Needed to store rolledBackBy value into rollingBackBy of ActivityHistoryRecord object to pass the condition in isRolledBack() function.
*While Highlighting paths,isRolledBack() function should return true, when rolledBackBy is set to activity index.
*/
hraRecord.rollingBackBy = hraRecord.rolledBackBy;
if (hraRecord.isRolledBack()) {
index=hraRecord.rolledBackBy;
}
var rbId=this.executionOrder[index];
gs.log('rollBack = '+index + '(' + rbId+')');
return rbId;
},
/**
* The purpose of this function is to return a list of sys_ids that represent activities
* that have been rolled back by the wf_history record sys_id passed in as the argument.
*
* the use of this activity is demonstrated in the following example.
*
* var model = new WorkflowModelManager('cf555c823b330000dada82c09ccf3d96')
* model.getExecutedHistory();
* var activities = model.getRolledBackActivityIdList('e7569c823b330000dada82c09ccf3d4a' );
*
* for( var i = 0; i < activities.length; i++ ){
* var record = model.getActivityHistoryRecordById( activities[i]);
* record.debugDump();
* }
*
*/
getRolledBackActivityIdList: function( hraSys_id /** wf_history.sys_id **/){
var rolledBackActivities = [];
var hraRecord = this.executedActivities[hraSys_id];
for (var i = 0; i < this.executionOrder.length; i++) {
var act = this.executedActivities[ this.executionOrder[i] ];
if (act.rolledBackBy == hraRecord.index) {
rolledBackActivities.push(this.executionOrder[i]);
}
}
return rolledBackActivities;
},
/**
* when rolling back, we want to stop at the instance of
* the wfaId closest to the point of rollback, and once
* found, stop looping to find it. In the case where there is
* things have rolledback and re-executed over and over the rollback
* will not rollback the entire loop. If the whole loop needs to
* rollback, insert and activity before the entry to the loop when
* designing the workflow.
*
*
*/
_isWFActivityInList: function(list /** history record **/,
wfaId /** wf_activity.sys_id **/){
if (list == null || wfaId.isNil())
return;
var isInList = false;
for (var i =0; i< list.length; i++) {
isInList = list[i].wfaId == wfaId;
if (isInList)
break;
}
return isInList;
},
/**
* When a workflow uses a turnstile to loop,
*
*
*/
_isWFActivityInALoop: function(list /** history record **/,
wfaId /** wf_activity.sys_id **/){
if (list == null || wfaId.isNil())
return;
var isInList = false;
for (var i =0; i< list.length; i++) {
isInList = list[i].wfaId == wfaId;
if (isInList)
break;
}
return isInList;
},
/**
* Activities like ApprovalCoordinator have children that do not
* have transitions, those children need to be rolled back if the
* parent is rolling back, so search for them in the executed activities
* and add them to the rollbackActivities list
* this was modified as part of STRY0028839 (FDT) to not go to the wf_history table
* but to walk through executed and select the activities closest to the
* parent in history using the activity_index.
**/
_addChildrenToRollback: function(parent, rollbackActivities) {
if (this.activityIds.length == 0)
return;
for (var i = 0; i < this.executionOrder.length; i++) {
// joins can leave blanks in order, as can activities that were deleted like timers
if (this.executionOrder[i].isNil())
continue;
var activity = this.executedActivities[this.executionOrder[i]];
if (activity.parent == parent.wfaId && !activity.isRolledBack())
rollbackActivities.push(activity);
}
},
_cancelExecuting: function( rollBackWfActivityIds, rollBackactivityIndex ) {
this._log('====== SET CANCEL EXECUTING ============================== ');
if (rollBackWfActivityIds.length == 0)
return;
var rollBackSysId = rollBackactivityIndex > 0 && rollBackactivityIndex < this.executionOrder.length ?
this.executionOrder[rollBackactivityIndex] : null;
if (rollBackSysId == null)
return;
var gr = new GlideRecord('wf_executing');
gr.addQuery('context', activity.context);
gr.addQuery('sys_id', '!=', rollBackSysId); // don't delete the rollback activity we are executing
gr.addQuery('activity', "IN", rollBackWfActivityIds);
gr.deleteMultiple();
},
_resetApprovalsAndTasks: function( rollBackWfActivityIds, activityIds ) {
this._log('====== SET APPROVALS ============================== ');
if (this.activityIds.length == 0)
return;
var gr = new GlideRecord('sysapproval_group');
gr.addQuery('parent', current.sys_id);
gr.addQuery('wf_activity', "IN", activityIds);
gr.addQuery('approval', '!=', 'not requested');
gr.query();
while (gr.next()) {
gr.setValue('active', 'true');
gr.setValue('approval', 'not requested');
gr.update();
}
var mu = new GlideMultipleUpdate('sysapproval_approver');
var qc = mu.addQuery('sysapproval', current.sys_id);
qc.addOrCondition('document_id', current.sys_id);
mu.addQuery('wf_activity', "IN", activityIds);
mu.addQuery('state', '!=', 'not requested');
mu.setValue('state', 'not requested');
mu.execute();
mu = new GlideMultipleUpdate('task');
mu.addQuery('parent', current.sys_id);
mu.addQuery('wf_activity', "IN", activityIds);
mu.addQuery('state', '!=', '-5');
mu.setValue('state', '-5');
mu.setValue('work_end', '');
mu.setValue('active', 'true');
mu.execute();
},
//==========================================================================================
// DEBUG TOOLS
//==========================================================================================
_log: function(message) {
if (typeof workflow == "undefined" || message == null)
return;
workflow.debug(message);
},
/**
* Intended as a debug assist when working with an executed(ing) workflow. This
* call is useful from the scripts - background window to see the
* output of and relationships between cached values of a workflow's execution.
*
* to see the output
*
* var model = new WorkflowModelManager('myContextId');
* model.getExecutedHistory();
* model.dump();
*
*/
dump: function() {
this.dumpTransitions();
this.dumpActivities();
this.dumpExecutionOrder();
this.dumpJoins();
},
dumpJoins: function() {
gs.print('============================================================================================================');
gs.print('JOINS ' + this.joinsByHistoryRecordIndex.length);
for (var i=0; i < this.joinsByHistoryRecordIndex.length; i++) {
this.joinsByHistoryRecordIndex[i].debugDump();
}
},
dumpTransitions: function() {
gs.print('============================================================================================================');
gs.print('TRANSITIONS ');
for (var sys_id in this.executedTransitions) {
this.executedTransitions[sys_id].debugDump();
}
},
dumpActivities: function(){
gs.print('============================================================================================================');
gs.print('HISTORY RECORDS ');
for (var sys_id in this.executedActivities) {
this.executedActivities[sys_id].debugDump();
}
},
dumpExecutionOrder: function(){
gs.print('============================================================================================================');
gs.print('ACTIVITY EXECUTION ORDER');
for ( var i = 0; i < this.executionOrder.length; i++) {
gs.print('i = ' + i + ' = ' + this.executionOrder[i] );
}
},
type: WorkflowModelManager
}
//==========================================================================================
// JAVASCRIPT OBJECTS FOR CAPTURING MODEL AND STATE
//==========================================================================================
/** class that captures the relevant information
about the activity represented in an wf_history
glide record
**/
var ActivityHistoryRecord = Class.create();
ActivityHistoryRecord.prototype = {
initialize: function( grA /** GlideRecord('wf_history) **/){
this.sys_id = grA.sys_id.toString();
this.index = grA.activity_index.toString();
this.startTime = grA.started.getGlideObject().getNumericValue();
this.endTime = grA.ended.getGlideObject().getNumericValue();
this.wfaId = grA.activity.toString(); /* wf_activity id field */
this.wfaIsParent = grA.activity.is_parent; /* wf_activity parent field, added as part of STRY0028839 (FDT) */
this.parent = grA.activity.parent.toString(); /* wf_activity parent field, added as part of STRY0028839 (FDT) */
this.wfaName = grA.activity.name; /* wf_activity name field */
this.adId = grA.activity.activity_definition.sys_id.toString(); /* activity definition id */
this.adName = grA.activity.activity_definition.name; /* activity definition name */
this.transitions = []; /* transitions from this activity */
this.rolledBackBy = grA.rolled_back_by.toString(); /* Activity index that rolled this back */
/**************** JOIN MANAGEMENT **************************/
this.ARRIVED = true;
this.NOT_ARRIVED = false; /* constant used for joinFromActivity.arriveState value */
this.joinFroms = []; /* an array of javascript objects constructed as joinFromActivity.id and joinFromActivity.arriveState **/
this.joinSatisfied = false; /* constant used for joinFromActivity.arriveState value */
// fix for Nested RollBack PRB583844
this.rollingBackBy = 0; /* used to store the activity index of the Roll BackTo activity which is in execution currently **/
},
isJoin: function() {
return this.adName=='Join';
},
isTurnstile: function() {
return this.adName=='Turnstile';
},
isARollback: function() {
return this.adName.indexOf('Rollback') != -1 ;
},
// added as part of STRY0028839 to support managing child activities (i.e. approvals in approval coordinator)
// that will not have their own transitions
isParent: function() {
return (this.wfaIsParent) ? this.wfaIsParent : false;
},
isRolledBack: function() {
var rolledBack = false;
// fix for Nested RollBack -- PRB583844
if (this.rolledBackBy.isNil() || this.rolledBackBy == 0 || this.rollingBackBy != this.rolledBackBy) {
rolledBack = false;
} else {
rolledBack = true;
}
return rolledBack;
},
/**
* As model is cached by _getExecutedTransitions(), this
* function adds transitions that have gone FROM this
* activity towards the .to activity
*
*/
addTransition: function(inTransition /** ExecutedTransition **/) {
this.transitions.push(inTransition);
},
getTransitionCount: function() {
return (this.transitions) ? this.transitions.length : 0;
},
getJoinExpectedTransitionCount: function() {
return (this.joinFroms) ? this.joinFroms.length : 0;
},
/**
* This is called as part of building the cached model in memory. It is
* not called during playBack. This seeds all the expected
* wf_activity sys_ids that are expected to pass through
* this join and sets their arriveState = false. This state
* is flipped to true as each expected activity transitions
* to this instance of the Join.
**/
addJoinFromActivityIds: function(activityIds /* array of wf_activity.sys_ids that are headed towards join*/) {
for (var i = 0; i < activityIds.length; i++) {
var joinFromActivity = {};
joinFromActivity.id = activityIds[i];
joinFromActivity.arriveState = this.NOT_ARRIVED;
this.joinFroms.push(joinFromActivity);
}
},
/**
* This is called during playback, as expected wf_activities
* flow to the join. This changes the arrivedState of already known
* ids to true. As soon as it does, it tests to see if the join
* is satisfied, if it is, the model walk can continue, if not
* it should continue to next appropriate transition
**/
addArrivedActivityToJoin: function(activityId) {
for (var i=0; i < this.joinFroms.length; i++) {
if (this.joinFroms[i].id == activityId)
this.joinFroms[i].arriveState = this.ARRIVED;
}
return this.isJoinSatisfied();
},
/**
* This function tests the incoming wf_activity sys_id, presumed to
* be seeded in this history record. If it is,
* it is tested to see if it has already been through, if it has
* then this join is not waiting on this activity and it should look
* further downstream for the one that is waiting. This test is
* is called in sequence as the model is walked, so it does presume
* in the instances of rollbacks and loops, if the sys_id is in here
* and the flag is true, this waiting Join is further up the sequence
* this sort of check is required as the history records of Joins
* are removed from the history table, and so it is possible for a
* record to be in transition table with no match in the corresponding
* history table, but is still an appropriate match for an earlier executed
* instance of the same Join.
*/
isJoinWaitingForActivity: function(activityId) {
/* FDT STRY0028839 defaulted to false instead of true */
var isWaiting = false;
for (var i=0; i < this.joinFroms.length; i++) {
if (this.joinFroms[i].id == activityId) {
/* if it has not arrived arriveState = false, then we're waiting isWaiting = true */
isWaiting = !this.joinFroms[i].arriveState;
break;
}
}
return isWaiting;
},
/**
* This function tests the incoming wf_activity sys_id, presumed to
* be seeded in this history record. If it is,
* it is tested to see if it is an activity that would come through this join.
* If it has
*/
doesJoinContainActivity: function(activityId) {
var contains = false;
for (var i=0; i < this.joinFroms.length; i++) {
if (this.joinFroms[i].id == activityId) {
contains = true
break;
}
}
return contains;
},
/**
* This function returns the sys_ids of the history
* records that transition to this Join activity
* that have already come through
*/
getSatisfiedJoinActivities: function(){
var ids = [];
for (var i=0; i < this.joinFroms.length; i++) {
if (this.joinFroms[i].arriveState) {
ids.push(this.joinFroms[i].id );
}
}
return ids;
},
/**
* This function returns the sys_ids of the history
* records that transition to this Join activity
* that the join is still waiting for
*/
getUnSatisfiedJoinActivities: function() {
var ids = [];
for (var i=0; i < this.joinFroms.length; i++) {
if (!this.joinFroms[i].arriveState) {
ids.push(this.joinFroms[i].id);
}
}
return contains;
},
/**
* As model is cached the transitions that
* Called during playback when the transition.to sys_id of a transition
* points to an activity that is a join. This function examines the value
* of the arriveState
*
*/
isJoinSatisfied: function() {
var satisfied = false;
for (var i=0; i < this.joinFroms.length; i++) {
satisfied = this.joinFroms[i].arriveState;
if (!satisfied)
break;
}
return satisfied;
},
/**
* The purpose of this function is
* to determine if the sys_id passed in
* is a destination of any of the
* transitions associated with this
* instance of an ActivityHistoryRecord
*
**/
isIdADestination: function(ahrSys_id) {
var isDestination = false;
if (this.getTransitionCount() == 0 || ahrSys_id.isNil())
return isDestination;
for (var i = 0; i < this.transitions.length; i++) {
if (this.transitions[i].to == ahrSys_id) {
isDestination = true;
break;
}
}
return isDestination;
},
debugDump: function(){
gs.print('');
gs.print('WF HISTORY RECORD:');
gs.print(' History ID: ' + this.sys_id);
gs.print(' History INDEX: ' + this.index);
gs.print(' History START TIME: ' + this.startTime);
gs.print(' History END TIME: ' + this.endTime);
gs.print(' History INDEX: ' + this.index);
gs.print(' WF Activity ID: ' + this.wfaId);
gs.print(' WF Activity NAME: ' + this.wfaName);
gs.print(' WF Activity DEF ID: ' + this.adId);
gs.print('WF Activity DEF NAME: ' + this.adName);
gs.print(' Transition Count: ' + this.getTransitionCount());
gs.print('Roll Back Originator: ' + this.rolledBackBy );
gs.print(' Is Turnstile (loop): ' + this.isTurnstile() );
gs.print(' Is Join: ' + this.isJoin());
for (var i=0; i < this.joinFroms.length; i++) {
gs.print(' joined activities ' + this.joinFroms[i].id + ' : ' + this.joinFroms[i].arriveState);
}
},
getLogString: function(){
var logStatement = 'HISTORY RECORD:' +
' History ID:' + this.sys_id +
' History INDEX:' + this.index +
' History START TIME:' + this.startTime +
' History END TIME:' + this.endTime +
' WF Activity ID:' + this.wfaId +
' WF Activity NAME:' + this.wfaName +
' WF Activity DEF ID:' + this.adId +
' WF Activity DEF NAME:' + this.adName +
' Transition Count: ' + this.getTransitionCount() +
' Is Turnstile (loop) : ' + this.isTurnstile() +
' Is Join: ' + this.isJoin();
if (this.isJoin()) {
' Joined Activities: ';
var joins = ' : ';
for (var i=0; i < this.joinFroms.length; i++) {
joins+= this.joinFroms[i].id + ' - Executed ' + this.joinFroms[i].arriveState + ' : ';
}
logStatement += joins;
}
return logStatement;
},
type:ActivityHistoryRecord
}
var ExecutedTransition = Class.create();
ExecutedTransition.prototype = {
initialize: function(grT /** new GlideRecord('wf_transition_history' **/) {
this.sys_id = grT.sys_id.toString(); /* sys_id of transition record **/
this.ahrId = grT.from_activity_history.toString();
this.to = grT.transition.to.toString(); /* wf_activity reference in the TO slot of this transition **/
this.from = grT.transition.from.toString(); /* wf_activity reference in the FROM slot of this transition **/
this.rolledBack = grT.rolled_back.toString(); /* true or false state of rolled back */
this.rolledBackBy = grT.rolled_back_by.toString(); /*activityRecord that rolled back this record */
},
debugDump: function() {
gs.print('');
gs.print(' TRANSITION RECORD:');
gs.print(' Transition ID: ' + this.sys_id);
gs.print(' WF History RECORD: ' + this.ahrId);
gs.print(' FROM Activity: ' + this.from);
gs.print(' TO Activity: ' + this.to);
gs.print(' Rolled Back: ' + this.rolledBack);
gs.print(' Roll Back Originator: ' + this.rolledBackBy );
},
type:ExecutedTransition
}
Sys ID
3d68dd633b130000dada82c09ccf3d28