Jira Plugins: Beberapa Contoh Penemuan Sepeda yang Berhasil



Kami di Mail.ru Group melakukan banyak upaya dalam pengembangan produk Atlassian dan, khususnya, Jira. Berkat upaya kami, plugin MyGroovy, JsIncluder, My Calendar, My ToDo dan lainnya melihat cahaya. Kami mengembangkan dan secara aktif menggunakan semua plugin ini di dalam perusahaan.

Kami menerima banyak permintaan dari departemen terkait untuk memperkenalkan fitur-fitur baru. Terkadang ini diterjemahkan ke dalam plugin baru, tetapi lebih sering kita menyelesaikan tugas menggunakan plugin yang ada, karena sebagian besar tugas sehari-hari mudah dicakup oleh mereka.

Untuk melakukan kunjungan di kantor, perlu untuk membuat permintaan dengan memeriksa kunjungan yang berpotongan. Untuk penguji - untuk membuat mekanisme untuk memantau tahapan pengujian dengan orang yang bertanggung jawab untuk implementasi. Dukungan teknis ingin mengakses basis pengetahuan secara otomatis.

Hari ini saya akan memberi tahu Anda bagaimana, dengan menggabungkan plug-in, saya berhasil menyelesaikan masalah ini.

Permintaan dari "pemandu"


Alat:

  • Kalender saya
  • Termasuk

Masalah


Ada banyak "panduan" di kantor Grup Mail.ru yang mengatur dengan para tamu dan kemudian mengatur tugas untuk AXO. Kadang-kadang terjadi bahwa beberapa kunjungan dapat dilakukan pada waktu yang bersamaan - kemudian beberapa kelompok pergi ke kantor pada saat yang sama, atau satu pemandu ditolak, dan ia pergi untuk bernegosiasi dengan para tamu.

Solusi


  1. Penampilan dalam tugas "slot" (tanggal dan waktu dari serangkaian pilihan gratis) untuk dipilih saat membuat aplikasi untuk tur Untuk hari itu - 3 slot. Sebagai contoh:

    • 9 pagi - 10 pagi
    • 17: 30-18: 30
    • 20: 00-21: 00

    Jika sebuah slot dipilih dalam tugas lain, Anda tidak dapat menawarkannya untuk dipilih di yang baru. Anda juga perlu kemampuan untuk menghapus slot dari pemilihan dengan tangan (dalam kasus ini, misalnya, ketika kunjungan di kantor tidak mungkin pada prinsipnya).
  2. Tampilan kalender, terbentuk dari slot yang bebas dan sibuk, yang dapat dibagikan di panduan.

Implementasi


Langkah 1 : tambahkan bidang yang diperlukan ke layar pembuatan permintaan.

Untuk melakukan ini, buat bidang "Tanggal" dari jenis Tanggal dan "Waktu tur" dari tipe Radiobutton untuk memilih satu nilai dari 3 opsi (9: 00-10: 00; 17: 30-18: 30; 20: 00-21: 00).

Langkah 2 : buat kalender.

Membuat kalender baru. Kami mengarahkannya melalui JQL ke proyek kami dengan kunjungan,
menunjukkan Acara memulai bidang "Tanggal" yang dibuat sebelumnya, dan juga menambahkan bidang "Waktu Ekskursi" yang dibuat sebelumnya ke tampilan.



Simpan kalender. Sekarang wisata kami dapat dilihat di kalender.



Langkah 3 : kami membatasi pembuatan kunjungan dan menambahkan spanduk dengan tautan ke kalender.

Untuk mencapai ini, Anda perlu JS, yang akan melacak perubahan di bidang Tanggal. Ketika tanggal dipilih, kita harus menggantinya dalam fungsi jql dan mendapatkan semua permintaan untuk tanggal ini, maka kita akan mengetahui waktu apa yang diambil dan menyembunyikan opsi-opsi ini di layar untuk membuatnya mustahil untuk memilih waktu yang diambil.


Ketika tidak ada permintaan


Ketika ada 2 permintaan pada jam 9 pagi dan jam 20 malam

(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.$); 

Permintaan dari penguji


Alat:

  • Asyik saya

Masalah


Dalam permintaan, Anda harus mengonfigurasi tampilan tahapan pengujian dengan indikasi karyawan yang bertanggung jawab atas tugas tersebut. Harus dilihat bahwa tahap belum selesai, atau tahap selesai (dan siapa yang melakukannya).

Solusi


Konfigurasikan bidang tipe bidang yang dituliskan untuk menampilkan tahapan pengujian dan kaitkan dengan alur kerja, catat dalam transisi yang bertanggung jawab atas tahapan penulis.

Implementasi


  1. Buat bidang "Kemajuan" dari bidang jenis skrip.
  2. Buat bidang tipe UserPicker yang sesuai dengan tahapan pengujian.

    Misalnya, tentukan langkah-langkah berikut dan buat bidang UserPicker dengan nama yang sama:

    • Informasi dasar dikumpulkan
    • Dilokalkan
    • Log dikumpulkan
    • Dimainkan
    • Bertanggung jawab ditemukan

  3. Kami mengatur alur kerja sehingga orang yang bertanggung jawab mengisi transisi.

    Sebagai contoh, transisi "Lokal" menulis CurrentUser ke bidang UserPicker "Localized".
  4. Kustomisasi tampilan menggunakan bidang skrip.

Isi blok asyik:

 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) } 

Di blok kecepatan, cetak $ value. Kami mendapatkan hasil sebagai berikut:



Permintaan Dukungan


Alat:

  • Termasuk
  • Asyik saya

Masalah


Dukungan teknis memiliki basis pengetahuan sendiri tentang Confluence. Perlu kemampuan untuk menampilkan artikel basis pengetahuan terkait masalah dalam permintaan Jira. Kami juga memerlukan mekanisme untuk memperbarui basis data - jika artikel tidak berguna, Anda perlu meminta penulis teknis di Jira untuk menulis artikel saat ini. Saat menutup permintaan, hanya artikel yang terkait dengan permintaan yang akan tetap ada. Tautan hanya dapat dilihat oleh dukungan teknis.

Solusi


Saat memilih jenis akses tertentu di Jira (bidang tipe kaskade), kueri harus menampilkan artikel dengan Confluence yang sesuai dengan itu di bidang terpisah dengan marka wiki.

Jika berhasil digunakan, artikel dipilih sebagai relevan menggunakan tanda centang.

Saat memecahkan masalah, jika tidak dijelaskan dalam artikel terlampir, tugas harus dibuat di Jira dengan jenis "Dokumentasi" yang terkait dengan permintaan saat ini.

Implementasi


Langkah 1 : persiapan

  1. Buat Bidang Teks (multi-line) dengan marka wiki - Tautan.
  2. Buat bidang tipe Pilih Daftar (berjenjang) - β€œJenis Panggilan”.

    Misalnya, kami menggunakan nilai-nilai berikut:

    • AKUN
    • Perangkat keras
  3. Kami akan menyiapkan label untuk artikel yang akan menghubungkan artikel di Confluence dengan pertanyaan di Jira:

    • Ubah keanggotaan grup AD - officeit_jira_ad_group_addresses_ad
    • Berlangganan / berhenti berlangganan dari buletin - officeit_jira_subscription_subscription_of_subscription
    • Memberikan akses ke folder - officeit_jira_sharing_access_to_folder
    • Setel ulang kata sandi dari domain KM - officeit_jira_reset_password_of_domain_uz
    • Setel ulang kata sandi email - officeit_jira_reset_password_mail_post
    • Penerbitan peralatan sementara - officeit_jira_ penerbitan peralatan sementara
    • Penerbitan peralatan baru - officeit_jira_new__new_technique
    • Mengganti hard drive dan menginstal sistem dari awal - officeit_jira_replace_hard_drive_and_install_system_s_ zero
    • Mengganti hard drive dengan mentransfer informasi - officeit_jira_replacing_hard_drive_with_data transfer_information
    • Mengganti peralatan yang rusak / usang - peralatan officeit_jira_replacing_ faulty_ obsolete_

    Selanjutnya, Anda perlu membuat artikel tentang Confluence, letakkan label untuk mereka.
  4. Mempersiapkan alur kerja.

    Jenis banding akan diisi saat membuat.

    Tautan ditambahkan ke layar terpisah dan ditempatkan pada transisi secara tertutup (dalam contoh, transisi disebut "Periksa Tautan aktual"), kami mengingat id transisi (diperlukan di masa depan untuk mengkonfigurasi js).

Langkah 2 : Fungsi pasca MyGroovy (tambahkan artikel ke permintaan)

 /* :   β€” 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" 



Langkah 3 : skrip 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.$); 

Inilah yang tampak seperti transisi kita sebelum pemrosesan JS.



Ini adalah transisi setelah diproses.



Jadi, jika satu atau lebih artikel dipilih.



Setelah transisi selesai, bidang Tautan akan ditimpa dengan nilai baru.

Langkah 4 : Fungsi pasca MyGroovy (buat permintaan untuk artikel baru)

Pada Periksa transisi Tautan yang sebenarnya, kami menulis skrip yang membuat permintaan jenis "Dokumentasi" jika tidak ada nilai di bidang Tautan.

Kesimpulannya


Solusi ini tidak akan muncul tanpa partisipasi aktif dari kolega - terutama mereka yang secara aktif menggunakan alat yang sudah jadi atau yang dihadapkan dengan tugas yang perlu diotomatisasi dalam pekerjaan mereka. Sering kali ternyata tugas yang menarik sudah setengah dari solusi: maka Anda hanya perlu memilih alat yang paling efisien, sederhana dan mudah (untuk pengguna akhir) memenuhi kebutuhan Anda. Sekarang, mungkin, Anda memiliki pertanyaan dan saran yang dapat membuat plugin yang disajikan lebih baik - tulis di komentar.

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


All Articles