Scripted Value Columns, Part VII

“Unplanned occurrences are reminders to check your tendency to think that you’re the one in control.”
James Martin

Last time, we created another example of how one might utilize the new scripted value column feature, this time with catalog item variables instead of Incident journal entries. There are a number of other things that we could try, but two examples should be enough to get the point across, and I’ll leave it to others to come up with additional examples of their own.

We still have two more wrapper widgets to update, though, and we still have that annoying misalignment between the original columns and the new. Here is the way things come out right now:

Misalignment of original columns and new columns

… and here is the way that it should look:

Correct alignment of original columns and new columns

I was able to capture that second image because I found and fixed the problem. I had to replace this HTML:

<sn-avatar ng-if="item[field].value && item[field].type == 'reference' && item[field].table == 'sys_user'" primary="item[field].value" class="avatar-small" show-presence="true" enable-context-menu="false"></sn-avatar>
<a ng-if="$first" href="javascript:void(0)" ng-click="go(item.targetTable, item)" aria-label="${Open record}: {{::item[field].display_value}}">{{::item[field].display_value | limitTo : item[field].limit}}{{::item[field].display_value.length > item[field].limit ? '...' : ''}}</a>
<a ng-if="!$first && item[field].type == 'reference' && item[field].value" href="javascript:void(0)" ng-click="referenceClick(field, item)" aria-label="${Click for more on }{{::item[field].display_value}}">{{::item[field].display_value | limitTo : item[field].limit}}{{::item[field].display_value.length > item[field].limit ? '...' : ''}}</a>
<span ng-if="!$first && item[field].type != 'reference'">{{::item[field].display_value | limitTo : item[field].limit}}{{::item[field].display_value.length > item[field].limit ? '...' : ''}}</span>   

… with this:

<span style="display: inline-flex;">
  <span style="display: inline-flex;" ng-if="item[field].value && item[field].type == 'reference' && item[field].table == 'sys_user'">
    <sn-avatar primary="item[field].value" class="avatar-small" show-presence="true" enable-context-menu="false"></sn-avatar>
    &nbsp;
  </span>
  <a ng-if="$first" href="javascript:void(0)" ng-click="go(item.targetTable, item)" aria-label="${Open record}: {{::item[field].display_value}}">{{::item[field].display_value | limitTo : item[field].limit}}{{::item[field].display_value.length > item[field].limit ? '...' : ''}}</a>
  <a ng-if="!$first && item[field].type == 'reference' && item[field].value" href="javascript:void(0)" ng-click="referenceClick(field, item)" aria-label="${Click for more on }{{::item[field].display_value}}">{{::item[field].display_value | limitTo : item[field].limit}}{{::item[field].display_value.length > item[field].limit ? '...' : ''}}</a>
  <span ng-if="!$first && item[field].type != 'reference'">{{::item[field].display_value | limitTo : item[field].limit}}{{::item[field].display_value.length > item[field].limit ? '...' : ''}}</span>
</span>

Now, without getting into too much detail that no one really cares about, the source of the problem was the sn-avatar tag, which I added a while back so that user columns would have the avatar in front of the name. For some reason, the tag renders out a carriage return and a handful of spaces just before the avatar image. With the ng-if attribute set to false, this collection of white space is still rendered on the page, even when the avatar itself is not. I solved that problem by wrapping the avatar tag with a span and putting the ng-if attribute on the outer span rather than on the sn-avatar tag. That took care of things for columns where there was no avatar, but the user columns, which show the avatar, were still out of alignment with the rest of the columns. Adding style=”display: inline-flex; took care of that problem with the avatar, but then the user name ended up underneath the avatar instead of next to it. To solve that problem, I wrapped the whole thing in another span with the same style attribute. Now everything lines up the way that it should.

Now that that is out of the way, we still have two more wrapper widgets to update. Let’s jump into the SNH Data Table from Instance Definition and do the same kind of searching we did before, looking for some code that might need to be copied and modified. On this particular widget, such a search turns up nothing at all in either the Server script or the Client script, so the only thing that we really need to do is to add another entry to the Option schema for our new scripted value column specification.

{"hint":"A JSON object containing the specifications for scripted value columns",
"name":"scripteds",
"default_value":"",
"section":"Behavior",
"label":"Scripted Value Column Specifications (JSON)",
"type":"String"}

To test this, we can modify our scripted_value_test_2 page to use this widget instead of the SNH Data Table from JSON Configuration widget, and then transfer our configuration options from the Script Include to the widget options.

Widget configuration options for the modified wrapper widget

Now all we need to do is to save it and then run out to the Service Portal and take a quick peek.

Testing the modified SNH Data Table from JSON Configuration widget

So that all looks good. And much, much better now that the column data all lines up as it should! It’s nice to finally have that fixed. That takes care of wrapper widget #2. Now let’s take a look at that last one, the SNH Data Table from URL Definition widget. The only line that appears to require modification is this one:

copyParameters(data, ['aggregates', 'buttons', 'refpage', 'bulkactions']);

… which we can convert to this to pick up our new configuration option:

copyParameters(data, ['scripteds', 'aggregates', 'buttons', 'refpage', 'bulkactions']);

Now we need to test it, so we will need to find or create a page that use this widget. Let’s take a look at the ones that are already out there by checking out the Related List down at the bottom of the form.

Pages that use the SNH Data Table from URL Definition widget

The page my_things looks like a good candidate, so we can take a look at the configuration script that it uses and then edit it to add one or more scripted value columns. One of the tables utilized on that page is the Incident table, so let’s go ahead and use our existing value provider script to add a journal entry column to one of those.

name: 'incident',
displayName: 'Incident',
open: {
	filter: 'caller_idDYNAMIC90d1921e5f510100a9ad2572f2b477fe^active=true',
	fields: 'number,opened_by,opened_at,short_description',
	svcarray: [{
		name: 'last_comment',
		label: 'Last Comment',
		heading: 'Last Comment',
		script: 'global.ScriptedJournalValueProvider'
	}],
	aggarray: [],
	btnarray: [],
	refmap: {
		sys_user: 'user_profile'
	},
	actarray: []
}

Now let’s take a look.

First test of the modified SNH Data Table from URL Definition widget

Well, that didn’t work! It’s always something. Even if there were no comments on any of these Incidents, we should still have a column heading for our new scripted value column. I don’t think this problem is in the widget that we just modified, however. This widget shares the page with the Configurable Data Table Widget Content Selector widget, and that is a widget that we have not even touched. That is going to have to be modified to accommodate our new feature as well, as it builds the URL that the SNH Data Table from URL Definition widget turns to for its configuration information. This was not on our list of things to do for this feature, but it definitely needs to be done.

I was hoping to wrap things up with this installment, but now we have a new widget to modify and more testing to do, so I think we will just save all of that, plus the Update Set creation, for our next time out.

Aggregate List Columns

“Get a good idea and stay with it. Do it, and work at it until it’s done right.”
Walt Disney

We have had a lot of fun with the Service Portal Data Table Widget on this site. So much so, in fact, that we had to make our own copy to avoid extensive modifications to a core component of the Service Portal. So far, we have created a Configurable Data Table Widget Content Selector, allowed individual columns to be links to referenced records, added buttons and icons, added User Avatars to user columns, set up the Configurable Data Table Widget Content Selector to use a JSON object to configure the widget, created an Editor for the JSON configuration object, added check boxes to the rows, added an additional extension of the base Data Table Widget to use the JSON configuration object directly, and built a User Directory using all of these custom components. That’s quite a bit of customization, but there is at least one more thing that we could do to make this all even better.

The feature that I have in mind is to have one or more columns that would include counts of related records. For example, on a list of Assignment Groups, you might want to include columns for how many people are in the group or how many open Incidents are assigned to the group. These are not columns on the Group table; these are counts of related records. It seems as if we could borrow some of the concepts from our Buttons and Icons strategy to come up with a similar approach to configuring Aggregate List Columns that could be defined when setting up the configuration for the table. You know what I always like to ask myself: How hard could it be?

Let’s take a quick look at what we need to configure a button or icon using the current version of the tools. Then we can compare that to what we would need to configure an Aggregate List Column using a similar approach. Here is the data that we collect when defining a button/icon:

  • Label
  • Name
  • Heading
  • Icon
  • Color
  • Hint
  • Page

The first three would appear to apply to our new requirement as well, but the rest are specific to the button/icon configuration. Still, we could snag those first three, and copy all of the code that deals with those first three, and then ditch the rest and replace them with data points that will be useful to our new purpose. Our list, then, would end up looking something like this:

  • Label
  • Name
  • Heading
  • Table
  • Field
  • Filter

The first three would be treated exactly the same way that their counterparts are treated in the button/icon code. The rest would be unique to our purpose. Table would be the name of the table that contains the related records to be counted. Field would be the name of the reference field on that table that would contain the sys_id of the current row. Filter would be an additional query filter that would be used to further limit the records to be counted. The purpose for the Filter would be to provide for the ability to count only those records desired. For example, a Table of Incident and a Field of assigned_to would count all of the Incidents ever assigned to that person, which is of questionable value. With the ability to add a Filter of active=true, that would limit the count to just those Incidents that were currently open. That is actually a useful statistic to add to a list.

One other thing that would be useful would be the addition of a link URL that would allow you to pull up a list of the records represented in the count. Although I definitely see the value in this additional functionality, anyone who has followed this web site for any length of time knows that I don’t really like to get too wild and crazy right out of the gate. My intent is to just see if I can get the count to appear on the list before I worry too much about other features that would be nice to have, regardless of their obvious value.

So, it seems as if the place to start here would be to pull up a JSON configuration object and see if we can add a configuration for an Aggregate List Column. When we built the User Directory, we added a simple configuration file to support both the Location Roster and the Department Roster. This looks like a good candidate to use as a starting point to see if we can set up a test case. Here is the original configuration file used in the User Directory project:

var RosterConfig = Class.create();
RosterConfig.prototype = Object.extendsObject(ContentSelectorConfig, {
	initialize: function() {
	},

	perspective: [{
		name: 'all',
		label: 'All',
		roles: ''
	}],

	state: [{
		name: 'department',
		label: 'Department'
	},{
		name: 'location',
		label: 'Location'
	}],

	table: {
		all: [{
			name: 'sys_user',
			displayName: 'User',
			department: {
				filter: 'active=true^department={{sys_id}}',
				fields: 'name,title,email,location',
				btnarray: [],
				refmap: {
					cmn_location: 'location_roster'
				},
				actarray: []
			},
			location: {
				filter: 'active=true^location={{sys_id}}',
				fields: 'name,title,department,email',
				btnarray: [],
				refmap: {
					cmn_department: 'department_roster'
				},
				actarray: []
			}
		}]
	},

	type: 'RosterConfig'
});

For our purpose, we don’t really need two state options, so we can simplify this even further by reducing this down to just one that we can call all. Then we can add our example aggregate configuration just above the button configuration. Also, since this is just a test, we will want to limit our list of people to just members of a single Assignment Group, so we can update the filter accordingly to limit the number of rows. Here is the configuration that I came up with for an initial test.

var AggregateTestConfig = Class.create();
AggregateTestConfig.prototype = Object.extendsObject(ContentSelectorConfig, {
	initialize: function() {
	},

	perspective: [{
		name: 'all',
		label: 'All',
		roles: ''
	}],

	state: [{
		name: 'all',
		label: 'All',
	}],

	table: {
		all: [{
			name: 'sys_user',
			displayName: 'User',
			all: {
				filter: 'active=true^sys_idIN46c4aeb7a9fe1981002bbd372644a37b,46d44a23a9fe19810012d100cca80666,5137153cc611227c000bbd1bd8cd2005,5137153cc611227c000bbd1bd8cd2007,9ee1b13dc6112271007f9d0efdb69cd0,f298d2d2c611227b0106c6be7f154bc8',
				fields: 'name,title,email,department,location',
				aggarray: [{
					label: 'Incidents',
					name: 'incidents',
					heading: 'Incidents',
					table: 'incident',
					field: 'assigned_to',
					filter: 'active=true'
				}],
				btnarray: [],
				refmap: {},
				actarray: []
			}
		}]
	},

	type: 'AggregateTestConfig'
});

For now, I just created a simple filter using all of the sys_ids of all of the members of the Hardware group rather than bringing in the group member table and filtering on the group itself. This is not optimum, but this is just a test, and it will avoid other issues that we can discuss at a later time. The main focus at this point, though is the new aggarray property, which includes all of the configuration information that we listed earlier.

aggarray: [{
   label: 'Incidents',
   name: 'incidents',
   heading: 'Incidents',
   table: 'incident',
   field: 'assigned_to',
   filter: 'active=true'
}]

Now that we have a configuration script, we can create a page and try it out, even though we have not yet done any coding to support the new configuration attributes. At this point, we just want to see if the list comes up, and since nothing will be looking for our new data, that portion of the configuration object will just be ignored. I grabbed a copy of the Location Roster page from the User Directory project and then cloned it to create a page that I called Aggregate Test. Then I edited the configuration for the table to change the Configuration Script value to our new Script Include, AggregateTestConfig. I also removed the value that was present in the State field, as we only have one state, so no value is needed.

Updating the Configuration Script on the cloned portal page

With that saved, we can run out to the Service Portal and pull up the new page and see how it looks.

First look at the new test page using our new configuration script

Well, that’s not too bad for just a few minutes of effort. Of course, that was just the easy part. Now we have to tinker with the widgets to actually do something with this new configuration data that we are passing into the modules. That’s going to be a bit of work, of course, so we’ll start taking a look at how we want to do that next time out.

Collaboration Store, Part XXX

“Writing the first 90 percent of a computer program takes 90 percent of the time. The remaining ten percent also takes 90 percent of the time and the final touches also take 90 percent of the time.”
Neil J. Rubenking

Well, the test results are starting to pour in, and it looks like I screwed things up when I manually edited the Update Set in an effort to eliminate one of the errors reported in the earlier testing cycle. It seems as if I should have left well enough alone and just informed people to ignore the error if it comes up. Here is an unmolested Update Set that should not have the problem that I created by hacking up the earlier version after it was generated. Hopefully, this will resolve that issue.

Two things to note, then, of this new version: 1) if you happen to get the unfortunate Table ‘sys_hub_action_status_metadata’ does not exist error, just ignore it, and 2) if you get any preview errors related to any sys_properties, be sure to skip those updates, as you do not want to overlay the property values that were established during the set-up process.

One of the other things that was reported was that it was not really clear as to what, exactly, needed to be tested. I have a tendency to focus on the construction process exclusively, without a lot of attention to the actual end product itself, so let’s see if we can’t rectify that situation a little bit now.

The initial early release of this effort was focused on the set-up process. All the set-up process does is set you up as the Host instance, or get you registered with a Host instance if you are setting up a Client instance. That’s all that it did, so the testing was limited to attempting to set up a Host, and then attempting to set up one or more Clients. A successful test would have all instances appear in the instance table on every instance involved in the testing. That seemed to be pretty straightforward.

For this next iteration, we introduced the ability to actually publish a Scoped Application to the Host. To test this newest feature, you will first have to have gone through the set-up process successfully, and then you need an app to publish. Any app in development will do, and if you don’t have one, you can always just stub one out for the testing.

To publish an app, you need to bring up the app’s primary form, and if all went well with the installation, there should be a new UI Action down at the bottom of the page called Publish to Collaboration Store.

New Publish to Collaboration Store UI Action

If you click on that guy and follow the ensuing pop-up dialogs through completion, the app should be published. To verify that all went well, you will have to go over to the Host instance and see if the app actually appears in the Related List under the publisher’s instance record. You should verify the presence of the application record, the version record, and the XML Update Set attachment. If all of those things are present, then the app was successfully published to the Host.

The one thing that will not happen just yet is for the newly published app to be distributed to any of the other Client instances in the community. That process is still under development, and is not included in this version of the application. Once that gets completed and all of the issues from this round of testing get resolved, we will put out yet another beta test version and go through the testing process all over again.

Thanks again to all of you who are taking the time to take this out for a spin. Any and all feedback is greatly appreciated. Please feel free to report any issues or successes in the comments below. Next time, if there are no further results to review, we will take a look at building out the distribution of newly published versions to the rest of the instances served by the Host.

Scoped Application Properties, Part II

“Any daily work task that takes 5 minutes will cost over 20 hours a year, or over half of a work week. Even if it takes 20 hours to automate that daily 5 minute task, the automation will break even in a year.”
Breaking into Information Security: Crafting a Custom Career Path to Get the Job You Really Want by Josh More, Anthony J Stieber, & Chris Liu

So far, we have created the UI Action to produce our Setup Properties button, configured the conditions under which the button would appear, and built the Business Rule to ensure that all properties created for an application are placed in a Systems Properties Category of the same name. Before we jump into the business of building out all of the things that we want to automate through the push of that button, there are just a couple of more little odds and ends that we will want to take care of first. One thing that will be helpful in maintaining System Properties for the application will be to have the properties listed out as Related Lists. That’s accomplished fairly easily by selecting Configure -> Related Lists from the hamburger menu on the main Application configuration page:

Configuring Related Lists on the main Application page

This will bring up the Available and Selected Related Lists for an Application, and you just need to find the System Property->Application entry in the Available bucket, highlight it, and then use the right arrow button to push it over into the Selected bucket. Oh, and don’t forget to click on that Save button once you’re done.

Activating the System Properties Related List

One other little thing that would be handy would be a link to the property admin page somewhere on the main Application configuration page. Even though our setup automation will be creating a link to that page somewhere on the main navigation menu, it would still be handy to have a link to that same page right here where we are configuring the app. The format of the URL for that link in both instances is the address of the page, plus a couple of parameters, one for the page title and the other for the name of the Category:

/system_properties_ui.do?sysparm_title=<appName> Properties&sysparm_category=<appName>

To build a link to that page on the main Application configuration page can be easily accomplished with another UI Action similar to the first UI Action that we built for our button, but this time we will select the Form link checkbox instead of the Form button checkbox. The code itself is just constructing the URL and then navigating to that location:

function openPage() {
	var appname = document.getElementById('sys_app.name').value;
	var url = '/system_properties_ui.do?sysparm_title=' + encodeURIComponent(appname) + '%20Properties&sysparm_category=' + encodeURIComponent(appname);
	window.location.href = url;
}

There may be more elegant ways to do that, but this works, so that’s good enough for now. This link should show after the Application Properties have been configured, so the display rules are basically opposite of those for our button: the button will show until you use it and the link will only show after you use the button. We can pretty much steal the condition from other UI Action, then, and just flip the last portion that checks for the presence of the Category:

gs.getCurrentApplicationId() == current.sys_id && (new GlideRecord('sys_properties_category').get('name',current.name)) && (new GlideRecord('sys_properties_category_m2m').get('category.name',current.name))

Those of you who are paying close attention will notice that we also add yet one more check to our condition, and that was just to make sure that there was at least one System Property defined for the app. There is no point in bringing up the property value maintenance page if there aren’t any properties. Assuming that you have set up application properties and you have defined at least one property, you should see the new link appear down at the bottom of the main Application configuration page:

New link to the property value maintenance page

That should take care of all of those other little odds and ends … now all that is left is to build out the code that will handle all of those initial setup tasks. Last time, I said that we would take care of that here, but as it turns out, that that was a lie. We’ll have to take care of that next time.