
Wir von der Mail.ru Group haben große Anstrengungen unternommen, um Atlassian-Produkte und insbesondere Jira zu entwickeln. Dank unserer Bemühungen haben die Plug-ins MyGroovy, JsIncluder, My Calendar, My ToDo und
andere das Licht erblickt. Wir entwickeln und nutzen alle diese Plugins aktiv im Unternehmen.
Wir erhalten viele Anfragen von verwandten Abteilungen, neue Funktionen einzuführen. Manchmal führt dies zu neuen Plugins, aber häufiger lösen wir Aufgaben mit vorhandenen Plugins, da die meisten alltäglichen Aufgaben leicht von ihnen abgedeckt werden.
Um Exkursionen im Büro durchführen zu können, war es erforderlich, die Erstellung von Anfragen mit Verifizierung von sich überschneidenden Exkursionen vorzusehen. Für Tester - einen Mechanismus zur Überwachung der Testphasen mit der für die Implementierung verantwortlichen Person einrichten. Der technische Support wollte automatisch auf die Wissensdatenbank zugreifen.
Heute werde ich Ihnen erzählen, wie ich es durch die Kombination von Plugins geschafft habe, diese Probleme zu lösen.
Anfrage von den "Guides"
Werkzeuge:
Das problem
Im Mail.ru-Gruppenbüro gibt es viele „Guides“, die sich mit den Gästen verabreden und dann Aufgaben für die AXO festlegen. Manchmal kann es vorkommen, dass mehrere Exkursionen gleichzeitig stattfinden - dann gehen mehrere Gruppen gleichzeitig ins Büro, oder ein Führer wird abgelehnt, und er verhandelt mit den Gästen.
Lösung
- Das Erscheinen in der Aufgabe "Slots" (Datum und Uhrzeit aus einer Reihe von freien Optionen) zur Auswahl beim Erstellen einer Anwendung für eine Tour für den Tag - 3 Slots. Zum Beispiel:
- 9 - 10 Uhr
- 17: 30-18: 30
- 20: 00-21: 00 Uhr
Wenn ein Slot in einer anderen Aufgabe ausgewählt wurde, können Sie ihn nicht zur Auswahl in einer neuen Aufgabe anbieten. Sie müssen auch die Möglichkeit haben, die Slots von Hand aus der Auswahl zu entfernen (z. B. wenn Ausflüge im Büro grundsätzlich nicht möglich sind). - Das Erscheinungsbild eines Kalenders, der aus freien und belegten Zeitnischen besteht und in Hilfslinien geteilt werden kann.
Implementierung
Schritt 1 : Fügen Sie die erforderlichen Felder zum Anforderungserstellungsbildschirm hinzu.
Erstellen Sie dazu das Feld "Datum" des Typs "Datum" und das Feld "Tourzeit" des Typs "Radiobutton", um einen Wert aus 3 Optionen auszuwählen (9: 00-10: 00; 17: 30-18: 30; 20: 00-21: 00).
Schritt 2 : Kalender erstellen.
Einen neuen Kalender erstellen. Wir zielen es durch JQL zu unserem Projekt mit Exkursionen,
Geben Sie Ereignisstart im Feld "Datum" an, das zuvor erstellt wurde, und fügen Sie außerdem das Feld "Exkursionszeit" hinzu, das zuvor erstellt wurde.

Speichern Sie den Kalender. Jetzt können unsere Touren im Kalender eingesehen werden.
Schritt 3 : Wir begrenzen die Erstellung von Ausflügen und fügen ein Banner mit einem Link zum Kalender hinzu.
Um dies zu erreichen, benötigen Sie JS, das die Änderung im Feld Datum verfolgt. Wenn das Datum ausgewählt ist, müssen wir es in der jql-Funktion ersetzen und alle Anfragen für dieses Datum erhalten. Dann werden wir herausfinden, welche Zeit benötigt wird, und diese Optionen auf dem Bildschirm ausblenden, um die Auswahl der benötigten Zeit unmöglich zu machen.
Wenn es keine Anfragen gibt
Bei 2 Anfragen um 9 Uhr und um 20 Uhr(function($){ $("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(); } 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(); var params = { jql: "issuetype = Events and cf[52500] is not EMPTY and cf[19620] = 20" + result, fields: "customfield_52500" }; $.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.$);
Anfrage von Testern
Werkzeug:
Das problem
In der Anforderung müssen Sie die Anzeige der Testphasen mit der Angabe des für die Aufgabe verantwortlichen Mitarbeiters konfigurieren. Es sollte sich herausstellen, dass die Phase noch nicht abgeschlossen ist oder dass die Phase abgeschlossen ist (und wer sie durchgeführt hat).
Lösung
Konfigurieren Sie das Feld für den Feldtyp "Skripted", um die Testphasen anzuzeigen und dem Workflow zuzuordnen. Zeichnen Sie den Übergang auf, der für die Phase des Autors verantwortlich ist.
Implementierung
- Erstellen Sie ein Feld "Fortschritt" vom Typ Skriptfeld.
- Erstellen Sie Felder vom Typ UserPicker, die den Testphasen entsprechen.
Definieren Sie beispielsweise die folgenden Schritte und erstellen Sie UserPicker-Felder mit demselben Namen:
- Grundlegende Informationen gesammelt
- Lokalisiert
- Protokolle gesammelt
- Gespielt
- Verantwortlich gefunden
- Wir haben den Workflow so eingerichtet, dass verantwortliche Personen die Übergänge ausfüllen.
Beispielsweise schreibt der Übergang "Lokalisiert" currentUser in das Feld "Lokalisierter UserPicker". - Passen Sie die Anzeige mithilfe eines Skriptfelds an.
Fülle den groovigen Block:
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'){
Geben Sie im Geschwindigkeitsblock $ value aus. Wir erhalten folgendes Ergebnis:

Support-Anfrage
Werkzeuge:
- Js Includer
- Mein grooviger
Das problem
Der technische Support verfügt über eine eigene Wissensbasis zu Confluence. Benötigen Sie die Möglichkeit, problembezogene Knowledge Base-Artikel in einer Jira-Abfrage anzuzeigen. Wir brauchen auch einen Mechanismus, um die Datenbank auf dem neuesten Stand zu halten. Wenn der Artikel nicht nützlich war, müssen Sie einen technischen Redakteur in Jira bitten, den aktuellen Artikel zu schreiben. Beim Schließen einer Anfrage sollten nur Artikel übrig bleiben, die sich auf die Anfrage beziehen. Links können nur für den technischen Support angezeigt werden.
Lösung
Bei der Auswahl einer bestimmten Zugriffsart in Jira (Kaskadierungsfeld) sollte die Abfrage Artikel mit Confluence anzeigen, die dieser Zugriffsart in einem separaten Feld mit Wiki-Markup entsprechen.
Bei erfolgreicher Verwendung wird der Artikel mit dem Häkchen als relevant markiert.
Wenn ein Problem behoben wird, sollte in Jira eine Aufgabe mit dem Typ „Documentation“ (Dokumentation) erstellt werden, die der aktuellen Anforderung zugeordnet ist, sofern dies nicht im angehängten Artikel beschrieben ist.
Implementierung
Schritt 1 : Vorbereitung
- Erstellen Sie ein Textfeld (mehrzeilig) mit Wiki-Markup - Links.
- Erstellen Sie ein Feld vom Typ Auswahlliste (kaskadierend) - „Anruftyp“.
Zum Beispiel verwenden wir die folgenden Werte:
- Wir bereiten Labels für Artikel vor, die Artikel über Confluence mit Abfragen in Jira verbinden:
- AD-Gruppenmitgliedschaften ändern - officeit_jira_ad_group_addresses_ad
- Abonnieren / Abbestellen eines Newsletters - officeit_jira_subscription_subscription_subscription
- Zugriff auf den Ordner gewähren - officeit_jira_sharing_access_to_folder
- Passwort aus Domain KM zurücksetzen - officeit_jira_reset_password_of_domain_uz
- Mail-Passwort zurücksetzen - officeit_jira_reset_password_mail_post
- Ausstellung von Ausrüstungsgegenständen - officeit_jira_ Ausstellung von Ausrüstungsgegenständen
- Ausgabe neuer Ausrüstung - officeit_jira_new__new_technique
- Ersetzen Sie die Festplatte und installieren Sie das System von Grund auf neu - officeit_jira_replace_hard_drive_and_install_system_s_ zero
- Ersetzen einer Festplatte durch Übertragen von Informationen - officeit_jira_replacing_hard_drive_with_data transfer_information
- Ersetzen von defekten / veralteten Geräten - officeit_jira_replacing_ faulty_ obsolete_ equipment
Als Nächstes müssen Sie Artikel in Confluence erstellen und deren Bezeichnungen hinzufügen.
- Workflow vorbereiten.
Die Art der Beschwerde wird beim Erstellen ausgefüllt.
Links werden zu einem separaten Bildschirm hinzugefügt und in der Nähe der Transition platziert (im Beispiel heißt die Transition „Check actual Links“).
Schritt 2 : MyGroovy Post-Funktion (Artikel zur Anfrage hinzufügen)
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() 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"
Schritt 3 : JS-Skript
(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); 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); }); }); $('#'+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.$);
So sieht unser Übergang vor der JS-Verarbeitung aus.

Dies ist der Übergang nach der Verarbeitung.

Und so, wenn ein oder mehrere Artikel ausgewählt sind.

Nach Abschluss des Übergangs wird das Feld Links mit dem neuen Wert überschrieben.
Schritt 4 : MyGroovy Post-Funktion (Anfrage für einen neuen Artikel erstellen)
Beim Übergang "Aktuelle Links prüfen" schreiben wir ein Skript, das eine Anforderung vom Typ "Dokumentation" erstellt, wenn das Feld "Links" keine Werte enthält.
Abschließend
Diese Lösungen wären nicht ohne die aktive Beteiligung von Kollegen entstanden - vor allem von jenen, die fertige Tools aktiv einsetzen oder vor Aufgaben stehen, die bei ihrer Arbeit automatisiert werden müssen. Oft stellt sich heraus, dass eine interessante Aufgabe bereits die halbe Miete ist: Dann müssen Sie nur noch das Werkzeug auswählen, das Ihre Anforderungen am effizientesten, einfachsten und einfachsten (für den Endbenutzer) erfüllt. Vielleicht haben Sie jetzt Fragen und Anregungen, die die vorgestellten Plugins noch besser machen könnten - schreiben Sie in die Kommentare.