måndag 8 februari 2016

Making your own "new Feature" window

Been having some trouble reaching out to the users with our own "new releases" and the information about our own fixes and new functionality. Looking at the community I realized that I wasn't alone with this problem. Today people are getting flooded with information through email so we threw that idea out the window. We also tried to publish the releases with knowledge articles, but the success hasn't been overwhelming.

So now I was thinking that perhaps we should do like ServiceNow and throw up a popup windows when user logs in. And this is how I did it.





First of all, this will give your some tools to make your own way of showing new features. I will also tell about a few things we also made, but it's made to work with our production and not guaranteed to work with your specific. I will not go deep into those functions, but I will tell you what I did, and I will happily answer any questions about it.


My main goal the whole time was to make this functionality so that I didn't need to make any code changes every time we have a new release. Our Service Manager for ServiceNow application should be able to do everything own their own and it will just work. My solution for this is letting them order this function from our Service Catalog and there setting the values that needed and everything else will be setup in the background. For example, having a schedule job turning the functionality on and off.


This is the criteria I was working after:

  • User should be able to just close the window if they like to or go to the Knowledge article for more info.
  • The popup window should only occur once per release.
  • It should only show for users with roles. Meaning our end users doesn't get it when they visit our portal.
  • The windows should only show 1 week after the release. Then it should stop showing even if the user haven't logged on after the release. But there should be an option to set this time range manually.
  • The Service Manager should be able to "review" the feature info before making it public.

A quick summary of how it is build together.

  • UI Script "showFeature": This is a Global UI Script that hits on "every page" in ServiceNow and Checks if we are inside and throws up the window if the user doesn't have the correct user preference.
  • Script include "siFeature": There are two functions here. One is used to check if the user has the preference or not. This so the UI Script can decide if the popup window is going to show or not. The other one is to set the preference on the user after it has been shown to them.
  • UI Page "newfeature": This is the page that show up inside the popup window.
  • Style sheet "UI Page newfeature": This page contains the CSS for the UI page.
  • Knowledge Articles: I have choose to have the design of the "feature info" for the popup in KB articles. One article for each release.
  • A User Preference "custom.newFeature: This is the preference a user will get when they have seen the popup window.
  • System properties: There are three properties, one holding which KB article that is going to show the info in the popup. One for the date the popup should start showing and one property to hold the date when the popup should stop showing.
  • Schedule job: This job runs and keeps track on the UI Script should be active or not.
  • Service Catalog: This is where the Service Manager "orders" the activation of the new release popup. This is also the place where I've only describe what we done and not be able to show any code. 

Here we go:

1. UI Script "showFeature"

Now, this is a global UI script which means it will run on every page in the system. This is perfect for me, since I want the users to get this directly after they enter the system or if they are already logged on when it turns active.

What this script does it checks so the user isn't a end user and if the user has seen this release or not. And it checks so we are inside the ServiceNow through checking the frame ID. If the user hasn't seen it it will throw up a GlideDialogWindow(GDW) with the information.

The script also sets some CSS values when the GDW renders. This to give the background a bit more opacity and change the color of it. I also needed to make the background color of the GDW here, since the color I give in the UI page doesn't apply on the whole window leaving some border and the title white.

Don't forget to check the "global" field on the UI Script.

Script: 
addLoadEvent( function() {
//Ensure that we call the dialog for the correct frame and the user isnt an end user. don't want this to pop up in for example our portal
if(window.frameElement){
if(window.frameElement.id == 'gsft_main' && g_user.hasRoles()){
//In geneva there is some strange redirect after you logged in. That's why I have the Delay, 
//otherwise the window will start loading then the whole page will reload and it doesn't look so good.
setTimeout(checkPreference, 1000);

}
}
});

//Here we check the users preferences to see if the user already seen the release info
function checkPreference() {
var ga = new GlideAjax('siFeature');
ga.addParam('sysparm_name','checkPreference');
ga.getXML(showFeatures);
}


function showFeatures(response) {

var answer = response.responseXML.documentElement.getAttribute("answer");
//If the user doesn't have the preference that they have already seen it, pop the window
if(answer == 'false'){
var featwin = new GlideDialogWindow('newfeature');
featwin.setTitle('New release information');
featwin.setSize(700,600);
//remove the X in the upper right corner
featwin.removeCloseDecoration();
featwin.render();
//This fixes another color on the background behind the window and some opacity
$j('#grayBackground').css('opacity','0.90');
$j('#grayBackground').css('background-color','#130d06');
//Need this to make the whole glideDialogWindow to the same background as the ui page has.
$j('#newfeature table').css('background-color','#E4DBBF');
}


}

I'm thinking of making the background color a system perference instead, so I can just change it on one place and it will change both in this script and in the UI page futher down here. But that will be a later project.

2. Script Include "siFeature"

This is a simple Script Include. I only has one method and that is to check if the user has the preference or not and to give the user the preference so the GDW doesn't shows more than once.

Don't forget to check the "Client callable" field.

Here comes the script:

var siFeature = Class.create();
siFeature.prototype = Object.extendsObject(AbstractAjaxProcessor, {

checkPreference: function() {
var gr = new GlideRecord('sys_user_preference');
gr.addQuery('name','custom.newFeature');
gr.addQuery('user', gs.getUserID());
gr.query();

if(gr.hasNext()) {
return true;
}
else {
return false;
}

},

setPreference: function() {

var grnew = new GlideRecord('sys_user_preference');
grnew.initialize();
grnew.name = 'custom.newFeature';
grnew.user = gs.getUserID();
grnew.value = 'true';
grnew.insert();

},

type: 'siFeature'
});

3. UI page "newfeature"

This the the page that renders in the GDW. there isn't so much content in the UI page since it get it all from the knowledge articles HTML-field (u_featureinfo). The UI page also have two buttons that either just closes the window or redirects you to the knowledge articles that holds the information about the release. And the client script closes the GDW and sets the user preference to true.

I have chosen to have all content in the article instead of in the UI page since that will let the Service Manager for ServiceNow to create it without me needed to make changes to the UI page for each release.

All CSS code is saved in a Style Sheet file and I use a UI Macro to be sure that I get the latest CSS-file and not a cached version. I have used the macro that you can find here on another blog: ServiceNow Style Sheets And Caching

XML field:

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">

<!-- Get the article content -->
<g:evaluate>
//Check if its a KB article UI action or the UI Script rendering the window
//If there is a preference from the script it from the UI Action otherwise get the system property
if (RP.getWindowProperties().get('number')) {
var artnum = RP.getWindowProperties().get('number');
}
else {
var artnum = gs.getProperty('custom.newfeature.kbdocument');
}
var article = new GlideRecord('kb_knowledge');
gs.log("detta är KB art: " + artnum);
article.get('number', artnum);

</g:evaluate>

<!-- Some CSS styling Using UI Macro to be sure to have the latest CSS-file(sys_id) Dont forget to replace the stylesheet_id below with your stylesheet sys_id-->
<g:call function="u_include_stylesheet" stylesheet_id="0e8d05bb0fdd96404cf365ba32050ea2" />

<!--Main page -->
<div id="info">
<!--Here we put the data from the article-->
<h1> ${article.short_description}</h1>
<g:no_escape>${HTML:article.u_featureinfo}</g:no_escape>  


<!-- Buttons at the button to either just close the window or go the correct kb article -->
<table>
<tr>
<td id="tdleft"> <g:dialog_button onclick="closeNow()" type="button" class="diabutt">close window</g:dialog_button></td>
<td id="tdright"><g:dialog_button onclick="toKnowledge('${article.number}')" type="button" class="diabutt">More information</g:dialog_button></td>
</tr>
</table>
</div>
</j:jelly>

Client Script in the UI Page:

function closeNow() {
setUserPreference();

GlideDialogWindow.get().destroy();
}

function toKnowledge(article) {
setUserPreference();
var url = 'kb_view.do?sysparm_article='+article;
GlideDialogWindow.get().destroy();
window.location = url; 
}

function setUserPreference() {
var ga = new GlideAjax('siFeature');
ga.addParam('sysparm_name','setPreference');
ga.getXML();
}

4. Style Sheet "UI Page newfeature"

I have created a style sheet in the Content Management System->Style sheets and then call this stylesheet with a macro. I could also have done it with this line in the UI Page
"<link href="sys_id_of_stylesheet_record.cssdbx" rel="stylesheet" type="text/css"></link> "
but then I can't be certain that it would be the latest version of it and that's why I'm using a UI Macro to put it in the UI Page.

This is what the CSS file contains:

/'* WHOLE PAGE **/
#info {
background-color: #E4DBBF;
color: #474139;
}

/** RED COLOR FOR THE SHORT DESC  **/
h1 {
        color: #F74906;
}

/** GIVING THE TABLE THE SAME BG COLOR **/
table {
width: 99%;
background-color: inherit;
}

/** STYLES FOR BOTH TABLE CELLS**/
#tdleft {
text-align: right;
width:  50%;
padding-right: 7px;
}
#tdright {
text-align: left;
width:  50%;
}


5. Knowledge article form

I haven't done so much here beside two things. First is that I added a HTML field called (u_featureinfo) with a label named "Information to feature popup" and restricted this so only the ServiceNow-team could see it. This because no one else will use this field.
The other thing is that I added a UI Action that you can click on and see how the feature GDW will look like. This so the Service Manager can see how his content looks like and dont have to wait until it's live to see if it looks good.

Make sure you click the "client" field and put the function name in the field "Onclick".
On condition I set so only the members of the ServiceNow group will see the UI Action.
And I also put a hint text that they need to save before previewing.





And here comes the script:

function featurePeek(){

var featwin = new GlideDialogWindow('newfeature');
featwin.setTitle('New release information');
featwin.setSize(700,600);
//remove the X in the upper right corner
featwin.removeCloseDecoration();
//Send with the number of the article
featwin.setPreference('number', g_form.getValue('number'));
featwin.render();

//This fixes another color on the background behind the window and some opacity
$j('#grayBackground').css('opacity','0.90');
$j('#grayBackground').css('background-color','#130d06');
//Need this to make the whole glideDialogWindow to the same background as the ui page has.
$j('#newfeature table').css('background-color','#E4DBBF');

}
I'm not happy with having the code for the GDW on two places (UI Action & UI Script), so I will probably do something about this later thou. But this have to do for now.

6. User preferences & System properties

Right now I got 1 user preference and 3 system properties. The user preference is created by the Script include and is used for keeping track of who have seen the GDW and not. This preference will be deleted when a new "order" from the catalog comes in.

User preference:

  • custom.newFeature: Just using this to keep record of who has seen the release info and not.

System Properties:

  • custom.newfeature.kbdocument: This holds which article that should be used to fill the GWD with content. This value is being set through the workflow for the order in our catalog.
  • custom.newfeature.validdate: This holds to what date the UI script should be active. The schedule job is working with this and this is also set by the workflow for the order in our catalog.
  • custom.newfeature.startdate: This holds the date when the UI script should be activated. The schedule job is working with this and this is also set by the workflow for the order in our catalog. 

7. Schedule Job "custom.newfeature"

This schedule job is running once a day towards the custom.newfeature.validdate & custom.newfeature.startdate and if that is a match with today's date it will deactivate/activate the UI Script. This is just a small performance thing, but I don't want to be running the UI script when I know the GDW isn't going to show.

The Script for the Schedule Job looks like this:

//Create variables to be able to compare them
var tid = new GlideDateTime(gs.nowDateTime());
var start = new GlideDateTime(gs.getProperty('custom.newfeature.startdate'));
var valid = new GlideDateTime(gs.getProperty('custom.newfeature.validdate'));

var uiScript = new GlideRecord('sys_ui_script');
uiScript.get('name','showfeature');

//If startdate is older than nowDateTime, UI script isn't active and the Valid Date isnt passed.
if( tid.compareTo(start) == 1 && uiScript.active == false && tid.compareTo(valid) == -1 ) {

//Activate the script.
uiScript.active = 'true';
uiScript.update();

}
//If validdate is older then nowDateTime and UI Script is active
else if(tid.compareTo(valid) == 1 && uiScript.active == true) {
//inactivate the UI script
uiScript.active = 'false';
uiScript.update();
}

Here I'm also thinking of activating the schedule when the "order" is made from the Catalog and then make it deactivate itself when it's deactivating the UI script. But that together with the rest of my ideas is version 2. Otherwise I will never be finished :).

8. Last part "Service Catalog".

This is one of the things that is hard to make so it's easy to reuse for others than in our instance. Basically we have a item for this feature and the requester fills in "valid to", "startdate" and which article it should be using. Then the workflow does the rest and of course also create/closes a change to keep it logged that it been done.
Workflow also deletes the user preference when a new "order" comes in.

 

So this is how I made my own feature release information and I hope you like it. If you got any questions about this or anything else, feel free to either comment here or through other channels and I'll get right back to you.

2 kommentarer:

  1. hi
    i was going through it
    actually

    if (RP.getWindowProperties().get('number')) {
    var artnum = RP.getWindowProperties().get('number');
    }
    else {
    var artnum = gs.getProperty('custom.newfeature.kbdocument');
    }

    is not working as i have to give the value of artnum manually help

    and please tell where it is fetching this value

    SvaraRadera
  2. Hi,

    You do this through 2 ways.

    * Either you do this through system properties "custom.newfeature.kbdocument"
    or
    * For testing purposes, it's in the UI action at " featwin.setPreference('number', g_form.getValue('number'));"

    SvaraRadera