
Nosotros en Mail.ru Group pusimos mucho esfuerzo en el desarrollo de productos Atlassian y, en particular, Jira. Gracias a nuestros esfuerzos, los complementos MyGroovy, JsIncluder, My Calendar, My ToDo y
otros vieron la luz. Desarrollamos y utilizamos activamente todos estos complementos dentro de la empresa.
Recibimos muchas solicitudes de departamentos relacionados para presentar nuevas funciones. A veces esto se traduce en nuevos complementos, pero con mayor frecuencia resolvemos tareas utilizando los complementos existentes, ya que la mayoría de las tareas cotidianas se cubren fácilmente.
Para realizar excursiones en la oficina, era necesario prever la creación de solicitudes con la verificación de las excursiones que se cruzan. Para los probadores: para crear un mecanismo para monitorear las etapas de prueba con la persona responsable de la implementación. El soporte técnico quería acceder automáticamente a la base de conocimiento.
Hoy les diré cómo, combinando complementos, logré resolver estos problemas.
Solicitud de los "guías"
Herramientas:
El problema
En la oficina de Mail.ru Group hay muchas "guías" que están de acuerdo con los invitados y luego establecen tareas para el AXO. A veces sucede que pueden realizarse varias excursiones al mismo tiempo, luego varios grupos van a la oficina al mismo tiempo, o se rechaza un guía, y él va a negociar con los invitados.
Solución
- La aparición en la tarea de "espacios" (fecha y hora de un conjunto de opciones gratuitas) para la selección al crear una aplicación para un recorrido por el día - 3 espacios. Por ejemplo:
- 9 a.m. a 10 a.m.
- 17: 30-18: 30
- 20: 00-21: 00
Si se seleccionó un espacio en otra tarea, no puede ofrecerlo para su selección en una nueva. También necesita la capacidad de eliminar las ranuras de la selección a mano (en el caso, por ejemplo, cuando las excursiones en la oficina son imposibles en principio). - La aparición de un calendario, formado por espacios libres y ocupados, que se pueden compartir en guías.
Implementación
Paso 1 : agregue los campos obligatorios a la pantalla de creación de solicitudes.
Para hacer esto, cree el campo "Fecha" del tipo Fecha y el campo "Tiempo de recorrido" del tipo Radiobutton para seleccionar un valor entre 3 opciones (9: 00-10: 00; 17: 30-18: 30; 20: 00-21: 00).
Paso 2 : crea un calendario.
Haciendo un nuevo calendario. Lo apuntamos a través de JQL a nuestro proyecto con excursiones,
indique Evento, inicie el campo "Fecha" creado anteriormente, y también agregue el campo "Tiempo de excursión" creado anteriormente a la pantalla.

Guarda el calendario. Ahora nuestros tours se pueden ver en el calendario.
Paso 3 : limitamos la creación de excursiones y agregamos un banner con un enlace al calendario.
Para lograr esto, necesita JS, que hará un seguimiento del cambio en el campo Fecha. Cuando se selecciona la fecha, debemos sustituirla en la función jql y obtener todas las solicitudes para esta fecha, luego descubriremos qué hora se tarda y ocultaremos estas opciones en la pantalla para que sea imposible elegir la hora que se toma.
Cuando no hay solicitudes
Cuando hay 2 solicitudes a las 9 a.m. y a las 20 p.m.(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.$);
Solicitud de probadores
Herramienta:
El problema
En la solicitud, debe configurar la visualización de las etapas de prueba con la indicación del empleado responsable de la tarea. Debe verse que la etapa aún no se ha completado, o la etapa está completa (y quién la condujo).
Solución
Configure el campo de tipo de campo con secuencia de comandos para mostrar las etapas de prueba y asociar con el flujo de trabajo, registrar en la transición responsable de la etapa del autor.
Implementación
- Cree un campo "Progreso" de tipo campo con script.
- Cree campos de tipo UserPicker que correspondan a las etapas de prueba.
Por ejemplo, defina los siguientes pasos y cree campos de UserPicker con los mismos nombres:
- Información básica recopilada
- Localizado
- Registros recopilados
- Jugado
- Responsable encontrado
- Configuramos el flujo de trabajo para que las personas responsables completen las transiciones.
Por ejemplo, la transición "Localizada" escribe currentUser en el campo "Localized" UserPicker. - Personalice la pantalla usando un campo con script.
Rellene el bloque maravilloso:
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'){
En el bloque de velocidad, imprime $ value. Obtenemos el siguiente resultado:

Solicitud de soporte
Herramientas:
- Inclusor Js
- Mi maravilloso
El problema
El soporte técnico tiene su propia base de conocimiento sobre Confluence. Necesita la capacidad de mostrar artículos de base de conocimiento relacionados con problemas en una consulta de Jira. También necesitamos un mecanismo para mantener actualizada la base de datos; si el artículo no fue útil, debe solicitar un escritor técnico en Jira para escribir el artículo actual. Al cerrar una solicitud, solo deben permanecer los artículos relacionados con la solicitud. Los enlaces solo pueden ser visibles para el soporte técnico.
Solución
Al elegir un tipo específico de acceso en Jira (campo de tipo en cascada), la consulta debe mostrar artículos con Confluence que le correspondan en un campo separado con marcado wiki.
Si se usa con éxito, el artículo se selecciona como relevante utilizando la casilla de verificación.
Al resolver un problema, si no se describe en el artículo adjunto, se debe crear una tarea en Jira con el tipo "Documentación" asociado con la solicitud actual.
Implementación
Paso 1 : preparación
- Cree un campo de texto (multilínea) con marcado wiki - Enlaces.
- Cree un campo de tipo Seleccionar lista (en cascada) - "Tipo de llamada".
Por ejemplo, usamos los siguientes valores:
- Prepararemos etiquetas para artículos que conectarán artículos sobre Confluence con consultas en Jira:
- Cambiar membresías de grupos de AD - officeit_jira_ad_group_addresses_ad
- Suscribirse / darse de baja de un boletín - officeit_jira_subscription_subscription_of_subscription
- Conceder acceso a la carpeta - officeit_jira_sharing_access_to_folder
- Restablecer contraseña del dominio KM - officeit_jira_reset_password_of_domain_uz
- Restablecer contraseña de correo - officeit_jira_reset_password_mail_post
- Emisión de equipo temporal - officeit_jira_ emisión de equipo temporal
- Emisión de nuevos equipos - officeit_jira_new__new_technique
- Reemplazar el disco duro e instalar el sistema desde cero - officeit_jira_replace_hard_drive_and_install_system_s_ zero
- Reemplazar un disco duro con la transferencia de información - officeit_jira_replacing_hard_drive_with_data transfer_information
- Reemplazo de equipo defectuoso / obsoleto - officeit_jira_replacing_ defecty_ obsolete_ equipment
A continuación, debe crear artículos sobre Confluence, colocar etiquetas para ellos.
- Preparando el flujo de trabajo.
El tipo de apelación se completará al crear.
Los enlaces se agregan a una pantalla separada y se colocan en la transición en cierre (en el ejemplo, la transición se llama "Verificar enlaces reales"), recordamos el ID de transición (necesario en el futuro para configurar js).
Paso 2 : función posterior MyGroovy (agregar artículos a la solicitud)
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"
Paso 3 : script JS
(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.$);
Así es como se ve nuestra transición antes del procesamiento JS.

Esta es la transición después del procesamiento.

Y así, si se seleccionan uno o más artículos.

Una vez completada la transición, el campo Enlaces se sobrescribirá con el nuevo valor.
Paso 4 : función posterior MyGroovy (crear una solicitud para un nuevo artículo)
En la transición Verificar enlaces reales, escribimos un script que crea una solicitud del tipo "Documentación" si no hay valores en el campo Enlaces.
En conclusión
Estas soluciones no habrían aparecido sin la participación activa de colegas, principalmente aquellos que usan activamente herramientas listas para usar o que se enfrentan a tareas que necesitan ser automatizadas en su trabajo. A menudo resulta que una tarea interesante ya es la mitad de la solución: entonces solo necesita elegir la herramienta que satisfaga sus necesidades de la manera más eficiente, simple y fácil (para el usuario final). Ahora, tal vez, tenga preguntas y sugerencias que podrían mejorar aún más los complementos presentados: escriba en los comentarios.