“The biggest room in the world is the room for improvement.”
— Helmut Schmidt
The other day I was working on adding Aggregate List Columns to my SNH Data Table collection and I lifted some code from the sn-record-picker Helper that looked like it might be fairly close to what I needed at the time. Unfortunately, when I tested it all out, I came to realize that the code that I lifted contained an error, and a pretty serious one at that. The problem is with the filter used on all of the table field drop-downs, which is supposed to include all of fields on the selected table. As it turns out, it includes all of the fields on the primary table, but does not include all of the other fields from any other table in the table hierarchy. That is definitely not good!
Tables can be extended from other tables, and in cases such as the CMDB, there can be quite a number of tables in the extension hierarchy. For example, the cmdb_ci_win_server table extends the cmdb_ci_server table, which extends the cmdb_ci_computer table, which extends the cmdb_ci_hardware table, which extends the cmdb_ci table, which extends the cmdb table. That’s a lot of missing fields if you only include the fields from the primary table. Clearly, this needs to be addressed, and should have been addressed long ago.
In digging through the code, the problem appears to be in this function that creates the filter for all of the field pickers:
$scope.buildFieldFilter = function() {
c.data.ready = false;
c.data.fieldFilter = 'elementISNOTEMPTY^name=' + c.data.table.value;
};
The current filter that is built turns out to be:
name=(name of selected table)
… when what it really needs to be is:
nameIN(list of all tables in the heirarchy)
That is simple enough to do, but then, the question becomes, where do we get a list of all of the tables in the hierarchy? As usual, the Now Platform has anticipated that someone might need that, and includes a way to get that very information. The solution to our query is the TableUtils object. That object includes a method called getTables() that will return just such a list. Unfortunately for us, this object is only available on the server side, and up until now, our widget did not need to have any server-side code. Well, things change.
Here is how we can use this object to get a Javascript Array of table names:
var tu = new TableUtils(input.table.value);
var tableList = tu.getTables();
data.tableList = j2js(tableList);
The object returned by the getTables() method is actually a Java object, so we have to use the j2js (Java to Javascript) function to turn it into an Array that we can use. Here is the entire new widget Server script with this functionality included:
(function() {
if (input && input.table.value) {
var tu = new TableUtils(input.table.value);
var tableList = tu.getTables();
data.tableList = j2js(tableList);
}
})();
With that now available on the server side, we need to go into our client-side function and have it call the server to get the list, and then use that list to rebuild the filter in a way that will include all of the fields on all of the tables involved. Here is the new buildFieldFilter function:
$scope.buildFieldFilter = function() {
c.data.ready = false;
c.data.fieldFilter = 'name=' + c.data.table.value;
c.server.update().then(function(response) {
if (response.tableList && Array.isArray(response.tableList) && response.tableList.length > 1) {
c.data.fieldFilter = 'nameIN' + response.tableList.join(',');
}
c.data.fieldFilter += '^elementISNOTEMPTY';
});
};
If there is not more than one table involved, then we can leave the filter with the straight equals condition, but if there is more than one table in the returned list, then we overlay that with the IN condition instead. Either way, we tack on the rest of the original filter, and we should be good to go.
Well, that’s all there is to that. Now I just need to pull all of the parts together for a new Update Set, and we should be good until we come across the next previously undetected problem!