Plugins Jira: quelques exemples de l'invention réussie du vélo



Chez Mail.ru Group, nous avons consacré beaucoup d'efforts au développement des produits Atlassian et, en particulier, de Jira. Grùce à nos efforts, les plug-ins MyGroovy, JsIncluder, My Calendar, My ToDo et d' autres ont vu le jour. Nous développons et utilisons activement tous ces plugins au sein de l'entreprise.

Nous recevons beaucoup de demandes de services connexes pour introduire de nouvelles fonctionnalités. Parfois, cela se traduit par de nouveaux plugins, mais le plus souvent, nous résolvons les tùches en utilisant des plugins existants, car la plupart des tùches quotidiennes sont facilement couvertes par eux.

Pour effectuer des excursions dans le bureau, il Ă©tait nĂ©cessaire de prĂ©voir la crĂ©ation de demandes avec vĂ©rification des excursions qui se croisent. Pour les testeurs - pour crĂ©er un mĂ©canisme de suivi des Ă©tapes du test avec la personne responsable de la mise en Ɠuvre. Le support technique souhaitait accĂ©der automatiquement Ă  la base de connaissances.

Aujourd'hui, je vais vous dire comment, en combinant des plug-ins, j'ai réussi à résoudre ces problÚmes.

Demande des "guides"


Outils:

  • Mon calendrier
  • Js includeer

Le problĂšme


Il y a beaucoup de «guides» dans le bureau du groupe Mail.ru qui organisent avec les invitĂ©s puis dĂ©finissent les tĂąches pour l'AXO. Parfois, il arrive que plusieurs excursions puissent avoir lieu en mĂȘme temps - puis plusieurs groupes se rendent au bureau en mĂȘme temps, ou un guide est refusĂ©, et il va nĂ©gocier avec les invitĂ©s.

Solution


  1. L'apparition dans la tùche des «slots» (date et heure d'un ensemble d'options gratuites) pour la sélection lors de la création d'une application pour une tournée Pour la journée - 3 slots. Par exemple:

    • 9 h Ă  10 h
    • 17h30-18h30
    • 20h00-21h00

    Si un emplacement a été sélectionné dans une autre tùche, vous ne pouvez pas le proposer pour une sélection dans un nouveau. Vous devez également avoir la possibilité de supprimer les emplacements de la sélection à la main (dans le cas, par exemple, lorsque les excursions au bureau sont en principe impossibles).
  2. L'apparition d'un calendrier, formĂ© de crĂ©neaux libres et occupĂ©s, qui peuvent ĂȘtre partagĂ©s sur des guides.

Implémentation


Étape 1 : ajoutez les champs requis Ă  l'Ă©cran de crĂ©ation de demande.

Pour ce faire, créez le champ "Date" du type Date et le champ "Heure tour" du type Radiobutton pour sélectionner une valeur parmi 3 options (9: 00-10: 00; 17: 30-18: 30; 20: 00-21: 00).

Étape 2 : crĂ©ez un calendrier.

Faire un nouveau calendrier. Nous le visons via JQL Ă  notre projet avec des excursions,
indiquez Event start le champ «Date» créé précédemment, et ajoutez également le champ «Excursion Time» créé précédemment à l'affichage.



Enregistrez le calendrier. Maintenant, nos visites peuvent ĂȘtre consultĂ©es sur le calendrier.



Étape 3 : nous limitons la crĂ©ation d'excursions et ajoutons une banniĂšre avec un lien vers le calendrier.

Pour ce faire, vous avez besoin de JS, qui suivra la modification dans le champ Date. Lorsque la date est sélectionnée, nous devons la remplacer dans la fonction jql et obtenir toutes les demandes pour cette date, puis nous verrons quelle heure est prise et cacherons ces options à l'écran pour qu'il soit impossible de choisir la durée.


Quand il n'y a pas de demande


Lorsqu'il y a 2 demandes Ă  9h et Ă  20h

(function($){ /* :  — customfield_19620   — customfield_52500   « »: 9:00-10:00 — 47611 17:30-18:30 — 47612 20:00-21:00 — 47613 */ /*       .       . */ $("input[name=customfield_19620]").on("click change", function(e) { var idOptions = []; var url = "/rest/api/latest/search"; /*  «»  ,    . */ if (!$("#customfield_19620").val()) { $('input:radio[name=customfield_52500]').closest('.group').hide(); } /*              jql ,        . */ else { var temp = $("#customfield_19620").val(); var arrDate = temp.split('.'); var result = "" + arrDate[2].trim() + "-" + arrDate[1].trim() + "-" + arrDate[0].trim(); $('input:radio[name=customfield_52500][value="-1"]').parent().remove(); $('input:radio[name=customfield_52500]').closest('.group').show(); $('input:radio[name=customfield_52500][value="47611"]').parent().show(); $('input:radio[name=customfield_52500][value="47612"]').parent().show(); $('input:radio[name=customfield_52500][value="47613"]').parent().show(); /*    jql. */ var params = { jql: "issuetype = Events and cf[52500] is not EMPTY and cf[19620] = 20" + result, fields: "customfield_52500" }; /*    JSON           . */ $.getJSON(url, params, function (data) { var issues = data.issues for (var i = 0; i < issues.length; i++) { idOptions.push(issues[i].fields.customfield_52500.id) } for (var k = 0; k < idOptions.length; k++) { $('input:radio[name=customfield_52500][value=' + idOptions[k] + ']').parent().hide(); } }); } }); /*      . */ $('div.field-group:has(#customfield_19620)').last().before(` <div id="bannerWithInfo" class="aui-message info"> <p class="title">     </p> <p>   </p> <p>      </p> <p>         </p> <p><a href='https://jira.ru/secure/MailRuCalendar.jspa#calendars=492' target="_blank"> </a></p> </div> `); })(AJS.$); 

Demande des testeurs


Outil:

  • Mon groovy

Le problĂšme


Dans la demande, vous devez configurer l'affichage des étapes de test avec l'indication de l'employé responsable de la tùche. Il faut voir que l'étape n'est pas encore terminée ou que l'étape est terminée (et qui l'a menée).

Solution


Configurez le champ de type de champ scripté pour afficher les étapes du test et associez-le au workflow, enregistrez-le dans la transition responsable de l'étape de l'auteur.

Implémentation


  1. Créez un champ «Progress» de type champ scripté.
  2. Créez des champs de type UserPicker qui correspondent aux étapes du test.

    Par exemple, dĂ©finissez les Ă©tapes suivantes et crĂ©ez des champs UserPicker avec les mĂȘmes noms:

    • Informations de base collectĂ©es
    • LocalisĂ©
    • Journaux collectĂ©s
    • JouĂ©
    • Responsable trouvĂ©

  3. Nous mettons en place le workflow pour que des personnes responsables remplissent les transitions.

    Par exemple, la transition «Localized» écrit currentUser dans le champ «Localized» UserPicker.
  4. Personnalisez l'affichage à l'aide d'un champ scripté.

Remplissez le bloc groovy:

 import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.config.properties.APKeys baseUrl = ComponentAccessor.getApplicationProperties().getString(APKeys.JIRA_BASEURL) colorApprove = "#D2F0C2" colorNotApprove = "#FDACAC" return getHTMLApproval() def getHTMLApproval(){ def approval = getApproval() def html = "<table class='aui'>" approval.each{k,v-> html += """<tr> <td ${v?"bgcolor='${colorApprove}'":"bgcolor='${colorNotApprove}'"}>${k}</td> <td ${v?"bgcolor='${colorApprove}'":"bgcolor='${colorNotApprove}'"}>${v?displayUser(v):""}</td> </tr>""" } html += "</table>" return html } def displayUser(user){ "<a href=${baseUrl}/secure/ViewProfile.jspa?name=${user.name}>${user.displayName}</a>" } def getApproval(){ def approval = [:] as LinkedHashMap if (issue.getIssueTypeId() == '10001'){ //  -  approval.put("  ", getCfValue(54407)) approval.put(" ", getCfValue(54409)) approval.put("", getCfValue(54410)) approval.put(" ", getCfValue(54411)) approval.put("", getCfValue(54408)) } return approval } def getCfValue(id){ ComponentAccessor.customFieldManager.getCustomFieldObject(id).getValue(issue) } 

Dans le bloc de vélocité, imprimez $ value. On obtient le résultat suivant:



Demande d'assistance


Outils:

  • Js includeer
  • Mon groovy

Le problĂšme


Le support technique possĂšde sa propre base de connaissances sur Confluence. Vous devez pouvoir afficher des articles de la base de connaissances liĂ©s aux problĂšmes dans une requĂȘte Jira. Nous avons Ă©galement besoin d'un mĂ©canisme pour maintenir la base de donnĂ©es Ă  jour - si l'article n'Ă©tait pas utile, vous devez demander Ă  un rĂ©dacteur technique de Jira d'Ă©crire l'article actuel. Lors de la fermeture d'une demande, seuls les articles liĂ©s Ă  la demande doivent rester. Les liens ne peuvent ĂȘtre visibles que par le support technique.

Solution


Lorsque vous choisissez un type d'accĂšs spĂ©cifique dans Jira (champ de type en cascade), la requĂȘte doit afficher les articles avec Confluence qui lui correspondent dans un champ sĂ©parĂ© avec le balisage wiki.

S'il est utilisé avec succÚs, l'article est sélectionné comme pertinent à l'aide de la case à cocher.

Lors de la rĂ©solution d'un problĂšme, s'il n'est pas dĂ©crit dans l'article joint, une tĂąche doit ĂȘtre créée dans Jira avec le type «Documentation» associĂ© Ă  la demande en cours.

Implémentation


Étape 1 : prĂ©paration

  1. Créer un champ de texte (multiligne) avec un balisage wiki - Liens.
  2. Créez un champ de type Select List (en cascade) - «Call Type».

    Par exemple, nous utilisons les valeurs suivantes:

    • COMPTE
    • MatĂ©riel informatique
  3. Nous prĂ©parerons des Ă©tiquettes pour les articles qui relieront les articles sur Confluence aux requĂȘtes dans Jira:

    • Modifier les appartenances au groupe AD - officeit_jira_ad_group_addresses_ad
    • Inscription / dĂ©sinscription Ă  une newsletter - officeit_jira_subscription_subscription_subscription
    • Accorder l'accĂšs au dossier - officeit_jira_sharing_access_to_folder
    • RĂ©initialiser le mot de passe du domaine KM - officeit_jira_reset_password_of_domain_uz
    • RĂ©initialiser le mot de passe de messagerie - officeit_jira_reset_password_mail_post
    • DĂ©livrance de matĂ©riel temporaire - officeit_jira_ DĂ©livrance de matĂ©riel temporaire
    • Émission de nouveaux Ă©quipements - officeit_jira_new__new_technique
    • Remplacement du disque dur et installation du systĂšme Ă  partir de zĂ©ro - officeit_jira_replace_hard_drive_and_install_system_s_ zero
    • Remplacement d'un disque dur par le transfert d'informations - officeit_jira_replacing_hard_drive_with_data transfer_information
    • Remplacement d'Ă©quipement dĂ©fectueux / obsolĂšte - officeit_jira_replacing_ faulty_ obsolete_ equipment

    Ensuite, vous devez créer des articles sur Confluence, mettre des étiquettes pour eux.
  4. Préparation du workflow.

    Le type d'appel sera rempli lors de la création.

    Les liens sont ajoutés à un écran séparé et placés sur la transition en fermeture (dans l'exemple, la transition est appelée «Vérifier les liens réels»), nous nous souvenons de l'ID de transition (nécessaire à l'avenir pour configurer js).

Étape 2 : Post-fonction MyGroovy (ajouter des articles à la demande)

 /* :   — customfield_40001 Links — customfield_50001 */ /*  ,      . */ def usr = "bot" def pas = "qwerty" def url = "https://confluence.ru" def browse = "/pages/viewpage.action?pageId=" /*   */ def updateCustomFieldValue(issue, Long customFieldId, newValue) { def customField = ComponentAccessor.customFieldManager.getCustomFieldObject(customFieldId) customField.updateValue(null, issue, new ModifiedValue(customField.getValue(issue), newValue), new DefaultIssueChangeHolder()) return issue } def getCustomFieldObject(Long fieldId) { ComponentAccessor.customFieldManager.getCustomFieldObject(fieldId) } def parseText(text) { def jsonSlurper = new JsonSlurper() return jsonSlurper.parseText(text) } def getCustomFieldValue(issue, Long fieldId) { issue.getCustomFieldValue(ComponentAccessor.customFieldManager.getCustomFieldObject(fieldId)) } /*  ,      . */ def getLabelFromMap(String main, String sub){ def mapLabels = [ "ACCOUNT": [ "    AD" :["officeit_jira_____ad"], "/  " :["officeit_jira____"], "   " :["officeit_jira____"], "    " :["officeit_jira_____"], "   " :["officeit_jira____"] ], "HARDWARE": [ "  " :["officeit_jira___"], "  " :["officeit_jira___"], "       ":["officeit_jira________"], "     ":["officeit_jira______"], " / ":["officeit_jira____"] ] ] def labels = mapLabels[main][sub] def result = "" if(!labels){ return "" } for (def i=0;i<labels.size;i++){ if(i<labels.size-1){ result += "\"" +labels[i]+ "\"," }else{ result += "\"" +labels[i]+ "\"" } } result = URLEncoder.encode(result, "utf-8") return result } /*    —  . */ def wikiLinkFieldId = 50001L def requestTypeFieldValue = getCustomFieldValue(issue, 40001) if(!requestTypeFieldValue){ return "required field is empty" } def mainType = requestTypeFieldValue.getAt(null).toString() def subType = requestTypeFieldValue.getAt('1').toString() /*     ,       : [TEST    1 (    AD)|https://confluence.ru/pages/viewpage.action?pageId=500001]. */ String labels = getLabelFromMap(mainType,subType) if(labels==""){ return "no avalible position on LabelMap" } def api = "/rest/api/content/search?cql=label%20in(${labels})" def URL = (url+api) def wikiString = "" def resp = "curl -u ${usr}:${pas} -X GET ${URL}".execute().text def result = parseText(resp) def ids = result.results.id def title = result.results.title for (def i=0;i<ids.size;i++){ wikiString += "[${title[i]}|${url+browse+ids[i]}]\n" } updateCustomFieldValue(issue,wikiLinkFieldId,wikiString) return "Done" 



Étape 3 : script JS

 /* :  — Check actual Links id  — 10 Links — customfield_50001 */ (function($){ /*   ,    ,                . */ var buttonNewArticle = '  '; var buttonDeleteUnchecked = ' '; var buttonNewArticleTitle = '      '; var buttonDeleteUncheckedTitle = '    .'; var avalibleTransitions = [10]; var currentTransition = parseInt(AJS.$('.hidden input[name^="action"]').val()); if(avalibleTransitions.indexOf(currentTransition)==-1){ console.log('Error: transition ' + currentTransition + ' is not avalible'); return; } var customFieldId = 50001; var labelTxt = '  '; var idname = 'cblist'; var checkboxCounter = 'cbsq'; var text = '<div class="field-group"><label for="'+idname+'">' + labelTxt +'</label><div id="'+idname+'"></div></div>' AJS.$('.field-group label[for^="customfield_'+customFieldId+'"]').parent().hide(); AJS.$('.field-group label[for^="comment"]').parent().hide(); $('.jira-dialog-content div.form-body').prepend(text); /*    : */ /* renameButtonNeedNewArticle  renameButtonDeleteUnchecked —   « »            addCheckbox —     . */ function arrayToString(arrays) { return arrays.join('\n'); } function renameButtonNeedNewArticle() { $('#issue-workflow-transition-submit').val(buttonNewArticle); $('#issue-workflow-transition-submit').attr("title",buttonNewArticleTitle); } function renameButtonDeleteUnchecked() { $('#issue-workflow-transition-submit').val(buttonDeleteUnchecked); $('#issue-workflow-transition-submit').attr("title",buttonDeleteUncheckedTitle); } function addCheckbox(array) { var value = array.join('|'); var name = array[0].replace('[',''); var link = array[1].replace(']',''); var container = $('#'+idname); var inputs = container.find('input'); var id = inputs.length+1; $('<input />', { type: 'checkbox', id: checkboxCounter+id, value: value }).appendTo(container); $('<label />', { for: checkboxCounter+id, text: ' ' }).appendTo(container); $('<a />', { href: link, text: name,target: "_blank" }).appendTo(container); $('<br>').appendTo(container); } /*       ,   : */ renameButtonNeedNewArticle(); $(document).ready(function() { var val = AJS.$('#customfield_'+customFieldId+'').val(); AJS.$('#customfield_'+customFieldId+'').val(''); if(val==""){return;} var i = val.split('\n'); i.forEach(function( index ) { if(index == ""){return;} var link = index.split('|'); addCheckbox(link); }); }); /*          Links. */ $('#'+idname+' input[type="checkbox"]').change(function() { var prevalue = []; AJS.$('#'+idname+' input:checkbox:checked').each(function(){ prevalue.push(this.value); }); AJS.$('#customfield_'+customFieldId+'').val(arrayToString(prevalue)); if(prevalue.length<1){ renameButtonNeedNewArticle(); }else{ renameButtonDeleteUnchecked(); } }); })(AJS.$); 

VoilĂ  Ă  quoi ressemble notre transition vers le traitement JS.



Il s'agit de la transition aprĂšs le traitement.



Et donc, si un ou plusieurs articles sont sélectionnés.



Une fois la transition terminée, le champ Liens sera remplacé par la nouvelle valeur.

Étape 4 : Post-fonction MyGroovy (crĂ©er une demande pour un nouvel article)

Lors de la transition Vérifier les liens réels, nous écrivons un script qui crée une demande de type «Documentation» s'il n'y a pas de valeurs dans le champ Liens.

En conclusion


Ces solutions n'auraient pas vu le jour sans la participation active de collĂšgues - principalement ceux qui utilisent activement des outils prĂȘts Ă  l'emploi ou qui sont confrontĂ©s Ă  des tĂąches qui doivent ĂȘtre automatisĂ©es dans leur travail. Il s'avĂšre souvent qu'une tĂąche intĂ©ressante reprĂ©sente dĂ©jĂ  la moitiĂ© de la solution: il vous suffit alors de choisir l'outil qui rĂ©pond le plus efficacement, simplement et facilement (pour l'utilisateur final) Ă  vos besoins. Maintenant, peut-ĂȘtre, vous avez des questions et des suggestions qui pourraient rendre les plugins prĂ©sentĂ©s encore meilleurs - Ă©crivez dans les commentaires.

Source: https://habr.com/ru/post/fr476446/


All Articles