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

655 Views Asked by At

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?

2

There are 2 best solutions below

0
On BEST ANSWER

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

You can set the datatype for the custom field to picklist and the underlying datatype to text or integer.

That way you can provide a list of values to select from.

As an alternative, there are a number of custom picklist controls available on the Azure DevOps Marketplace. These can be used to databind a field to a REST API or different datatypes etc.

If needed you can hide the existing field and put a new control on the form.