Generic Feedback Widget

“Feedback is the breakfast of champions.”
Ken Blanchard

The other day when I was testing out my Static Monthly Calendar I needed a page to demonstrate the modal pop-up, so I grabbed a page that I had created to show off my feedback form field. That got me to thinking that I would be nice to have a universal widget to not only collect new feedback on an item, but to also display all of the feedback that had been collected so far. This is something that ServiceNow already does quite well for Knowledge documents, but that isn’t really universal, and can’t be applied to any other component. Still, it’s a nice model, and one that I wanted to emulate in a way that could be applied to other things.

Typical feedback section of a Knowledge document

Previous comments are listed in reverse chronological order followed by an input form where you can enter new comments. This would be the layout of my proposed widget, with the intent that you could use it for any type of entity, and not just Knowledge. I looked at the underlying table structure for this feature, and the data is eventually stored in two places, a Knowledge table called kb_feedback, and the Live Feed table, live_message. After looking into both, I decided that I could accomplish what I had in mind with the live_message table alone, so I cracked open a shiny new widget that I called generic_feedback and got to work.

I like to solve one problem at a time and then move on to the next one, so I started out with the basic structure of the HTML for the widget, basically copying the original from the Knowledge page:

<div ng-repeat="item in data.feedback">
  <div class="snc-kb-comment-by" style="margin-top: 10px;">
    <img src="/images/icons/feedback.gifx" alt="Feedback" aria-label="Feedback">
    <span class="snc-kb-comment-by-title">
      Posted by <a class="snc-kb-comment-by-user" href="?id=user_profile&table=sys_user&sys_id={{item.userSysId}}">{{item.userName}}</a>
      <span class="snc-kb-comment-by-title">{{item.dateTime}}</span>
    </span>
  </div>
  <div>
    <span class="snc-kb-comment-by-text" ng-bind-html="item.comment"></span>
  </div>
</div>

My only significant deviation from the original was to make the name of the person leaving the comment a link to that person’s User profile page. Other than that, it pretty much followed the original layout.

Based on my data references, I was going to need an array of objects with userSysId, userName, dateTime, and comment properties. These values would all come from the live_message table, but first I needed to figure out how to select the specific messages for the particular item that was the subject of the page. One of the properties of the live_message table is labeled Conversation, and is a reference to another table, live_group_profile. The live_group_profile table contains two properties, table and document, that we can leverage to point to a specific record on a specific table, linking our feedback to pretty much anything in the ServiceNow database. Using this approach, to find all of the feedback for a particular entity, you need the name of the table and the sys_id of the record, and with that information, you can find the live_group_profile record that identifies the “conversation” about that entity. On the server side, that code looks something like this:

var conv = new GlideRecord('live_group_profile');
conv.addQuery('table', data.table);
conv.addQuery('document', data.sys_id);
conv.query();
if (conv.next()) {
	data.convId = conv.getValue('sys_id');
	var fb = new GlideRecord('live_message');
	fb.addQuery('group', data.convId);
	fb.orderByDesc('sys_created_on');
	fb.query();
	while(fb.next()) {
		var feedback = {};
		feedback.userSysId = fb.getValue('profile');
		feedback.userName = fb.getDisplayValue('profile');
		feedback.dateTime = fb.getValue('sys_created_on');
		feedback.comment = fb.getDisplayValue('message');
		data.feedback.push(feedback);
	}
}

Before we can gather up all of the feedback, though, we have to establish the name of the table and the sys_id of the record. For the purpose of this particular widget, we will require that this information be passed in the form of URL parameters. These are the very same URL parameters that drive the Dynamic Service Portal Breadcrumbs (which I also included on the page), and are pretty much a standard in the Service Portal, so that shouldn’t be too onerous of a requirement. The code to pull those values in from the URL and validate them looks like this:

if (input) {
	data.table = input.table;
	data.sys_id = input.sys_id;
} else {
	data.table = $sp.getParameter('table');
	data.sys_id = $sp.getParameter('sys_id');
}
if (data.table && data.sys_id) {
	var gr = new GlideRecord(data.table);
	if (gr.isValid()) {
		if (gr.get(data.sys_id)) {
			data.tableLabel = gr.getLabel();
			data.recordLabel = gr.getDisplayValue();
			...
		}
	}
}

This doesn’t give us a way to enter new feedback, but we have enough now to give things a try, so let’s create a page, drag our new widget onto the page, and see how things are looking so far.

First test of feedback layout using existing Change Record comments

Well, overall it didn’t turn out too bad, but I can see right away a number of things that need a little work. For one, I don’t really like the date format. That definitely needs some improvement. Also, my links don’t work for the User Profile page. That’s because the profile column in the live_message table contains the sys_id of the live_profile record, not the sys_user record. That’s not going to work. It’s always something!

Still, I like the way things are shaping up. Next time out, let’s see if we can’t clean up all of those pesky little issues and then work on capturing new comments and posting them back to the database.