Zendesk View Agent Name v2.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      2.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 => {
            const img = button.querySelector('img');
            if (!img) {
                console.debug(`${LOG_PREFIX} Button has no img element, skipping`);
                return;
            }

            const altText = img.getAttribute('alt');
            if (!altText) {
                console.debug(`${LOG_PREFIX} img has no alt attribute, skipping`);
                return;
            }

            // Skip if the alt text hasn't changed since we last processed this button
            if (button.dataset.lastAlt === altText) {
                return;
            }

            // alt format is "Agent John Smith is isIdle" — strip leading "Agent " then take first word
            const nameText = altText.replace(/^Agent\s+/i, '').trim();
            const firstName = nameText.split(/\s+/)[0];

            if (!firstName) {
                console.debug(`${LOG_PREFIX} Could not extract first name from alt: "${altText}"`);
                return;
            }

            console.debug(`${LOG_PREFIX} Extracted first name: "${firstName}" from alt: "${altText}"`);
            
            // Find the parent li element
            const parentLi = button.closest('li');
            if (!parentLi) {
                console.debug(`${LOG_PREFIX} Could not find parent li element`);
                return;
            }
            
            // Update existing label or create a new one
            const existingLabel = parentLi.querySelector('.agent-name-label');
            if (existingLabel) {
                existingLabel.textContent = firstName;
            } else {
                const nameLabel = document.createElement('div');
                nameLabel.className = 'agent-name-label';
                nameLabel.textContent = firstName;
                parentLi.appendChild(nameLabel);
            }
            
            // Store the alt text we processed so we can detect future changes
            button.dataset.lastAlt = altText;
            processedCount++;
            console.debug(`${LOG_PREFIX} Added/updated 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 === 'alt') {
                    // If img alt changes, we need to reprocess
                    console.debug(`${LOG_PREFIX} alt 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: ['alt']
        });

        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();
        }

        // Use the narrowest available container to reduce noise
        const container = document.querySelector('[data-test-id="workspace-content"]') || document.body;
        console.debug(`${LOG_PREFIX} Observing container: ${container === document.body ? 'document.body' : '[data-test-id="workspace-content"]'}`);

        documentObserver = new MutationObserver(() => {
            console.debug(`${LOG_PREFIX} Document MutationObserver fired`);
            const list = document.getElementById('agentCollisionViewerList');
            
            if (list) {
                // Found the list — disconnect immediately to stop unnecessary firing
                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(container, {
            childList: true,
            subtree: true
        });

        console.debug(`${LOG_PREFIX} Now observing container 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`);

        // Use the narrowest available container to reduce noise
        const container = document.querySelector('[data-test-id="workspace-content"]') || document.body;
        
        const disappearanceObserver = new MutationObserver(() => {
            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(container, {
            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`);
})();