Azure DevOps Zendesk Link latest version (currently v1.2.2)
← Back to User Scripts
Script Content
// ==UserScript==
// @name Azure DevOps Zendesk Link
// @namespace https://www.timhilton.xyz/user-scripts
// @version 1.2.2
// @description Adds a button to Azure DevOps work items to open the corresponding Zendesk ticket.
// @author Tim Hilton using Jules
// @match https://dev.azure.com/*
// @match https://*.visualstudio.com/*
// @grant GM_openInTab
// @grant GM_addStyle
// ==/UserScript==
(function() {
'use strict';
const LOG_PREFIX = '[Azure DevOps: Zendesk Link]';
console.debug(`${LOG_PREFIX} Script initialized`);
const zendeskRegex = /\[Zendesk (\d+)\]/;
const zendeskIconSvg = `
`;
function addButtonToTitle(titleElement) {
console.debug(`${LOG_PREFIX} addButtonToTitle() called`);
const match = titleElement.value.match(zendeskRegex);
if (!match) {
console.debug(`${LOG_PREFIX} No Zendesk link found in title`);
return;
}
const zendeskId = match[1];
const zendeskUrl = `https://audacia.zendesk.com/agent/tickets/${zendeskId}`;
// Check if button already exists
if (document.getElementById('zendesk-link-button')) {
console.debug(`${LOG_PREFIX} Button already exists, skipping`);
return;
}
console.debug(`${LOG_PREFIX} Zendesk ticket ${zendeskId} found, adding button`);
const button = document.createElement('button');
button.id = 'zendesk-link-button';
button.innerHTML = zendeskIconSvg;
button.onclick = () => {
GM_openInTab(zendeskUrl, { active: true });
};
const titleElementParent = titleElement.parentElement;
titleElementParent.style.position = 'relative';
// Add padding to the title element to make space for the button
titleElement.style.paddingLeft = '40px';
GM_addStyle(`
#zendesk-link-button {
position: absolute;
left: 0px;
top: 50%;
transform: translateY(-50%);
width: 34px;
height: 34px;
z-index: 1000;
border: none;
background: transparent;
cursor: pointer;
padding: 3px;
}
#zendesk-link-button svg {
width: 100%;
height: 100%;
}
`);
titleElementParent.appendChild(button);
console.log(`${LOG_PREFIX} 🎉 Zendesk link button added successfully!`);
}
function addZendeskLinkToInlineItem(inlineLinkElement) {
console.debug(`${LOG_PREFIX} addZendeskLinkToInlineItem() called`);
const wiData = JSON.parse(inlineLinkElement.dataset.wi);
const title = wiData[0].title;
const match = title.match(zendeskRegex);
if (!match) {
console.debug(`${LOG_PREFIX} No Zendesk link found in inline item title`);
return;
}
inlineLinkElement.dataset.zendeskLinkAdded = 'true';
const zendeskId = match[1];
const zendeskUrl = `https://audacia.zendesk.com/agent/tickets/${zendeskId}`;
console.debug(`${LOG_PREFIX} Zendesk ticket ${zendeskId} found, adding inline link`);
const zendeskLink = document.createElement('a');
zendeskLink.href = zendeskUrl;
zendeskLink.target = '_blank';
zendeskLink.innerHTML = zendeskIconSvg;
zendeskLink.classList.add('zendesk-inline-link');
inlineLinkElement.before(zendeskLink);
}
let mutationCount = 0;
const observer = new MutationObserver(() => {
mutationCount++;
console.debug(`${LOG_PREFIX} MutationObserver fired (#${mutationCount})`);
const titleElement = document.querySelector('[aria-label="Title field"]');
if (titleElement) {
addButtonToTitle(titleElement);
} else {
console.debug(`${LOG_PREFIX} Title element not found`);
}
const inlineLinks = document.querySelectorAll('a[data-wi]:not([data-zendesk-link-added])');
inlineLinks.forEach(addZendeskLinkToInlineItem);
});
GM_addStyle(`
.zendesk-inline-link {
display: inline-block;
width: 16px;
height: 16px;
margin-right: 4px;
vertical-align: middle;
}
.zendesk-inline-link svg {
width: 100%;
height: 100%;
}
`);
console.debug(`${LOG_PREFIX} Starting MutationObserver on document`);
observer.observe(document, {
childList: true,
subtree: true
});
})();