Daniel Borkowi1
Mega Sage

Hi community,

 

within my team we had a discussion about scripting within Flow Designer. We realized that we created tons of Script Actions for one time scripts. This caused our flow action list to grow immeasurably and makes us doubt that we will be able to manage this in the future. Especially when script actions are no longer needed because the associated flows are no longer used.

 

We have come up with an approach that I would like to put up for discussion here.
Here's the idea: we store such scripts in Script Includes and call them via a generic script action. What do you think of this and if you don't think it's a bad idea, perhaps you have some suggestions for improving the approach?

 

Now, step by step, what we have come up with:

 

Create a Script Action "Run Script"

First define 3 Input Parameters:

  1. Name of Script Include where the function is placed
  2. Name of Function within the Script include which should be called
  3. Array of Strings as Input values for the function

DanielBorkowi1_0-1712759930910.png

 

Use Script Step to call the Script Include:

  • Ensure that input parameters are available within script by mapping the input variablesDanielBorkowi1_1-1712760059974.png
  • Use inputs variables to build a function call script and utilize the dynamic capabilities to instantiate a Script Include class and call a function (Edit: thanks to @rsander without utilizing evil eval anymore)image.png

 

 

(function execute(inputs, outputs) {
    var SI_Class = JSUtil.getGlobal()[inputs.script_include];      
    var result = new SI_Class()[inputs.function_name](inputs.input);   
    outputs.return_object = result;
})(inputs, outputs);

 

 

  • Don't forget to define the output variables for the script step - in our case a String Array called return_object

DanielBorkowi1_3-1712760210890.png

 

Map the Script Step Output to the Action Output

 

DanielBorkowi1_4-1712760264286.png

 

Create a Script Include to encapsulate your scripts

As our script action allows to define the Script Include name you can use for each usage a new Script Include or you use a central Script Include and adds new functions for each use case. It's your choice.

Here is an example how this SI could look like:

DanielBorkowi1_0-1712761739899.png

Here is the code of this example Script Include:

 

 

 

var ScriptActionTestSI = Class.create();
ScriptActionTestSI.prototype = {
    initialize: function() {
    },
	test_function : function(input){
		var a = input[0];
		var b = input[1];
		result = [];
		result[0] = a+b;
		result[1] ="DBOR";
		return result;
	},
    type: 'ScriptActionTestSI'
};

 

 

 

You can create any Script Include and define your own functions, names doesn't matter, as you define them as input in the Run Script action. We have only two restrictions regarding the function signature:

  1. As the input parameter needs to be generic, we decided to use a Array of Strings. If you need complex objects there, you can use JSON.stringify()  and JSON.parse() to transfer these.
  2. Also the output should be generic, that's why we also have chosen a string array.

But as mentioned, it's not really a restriction because these string could also contain complex objects in JSON format.

 

Usage of Script Action within flow

As an example the flow simple calls the new action and logs the result afterwards:

DanielBorkowi1_1-1712762173685.png

DanielBorkowi1_2-1712762230013.png

The result of this flow:

DanielBorkowi1_0-1712904382159.png

 

Conclusion

To summarise, my intention is not to recommend this approach as best practice, but rather to hear your opinion on it. From our point of view, this saves a lot of single use actions, but adds another level of complexity, in which the good old script include now has to be used in addition to the flow and action editor.

So fire away, what's your opinion dear community.

 

9 Comments
Brady Holliday
Tera Guru

Hey Daniel,

 

I found you post interesting to read, thanks for sharing. I do agree that being able to get a handle on one-off scripts/flows is something worth getting ahead of and you present an intriguing way of doing so. I think the single use action could definitely come in handy for when you want to call an already existing script include.

 

The one adjustment that I would try to think about incorporating would be to add a decision maker to the flow. In essence, the decision maker/table would help you track which custom scripts and or one-off actions you are calling. When a script/flow action is no longer needed, you could then track that deprecation and soon removal via the decision table as well. 

 

By enforcing the use of the decision maker, the idea is to help prevent the shift of managing a bunch of one-off flow actions to a bunch of script includes and/or functions within a single script include. At the end of the day, if you don't build actions and/or script includes to be reused then you are always going to have some maintenance of those items. So rather than trying to get rid of all custom one-off actions, I am thinking more along the lines of being able to better track them and 'why' they exist, which is where I think the decision maker could help assist with.

 

Here are the docs on how to call a decision table from a flow: https://docs.servicenow.com/csh?topicname=flow-logic-make-decision.html&version=latest

 

I'm curious to see what other people think!

AndersCMA
Giga Guru

I love it. Script everything.

rsander
Tera Contributor

Hi Daniel,

 

instead of using eval() which pops up in every HealthScan, you could do the following:

 

// Option 1: "process" could be the base function used in any kind of these script includes
var scriptInclude = new(JSUtil.getGlobal()[scriptIncludeName])();
var result = scriptInclude.process(input);

// Option 2: Might work as well...
var scriptInclude = new global[scriptIncludeName]();

 

AndersCMA
Giga Guru

By the way, there might be a bug in your code.  I believe that these lines

    var func = "new " + inputs.script_include + "()" + "." + inputs.function_name + "(input_object)";
    var input_object = inputs.input;

might be better looking like this?

    var input_object = inputs.input;
    var func = "new " + inputs.script_include + "()." + inputs.function_name + "(" + input_object + ")";

If not, the input_object would just be inserted as the string "(input_object)" I believe.
I also switched the order for the variable to make sense.

 

- Anders

Daniel Borkowi1
Mega Sage

@AndersCMA eval can work with "input_object" within the string and it can dynamically insert the parameter when you create the variable like I did. It's tested 😉. I used the source of knowledge https://www.w3schools.com/jsref/jsref_eval.asp. Instead of let, I changed to var.

 

Your first comment is gold, I didn't expect something else from you 😂

Daniel Borkowi1
Mega Sage

Hi Robert @rsander thanks for this hint, this approach was unknown to me, I will test it and replace eval with this approach. Thanks a lot, because the part with eval I also didn't like (eval is evil).

AndersCMA
Giga Guru

@Daniel Borkowi1  aha if it works it works!  I know about eval but haven't really found a reason to use it, so the details aren't known to me.  I just assumed it would adhere to strict javascript syntax. 

In any case I love the idea to have a script insert directly in a Flow, a bit irritating to have to make a new Spoke for smaller needs!

 

Good stuff

 

gjz
Mega Sage

@Daniel Borkowi1 - Did you get this working?  

 

I also would like a custom action that will allow me to call any script include and script include function, but using your code, I am getting an error from the script step "Error: rhino.global is not a function.,Detail: rhino.global is not a function."  I even tried changing it based on some of the comments, but it still doesn't work.  

 

In my action, I'm not getting any data back from the script include even though it runs without an error. The function in the script include is exactly as you put in your example, but I had to change the code in the script step in the action as I kept getting either a runtime error or script syntax error using the code you presented or if I used any code in the comments.

gjz_0-1758912628338.png

Would you please share the code that works in your script step?  I suspect the issue is the 2nd line, inputs.si_function adds a second period that I suspect shouldn't be there.

 

 

 

Daniel Borkowi1
Mega Sage

Hi @gjz , sorry for the late answer, long time too busy. Yes it's working well in my instance. Could it be, that your script action is in an application scope?

If yes this will be the root cause as JSUtil.getGlobal won't work in application scopes.  

 

DanielBorkowi1_0-1761840007949.png

 

(function execute(inputs, outputs) {
    var SI_Class = JSUtil.getGlobal()[inputs.script_include];      
    var result = new SI_Class()[inputs.function_name](inputs.input);   
    outputs.return_object = result;
})(inputs, outputs);

DanielBorkowi1_1-1761840036685.png

 

Btw. found this action on my new Zurich instance: "Run script include on FDIH engine." seems to do the similar what my post wants to solve. 

 

Greets

Daniel