Configuration Item Icon Assignment Widget

“You don’t have to see the whole staircase, just take the first step.”
Martin Luther King, Jr.

The main reason that I wanted to find a way to include an Icon in a pick list is because I wanted to assign different icons to various classes of Configuration Items to help visually distinguish the items based on their type. Now that I know that I can’t just add an icon to a single SELECT statement, I’m not exactly sure how I am going to that, but that’s not today’s issue. When all is said and done, I may not even be able to do what I am hoping to do, but I’m not working on that part right now. I try not to get too distracted by the things that I don’t understand or don’t know how I’m going to accomplish before it’s time. Since I can’t code everything all at once, I don’t need to solve all of the mysteries all at once, either. My theory is that I should be able to figure it out when the time comes, so there is no need to worry about it right now. To keep productive, and to maintain focus, I like to deal with things One Piece at a Time.

Today, I want to build a function that will return the appropriate icon name based on a passed configuration item class, which will give me something to call when it is time to go get the icon associated with a particular item. I want to return an icon name in all circumstances, so if the specific CI class is not mapped to an icon, then I want the function to check the parent class, and basically keep doing that until an icon name is found. If it makes it all the way to the top and there is still no icon, then there needs to be a default, because one way or another, I want to return an icon name no matter what. I will eventually stuff this function into a Script Include of some kind, but for now, I just want to code out the function. Here is what I came up with.

getIcon: function(ciClass) {
	var icon = this.map[ciClass];
	if (!icon) {
		ciClass = this.getParentClass(ciClass);
		if (ciClass) {
			icon = this.getIcon(ciClass);
		}
	}
	return icon;
},

getParentClass: function(ciClass) {
	var parentClass = '';
	var tableGR = new GlideRecord('sys_db_object');
	if (tableGR.get('name', ciClass)) {
		parentClass = tableGR.super_class.name;
	}
	return parentClass;
},

OK, it turns out that it is actually two functions, but you get the idea. This code assumes that there is a map included that associates icon names to CI classes, and that the map is keyed by CI class and returns the icon name. We don’t have to have the fully populated map right now, but to test the code, we will at least need to stub it out with a minimum of one item. That should be fairly simple to do.

map: {
	cmdb_ci: 'configuration'
},

Since the cmdb_ci class is basically the root class of all Configuration Items, defining a value for that class essentially establishes the default. As you crawl up the parentage of any specific CI class, you will eventually find your way to the cmdb_ci class, so that should satisfy my requirement that there should always be a default response from the function call.

The stubbed-out map is a good start, but I want to build up my map using some kind of tool that will allow me to select a CI class and then use my new icon picker to select an appropriate icon for the class. Something that would look like this:

CI/Icon map maintenance tool

Once you added your CI class to the list, then you could click on the little magnifying glass icon to launch our icon picker to select your icon.

Using the Icon Picker to select an icon for a CI class

What you are seeing is just a screen mock up at this point, but putting the screen together is always a good place to start. Here is the HTML that I used to produce the screen image.

<div class="panel">
<div style="width: 100%; padding: 5px 50px;">
  <h2 style="width: 100%; text-align: center;">${Configuration Item Icon Assignment}</h2>
  <table class="table table-hover table-condensed">
    <thead>
      <tr>
        <th style="text-align: center;">Item</th>
        <th style="text-align: center;">Label</th>
        <th style="text-align: center;">Icon</th>
        <th style="text-align: center;">Icon Name</th>
        <th style="text-align: center;">Delete</th>
      </tr>
    </thead>
    <tbody>
      <tr ng-repeat="item in c.data.itemArray track by item.id | orderBy: 'id'" ng-hide="item.removed">
        <td data-th="Item"><input class="form-control" ng-model="item.id" readonly="readonly"/></td>
        <td data-th="Label"><input class="form-control" ng-model="item.name" readonly="readonly"/></td>
        <td data-th="Icon" style="text-align: center;"><span style="font-size: 25px;" class="icon icon-{{item.icon}}"></span></td>
        <td data-th="Icon Name">
          <span class="input-group" style="width: 100%;">
            <input class="form-control" ng-model="item.icon" readonly="readonly"/>
            <span class="input-group-btn" ng-click="selectIcon($index)" aria-hidden="false">
              <button class="btn-ref btn btn-default">
                <span class="icon icon-search" aria-hidden="true"></span>
                <span class="sr-only ng-binding">${Select an icon}</span>
              </button>
            </span>
          </span>
        </td>
        <td data-th="Delete" style="text-align: center;"><img src="/images/delete_row.gif" ng-click="removeItem($index)" alt="Click here to remove this item from the list" title="Click here to remove this item from the list" style="cursor: pointer;"/></td>
      </tr>
    </tbody>
  </table>
  <p>To add another Configuration Item to the list, select an item from below:</p>
  <sn-record-picker
    id="snrp"
    field="data.classToAdd"
    ng-change="addSelected()"
    table="'sys_db_object'"
    default-query="'super_class=72e7251abc002300aadb875973a34b54^ORsuper_class.super_class=72e7251abc002300aadb875973a34b54^ORsuper_class.super_class.super_class=72e7251abc002300aadb875973a34b54^ORsuper_class.super_class.super_class.super_class=72e7251abc002300aadb875973a34b54^ORsuper_class.super_class.super_class.super_class.super_class=72e7251abc002300aadb875973a34b54^ORsuper_class.super_class.super_class.super_class.super_class.super_class=72e7251abc002300aadb875973a34b54^ORsuper_class.super_class.super_class.super_class.super_class.super_class.super_class=72e7251abc002300aadb875973a34b54'"
    display-field="'label'"
    display-fields="'name'"
    search-fields="'label'"
    value-field="'name'">
  </sn-record-picker>
  <br/>
  <p>To remove an item from the list, click on the Delete icon.</p>
</div>

<div style="width: 100%; padding: 5px 50px; text-align: center;">
  <button ng-click="save()" class="btn btn-primary ng-binding ng-scope" role="button" title="Click here to save your changes">Save</button>
   
  <button ng-click="cancel()" class="btn ng-binding ng-scope" role="button" title="Click here to cancel your changes">Cancel</button>
</div>
</div>

For the sn-record-picker, I used my old friend, the sn-record-picker Helper, but before I got that far, I had to first work out the query. I wanted any CI table (the table name is also the class name), so I was looking for any table that was based on the cmdb_ci table. There may be an easier way to do this, but this works, so I just went with it.

super_class=72e7251abc002300aadb875973a34b54^OR
super_class.super_class=72e7251abc002300aadb875973a34b54^OR
super_class.super_class.super_class=72e7251abc002300aadb875973a34b54^OR
super_class.super_class.super_class.super_class=72e7251abc002300aadb875973a34b54^OR
super_class.super_class.super_class.super_class.super_class=72e7251abc002300aadb875973a34b54^OR
super_class.super_class.super_class.super_class.super_class.super_class=72e7251abc002300aadb875973a34b54^OR
super_class.super_class.super_class.super_class.super_class.super_class.super_class=72e7251abc002300aadb875973a34b54

That’s an ugly, brute force way of doing that, but it gets the job done, which is really all that we are after at this point. The real fun will be putting all of the code behind the screen to both build up the map and then store it somewhere. That’s pretty complicated stuff, and I’m not really sure exactly how I am going to do all of that, so we’ll save that as an exercise for another day.

Reference Type System Properties

“To succeed in life, you need two things: ignorance and confidence.”
— Mark Twain

Whenever I create a ServiceNow application, I invariably end up setting up one or more System Properties to control the behavior of the app, set up options for the app, or allow the app to function differently in different environments such as Test or Production. Recently, I wanted to create a property that would be selected from a table, or in ServiceNow terms, a Reference property. Unfortunately, when I scanned the list of available property types, Reference was not on the list.

Available System Property Types

Well, not to worry. Choice Lists are easily modified, so I went to sys_properties.list, then Configure -> Table, and clicked on the Type column to bring up the Dictionary Entry. From there, I clicked on the Choices (13) tab and then clicked on the New button to create a new choice. I typed the word reference into both the Label and Value fields and then clicked on the Submit button to save the new choice. Voila! Now my instance supports System Properties of Type Reference:

New Reference Type added to the Choice List

Of course, that was the easy part … there’s still much to do. First of all, we are going to need to add another field so that we can capture the Table we will be using for the Reference. And now that I am looking at that form, there are a couple of things that are irritating my sense of The Way Things Ought To Be that I am just going to have to clean up while I’m in there. For one, the need for the Choices field is dependent on the value of the Type field, so the Type field should come first. Additionally, the Choices field shouldn’t even appear on the form if the Type is not Choice List. The new Table field, which itself should be a Reference to the Table table, should only appear if the Type is Reference. If the Type is not Choice List and the Type is not Reference, then the very next field should be Value. Let’s see if we can’t clean all of that up next.

To begin, we can add the new column to the sys_properties table right from the form itself using the hamburger menu:

Updating the Table from the Form

This will take us to the list of columns where we can click on the New button to create a new column on the table. Select Reference as the Type, enter Table as the Label and then go down to the Reference Specification tab and select Table as the Reference.

New Table Column

Saving the new column will return us to the Table specification, and clicking on the return (<) icon up in the left corner will get us back to the form where we can use the hamburger menu to select Configure -> Form Layout to move things around. Doesn’t that look better!

Rearranged System Property fields

Now, with a little UI Policy magic, we can hide both the original Choices and the new Table unless the appropriate Type has been selected. Back again to the hamburger menu where we can select Configure -> UI Policies to implement the desired changes. We’ll need to add two new policies, one for the Choices field and one for the Table field. You first have to create the policies and save them, then you can go back in and add the actions to take when the conditions are met. The condition on the first will be Type = choice list and the condition on the second will be Type = reference. Be sure the check the Reverse if false option, as we will want these policies to toggle between the true and false conditions.

Once the policies have been created, you can go back in and add the appropriate action. On the Type = choice list policy, the action will be to make the field Choices visible. On the Type = reference policy, the action will be to make the new field Table visible. Because you selected the Reverse if false option on both policies, when the the Type is not set to the specified value, the result will be that the relevant field will no longer be present on the screen.

System Property form with neither Choice List nor Reference selected

There are still a couple of more things that we could do here to make this complete, but at this point I don’t really have a pressing need for either, so I am just going to let those go for now. The first would be a potential filter for the reference fields, as there are occasions where you don’t want the user choosing from the entire contents of the table. That would basically be handled like the Table field itself, appearing on the form only when reference was selected as the Type.

The other thing that could be done at this point would be to alter the Value field to behave in accordance with the selected Type. This would seen like the appropriate thing to do, but out of the box, the open text input box does not transition to a selection of choices when you select choice list as the Type. I think the main reason that it doesn’t do that is because this is not really the place where property values are set. Properties are defined on this form, but there is another form that is generally used to set the value. We’ll tackle that form next time, as that work will be a job in and of itself.