AttilaVarga
Tera Guru

There are several prerequisites before we start implementing any solution in ServiceNow. One of them is to define the test personas (also called actors). These test users will be used to test the business use cases.

 

It goes without saying that test personas are used by both developers and testers, right?

 

AttilaVarga_0-1750744517995.png

 

I have created this meme here to highlight two important points:

  • As a developer, testing is an essential step during the implementation phase.
  • The functionality should to be tested by the right persona and not by the admin user (as it usually happens 🙂).

 

Both messages are important and I can write pages about them - but now I want to focus on another point, that helps the developers test uses cases quicker: this is the impersonation feature.

 

Impersonation

 

Impersonation is part of the base concept of ServiceNow, which is part of the administrator training, so we can meet with this very early in our ServiceNow journey.

 

The feature can be used on both the platform (back-end) and the portal (front-end). Regarding the end-user surface (portal) it is available only on the Employee Center Portal. However it does not mean, that we can't use it on other portals, because there is a specific URL (/impersonate_dialog), that helps us to access the feature.

 

AttilaVarga_1-1750745216071.png

 

This is a great option to use impersonate feature from anywhere, but in my opinion, it is a bit uncomfortable.

 

Solution

 

During one of our Customer Service Management implementation projects, I decided to implement the impersonate feature to the CSM portal based on the solution, available on Employee Center. I would like to introduce this solution, which - I believe - can be useful for anyone.

 

I give a step-by-step description, that walks you through how it can be implemented.

 

The main goal is to add the Impersonate menu item below the Profile.

AttilaVarga_2-1750745578711.png

 

In order to do that, we need to locate the definition of this part of the header menu, which can be found in the Portal Theme record as the Header reference.

AttilaVarga_3-1750745711351.png

 

The Portal Polaris Header artefact is an OOTB widget. Let's do a quick analysis of the possibilities from an architectural point of view:

 

AttilaVarga_0-1750754472228.png

 

Finally I decided to modify the existing widget in my solution. As it was previously mentioned, the implementation was based on the Employee Center header widget. (Here I should highlight, that the code for the functionality is not located directly in the the Employee Center Header widget. There is an embedded one, called Profile with Drop Down, which contains all the necessary elements I need.)

 

Implementation steps

 

Step 1

 

As a first step, I added the necessary menu items to the Portal Polaris Header widget:

  • Impersonate
  • End Impersonate

 

<ul class="dropdown-menu">
   <li><a ng-href="?id={{::data.supportProfilePortal}}">${Profile}</a></li>
   <!-- Impersonate feature extension -->
   <li ng-if="data.isimpersonationenabled && data.isImpersonating">
      <a href="javascript&colon;void(0)" ng-click="endImpersonate()">${End Impersonation}</a>
   </li>
   <li ng-if="data.isimpersonationenabled && (data.canImpersonate || data.isImpersonating)">
      <a href="javascript&colon;void(0)" ng-click="impersonate()">${Impersonate}</a>
   </li>
   <!-- Impersonate feature extension -->
   <li class="logout"><a href="{{::portal.logoutUrl}}">${Logout}</a></li>
</ul>

 

There are some variables, that control the visibility of these menu items. Let's define them.

 

Step 2 - visibility definition

 

There are some out-of-the-box functions, which have to be used for checking the followings:

- whether current user has rights to use the impersonate feature,

-whether the impersonation is active or not,

- and the ID of  the original user who is impersonating. (It is important when we want to stop impersonation.)

These functions are part of the GlideImpersonate class, which are available only in the Global scope. This means that if our widget is a part of a scoped application, we need a wrapper class in the Global scope.

 

var ImpersonateHelper = Class.create();
ImpersonateHelper.prototype = {
    initialize: function() {
    },

	/**
	 * This function checks that the current session is an impersonated one or not.
	 * @returns {boolean}
	 */
	isImpersonating: function() {
		return new GlideImpersonate().isImpersonating();
	},

	/**
	 * This function checks that the current has rights to use the impersonate feature or not.
	 * @returns {boolean}
	 */
	canImpersonate: function() {
		return new GlideImpersonate().canImpersonate(gs.getUserID());
	},

	/**
	 * This function returns the sys_is of user, who initiated the impersonate feature
	 * @returns {string}
	 */
	getImpersonatingUserID: function() {
		return gs.getImpersonatingUserID();
	},

    type: 'ImpersonateHelper'
};

 

Step 3 - Adding security and the operational logic to the server-side code of widget

We need to implement the back-end part of the widget template (remember the ng-if attributes...). Therefore the following lines of code must be added to the widget, to define the visibility of menu items in the header menu:

var imersonateHelper = new global.ImpersonateHelper();
data.isImpersonating = imersonateHelper.isImpersonating();
data.canImpersonate = imersonateHelper.canImpersonate();
data.modalTitle = gs.getMessage("Impersonate user");
data.isimpersonationenabled = gs.getProperty('glide.ui.impersonate_button.enable', 'true')=='true' ? true : false;
data.realUser = imersonateHelper.getImpersonatingUserID();

 

Step 4 - Binding events to action buttons (menu items)

 

There are two menu items (Impersonate, End Impersonation), for which we need to define the click actions in the Client controller of widget:

 

// Click action of Impersonate button
$scope.impersonate = function() {
    spModal.open({
        title: c.data.modalTitle,
        widget: 'impersonate-user',
        scope: scope,
        buttons: []
    })
};

// Click action of End Impersonation button
$scope.endImpersonate = function() {
    var userName = c.data.realUser;
    //If we don't have a user we can't impersonate
    if (!userName) {
        return;
    }
    //Call to the impersonation api with username/sys_id
    $http.post("/api/now/ui/impersonate/" + userName, {}).success(function() {
        $scope.showError = false;
        $window.location.href = $scope.portal.url_suffix;
    }).error(function(response) {
        if (response.error) {
            $scope.showError = true;
            $scope.error = response.error;
        }
    });
};

 

The spModal and $http service references can be missing from the widget, if so, they need to be added to the client controller:

 

function pspHeaderController($rootScope, $scope, $window, spUtil, $location, $uibModal, cabrillo, $timeout, i18n, spContextManager,spAriaUtil, spModal, $http) {
	var c = this;
	...
}

 

Once everything is done, the functionality can be tested. (Yes, this is one of the few cases, where we use our admin user to test the feature. 🙂)

 

AttilaVarga_0-1751017985176.png

 

AttilaVarga_1-1751018003855.png

 

AttilaVarga_2-1751018030129.png

 

It works fine.

 

Adding some styles

 

This feature doesn't exist on the Employee Center Portal, so this one is my personal added value. 🙂

 

As a final or additional step, a visual enhancement can be added, to indicate that the impersonation is currently used or not.

 

First, some visual components need to be added to the HTML template to change the appearance of the logged-in user's avatar:

<!-- From this -->
<a href="javascript&colon;void(0)" class="dropdown-toggle" data-toggle="dropdown">
   <span class="navbar-avatar">
      <sn-avatar class="avatar-small-medium" primary="avatarProfile" />
   </span>
   <span class="visible-lg-inline">{{::user.name}}</span>
</a>

<!-- To this -->
<a href="javascript&colon;void(0)" class="dropdown-toggle" data-toggle="dropdown">
   <span class="navbar-avatar">
      <sn-avatar class="avatar-small-medium {{data.isImpersonating? 'is-impersonating': ''}}" primary="avatarProfile" />
   </span>
   <span class="visible-lg-inline">{{::user.name}}</span>
   <span ng-if="data.isImpersonating" class="avatar-eye">
      <svg class="now-icon-sm" style="fill: red" aria-hidden="true" viewBox="0 0 12 12">
         <path d="M4.5 6a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Z"></path>
         <path d="M.497 5.153C1.418 3.976 3.374 2 6 2c2.626 0 4.582 1.976 5.503 3.153.39.5.39 1.195 0 1.694C10.582 8.024 8.626 10 6 10 3.374 10 1.418 8.024.497 6.847a1.372 1.372 0 0 1 0-1.694ZM6 3.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5Z"></path>
      </svg>
   </span>
</a>

 

And then, some css properties have to be defined to the widget:

.is-impersonating {
  border: .2rem solid RGB(var(--now-color_presence--busy,234,60,16))
}

.avatar-eye {
	position: absolute;
	width: 1.5rem;
	top: .7rem;
	left: 4rem;
}

 

I like the result. 🙂

(Just take a look at Abel's avatar.)

AttilaVarga_3-1751018800196.png

 

Feel free to use this solution.

4 Comments