azure-devopsazure-boardsazure-devops-server-2022

How to add a drop-down list for data entry in Azure DevOps Server 2022?


I've recently switched process models from XML to Inherited, to allow for easier customization of Work Item Types, etc.

Under the old XML model we could modify, for example, a PBI's Effort field to create a pick list of numbers for easier data entry. I'm not seeing how to do this with the Inheritance model:

enter image description here

The documentation for doing so under XML is here, but there doesn't seem to be an equivalent under Inheritance.

enter image description here

For example, here's the Effort field in my XML-modeled collection:

enter image description here

Is it possible to modify a PBI to this extent under the Inheritance model? If so, how does one go about doing it?


Solution

  • It can't be done using in-built methods. So say Microsoft Support Engineers.

    Tossing together this clunky workaround wasn't pleasant, but at least it accomplishes the goal (I say that because I loathe working in JavaScript).

    Install the TamperMonkey extension in your browser and load up the script below. Replace example, host, collection and project in the URLs with your own values. Edit a PBI and feel the love.

    Note that this only addresses PBIs and their Effort and Business Value fields under the Scrum template; that's all I needed to cover at this point. The script will need to be adjusted to support other Work Item Types and fields.

    // ==UserScript==
    // @name         PBI PickLists
    // @namespace    https://example.com/
    // @version      0.1
    // @description  Try to take over the world!
    // @author       You
    // @match        http://host/collection/*
    // @icon         none
    // @grant        none
    // ==/UserScript==
    
    (function() {
        "use strict";
    
        // Your code here...
        function setSelected(item) {
            item.setAttribute("aria-selected", "true");
            item.classList.add("selected");
            item.style.backgroundColor = "#deecf8";
            item.style.border = "1px solid #c7dff3";
        }
    
        function clearSelected(item) {
            item.setAttribute("aria-selected", "false");
            item.classList.remove("selected");
            item.style.backgroundColor = "white";
            item.style.border = "1px solid white";
        }
    
        function getFibonacciList(textBox, container) {
            var pickList = document.createElement("ul");
            var items = ["1", "2", "3", "5", "8", "13"];
    
            pickList.setAttribute("role", "listbox");
            pickList.classList.add("items");
            pickList.style.marginBottom = "0px";
            pickList.style.marginTop = "0px";
    
            for (var i = 0; i < items.length; i++) {
                var item = document.createElement("li");
                item.setAttribute("role", "option");
                item.setAttribute("aria-posinset", i + 1);
                item.setAttribute("aria-setsize", items.length);
                item.setAttribute("data-id", i);
                item.style.padding = "3px";
                item.style.cursor = "pointer";
                item.textContent = items[i];
    
                if (item.textContent == textBox.value) {
                    setSelected(item);
                } else {
                    clearSelected(item);
                }
    
                pickList.appendChild(item);
            }
    
            pickList.childNodes.forEach(function(item) {
                item.addEventListener("mouseenter", function() {
                    setSelected(item);
                });
    
                item.addEventListener("mouseleave", function() {
                    clearSelected(item);
                });
            });
    
            return pickList;
        }
    
        function clearList(textBox, uniqueId) {
            var container = document.getElementById(uniqueId);
    
            if (container) {
                var items = container.querySelectorAll("li");
                var item = container.querySelector("li.selected");
    
                if (item) {
                    textBox.value = item.textContent;
                    textBox.dispatchEvent(new Event("change"));
                }
    
                items.forEach(function(item) {
                    item.removeEventListener("mouseenter", function() {
                        setSelected(item);
                    });
    
                    item.removeEventListener("mouseleave", function() {
                        clearSelected(item);
                    });
                });
    
                container.remove();
            }
        }
    
        function buildList(textBox, uniqueId) {
            var container = document.getElementById(uniqueId);
    
            if (!container) {
                var rect = textBox.getBoundingClientRect();
                var width = rect.width + 5
                var x = rect.left - 3;
                var y = rect.top + rect.height + 1;
    
                container = document.createElement("div");
                container.setAttribute("aria-label", textBox.getAttribute("aria-label"));
                container.setAttribute("id", uniqueId);
                container.classList.add("combo-drop-popup");
                container.style.backgroundColor = "#fff";
                container.style.transition = 'height 0.25s ease';
                container.style.overflow = 'hidden';
                container.style.opacity = "1";
                container.style.zIndex = "1910887";
                container.style.border = "1px solid #c8c8c8";
                container.style.height = '0';
                container.style.width = width + "px";
                container.style.left = x + "px";
                container.style.top = y + "px";
    
                var pickList = getFibonacciList(textBox, container);
    
                container.appendChild(pickList);
                textBox.parentNode.parentNode.appendChild(container);
    
                setTimeout(function() {
                    container.style.height = "auto"
                }, 250);
            }
        }
    
        function addPickList(textBox, uniqueId) {
            if (textBox) {
                var arrow = textBox.parentNode.nextSibling
    
                arrow.style.marginLeft = textBox.offsetWidth - 23 + "px";
                arrow.style.marginTop = "3px";
                arrow.style.display = "block";
    
                if (!document.getElementById(uniqueId)) {
                    textBox.addEventListener("focus", function() { buildList(textBox, uniqueId); });
                    textBox.addEventListener("blur", function() { clearList(textBox, uniqueId); });
                    arrow.addEventListener("click", function() { buildList(textBox, uniqueId); });
                };
            }
        }
    
        function isPbiEditor() {
            var isPbiEditor = false;
            var anchors = document.querySelectorAll("a");
    
            for (var i = 0; i < anchors.length; i++) {
                var anchor = anchors[i];
                if (anchor.href.startsWith("http://host/collection/project/_workitems/edit/")) {
                    if (anchor.textContent.startsWith("Product Backlog Item")) {
                        isPbiEditor = true;
                        break;
                    }
                }
            }
    
            return isPbiEditor;
        }
    
        var observer = new MutationObserver(function(mutations) {
            var effort = document.querySelector("input[aria-label='Effort']");
            var value = document.querySelector("input[aria-label='Business Value']");
    
            for (var mutation of mutations) {
                if (mutation.type === "childList") {
                    if (isPbiEditor()) {
                        addPickList(effort, "58KJ76F");
                        addPickList(value, "37L9Q9P");
                    }
                }
            }
        });
    
        observer.observe(document.body, { childList: true, subtree: true });
    })();