laptop computer on glass-top table

Afficher les tableaux de bord Performance Analytics dans un portail ServiceNow

Introduction

Les tableaux de bord Platform Analytics de ServiceNow offrent une visualisation unifiée des données métiers, avec des fonctionnalités avancées comme les insights proactifs et une intégration fluide dans les portails. Ce widget personnalisé permet d’afficher dynamiquement un tableau de bord Platform Analytics dans une iframe, tout en gérant les interactions utilisateur (clics sur KPI, redirections vers des listes ou fiches détaillées) et les permissions d’accès.

Pourquoi migrer vers Platform Analytics ?
ServiceNow encourage la transition vers Platform Analytics pour bénéficier d’une expérience unifiée, surtout depuis l’activation de la propriété com.glide.par.unified_analytics.enabled. Les instances migrées voient leurs anciens tableaux de bord, rapports et filtres intégrés dans ce nouvel environnement, offrant une cohérence des données et une simplification de la maintenance.

À noter qu'à partir de la version Zurich, les tableaux de bord standard seront automatiquement migrés vers Platform Analytics.

Avant de commencer

Afin de pouvoir afficher un tableau de bord Performance Analytics, assurez-vous que les permissions

Structure du Widget

Le widget combine HTML, un Client Script (AngularJS) et un Server Script pour :

  • Charger un tableau de bord Platform Analytics via une iframe.

  • Gérer les événements de navigation (retour, redirections).

  • Vérifier les permissions d’accès.

  • Personnaliser l’affichage (hauteur, en-tête, mode édition).

2. Code HTML : L’interface utilisateur

Le template HTML utilise AngularJS (ng-if, ng-show) pour conditionner l’affichage et gérer les états (chargement, erreurs, interactions).

htmlCopier<div ng-if="data.hasAccess">
  <!-- Iframe pour le tableau de bord -->
  <iframe
    id="widget-dashboard"
    ng-if="data.dashboard"
    ng-src="{{iframeUrl}}"
    ng-show="!loading"
    sandbox="allow-forms allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation allow-downloads"
    width="100%"
    height="100%"
    frameBorder="0"
    ng-class="{'panel panel-default' : c.data.showPanel == 'true'}"
    style="height: {{iframeHeight}}"
    iframe-onload
    on-load-callback="iframeLoaded()"/>

  <!-- Bouton de retour -->
  <div>
    <button class="btn btn-default btn-home" ng-click="goBack()" ng-if="indicator">
      <i class="fa fa-chevron-left"></i>
    </button>
  </div>

  <!-- Indicateur de chargement -->
  <div class="loader"
       ng-class="{'panel panel-default' : c.data.showPanel == 'true'}"
       ng-if="loading"
       width="100%"
       height="100%"
       style="height: {{iframeHeight}}">
    <i class="fa fa-circle-o-notch fa-spin"></i>
  </div>
</div>

<!-- Message d'erreur si aucun tableau de bord n'est spécifié -->
<div ng-if="!data.dashboard">
  <div class="alert alert-danger">
    No dashboard input has been given, either through options, input or the url parameter.
  </div>
</div>
htmlCopier<div ng-if="data.hasAccess">
  <!-- Iframe pour le tableau de bord -->
  <iframe
    id="widget-dashboard"
    ng-if="data.dashboard"
    ng-src="{{iframeUrl}}"
    ng-show="!loading"
    sandbox="allow-forms allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation allow-downloads"
    width="100%"
    height="100%"
    frameBorder="0"
    ng-class="{'panel panel-default' : c.data.showPanel == 'true'}"
    style="height: {{iframeHeight}}"
    iframe-onload
    on-load-callback="iframeLoaded()"/>

  <!-- Bouton de retour -->
  <div>
    <button class="btn btn-default btn-home" ng-click="goBack()" ng-if="indicator">
      <i class="fa fa-chevron-left"></i>
    </button>
  </div>

  <!-- Indicateur de chargement -->
  <div class="loader"
       ng-class="{'panel panel-default' : c.data.showPanel == 'true'}"
       ng-if="loading"
       width="100%"
       height="100%"
       style="height: {{iframeHeight}}">
    <i class="fa fa-circle-o-notch fa-spin"></i>
  </div>
</div>

<!-- Message d'erreur si aucun tableau de bord n'est spécifié -->
<div ng-if="!data.dashboard">
  <div class="alert alert-danger">
    No dashboard input has been given, either through options, input or the url parameter.
  </div>
</div>

Points clés du HTML

  • ng-if="data.hasAccess" : Affiche le contenu uniquement si l’utilisateur a les permissions.

  • iframe :

    • ng-src="{{iframeUrl}}" : URL dynamique générée par le Client Script.

    • sandbox : Restrictions de sécurité pour l’iframe (scripts, formulaires, popups autorisés).

    • style="height: {{iframeHeight}}" : Hauteur personnalisable via les options.

  • Bouton de retour : Apparaît lorsque l’utilisateur consulte les détails d’un KPI (ng-if="indicator").

  • Loader : Animation de chargement pendant le chargement de l’iframe.

3. Client Script : La logique front-end

Le script AngularJS gère :

  • La construction de l’URL de l’iframe.

  • Les événements de navigation (clics sur les KPI, listes, enregistrements).

  • Le retour à l’état initial du tableau de bord.

javascriptCopierapi.controller = function($scope, spModal, spUtil) {
  var c = this;

  // Construction de l'URL du tableau de bord
  var dashboardURL = "/now/platform-analytics-portal/dashboard/" + c.data.dashboard;

  // Ajout des paramètres optionnels (en-tête, mode édition, sélecteur de tableau de bord)
  if(c.data.showHeader === 'true') {
    dashboardURL += '/params/header/true';
    if(c.data.showEdit === 'true')
      dashboardURL += '/showedit/true';
    if(c.data.showDashboardPicker === 'true')
      dashboardURL += '/picker/true';
  }

  $scope.loading = false;
  $scope.iframeUrl = dashboardURL;
  $scope.iframeHeight = c.data.height + 'px';

  // Fonction pour revenir au tableau de bord initial
  $scope.goBack = function() {
    $scope.iframeUrl = dashboardURL;
    $scope.indicator = false;
  };

  // Écoute des événements envoyés par le tableau de bord (via postMessage)
  window.addEventListener("message", function(event) {
    // Ignorer les événements "hello" de Platform Analytics
    if(!event.data || event.data.hello){
      return;
    } 

    console.log('Received event from Dashboard', event.data);
    var eventReceived = event.data.eventOrigin;

    // Gestion des clics sur les visualisations (KPI, listes, enregistrements)
    if(eventReceived === "NOW_VIS_WRAPPER#CLICKED") {
      // Redirection vers les détails d'un KPI
      if(event.data.params.dataSourceType == "indicator") {
        $scope.$apply(function() {
          var kpiDetailsUrl = '/now/platform-analytics-portal/kpi-details/' + event.data.params.indicatorSysid;
          $scope.iframeUrl = kpiDetailsUrl;
          $scope.indicator = true;
          $scope.loading = true;
        });
        return;
      }
      // Redirection vers une liste filtrée
      window.location.href = "?id=" + c.data.listPageID + "&table=" + event.data.params.table + "&filter=" + event.data.params.query;
    }

    // Afficher tous les enregistrements d'une liste
    if(eventReceived === "NOW_RECORD_LIST_CONNECTED#VIEW_ALL_CLICKED") {
      window.location.href = "?id=" + c.data.listPageID + "&table=" + event.data.table + "&filter=" + event.data.query;
    }

    // Redirection vers la fiche détaillée d'un enregistrement
    if(eventReceived === "NOW_RECORD_LIST_CONNECTED#ROW_CLICKED") {
      window.location.href = "?id=" + c.data.recordPageID + "&table=" + event.data.table + "&sys_id=" + event.data.recordId;
    }
  });
};
javascriptCopierapi.controller = function($scope, spModal, spUtil) {
  var c = this;

  // Construction de l'URL du tableau de bord
  var dashboardURL = "/now/platform-analytics-portal/dashboard/" + c.data.dashboard;

  // Ajout des paramètres optionnels (en-tête, mode édition, sélecteur de tableau de bord)
  if(c.data.showHeader === 'true') {
    dashboardURL += '/params/header/true';
    if(c.data.showEdit === 'true')
      dashboardURL += '/showedit/true';
    if(c.data.showDashboardPicker === 'true')
      dashboardURL += '/picker/true';
  }

  $scope.loading = false;
  $scope.iframeUrl = dashboardURL;
  $scope.iframeHeight = c.data.height + 'px';

  // Fonction pour revenir au tableau de bord initial
  $scope.goBack = function() {
    $scope.iframeUrl = dashboardURL;
    $scope.indicator = false;
  };

  // Écoute des événements envoyés par le tableau de bord (via postMessage)
  window.addEventListener("message", function(event) {
    // Ignorer les événements "hello" de Platform Analytics
    if(!event.data || event.data.hello){
      return;
    } 

    console.log('Received event from Dashboard', event.data);
    var eventReceived = event.data.eventOrigin;

    // Gestion des clics sur les visualisations (KPI, listes, enregistrements)
    if(eventReceived === "NOW_VIS_WRAPPER#CLICKED") {
      // Redirection vers les détails d'un KPI
      if(event.data.params.dataSourceType == "indicator") {
        $scope.$apply(function() {
          var kpiDetailsUrl = '/now/platform-analytics-portal/kpi-details/' + event.data.params.indicatorSysid;
          $scope.iframeUrl = kpiDetailsUrl;
          $scope.indicator = true;
          $scope.loading = true;
        });
        return;
      }
      // Redirection vers une liste filtrée
      window.location.href = "?id=" + c.data.listPageID + "&table=" + event.data.params.table + "&filter=" + event.data.params.query;
    }

    // Afficher tous les enregistrements d'une liste
    if(eventReceived === "NOW_RECORD_LIST_CONNECTED#VIEW_ALL_CLICKED") {
      window.location.href = "?id=" + c.data.listPageID + "&table=" + event.data.table + "&filter=" + event.data.query;
    }

    // Redirection vers la fiche détaillée d'un enregistrement
    if(eventReceived === "NOW_RECORD_LIST_CONNECTED#ROW_CLICKED") {
      window.location.href = "?id=" + c.data.recordPageID + "&table=" + event.data.table + "&sys_id=" + event.data.recordId;
    }
  });
};

Points clés du Client Script

  • Construction de l’URL :

    • Base : /now/platform-analytics-portal/dashboard/{ID_DU_TABLEAU_DE_BORD}.

    • Paramètres optionnels : header, showedit, picker pour personnaliser l’interface.

  • Gestion des événements :

    • Écoute des messages postMessage envoyés par l’iframe (ex. : clic sur un KPI).

    • Redirections dynamiques vers :

      • Les détails d’un KPI (kpi-details/{sysid}).

      • Une liste filtrée (listPageID).

      • La fiche détaillée d’un enregistrement (recordPageID).

  • $scope.$apply : Force la mise à jour de l’interface après un événement asynchrone.

4. Server Script : La logique back-end

Le script côté serveur :

  • Récupère les paramètres du widget (ID du tableau de bord, hauteur, options d’affichage).

  • Vérifie les permissions d’accès via les rôles utilisateur ou les ACL spécifiques.

javascriptCopier(function() {
  // Récupération des paramètres (dashboard ID, hauteur, options d'affichage)
  data.dashboard = input.dashboard || options.dashboard || $sp.getParameter('dashboard');
  
  if(!data.dashboard){
    return;
  } 

  data.height = input.height || options.height || '870';
  data.showPanel = input.show_panel || options.show_panel || 'true';

  // Options de redirection (pages de liste et de fiche)
  data.listPageID = input.list_page_id || options.list_page_id || 'list';
  data.recordPageID = input.record_page_id || options.record_page_id || 'form';

  // Options d'affichage du tableau de bord (en-tête, mode édition, sélecteur)
  data.showHeader = input.show_header || options.show_header || 'false';
  data.showEdit = input.show_edit || options.show_edit || 'false';
  data.showDashboardPicker = input.show_picker || options.show_picker || 'false';

  // Vérification des permissions
  data.hasAccess = hasAccess(data.dashboard);

  // Fonction de vérification des permissions
  function hasAccess(id) {
    // Accès automatique pour les admins
    if(gs.getUser().hasRole('admin')) {
      return true;
    }

    // Vérification des permissions spécifiques (rôles, utilisateurs, groupes)
    var glidePermission = new GlideRecord('par_dashboard_permission');
    glidePermission.addEncodedQuery('dashboard=' + id + '^can_read=true');
    glidePermission.query();

    while(glidePermission.next()) {
      var user = glidePermission.getValue('user');
      var group = glidePermission.getValue('group');
      var role = glidePermission.getValue('role');

      if(gs.getUser().hasRole(role) || user == gs.getUserID() || gs.getUser().isMemberOf(group)) {
        return true;
      }
    }
    return false;
  }
})();
javascriptCopier(function() {
  // Récupération des paramètres (dashboard ID, hauteur, options d'affichage)
  data.dashboard = input.dashboard || options.dashboard || $sp.getParameter('dashboard');
  
  if(!data.dashboard){
    return;
  } 

  data.height = input.height || options.height || '870';
  data.showPanel = input.show_panel || options.show_panel || 'true';

  // Options de redirection (pages de liste et de fiche)
  data.listPageID = input.list_page_id || options.list_page_id || 'list';
  data.recordPageID = input.record_page_id || options.record_page_id || 'form';

  // Options d'affichage du tableau de bord (en-tête, mode édition, sélecteur)
  data.showHeader = input.show_header || options.show_header || 'false';
  data.showEdit = input.show_edit || options.show_edit || 'false';
  data.showDashboardPicker = input.show_picker || options.show_picker || 'false';

  // Vérification des permissions
  data.hasAccess = hasAccess(data.dashboard);

  // Fonction de vérification des permissions
  function hasAccess(id) {
    // Accès automatique pour les admins
    if(gs.getUser().hasRole('admin')) {
      return true;
    }

    // Vérification des permissions spécifiques (rôles, utilisateurs, groupes)
    var glidePermission = new GlideRecord('par_dashboard_permission');
    glidePermission.addEncodedQuery('dashboard=' + id + '^can_read=true');
    glidePermission.query();

    while(glidePermission.next()) {
      var user = glidePermission.getValue('user');
      var group = glidePermission.getValue('group');
      var role = glidePermission.getValue('role');

      if(gs.getUser().hasRole(role) || user == gs.getUserID() || gs.getUser().isMemberOf(group)) {
        return true;
      }
    }
    return false;
  }
})();

Points clés du Server Script

  • Récupération des paramètres :

    • Priorité : input > options > paramètre URL ($sp.getParameter).

    • Exemple : data.dashboard peut être passé via les options du widget, une entrée utilisateur, ou l’URL.

  • Gestion des permissions :

    • Les admins ont un accès automatique.

    • Pour les autres utilisateurs, vérification via la table par_dashboard_permission (rôles, groupes, utilisateurs spécifiques).

  • Options de redirection :

    • listPageID et recordPageID définissent les pages cibles pour les redirections (ex. : list.do ou form.do).

5. Cas d’usage et personnalisation

Paramètres disponibles

Paramètre

Description

Valeur par défaut

Exemple

dashboard

ID du tableau de bord Platform Analytics

"a1b2c3d4e5f6"

height

Hauteur de l’iframe (en pixels)

870

"1000"

show_panel

Affiche un cadre autour de l’iframe

"true"

"false"

show_header

Affiche l’en-tête du tableau de bord

"false"

"true"

show_edit

Active le mode édition

"false"

"true"

show_picker

Affiche le sélecteur de tableaux de bord

"false"

"true"

list_page_id

ID de la page de liste pour les redirections

"list"

"incident_list"

record_page_id

ID de la page de fiche pour les redirections

"form"

"incident_form"

Exemple d’utilisation

  "dashboard": "a1b2c3d4e5f67890",
  "height": "1000",
  "show_panel": "true",
  "show_header": "true",
  "show_edit": "false",
  "list_page_id": "custom_list",
  "record_page_id": "custom_form"
}
  "dashboard": "a1b2c3d4e5f67890",
  "height": "1000",
  "show_panel": "true",
  "show_header": "true",
  "show_edit": "false",
  "list_page_id": "custom_list",
  "record_page_id": "custom_form"
}

6. Conclusion

Ce widget offre une intégration fluide des tableaux de bord Platform Analytics dans un portail ServiceNow, avec :
Une gestion dynamique des permissions.
Des redirections intelligentes vers les listes et fiches détaillées.
Une personnalisation avancée (hauteur, en-tête, mode édition).


Source:

https://developer.servicenow.com/connect.do#!/share/contents/8949624_platform_analytics_experience_in_the_portal?v=1.1&t=PRODUCT_DETAILS


Une collaboration Snowlab,
ça vous dit ?

Basés à Bordeaux, nous intervenons partout en France pour des projets ServiceNow ambitieux et humains. Contactez-nous pour échanger sur votre projet et découvrir comment nous pouvons optimiser votre Service Portal ServiceNow !

Une collaboration Snowlab,
ça vous dit ?

Basés à Bordeaux, nous intervenons partout en France pour des projets ServiceNow ambitieux et humains. Contactez-nous pour échanger sur votre projet et découvrir comment nous pouvons optimiser votre Service Portal ServiceNow !