- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎09-25-2015 04:55 AM
Im sorry to ask again but need help and also need to learn how to script this demand.
I can find way how to populate service on incident form if SD user select last appllication on the image below - 3
I havent luck to buil onchange client script - Im able to build script which returns me only direct relation ( from 3 to 2 ) but not what we need ( from 3 to X)
would somebody give me a lesson here please ? Please note that I need onchange solution, so service is prefilled always when suer change CI
My system fields are
cmdb_ci -- for apps
u_service_ci -- for service
thank you
/Petr
Solved! Go to Solution.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎10-01-2015 07:22 AM
Hi Petr,
Nice work on the GlideAjax script. I do have a few recommendations that can improve your script and hopefully help you learn a little more about GlideAjax in the process (you can also check out my infographic on GlideAjax for additional info: Demystifying GlideAjax).
So first, the scripts (please bear in mind I haven't fully tested these yet so they may need some tweaks):
Client Script
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue == '') {
return;
}
var ooo = g_form.getValue('cmdb_ci'); // this is the sys id of CI entered by user
var ga = new GlideAjax('BusinessServiceFinder');
ga.addParam('sysparm_name', 'getServiceForCI');
ga.addParam('sysparm_ci_sys_id', ooo); // passing CI sys_id to script include
ga.getXML(setBusinessService);
function setBusinessService(response) {
var answer = response.responseXML.documentElement.getAttribute('answer');
g_form.setValue('u_service_ci', answer); // get answer from Script Include and set Service CI field
alert(answer);
}
}
Script Include
var BusinessServiceFinder = Class.create();
BusinessServiceFinder.prototype = Object.extendsObject(AbstractAjaxProcessor, {
getServiceForCI: function() {
var this_is_variable_from_form = this.getParameter('sysparm_ci_sys_id') + '';
return this._getParentRelationship(this_is_variable_from_form);
},
_getParentRelationship: function(ciSysId) {
var o = new GlideRecord('cmdb_rel_ci');
o.addQuery('child', ciSysId);
o.query();
if (o.next()) {
if (o.parent.sys_class_name == 'cmdb_ci_service') {
return o.getValue('parent'); // return the CI's sys_id
}
else {
return this._getParentRelationship(o.getValue('parent')); // recursively execute the function for the parent's sys_id
}
}
else {
return 'There is no direct relation to service';
}
},
type: 'BusinessServiceFinder'
})
Changes
1. You will notice that I changed the names of the Script Include and its function. You can change these names as you desire, but with GlideAjax there is a convention that you must follow:
- The name of the Script Include (BusinessServiceFinder) must be included inside the parenthesis of the GlideAjax call as shown on line 7 of the Client Script. This tells GlideAjax which Script Include to use.
- The name of the Script Include's function (getServiceForCI) must be included in the Client Script by setting it as the sysparm_name parameter as shown on line 8 of the Client Script.
2. You will also notice that I added a private function _getParentRelationship and changed the getServiceForCI function to use it. The _getParentRelationship is a recursive function that calls itself. This eliminates the need for the nested if statements and reduces the duplicated code. Basically, the _getParentRelationship function will keep calling itself by passing the parent CI in the relationship until it finds a Business Service or is unable to find a relationship. The advantage in this script is that it will work for any sized relationship tree depth. If you were to at some point in the future link relationships 6 nodes from the Business Service, this script will still work.
I hope this helps. And thank you deepak.ingale for looping me in!
Kind regards,
Travis
EDIT: A few typos, missing punctuation, wrong punctuation, and other miscellaneous errors later, this script has now been tested. Thanks guys!
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎10-31-2015 11:56 AM
Hi Mathew,
Currently the script is designed to traverse a single parent-child branch up to its first parent Business Service. In other words it assumes a tree data structure with the first parent Business Service node being significant.
When you say you want to fetch all the associated Business Services, the complexity depends on your data structure. There are three possibilities:
Tree Data Structure with Multiple Business Services in Ancestry
If we are still working with a tree and your goal is to get "all of the parent Business Services in the ancestry" then we can modify the existing recursive script easily to accommodate. The key is to recursively populate an array when a Business Service type is found (lines 13-15) and to return the resulting array when no more parents are found (lines 20-22, instead of an error, the result would be an empty array or populated array).
Example Tree: Business Service 1 -> Business Service 2 -> Child CI
Directed Graph Data Structure without Cycles
Directed graph gets a little more complicated but we should still be able to use most of the existing script. The key to work the directed graph is to change the if on line 12 to a while. Basically we want to loop through all the parents, not just the first one. Also, you will probably want an array to capture the associated Business Services as in the example above. Finally, move the return statement outside the for loop.
Basically, the script include above walks a single branch of a Directed Graph because of the if(next()). Changing it to a while changes it to say "for each branch coming into the CI, walk the branch and find the Business Services.
Example Directed Graph: Business Service 1 -> Child CI <- Business Service 2
Directed Graph Data Structure with Cycles
I'm not going to go too much into this one as its the least likely scenario and also the most complicated. If you have cycled relationships in your CMDB, then you can end up in an infinite loop of searching for the next related item. You shouldn't have this issue dealing with strictly 'parent-child' relationships but a more generic 'associated with' or 'related too' could result in this.
Example Directed Graph with Cycles: Business Service 1 -> Business Service 2 -> Child CI -> Business Service 1
As you can see, if you were to recursively search for associated items in the previous, you would go from Child CI to Business Service and end up looping right back around to Child CI. The solution is to keep track of the ancestry that you have already passed on a given branch. If you encounter any item in the ancestry, then you have encountered a cycle and should break the loop.
I hope this helps and isn't too overwhelming. Graph traversal has a few edge cases.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎10-31-2015 10:31 PM
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎11-01-2015 12:08 PM
Hi Mathew,
The following script should retrieve the first encountered Business Service on each ancestry branch of the tree. You can see the while loop on line 13 and I have adapted the script to capture all the Business Services found in an array. Line 18 holds the recursive function call. The function will traverse the tree depth first. At each node, an array will be built of the node's parents which are Business Services. Once every node has been visited in the ancestry, the while loops begin to exit causing each recursive function to return. Each return concat's the array from the child node with the array from the parent node, building up a single array of all of the first Business Services reached. The final array gets returned to the client script as a JSON encoded string, so make sure you answer = JSON.parse(answer); after getting the result in the client script. Here's the script:
var BusinessServiceFinder = Class.create();
BusinessServiceFinder.prototype = Object.extendsObject(AbstractAjaxProcessor, {
getServiceForCI: function() {
var this_is_variable_from_form = this.getParameter('sysparm_ci_sys_id') + '';
return new JSON().encode(this._getParentRelationship(this_is_variable_from_form));
},
_getParentRelationship: function(ciSysId) {
var o = new GlideRecord('cmdb_rel_ci'),
businessServices = [];
o.addQuery('child', ciSysId);
o.query();
while (o.next()) {
if (o.parent.sys_class_name == 'cmdb_ci_service') {
businessServices.push(o.getValue('parent')); // add the CI's sys_id to the return array
}
else {
businessServices = businessServices.concat(this._getParentRelationship(o.getValue('parent'))); // recursively execute the function for the parent's sys_id and join the returned businessServices array to the current array
}
}
return businessServices;
},
type: 'BusinessServiceFinder'
})
Edit: Changed line 18 to an assignment to fix bug mentioned by Mathew
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎11-01-2015 03:38 PM
HI Travis thank you for the script, When i tested i got only one Business Service returned
I checked a few logs statement ans it seems like only the script is identifying two Business services but its returning the last one! seems like some error?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎11-01-2015 03:57 PM
Sorry about that! I was using a slightly simplified test case and it worked for me. I have fixed the bug. In javascript, Array.concat RETURNS the merged array and I thought it did an in place edit of the existing array. The change is on line 18, I added an assignment to businessServices.
Kind regards,
Travis