Luke Van Epen
Tera Guru

A massive frustration of mine, and of many others it seems (see This Idea) is that sharing Reports on a dashboard is a major hassle.

I've built a UI action, from the Dashboard Properties page, that will force a sync between the sharing permissions for the dashboard with all reports on that dashboard.
It is additive only, so you will not break the sharing permissions of reports that are on multiple dashboards or shared separately.

 

Attached is the UI Action XML, so you can just import this into your instance and you're good to go, or you can continue reading the code below and explanation so you can edit it to suit your purpose.

 

How to use

Open up a dashboard, and go to Dashboard Properties

find_real_file.png

 

Then the UI Action is a link in the related links section

find_real_file.png

 

How it works

Dashboards can be shared with 3 different things, Users, Groups, and Roles.

Reports can be shared with either Users or Groups, or they can be shared with Everyone and restricted by role. They can't be both.

As a workaround for the Role limitation, the code instead looks up all individuals with a particular role, and gives them User access permission to the report. The net result is the same, all users who can see the dashboard, can also see the reports on the dashboard.

 

Recommended changes for your instance

If you have very large groups/role inheritance (e.g. hundreds/thousands of users) it's probably a good idea to wrap this UI Action script into a Script Action, and trigger an event with the UI Action for the system to process later. However you wont get the instant feedback on what you were sharing

If you don't care about getting told what was shared and with who, then you can comment out or change the gs.addInfoMessage lines.

 

find_real_file.png

var dashboardTabM2M = new GlideRecord('pa_m2m_dashboard_tabs');
dashboardTabM2M.addQuery("dashboard", current.sys_id);
dashboardTabM2M.query();
var pages = [];
while (dashboardTabM2M.next()) {
    pages.push(dashboardTabM2M.tab.page.sys_id + "");
}

var reportIds = [];
pages.forEach(function(pageID) {
    var portals = new GlideRecord("sys_portal");
    portals.addQuery("page", pageID);
    portals.query();
    while (portals.next()) {
        var pageProp = new GlideRecord("sys_portal_preferences");
        pageProp.addQuery("portal_section", portals.getUniqueValue());
        pageProp.addQuery("name", "sys_id");
        pageProp.query();
        if (pageProp.next()) {
            reportIds.push(pageProp.getValue("value"));
        }
    }
});

var shareUsers = [];
var shareRoles = [];
var shareGroup = [];
var dashboardPerms = new GlideRecord("pa_dashboards_permissions");
dashboardPerms.addQuery("dashboard", current.getUniqueValue());
dashboardPerms.query();
while (dashboardPerms.next()) {
    if (dashboardPerms.type == "2")
        shareGroup.push(dashboardPerms.getValue("group"));
    if (dashboardPerms.type == "1")
        shareRoles.push(dashboardPerms.getValue("role"));
    if (dashboardPerms.type == "3")
        shareUsers.push(dashboardPerms.getValue("user"));
}

var reportGR = new GlideRecord("sys_report");
reportGR.addQuery("sys_id", "IN", reportIds.join(","));
reportGR.query();
while (reportGR.next()) {
    reportGR.setValue("user", "group");
    reportGR.update();
}

reportIds.forEach(function(reportId) {
    var validCheck = new GlideRecord('sys_report');
    if(!validCheck.get(reportId))
	return; // not a real report id

    shareGroup.forEach(function(groupId) {
        var reportPerm = new GlideRecord("sys_report_users_groups");
        reportPerm.addQuery("report_id", reportId);
        reportPerm.addQuery("group_id", groupId);
        reportPerm.query();
        if (reportPerm.next()) {
            gs.addInfoMessage(gs.getMessage("Report {0} is already shared with group {1}", [reportPerm.getDisplayValue("report_id"), reportPerm.getDisplayValue("group_id")]));
        } else {
            reportPerm.newRecord();
            reportPerm.setValue("report_id", reportId);
            reportPerm.setValue("group_id", groupId);
            reportPerm.insert();
            gs.addInfoMessage(gs.getMessage("Report {0} shared with group {1}", [reportPerm.getDisplayValue("report_id"), reportPerm.getDisplayValue("group_id")]));
        }
    });

    shareUsers.forEach(function(userId) {
        var reportPerm = new GlideRecord("sys_report_users_groups");
        reportPerm.addQuery("report_id", reportId);
        reportPerm.addQuery("user_id", userId);
        reportPerm.query();
        if (reportPerm.next()) {
            gs.addInfoMessage(gs.getMessage("Report {0} is already shared with user {1}", [reportPerm.getDisplayValue("report_id"), reportPerm.getDisplayValue("user_id")]));
        } else {
            reportPerm.newRecord();
            reportPerm.setValue("report_id", reportId);
            reportPerm.setValue("user_id", userId);
            reportPerm.insert();
            gs.addInfoMessage(gs.getMessage("Report {0} shared with user {1}", [reportPerm.getDisplayValue("report_id"), reportPerm.getDisplayValue("user_id")]));
        }
    });

    shareRoles.forEach(function(roleId) {
        var userIds = [];
        var userRoleGR = new GlideRecord("sys_user_has_role");
        userRoleGR.addQuery("user.active", true);
        userRoleGR.addQuery("role", roleId);
        userRoleGR.query();
		var roleName;
        while (userRoleGR.next()) {
			if(!roleName){
				roleName = userRoleGR.getDisplayValue("role");//for info msg
			}
            userIds.push(userRoleGR.getValue("user"));
        }
        var au = new global.ArrayUtil();
        userIds = au.unique(userIds);
		
		var reportName;
        userIds.forEach(function(userId) {
            var reportPerm = new GlideRecord("sys_report_users_groups");
            reportPerm.addQuery("report_id", reportId);
            reportPerm.addQuery("user_id", userId);
            reportPerm.query();
            if (reportPerm.next()) {
//                 gs.addInfoMessage(gs.getMessage("Report {0} is already shared with user {1}", [reportPerm.getDisplayValue("report_id"), reportPerm.getDisplayValue("user_id")]));
            } else {
                reportPerm.newRecord();
                reportPerm.setValue("report_id", reportId);
                reportPerm.setValue("user_id", userId);
                reportPerm.insert();
//                 gs.addInfoMessage(gs.getMessage("Report {0} shared with user {1}", [reportPerm.getDisplayValue("report_id"), reportPerm.getDisplayValue("user_id")]));
            }
			if(!reportName){
				reportName = reportPerm.getDisplayValue("report_id");
			}
        });
		gs.addInfoMessage(gs.getMessage("Report {0} shared with {1} users who have the {2} role",[reportName, userIds.length + "", roleName]));
    });
});


action.setRedirectURL(current);
Comments
J_r_my POTTIER
ServiceNow Employee
ServiceNow Employee

It looks very handy. Thanks for the share !

Friedrich Gehri
Tera Expert

Thanks for this code. It helped very much.
However, on our instance it has been the case that the field 'page' has been empty at the dashboard tabs, but instead I had to use the field 'canvas_page' to find the related reportIDs. Then the rest of the script worked fine.
As this might help someone else as well, I will just paste the code here. You need to replace the part before the line

var shareUsers = [];

with it:

var dashboardTabM2M = new GlideRecord('pa_m2m_dashboard_tabs');
dashboardTabM2M.addQuery("dashboard", current.sys_id);
dashboardTabM2M.query();
var canvas_pages = [];
while (dashboardTabM2M.next()) {
    canvas_pages.push(dashboardTabM2M.tab.canvas_page.sys_id + "");
}

var portal_widgets = [];

canvas_pages.forEach(function(canvas_pageID) {
    var grid_canvas_pane = new GlideRecord("sys_grid_canvas_pane");
    grid_canvas_pane.addQuery("grid_canvas", canvas_pageID);
    grid_canvas_pane.query();
    while (portals.next()) {
        portal_widgets.push(grid_canvas_pane.portal_widget + '');
    }
});


var reportIds = [];
portal_widgets.forEach(function(portal_widgetIDs) {
    var portals = new GlideRecord("sys_portal");
    portals.addQuery("sys_id", portal_widgetIDs);
    portals.query();
    while (portals.next()) {
        var pageProp = new GlideRecord("sys_portal_preferences");
        pageProp.addQuery("portal_section", portals.getUniqueValue());
        pageProp.addQuery("name", "sys_id");
        pageProp.query();
        if (pageProp.next()) {
            reportIds.push(pageProp.getValue("value"));
        }
    }
});

 

 

Duane van Gesse
Tera Contributor

@J_r_my POTTIER and @Luke Van Epen .. first.. love this option.. however, is this considered a customization? Any impacts or conflicts with future ServiceNow rollouts?

Luke Van Epen
Tera Guru

@Duane van Gesse   you're not modifying anything baseline so it wont impact future upgrades.

If you're worried about 'good' vs 'bad' customisation, I suggest you read this whitepaper on the topic: 

https://www.servicenow.com/content/dam/servicenow-assets/public/en-us/doc-type/success/workbook/busi... 

JenniferS777
Tera Contributor

I just wanted to tell you thank you so much for this!!!! With no ability to publish this at least gives us options if we can't share to roles in reports!

Donte Hooker1
Giga Guru
Giga Guru

This is worth its weight in gold! Appreciate it.

Andrew_TND
Mega Sage
Mega Sage

Absolute game changer, thanks @Luke Van Epen 

Yingbo Wang
Tera Explorer

@Luke Van Epen 

This solution is great. Thank you for sharing. 

It does not work with deleting a user/group/role. When we delete a user/group/role from the dashboard, we want to click the "Sync sharing permissions" UI link to sync and delete the user/group/role from all the reports in the Dashboard.

How to implement it? Thank you. 

Karin Duijnker1
Tera Contributor

@Luke Van Epen 

Thank you for sharing. 

It does not work with deleting a user/group/role. If I delete a user/group from the dashboard permission and use sync, it does not remove the user/group. Can this being improved?

ZW-C
Tera Contributor

Very helpful and time saving! Thanks for the effort and sharing @Luke Van Epen 

Luke Van Epen
Tera Guru

@Karin Duijnker1  @Yingbo Wang  As I mentioned in the article, this use case only covers additions, not deletions. This prevents cases where multiple dashboards contain the same report, where the user needs to keep permissions on one dashboard but not the other. If you also delete permissions using the sync, the user would have permissions to the underlying reports removed in all places. I do not want that, but you are free to modify this to suit. I would personally be handling that scenario in an after-delete business rule for the affected tables, rather than in the sync button. 

pbusch
Tera Expert

Excellent work. Thanks a million !

tom_p94
Tera Contributor

Thanks @Luke Van Epen, very useful!

Q-I appreciate this was acknowledged in the original post, but has anyone since managed to amend the script to allow the role to cascade down from dashboard to reports rather than adding the members to the report who have that role?

Version history
Last update:
‎11-11-2021 05:43 PM
Updated by: