“Every great mistake has a halfway moment, a split second when it can be recalled and perhaps remedied.” — Pearl S. Buck
A while back, I took a quick look at the new Flow Variables feature of the Flow Designer in the hopes that the new feature might eliminate the need for my earlier effort to develop a Flow DesignerScratchpad. Unfortunately, I wasn’t able to figure out how to make it work in the way that it was described in the Blog Entry that I was attempting to follow. I tried a few things on my own, but that did not work for me, either. Since I was not smart enough to figure out how to make it do what I was trying to do, I just gave up.
Since that time, though, I have upgraded my Personal Developer Instance to Rome, so I thought that it might be a good time to go back and see if anything had changed since the last time that I tried it. As it turns out, not much has changed, but during my second look I realized that if I had just kept reading the original Blog Entry that I had been following, I would have come across the way to actually pull this off. The secret is in this little icon that I never bothered to click on when I was looking at things the first time.
I still cannot simply add the +1 after the Flow Variable pill as it shows earlier in the post, but by clicking on that little icon, I can bring up the transform options and select Math -> Add from the Suggested Transforms menu. That brings up a brief dialog box where I can enter the 1 as the number that I would like to add to the current value.
Unlike my earlier attempt at manually scripting the math, this actually worked when I ran a test execution.
This is great news. Now all that I have to do is hunt down all of the places where I have used Scratchpad variables and see if I can replace them with Flow Variables. One of the Actions that I created using the Scratchpad was the Array Iterator, which was built back when the For EachFlow Logic only supported GlideRecords. Now that For Each can be used on Arrays as well, there is no longer a need for the custom built Array Iterator. I also used the Scratchpad for a Counter, something else that could now be replaced with a Flow Variable (now that I know how to make it work!). I should really hunt down the entire lot and replace them all.
That sounds like a lot of work, though, so I will probably take that that on as I crack things open for other maintenance. Still, it is good to finally be able to use the features of the product rather than these homemade concoctions. They definitely served their purpose at the time, but now that the platform has caught up with my needs, it’s definitely time to move on.
“An essential part of being a humble programmer is realizing that whenever there’s a problem with the code you’ve written, it’s always your fault.” — Jeff Atwood
Now that you can get yourself a copy of the new Quebec version of ServiceNow, you can start playing around with all of the new features that will be coming out with this release. One of those new features is Flow Variables, which look like they might serve to replace the Scratchpad that I had built earlier for pretty much the exact same purpose. Since I have used that custom feature in a variety of different places after I first put it together, the trick will be to see if I can just do a one-for-one swap without having to re-engineer things using an entirely different philosophy.
I like building parts, but I also like to get rid my home-made parts whenever I find that there is a ServiceNow component that can serve the same purpose. If I can figure out how to make a Flow Variable do all of the things that I have been able to do with a Scratchpad Variable, then I will be the first in line to throw my home-made scratchpad into the dust bin. But before we get too far ahead of ourselves, let’s try a few things and see what we can actually do.
According to the ServiceNow Developer Blog, you can set the value of a Flow Variable inside of a loop and use it to control the number of times that you go through the loop.
Let’s create a sample flow, define our first Integer Flow Variable and then set up a loop that we can escape based on the value of the variable. Open up the Flow Designer, click on the New button, select Flow from the drop-down, and then name the new Flow Sample Flow. From the vertical ellipses menu in the upper right-hand corner, we can select Flow Variables and define a new variable called index of type Integer. For our first step, we can set the value of our new variable to 0, and then we can add a loop that will run until the variable value is greater than 5. After that, all we will have to add is another step inside the loop to increment our index variable.
All of that went relatively smoothly for me until I tried to add the +1 to the Set Flow Variables step inside of the loop. Once I dragged the index pill into the value box, it would not accept any more input. I tried putting the +1 in first, and then dragging the pill in front of it second, but that just replaced the +1. I could never get the pill+1 value like the sample image in the developers blog entry. Obviously, I was doing something wrong, but I did not know exactly what. Nothing that I tried seemed to work, though, so eventually I decided to give that up and just script it.
It was a fairly simple script that just added one to the existing value.
return fd_data.flow_var.index + 1;
Unfortunately, that resulted in a text concatenation rather than an increment of the value. 0 + 1 became 01 and 01 + 1 became 011 and so forth. Even though I had defined my variable as an Integer, it was getting treated as if it were text. No problem, though … I will just convert it to an Integer before I attempt to increment the value.
return parseInt(fd_data.flow_var.index) + 1;
That’s better. Now 0 + 1 becomes 1 and 1 + 1 becomes 2 just the way that I thought that it should. But … things are still not working the way that they should. In my test runs, the new value was not always present when I reentered the loop. 0 + 1 became 1, but then the next time through, the variable value was still 0. That’s not right! I kept erroring out by looping through the code more than the maximum 10,000 times until I finally changed my bail-out condition to be any value greater than zero. Even then, it ended up looping 456 times before it dropped out.
Clearly, something is not quite right here. It is possible that I just got a hold of an early release that still has a few kinks left to be worked out, but if you’re a true believer in The First Rule of Programming, then you know that I’ve just done something wrong somewhere along the line and need to figure out what that is. Obviously, you shouldn’t have to run through the loop over 400 times just bump the count above zero.
Well, back to the drawing board, as they say. Maybe I will have to resort to reading the instructions. Nah … I’ll just keep trying stuff until I get something to work. In the meantime, it looks like I won’t be flushing the old home-grown scratchpad down the drain just yet. At least not until I figure out how to make this thing work. Too bad, though. I had such high hopes when I saw that this was coming out.
One of the reasons that I built my little Flow Designer Scratchpad was to keep track of an index while I looped through items in an Array. After doing a few of those, I decided it would be even nicer if I had some kind of iteratorAction that would do the work of incrementing the index and returning the Array element at the current index. I already had the scratchpad to store all of the data necessary to support an iterator, so it seemed as if I could write some kind of Action that would take a Scratchpad ID, an Iterator ID, and an Array as input and use that information to set up the ability to iterate through the Array provided. As usual, I decided to put the bulk of the code in a Script Include to keep the actual code in the Action down to the absolute minimum. Here is the SNHStringArrayUtils that I came up with:
var SNHStringArrayUtils = Class.create();
SNHStringArrayUtils.prototype = {
initialize: function() {
},
createIterator: function(scratchpadId, interatorId, stringArray) {
var response = {};
var snhspu = new SNHScratchpadUtils();
response = snhspu.setScratchpadProperty(scratchpadId, interatorId, interatorId);
if (response.success) {
if (Array.isArray(stringArray)) {
var iterator = {};
iterator.index = 0;
iterator.array = stringArray;
response = snhspu.setScratchpadProperty(scratchpadId, interatorId, JSON.stringify(iterator));
if (response.success) {
response.message = 'Array Interator ' + interatorId + ' successfully created';
}
} else {
response.success = false;
response.message = 'String Array parameter is missing or invalid';
}
}
return response;
},
iteratorNext: function(scratchpadId, interatorId) {
var response = {};
var snhspu = new SNHScratchpadUtils();
response = snhspu.getScratchpadProperty(scratchpadId, interatorId);
if (response.success) {
var iterator = {};
try {
iterator = JSON.parse(response.property_value);
} catch (e) {
response.success = false;
response.message = 'Unable to parse JSON string containing iterator details';
}
if (response.success) {
if (iterator.index >= 0 && iterator.index < iterator.array.length) {
response.current_value = iterator.array[iterator.index];
response.current_index = iterator.index;
iterator.index++;
response.has_next = (iterator.index < iterator.array.length);
response.message = 'The current value at index ' + response.current_index + ' is ' + response.current_value;
snhspu.setScratchpadProperty(scratchpadId, interatorId, JSON.stringify(iterator));
} else {
response.success = false;
response.message = 'Current index value out of range';
}
}
}
return response;
},
type: 'SNHStringArrayUtils'
};
Basically, there are two methods, one for each of the two Flow Designer Actions that I intend to build. The first one is createIterator, which is used to initialize a new iterator, and the second is iteratorNext, which will support the Action that you will invoke inside of your loop to get the next item in the Array. Both utilize an existing scratchpad, so you will need to create that prior to invoking these Actions, and both require an Iterator ID, which is just a unique key to be used in storing the iterator data in the scratchpad. The createIterator action would be called once outside of the loop, and then the iteratorNext function would be called inside of the loop, usually right at the top to pull out the next value in the array.
The iterator itself is just a two-property object containing the array and the current value of the index. This is converted to a JSON string and stuffed into the scratchpad using the passed Iterator ID as the key. When creating the iterator, we set the index value to zero, and in the next Action, after using the index to get the current element, we increment it and update the scratchpad. Now that we have the basic code to support the two Actions, we need to go into the Flow Designer and create the Actions.
The Create Array Iterator Action seems like the logical place to start. That will will need three Inputs defined.
… and it will need two Outputs defined, a success indicator and an optional error message detailing any failure to perform its intended function.
In between the Inputs and Outputs will be a simple Script step, where we will produce the Outputs by passing the Inputs to our associated Script Include function.
var snhsau = new SNHStringArrayUtils();
var result = snhsau.createIterator(inputs.scratchpad_id, inputs.iterator_id, inputs.string_array);
for (var key in result) {
outputs[key] = result[key];
}
That’s pretty much all there is to that. We can test it using the Test button up at the top of the Action Editor, but first we will need a Scratchpad. We can take care of that real quick by hitting the Test button on the Create Scratchpad Action and then grabbing the Scratchpad ID from the Outputs. With our Scratchpad ID in hand, we can now test our Create Array Iterator Action.
So far so good. now we just need to do the same thing for the Array Iterator Next Action, and we’ll be all set.
This is pretty much a rinse and repeat kind of thing, with fewer Inputs, but more Outputs. When it comes time to test, we can use the Scratchpad ID and Iterator ID from our last test, and then run it through a few times to see the different results at different stages of the process. Rather that go through all of that here, I will just bundle everything up into an Update Set, and you can pull it down and play with it on your own.
I really should be working on testing out the latest enhancements to my Dynamic Service Portal Breadcrumbs right now, but I ran into this other issue recently, and I really want to see if I can make this work. I hate it when people start something and then move on to other things without ever finishing up what they started, so I definitely want to circle back and wrap that one up; however, today is not that day.
Today I want to talk about the Flow Designer. I have been striving to convert any of my old legacy Workflows over to the newer Flow Designer tool whenever the opportunity arises. The other day I was doing just that with a Workflow that made extensive use of the Workflow Scratchpad feature. When I went to look for the equivalent feature in the Flow Designer, I couldn’t find anything. I thought maybe that it wasn’t needed for some reason, so I tried several workarounds, but nothing worked. Nothing that I tried would preserve and/or modify data between or across Actions or Subflows. After quite a number of failed attempts to find something that would do the job, I eventually came to realize that if I wanted some kind of Scratchpad capability in the Flow Designer, I was going to have to build it myself.
My first thought was that all that I would need would be simple setProperty and getProperty functions, but then I realized that I would first need to establish the scratchpad, and once established, I would want to be able to get rid of it as well, so that turned into four relatively simple functions, which is still not too bad. When I say functions, what I really mean are Flow Designer Actions, but since I will be calling some function in a common Script Include built for this purpose, I still think of them as functions. Here is the function to create a scratchpad, which is just basically a record on a table that I created for this purpose:
createScratchpad: function() {
var response = {};
var spGR = new GlideRecord('u_snh_scratchpad');
spGR.initialize();
spGR.setValue('u_scratchpad', '{}');
if (spGR.insert()) {
response.success = true;
response.scratchpad_id = spGR.getUniqueValue();
response.message = 'Scratchpad record successfully created';
} else {
response.success = false;
response.message = 'Unable to create scratchpad record';
}
return response;
},
The scratchpad itself is just a JSON string stored in the only column added to the table, u_scratchpad. We initialize that to an empty object and save the record and that’s about all there is to that. To get rid of it, we will need to have the sys_id of the record, but there is not much code behind that process, either:
deleteScratchpad: function(spId) {
var response = {};
var spGR = new GlideRecord('u_snh_scratchpad');
if (spGR.get(spId)) {
if (spGR.deleteRecord()) {
response.success = true;
response.message = 'Scratchpad record successfully deleted';
} else {
response.success = false;
response.message = 'Unable to delete scratchpad record';
}
} else {
response.success = false;
response.message = 'Scratchpad record not found';
}
return response;
},
That takes care of building up and tearing down the scratchpad object. Now, to use it, we will need those setProperty and getProperty functions that we were talking about earlier. This one will let you set the value of a property on the scratchpad:
setScratchpadProperty: function(spId, propertyName, propertyValue) {
var response = {};
var spGR = new GlideRecord('u_snh_scratchpad');
if (spGR.get(spId)) {
var jsonString = spGR.getValue('u_scratchpad');
var jsonObject = {};
try {
jsonObject = JSON.parse(jsonString);
} catch(e) {
response.warning = 'Unable to parse JSON string: ' + e;
}
jsonObject[propertyName] = propertyValue;
jsonString = JSON.stringify(jsonObject, null, '\t');
spGR.setValue('u_scratchpad', jsonString);
if (spGR.update()) {
response.success = true;
response.message = 'Scratchpad property "' + propertyName + '" set to "' + propertyValue + '"';
} else {
response.success = false;
response.message = 'Unable to update scratchpad record';
}
} else {
response.success = false;
response.message = 'Scratchpad record not found';
}
return response;
},
… and this one lets you retrieve the value of a property on the scratchpad:
getScratchpadProperty: function(spId, propertyName) {
var response = {};
var spGR = new GlideRecord('u_snh_scratchpad');
if (spGR.get(spId)) {
var jsonString = spGR.getValue('u_scratchpad');
try {
var jsonObject = JSON.parse(jsonString);
var propertyValue = jsonObject[propertyName];
if (propertyValue > '') {
response.success = true;
response.property_value = propertyValue;
response.message = 'Returning value "' + propertyValue + '" for scratchpad property "' + propertyName + '"';
} else {
response.success = false;
response.message = 'Scratchpad property "' + propertyName + '" has no value';
}
} catch(e) {
response.success = false;
response.message = 'Unable to parse JSON string: ' + e;
}
} else {
response.success = false;
response.message = 'Scratchpad record not found';
}
return response;
},
That’s it for the core functions needed to make this work. Putting it all together, the entire Script Include looks like this:
var SNHScratchpadUtils = Class.create();
SNHScratchpadUtils.prototype = {
initialize: function() {
},
createScratchpad: function() {
var response = {};
var spGR = new GlideRecord('u_snh_scratchpad');
spGR.initialize();
spGR.setValue('u_scratchpad', '{}');
if (spGR.insert()) {
response.success = true;
response.scratchpad_id = spGR.getUniqueValue();
response.message = 'Scratchpad record successfully created';
} else {
response.success = false;
response.message = 'Unable to create scratchpad record';
}
return response;
},
setScratchpadProperty: function(spId, propertyName, propertyValue) {
var response = {};
var spGR = new GlideRecord('u_snh_scratchpad');
if (spGR.get(spId)) {
var jsonString = spGR.getValue('u_scratchpad');
var jsonObject = {};
try {
jsonObject = JSON.parse(jsonString);
} catch(e) {
response.warning = 'Unable to parse JSON string: ' + e;
}
jsonObject[propertyName] = propertyValue;
jsonString = JSON.stringify(jsonObject, null, '\t');
spGR.setValue('u_scratchpad', jsonString);
if (spGR.update()) {
response.success = true;
response.message = 'Scratchpad property "' + propertyName + '" set to "' + propertyValue + '"';
} else {
response.success = false;
response.message = 'Unable to update scratchpad record';
}
} else {
response.success = false;
response.message = 'Scratchpad record not found';
}
return response;
},
getScratchpadProperty: function(spId, propertyName) {
var response = {};
var spGR = new GlideRecord('u_snh_scratchpad');
if (spGR.get(spId)) {
var jsonString = spGR.getValue('u_scratchpad');
try {
var jsonObject = JSON.parse(jsonString);
var propertyValue = jsonObject[propertyName];
if (propertyValue > '') {
response.success = true;
response.property_value = propertyValue;
response.message = 'Returning value "' + propertyValue + '" for scratchpad property "' + propertyName + '"';
} else {
response.success = false;
response.message = 'Scratchpad property "' + propertyName + '" has no value';
}
} catch(e) {
response.success = false;
response.message = 'Unable to parse JSON string: ' + e;
}
} else {
response.success = false;
response.message = 'Scratchpad record not found';
}
return response;
},
deleteScratchpad: function(spId) {
var response = {};
var spGR = new GlideRecord('u_snh_scratchpad');
if (spGR.get(spId)) {
if (spGR.deleteRecord()) {
response.success = true;
response.message = 'Scratchpad record successfully deleted';
} else {
response.success = false;
response.message = 'Unable to delete scratchpad record';
}
} else {
response.success = false;
response.message = 'Scratchpad record not found';
}
return response;
},
type: 'SNHScratchpadUtils'
};
Now that we have all of the functions, we need to turn those into Flow Designer Actions. Before we do that, though, let’s create a Category for them so that we can group them and they will be easy to find. We do that by adding a row to the Action Category table, sys_hub_category. With that out of the way, we can create our first Action, which will invoke the createScratchpad function in our Script Include.
The entire Action is just a Script step that leverages our Script Include and passes the results on to the Action Outputs. The small script to make that happen is just a few short lines of code:
var snhspu = new SNHScratchpadUtils();
var result = snhspu.createScratchpad();
for (var key in result) {
outputs[key] = result[key];
}
Now we just need to repeat that process 3 more times to create Flow Designer Actions from our three other Script Include functions and we’re all set. To test things out, there is a little Test button right at the top of the Flow Designer page, and for the Create Scratchpad Action, there isn’t even any input to set up, so you can just click that button and go. Once you test out the Create Scratchpad Action, you can snag the Scratchpad ID out of the Action Outputs and then use that as an input to test all of the others.
Well, that wasn’t so bad: one Script Include, four functions, one Action Category, and four Actions. I threw this together rather quickly, but here is the Update Set. If you run into any issues with that, or if you can think of any way to make it better, please let me know in the comments. Of if you know of a built-in function that eliminates the need for this, that would be even better!