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).
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 -->
<iframeid="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-onloadon-load-callback="iframeLoaded()"/><!-- Bouton de retour -->
<div><buttonclass="btn btn-default btn-home"ng-click="goBack()"ng-if="indicator"><iclass="fa fa-chevron-left"></i></button></div><!-- Indicateur de chargement -->
<divclass="loader"ng-class="{'panel panel-default' : c.data.showPanel == 'true'}"ng-if="loading"width="100%"height="100%"style="height: {{iframeHeight}}"><iclass="fa fa-circle-o-notch fa-spin"></i></div></div><!-- Message d'erreur si aucun tableau de bord n'est spécifié -->
<divng-if="!data.dashboard"><divclass="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 -->
<iframeid="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-onloadon-load-callback="iframeLoaded()"/><!-- Bouton de retour -->
<div><buttonclass="btn btn-default btn-home"ng-click="goBack()"ng-if="indicator"><iclass="fa fa-chevron-left"></i></button></div><!-- Indicateur de chargement -->
<divclass="loader"ng-class="{'panel panel-default' : c.data.showPanel == 'true'}"ng-if="loading"width="100%"height="100%"style="height: {{iframeHeight}}"><iclass="fa fa-circle-o-notch fa-spin"></i></div></div><!-- Message d'erreur si aucun tableau de bord n'est spécifié -->
<divng-if="!data.dashboard"><divclass="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){varc = this;// Construction de l'URL du tableau de bordvardashboardURL = "/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 Analyticsif(!event.data || event.data.hello){return;}console.log('Received event from Dashboard',event.data);vareventReceived = 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 KPIif(event.data.params.dataSourceType == "indicator"){$scope.$apply(function(){varkpiDetailsUrl = '/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éewindow.location.href = "?id=" + c.data.listPageID + "&table=" + event.data.params.table + "&filter=" + event.data.params.query;}// Afficher tous les enregistrements d'une listeif(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 enregistrementif(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){varc = this;// Construction de l'URL du tableau de bordvardashboardURL = "/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 Analyticsif(!event.data || event.data.hello){return;}console.log('Received event from Dashboard',event.data);vareventReceived = 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 KPIif(event.data.params.dataSourceType == "indicator"){$scope.$apply(function(){varkpiDetailsUrl = '/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éewindow.location.href = "?id=" + c.data.listPageID + "&table=" + event.data.params.table + "&filter=" + event.data.params.query;}// Afficher tous les enregistrements d'une listeif(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 enregistrementif(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 permissionsdata.hasAccess = hasAccess(data.dashboard);// Fonction de vérification des permissionsfunctionhasAccess(id){// Accès automatique pour les adminsif(gs.getUser().hasRole('admin')){returntrue;}// Vérification des permissions spécifiques (rôles, utilisateurs, groupes)varglidePermission = newGlideRecord('par_dashboard_permission');glidePermission.addEncodedQuery('dashboard=' + id + '^can_read=true');glidePermission.query();while(glidePermission.next()){varuser = glidePermission.getValue('user');vargroup = glidePermission.getValue('group');varrole = glidePermission.getValue('role');if(gs.getUser().hasRole(role) || user == gs.getUserID() || gs.getUser().isMemberOf(group)){returntrue;}}returnfalse;}})();
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 permissionsdata.hasAccess = hasAccess(data.dashboard);// Fonction de vérification des permissionsfunctionhasAccess(id){// Accès automatique pour les adminsif(gs.getUser().hasRole('admin')){returntrue;}// Vérification des permissions spécifiques (rôles, utilisateurs, groupes)varglidePermission = newGlideRecord('par_dashboard_permission');glidePermission.addEncodedQuery('dashboard=' + id + '^can_read=true');glidePermission.query();while(glidePermission.next()){varuser = glidePermission.getValue('user');vargroup = glidePermission.getValue('group');varrole = glidePermission.getValue('role');if(gs.getUser().hasRole(role) || user == gs.getUserID() || gs.getUser().isMemberOf(group)){returntrue;}}returnfalse;}})();
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).
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 !
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 !