Jira helper to preselect some fields when creating dialog is shown
Version vom
// ==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;
}
}
}
})();