JiraRoboScript

Jira helper to preselect some fields when creating dialog is shown

2021/11/19のページです。最新版はこちら

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         JiraRoboScript
// @namespace    http://tampermonkey.net/
// @version      3.7
// @description  Jira helper to preselect some fields when creating dialog is shown
// @author       Robo
// @homepage     https://greasyfork.org/sk/scripts/400374-jiraroboscript
// @require      https://code.jquery.com/jquery-3.4.1.min.js
// @match        https://pmjira.cz.tmo/*
// @match        https://vpconfluence.cz.tmo/*
// @grant        GM_addValueChangeListener
// @grant        GM_setValue
// @grant        GM_addStyle
// ==/UserScript==

(function () {
	'use strict';
	//due to conflicts with page scripts
	let $myJQuery = jQuery.noConflict(true);
    const CONFLUENCE_TO_JIRA_VAR = "CONFLUENCE_TO_JIRA_VAR";

	// Starts listening for changes in the target HTML element of the page.
	let target = document.getElementById('jira');

    if(target) {
        let bodyMutationObserver = new MutationObserver(function (mutations) {
            for (let i = 0; i < mutations.length; i++) { //faster than forEach
                let mutation = mutations[i];
                //console.log(mutation);
                if (mutation.addedNodes.length > 0
                    && (mutation.addedNodes[0].id === "create-issue-dialog" || mutation.addedNodes[0].id === "create-subtask-dialog")) {
                    //add inner dialog mutationObserver
                    initializeDialogMutationObserver(mutation.addedNodes[0]);
                }
            }
        });

        bodyMutationObserver.observe(target, {
            childList: true
        });
    }

    //confluence interaction
    confluenceTableInit($myJQuery("body"));
    //for iframe
    setTimeout(function () {
        confluenceTableInit($myJQuery("#wysiwygTextarea_ifr").contents());
	}, 2000);


    GM_addValueChangeListener(CONFLUENCE_TO_JIRA_VAR, function() {
        fillFieldsFromConfluence(arguments[2]);
    });

    ////////////////

	function initializeDialogMutationObserver(dialog) {
		let dialogMutationObserver = new MutationObserver(function (mutations) {
			for (let i = 0; i < mutations.length; i++) { //faster than forEach
				let mutation = mutations[i];
				//console.log(mutation);
				if (mutation.target.className.includes("jira-dialog-content-ready")) {
					//dialog is ready
					//don't disconnect observer, because changing project type cause reload of inner content and dialog panel class change is triggered
					//makeMiracle(mutation.target);
					createMiracleButtons(mutation.target);
				}
			}
		});

		dialogMutationObserver.observe(dialog, {
			attributes: true,
			attributeFilter: ["class"]
		});
	}

    function createMiracleButtons(dialog) {
		let dialogHeader = $myJQuery(dialog).find(".jira-dialog-heading h2");
		if (dialogHeader.find(".miracleButton").length === 0) {
			let oneAppMiracleButton = $myJQuery("<a class='aui-button aui-button-primary aui-style miracleButton' title='Prefil form' href='#' style='margin-left:20px;'>OneApp miracle</a>")
            .on("click", {team:"oneApp"}, miracleButtonHandler);
            let oneShopMiracleButton = $myJQuery("<a class='aui-button aui-button-primary aui-style miracleButton' title='Prefil form' href='#' style='margin-left:20px;'>OneShop miracle</a>")
            .on("click", {team:"oneShop"}, miracleButtonHandler);
            let debtMiracleButton = $myJQuery("<a class='aui-button aui-button-primary aui-style miracleButton' title='Prefil form' href='#' style='margin-left:20px;'>Tech debt labels</a>")
            .on("click", {labels:["S2S","OASK-technical_debt"]}, debtButtonHandler);
			dialogHeader.append(oneAppMiracleButton);
			dialogHeader.append(oneShopMiracleButton);
            dialogHeader.append(debtMiracleButton);

            $myJQuery(dialog).find("#create-issue-submit").on("click", verifyInputs);
		}
	}

	function miracleButtonHandler(event) {
        let dialog = $myJQuery(event.target).parents(".jira-dialog");
        let dialogContent = (dialog).find(".jira-dialog-content");

        makeMiracle(dialogContent, event.data.team);
	}

    function makeMiracle(dialogContent, team) {
		let prefilledFields = getPrefilledFieldsByIssueType(dialogContent, team);

		prefilledFields.forEach(function(prefilledField) {
            prefilledField.check(dialogContent);
			prefilledField.apply(dialogContent);

            //epic hack, fill hidden select with selected option
            if (prefilledField.readableName == "Epic") {
                let epicHiddenSelect = dialogContent.find("#customfield_10000");
                epicHiddenSelect.find("option").remove();
                if (prefilledField.value == "OASK - technical improvements") {
                    epicHiddenSelect.append("<option value='key:AT-137015' title='undefined' selected='selected'>OASK - technical improvements</option>");
                } else {
                    epicHiddenSelect.append("<option value='key:AT-119096' title='undefined' selected='selected'>OSSK - Technical improvements</option>");
                }

                dialogContent.find("#customfield_10000-field, #customfield_10000-single-select .drop-menu").one("click", function() {
                    dialogContent.find("#customfield_10000-field").val("");
                    dialogContent.find("#customfield_10000 option").remove();
                });
            }
		});
	}

    function debtButtonHandler(event) {
        let dialog = $myJQuery(event.target).parents(".jira-dialog");
        let dialogContent = (dialog).find(".jira-dialog-content");

        addLabels(dialogContent, event.data.labels);
	}

    function addLabels(dialogContent, labels) {
        let labelsTextArea = dialogContent.find("#labels-textarea");
        let labelsRepresentation = dialogContent.find("#labels-multi-select .representation ul.items");
        let labelsSelect = dialogContent.find("select#labels");

        labels.forEach(function(label){
            labelsRepresentation.append($myJQuery("<li class='item-row' role='option' aria-describedby='label-0'><button type='button' tabindex='-1' class='value-item'><span><span class='value-text'>"+label+"</span></span></button><em class='item-delete' aria-label=' ' title='' original-title=''></em></li>"));
            labelsSelect.append($myJQuery("<option value='"+label+"' title='"+label+"' selected='selected'>"+label+"</option>"));
        });

        dialogContent.find("#labels-multi-select").one("click", function() {
            dialogContent.find("#labels-multi-select .representation ul.items li").remove();
            dialogContent.find("select#labels option").remove();
        });
    }

    function verifyInputs(event) {
        let dialog = $myJQuery(event.target).parents(".jira-dialog");
        let dialogContent = (dialog).find(".jira-dialog-content");

        let prefilledFields = getPrefilledFieldsByIssueType(dialogContent);

        let issueType = dialogContent.find("#issuetype-field").val();
        if (issueType == "Sub-task") {
            if(!confirm("I am not sure that you realy need 'Sub-Task' type, maybe 'Technical sub-task' will be better. Continue?")) {
                return false;
            }
        }

        let fieldsFilled = true;
        prefilledFields.forEach(function(prefilledField) {
            if(!prefilledField.check(dialogContent)) {
                fieldsFilled = false;
                alert("Please fill value in " + prefilledField.readableName);
            }
		});

        return fieldsFilled;
    }

    function setConfluenceFields(event) {
        let tableRow = $myJQuery(event.target).parents("tr");
        let confluenceFields = {};
        confluenceFields.components = tableRow.find("td").eq(2).text();
        confluenceFields.summary = tableRow.find("td").eq(3).text();
        confluenceFields.estimate = parseInt(tableRow.find("td").eq(4).text()) + "h";
        confluenceFields.description = tableRow.find("td").eq(5).html();

        GM_setValue(CONFLUENCE_TO_JIRA_VAR, null);
        GM_setValue(CONFLUENCE_TO_JIRA_VAR, confluenceFields);

        //tableRow.addClass("miracle");
        //hack for chrome problem with iframe css in confluence
        tableRow.find("td").each( function( index, element ) {
            let td = $myJQuery(this);
            let oldBackground = td.css("background-color");
            td.css("background-color", "rgb(255, 0, 255)");
            setTimeout(function () {
                if (oldBackground == "rgb(255, 0, 255)") {
                    td.css("background-color", "");
                } else {
                    td.css("background-color", oldBackground);
                }
            }, 1000);
        });

        setTimeout(function () {
            tableRow.removeClass("miracle");
        }, 1000);
    }

    function confluenceTableInit(container) {
        let miracleTable = container.find(".confluenceTable th:contains('JIRA')").parents("table");
        if (miracleTable.length == 0) {
            miracleTable = container.find(".confluenceTable th:contains('Jira')").parents("table");
        }

        if (miracleTable.length > 0) {
            miracleTable.addClass("miracleTable");
            //in edit mode tinyMce editor manipulate DOM and therefore attach click on whole body and filter node by selector
            miracleTable.on("click", "td:first-child", setConfluenceFields);
            GM_addStyle(".miracleTable td:first-child:hover { background-color: magenta !important; cursor: alias;}");
            //GM_addStyle(".miracleTable tr.miracle td { background-color: magenta !important;}");
        }
    }

    function fillFieldsFromConfluence(confluenceFields) {
        if (confluenceFields == null) {
            return;
        }
        let dialogContent = $myJQuery(".jira-dialog-content");
        makeMiracle(dialogContent);

        dialogContent.find("#summary").val(confluenceFields.summary);
        dialogContent.find("#timetracking_originalestimate").val(confluenceFields.estimate);
        //#description
        tinymce.activeEditor.setContent(confluenceFields.description);

        let componentsTextArea = dialogContent.find("#components-textarea");
        let enteredComponents = confluenceFields.components.split(/(?:\r?\n|,)/);
        let halComponentsOptions = dialogContent.find("#components option")
                .filter(function(i, option) {
                    return $myJQuery(option).text().trim().startsWith("HAL - ")
                });

        componentsTextArea.val("");
        halComponentsOptions.removeAttr("selected");
        enteredComponents.forEach(function(component){
            component = component.trim();
            if (!component.startsWith("HAL - ")) {
                component = "HAL - " + component;
            }
            let foundComponent = halComponentsOptions
                .filter(function(i, option) { return $myJQuery(option).text().trim().startsWith(component)});
            if (foundComponent.length == 0) {
                alert("Component not found: " + component);
            } else {
                componentsTextArea.val(componentsTextArea.val() + " " + foundComponent.text().trim());
                foundComponent.attr("selected", "selected");
            }
        });
    }

    function getPrefilledFieldsByIssueType(dialogContent, team) {
        let newTaskTeamValue;
        let epic;

        if (team == "oneApp") {
            newTaskTeamValue = "17137";
            epic = "OASK - technical improvements";
        } else {
            newTaskTeamValue = "17161";
            epic = "OSSK - Technical improvements";
        }

        let descriptionTextArea = new ElementConfig("#description", null, "Description"); //empty
        let componentsTextArea = new ElementConfig("#components-textarea", null, "Components"); //empty

        let agileRadio = new ElementConfig("#customfield_10267-1", true, "Agile");
		let epicInput = new ElementConfig("#customfield_10000-field", epic, "Epic");
		let teamSelect = new ElementConfig("#customfield_10685", newTaskTeamValue, "Team"); //AF: SAT - OneApp - SK
		let privacyRelevanceSelect = new ElementConfig("#customfield_12605", "14042", "Privacy Relevance"); //NO

		let issueTypeToFieldMap = {
			//Technical US
            "10111": [descriptionTextArea, agileRadio, epicInput, teamSelect],
            //Task
            "10112": [descriptionTextArea, agileRadio, epicInput, teamSelect],
			//Sub-task
            "10113": [descriptionTextArea, agileRadio, teamSelect],
            //Technical sub-task
            "10228": [descriptionTextArea],
			//Story
            "10001": [descriptionTextArea, agileRadio, epicInput, teamSelect, privacyRelevanceSelect],
			//Bug
            "10110": [descriptionTextArea, agileRadio, epicInput, teamSelect]
		};

		let issueType = dialogContent.find("#issuetype").val();
		return issueTypeToFieldMap[issueType];
    }

	function ElementConfig(selector, value, readableName) {
		this.selector = selector;
		this.value = value;
		this.readableName = readableName;
		this.apply = function (parentElement) {
			if (this.value == null) {
                return;
            }
            let element = parentElement.find(this.selector);
			if (element.is(":checkbox, :radio")) {
				element.prop('checked', this.value);
			} else if (element.is("input, textarea")) {
				element.val(this.value);
			} else if (element.is("select")) {
				element.val(this.value);
				//scroll to selected
				setTimeout(function () {
					let optionTop = element.find("option:selected").offset().top;
					let selectTop = element.offset().top;
					element.scrollTop(element.scrollTop() + (optionTop - selectTop));
				}, 100);
			}
		};
        this.check = function (parentElement) {
			let element = parentElement.find(this.selector);
			if (element.parents(".qf-form.qf-configurable-form").length === 1 && element.parents(".qf-field-active").length === 0) {
				alert("Missing required field '" + this.readableName + "'");
                return false;
			}
			if (element.is(":checkbox, :radio")) {
				return parentElement.find("[name='"+element.attr("name")+"']:checked").length != 0;
			} else if (element.is("input, textarea")) {
				return element.val().length != 0;
			} else if (element.is("select")) {
				return element.val().length != 0;
			} else {
                return true;
            }
		}
	}
})();