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.