Personnaliser Jira selon vos besoins. Flux parfait et ticket parfait



Si vous travaillez dans une entreprise informatique, vos processus sont très probablement basés sur le produit Atlassian bien connu - Jira. Il existe de nombreux trackers de tâches sur le marché pour résoudre les mêmes problèmes, y compris des solutions open source (Trac, Redmine, Bugzilla), mais Jira est peut-être le plus utilisé aujourd'hui.

Je m'appelle Dmitry Semenikhin, je suis chef d'équipe chez Badoo. Dans une courte série d'articles, je vais vous dire exactement comment nous utilisons Jira, comment nous l'avons personnalisé pour nos processus, quelles bonnes choses ont été «vissées» au-dessus et comment nous avons transformé le traqueur de problèmes en un centre de communication unique pour la tâche et simplifié notre vie. Dans cet article, vous verrez notre flux à l'intérieur, apprenez comment vous pouvez «tordre» votre Jira et découvrez des fonctionnalités supplémentaires de l'outil que vous ne connaissez peut-être pas.

L'article est principalement destiné à ceux qui utilisent déjà Jira, mais peut avoir des difficultés à intégrer ses fonctionnalités standard dans les processus existants de l'entreprise. En outre, l'article peut être utile aux entreprises qui utilisent d'autres suiveurs de tâches, mais ont rencontré certaines restrictions et envisagent un changement de décision. L'article n'est pas construit sur le principe de «problème - solution», dans lequel je décris les outils et fonctionnalités existants que nous avons construits autour de Jira, ainsi que les technologies que nous avons utilisées pour les implémenter.

Fonctionnalités supplémentaires de Jira


Pour rendre le texte suivant plus compréhensible, voyons quels outils Jira nous fournit pour la mise en œuvre de liste de souhaits non standard - ceux qui vont au-delà de la fonctionnalité Jira standard.

API REST


En général, un appel de commande API est une demande HTTP vers une URL API indiquant la méthode (GET, PUT, POST et DELETE), la commande et le corps de la demande. Le corps de la demande, ainsi que la réponse de l'API, sont au format JSON. Un exemple de demande qui renvoie une représentation JSON d'un ticket:

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

En utilisant l'API, vous pouvez, en utilisant des scripts dans n'importe quel langage de programmation:

  • créer des billets;
  • modifier toutes les propriétés des tickets (intégrées et personnalisées);
  • rédiger des commentaires;
  • utiliser JQL (langage de requête intégré) pour recevoir les listes de tickets;
  • et bien plus.

La documentation détaillée de l'API est disponible ici .

Nous avons écrit notre propre client API Jira de haut niveau en PHP, qui implémente toutes les commandes dont nous avons besoin. Voici un exemple de commandes pour travailler avec des commentaires:

 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


À l'aide de webhook, vous pouvez configurer l'appel d'une fonction de rappel externe sur votre hôte à divers événements dans Jira. En même temps, vous pouvez configurer autant de règles que vous le souhaitez afin que différentes URL se «tordent» pour différents événements et pour les tickets qui correspondent au filtre spécifié dans le webhook. L'interface de configuration des webhooks est disponible pour l'administrateur Jira.

Par conséquent, vous pouvez créer des règles comme celle-ci:

Nom : «SRV - Nouvelle fonctionnalité créée / mise à jour»
URL : www.myremoteapp.com/webhookreceiver
Portée : Project = SRV AND type in ('New Feature')
Événements : problème mis à jour, problème créé

Dans cet exemple, l'URL spécifiée sera appelée pour la création de tickets et les événements de modification correspondant au filtre d' étendue . Dans le même temps, le corps de la demande contiendra toutes les informations nécessaires sur ce qui a exactement changé et quel événement s'est produit.

Il est important de comprendre que Jira ne garantit pas que votre événement sera livré. Si l'URL externe n'a pas répondu ou a répondu avec une erreur, cela ne sera visible nulle part (sauf pour les journaux, peut-être). Par conséquent, le gestionnaire d'événements Webhook doit être aussi fiable que possible. Par exemple, vous pouvez mettre les événements en file d'attente et essayer de les traiter jusqu'à ce qu'il réussisse. Cela aidera à résoudre les problèmes liés aux services temporairement indisponibles, par exemple, toute base de données externe nécessaire au traitement correct de l'événement.

Une documentation détaillée sur les webhooks est disponible ici .

Scriptrunner


Il s'agit d'un plugin pour Jira, un outil très puissant qui vous permet de personnaliser beaucoup de Jira (y compris la possibilité de remplacer les webhooks). L'utilisation de ce plugin nécessite une connaissance de Groovy. Le principal avantage de l'outil pour nous est que vous pouvez intégrer une logique personnalisée dans le flux en ligne. Votre code de script sera exécuté immédiatement dans l'environnement Jira en réponse à une action spécifique. Par exemple, vous pouvez créer votre propre bouton dans l'interface de ticket, en cliquant dessus, vous créerez des tickets associés à la tâche en cours ou exécuterez des tests unitaires pour cette tâche. Et si soudainement quelque chose se passe mal, vous en tant qu'utilisateur le saurez immédiatement.

Les personnes intéressées peuvent lire la documentation .

Flow: ce qui est caché sous le capot


Et maintenant sur la façon dont nous appliquons des fonctionnalités supplémentaires de Jira dans nos projets. Considérez cela dans le contexte du passage de notre ticket de flux typique de la création à la fermeture. En même temps, je vais vous parler du flux lui-même.

Ouvert / backlog


Ainsi, tout d'abord, le ticket entre dans l'arriéré de nouveaux tickets avec le statut Ouvert . De plus, le chef de composant, après avoir vu un nouveau ticket sur son tableau de bord, prend une décision: attribuer un ticket dès maintenant au développeur ou l'envoyer au backlog des tickets connus (statut Backlog ) afin que vous puissiez l'attribuer plus tard lorsqu'un développeur gratuit apparaît et que les tickets de priorité supérieure seront fermés. Cela peut sembler étrange, car il semble logique de faire le contraire: créer des tickets avec le statut Backlog , puis les traduire en statut Open. Mais nous avons précisément pris racine dans ce schéma. Il vous permet de configurer facilement des filtres pour réduire le temps de décision pour les nouveaux tickets. Un exemple de filtre JQL qui affiche de nouvelles tâches pour une piste:

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

En cours


Nuances techniques du travail avec Git
Il convient de noter que nous travaillons sur chaque tâche dans une branche Git distincte. Quant à cela, nous avons convenu que le nom de la succursale au début devrait contenir le numéro de ticket. Par exemple, SRV-123_new_super_feature . De plus, les commentaires pour chaque commit dans la branche doivent contenir le numéro de ticket au format [SRV-123]: {comment}. Nous avons besoin d'un tel format, par exemple, pour la suppression correcte d'une «mauvaise» tâche d'une build. La procédure à suivre est décrite en détail dans l' article .

Ces exigences sont contrôlées par des crochets Git. Par exemple, voici le contenu de prepare-commit-msg, qui prépare un commentaire pour la validation, obtenant le numéro de ticket à partir du nom de la branche actuelle:

 #!/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 vous essayez de pousser un commit avec un commentaire «incorrect», un tel push sera rejeté. Une tentative de création d'une agence sans numéro de ticket au début sera également rejetée.

Lorsqu'un ticket frappe le développeur, la première chose qu'il décompose. Le résultat de la décomposition est l’idée du développeur de savoir comment résoudre le problème et combien de temps la solution prendra. Une fois que tous les détails principaux ont été clarifiés, le ticket est transféré au statut En cours et le développeur commence à écrire du code.

Il est habituel pour nous de définir la date d'échéance de la tâche au moment où elle est transférée au statut En cours. Si le développeur ne l'a pas fait, il recevra un rappel dans le messager d'entreprise HipChat. Un script spécial toutes les deux heures:

  • l'utilisation de l'API Jira REST sélectionne les tickets en état de progression avec un champ de date d'échéance vide ( projet = SRV AND status = 'In Progress' AND duedate is EMPTY );
  • sélectionne les tickets incomplets dont la date d'échéance est antérieure à la date actuelle ( projet = SRV AND status = 'In Progress' AND duedate is not EMPTY AND duedate <now () );
  • reconnaît le développeur pour chaque ticket en lisant le champ correspondant dans le ticket, ainsi que le lead du développeur;
  • regroupe les tickets des développeurs et des prospects et envoie des rappels à HipChat à l'aide de son API.

Après avoir fait tous les commits nécessaires, le développeur pousse la branche dans un navet commun. Dans ce cas, le crochet Git post-réception se déclenche, ce qui fait beaucoup de choses intéressantes:

  • Le nom de la branche Git, ainsi que les commentaires sur les commits, sont vérifiés pour la conformité à nos règles;
  • il est vérifié que le ticket auquel la branche est associée n'est pas fermé (vous ne pouvez pas insérer de nouveau code dans des tickets fermés);
  • La syntaxe des fichiers PHP modifiés est vérifiée (PHP -l nom_fichier.php );
  • le formatage est vérifié;
  • si le ticket dans lequel la branche est poussée est dans l'état Ouvert , il sera automatiquement transféré dans l'état En cours ;
  • le ticket est attaché à la succursale, l'entrée correspondante est effectuée dans le champ personnalisé du ticket Commits à l'aide de l'API Jira. Cela ressemble à ceci:


( branchdiff est un lien vers le diff de la branche dont la tête est à l'origine de la branche actuelle dans notre outil de révision de code Codeisok );

  • un commentaire est créé dans le ticket avec tous les commits de cette push.

    (Aida est le nom conditionnel de notre complexe d'automatisation pour travailler avec Jira, Git et pas seulement. C'est à partir de ce nom que les commentaires automatiques apparaissent dans le ticket. Nous en avons écrit plus sur Aida dans l' article ).
    Un clic sur le hachage du commit ouvre le diff avec la révision précédente de la branche (je vais le montrer ci-dessous);
  • il vérifie s'il existe des fichiers dans la branche qui peuvent nécessiter une traduction dans les langues prises en charge (par exemple, les modèles de page Web), et s'il y en a, la nouvelle valeur \ Changed est définie dans le champ personnalisé du ticket Lexems. Cela garantit que le ticket ne sera pas mis en production sans une traduction complète;
  • le nom de l'employé qui pousse la branche est ajouté à la liste des développeurs (un champ personnalisé du ticket Développeurs )

En révision


Après avoir écrit le code et vérifié que toutes les conditions requises pour la tâche sont remplies et que les tests ne sont pas rompus, le développeur attribue un ticket au réviseur (statut En révision ). En règle générale, le développeur décide qui examinera son ticket. Ce sera probablement un autre développeur qui connaît bien la bonne partie du code. La révision s'effectue à l'aide de l'outil Codeisok , qui s'ouvre immédiatement avec le diff souhaité en cliquant sur le lien branchdiff dans le champ Ticket de validation ou dans le commentaire en tant que hachage de validation dans les commentaires.

L'évaluateur voit quelque chose comme ceci:


Une fois la révision terminée, le réviseur appuie sur le bouton Terminer et, entre autres, à ce moment, les événements suivants se produisent:

  • à l'aide de l'API JIra, un commentaire est créé dans le ticket avec les commentaires du réviseur dans le contexte du code. Cela ressemble à ceci:


  • s'il y avait des commentaires sur le code et que le réviseur a décidé de rouvrir le ticket, le développeur recevra une notification à ce sujet dans HipChat (cela se fait en utilisant la règle du webhook, qui fonctionne à la réouverture);
  • Le champ de ticket Reviewers est rempli.

Résolu


En outre, si l'examen a réussi, le ticket est envoyé à l'arriéré des ingénieurs QA dans le statut Résolu . Mais en même temps, en utilisant webhook pour l'événement résolu, des tests automatiques sur le code de branche sont lancés en arrière-plan. Après quelques minutes, un nouveau commentaire apparaîtra dans le ticket, qui vous informera des résultats du test.



De plus, à tout moment, vous pouvez lancer manuellement un cycle de test répété en cliquant sur le bouton spécial Exécuter les tests unitaires dans le menu du ticket. Après une exécution réussie, un nouveau commentaire apparaîtra dans le ticket, similaire au précédent.


En fait, ce bouton est l'un des états de tâche supplémentaires dans le flux de travail Jira, une traduction dans laquelle déclenche un script Groovy pour le plugin ScriptRunner. Le script appelle une URL externe, qui lance l'exécution du test, et si l'URL a répondu avec succès, le ticket revient à son état précédent (dans notre cas, Résolu ).

In Shot / In Shot - OK


La tâche est d'abord testée dans un environnement de développement. Si tout va bien, un plan est créé (par exemple, en cliquant sur le lien Créer un plan dans le champ Valeurs ) - le répertoire sur le serveur dédié vers lequel les modifications du ticket sont copiées qui sont adjacentes au maître actuel. Le serveur fonctionne avec les données de production: les bases de données et les services sont les mêmes qui servent les vrais utilisateurs. Ainsi, le testeur peut ouvrir un site Web ou se connecter à la prise de vue à l'aide d'un client mobile et "isoler" la fonctionnalité dans l'environnement de production. "Isolé" signifie qu'aucun autre code / fonctionnalité, à l'exception du nouveau de la branche et du maître actuel, n'est exécuté. Par conséquent, cette étape de test est peut-être la principale, car elle permet à l'ingénieur QA de trouver le problème de la manière la plus fiable directement dans le problème de test.

Les ressources de prise de vue sont accessibles à l'aide d'URL spéciales générées dans le script de création de prise de vue et placées dans l'en-tête du ticket à l'aide de l'API Jira. En conséquence, nous voyons des liens vers le site, le panneau d'administration, les journaux et d'autres outils qui sont exécutés dans un environnement de prise de vue:



De plus, au moment de la génération de plans, un script est lancé qui analyse le contenu des fichiers modifiés et crée des demandes de traduction des nouveaux jetons trouvés. Une fois la traduction terminée, la valeur du champ Lexems est remplacée par Terminé et le ticket peut être ajouté à la génération.

Si le test en plan a réussi, le ticket est transféré au statut En plan - OK.

En construction / En construction - OK


Nous téléchargeons le code deux fois par jour - le matin et le soir. Pour ce faire, une branche de construction spéciale est créée, qui sera finalement fusionnée avec le maître et disposée «au combat».

Au moment de la création de la branche de génération, un script spécial utilisant une requête JQL reçoit une liste de tickets dans l'état In Shot - OK et essaie de les figer dans la branche de construction lorsque toutes les conditions suivantes sont remplies:

  • la traduction du ticket est terminée ou rien n'a besoin d'être traduit ( Lexems dans ('Non', 'Terminé') );
  • le développeur est présent sur le lieu de travail (le système de fusion automatique vérifie sur la base interne si le développeur est en vacances ou en congé de maladie, et si c'est le cas, le ticket ne peut être gelé que manuellement par les ingénieurs de publication ou un autre développeur responsable, ce qui est indiqué dans le champ spécial Vice Developer ; le chef du développeur absent dans ce cas reçoit une notification indiquant que le ticket ne peut pas être automatiquement ajouté à la version);
  • le ticket n'a pas le drapeau Up in Build défini par le développeur (il s'agit d'un champ personnalisé spécial du ticket qui permet au développeur de déterminer quand le ticket ira à la build);
  • la branche de ticket ne dépend pas d'une autre branche qui n'a pas encore atteint le maître ou la build actuelle. Nous faisons de notre mieux pour éviter une telle situation, mais cela se produit parfois lorsque le développeur crée sa propre branche non pas à partir du maître, mais à partir d'une branche d'un autre ticket, ou lorsqu'il se fige une autre branche pour lui-même. Cela peut également être fait par hasard, nous avons donc décidé qu'une protection supplémentaire ne nuirait pas.

Il convient de noter que la fusion automatique peut ne pas se produire en raison d'un conflit de fusion. Dans ce cas, le ticket est automatiquement transféré au statut de réouverture et attribué au développeur, à propos duquel il reçoit immédiatement une notification dans HipChat, et un message correspondant est ajouté au commentaire du ticket. Après avoir résolu le conflit, le ticket revient à la génération.

Si tout va bien et que la branche de ticket est figée dans la build, le ticket est automatiquement transféré vers le statut In Build et le nom de la build est écrit dans le champ personnalisé du ticket Build_Name .


De plus, en utilisant cette valeur, il est facile d'obtenir une liste des tickets qui ont été publiés avec chaque build. Par exemple, pour rechercher quelqu'un à blâmer en cas de problème.

À l'étape suivante, les ingénieurs QA vérifient en outre si le code de tâche fonctionne correctement en conjonction avec d'autres tâches de la build. Si tout va bien, le ticket est défini manuellement sur In Build - OK.

En production / En production - OK / Fermé


De plus, lors de la construction, l'ensemble de nos tests est exécuté (Unit, intégration, Selenium-, etc.). Si tout va bien, la construction est figée dans master et le code est mis en production. Le ticket est transféré au statut En production.

De plus, le développeur (ou le client) s'assure que la fonctionnalité fonctionne correctement en production et définit le statut du ticket En production - OK.

Après deux semaines, les tickets en statut En production - OK sont automatiquement transférés en statut Fermé , si quelqu'un ne l'a pas fait manuellement auparavant.

Il convient également de mentionner des statuts supplémentaires dans lesquels le ticket peut être situé:

  • Exigences - lorsqu'il n'est pas possible d'obtenir rapidement du client les clarifications nécessaires sur la tâche, et sans eux, un travail supplémentaire sur le ticket est impossible, le ticket est transféré à ce statut et attribué à celui qui a besoin de donner des explications;
  • Suspendu - si le travail de ticket est suspendu, par exemple, si le développeur est bloqué par les tâches d'une équipe adjacente ou a été contraint de passer à une tâche plus urgente;
  • Rouvert - une tâche peut être redécouverte au développeur après un examen, après des tests, après une tentative infructueuse de fusionner une branche avec le maître.

En conséquence, un diagramme simplifié de notre flux de travail ressemble à ceci:


Ticket - centre de communication pour la tâche


À la suite du passage du ticket dans le flux, son en-tête acquiert approximativement la forme suivante:



Quoi d'autre est intéressant ici que nous avons personnalisé pour nous et que je n'ai pas encore mentionné?

  • Composant - utilisé pour regrouper un ticket dans un grand département. Différents sous-groupes sont responsables de différents composants et, par conséquent, sur leurs tableaux de bord, ils ne voient que les tâches de leurs composants. Par exemple, je peux lister tous les bogues ouverts pour les composants de mon équipe avec cette requête:

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

  • Révision - si une révision du code est nécessaire. La valeur par défaut est nécessaire. Si la valeur du champ est définie sur Non, le ticket obtiendra immédiatement le statut Résolu.

    QA - Le testeur doit-il être vérifié? La valeur par défaut est nécessaire. Si la valeur du champ est définie sur Non, le ticket passe immédiatement à l'état In Shot - OK.

    Sprint - dans notre cas, il n'est pertinent que pour les tâches avec le type Nouvelle fonctionnalité, un plan pour lequel nous établissons à l'avance pour une semaine.
  • Date d'échéance - le développeur détermine la date à laquelle le ticket sera en production. Exposé avant de commencer le travail sur la tâche.
  • Situation - en fait, un court journal avec une brève description de l'état actuel de la tâche. Par exemple, «20/08 j'attends des traductions» , «21/08 Une clarification est demandée au client sur le problème X» . Cela permet de voir un bref résumé de la tâche dans la liste des autres tâches.
  • Msg4QA - informations pour les ingénieurs QA, que le développeur partage pour simplifier le processus de test

Nous essayons de mener une discussion sur les questions controversées avec le directeur de tâche dans les commentaires du ticket, et non de «salir» les clarifications importantes par courrier et messagerie instantanée. Si la discussion a néanmoins eu lieu «en marge», il est hautement souhaitable de copier ce que vous avez convenu dans le ticket.

En plus des textes «humains», comme je l'ai mentionné plus haut, beaucoup de choses sont écrites automatiquement dans un commentaire à l'aide de l'API:

  • commet
  • examiner les résultats;
  • résultats des tests.

Parfois, les commentaires automatiques peuvent interférer, par exemple, avec les chefs de produit. Par conséquent, nous avons créé un script JS simple qui ajoute un bouton à l'interface Jira et vous permet de minimiser tous les commentaires automatiques, en ne laissant que des commentaires humains. Par conséquent, les commentaires automatiques minimisés semblent compacts.



Code JS du script que nous avons intégré dans le modèle de 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);   } }); 


Quoi d'autre?


API webhooks Jira :

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

Résumé


Jira — , , . , , . Jira , .

— . Jira , Jira. , - .

Merci de votre attention!

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


All Articles