paint-brush
Gagner du temps et des nerfs avec les formules et le plugin Structure Jirapar@ipolubentcev
874 lectures
874 lectures

Gagner du temps et des nerfs avec les formules et le plugin Structure Jira

par Ivan Polubentsev36m2023/10/29
Read on Terminal Reader

Trop long; Pour lire

Les formules avec le plugin Jira Structure peuvent être époustouflantes : améliorez votre jeu en créant des tableaux, simplifiez le travail avec les tâches et analysez les versions et les projets.
featured image - Gagner du temps et des nerfs avec les formules et le plugin Structure Jira
Ivan Polubentsev HackerNoon profile picture
0-item
1-item

Le plugin Structure pour Jira est très utile pour le travail quotidien avec les tâches et leur analyse ; il porte la visualisation et la structuration des tickets Jira à un nouveau niveau, et ce, dès le départ.


Et tout le monde ne le sait pas, mais la fonctionnalité des formules de structure peut tout simplement vous époustoufler. À l'aide de formules, vous pouvez créer des tableaux extrêmement utiles qui peuvent grandement simplifier le travail avec les tâches et, plus important encore, ils sont utiles pour effectuer une analyse plus approfondie des versions, des épopées et des projets.


Que diriez-vous d'afficher un graphique Burndown ou d'afficher la santé d'un ticket dans un tableau avec des tâches ?


Dans cet article, vous verrez comment créer vos propres formules, en commençant par les exemples les plus simples et en terminant par des cas complexes, mais plutôt utiles.


Alors, à qui s’adresse ce texte ? On pourrait se demander pourquoi écrire un article alors que la documentation officielle sur le site Web d'ALM Works est là, attendant que les lecteurs s'y penchent. C'est vrai. Cependant, je fais partie de ces personnes qui n'avaient même pas la moindre idée que Structure cachait des fonctionnalités aussi étendues : "Attendez, c'était une option depuis le début ?!" Cette prise de conscience m'a fait réfléchir : il se peut qu'il y ait d'autres personnes qui ne savent toujours pas le genre de choses qu'elles peuvent faire avec les formules et la structure.


Cet article sera également utile à ceux qui connaissent déjà les formules. Vous apprendrez quelques options pratiques intéressantes pour utiliser les champs personnalisés et, peut-être, en emprunterez quelques-unes pour vos projets . À propos, si vous avez vos propres exemples intéressants, je serai heureux que vous les partagiez dans les commentaires. .


Chaque exemple est analysé en détail, depuis la description du problème jusqu'à l'explication du code, de manière suffisamment approfondie pour qu'il n'y ait plus de questions. Bien entendu, parallèlement aux explications, chaque exemple est illustré par un code que vous pouvez essayer par vous-même sans vous lancer dans l'analyse.


Si vous n'avez pas envie de lire, mais que les formules vous intéressent, consultez les webinaires ALM Works . Ceux-ci expliquent les bases en 40 minutes ; les informations y sont présentées de manière très compressée.


Vous n'avez besoin d'aucune connaissance supplémentaire pour comprendre les exemples, donc toute personne ayant travaillé avec Jira et Structure pourra répéter les exemples dans ses tableaux sans aucun problème.


Les développeurs ont fourni une syntaxe assez souple avec leur langage Expr. Fondamentalement, la philosophie ici est « écrivez comme vous voulez et ça marchera ».


Alors, commençons!


Pourquoi avons-nous besoin de formules ?

Alors, pourquoi voudrions-nous utiliser des formules ? Eh bien, il s'avère parfois que nous n'avons pas suffisamment de champs Jira standards, tels que « Assignee », « Story Points », etc. Ou nous devons calculer un certain montant pour certains champs, afficher la capacité restante par version et savoir combien de fois la tâche a changé de statut. Peut-être souhaitons-nous même fusionner plusieurs champs en un seul afin de rendre notre structure plus facile à lire.


Pour résoudre ces problèmes, nous avons besoin de formules et nous les utiliserons pour créer des champs personnalisés.


La première chose que nous devons faire est de comprendre comment fonctionne une formule. Cela nous permet d'appliquer une sorte d'opération à une chaîne. Étant donné que nous téléchargeons de nombreuses tâches dans la structure, la formule est appliquée à chaque ligne du tableau entier. Habituellement, toutes ses opérations visent à travailler avec des tâches dans ces lignes.


Ainsi, si nous demandons à la formule d'afficher un champ Jira, par exemple « Destinataire », alors la formule sera appliquée pour chaque tâche, et nous aurons une autre colonne « Destinataire ».


Les formules se composent de plusieurs entités de base :

  • Variables - pour accéder aux champs Jira et enregistrer les résultats intermédiaires
  • Fonctions intégrées : elles effectuent une opération prédéfinie, par exemple compter le nombre d'heures entre des dates ou filtrer les données dans un tableau.
  • Fonctions personnalisées – si nous avons besoin de calculs uniques
  • Différentes formes d'affichage du résultat, par exemple « Date/Heure », « Durée », « Nombre » ou « Marquage Wiki » pour votre option.


Connaître les formules

Nous nous familiariserons davantage avec les formules et leur syntaxe à travers quelques exemples, et nous allons parcourir six cas pratiques.


Avant d'examiner chaque exemple, nous indiquerons quelles fonctionnalités de structure nous utilisons ; les nouvelles fonctionnalités qui n'ont pas encore été expliquées seront en gras. Chacun des exemples suivants aura un niveau de complexité croissant. Ils sont organisés de manière à vous présenter progressivement les fonctionnalités importantes de la formule.


Voici la structure de base que vous verrez à chaque fois :

  • Le problème
  • La solution proposée
  • Les fonctionnalités de la structure utilisées
  • Un exemple de code
  • Une analyse de la solution


Ces exemples couvrent des sujets allant du mappage de variables aux tableaux complexes :

  • Deux exemples affichant les dates de début et de fin de travail sur une tâche (options avec affichage différent)
  • Une tâche parent — affichant le type et le nom de la tâche parent
  • La somme des Story Points des sous-tâches et le statut de ces évaluations
  • Une indication des changements récents dans l'état de la tâche
  • Un calcul du temps de travail, hors jours chômés (week-end) et statuts supplémentaires


Création de formules

Voyons d’abord comment créer des champs personnalisés avec des formules. Dans la partie supérieure droite de Structure, à la fin de toutes les colonnes, il y a une icône « + » — cliquez dessus. Dans le champ qui apparaît, écrivez « Formule… » et sélectionnez l'élément approprié.


Création de formules


Formules de sauvegarde

Discutons de la sauvegarde d'une formule. Malheureusement, il n'est toujours pas possible de sauvegarder une formule spécifique séparément quelque part (uniquement dans votre cahier, comme je le fais). Lors du webinaire ALM Works, l'équipe a mentionné qu'elle travaillait sur une banque de formules, mais pour l'instant, la seule façon de les enregistrer est d'enregistrer l'intégralité de la vue avec la formule.


Lorsque nous avons fini de travailler sur une formule, nous devons cliquer sur la vue de notre structure (elle sera probablement marquée d'un astérisque bleu) et cliquer sur « Enregistrer » pour écraser la vue actuelle. Ou vous pouvez cliquer sur « Enregistrer sous… » pour créer une nouvelle vue. (N'oubliez pas de le rendre disponible aux autres utilisateurs de Jira car les nouvelles vues sont privées par défaut.)


La formule sera enregistrée dans le reste des champs dans une vue particulière, et vous pourrez la voir dans l'onglet « Avancé » du menu « Afficher les détails ».


À partir de la version 8.2, Structure a désormais la possibilité d'enregistrer des formules en 3 clics rapides.

La boîte de dialogue d'enregistrement est disponible dans la fenêtre d'édition de formule. Si cette fenêtre n'est pas ouverte, cliquez simplement sur l'icône triangle ▼ dans la colonne souhaitée.


Formules de sauvegarde


Dans la fenêtre d'édition, nous voyons le champ « Colonne enregistrée », à droite il y a une icône avec une notification bleue, ce qui signifie que les modifications dans la formule n'ont pas été enregistrées. Cliquez sur cette icône et sélectionnez l'option « Enregistrer sous… ».


Colonne enregistrée


Entrez ensuite les noms de notre colonne (formule) et choisissez dans quel espace la sauvegarder. «Mes colonnes» si nous voulons l'enregistrer dans une liste personnelle. « Global », pour que la formule soit enregistrée dans la liste générale, où elle pourra être modifiée par tous les utilisateurs de votre Structure. Cliquez sur « Enregistrer ».


Cliquez sur Enregistrer


Notre formule est désormais enregistrée. Nous pouvons le charger dans n’importe quelle structure ou le réenregistrer de n’importe où. En réenregistrant la formule, elle sera mise à jour dans toutes les structures dans lesquelles elle est utilisée.


Le mappage des variables est également enregistré avec la formule, mais nous parlerons du mappage plus tard.


Passons maintenant à nos exemples !


Afficher les dates de début et de fin de travail sur une tâche

Dates personnalisées dans les deux dernières colonnes

Problème

Nous avons besoin d'un tableau avec une liste de tâches, ainsi que les dates de début et de fin pour travailler sur ces tâches. Nous avons également besoin du tableau pour l'exporter vers un Gantt Excel distinct. Malheureusement, Jira et Structure ne savent pas comment fournir de telles dates dès le départ.

Solution proposée

Les dates de début et de fin sont les dates de transition vers des statuts spécifiques, dans notre cas ce sont « En cours » et « Fermé ». Nous devons prendre ces dates et afficher chacune d'elles dans un champ séparé (cela est nécessaire pour une exportation ultérieure vers le Gantt). Nous aurons donc deux champs (deux formules).


Les fonctionnalités de la structure utilisées

  1. Mappage de variables
  2. La possibilité d'ajuster le format d'affichage


Un exemple de code

Champ pour la date de début :

 firstTransitionToStart


Champ pour la date de fin :

 latestTransitionToDone


Une analyse de la solution

Dans ce cas, le code est une variable unique, firstTransitionToStart pour le champ de date de début et lastTransitionToDone pour le deuxième champ.


Concentrons-nous sur le champ de date de début pour l'instant. Notre objectif est d'obtenir la date à laquelle la tâche est passée au statut « En cours » (cela correspond au début logique de la tâche), de sorte que la variable est nommée, de manière assez explicite pour éviter d'avoir à deviner ultérieurement, comme « première transition vers commencer".


Pour transformer une date en variable, nous nous tournons vers le mappage de variables. Sauvons notre formule en cliquant sur le bouton « Enregistrer ».


Cliquez pour enregistrer la formule


Notre variable est apparue dans la section « Variables », avec un point d'exclamation à côté. La structure indique qu'il ne peut pas lier une variable à un champ dans Jira, et nous devrons le faire nous-mêmes (c'est-à-dire le mapper).


Cliquez sur la variable et accédez à l'interface de cartographie. Sélectionnez le champ ou l'opération nécessaire — recherchez l'opération « Date de transition… ». Pour ce faire, écrivez « transition » dans le champ de sélection. Plusieurs options vous seront proposées à la fois, et l'une d'elles nous convient : « Première transition vers En cours ». Mais afin de démontrer le fonctionnement du mappage, choisissons l'option « Date de transition… ».


Configuration du mappage


Après cela, vous devez choisir le statut dans lequel la transition s'est produite et l'ordre de cette transition : la première ou la dernière.


Sélectionnez ou saisissez dans « Statut » — « Statut : En cours » (ou le statut correspondant dans votre Workflow), et dans « Transition » — « Première transition vers le statut », puisque le début du travail sur une tâche est la toute première transition au statut correspondant.


Sélectionnez la catégorie souhaitée



Si au lieu de « Date de transition… » nous choisissions l'option initialement proposée « Première transition vers En cours », alors le résultat serait presque le même : la structure choisirait pour nous les paramètres nécessaires. La seule chose est qu'au lieu de « Statut : En cours », nous aurions « Catégorie : En cours ».


Différence entre la catégorie de statut et le statut


Permettez-moi de noter une caractéristique importante : un statut et une catégorie sont deux choses différentes. Un statut est un statut spécifique, c'est sans ambiguïté, mais une catégorie peut regrouper plusieurs statuts. Il n'y a que trois catégories : « À faire », « En cours » et « Terminé ». Dans Jira, ils sont généralement marqués respectivement de couleurs grise, bleue et verte. Le statut doit appartenir à l'une de ces catégories.

Je recommande d'indiquer un statut spécifique dans des cas comme celui-ci afin d'éviter toute confusion avec des statuts de même catégorie. Par exemple, nous avons deux statuts de la catégorie « To Do » sur le projet, « Open » et « QA Queue ».


Revenons à notre exemple.


Une fois que nous avons sélectionné les options nécessaires, nous pouvons cliquer sur « < Retour à la liste des variables » pour compléter les options de mappage pour la première variable TransitionToStart. Si nous faisons tout correctement, nous verrons une coche verte.


Général affiche la valeur par défaut (en millisecondes)


En même temps, dans notre champ personnalisé, nous voyons des nombres étranges qui ne ressemblent pas du tout à une date. Dans notre cas, le résultat de la formule sera la valeur de la variable firstTransitionToStart, et sa valeur est en millisecondes depuis janvier 1970. Afin d'obtenir la date correcte, nous devons choisir un format d'affichage de formule spécifique.


La sélection du format est située tout en bas de la fenêtre d'édition. « Général » y est sélectionné par défaut. Nous avons besoin de « Date/Heure » pour afficher correctement la date.


Sélectionnez Date/Heure, au lieu de Général


Pour le deuxième champ, lastTransitionToDone, nous ferons de même. La seule différence est que lors du mappage, nous pouvons déjà sélectionner la catégorie « Terminé », et non le statut (puisqu'il n'y a généralement qu'un seul statut d'achèvement de tâche sans ambiguïté). Nous sélectionnons « Dernière transition » comme paramètre de transition, car nous nous intéressons à la transition la plus récente vers la catégorie « Terminé ».


Le résultat final pour les deux champs ressemblera à ceci.


Vue finale avec dates


Voyons maintenant comment obtenir le même résultat, mais avec notre propre format d'affichage.


Affichage de la date avec notre propre format

Exemple de format personnalisé


Problème

Nous ne sommes pas satisfaits du format d'affichage de la date de l'exemple précédent, car nous en avons besoin d'un format spécial pour le tableau du Gantt : « 01.01.2022 ».


Solution proposée

Affichons les dates à l'aide des fonctions intégrées à Structure, en précisant le format qui nous convient.


Caractéristiques structurelles utilisées

  1. Mappage de variables
  2. Fonctions expr.


Un exemple de code

 FORMAT_DATETIME(firstTransitionToStart;"dd.MM.yyyy")


Une analyse de la solution

Les développeurs ont fourni de nombreuses fonctions différentes, dont une distincte pour afficher la date dans notre propre format : FORMAT_DATETIME ; c'est ce que nous allons utiliser. La fonction utilise deux arguments : une date et une chaîne du format souhaité.


Nous configurons la variable firstTransitionToStart (premier argument) en utilisant les mêmes règles de mappage que dans l'exemple précédent. Le deuxième argument est une chaîne spécifiant le format, et nous le définissons comme ceci : « jj.MM.aaaa ». Cela correspond au formulaire souhaité, « 01.01.2022 ».


Ainsi, notre formule donnera immédiatement un résultat sous la forme souhaitée. Ainsi, nous pouvons conserver l’option « Général » dans les paramètres du champ.


Le deuxième champ avec la date de fin des travaux se fait de la même manière. En conséquence, la structure devrait ressembler à l’image ci-dessous.


Aliment final après la transformation


En principe, il n'y a pas de difficultés significatives à travailler avec la syntaxe des formules. Si vous avez besoin d'une variable, écrivez son nom ; si vous avez besoin d'une fonction, encore une fois, écrivez simplement son nom et transmettez les arguments (s'ils sont requis).


Lorsque Structure rencontre un nom inconnu, elle suppose qu'il s'agit d'une variable et essaie de la mapper elle-même, ou nous demande de l'aide.


À propos, remarque importante : la structure n'est pas sensible à la casse, donc firstTransitionToStart, firsttransitiontostart et firSttrAnsItiontOSarT sont la même variable. La même règle s'applique aux fonctions. Afin d'obtenir un style de code sans ambiguïté, dans les exemples, nous essaierons de respecter les règles des conventions de capitalisation de MSDN.


Examinons maintenant la syntaxe et examinons un format spécial pour afficher le résultat.


Afficher le nom de la tâche parent

Le nom du parent est affiché avant le résumé


Problème

Nous travaillons avec des tâches régulières (Tâche, Bug, etc.) et avec des tâches de type Story qui comportent des sous-tâches. À un moment donné, nous devons découvrir sur quelles tâches et sous-tâches l'employé a travaillé pendant une certaine période.


Le problème est que de nombreuses sous-tâches ne fournissent pas d'informations sur l'histoire elle-même, car elles sont appelées « travailler sur l'histoire », « mettre en place » ou, par exemple, « activer l'effet ». Et si nous demandons une liste de tâches pour une certaine période, nous obtiendrons une douzaine de tâches avec le nom « travailler sur l'histoire » sans aucune autre information utile.


Nous aimerions avoir une vue avec une liste divisée en deux colonnes : une tâche et une tâche parent, afin qu'à l'avenir il soit possible de regrouper une telle liste par employés.


Solution proposée

Sur notre projet, nous avons deux options lorsqu'une tâche peut avoir un parent :

  1. Une tâche est une sous-tâche et son parent est uniquement Story
  2. Une tâche est une tâche normale (tâche, bug, etc.) et elle peut ou non avoir Epic, auquel cas la tâche n'a aucun parent.


Il faut donc :

  1. Découvrez si une tâche a un parent
  2. Découvrez le type de ce parent
  3. Déterminez le type et le nom de cette tâche selon le schéma suivant : « [Parent-type] Parent-name ».


Pour simplifier la perception de l'information, nous colorerons le texte du type de tâche : c'est-à-dire soit « [Histoire] » soit « [Epic] ».


Ce que nous utiliserons :

  1. Mappage de variables
  2. Condition
  3. Accès aux champs de tâches
  4. Format d'affichage — balisage wiki


Un exemple de code

 if( Parent.Issuetype = "Story"; """{color:green}[${Parent.Issuetype}]{color} ${Parent.Summary}"""; EpicLink; """{color:#713A82}[${EpicLink.Issuetype}]{color} ${EpicLink.EpicName}""" )


Une analyse de la solution

Pourquoi la formule commence-t-elle par une condition if, si nous avons simplement besoin de générer une chaîne et d'y insérer le type et le nom de la tâche ? N'existe-t-il pas un moyen universel d'accéder aux champs de tâches ? Oui, mais pour les tâches et les epics, ces champs sont nommés différemment et vous devez également y accéder différemment, c'est une fonctionnalité de Jira.


Les différences commencent au niveau de la recherche parent. Pour une sous-tâche, le parent réside dans le champ Jira « Parent Issue », et pour une tâche normale, l'epic sera le parent, situé dans le champ « Epic Link ». En conséquence, nous devrons écrire deux options différentes pour accéder à ces champs.


C'est là que nous avons besoin d'une condition if. Le langage Expr a différentes manières de gérer les conditions. Le choix entre eux est une question de goût.


Il existe une méthode « de type Excel » :

 if (condition1; result1; condition2; result2 … )


Ou une méthode plus « code-like » :

 if condition1 : result1 else if condition2 : result2 else result3


Dans l'exemple, j'ai utilisé la première option ; regardons maintenant notre code de manière simplifiée :

 if( Parent.Issuetype = "Story"; Some kind of result 1; EpicLink; Some kind of result 2 )


Nous voyons deux conditions évidentes :

  • Parent.Issuetype = « Histoire »
  • Lien épique


Voyons ce qu'ils font et commençons par le premier, Parent.Issuetype=”Story”.


Dans ce cas, Parent est une variable qui est automatiquement mappée au champ « Parent Issue ». C'est là que, comme nous l'avons vu ci-dessus, le parent de la sous-tâche devrait vivre. Grâce à la notation par points (.), on accède à la propriété de ce parent, notamment à la propriété Issuetype, qui correspond au champ Jira « Issue Type ». Il s'avère que toute la ligne Parent.Issuetype nous renvoie le type de la tâche parent, si une telle tâche existe.


De plus, nous n'avons pas eu besoin de définir ou de cartographier quoi que ce soit, car les développeurs ont déjà fait de leur mieux pour nous. Ici, par exemple, vous trouverez un lien vers toutes les propriétés (y compris les champs Jira) prédéfinies dans le langage, et ici vous pouvez voir une liste de toutes les variables standard, qui peuvent également être consultées en toute sécurité sans paramètres supplémentaires.


Ainsi, la première condition est de voir si le type de la tâche parent est Story. Si la première condition n’est pas satisfaite, alors le type de la tâche parent n’est pas Story, ou n’existe pas du tout. Et cela nous amène à la deuxième condition : EpicLink.


En fait, c’est à ce moment-là que l’on vérifie si le champ Jira « Epic Link » est renseigné (c’est-à-dire que l’on vérifie son existence). La variable EpicLink est également standard et n'a pas besoin d'être mappée. Il s'avère que notre condition est satisfaite si la tâche possède Epic Link.


Et la troisième option est lorsqu'aucune des conditions n'est remplie, c'est-à-dire que la tâche n'a ni parent ni Epic Link. Dans ce cas, nous n'affichons rien et laissons le champ vide. Cela se fait automatiquement puisque nous n’obtiendrons aucun résultat.


Nous avons compris les conditions, passons maintenant aux résultats. Dans les deux cas, il s'agit d'une chaîne avec du texte et une mise en forme spéciale.


Résultat 1 (si le parent est Story) :

 """{color:green}[${Parent.Issuetype}]{color} ${Parent.Summary}"""


Résultat 2 (s'il y a Epic Link) :

 """{color:#713A82}[${EpicLink.Issuetype}]{color} ${EpicLink.EpicName}"""


Les deux résultats ont une structure similaire : ils sont tous deux constitués de guillemets triples « » » au début et à la fin de la chaîne de sortie, de spécifications de couleur dans les blocs d'ouverture {color: COLOR} et de fermeture {color}, ainsi que d'opérations effectuées via le Symbole $. Les guillemets triples indiquent à la structure qu'à l'intérieur il y aura des variables, des opérations ou des blocs de formatage (tels que des couleurs).


Pour le résultat de la première condition, nous :

  1. Transférer le type de la tâche parent ${Parent.Issuetype}
  2. Mettez-le entre crochets «[…]»
  3. Mettez tout en surbrillance en vert, en enveloppant cette expression [${Parent.Issuetype}] dans le bloc de sélection de couleur {color:green}…{color}, où nous avons écrit « vert »
  4. Et une dernière chose, ajoutez le nom de la tâche parent séparé par un espace ${Parent.Summary}.


Ainsi, nous obtenons la chaîne « [Histoire] Un nom de tâche ». Comme vous l'avez peut-être deviné, Summary est également une variable standard. Pour rendre le schéma de construction de telles chaînes plus clair, permettez-moi de partager une image tirée de la documentation officielle.


Schéma de lignes personnalisé à partir de la documentation officielle


De la même manière, nous collectons la chaîne pour le deuxième résultat, mais définissons la couleur via le code hexadécimal. J'ai compris que la couleur de l'Epic était « #713A82 » (dans les commentaires, d'ailleurs, vous pouvez suggérer une couleur plus précise pour Epic). N'oubliez pas les champs (propriétés) qui changent pour Epic. Au lieu de « Résumé », utilisez « EpicName », au lieu de « Parent », utilisez « EpicLink ».


En conséquence, le schéma de notre formule peut être représenté comme un tableau de conditions.


Condition : la tâche parent existe et son type est Story.

Résultat : Ligne avec le type vert de la tâche parent et son nom.

Condition : Le champ Epic Link est rempli.

Résultat : Ligne avec la couleur épique du type et son nom.


Par défaut, l'option d'affichage « Général » est sélectionnée dans le champ, et si vous ne la modifiez pas, le résultat ressemblera à du texte brut sans changer la couleur ni identifier les blocs. Si vous changez le format d'affichage en « Wiki Markup », le texte sera transformé.


1) Affichage du général - par défaut, il affiche le texte brut tel quel 2) Remplacer le général par le balisage Wiki



Faisons maintenant connaissance avec les variables qui ne sont pas liées aux champs Jira : les variables locales.


Calculer le nombre de Story points avec indication de couleur

Les sommes des points d'histoire sont mises en évidence avec différentes couleurs


Problème

Grâce à l'exemple précédent, vous avez appris que nous travaillons avec des tâches de type Story, qui comportent des sous-tâches. Cela donne lieu à un cas particulier avec les estimations. Pour obtenir un score Story, nous résumons les scores de ses sous-tâches, qui sont estimés en Story points abstraits.


L'approche est inhabituelle, mais elle fonctionne pour nous. Ainsi, lorsque Story n'a pas d'estimation, mais que les sous-tâches en ont, il n'y a pas de problème, mais lorsque Story et les sous-tâches ont une estimation, l'option standard de Structure, « Σ Story Points », ne fonctionne pas correctement.


En effet, l'estimation de Story s'ajoute à la somme des sous-tâches. En conséquence, un mauvais montant est affiché dans Story. Nous aimerions éviter cela et ajouter une indication d'incohérence avec l'estimation établie dans Story et la somme des sous-tâches.


Solution proposée

Nous avons besoin de plusieurs conditions, car tout dépend si l'estimation est définie dans Story.


Les conditions sont donc :


Lorsque Story n'a pas d'estimation , nous affichons l'estimation de la somme des sous-tâches en orange pour indiquer que cette valeur n'a pas encore été définie dans Story.


Si Story a une estimation , alors vérifiez si elle correspond à l'estimation de la somme des sous-tâches :

  • Si cela ne correspond pas, coloriez une estimation en rouge et écrivez le montant correct à côté entre parenthèses.
  • Si une estimation et la somme correspondent, écrivez simplement une estimation en vert


La formulation de ces conditions peut prêter à confusion, exprimons-les donc sous forme de schéma.


Algorithme de choix de l'option d'affichage du texte


Caractéristiques structurelles utilisées

  1. Mappage de variables
  2. Variables locales
  3. Méthodes d'agrégation
  4. Conditions
  5. Texte avec mise en forme


Un exemple de code

 with isEstimated = storypoints != undefined: with childrenSum = sum#children{storypoints}: with isStory = issueType = "Story": with isErr = isStory AND childrenSum != storypoints: with color = if isStory : if isEstimated : if isErr : "red" else "green" else "orange": if isEstimated : """{color:$color}$storypoints{color} ${if isErr :""" ($childrenSum)"""}""" else """{color:$color}$childrenSum{color}"""


Une analyse de la solution

Avant de plonger dans le code, transformons notre schéma en une manière plus « semblable à du code » pour comprendre de quelles variables nous avons besoin.


Le même algorithme réécrit avec des variables


À partir de ce schéma, nous voyons que nous aurons besoin de :


Variables de condition :

  • isEstimated (disponibilité de l'estimation)
  • isError (correspondance de l'estimation de l'histoire et de la somme)


Une variable de couleur du texte – couleur


Deux variables d'estimation :

  • sum (la somme de l'estimation des sous-tâches)
  • sp (Points d'histoire)


De plus, la variable de couleur dépend également d'un certain nombre de conditions, par exemple de la disponibilité d'un devis et du type de tâche dans la ligne (voir le schéma ci-dessous).


Algorithme de choix des couleurs


Ainsi, pour déterminer la couleur, nous aurons besoin d'une autre variable de condition, isStory, qui indique si le type de tâche est Story.


La variable sp (storypoints) sera standard, ce qui signifie qu'elle sera automatiquement mappée au champ Jira approprié. Nous devons définir le reste des variables par nous-mêmes et elles seront locales pour nous.


Essayons maintenant d'implémenter les schémas dans le code. Tout d’abord, définissons toutes les variables.


 with isEstimated = storypoints != undefined: with childrenSum = sum#children{storypoints}: with isStory = issueType = "Story": with isErr = isStory AND childrenSum != storypoints:


Les lignes sont unies par le même schéma syntaxique : le mot-clé with, le nom de la variable et le symbole deux-points « : » à la fin de la ligne.


Syntaxe pour la déclaration des variables locales


Le mot-clé with est utilisé pour désigner les variables locales (et les fonctions personnalisées, mais plus à ce sujet dans un exemple séparé). Il indique à la formule qui va ensuite une variable qui n'a pas besoin d'être mappée. Les deux points « : » marquent la fin de la définition de la variable.


Ainsi, nous créons la variable isEstimated (rappel, ce cas n'est pas important). Nous y stockerons soit 1, soit 0, selon que le champ story points est rempli ou non. La variable storypoints est mappée automatiquement car nous n'avons jamais créé de variable locale du même nom auparavant (par exemple, avec storypoints = … :).


La variable non définie dénote la non-existence de quelque chose (comme null, NaN et autres dans d'autres langues). Ainsi, l’expression storypoints != undefined peut être lue comme une question : « Le champ story points est-il rempli ?


Ensuite, nous devons déterminer la somme des points d’histoire de toutes les tâches enfants. Pour ce faire, nous créons une variable locale : childrenSum.


 with childrenSum = sum#children{storypoints}:


Cette somme est calculée via la fonction d'agrégation. (Vous pouvez en savoir plus sur des fonctions comme celle-ci dans la documentation officielle .) En un mot, Structure peut effectuer diverses opérations avec des tâches, en tenant compte de la hiérarchie de la vue actuelle.


Nous utilisons la fonction somme, et en plus, en utilisant le symbole « # », nous transmettons la clarification aux enfants, ce qui limite le calcul de la somme uniquement à toutes les tâches enfants de la ligne actuelle. Entre accolades, nous indiquons quel champ nous voulons résumer — nous avons besoin d'une estimation en storypoints.


La variable locale suivante, isStory, stocke une condition : si le type de tâche dans la ligne actuelle est une Story.


 with isStory = issueType = "Story":


Nous nous tournons vers la variable issueType, familière de l'exemple précédent, c'est-à-dire le type de tâche qui correspond au champ souhaité par lui-même. Nous faisons cela parce qu'il s'agit d'une variable standard et que nous ne l'avons pas définie auparavant avec.


Définissons maintenant la variable isErr — elle signale un écart entre la somme de la sous-tâche et l'estimation de l'histoire.


 with isErr = isStory AND childrenSum != storypoints:


Ici, nous utilisons les variables locales isStory et childrenSum que nous avons créées précédemment. Pour signaler une erreur, nous avons besoin que deux conditions soient remplies simultanément : le type de problème est Story (isStory) et (ET) la somme des points enfants (childrenSum) n'est pas égale (!=) à l'estimation définie dans la tâche (storypoints ). Tout comme en JQL, nous pouvons utiliser des mots de lien lors de la création de conditions, comme AND ou OR.


Notez que pour chacune des variables locales, il y a un symbole « : » à la fin de la ligne. Cela devrait être à la fin, après toutes les opérations qui définissent la variable. Par exemple, si nous devons diviser la définition d'une variable en plusieurs lignes, alors les deux points « : » ne sont placés qu'après la dernière opération. Comme dans l'exemple avec la variable color - la couleur du texte.


 with color = if isStory : if isEstimated : if isErr : "red" else "green" else "orange":


Ici, nous voyons beaucoup de « : », mais ils jouent des rôles différents. Les deux points après if isStory sont le résultat de la condition isStory. Rappelons la construction : si condition : résultat. Présentons cette construction sous une forme plus complexe, qui définit une variable.


 with variable = (if condition: (if condition2 : result2 else result3) ):


Il s'avère que si condition2 : résultat2 sinon résultat3 est en quelque sorte le résultat de la première condition, et à la toute fin il y a deux points « : », qui complète la définition de la variable.


À première vue, la définition de la couleur peut sembler compliquée, même si, en fait, nous avons décrit ici le schéma de définition des couleurs présenté au début de l'exemple. C'est juste qu'à la suite de la première condition, une autre condition commence – une condition imbriquée et une autre dans celle-ci.


Mais le résultat final est légèrement différent du schéma présenté précédemment.


 if isEstimated : """{color:$color}$storypoints{color} ${if isErr :""" ($childrenSum)"""}""" else """{color:$color}$childrenSum{color}"""


Nous n'avons pas besoin d'écrire « {color}$sp » deux fois dans le code, comme c'était le cas dans le schéma ; nous serons plus intelligents dans les choses. Dans la branche, si la tâche a une estimation, nous afficherons toujours {color: $color}$storypoints{color} (c'est-à-dire juste une estimation en story points dans la couleur nécessaire), et s'il y a une erreur, alors après un espace, nous compléterons la ligne avec la somme des sous-tâches estimées : ($childrenSum).


S'il n'y a pas d'erreur, il ne sera pas ajouté. J'attire également votre attention sur le fait qu'il n'y a pas de symbole « : », puisque nous ne définissons pas de variable, mais affichons le résultat final à travers une condition.


Nous pouvons évaluer notre travail dans l'image ci-dessous dans le champ « ∑SP (mod) ». La capture d'écran montre spécifiquement deux champs supplémentaires :


  • « Story Points » — une estimation en story points (champ Jira standard).
  • « ∑ Story Points » – un champ personnalisé standard de structure, qui calcule le montant de manière incorrecte.


Vue finale du champ et comparaison avec les champs Story Points standards et ∑ Story Points


À l'aide de ces exemples, nous avons analysé les principales fonctionnalités du langage structuré qui vous aideront à résoudre la plupart des problèmes. Examinons maintenant deux autres fonctionnalités utiles, nos fonctions et nos tableaux. Nous verrons comment créer notre propre fonction personnalisée.


Derniers changements

Faites attention à l'emoji à gauche – il représente un champ personnalisé


Problème

Parfois, il y a de nombreuses tâches dans un sprint et nous pouvons manquer de petits changements. Par exemple, nous pouvons manquer une nouvelle sous-tâche ou le fait que l'une des histoires est passée à l'étape suivante. Ce serait bien d'avoir un outil nous informant des derniers changements importants dans les tâches.


Solution proposée

Nous nous intéressons à trois types de changements de statut de tâche survenus depuis hier : nous avons commencé à travailler sur la tâche, une nouvelle tâche est apparue, la tâche est fermée. De plus, il sera utile de voir que la tâche est clôturée avec la résolution « Won't Do ».


Pour ce faire, nous allons créer un champ avec une chaîne d’émojis responsables des dernières modifications. Par exemple, si une tâche a été créée hier et que nous avons commencé à travailler dessus, alors elle sera marquée de deux emojis : « En cours » et « Nouvelle tâche ».


Pourquoi avons-nous besoin d'un tel champ personnalisé, si plusieurs champs supplémentaires peuvent être affichés, par exemple la date de passage au statut « En cours » ou un champ « Résolution » distinct ? La réponse est simple : les gens perçoivent les emojis plus facilement et plus rapidement que le texte, qui se trouve dans différents champs et doit être analysé. La formule rassemblera tout en un seul endroit et l'analysera pour nous, ce qui nous fera gagner du temps et des efforts pour des choses plus utiles.


Déterminons de quoi les différents emoji seront responsables :

  • *️⃣ est la manière la plus courante de marquer une nouvelle tâche
  • ✅ marque une tâche terminée
  • ❌ indique une tâche que vous avez décidé d'annuler (« Won't Do »)
  • 🚀 signifie que nous avons décidé de commencer à travailler sur la tâche (cet emoji convient à notre équipe, il peut être différent pour vous)


Caractéristiques structurelles utilisées

  1. Mappage de variables
  2. Méthodes du langage expr
  3. Variables locales
  4. Conditions
  5. Notre propre fonction


Un exemple de code

 if defined(issueType): with now = now(): with daysScope = 1.3: with workDaysBetween(today, from)= ( with weekends = (Weeknum(today) - Weeknum(from)) * 2: HOURS_BETWEEN(from;today)/24 - weekends ): with daysAfterCreated = workDaysBetween(now,created): with daysAfterStart = workDaysBetween(now,latestTransitionToProgress): with daysAfterDone = workDaysBetween(now, resolutionDate): with isWontDo = resolution = "Won't Do": with isRecentCreated = daysAfterCreated >= 0 and daysAfterCreated <= daysScope and not(resolution): with isRecentWork = daysAfterStart >= 0 and daysAfterStart <= daysScope : with isRecentDone = daysAfterDone >= 0 and daysAfterDone <= daysScope : concat( if isRecentCreated : "*️⃣", if isRecentWork : "🚀", if isRecentDone : "✅", if isWontDo : "❌")

Une analyse de la solution


Pour commencer, réfléchissons aux variables globales dont nous avons besoin pour déterminer les événements qui nous intéressent. Il faut savoir, si depuis hier :

  • La tâche a été créée
  • Le statut est passé à « En cours »
  • Une résolution a été trouvée (et laquelle)


L'utilisation de variables déjà existantes aux côtés de nouvelles variables de mappage nous aidera à vérifier toutes ces conditions.

  • créé - la date de création de la tâche
  • lastTransitionToProgress — la dernière date de transition vers le statut « En cours » (nous la mappons comme dans l'exemple précédent)
  • résolutionDate - la date d'achèvement de la tâche
  • résolution — texte de la résolution


Passons au code. La première ligne commence par une condition qui vérifie si le type de tâche existe.


 if defined(issueType):


Cela se fait via la fonction définie intégrée, qui vérifie l'existence du champ spécifié. Le contrôle est effectué pour optimiser le calcul de la formule.


Nous ne chargerons pas Structure avec des calculs inutiles, si la ligne n'est pas une tâche. Il s'avère que tout le code après if est le résultat, je veux dire, la deuxième partie de la construction if (condition : result). Et si la condition n’est pas remplie, le code ne fonctionnera pas non plus.


La ligne suivante avec now = now(): est également nécessaire pour optimiser les calculs. Plus loin dans le code, nous devrons comparer plusieurs fois différentes dates avec la date actuelle. Afin de ne pas faire plusieurs fois le même calcul, nous allons calculer cette date une seule fois et en faire maintenant une variable locale.


Ce serait aussi bien de garder notre « hier » séparément. Un « hier » pratique s'est transformé empiriquement en 1,3 jour. Faisons cela en variable : avec dayScope = 1.3 :.


Nous devons maintenant calculer plusieurs fois le nombre de jours entre deux dates. Par exemple, entre la date du jour et la date de début des travaux. Bien sûr, il existe une fonction DAYS_BETWEEN intégrée, qui semble nous convenir. Mais si la tâche, par exemple, a été créée vendredi, nous ne verrons pas d'avis de nouvelle tâche lundi, car en fait plus de 1,3 jours se sont écoulés. De plus, la fonction DAYS_BETWEEN ne compte que le nombre total de jours (c'est-à-dire que 0,5 jour se transformera en 0 jour), ce qui ne nous convient pas non plus.


Nous avons formulé une exigence : nous devons calculer le nombre exact de jours ouvrables entre ces dates ; et une fonction personnalisée nous y aidera.


Syntaxe pour la déclaration des fonctions locales


Sa syntaxe de définition est très similaire à la syntaxe de définition d'une variable locale. La seule différence et le seul ajout sont l'énumération facultative des arguments entre les premières parenthèses. Les secondes parenthèses contiennent les opérations qui seront effectuées lorsque notre fonction sera appelée. Cette définition de la fonction n'est pas la seule possible, mais nous utiliserons celle-ci (d'autres peuvent être trouvées dans la documentation officielle ).


 with workDaysBetween(today, from)= ( with weekends = (Weeknum(today) - Weeknum(from)) * 2: HOURS_BETWEEN(from;today)/24 - weekends ):


Notre fonction personnalisée workDaysBetween calculera les jours ouvrables entre aujourd'hui et les dates de début, qui sont passées en arguments. La logique de la fonction est très simple : on compte le nombre de jours de congé et on les soustrait du nombre total de jours entre les dates.


Pour calculer le nombre de jours de congé, nous devons savoir combien de semaines se sont écoulées entre aujourd'hui et à partir de. Pour ce faire, on calcule la différence entre les numéros de chacune des semaines. Nous obtiendrons ce numéro grâce à la fonction Weeknum, qui nous fournit le numéro de la semaine du début de l'année. En multipliant cette différence par deux, on obtient le nombre de jours de congé écoulés.


Ensuite, la fonction HOURS_BETWEEN compte le nombre d'heures entre nos dates. Nous divisons le résultat par 24 pour obtenir le nombre de jours, et soustrayons les jours de congé de ce nombre, afin d'obtenir les jours ouvrables entre les dates.


À l'aide de notre nouvelle fonction, définissons un ensemble de variables auxiliaires. Notez que certaines dates dans les définitions sont des variables globales, dont nous avons parlé au début de l'exemple.


 with daysAfterCreated = workDaysBetween(now,created): with daysAfterStart = workDaysBetween(now,latestTransitionToProgress): with daysAfterDone = workDaysBetween(now, resolutionDate):


Pour rendre le code facile à lire, définissons des variables qui stockent les résultats des conditions.


 with isWontDo = resolution = "Won't Do": with isRecentCreated = daysAfterCreated >= 0 and daysAfterCreated <= daysScope and not(resolution): with isRecentWork = daysAfterStart >= 0 and daysAfterStart <= daysScope : with isRecentDone = daysAfterDone >= 0 and daysAfterDone <= daysScope :


Pour la variable isRecentCreated, j'ai ajouté une condition facultative et not(resolution), ce qui m'aide à simplifier la future ligne, car si la tâche est déjà fermée, alors je ne suis pas intéressé par les informations sur sa création récente.


Le résultat final est construit via la fonction concat, concaténant les lignes.


 concat( if isRecentCreated : "*️⃣", if isRecentWork : "🚀", if isRecentDone : "✅", if isWontDo : "❌")


Il s'avère que l'emoji ne sera dans la ligne que lorsque la variable dans la condition est égale à 1. Ainsi, notre ligne peut afficher simultanément des modifications indépendantes de la tâche.


Vue finale de la colonne avec modifications (côté gauche)


Nous avons abordé le sujet du comptage des jours ouvrables sans jours de congé. Il existe un autre problème lié à cela, que nous analyserons dans notre dernier exemple tout en nous familiarisant avec les tableaux.


Calcul des heures de travail, hors jours fériés

Exemple d'affichage final


Problème

Parfois, nous souhaitons savoir depuis combien de temps une tâche est exécutée, sans compter les jours de congé. Cela est nécessaire, par exemple, pour analyser la version publiée. Comprendre pourquoi nous avons besoin de jours de congé. Sauf que l’un circulait du lundi au jeudi et l’autre du vendredi au lundi. Dans une telle situation, on ne peut pas affirmer que les tâches sont équivalentes, même si la différence en jours calendaires nous dit le contraire.


Malheureusement, la structure prête à l'emploi ne sait pas comment ignorer les jours de congé, et le champ avec l'option « Temps en statut… » produit un résultat quels que soient les paramètres Jira, même si samedi et dimanche sont spécifiés comme jours de congé.


De ce fait, notre objectif est de calculer le nombre exact de jours ouvrés, en ignorant les jours chômés, et de prendre en compte l'impact des transitions de statut sur ce temps.


Et qu’est-ce que les statuts ont à voir là-dedans ? Laissez-moi répondre. Supposons que nous ayons calculé qu'entre le 10 et le 20 mars, la tâche a été à l'œuvre pendant trois jours. Mais sur ces 3 jours, il a été en pause pendant un jour et en revue pendant un jour et demi. Il s'avère que la tâche n'a duré qu'une demi-journée.


La solution de l'exemple précédent ne nous convient pas en raison du problème de basculement entre les statuts, car la fonction personnalisée workDaysBetween ne prend en compte que le temps entre deux dates sélectionnées.


Solution proposée

Ce problème peut être résolu de différentes manières. La méthode de l'exemple est la plus coûteuse en termes de performances, mais la plus précise en termes de comptage des jours de congés et des statuts. A noter que son implémentation ne fonctionne que dans la version Structure antérieure à 7.4 (décembre 2021).


Ainsi, l’idée derrière la formule est la suivante :


  1. Nous devons savoir combien de jours se sont écoulés entre le début et la fin de la tâche.
  2. Nous en faisons un tableau, c'est-à-dire une liste de jours entre le début et la fin de notre travail sur la tâche.
  3. Ne garder que les jours de congé dans la liste


Filtrer uniquement les week-ends de toutes les dates (ils peuvent avoir des statuts différents)


  1. De ces jours fériés, nous ne conservons que ceux où la tâche était au statut « En cours » (la fonctionnalité de la version 7.4 « Valeur historique » nous aidera ici)


Suppression des statuts inutiles avec le statut "en cours" restant


  1. Maintenant dans la liste, nous n'avons que les jours de congé qui ont coïncidé avec la période « En cours »
  2. Séparément, nous découvrons la durée totale du statut « En cours » (via l'option de structure intégrée « Temps en statut… ») ;
  3. Soustraire de ce temps le nombre de jours de congés précédemment obtenus


Ainsi, nous obtiendrons l'heure exacte de travail sur la tâche, en ignorant les jours de congé et les transitions entre les statuts supplémentaires.


Caractéristiques structurelles utilisées

  1. Mappage de variables
  2. Méthodes du langage expr
  3. Variables locales
  4. Conditions
  5. Une méthode interne (notre propre fonction)
  6. Tableaux
  7. Accès à l'historique de la tâche
  8. Texte avec mise en forme


Un exemple de code

 if defined(issueType) : if status != "Open" : with finishDate = if toQA != Undefined : toQA else if toDone != Undefined : toDone else now(): with startDate = DEFAULT(toProgress, toDone): with statusWeekendsCount(dates, status) = ( dates.filter(x -> weekday(x) > 5 and historical_value(this,"status",x)=status).size() ): with overallDays = round(hours_between(startDate,finishDate)/24): with sequenceArray = SEQUENCE(0,overallDays): with datesArray = sequenceArray.map(DATE_ADD(startDate,$,"day")): with progressWeekends = statusWeekendsCount(datesArray, "in Progress"): with progressDays = (timeInProgress/86400000 - progressWeekends).round(1): with color = if( progressDays = 0 ; "gray" ; progressDays > 0 and progressDays <= 2.5; "green" ; progressDays > 2.5 and progressDays <= 4; "orange" ; progressDays > 4; "red" ): """{color:$color}$progressDays d{color}"""


Une analyse de la solution


Avant de transférer notre algorithme dans le code, facilitons les calculs pour Structure.


 if defined(issueType) : if status != "Open" :


Si la ligne n'est pas une tâche ou si son statut est « Ouvert », alors nous ignorerons ces lignes. Nous ne nous intéressons qu'aux tâches qui ont été lancées pour fonctionner.


Pour calculer le nombre de jours entre des dates, il faut d'abord déterminer ces dates : finishDate et startDate.


 with finishDate = if toQA != Undefined : toQA else if toDone != Undefined : toDone else now(): with startDate = DEFAULT(toProgress, toDone): 


Identifier les statuts qui signifient la fin logique du travail


Nous supposerons que la date d'achèvement de la tâche (finishDate) est :

  • Soit la date à laquelle la tâche a été transférée au statut « QA »
  • Soit la date de passage en « Fermé »
  • Ou si la tâche est toujours en « En cours », alors la date du jour (pour comprendre combien de temps s'est écoulé)


La date de début des travaux startDate est déterminée par la date de passage au statut « En cours ». Il existe des cas où la tâche est clôturée sans passer à l'étape en cours de travail. Dans de tels cas, nous considérons la date de clôture comme la date de début, le résultat est donc 0 jour.


Comme vous l'avez peut-être deviné, toQA, toDone et toProgress sont des variables qui doivent être mappées aux statuts appropriés comme dans le premier exemple et les exemples précédents.


Nous voyons également la nouvelle fonction DEFAULT(toProgress, toDone). Il vérifie si toProgress a une valeur, et sinon, il utilise la valeur de la variable toDone.


Vient ensuite la définition de la fonction personnalisée statusWeekendsCount, mais nous y reviendrons plus tard, car elle est étroitement liée aux listes de dates. Il est préférable d'aller directement à la définition de cette liste, pour que nous puissions comprendre plus tard comment lui appliquer notre fonction.


Nous souhaitons obtenir une liste de dates sous la forme suivante : [startDate (disons 11.03), 12.03, 13.03, 14.03… finishDate]. Il n’existe pas de fonction simple qui ferait tout le travail à notre place dans Structure. Alors recourons à une astuce :


  1. Nous allons créer une liste simple à partir d'une séquence de nombres allant de 0 au nombre de jours de travail, c'est-à-dire [0, 1, 2, 3… n jours de travail]
  2. Ajoutez la date de début de la tâche à chaque numéro (c'est-à-dire le jour). En conséquence, nous obtenons une liste (tableau) du type requis : [début + 0 jours, début + 1 jour, début + 2 jours… début + n jours de travail].


Création d'un tableau initial de dates de la date de début à la fin logique


Voyons maintenant comment nous pouvons l'implémenter dans le code. Nous travaillerons avec des tableaux.


 with overallDays = round(hours_between(startDate,finishDate)/24): with sequenceArray = SEQUENCE(0,overallDays): with datesArray = sequenceArray.map(DATE_ADD(startDate,$,"day")):


Nous comptons combien de jours le travail sur une tâche prendra. Comme dans l'exemple précédent, par division par 24 et par la fonction hours_between(startDate,finishDate). Le résultat est écrit dans la variable globalDays.


Nous créons un tableau de la séquence de nombres sous la forme de la variable séquenceArray. Ce tableau est construit via la fonction SEQUENCE(0,overallDays), qui crée simplement un tableau de la taille souhaitée avec une séquence de 0 à globalDays.


Vient ensuite la magie. L'une des fonctions du tableau est map. Il applique l'opération spécifiée à chaque élément du tableau.


Notre tâche est d'ajouter la date de début à chaque numéro (c'est-à-dire le numéro du jour). La fonction DATE_ADD peut faire cela, elle ajoute un certain nombre de jours, de mois ou d'années à la date spécifiée.


Sachant cela, décryptons la chaîne :


 with datesArray = sequenceArray.map(DATE_ADD(startDate, $,"day"))


À chaque élément du séquenceArray, la fonction .map() applique DATE_ADD(startDate, $, « day »).


Voyons ce qui est passé dans les arguments de DATE_ADD. La première chose est startDate, la date à laquelle le numéro souhaité sera ajouté. Ce nombre est spécifié par le deuxième argument, mais nous voyons $.


Le symbole $ désigne un élément du tableau. La structure comprend que la fonction DATE_ADD est appliquée à un tableau, et donc à la place de $ il y aura l'élément de tableau souhaité (c'est-à-dire 0, 1, 2…).


Le dernier argument « jour » indique que nous ajoutons un jour, puisque la fonction peut ajouter un jour, un mois et une année, en fonction de ce que nous spécifions.


Ainsi, la variable datesArray stockera un tableau de dates depuis le début des travaux jusqu'à leur fin.


Revenons à la fonction personnalisée que nous avons manquée. Il filtrera les jours supplémentaires et calculera le reste. Nous avons décrit cet algorithme au tout début de l'exemple, avant d'analyser le code, notamment dans les paragraphes 3 et 4 sur le filtrage des jours de congés et des statuts.


 with statusWeekendsCount(dates, status) = ( dates.filter(x -> weekday(x) > 5 and historical_value(this,"status",x)=status).size() ):


Nous allons transmettre deux arguments à la fonction personnalisée : un tableau de dates, appelons-le dates, et le statut requis : statut. Nous appliquons la fonction .filter() au tableau de dates transférées, qui conserve uniquement les enregistrements du tableau qui ont passé par la condition de filtre. Dans notre cas, il y en a deux, et ils se combinent par et. Après le filtre, nous voyons .size(), il renvoie la taille du tableau une fois toutes les opérations effectuées.


Si nous simplifions l'expression, nous obtenons quelque chose comme ceci : array.filter(condition1 and condition2).size(). Ainsi, nous avons obtenu le nombre de jours de congé qui nous convenait, c'est-à-dire les jours de congé qui remplissaient les conditions.


Examinons de plus près les deux conditions :


 x -> weekday(x) > 5 and historical_value(this,"status",x)=status


L'expression x -> fait simplement partie de la syntaxe du filtre, indiquant que nous appellerons l'élément du tableau x . Par conséquent, x apparaît dans chaque condition (de la même manière que c'était le cas avec $). Il s'avère que x correspond à chaque date du tableau de dates transféré.


La première condition, weekday(x) > 5, requiert que le jour de la semaine de la date x (c'est-à-dire chaque élément) soit supérieur à 5 : il s'agit soit du samedi (6), soit du dimanche (7).


La deuxième condition utilise historic_value.


 historical_value(this,"status",x) = status


C'est une fonctionnalité de Structure de la version 7.4.


La fonction accède à l'historique de la tâche et recherche une date précise dans le champ spécifié. Dans ce cas, nous recherchons la date x dans le champ « statut ». La variable this n'est qu'une partie de la syntaxe de la fonction, elle est mappée automatiquement et représente la tâche en cours dans la ligne.


Ainsi, dans la condition, nous comparons l'argument de statut transféré et le champ « status », qui est renvoyé par la fonction historic_value pour chaque date x du tableau. S'ils correspondent, l'entrée reste dans la liste.


La touche finale est l'utilisation de notre fonction pour compter le nombre de jours dans le statut souhaité :


 with progressWeekends = statusWeekendsCount(datesArray, "in Progress"): with progressDays = (timeInProgress/86400000 - progressWeekends).round(1):


Tout d'abord, découvrons combien de jours de congé avec le statut « en cours » ont eu dans notre datesArray. Autrement dit, nous transmettons notre liste de dates et le statut souhaité à la fonction personnalisée statusWeekendsCount. La fonction supprime tous les jours de la semaine et tous les jours chômés dont le statut de la tâche diffère du statut « en cours » et renvoie le nombre de jours restants dans la liste.


Ensuite, nous soustrayons ce montant de la variable timeInProgress, que nous mappons via l'option « Time in status… ».


Le nombre 86400000 est le diviseur qui transformera les millisecondes en jours. La fonction .round(1) est nécessaire pour arrondir le résultat aux dixièmes, par exemple à « 4.1 », sinon vous pouvez obtenir ce type d'entrée : « 4.0999999… ».


Pour indiquer la durée de la tâche, nous introduisons la variable couleur. Nous le modifierons en fonction du nombre de jours passés sur la tâche.


  • Gris — 0 jours
  • Vert — plus de 0 mais moins de 2,5 jours
  • Rouge — de 2,5 à 4 jours
  • Rouge — plus de 4 jours


 with color = if( progressDays = 0 ; "gray" ; progressDays > 0 and progressDays <= 2.5; "green" ; progressDays > 2.5 and progressDays <= 4; "orange" ; progressDays > 4; "red" ):


Et la dernière ligne avec le résultat des jours calculés :


 """{color:$color}$progressDays d{color}"""


Notre résultat ressemblera à l’image ci-dessous.


Vue finale du champ Temps de travail


À propos, dans la même formule, vous pouvez afficher l'heure de n'importe quel statut. Si, par exemple, nous transmettons le statut « Pause » à notre fonction personnalisée et mappons la variable timeInProgress via « Time in… — Pause », alors nous calculerons l'heure exacte de la pause.


Vous pouvez combiner les statuts et faire une entrée comme « wip : 3.2d | rev: 12d", c'est-à-dire calculer le temps de travail et le temps de révision. Vous n'êtes limité que par votre imagination et votre flux de travail.


Conclusion

Nous avons présenté un nombre exhaustif de fonctionnalités de ce langage de formule qui vous aideront à faire quelque chose de similaire ou à écrire quelque chose de complètement nouveau et intéressant pour analyser les tâches Jira.


J'espère que l'article vous a aidé à comprendre les formules, ou du moins vous a intéressé à ce sujet. Je ne prétends pas avoir « le meilleur code et le meilleur algorithme », donc si vous avez des idées sur la façon d'améliorer les exemples, je serais heureux si vous les partagez !


Bien sûr, vous devez comprendre que personne ne vous parlera mieux des formules que les développeurs d'ALM Works. Par conséquent, je joins des liens vers leur documentation et leurs webinaires. Et si vous commencez à travailler avec des champs personnalisés, consultez-les souvent pour voir quelles autres fonctionnalités vous pouvez utiliser.