Show Workgroup Member Plugin


:spiral_notepad: Description Show Workgroup Member Plugin
:balance_scale: Legal Agreement By using this CoLab item, you are agreeing to SailPoint’s Terms of Service for our developer community and open-source CoLab.
:hammer_and_wrench: Repository Link GitHub - sailpoint-oss/colab-show-workgroup-member-plugin
:open_book: New to IIQ Plugins in the CoLab? Read the getting started guide for IIQ Plugins in the CoLab.
:hospital: Supported by Community Developed

Overview

The ‘Show Workgroup Member’ plugin enhances the approval process for access requests by allowing end users to see the members of a workgroup responsible for approving their requests.

Requirements

When a user submits an access request that requires approval from a workgroup, the end user cannot see the members of that workgroup. This makes it difficult for users to contact an individual for pending approvals. The ‘Show Workgroup Member’ plugin addresses this issue by adding an ‘i’ icon next to the existing mail icon for each requested item pending workgroup approval. When the end user clicks on the ‘i’ icon, it displays the workgroup members along with their ‘admin-defined’ user attributes.

IIQ version: 8.0+

Guide

Follow below steps to install the plugin and required configuration:

  1. Build the plugin.

  2. Upload the plugin zip in SailPoint IdentityIQ.

  3. Once installed, click on Configure to configure the fields you would like to display for the approvers.


  4. Navigate to the request pending for ‘Workgroup’ approval.

  5. Click on ‘i’ icon beside the workgroup to view the members.

Feedback

For bugs, feature enhancement requests, issues, or questions please contact me on LinkedIn or aashish.bhatia07@gmail.com

7 Likes

@ashishbhatia -This is a very clean and useful plugin that significantly reduces the time administrators spend repeatedly checking workgroup members. It also helps requesters gain better visibility while an approval is pending, making it easier for them to understand who is involved in the workgroup and reach out if additional clarification is needed.

In addition, there are two points I would like to highlight:

  • While adding the member properties (Manager), I encounter the error shown below. The behavior is inconsistent—sometimes the properties are recognized, and other times they are not. Is this a known issue, and do you have any recommendations or a resolution for it?
Caused by: java.lang.ClassCastException: class sailpoint.object.Identity$HibernateProxy$GTQie9ks cannot be cast to class java.lang.String (sailpoint.object.Identity$HibernateProxy$GTQie9ks is in unnamed module of loader org.apache.catalina.loader.ParallelWebappClassLoader @38029686; java.lang.String is in module java.base of loader 'bootstrap')
	at com.showwkgpmember.service.ShowWorkgroupMemberService.getMembersDetails(ShowWorkgroupMemberService.java:73)
	at com.showwkgpmember.resource.ShowWorkgroupMemberResource.getWorkgroupDetails(ShowWorkgroupMemberResource.java:61)
  • UI consistency
    The existing JavaScript snippet was not fully aligned with the SailPoint UI. I have updated the script to ensure the look and feel are consistent with the native SailPoint interface.

const WGP_BASE_PATH = "showWorkgroupMember";

(function ($) {

    /* ============================
       SailPoint Modal UI
       ============================ */
    function showWorkgroupModal(workgroupName, contentHtml) {

        const modalId = "sp-wgp-modal";
        $("#" + modalId).remove();

        const modalHtml = `
        <div id="${modalId}" class="sp-modal-overlay">
            <div class="sp-modal">
                <div class="sp-modal-header">
                    <h3>Workgroup: ${workgroupName}</h3>
                    <button class="sp-modal-close">&times;</button>
                </div>
                <div class="sp-modal-body">
                    ${contentHtml}
                </div>
            </div>
        </div>`;

        $("body").append(modalHtml);

        $(".sp-modal-close, .sp-modal-overlay").on("click", function (e) {
            if (e.target === this) {
                $("#" + modalId).remove();
            }
        });
    }

    /* ============================
       Inject SailPoint‑Style CSS
       ============================ */
    function injectStyles() {
        if ($("#sp-wgp-style").length) return;

        const css = `
        .sp-modal-overlay {
            position: fixed;
            inset: 0;
            background: rgba(0,0,0,0.45);
            z-index: 10000;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .sp-modal {
            background: #ffffff;
            width: 720px;
            max-height: 80vh;
            border-radius: 6px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.25);
            display: flex;
            flex-direction: column;
            font-family: "Open Sans","Helvetica Neue",Arial,sans-serif;
        }

        .sp-modal-header {
            padding: 14px 18px;
            border-bottom: 1px solid #e5e5e5;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .sp-modal-header h3 {
            margin: 0;
            color: #0070a8;
            font-size: 18px;
        }

        .sp-modal-close {
            background: none;
            border: none;
            font-size: 22px;
            cursor: pointer;
            color: #666;
        }

        .sp-modal-body {
            padding: 16px;
            overflow-y: auto;
        }

        .sp-modal-body table {
            width: 100%;
            border-collapse: collapse;
        }

        .sp-modal-body th {
            background: #f3f6f9;
            padding: 8px;
            text-align: left;
        }

        .sp-modal-body td {
            padding: 8px;
            border-top: 1px solid #e5e5e5;
        }

        .sp-wgp-btn {
            margin-left: 6px;
            padding: 2px 6px;
            border: 1px solid #cfd8dc;
            background: #ffffff;
            border-radius: 3px;
            cursor: pointer;
        }

        .sp-wgp-btn i {
            color: #0070a8;
        }
        `;

        $("<style id='sp-wgp-style'>").text(css).appendTo("head");
    }

    /* ============================
       Approval Scan Logic
       ============================ */
    function scanApprovalItems() {

        const approvalItems =
            $("sp-identity-request-item-approval-item[sp-approval-item='moreLessItem'] > div");

        approvalItems.each(function (_, element) {

            if (!element.children || !element.children[1]) return;

            // 🔒 Hard DOM lock (survives observer re‑fires)
            if (element.getAttribute("data-wgp-locked") === "true") return;
            element.setAttribute("data-wgp-locked", "true");

            // Avoid duplicate buttons
            if ($(element).find(".sp-wgp-btn").length) return;

            let workgroupName;
            try {
                workgroupName =
                    element.childNodes[0].nodeValue.split("\n")[1].trim();
            } catch (e) {
                return;
            }

            const url = PluginHelper.getPluginRestUrl(
                `${WGP_BASE_PATH}/getWorkgroupDetails?workgroupName=${encodeURIComponent(workgroupName)}`
            );

            $.ajax({
                type: "GET",
                url: url,
                beforeSend: function (request) {
                    request.setRequestHeader(
                        "X-XSRF-TOKEN",
                        PluginHelper.getCsrfToken()
                    );
                },
                success: function (data, status) {

                    if (!data || status !== "success") return;

                    const button = document.createElement("button");
                    button.className = "sp-wgp-btn";
                    button.title = "View workgroup members";

                    const icon = document.createElement("i");
                    icon.className = "fa fa-users";
                    button.appendChild(icon);

                    element.children[0].after(button);

                    $(button).on("click", function () {
                        showWorkgroupModal(workgroupName, data);
                    });
                }
            });
        });
    }

    /* ============================
       Mutation Observer (FIXED)
       ============================ */
    $(document).ready(function () {

        injectStyles();

        let debounceTimer = null;

        function debouncedScan() {
            if (debounceTimer) return;

            debounceTimer = setTimeout(function () {
                debounceTimer = null;
                scanApprovalItems();
            }, 120);
        }

        const observer = new MutationObserver(debouncedScan);

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        // Initial scan
        debouncedScan();
    });

})(jQuery);



2 Likes

Looks nice @kannan_sb85, and definitely thanks for the plugin @ashishbhatia, amazing work :slight_smile: !

Regards,

Mustafa

1 Like

Thanks @kannan_sb85 , I will take a look at the issue.

Really liked the new UI. Appreciate your efforts.

1 Like