Zendesk View Agent Name latest version (currently v2.0.0)

← Back to User Scripts

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