Skip to content

Commit b650447

Browse files
authored
Flow approval script (ServiceNowDevProgram#883)
* Create ApprovalRuleBuilder.js * Create readme.md
1 parent a43ccb9 commit b650447

File tree

2 files changed

+328
-0
lines changed

2 files changed

+328
-0
lines changed
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
var ApprovalRuleBuilder = Class.create();
2+
3+
// Valid Rulesets
4+
ApprovalRuleBuilder.RULESET_APPROVES = "Approves";
5+
ApprovalRuleBuilder.RULESET_REJECTS = "Rejects";
6+
ApprovalRuleBuilder.RULESET_APPROVEREJECTS = "ApproveReject";
7+
8+
// Valid Rules
9+
ApprovalRuleBuilder.RULE_ANY = "Any"; // Anyone approves
10+
ApprovalRuleBuilder.RULE_ALL = "All"; // All users approve
11+
ApprovalRuleBuilder.RULE_RESPONDED = "Res"; // All responded and anyone approves
12+
ApprovalRuleBuilder.RULE_PERCENT = "%"; // % of users approve
13+
ApprovalRuleBuilder.RULE_NUMBER = "#"; // number of users approve
14+
15+
ApprovalRuleBuilder.prototype = {
16+
17+
/**
18+
* Main ApprovalRuleBuilder class
19+
* @example
20+
* var query = new ApprovalRuleBuilder();
21+
* @constructor
22+
* @param {boolean} (optional) Enable debug output
23+
*/
24+
initialize: function (debug) {
25+
this._debug = debug | false;
26+
this._approval_rules = '';
27+
28+
// keep track of required steps
29+
this._ruleset_added = false; // additional rulesets can be added once the current ruleset is complete (has rules and users/groups)
30+
this._rule_added = false; // rule can only be added to an open ruleset, Or/And rules can be added once users/groups have been set for current rule
31+
this._users_added = false; // users/groups can only be added to an open rule but not if manual users has been set
32+
this._manual_users = false; // manual users cannot be added to a rule if users/groups already applied
33+
34+
this._users = []; // temporary store for users, allows multiple .addUsers calls
35+
this._groups = []; // temporary store for groups, allows multiple .addGroups calls
36+
},
37+
38+
/**
39+
* Starts a new ruleset
40+
* @example
41+
* var rules = new ApprovalRuleBuilder()
42+
* .addRuleSet(ApprovalRuleBuilder.RULESET_APPROVES)
43+
* @param {string} ruleset to create (APPROVES|REJECTS|APPROVEREJECTS)
44+
* @returns {ApprovalRuleBuilder} New ApprovalRuleBuilder containing approval rules
45+
*/
46+
addRuleSet: function (ruleset) {
47+
48+
if (!this._isValidRuleSet(ruleset)) {
49+
NiceError.raise('Unknown ruleset (' + ruleset + ')');
50+
}
51+
52+
if (this._approval_rules != '' && !this._users_added) {
53+
NiceError.raise('Cannot add ruleset (' + ruleset + ') as previous set not complete');
54+
}
55+
56+
this._commitUsersAndGroups();
57+
58+
if (this._approval_rules != '') {
59+
if (this._debug) gs.info('- [RuleSet] Or' + ruleset);
60+
this._approval_rules += "Or";
61+
} else {
62+
if (this._debug) gs.info('- [RuleSet] ' + ruleset);
63+
}
64+
65+
this._approval_rules += ruleset;
66+
67+
this._ruleset_added = true;
68+
this._rule_added = false;
69+
this._users_added = false;
70+
this._manual_users = false;
71+
72+
return this;
73+
},
74+
75+
/**
76+
* Starts a new rule
77+
* @example
78+
* var rules = new ApprovalRuleBuilder()
79+
* .addRuleSet(ApprovalRuleBuilder.RULESET_APPROVES)
80+
* .addRule(ApprovalRuleBuilder.RULE_ANY)
81+
* @param {string} rule to create (ANY|ALL|RES|%|#)
82+
* @param {integer} number to use for percentage and number of users rule (optional)
83+
* @returns {ApprovalRuleBuilder} New ApprovalRuleBuilder containing approval rules
84+
*/
85+
addRule: function (rule, value) {
86+
value = value | 0;
87+
88+
if (!this._isValidRule(rule)) {
89+
NiceError.raise('Unknown rule (' + rule + ')');
90+
}
91+
92+
if (!this._ruleset_added) {
93+
NiceError.raise('Cannot add rule (' + rule + ') as no ruleset defined.');
94+
}
95+
96+
if (this._rule_added) {
97+
NiceError.raise('Cannot add rule (' + rule + '), use addAndRule or addOrRule instead');
98+
}
99+
100+
if (rule == ApprovalRuleBuilder.RULE_PERCENT || rule == ApprovalRuleBuilder.RULE_NUMBER) {
101+
if (value > 0) {
102+
this._approval_rules += value;
103+
} else {
104+
NiceError.raise("Cannot add rule (' + rule + ') as no value specified");
105+
}
106+
}
107+
108+
if (this._debug) gs.info('-- [Rule] ' + (value > 0 ? value : '') + rule);
109+
110+
this._approval_rules += rule;
111+
112+
this._rule_added = true;
113+
this._users_added = false;
114+
this._manual_users = false;
115+
return this;
116+
},
117+
118+
/**
119+
* Adds users to a rule
120+
* @example
121+
* var rules = new ApprovalRuleBuilder()
122+
* .addRuleSet(ApprovalRuleBuilder.RULESET_APPROVES)
123+
* .addRule(ApprovalRuleBuilder.RULE_ANY)
124+
* .addUsers(['a8f98bb0eb32010045e1a5115206fe3a','a2826bf03710200044e0bfc8bcbe5ded'])
125+
* @param {array} sys_id's of users to add
126+
* @returns {ApprovalRuleBuilder} New ApprovalRuleBuilder containing approval rules
127+
*/
128+
addUsers: function (user_sys_id_list) {
129+
if (this._rule_added) {
130+
if (!this._manual_users) {
131+
if (this._debug) gs.info('--- [Users] (temporary) ' + user_sys_id_list.join(','));
132+
var au = new ArrayUtil();
133+
this._users = au.union(this._users, user_sys_id_list);
134+
this._users_added = this._users_added || this._users.length > 0;
135+
} else {
136+
NiceError.raise('Cannot add groups as manual users have already been added.');
137+
}
138+
} else {
139+
NiceError.raise('Cannot add users as no rule in progress');
140+
}
141+
return this;
142+
},
143+
144+
/**
145+
* Adds groups to a rule
146+
* @example
147+
* var rules = new ApprovalRuleBuilder()
148+
* .addRuleSet(ApprovalRuleBuilder.RULESET_APPROVES)
149+
* .addRule(ApprovalRuleBuilder.RULE_ANY)
150+
* .addGroups(['a8f98bb0eb32010045e1a5115206fe3a','a2826bf03710200044e0bfc8bcbe5ded'])
151+
* @param {array} sys_id's of groups to add
152+
* @returns {ApprovalRuleBuilder} New ApprovalRuleBuilder containing approval rules
153+
*/
154+
addGroups: function (group_sys_id_list) {
155+
if (this._rule_added) {
156+
if (!this._manual_users) {
157+
if (this._debug) gs.info('--- [Groups] (temporary)' + group_sys_id_list.join(','));
158+
var au = new ArrayUtil();
159+
this._groups = au.union(this._groups, group_sys_id_list);
160+
this._users_added = this._users_added || this._groups.length > 0;
161+
162+
} else {
163+
NiceError.raise('Cannot add groups as manual users have already been added.');
164+
}
165+
} else {
166+
NiceError.raise('Cannot add groups as no rule in progress');
167+
}
168+
return this;
169+
},
170+
171+
/**
172+
* Adds manual users to a rule
173+
* @example
174+
* var rules = new ApprovalRuleBuilder()
175+
* .addRuleSet(ApprovalRuleBuilder.RULESET_APPROVES)
176+
* .addRule(ApprovalRuleBuilder.RULE_ANY)
177+
* .addManualUsers()
178+
* @returns {ApprovalRuleBuilder} New ApprovalRuleBuilder containing approval rules
179+
*/
180+
addManualUsers: function () {
181+
if (this._rule_added) {
182+
if (this._debug) gs.info('--- [Manual Users]');
183+
if (!this._users_added) {
184+
this._approval_rules += 'M';
185+
this._users_added = true;
186+
this._manual_users = true;
187+
} else {
188+
NiceError.raise('Cannot add manual users as users/groups have already been added.');
189+
}
190+
} else {
191+
NiceError.raise('Cannot add manual users as no rule in progress');
192+
}
193+
return this;
194+
},
195+
196+
/**
197+
* Adds an Or rule to a ruleset
198+
* @example
199+
* var rules = new ApprovalRuleBuilder()
200+
* .addRuleSet(ApprovalRuleBuilder.RULESET_APPROVES)
201+
* .addRule(ApprovalRuleBuilder.RULE_ANY)
202+
* .addGroups(['a8f98bb0eb32010045e1a5115206fe3a','a2826bf03710200044e0bfc8bcbe5ded'])
203+
* .addOrRule(ApprovalRuleBuilder.RULE_RESPONDED)
204+
* .addGroups(['a8f98bb0eb32010045e1a5115206fe3a','a2826bf03710200044e0bfc8bcbe5ded'])
205+
* @param {string} rule to create (ANY|ALL|RES|%|#)
206+
* @param {integer} number to use for percentage and number of users rule (optional)
207+
* @returns {ApprovalRuleBuilder} New ApprovalRuleBuilder containing approval rules
208+
*/
209+
addOrRule: function (rule, value) {
210+
if (this._rule_added && this._users_added) {
211+
this._commitUsersAndGroups();
212+
this._rule_added = false;
213+
this._approval_rules += '|';
214+
if (this._debug) gs.info('-- [Or]');
215+
return this.addRule(rule, value);
216+
} else {
217+
NiceError.raise('Cannot add Or rule as previous rule not complete');
218+
}
219+
return this;
220+
},
221+
222+
/**
223+
* Adds an And rule to a ruleset
224+
* @example
225+
* var rules = new ApprovalRuleBuilder()
226+
* .addRuleSet(ApprovalRuleBuilder.RULESET_APPROVES)
227+
* .addRule(ApprovalRuleBuilder.RULE_ANY)
228+
* .addGroups(['a8f98bb0eb32010045e1a5115206fe3a','a2826bf03710200044e0bfc8bcbe5ded'])
229+
* .addAndRule(ApprovalRuleBuilder.RULE_RESPONDED)
230+
* .addGroups(['a8f98bb0eb32010045e1a5115206fe3a','a2826bf03710200044e0bfc8bcbe5ded'])
231+
* @param {string} rule to create (ANY|ALL|RES|%|#)
232+
* @param {integer} number to use for percentage and number of users rule (optional)
233+
* @returns {ApprovalRuleBuilder} New ApprovalRuleBuilder containing approval rules
234+
*/
235+
addAndRule: function (rule, value) {
236+
if (this._rule_added && this._users_added) {
237+
this._commitUsersAndGroups();
238+
this._rule_added = false;
239+
this._approval_rules += '&';
240+
if (this._debug) gs.info('-- [And]');
241+
return this.addRule(rule, value);
242+
} else {
243+
NiceError.raise('Cannot add And rule as previous rule not complete');
244+
}
245+
return this;
246+
},
247+
248+
/**
249+
* Returns the built approval rule
250+
* @example
251+
* ApprovesAllU[a8f98bb0eb32010045e1a5115206fe3a,a2826bf03710200044e0bfc8bcbe5ded]G[b85d44954a3623120004689b2d5dd60a,287ee6fea9fe198100ada7950d0b1b73]
252+
* |10%G[db53580b0a0a0a6501aa37c294a2ba6b,74ad1ff3c611227d01d25feac2af603f]
253+
* @returns {string} encoded rule string for use in Flow Designer
254+
*/
255+
getApprovalRules: function () {
256+
this._commitUsersAndGroups();
257+
return this._approval_rules;
258+
},
259+
260+
/*
261+
* Internal methods
262+
*/
263+
264+
_isValidRuleSet: function (ruleset) {
265+
return (ruleset == ApprovalRuleBuilder.RULESET_APPROVES ||
266+
ruleset == ApprovalRuleBuilder.RULESET_REJECTS ||
267+
ruleset == ApprovalRuleBuilder.RULESET_APPROVEREJECT);
268+
},
269+
270+
_isValidRule: function (rule) {
271+
return (rule == ApprovalRuleBuilder.RULE_ANY ||
272+
rule == ApprovalRuleBuilder.RULE_ALL ||
273+
rule == ApprovalRuleBuilder.RULE_RESPONDED ||
274+
rule == ApprovalRuleBuilder.RULE_PERCENT ||
275+
rule == ApprovalRuleBuilder.RULE_NUMBER);
276+
},
277+
278+
_commitUsersAndGroups: function () {
279+
if (this._users.length > 0) {
280+
this._approval_rules += 'U[' + this._users.join(',') + ']';
281+
if (this._debug) gs.info('--- [Users] ' + this._users.join(','));
282+
this._users = [];
283+
}
284+
285+
if (this._groups.length > 0) {
286+
if (this._debug) gs.info('--- [Groups] ' + this._groups.join(','));
287+
this._approval_rules += 'G[' + this._groups.join(',') + ']';
288+
this._groups = [];
289+
}
290+
},
291+
292+
type: 'ApprovalRuleBuilder'
293+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Approval Rule Builder for Flow Designer
2+
3+
*This script was originally posted on the Share site but thought it useful to post here as well.*
4+
5+
## Overview
6+
This project includes a script include "ApprovalRuleBuilder" which can be used to script, via the f(x) icon, an "Ask for approval" action within Flow Designer.
7+
8+
The "Ask for approval" provides a condition builder like interface for selecting the approve and reject rules and allows for multiple combinations of user and group approvals. The selections can be dynamic using the data pill picker.
9+
10+
This project provides the ability to script the approvals/rejections should the inbound data driving the action not be predictable. One use case is a data-driven catalog workflow which could have many approval rules and levels which may be hard to implement in the interface. The project was also a way for me to understand the data driving the approval action and a coding challenge to replicate it.
11+
12+
## Example
13+
- Approval - Anyone approves - (User) Abraham Lincoln, (Group) Application Development
14+
- Rejects - Anyone rejects - (User) Abraham Lincoln, (Group) Application Development
15+
16+
The resulting data used by Flow Designer looks like the following;
17+
```
18+
ApprovesAnyU[a8f98bb0eb32010045e1a5115206fe3a]G[0a52d3dcd7011200f2d224837e6103f2]OrRejectsAnyU[a8f98bb0eb32010045e1a5115206fe3a]G[0a52d3dcd7011200f2d224837e6103f2]
19+
```
20+
21+
The code to replicate this using 'ApprovalRuleBuilder' is as follows;
22+
23+
```javascript
24+
var approval_rules = new ApprovalRuleBuilder()
25+
.addRuleSet(ApprovalRuleBuilder.RULESET_APPROVES)
26+
.addRule(ApprovalRuleBuilder.RULE_ANY)
27+
.addUsers(['a8f98bb0eb32010045e1a5115206fe3a'])
28+
.addGroups(['0a52d3dcd7011200f2d224837e6103f2'])
29+
.addRuleSet(ApprovalRuleBuilder.RULESET_REJECTS)
30+
.addRule(ApprovalRuleBuilder.RULE_ANY)
31+
.addUsers(['a8f98bb0eb32010045e1a5115206fe3a'])
32+
.addGroups(['0a52d3dcd7011200f2d224837e6103f2'])
33+
.getApprovalRules();
34+
```
35+
Each method call is chained together to build the rules, and the final 'getApprovalRules()' call will return a string containing the data required by Flow Designer.

0 commit comments

Comments
 (0)