Zendesk View Agent Name v1.0.0
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`);
})();