Personalizando Jira a tus necesidades. Flujo perfecto y boleto perfecto



Si trabaja en una empresa de TI, lo más probable es que sus procesos se basen en el conocido producto Atlassian: Jira. Hay muchos rastreadores de tareas en el mercado para resolver los mismos problemas, incluidas las soluciones de código abierto (Trac, Redmine, Bugzilla), pero quizás Jira es la más utilizada en la actualidad.

Mi nombre es Dmitry Semenikhin, soy un líder de equipo en Badoo. En una breve serie de artículos, le diré exactamente cómo usamos Jira, cómo lo personalizamos para nuestros procesos, qué cosas buenas se "atornillaron" en la parte superior y cómo convertimos el rastreador de problemas en un único centro de comunicación para la tarea y simplificamos nuestra vida. En este artículo, verá nuestro flujo interno, aprenderá cómo puede "torcer" su Jira y leerá sobre características adicionales de la herramienta que quizás no conozca.

El artículo está dirigido principalmente a aquellos que ya usan Jira, pero pueden tener dificultades para integrar sus características estándar en los procesos existentes en la empresa. Además, el artículo puede ser útil para las empresas que usan otros rastreadores de tareas, pero han encontrado algunas restricciones y están considerando un cambio de decisión. El artículo no se basa en el principio de "solución de problemas", en él describo las herramientas y características existentes que creamos en torno a Jira, así como las tecnologías que utilizamos para implementarlas.

Características adicionales de Jira


Para que el siguiente texto sea más comprensible, descubramos qué herramientas nos proporciona Jira para la implementación de la Lista de deseos no estándar, aquellas que van más allá de la funcionalidad estándar de Jira.

API REST


En general, una llamada de comando de API es una solicitud HTTP a una URL de API que indica el método (GET, PUT, POST y DELETE), el comando y el cuerpo de la solicitud. El cuerpo de la solicitud, así como la respuesta de la API, está en formato JSON. Un ejemplo de una solicitud que devuelve una representación JSON de un ticket:

GET /rest/api/latest/issue/{ticket_number} 

Usando la API, puede, usando scripts en cualquier lenguaje de programación:

  • crear boletos;
  • modificar las propiedades de los tickets (integrados y personalizados);
  • escribir comentarios;
  • usando JQL (lenguaje de consulta incorporado) para recibir cualquier lista de tickets;
  • y mucho mas

La documentación detallada de la API está disponible aquí .

Escribimos nuestro propio cliente Jira API de alto nivel en PHP, que implementa todos los comandos que necesitamos. Aquí hay un ejemplo de comandos para trabajar con comentarios:

 public function addComment($issue_key, $comment) {  return $this->_post("issue/{$issue_key}/comment", ['body' => $comment]); } public function updateComment($issue_key, $comment_id, $new_text) {  return $this->_put("issue/{$issue_key}/comment/{$comment_id}", ['body' => $new_text]); } public function deleteComment($issue_key, $comment_id) {  return $this->_delete("issue/{$issue_key}/comment/{$comment_id}"); } 

Webhooks


Con webhook, puede configurar la llamada de una función de devolución de llamada externa en su host para varios eventos en Jira. Al mismo tiempo, puede configurar tantas reglas como desee para que diferentes URL "se contraigan" para diferentes eventos y para tickets que coincidan con el filtro especificado en el webhook. La interfaz de configuración de webhooks está disponible para el administrador de Jira.

Como resultado, puede crear reglas como esta:

Nombre : "SRV - Nueva característica creada / actualizada"
URL : www.myremoteapp.com/webhookreceiver
Alcance : Proyecto = SRV Y escriba ('Nueva función')
Eventos : Problema actualizado, Problema creado

En este ejemplo, se llamará a la URL especificada para la creación del ticket y los eventos de cambio correspondientes al filtro Scope . Al mismo tiempo, el cuerpo de la solicitud contendrá toda la información necesaria sobre qué ha cambiado exactamente y qué evento ha ocurrido.

Es importante comprender que Jira no garantiza que su evento será entregado. Si la URL externa no respondió o respondió con un error, esto no será visible en ninguna parte (excepto los registros, tal vez). Por lo tanto, el controlador de eventos webhook debe ser lo más confiable posible. Por ejemplo, puede poner en cola eventos e intentar procesarlos hasta que tenga éxito. Esto ayudará a resolver problemas con servicios temporalmente no disponibles, por ejemplo, cualquier base de datos externa necesaria para el correcto procesamiento del evento.

La documentación detallada sobre webhooks está disponible aquí .

Scriptrunner


Este es un complemento para Jira, una herramienta muy poderosa que le permite personalizar una gran cantidad de Jira (incluida la capacidad de reemplazar webhooks). El uso de este complemento requiere conocimiento de Groovy. La principal ventaja de la herramienta para nosotros es que puede incorporar lógica personalizada en el flujo en línea. Su código de script se ejecutará inmediatamente en el entorno Jira en respuesta a una acción específica. Por ejemplo, puede crear su propio botón en la interfaz del ticket; al hacer clic en él, se crearán tickets asociados con la tarea actual o se ejecutarán pruebas unitarias para esta tarea. Y si de repente algo sale mal, usted como usuario lo sabrá de inmediato.

Los interesados ​​pueden leer la documentación .

Flow: lo que se esconde debajo del capó


Y ahora sobre cómo aplicamos características adicionales de Jira en nuestros proyectos. Considere esto en el contexto de pasar por nuestro ticket de flujo típico desde la creación hasta el cierre. Al mismo tiempo, le contaré sobre el flujo en sí.

Abrir / acumular


Entonces, primero el boleto entra en la cartera de nuevos boletos con el estado Abierto . Además, el líder del componente, después de haber visto un nuevo ticket en su tablero, toma una decisión: asignar un ticket ahora al desarrollador o enviarlo al backlog de tickets conocidos (estado de Backlog ) para que pueda asignarlo más tarde cuando aparezca un desarrollador gratuito y se cerrarán los tickets de mayor prioridad. Esto puede parecer extraño, ya que parece lógico hacer lo contrario: crear tickets en estado Backlog y luego traducirlos al estado Open. Pero hemos echado raíces precisamente en este esquema. Le permite configurar fácilmente filtros para reducir el tiempo de decisión para nuevos tickets. Un ejemplo de un filtro JQL que muestra nuevas tareas para un cliente potencial:

Project = SRV AND assignee is EMPTY AND status in (Open)

En progreso


Matices técnicos de trabajar con Git
Cabe señalar que trabajamos en cada tarea en una rama Git separada. En cuanto a esto, tenemos un acuerdo de que el nombre de la sucursal al principio debe contener el número de ticket. Por ejemplo, SRV-123_new_super_feature . Además, los comentarios para cada confirmación en la rama deben contener el número de ticket en el formato [SRV-123]: {comentario}. Necesitamos dicho formato, por ejemplo, para la eliminación correcta de una tarea "mala" de una compilación. Cómo se hace esto se describe en detalle en el artículo .

Estos requisitos están controlados por ganchos Git. Por ejemplo, aquí está el contenido de prepare-commit-msg, que prepara un comentario para el commit, obteniendo el número del ticket del nombre de la sucursal actual:

 #!/bin/bash b=`git symbolic-ref HEAD| sed -e 's|^refs/heads/||' | sed -e 's|_.*||'` c=`cat $1` if [ -n "$b" ] && [[ "$c" != "[$b]:"* ]] then echo "[$b]: $c" > $1 fi 

Si intenta impulsar una confirmación con un comentario "incorrecto", dicha inserción será rechazada. También se rechazará un intento de iniciar una sucursal sin un número de ticket al principio.

Cuando un boleto golpea al desarrollador, lo primero que descompone. El resultado de la descomposición es la idea del desarrollador de cómo resolver el problema y cuánto tiempo llevará la solución. Una vez que se han aclarado todos los detalles principales, el ticket se transfiere al estado En progreso y el desarrollador comienza a escribir el código.

Es habitual que establezcamos la fecha de vencimiento de la tarea en el momento en que se transfiere al estado En progreso. Si el desarrollador no hizo esto, recibirá un recordatorio en el mensajero corporativo de HipChat. Un guión especial cada dos horas:

  • el uso de Jira REST API selecciona tickets en estado de progreso con un campo de fecha de vencimiento vacío ( proyecto = SRV Y estado = 'En curso' Y la fecha de vencimiento es VACÍA );
  • selecciona tickets incompletos con fecha de vencimiento anterior a la fecha actual ( proyecto = SRV Y estado = 'En progreso' Y la fecha de vencimiento no está VACÍA Y fecha de vencimiento <ahora () );
  • reconoce al desarrollador para cada ticket leyendo el campo correspondiente en el ticket, así como el lead del desarrollador;
  • agrupa los tickets de desarrolladores y clientes potenciales y envía recordatorios a HipChat usando su API.

Después de realizar todas las confirmaciones necesarias, el desarrollador empuja la rama a un nabo común. En este caso, el gancho Git posterior a la recepción se dispara, lo que hace muchas cosas interesantes:

  • El nombre de la rama de Git, así como los comentarios sobre los commits, se verifican para cumplir con nuestras reglas;
  • se verifica que el ticket con el que está asociada la sucursal no está cerrado (no puede insertar un nuevo código en tickets cerrados);
  • Se verifica la sintaxis de los archivos PHP modificados (PHP -l nombre_archivo.php );
  • se verifica el formato;
  • si el ticket en el que se empuja la sucursal está en estado Abierto , se transferirá automáticamente al estado En progreso ;
  • el ticket se adjunta a la sucursal, la entrada correspondiente se realiza en el campo personalizado del ticket Commits utilizando la API de Jira. Se ve así:


( branchdiff es un enlace a diff de la rama con el encabezado desde el cual se originó la rama actual en nuestra herramienta de revisión de código Codeisok );

  • Se crea un comentario en el ticket con todos los commits en este push.

    (Aida es el nombre condicional de nuestro complejo de automatización para trabajar con Jira, Git y no solo. Es a partir de este nombre que aparecen comentarios automáticos en el ticket. Escribimos más sobre Aida en el artículo ).
    Un clic en el hash de la confirmación abre diff con la revisión previa de la rama (mostraré a continuación cómo se ve);
  • comprueba si hay archivos en la rama que pueden requerir traducción a los idiomas admitidos (por ejemplo, plantillas de página web), y si hay alguno, entonces el nuevo valor \ Modificado se establece en el campo personalizado del ticket Lexems. Esto asegura que el boleto no irá a producción sin una traducción completa;
  • el nombre del empleado que empuja la sucursal se agrega a la lista de desarrolladores (un campo personalizado del ticket Desarrolladores )

En revisión


Después de escribir el código y asegurarse de que se cumplan todos los requisitos para la tarea y que las pruebas no se rompan, el desarrollador asigna un ticket al revisor (estado En revisión ). Por lo general, el desarrollador decide quién revisará su boleto. Lo más probable es que sea otro desarrollador que esté bien versado en la parte correcta del código. La revisión se lleva a cabo utilizando la herramienta Codeisok , que se abre inmediatamente con la diferencia deseada haciendo clic en el enlace branchdiff en el campo Ticket de commits o en el comentario como hash de commit en los comentarios.

El revisor ve algo como esto:


Una vez finalizada la revisión, el revisor presiona el botón Finalizar y, entre otras cosas, en este momento sucede lo siguiente:

  • usando la API JIra, se crea un comentario en el ticket con comentarios del revisor en el contexto del código. Se parece a esto:


  • si hubo algún comentario sobre el código y el revisor decidió volver a abrir el ticket, el desarrollador recibirá una notificación al respecto en HipChat (esto se hace utilizando la regla de webhook, que funciona en la reapertura);
  • El campo del ticket de revisores se rellena.

Resuelto


Además, si la revisión fue exitosa, el ticket se envía al trabajo pendiente de los ingenieros de control de calidad en el estado Resuelto . Pero al mismo tiempo, usando webhook para el evento resuelto, las pruebas automáticas del código de sucursal se inician en segundo plano. Después de unos minutos, aparecerá un nuevo comentario en el ticket, que le informará de los resultados de la prueba.



Además, en cualquier momento, puede iniciar manualmente una ejecución de prueba repetida haciendo clic en el botón especial Ejecutar pruebas de unidad en el menú del ticket. Después de una ejecución exitosa, aparecerá un nuevo comentario en el ticket, similar al anterior.


De hecho, este botón es uno de los estados de tareas adicionales en el flujo de trabajo de Jira, una traducción en la que se activa un script Groovy para el complemento ScriptRunner. El script llama a una URL externa, que inicia la ejecución de la prueba, y si la URL respondió con éxito, el ticket vuelve a su estado anterior (en nuestro caso, Resuelto ).

En tiro / En tiro - OK


La tarea se prueba primero en un entorno de desarrollo. Si todo está bien, se crea un disparo (por ejemplo, haciendo clic en el enlace Crear disparo en el campo Confirmar ), el directorio en el servidor dedicado al que se copian los cambios del ticket adyacentes al maestro actual. El servidor funciona con datos de producción: las bases de datos y los servicios son los mismos que sirven a los usuarios reales. Por lo tanto, el probador puede abrir un sitio web o conectarse a la toma utilizando un cliente móvil y "aislar" la función en el entorno de producción. "Aislado" significa que no se ejecuta ningún otro código / funcionalidad, excepto el nuevo de la rama y el maestro actual. Por lo tanto, esta etapa de prueba es quizás la principal, ya que le permite al ingeniero de control de calidad encontrar el problema de manera más confiable directamente en el problema de prueba.

Se accede a los recursos de disparo utilizando URL especiales que se generan en el script de creación de disparo y se colocan en el encabezado del ticket utilizando la API de Jira. Como resultado, vemos enlaces al sitio, panel de administración, registros y otras herramientas que se ejecutan en un entorno de disparo:



Además, en el momento de la generación del disparo, se inicia un script que analiza el contenido de los archivos modificados y crea solicitudes para la traducción de los nuevos tokens encontrados. Una vez completada la traducción, el valor del campo Lexems se cambia a Listo y el ticket se puede agregar a la compilación.

Si la prueba en el disparo fue exitosa, el boleto se transfiere al estado In Shot - OK.

En Build / In Build - OK


Subimos el código dos veces al día, por la mañana y por la noche. Para hacer esto, se crea una rama de construcción especial, que eventualmente se fusionará con master y se presentará "en batalla".

En el momento de compilar la rama de compilación, un script especial que utiliza una consulta JQL recibe una lista de tickets en el estado In Shot - OK e intenta congelarlos en la rama de compilación cuando se cumplen todas las condiciones siguientes:

  • la traducción del boleto se ha completado o no es necesario traducir nada ( Lexems en ('No', 'Done') );
  • el desarrollador está presente en el lugar de trabajo (el sistema de fusión automática verifica en la base interna si el desarrollador está de vacaciones o en licencia por enfermedad, y si es así, el boleto solo puede ser congelado manualmente por los ingenieros de liberación u otro desarrollador responsable, que se indica en el campo especial Vice Developer ; el líder del desarrollador ausente en este caso recibe una notificación de que el ticket no se puede agregar automáticamente a la compilación);
  • el ticket no tiene el indicador Up in Build establecido por Developer (este es un campo personalizado especial del ticket que le permite al desarrollador determinar cuándo irá el ticket a la compilación);
  • la rama del ticket no depende de otra rama que aún no haya alcanzado el maestro o la compilación actual. Hacemos nuestro mejor esfuerzo para evitar tal situación, pero a veces esto sucede cuando el desarrollador crea su propia sucursal no desde el maestro, sino desde una sucursal de otro ticket, o cuando congela otra rama para sí mismo. Esto también se puede hacer por casualidad, por lo que decidimos que la protección adicional no haría daño.

Vale la pena señalar que la fusión automática puede no ocurrir debido a un conflicto de fusión. En este caso, el ticket se transfiere automáticamente al estado Reabrir y se asigna al desarrollador, sobre el cual recibe inmediatamente una notificación en HipChat, y se agrega un mensaje correspondiente al comentario del ticket. Después de resolver el conflicto, el ticket vuelve a la compilación.

Si todo está bien y la rama del ticket se congela en la compilación, el ticket se transfiere automáticamente al estado In Build , y el nombre de la compilación se escribe en el campo personalizado del ticket Build_Name .


Además, con este valor, es fácil obtener una lista de tickets que se publicaron con cada compilación. Por ejemplo, buscar a alguien a quien culpar si algo salió mal.

En la siguiente etapa, los ingenieros de control de calidad también verifican si el código de la tarea funciona correctamente junto con otras tareas en la compilación. Si todo está bien, el ticket se configura manualmente en In Build - OK.

En producción / En producción - OK / Cerrado


Además, en la compilación, se ejecuta todo nuestro conjunto de pruebas (Unidad, integración, Selenio, etc.). Si todo está bien, la compilación se congela en master y el código se presenta para la producción. El ticket se transfiere al estado En producción.

Además, el desarrollador (o el cliente) se asegura de que la función funcione correctamente en la producción y establece el estado del ticket en producción: OK.

Después de dos semanas, los tickets en el estado En producción - OK se transfieren automáticamente al estado Cerrado , si alguien no ha hecho esto manualmente antes.

También vale la pena mencionar estados adicionales en los que se puede ubicar el boleto:

  • Requisitos : cuando no es posible obtener rápidamente del cliente las aclaraciones necesarias sobre la tarea, y sin ellos es imposible seguir trabajando en el ticket, el ticket se transfiere a este estado y se asigna al que necesita dar explicaciones;
  • Suspendido : si el trabajo del ticket se suspende, por ejemplo, si el desarrollador está bloqueado por tareas de un equipo adyacente o se vio obligado a cambiar a una tarea más urgente;
  • Reabierto : se puede redescubrir una tarea para el desarrollador después de una revisión, después de la prueba, después de un intento fallido de fusionar una rama con el maestro.

Como resultado, un diagrama simplificado de nuestro flujo de trabajo se ve así:


Ticket - centro de comunicación para la tarea


Como resultado de pasar el ticket por el flujo, su encabezado adquiere aproximadamente la siguiente forma:



¿Qué más es interesante aquí que hayamos personalizado para nosotros y que aún no he mencionado?

  • Componente : se utiliza para agrupar un ticket dentro de un departamento grande. Los diferentes subgrupos son responsables de los diferentes componentes y, en consecuencia, en sus paneles solo ven tareas para sus componentes. Por ejemplo, puedo enumerar todos los errores abiertos para los componentes de mi equipo con esta consulta:

     Project = SRV AND type = Bug AND status = Open AND component in componentsLeadByUser(d.semenihin) 

  • Revisión : si se necesita una revisión del código. Por defecto es necesario. Si el valor del campo se establece en No, el ticket obtendrá inmediatamente el estado Resuelto.

    QA: ¿el verificador necesita verificación? Por defecto es necesario. Si el valor del campo se establece en No, el ticket pasará inmediatamente al estado In Shot - OK.

    Sprint: en nuestro caso, es relevante solo para tareas con el tipo de Nueva Función, un plan para el cual elaboramos con anticipación durante una semana.
  • Fecha de vencimiento : el desarrollador determina la fecha en que el boleto estará en producción. Expuesto antes de comenzar a trabajar en la tarea.
  • Situación : de hecho, un registro breve con una breve descripción del estado actual de la tarea. Por ejemplo, “20/08 estoy esperando traducciones” , “21/08 se requiere una aclaración del cliente sobre el problema X” . Esto ayuda a ver un breve resumen de la tarea en la lista de otras tareas.
  • Msg4QA : información para ingenieros de control de calidad, que el desarrollador comparte para simplificar el proceso de prueba

Tratamos de llevar a cabo una discusión de temas controvertidos con el director de tareas en los comentarios del ticket, y no "difuminar" aclaraciones importantes por correo y mensajería instantánea. Sin embargo, si la discusión tuvo lugar "de forma paralela", es muy conveniente copiar lo que ha acordado en el boleto.

Además de los textos "humanos", como mencioné anteriormente, muchas cosas se escriben automáticamente en un comentario utilizando la API:

  • comete
  • revisar los resultados;
  • resultados de la prueba de funcionamiento.

A veces, los comentarios automáticos pueden interferir, por ejemplo, con los gerentes de producto. Por lo tanto, creamos un script JS simple que agrega un botón a la interfaz de Jira y le permite minimizar todos los comentarios automáticos, dejando solo los humanos. Como resultado, los comentarios automáticos minimizados se ven compactos.



Código JS del script que incorporamos en la plantilla del ticket
 window.addEventListener('load', () => {   const $ = window.jQuery;   const botsAttrMatch = [       'aida',       'itops.api'   ].map(bot => `[rel="${bot}"]`).join(',');   if (!$) {       return;   }   const AIDA_COLLAPSE_KEY = 'aida-collapsed';   const COMMENT_SELECTOR = '.issue-data-block.activity-comment.twixi-block';   const JiraImprovements = {       init() {           this.addButtons();           this.handleAidaCollapsing();           this.handleCommentExpansion();           // Handle toggle button and aida collapsing and put it on a loop           // to handle unexpected JIRA behaviour           const self = this;           setInterval(function () {               self.addButtons();               self.handleAidaCollapsing();           }, 2000);           addCss(`               #badoo-toggle-bots {                   background: #fff2c9;                   color: #594300;                   border-radius: 0 3px 0 0;                   margin-top: 3px;                   display: inline-block;               }           `);       },       addButtons() {           // Do we already have the button?           if ($('#badoo-toggle-bots').length > 0) {               return;           }           // const headerOps = $('ul#opsbar-opsbar-operations');           const jiraHeader = $('#issue-tabs');           // Only add it in ticket state           if (jiraHeader.length > 0) {               const li = $('<a id="badoo-toggle-bots" class="aui-button aui-button-primary aui-style" href="/">Collapse Bots</a>');               li.on('click', this.toggleAidaCollapsing.bind(this));               jiraHeader.append(li);           }       },       toggleAidaCollapsing(e) {           e.preventDefault();           const isCollapsed = localStorage.getItem(AIDA_COLLAPSE_KEY) === 'true';           localStorage.setItem(AIDA_COLLAPSE_KEY, !isCollapsed);           this.handleAidaCollapsing();       },       handleAidaCollapsing() {           const isCollapsed = localStorage.getItem(AIDA_COLLAPSE_KEY) === 'true';           const aidaComments = $(COMMENT_SELECTOR).has(botsAttrMatch).not('.manual-toggle');           if (isCollapsed) {               aidaComments.removeClass('expanded').addClass('collapsed');               $('#badoo-toggle-bots').text('Show Bots');           }           else {               aidaComments.removeClass('collapsed').addClass('expanded');               $('#badoo-toggle-bots').text('Collapse Bots');           }       },       handleCommentExpansion() {           $(document.body).delegate('a.collapsed-comments', 'click', function () {               const self = this; // eslint-disable-line no-invalid-this               let triesLeft = 100;               const interval = setInterval(() => {                   if (--triesLeft < 0 || self.offsetHeight === 0) {                       clearInterval(interval);                   }                   // Element has been removed from DOM. ie new jira comments have been added                   if (self.offsetHeight === 0) {                       JiraImprovements.handleAidaCollapsing();                   }               }, 100);           });           $(document.body).delegate(COMMENT_SELECTOR, 'click', function () {               $(this).addClass('manual-toggle');// eslint-disable-line no-invalid-this           });       }   };   JiraImprovements.init();   function addCss(cssText) {       const style = document.createElement('style');       style.type = 'text/css';       if (style.styleSheet) {           style.styleSheet.cssText = cssText;       }       else {           style.appendChild(document.createTextNode(cssText));       }       document.head.appendChild(style);   } }); 


Que mas


API webhooks Jira :

  • HipChat, - ( );
  • HipChat ( , );
  • ( ) ( ; );
  • ; , ;
  • In progress ;
  • , «» (, On Review), ;
  • , Jira (, «d.semenihin (Day off)»). .

Resumen


Jira — , , . , , . Jira , .

— . Jira , Jira. , - .

Gracias por su atencion!

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


All Articles