Zendesk View Agent Name v1.0.0

← Back to User Scripts

This userscript enhances the Zendesk ticket viewing experience by displaying the first name of agents below their avatar in the agent collision viewer list. This makes it easier to quickly identify which agents are currently viewing or editing the same ticket.

The script automatically detects when the agent collision viewer list appears and updates the display when agents join or leave.

Script Content

// ==UserScript==
// @name         Zendesk: View Agent Name
// @namespace    https://www.timhilton.xyz/user-scripts
// @version      1.0.0
// @description  Displays the first name of agents below their avatar in the agent collision viewer list.
// @author       Tim Hilton using GitHub Copilot
// @match        https://*.zendesk.com/agent/tickets/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const LOG_PREFIX = '[Zendesk: View Agent Name]';
    let observerFireCount = 0;

    console.debug(`${LOG_PREFIX} Script initialised`);

    // Add styles for the name labels
    const style = document.createElement('style');
    style.textContent = `
        .agent-name-label {
            position: absolute;
            bottom: -20px;
            left: 50%;
            transform: translateX(-50%);
            background: green;
            color: white;
            text-align: center;
            padding: 2px 6px;
            font-size: 11px;
            white-space: nowrap;
            border-radius: 2px;
            z-index: 1000;
        }
        
        #agentCollisionViewerList li {
            position: relative;
        }
    `;
    document.head.appendChild(style);

    let listObserver = null;
    let documentObserver = null;

    // Process collision avatars and add name labels
    function processCollisionAvatars() {
        console.debug(`${LOG_PREFIX} Processing collision avatars`);
        const buttons = document.querySelectorAll('#agentCollisionViewerList button[data-test-id="collision-avatar"]');
        console.debug(`${LOG_PREFIX} Found ${buttons.length} collision avatar button(s)`);
        
        let processedCount = 0;
        buttons.forEach(button => {
            // Check if we've already processed this button
            if (button.dataset.nameProcessed === 'true') {
                return;
            }
            
            const ariaLabel = button.getAttribute('aria-label');
            if (!ariaLabel) {
                console.debug(`${LOG_PREFIX} Button has no aria-label, skipping`);
                return;
            }
            
            // Extract the first word (first name) from the aria-label
            const firstName = ariaLabel.trim().split(/\s+/)[0];
            
            if (!firstName) {
                console.debug(`${LOG_PREFIX} Could not extract first name from aria-label: "${ariaLabel}"`);
                return;
            }
            
            console.debug(`${LOG_PREFIX} Extracted first name: "${firstName}" from aria-label: "${ariaLabel}"`);
            
            // Find the parent li element
            const parentLi = button.closest('li');
            if (!parentLi) {
                console.debug(`${LOG_PREFIX} Could not find parent li element`);
                return;
            }
            
            // Remove any existing label
            const existingLabel = parentLi.querySelector('.agent-name-label');
            if (existingLabel) {
                existingLabel.remove();
            }
            
            // Create and add the name label
            const nameLabel = document.createElement('div');
            nameLabel.className = 'agent-name-label';
            nameLabel.textContent = firstName;
            parentLi.appendChild(nameLabel);
            
            // Mark as processed
            button.dataset.nameProcessed = 'true';
            processedCount++;
            console.debug(`${LOG_PREFIX} Added name label for: ${firstName}`);
        });
        
        if (processedCount > 0) {
            console.log(`${LOG_PREFIX} ✅ Successfully added ${processedCount} name label(s)`);
        }
    }

    // Observe mutations within the agentCollisionViewerList
    function observeList() {
        console.debug(`${LOG_PREFIX} Entering observeList()`);
        const list = document.getElementById('agentCollisionViewerList');
        if (!list) {
            console.debug(`${LOG_PREFIX} agentCollisionViewerList not found, exiting`);
            return;
        }

        console.debug(`${LOG_PREFIX} agentCollisionViewerList found, processing existing avatars`);
        // Process existing avatars
        processCollisionAvatars();

        // Create observer for the list
        if (listObserver) {
            console.debug(`${LOG_PREFIX} Disconnecting existing list observer`);
            listObserver.disconnect();
        }

        listObserver = new MutationObserver((mutations) => {
            observerFireCount++;
            console.debug(`${LOG_PREFIX} List MutationObserver fired (count: ${observerFireCount})`);
            
            let shouldProcess = false;

            for (const mutation of mutations) {
                if (mutation.type === 'childList') {
                    // Check if buttons were added or removed
                    if (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0) {
                        console.debug(`${LOG_PREFIX} childList mutation detected: ${mutation.addedNodes.length} added, ${mutation.removedNodes.length} removed`);
                        shouldProcess = true;
                        break;
                    }
                } else if (mutation.type === 'attributes' && mutation.attributeName === 'aria-label') {
                    // If aria-label changes, we need to reprocess
                    console.debug(`${LOG_PREFIX} aria-label attribute changed`);
                    shouldProcess = true;
                    break;
                }
            }

            if (shouldProcess) {
                console.debug(`${LOG_PREFIX} Mutations require reprocessing`);
                processCollisionAvatars();
            } else {
                console.debug(`${LOG_PREFIX} No relevant mutations, skipping reprocessing`);
            }
        });

        listObserver.observe(list, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['aria-label']
        });

        console.debug(`${LOG_PREFIX} Now observing agentCollisionViewerList for mutations`);
    }

    // Observe the document for when agentCollisionViewerList appears
    function observeDocument() {
        console.debug(`${LOG_PREFIX} Entering observeDocument()`);
        
        if (documentObserver) {
            console.debug(`${LOG_PREFIX} Disconnecting existing document observer`);
            documentObserver.disconnect();
        }

        documentObserver = new MutationObserver((mutations) => {
            console.debug(`${LOG_PREFIX} Document MutationObserver fired`);
            const list = document.getElementById('agentCollisionViewerList');
            
            if (list) {
                // Found the list, stop observing document
                console.debug(`${LOG_PREFIX} agentCollisionViewerList appeared in DOM`);
                documentObserver.disconnect();
                
                // Start observing the list
                observeList();
                
                // Also observe for when the list disappears
                observeListDisappearance();
            }
        });

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

        console.debug(`${LOG_PREFIX} Now observing document.body for agentCollisionViewerList`);
        
        // Check if the list already exists
        const list = document.getElementById('agentCollisionViewerList');
        if (list) {
            console.debug(`${LOG_PREFIX} agentCollisionViewerList already exists in DOM`);
            documentObserver.disconnect();
            observeList();
            observeListDisappearance();
        } else {
            console.debug(`${LOG_PREFIX} agentCollisionViewerList not yet in DOM, waiting for it to appear`);
        }
    }

    // Observe for when the list disappears
    function observeListDisappearance() {
        console.debug(`${LOG_PREFIX} Setting up disappearance observer`);
        
        const disappearanceObserver = new MutationObserver((mutations) => {
            console.debug(`${LOG_PREFIX} Disappearance MutationObserver fired`);
            const list = document.getElementById('agentCollisionViewerList');
            
            if (!list) {
                // List has disappeared
                console.debug(`${LOG_PREFIX} agentCollisionViewerList has disappeared from DOM`);
                
                // Stop all observers
                if (listObserver) {
                    console.debug(`${LOG_PREFIX} Disconnecting list observer`);
                    listObserver.disconnect();
                    listObserver = null;
                }
                disappearanceObserver.disconnect();
                
                // Resume observing the document
                console.debug(`${LOG_PREFIX} Resuming document observation`);
                observeDocument();
            }
        });

        disappearanceObserver.observe(document.body, {
            childList: true,
            subtree: true
        });
        
        console.debug(`${LOG_PREFIX} Disappearance observer active`);
    }

    // Initialise
    console.debug(`${LOG_PREFIX} Starting initialisation`);
    observeDocument();
    console.log(`${LOG_PREFIX} 🎉 Script ready and monitoring for agent collision list`);
})();