paint-brush
Zeit und Nerven sparen mit Formeln und dem Structure Jira Pluginvon@ipolubentcev
944 Lesungen
944 Lesungen

Zeit und Nerven sparen mit Formeln und dem Structure Jira Plugin

von Ivan Polubentsev36m2023/10/29
Read on Terminal Reader

Zu lang; Lesen

Formeln mit dem Jira Structure-Plugin können umwerfend sein: Bringen Sie Ihr Spiel voran, indem Sie Tabellen erstellen, die Arbeit mit Aufgaben vereinfachen und Releases und Projekte analysieren.
featured image - Zeit und Nerven sparen mit Formeln und dem Structure Jira Plugin
Ivan Polubentsev HackerNoon profile picture
0-item
1-item

Das Struktur-Plugin für Jira ist sehr nützlich für die tägliche Arbeit mit Aufgaben und deren Analyse; Es bringt die Visualisierung und Strukturierung von Jira-Tickets auf ein neues Niveau und das alles sofort einsatzbereit.


Und nicht jeder weiß es, aber die Funktionalität von Strukturformeln kann Sie einfach umhauen. Mithilfe von Formeln können Sie äußerst nützliche Tabellen erstellen, die die Arbeit mit Aufgaben erheblich vereinfachen können, und vor allem sind sie nützlich, um eine tiefergehende Analyse von Releases, Epics und Projekten durchzuführen.


Wie wäre es mit der Anzeige eines Burndown-Diagramms oder der Anzeige des Zustands eines Tickets in einer Tabelle mit Aufgaben?


In diesem Artikel erfahren Sie, wie Sie Ihre eigenen Formeln erstellen, angefangen bei den einfachsten Beispielen bis hin zu komplexen, aber durchaus nützlichen Fällen.


Also, für wen ist dieser Text? Man könnte sich fragen, warum einen Artikel schreiben, wenn die offizielle Dokumentation auf der ALM Works-Website direkt darauf wartet, von den Lesern gelesen zu werden. Das stimmt. Allerdings gehöre ich zu den Leuten, die nicht einmal die geringste Ahnung hatten, dass Structure eine so umfangreiche Funktionalität verbirgt: „Moment, das war die ganze Zeit eine Option?!“ Diese Erkenntnis brachte mich zum Nachdenken: Vielleicht gibt es auch andere Leute, die noch nicht wissen, was sie mit Formeln und Strukturen machen können.


Dieser Artikel wird auch für diejenigen nützlich sein, die bereits mit Formeln vertraut sind. Sie lernen einige interessante praktische Möglichkeiten zur Verwendung benutzerdefinierter Felder kennen und können einige davon vielleicht für Ihre Projekte ausleihen. Wenn Sie übrigens selbst interessante Beispiele haben, freue ich mich, wenn Sie diese in den Kommentaren teilen .


Jedes Beispiel wird im Detail analysiert, von der Beschreibung des Problems bis zur Erklärung des Codes, und zwar so gründlich, dass keine Fragen mehr offen bleiben. Natürlich wird jedes Beispiel neben den Erläuterungen auch durch Code illustriert, den Sie selbst ausprobieren können, ohne sich in die Analyse zu vertiefen.


Wenn Sie keine Lust zum Lesen haben, sich aber für Formeln interessieren, schauen Sie sich die ALM Works-Webinare an. Diese erklären die Grundlagen in 40 Minuten; die Informationen werden dort sehr komprimiert dargestellt.


Um die Beispiele zu verstehen, sind keine zusätzlichen Kenntnisse erforderlich, sodass jeder, der mit Jira und Structure gearbeitet hat, die Beispiele problemlos in seinen Tabellen wiederholen kann.


Die Entwickler haben mit ihrer Expr-Sprache eine recht flexible Syntax bereitgestellt. Im Grunde gilt hier die Philosophie: „Schreiben Sie, wie Sie wollen, und es wird funktionieren.“


Also lasst uns anfangen!


Warum brauchen wir Formeln?

Warum sollten wir also überhaupt Formeln verwenden wollen? Nun, manchmal stellt sich heraus, dass wir nicht genügend Standard-Jira-Felder wie „Beauftragter“, „Story Points“ usw. haben. Oder wir müssen einen Betrag für bestimmte Felder berechnen, die verbleibende Kapazität nach Version anzeigen und herausfinden, wie oft die Aufgabe ihren Status geändert hat. Vielleicht möchten wir sogar mehrere Felder zu einem zusammenfassen, um unsere Struktur leichter lesbar zu machen.


Um diese Probleme zu lösen, benötigen wir Formeln, mit denen wir benutzerdefinierte Felder erstellen.


Als Erstes müssen wir verstehen, wie eine Formel funktioniert. Es ermöglicht uns, eine Operation auf eine Zeichenfolge anzuwenden. Da wir viele Aufgaben in die Struktur hochladen, wird die Formel auf jede Zeile der gesamten Tabelle angewendet. Normalerweise zielen alle seine Operationen auf die Bearbeitung von Aufgaben in diesen Bereichen ab.


Wenn wir also die Formel bitten, ein Jira-Feld anzuzeigen, zum Beispiel „Beauftragter“, dann wird die Formel auf jede Aufgabe angewendet und wir erhalten eine weitere Spalte „Beauftragter“.


Formeln bestehen aus mehreren Grundeinheiten:

  • Variablen – für den Zugriff auf Jira-Felder und das Speichern von Zwischenergebnissen
  • Integrierte Funktionen – diese führen einen vordefinierten Vorgang aus, zählen beispielsweise die Anzahl der Stunden zwischen Datumsangaben oder filtern Daten in einem Array
  • Benutzerdefinierte Funktionen – wenn wir einzigartige Berechnungen benötigen
  • Verschiedene Formen der Ergebnisanzeige, zum Beispiel „Datum/Uhrzeit“, „Dauer“, „Zahl“ oder „Wiki-Markup“ für Ihre Option.


Formeln kennenlernen

Anhand einiger Beispiele machen wir uns mit Formeln und deren Syntax vertraut und gehen sechs Praxisfälle durch.


Bevor wir uns die einzelnen Beispiele ansehen, geben wir an, welche Strukturfunktionen wir verwenden. Neue Funktionen, die noch nicht erklärt wurden, werden fett gedruckt. Jedes der folgenden Beispiele weist einen zunehmenden Komplexitätsgrad auf. Sie sind so angeordnet, dass Sie nach und nach an die wichtigen Formelfunktionen herangeführt werden.


Hier ist die Grundstruktur, die Sie jedes Mal sehen werden:

  • Das Problem
  • Die vorgeschlagene Lösung
  • Die verwendeten Strukturfunktionen
  • Ein Codebeispiel
  • Eine Analyse der Lösung


Diese Beispiele decken Themen ab, die von der Variablenzuordnung bis hin zu komplexen Arrays reichen:

  • Zwei Beispiele für die Anzeige des Start- und Enddatums der Arbeit an einer Aufgabe (Optionen mit unterschiedlicher Anzeige)
  • Eine übergeordnete Aufgabe – Anzeige des Typs und Namens der übergeordneten Aufgabe
  • Die Summe der Story Points der Teilaufgaben und der Status dieser Bewertungen
  • Ein Hinweis auf aktuelle Änderungen im Aufgabenstatus
  • Eine Berechnung der Arbeitszeit, ohne freie Tage (Wochenenden) und Sonderstatus


Formeln erstellen

Lassen Sie uns zunächst herausfinden, wie Sie benutzerdefinierte Felder mit Formeln erstellen. Im oberen rechten Teil der Struktur, am Ende aller Spalten, befindet sich ein „+“-Symbol – klicken Sie darauf. Schreiben Sie in das angezeigte Feld „Formel …“ und wählen Sie das entsprechende Element aus.


Formeln erstellen


Formeln speichern

Lassen Sie uns über das Speichern einer Formel sprechen. Leider ist es immer noch nicht möglich, eine bestimmte Formel separat irgendwo zu speichern (nur im Notizbuch, wie ich es tue). Beim ALM Works-Webinar erwähnte das Team, dass es an einer Datenbank mit Formeln arbeitet, die einzige Möglichkeit, diese zu speichern, besteht jedoch derzeit darin, die gesamte Ansicht zusammen mit der Formel zu speichern.


Wenn wir mit der Arbeit an einer Formel fertig sind, müssen wir auf die Ansicht unserer Struktur klicken (sie wird höchstwahrscheinlich mit einem blauen Sternchen markiert) und auf „Speichern“ klicken, um die aktuelle Ansicht zu überschreiben. Oder Sie können auf „Speichern unter…“ klicken, um eine neue Ansicht zu erstellen. (Vergessen Sie nicht, es anderen Jira-Benutzern zur Verfügung zu stellen, da neue Ansichten standardmäßig privat sind.)


Die Formel wird in den übrigen Feldern einer bestimmten Ansicht gespeichert und Sie können sie auf der Registerkarte „Erweitert“ des Menüs „Details anzeigen“ anzeigen.


Ab Version 8.2 bietet Structure nun die Möglichkeit, Formeln mit drei schnellen Klicks zu speichern.

Der Speicherdialog ist im Formelbearbeitungsfenster verfügbar. Wenn dieses Fenster nicht geöffnet ist, klicken Sie einfach auf das Dreiecksymbol ▼ in der gewünschten Spalte.


Formeln speichern


Im Bearbeitungsfenster sehen wir das Feld „Gespeicherte Spalte“, rechts befindet sich ein Symbol mit einer blauen Benachrichtigung, was bedeutet, dass die Änderungen in der Formel nicht gespeichert wurden. Klicken Sie auf dieses Symbol und wählen Sie die Option „Speichern unter…“.


Gespeicherte Spalte


Geben Sie dann Namen für unsere Spalte (Formel) ein und wählen Sie aus, wo sie gespeichert werden soll. „Meine Spalten“, wenn wir es in einer persönlichen Liste speichern möchten. „Global“, damit die Formel in der allgemeinen Liste gespeichert wird, wo sie von allen Benutzern Ihrer Struktur bearbeitet werden kann. Klicken Sie auf „Speichern“.


Klicken Sie auf Speichern


Jetzt ist unsere Formel gespeichert. Wir können es in jede beliebige Struktur laden oder von überall aus erneut speichern. Durch erneutes Speichern der Formel wird diese in allen Strukturen aktualisiert, in denen sie verwendet wird.


Die Variablenzuordnung wird ebenfalls mit der Formel gespeichert, über die Zuordnung sprechen wir jedoch später.


Kommen wir nun zu unseren Beispielen!


Anzeige des Start- und Enddatums der Arbeit an einer Aufgabe

Benutzerdefinierte Daten in den letzten beiden Spalten

Problem

Wir benötigen eine Tabelle mit einer Aufgabenliste sowie den Start- und Enddaten für die Bearbeitung dieser Aufgaben. Wir benötigen die Tabelle auch, um sie in ein separates Excel-Gantt zu exportieren. Leider wissen Jira und Structure nicht, wie man solche Daten sofort bereitstellen kann.

Vorgeschlagene Lösung

Das Start- und Enddatum sind die Daten des Übergangs zu bestimmten Status, in unserem Fall „In Bearbeitung“ und „Geschlossen“. Wir müssen diese Daten nehmen und sie jeweils in einem separaten Feld anzeigen (dies ist für den weiteren Export nach Gantt erforderlich). Wir haben also zwei Felder (zwei Formeln).


Die verwendeten Strukturfunktionen

  1. Variablenzuordnung
  2. Die Möglichkeit, das Anzeigeformat anzupassen


Ein Codebeispiel

Feld für das Startdatum:

 firstTransitionToStart


Feld für das Enddatum:

 latestTransitionToDone


Eine Analyse der Lösung

In diesem Fall ist der Code eine einzelne Variable, „firstTransitionToStart“ für das Startdatumsfeld und „laestTransitionToDone“ für das zweite Feld.


Konzentrieren wir uns zunächst auf das Feld „Startdatum“. Unser Ziel ist es, das Datum zu ermitteln, an dem die Aufgabe in den Status „In Bearbeitung“ übergegangen ist (dies entspricht dem logischen Beginn der Aufgabe). Deshalb wird die Variable ganz explizit als „erster Übergang zu“ benannt, um späteres Raten zu vermeiden Start".


Um ein Datum in eine Variable umzuwandeln, wenden wir uns der Variablenzuordnung zu. Speichern wir unsere Formel, indem wir auf die Schaltfläche „Speichern“ klicken.


Klicken Sie, um die Formel zu speichern


Unsere Variable erschien im Abschnitt „Variablen“ mit einem Ausrufezeichen daneben. Die Struktur weist darauf hin, dass eine Variable nicht mit einem Feld in Jira verknüpft werden kann und wir dies selbst tun (dh zuordnen müssen).


Klicken Sie auf die Variable und gehen Sie zur Mapping-Oberfläche. Wählen Sie das Feld oder den benötigten Vorgang aus – suchen Sie nach dem Vorgang „Übergangsdatum …“. Geben Sie dazu „Übergang“ in das Auswahlfeld ein. Ihnen werden mehrere Optionen gleichzeitig angeboten, und eine davon passt zu uns: „Erster Übergang zu In Bearbeitung“. Um aber zu veranschaulichen, wie die Zuordnung funktioniert, wählen wir die Option „Übergangsdatum …“.


Mapping-Konfiguration


Danach müssen Sie den Status auswählen, in dem der Übergang stattgefunden hat, und die Reihenfolge dieses Übergangs – den ersten oder den letzten.


Wählen oder geben Sie bei „Status“ „Status: In Bearbeitung“ (oder den entsprechenden Status in Ihrem Workflow) und bei „Übergang“ „Erster Übergang zum Status“ ein, da der Beginn der Arbeit an einer Aufgabe der allererste Übergang ist auf den entsprechenden Status.


Wählen Sie die gewünschte Kategorie aus



Wenn wir anstelle von „Übergangsdatum…“ die ursprünglich vorgeschlagene Option „Erster Übergang zu In Bearbeitung“ wählen würden, wäre das Ergebnis fast das gleiche – Structure würde die notwendigen Parameter für uns auswählen. Das Einzige ist, dass wir statt „Status: In Bearbeitung“ „Kategorie: In Bearbeitung“ haben würden.


Unterschied zwischen Statuskategorie und Status


Lassen Sie mich auf ein wichtiges Merkmal hinweisen: Ein Status und eine Kategorie sind zwei verschiedene Dinge. Ein Status ist ein spezifischer Status, er ist eindeutig, aber eine Kategorie kann mehrere Status umfassen. Es gibt nur drei Kategorien: „To Do“, „In Progress“ und „Done“. In Jira sind sie normalerweise mit den Farben Grau, Blau und Grün gekennzeichnet. Der Status muss zu einer dieser Kategorien gehören.

Ich empfehle in solchen Fällen die Angabe eines bestimmten Status, um Verwechslungen mit Status derselben Kategorie zu vermeiden. Beispielsweise haben wir zwei Status der Kategorie „Zu erledigen“ für das Projekt: „Offen“ und „QA-Warteschlange“.


Kehren wir zu unserem Beispiel zurück.


Sobald wir die erforderlichen Optionen ausgewählt haben, können wir auf „< Zurück zur Variablenliste“ klicken, um die Zuordnungsoptionen für die Variable firstTransitionToStart abzuschließen. Wenn wir alles richtig machen, sehen wir ein grünes Häkchen.


Allgemein zeigt den Standardwert (in Millisekunden)


Gleichzeitig sehen wir in unserem benutzerdefinierten Feld einige seltsame Zahlen, die überhaupt nicht wie ein Datum aussehen. In unserem Fall ist das Ergebnis der Formel der Wert der Variable firstTransitionToStart, und sein Wert beträgt Millisekunden seit Januar 1970. Um das richtige Datum zu erhalten, müssen wir ein bestimmtes Formelanzeigeformat auswählen.


Die Formatauswahl befindet sich ganz unten im Bearbeitungsfenster. Standardmäßig ist dort „Allgemein“ ausgewählt. Wir benötigen „Datum/Uhrzeit“, um das Datum korrekt anzuzeigen.


Wählen Sie Datum/Uhrzeit anstelle von Allgemein


Für das zweite Feld „laestTransitionToDone“ machen wir dasselbe. Der einzige Unterschied besteht darin, dass wir beim Mapping bereits die Kategorie „Fertig“ auswählen können und nicht den Status (da es normalerweise nur einen eindeutigen Aufgabenerledigungsstatus gibt). Als Übergangsparameter wählen wir „Latest Transition“, da uns der jüngste Übergang zur Kategorie „Done“ interessiert.


Das Endergebnis für die beiden Felder sieht folgendermaßen aus.


Endgültige Ansicht mit Daten


Sehen wir uns nun an, wie wir das gleiche Ergebnis erzielen, jedoch mit unserem eigenen Anzeigeformat.


Datumsanzeige mit unserem eigenen Format

Beispiel für ein benutzerdefiniertes Format


Problem

Mit dem Datumsanzeigeformat aus dem vorherigen Beispiel sind wir nicht zufrieden, da wir ein spezielles für die Gantt-Tabelle benötigen – „01.01.2022“.


Vorgeschlagene Lösung

Lassen Sie uns die Daten mithilfe der in Structure integrierten Funktionen anzeigen und das für uns geeignete Format angeben.


Verwendete Strukturmerkmale

  1. Variablenzuordnung
  2. Expr-Funktionen


Ein Codebeispiel

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


Eine Analyse der Lösung

Die Entwickler haben viele verschiedene Funktionen bereitgestellt, darunter eine separate zur Anzeige des Datums in unserem eigenen Format: FORMAT_DATETIME; Das ist es, was wir verwenden werden. Die Funktion verwendet zwei Argumente: ein Datum und eine Zeichenfolge im gewünschten Format.


Wir richten die Variable firstTransitionToStart (erstes Argument) mit denselben Zuordnungsregeln wie im vorherigen Beispiel ein. Das zweite Argument ist eine Zeichenfolge, die das Format angibt, und wir definieren es wie folgt: „tt.MM.jjjj“. Dies entspricht der von uns gewünschten Form „01.01.2022“.


Somit liefert unsere Formel sofort ein Ergebnis in der gewünschten Form. Daher können wir die Option „Allgemein“ in den Feldeinstellungen beibehalten.


Das zweite Feld mit dem Enddatum der Arbeit erfolgt auf die gleiche Weise. Als Ergebnis sollte die Struktur wie im Bild unten aussehen.


Endfütterung nach der Transformation


Grundsätzlich gibt es keine nennenswerten Schwierigkeiten bei der Arbeit mit der Formelsyntax. Wenn Sie eine Variable benötigen, schreiben Sie ihren Namen; Wenn Sie wiederum eine Funktion benötigen, schreiben Sie einfach ihren Namen und übergeben Sie die Argumente (falls erforderlich).


Wenn Structure auf einen unbekannten Namen stößt, geht es davon aus, dass es sich um eine Variable handelt, und versucht, diese selbst zuzuordnen, oder bittet uns um Hilfe.


Übrigens ein wichtiger Hinweis: Bei der Struktur wird die Groß-/Kleinschreibung nicht beachtet, daher sind firstTransitionToStart, firsttransitiontostart und firSttrAnsItiontOStarT dieselben Variablen. Die gleiche Regel gilt für Funktionen. Um einen eindeutigen Codestil zu erreichen, werden wir in den Beispielen versuchen, die Regeln der Großschreibungskonventionen von MSDN einzuhalten.


Lassen Sie uns nun in die Syntax eintauchen und uns ein spezielles Format zur Anzeige des Ergebnisses ansehen.


Zeigt den Namen der übergeordneten Aufgabe an

Der Name des Elternteils wird vor der Zusammenfassung angezeigt


Problem

Wir arbeiten mit regulären Aufgaben (Aufgabe, Fehler usw.) und mit Aufgaben vom Typ Story, die Unteraufgaben haben. Irgendwann müssen wir herausfinden, welche Aufgaben und Teilaufgaben der Mitarbeiter in einem bestimmten Zeitraum bearbeitet hat.


Das Problem besteht darin, dass viele Teilaufgaben keine Informationen über die Geschichte selbst liefern, da sie „An der Geschichte arbeiten“, „Einrichten“ oder beispielsweise „Wirkung aktivieren“ heißen. Und wenn wir eine Aufgabenliste für einen bestimmten Zeitraum anfordern, erhalten wir ein Dutzend Aufgaben mit der Bezeichnung „An der Story arbeiten“ ohne weitere nützliche Informationen.


Wir wünschen uns eine Ansicht mit einer Liste, die in zwei Spalten unterteilt ist: eine Aufgabe und eine übergeordnete Aufgabe, damit es in Zukunft möglich wäre, eine solche Liste nach Mitarbeitern zu gruppieren.


Vorgeschlagene Lösung

In unserem Projekt haben wir zwei Möglichkeiten, wenn eine Aufgabe ein übergeordnetes Element haben kann:

  1. Eine Aufgabe ist eine Unteraufgabe und ihre übergeordnete Aufgabe ist nur Story
  2. Eine Aufgabe ist eine reguläre Aufgabe (Aufgabe, Fehler usw.) und kann episch sein oder auch nicht. In diesem Fall hat die Aufgabe überhaupt kein übergeordnetes Element.


Also müssen wir:

  1. Finden Sie heraus, ob eine Aufgabe ein übergeordnetes Element hat
  2. Finden Sie den Typ dieses Elternteils heraus
  3. Ermitteln Sie den Typ und Namen dieser Aufgabe nach folgendem Schema: „[Übergeordneter-Typ] Übergeordneter-Name“.


Um die Wahrnehmung von Informationen zu vereinfachen, färben wir den Text des Aufgabentyps ein: also entweder „[Story]“ oder „[Epic]“.


Was wir verwenden werden:

  1. Variablenzuordnung
  2. Zustand
  3. Zugriff auf Aufgabenfelder
  4. Anzeigeformat – Wiki-Markup


Ein Codebeispiel

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


Eine Analyse der Lösung

Warum beginnt die Formel mit einer if-Bedingung, wenn wir nur einen String ausgeben und dort den Aufgabentyp und -namen einfügen müssen? Gibt es nicht eine universelle Möglichkeit, auf Aufgabenfelder zuzugreifen? Ja, aber bei Aufgaben und Epics werden diese Felder anders benannt und Sie müssen auch anders darauf zugreifen, das ist eine Funktion von Jira.


Die Unterschiede beginnen auf der Ebene der übergeordneten Suche. Bei einer Unteraufgabe befindet sich das übergeordnete Element im Jira-Feld „Übergeordnetes Problem“, und bei einer regulären Aufgabe ist das Epic das übergeordnete Element, das sich im Feld „Epic-Link“ befindet. Dementsprechend müssen wir zwei verschiedene Optionen für den Zugriff auf diese Felder schreiben.


Hier benötigen wir eine if-Bedingung. Die Expr-Sprache verfügt über verschiedene Möglichkeiten, mit Bedingungen umzugehen. Die Wahl zwischen ihnen ist Geschmackssache.


Es gibt eine „Excel-ähnliche“ Methode:

 if (condition1; result1; condition2; result2 … )


Oder eine „codeähnlichere“ Methode:

 if condition1 : result1 else if condition2 : result2 else result3


Im Beispiel habe ich die erste Option verwendet; Schauen wir uns nun unseren Code vereinfacht an:

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


Wir sehen zwei offensichtliche Bedingungen:

  • Parent.Issuetype = „Story“
  • EpicLink


Lassen Sie uns herausfinden, was sie tun, und beginnen wir mit dem ersten, Parent.Issuetype=“Story“.


In diesem Fall ist „Parent“ eine Variable, die automatisch dem Feld „Parent Issue“ zugeordnet wird. Hier sollte sich, wie oben besprochen, das übergeordnete Element der Unteraufgabe befinden. Mithilfe der Punktnotation (.) greifen wir auf die Eigenschaft dieses übergeordneten Elements zu, insbesondere auf die Issuetype-Eigenschaft, die dem Jira-Feld „Issue Type“ entspricht. Es stellt sich heraus, dass die gesamte Zeile „Parent.Issuetype“ uns den Typ der übergeordneten Aufgabe zurückgibt, sofern eine solche Aufgabe vorhanden ist.


Darüber hinaus mussten wir nichts definieren oder zuordnen, da die Entwickler bereits ihr Bestes für uns getan haben. Hier ist beispielsweise ein Link zu allen Eigenschaften (einschließlich Jira-Feldern), die in der Sprache vordefiniert sind, und hier sehen Sie eine Liste aller Standardvariablen, auf die auch ohne zusätzliche Einstellungen sicher zugegriffen werden kann.


Daher besteht die erste Bedingung darin, festzustellen, ob der Typ der übergeordneten Aufgabe „Story“ ist. Wenn die erste Bedingung nicht erfüllt ist, ist der Typ der übergeordneten Aufgabe nicht „Story“ oder sie existiert überhaupt nicht. Und das bringt uns zur zweiten Bedingung: EpicLink.


Tatsächlich prüfen wir dann, ob das Jira-Feld „Epic Link“ ausgefüllt ist (das heißt, wir überprüfen seine Existenz). Die EpicLink-Variable ist ebenfalls Standard und muss nicht zugeordnet werden. Es stellt sich heraus, dass unsere Bedingung erfüllt ist, wenn die Aufgabe über Epic Link verfügt.


Und die dritte Option besteht darin, dass keine der Bedingungen erfüllt ist, d. h. die Aufgabe hat weder einen übergeordneten noch einen Epic-Link. In diesem Fall zeigen wir nichts an und lassen das Feld leer. Dies erfolgt automatisch, da wir keine Ergebnisse erhalten.


Wir haben die Bedingungen herausgefunden, jetzt kommen wir zu den Ergebnissen. In beiden Fällen handelt es sich um eine Zeichenfolge mit Text und spezieller Formatierung.


Ergebnis 1 (wenn das übergeordnete Element Story ist):

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


Ergebnis 2 (sofern Epic Link vorhanden ist):

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


Beide Ergebnisse sind in ihrer Struktur ähnlich: Sie bestehen beide aus dreifachen Anführungszeichen „““ am Anfang und Ende der Ausgabezeichenfolge, einer Farbangabe in den öffnenden {color: COLOR}- und schließenden {color}-Blöcken sowie aus Operationen, die über durchgeführt werden $-Symbol. Dreifache Anführungszeichen sagen der Struktur, dass sich darin Variablen, Operationen oder Formatierungsblöcke (z. B. Farben) befinden.


Für das Ergebnis der ersten Bedingung gilt:

  1. Übertragen Sie den Typ der übergeordneten Aufgabe ${Parent.Issuetype}
  2. Setzen Sie es in eckige Klammern „[…]“
  3. Markieren Sie alles in Grün und schließen Sie diesen Ausdruck [${Parent.Issuetype}] in den Farbauswahlblock {color:green}…{color} ein, wo wir „grün“ geschrieben haben.
  4. Und noch etwas: Fügen Sie den Namen der übergeordneten Aufgabe hinzu, getrennt durch ein Leerzeichen ${Parent.Summary}.


Somit erhalten wir die Zeichenfolge „[Story] Some task name.“ Wie Sie vielleicht schon vermutet haben, ist Zusammenfassung auch eine Standardvariable. Um das Schema zum Aufbau solcher Strings klarer zu machen, möchte ich ein Bild aus der offiziellen Dokumentation zeigen.


Benutzerdefiniertes Zeilenschema aus der offiziellen Dokumentation


Auf ähnliche Weise sammeln wir die Zeichenfolge für das zweite Ergebnis, legen die Farbe jedoch über den Hex-Code fest. Ich habe herausgefunden, dass die Farbe des Epic „#713A82“ war (in den Kommentaren können Sie übrigens eine genauere Farbe für Epic vorschlagen). Vergessen Sie nicht die Felder (Eigenschaften), die sich für Epic ändern. Verwenden Sie anstelle von „Summary“ „EpicName“ und anstelle von „Parent“ „EpicLink“.


Dadurch lässt sich das Schema unserer Formel als Bedingungstabelle darstellen.


Bedingung: Die übergeordnete Aufgabe ist vorhanden und hat den Typ „Story“.

Ergebnis: Zeile mit grünem Typ der übergeordneten Aufgabe und ihrem Namen.

Bedingung: Das Feld „Epic Link“ ist ausgefüllt.

Ergebnis: Linie mit der epischen Farbe des Typs und seinem Namen.


Standardmäßig ist im Feld die Anzeigeoption „Allgemein“ ausgewählt. Wenn Sie diese nicht ändern, sieht das Ergebnis wie einfacher Text aus, ohne die Farbe zu ändern und die Blöcke zu identifizieren. Wenn Sie das Anzeigeformat auf „Wiki Markup“ ändern, wird der Text transformiert.


1) Allgemeines anzeigen – standardmäßig wird der reine Text so angezeigt, wie er ist. 2) Ersetzen Sie „Allgemein“ durch Wiki-Markup



Machen wir uns nun mit Variablen vertraut, die nichts mit Jira-Feldern zu tun haben – lokalen Variablen.


Berechnung der Anzahl der Story-Punkte mit Farbangabe

Story-Point-Summen werden mit verschiedenen Farben hervorgehoben


Problem

Aus dem vorherigen Beispiel haben Sie gelernt, dass wir mit Aufgaben des Typs Story arbeiten, die Unteraufgaben haben. Hierbei handelt es sich um einen Sonderfall bei Schätzungen. Um einen Story-Score zu erhalten, fassen wir die Scores seiner Unteraufgaben zusammen, die in abstrakten Story-Punkten geschätzt werden.


Der Ansatz ist ungewöhnlich, funktioniert aber bei uns. Wenn also die Story keine Schätzung hat, die Unteraufgaben jedoch schon, gibt es kein Problem, aber wenn sowohl die Story als auch die Unteraufgaben eine Schätzung haben, funktioniert die Standardoption von Structure, „Σ Story Points“, nicht richtig.


Dies liegt daran, dass die Schätzung der Story zur Summe der Unteraufgaben addiert wird. Infolgedessen wird in Story der falsche Betrag angezeigt. Wir möchten dies vermeiden und einen Hinweis auf eine Inkonsistenz mit der festgelegten Schätzung in Story und der Summe der Teilaufgaben hinzufügen.


Vorgeschlagene Lösung

Wir benötigen mehrere Bedingungen, da alles davon abhängt, ob die Schätzung in Story festgelegt ist.


Die Bedingungen sind also:


Wenn Story keine Schätzung hat , zeigen wir die Summe der Teilaufgabenschätzungen in Orange an, um anzuzeigen, dass dieser Wert noch nicht in Story festgelegt wurde


Wenn Story eine Schätzung hat , prüfen Sie, ob diese der Summe der Teilaufgabenschätzung entspricht:

  • Wenn es nicht übereinstimmt, färben Sie eine Schätzung rot und schreiben Sie den korrekten Betrag in Klammern daneben
  • Wenn eine Schätzung und die Summe übereinstimmen, schreiben Sie einfach eine Schätzung in Grün


Der Wortlaut dieser Bedingungen kann verwirrend sein, also lassen Sie uns sie in einem Schema ausdrücken.


Algorithmus zur Auswahl der Textanzeigeoption


Verwendete Strukturmerkmale

  1. Variablenzuordnung
  2. Lokale Variablen
  3. Aggregationsmethoden
  4. Bedingungen
  5. Text mit Formatierung


Ein Codebeispiel

 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}"""


Eine Analyse der Lösung

Bevor wir uns mit dem Code befassen, wandeln wir unser Schema in eine „codeähnlichere“ Art um, um zu verstehen, welche Variablen wir benötigen.


Derselbe Algorithmus wurde mit Variablen neu geschrieben


Aus diesem Schema sehen wir, dass wir Folgendes benötigen:


Bedingungsvariablen:

  • isschätzung (Verfügbarkeit der Schätzung)
  • isError (Übereinstimmung der Story-Schätzung und der Summe)


Eine Variable der Textfarbe – Farbe


Zwei Schätzvariablen:

  • Summe (die Summe der Teilaufgabenschätzung)
  • sp (Story-Punkte)


Darüber hinaus hängt die Farbvariable auch von einer Reihe von Bedingungen ab, beispielsweise von der Verfügbarkeit eines Kostenvoranschlags und von der Art der Aufgabe in der Linie (siehe Schema unten).


Algorithmus zur Farbauswahl


Um die Farbe zu bestimmen, benötigen wir also eine weitere Bedingungsvariable, isStory, die angibt, ob der Aufgabentyp „Story“ ist.


Die SP-Variable (Storypoints) ist Standard, was bedeutet, dass sie automatisch dem entsprechenden Jira-Feld zugeordnet wird. Den Rest der Variablen sollten wir selbst definieren und sie sind für uns lokal.


Versuchen wir nun, die Schemata im Code zu implementieren. Definieren wir zunächst alle Variablen.


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


Die Zeilen werden durch dasselbe Syntaxschema verbunden: das Schlüsselwort „with“, der Variablenname und das Doppelpunktsymbol „:“ am Ende der Zeile.


Syntax für die Deklaration lokaler Variablen


Das Schlüsselwort with wird zur Bezeichnung lokaler Variablen (und benutzerdefinierter Funktionen, mehr dazu in einem separaten Beispiel) verwendet. Es teilt der Formel mit, dass als nächstes eine Variable folgt, die nicht zugeordnet werden muss. Der Doppelpunkt „:“ markiert das Ende der Variablendefinition.


Daher erstellen wir die Variable isschätzung (zur Erinnerung: Dieser Fall ist nicht wichtig). Wir speichern darin entweder 1 oder 0, je nachdem, ob das Story-Points-Feld gefüllt ist. Die Variable storypoints wird automatisch zugeordnet, da wir zuvor noch keine lokale Variable mit demselben Namen erstellt haben (z. B. mit storypoints = … :).


Die undefinierte Variable bezeichnet die Nichtexistenz von etwas (als null, NaN und dergleichen in anderen Sprachen). Daher kann der Ausdruck Storypoints != undefiniert als Frage gelesen werden: „Ist das Story-Points-Feld ausgefüllt?“.


Als nächstes sollten wir die Summe der Story Points aller untergeordneten Aufgaben ermitteln. Dazu erstellen wir eine lokale Variable: childrenSum.


 with childrenSum = sum#children{storypoints}:


Diese Summe wird über die Aggregationsfunktion berechnet. (Über Funktionen wie diese können Sie in der offiziellen Dokumentation nachlesen.) Kurz gesagt: Structure kann verschiedene Operationen mit Aufgaben ausführen und dabei die Hierarchie der aktuellen Ansicht berücksichtigen.


Wir verwenden die Summenfunktion und übergeben zusätzlich mit dem Symbol „#“ die untergeordneten Klärungen, wodurch die Berechnung der Summe nur auf alle untergeordneten Aufgaben der aktuellen Zeile beschränkt wird. In geschweiften Klammern geben wir an, welches Feld wir zusammenfassen möchten – wir benötigen eine Schätzung in Storypoints.


Die nächste lokale Variable, isStory, speichert eine Bedingung: ob der Aufgabentyp in der aktuellen Zeile eine Story ist.


 with isStory = issueType = "Story":


Wir wenden uns der aus dem letzten Beispiel bekannten Variable „issueType“ zu, also dem Aufgabentyp, der sich selbst dem gewünschten Feld zuordnet. Wir machen dies, weil es sich um eine Standardvariable handelt und wir sie zuvor noch nicht mit definiert haben.


Definieren wir nun die Variable isErr – sie signalisiert eine Diskrepanz zwischen der Unteraufgabensumme und der Story-Schätzung.


 with isErr = isStory AND childrenSum != storypoints:


Hier verwenden wir die lokalen Variablen isStory und childrenSum, die wir zuvor erstellt haben. Um einen Fehler zu melden, müssen zwei Bedingungen gleichzeitig erfüllt sein: Der Problemtyp ist Story (isStory) und (AND) die Summe der untergeordneten Punkte (childrenSum) ist nicht gleich (!=) der festgelegten Schätzung in der Aufgabe (storypoints). ). Genau wie in JQL können wir beim Erstellen von Bedingungen Verknüpfungswörter wie AND oder OR verwenden.


Beachten Sie, dass für jede der lokalen Variablen am Ende der Zeile ein „:“-Symbol steht. Es sollte am Ende stehen, nach allen Operationen, die die Variable definieren. Wenn wir beispielsweise die Definition einer Variablen in mehrere Zeilen aufteilen müssen, wird der Doppelpunkt „:“ erst nach der letzten Operation platziert. Wie im Beispiel mit der Farbvariablen – der Farbe des Textes.


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


Hier sehen wir viele „:“, aber sie spielen unterschiedliche Rollen. Der Doppelpunkt nach if isStory ist das Ergebnis der isStory-Bedingung. Erinnern wir uns an die Konstruktion: wenn Bedingung: Ergebnis. Stellen wir uns diese Konstruktion in einer komplexeren Form vor, die eine Variable definiert.


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


Es stellt sich heraus, dass wenn Bedingung2: Ergebnis2, sonst Ergebnis3 sozusagen das Ergebnis der ersten Bedingung ist und ganz am Ende ein Doppelpunkt „:“ steht, der die Definition der Variablen vervollständigt.


Auf den ersten Blick mag die Definition von Farbe kompliziert erscheinen, obwohl wir hier tatsächlich das zu Beginn des Beispiels vorgestellte Farbdefinitionsschema beschrieben haben. Es ist nur so, dass als Ergebnis der ersten Bedingung eine andere Bedingung beginnt – eine verschachtelte Bedingung und eine weitere darin.


Das Endergebnis unterscheidet sich jedoch geringfügig vom zuvor vorgestellten Schema.


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


Wir müssen „{color}$sp“ nicht zweimal in den Code schreiben, wie es im Schema der Fall war; Wir werden die Dinge klüger angehen. Wenn die Aufgabe eine Schätzung hat, zeigen wir in der Verzweigung immer {color: $color}$storypoints{color} an (d. h. nur eine Schätzung in Story Points in der benötigten Farbe), und wenn ein Fehler vorliegt, dann Nach einem Leerzeichen ergänzen wir die Zeile mit der geschätzten Summe der Teilaufgaben: ($childrenSum).


Wenn kein Fehler vorliegt, wird er nicht hinzugefügt. Ich mache Sie auch darauf aufmerksam, dass es kein „:“-Symbol gibt, da wir keine Variable definieren, sondern das Endergebnis durch eine Bedingung anzeigen.


Unsere Arbeit können wir im Bild unten im Feld „∑SP (mod)“ bewerten. Der Screenshot zeigt speziell zwei zusätzliche Felder:


  • „Story Points“ – eine Schätzung in Story Points (Standard-Jira-Feld).
  • „∑ Story Points“ – ein benutzerdefiniertes Standardfeld von Structure, das den Betrag falsch berechnet.


Abschließende Ansicht des Feldes und Vergleich mit Standard-Story Points- und ∑ Story Points-Feldern


Mithilfe dieser Beispiele haben wir die Hauptmerkmale der Struktursprache analysiert, die Ihnen bei der Lösung der meisten Probleme helfen werden. Schauen wir uns nun zwei weitere nützliche Funktionen an, unsere Funktionen und Arrays. Wir werden sehen, wie wir unsere eigene benutzerdefinierte Funktion erstellen.


Letzte Änderungen

Achten Sie auf das Emoji auf der linken Seite – es stellt ein benutzerdefiniertes Feld dar


Problem

Manchmal gibt es in einem Sprint viele Aufgaben, bei denen wir möglicherweise kleine Änderungen übersehen. Beispielsweise übersehen wir möglicherweise eine neue Unteraufgabe oder die Tatsache, dass eine der Geschichten in die nächste Phase übergegangen ist. Es wäre schön, ein Tool zu haben, das uns über die neuesten wichtigen Änderungen bei Aufgaben informiert.


Vorgeschlagene Lösung

Uns interessieren drei Arten von Aufgabenstatusänderungen, die seit gestern aufgetreten sind: Wir haben mit der Arbeit an der Aufgabe begonnen, eine neue Aufgabe ist erschienen, die Aufgabe ist geschlossen. Darüber hinaus ist es hilfreich zu sehen, dass die Aufgabe mit der Lösung „Geht nicht“ abgeschlossen wird.


Dazu erstellen wir ein Feld mit einer Reihe von Emojis, die für die neuesten Änderungen verantwortlich sind. Wenn beispielsweise gestern eine Aufgabe erstellt wurde und wir mit der Arbeit daran begonnen haben, wird sie mit zwei Emojis markiert: „In Bearbeitung“ und „Neue Aufgabe“.


Warum brauchen wir ein solches benutzerdefiniertes Feld, wenn mehrere zusätzliche Felder angezeigt werden können, beispielsweise das Datum des Übergangs in den Status „In Bearbeitung“ oder ein separates Feld „Lösung“? Die Antwort ist einfach: Menschen nehmen Emojis einfacher und schneller wahr als Text, der sich in verschiedenen Feldern befindet und analysiert werden muss. Die Formel sammelt alles an einem Ort und analysiert es für uns, was uns Mühe und Zeit für nützlichere Dinge erspart.


Lassen Sie uns bestimmen, wofür die verschiedenen Emojis verantwortlich sein werden:

  • *️⃣ ist die gebräuchlichste Art, eine neue Aufgabe zu markieren
  • ✅ markiert eine erledigte Aufgabe
  • ❌ weist auf eine Aufgabe hin, die Sie abgebrochen haben („Wird nicht durchgeführt“).
  • 🚀 bedeutet, dass wir uns entschieden haben, mit der Arbeit an der Aufgabe zu beginnen (dieses Emoji passt zu unserem Team, bei Ihnen kann es anders sein)


Verwendete Strukturmerkmale

  1. Variablenzuordnung
  2. Expr-Sprachmethoden
  3. Lokale Variablen
  4. Bedingungen
  5. Unsere eigene Funktion


Ein Codebeispiel

 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 : "❌")

Eine Analyse der Lösung


Lassen Sie uns zunächst über die globalen Variablen nachdenken, die wir benötigen, um die für uns interessanten Ereignisse zu bestimmen. Wir müssen wissen, ob seit gestern:

  • Die Aufgabe wurde erstellt
  • Der Status hat sich in „In Bearbeitung“ geändert.
  • Es wurde eine Lösung gefunden (und welche)


Die Verwendung bereits vorhandener Variablen neben neuen Zuordnungsvariablen hilft uns, alle diese Bedingungen zu überprüfen.

  • erstellt – das Datum der Aufgabenerstellung
  • LatestTransitionToProgress – das späteste Datum des Übergangs in den Status „In Bearbeitung“ (wir ordnen es wie im vorherigen Beispiel zu)
  • ResolutionDate – das Datum des Aufgabenabschlusses
  • Auflösung – Auflösungstext


Kommen wir zum Code. Die erste Zeile beginnt mit einer Bedingung, die prüft, ob der Aufgabentyp vorhanden ist.


 if defined(issueType):


Dies erfolgt über die integrierte definierte Funktion, die prüft, ob das angegebene Feld vorhanden ist. Die Prüfung wird durchgeführt, um die Berechnung der Formel zu optimieren.


Wir werden Structure nicht mit nutzlosen Berechnungen belasten, wenn die Zeile keine Aufgabe ist. Es stellt sich heraus, dass der gesamte Code nach „if“ das Ergebnis ist, also der zweite Teil der if-Konstruktion (Bedingung: Ergebnis). Und wenn die Bedingung nicht erfüllt ist, funktioniert der Code auch nicht.


Die nächste Zeile mit now = now(): wird ebenfalls benötigt, um die Berechnungen zu optimieren. Weiter unten im Code müssen wir verschiedene Daten mehrmals mit dem aktuellen Datum vergleichen. Um die gleiche Berechnung nicht mehrmals durchzuführen, berechnen wir dieses Datum einmal und machen es jetzt zu einer lokalen Variablen.


Es wäre auch schön, unser „Gestern“ getrennt zu halten. Aus dem bequemen „gestern“ wurden empirisch 1,3 Tage. Machen wir daraus eine Variable: mit daysScope = 1.3:.


Jetzt müssen wir die Anzahl der Tage zwischen zwei Daten mehrmals berechnen. Beispielsweise zwischen dem aktuellen Datum und dem Arbeitsbeginndatum. Natürlich gibt es eine eingebaute DAYS_BETWEEN-Funktion, die uns zu passen scheint. Wenn die Aufgabe jedoch beispielsweise am Freitag erstellt wurde, sehen wir am Montag keine Benachrichtigung über eine neue Aufgabe, da tatsächlich mehr als 1,3 Tage vergangen sind. Darüber hinaus zählt die Funktion DAYS_BETWEEN nur die Gesamtzahl der Tage (d. h. aus 0,5 Tagen werden 0 Tage), was uns ebenfalls nicht passt.


Wir haben eine Anforderung formuliert: Wir müssen die genaue Anzahl der Arbeitstage zwischen diesen Daten berechnen. und eine benutzerdefinierte Funktion wird uns dabei helfen.


Syntax für die Deklaration lokaler Funktionen


Seine definierende Syntax ist der Syntax zum Definieren einer lokalen Variablen sehr ähnlich. Der einzige Unterschied und die einzige Ergänzung besteht in der optionalen Aufzählung von Argumenten in den ersten Klammern. Die zweiten Klammern enthalten die Operationen, die ausgeführt werden, wenn unsere Funktion aufgerufen wird. Diese Definition der Funktion ist nicht die einzig mögliche, aber wir werden diese verwenden (andere finden Sie in der offiziellen Dokumentation ).


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


Unsere benutzerdefinierte Funktion „workDaysBetween“ berechnet die Arbeitstage zwischen dem heutigen Tag und dem Datum, das als Argumente übergeben wird. Die Logik der Funktion ist sehr einfach: Wir zählen die Anzahl der freien Tage und subtrahieren sie von der Gesamtzahl der Tage zwischen den Daten.


Um die Anzahl der freien Tage zu berechnen, müssen wir herausfinden, wie viele Wochen zwischen heute und ab vergangen sind. Dazu berechnen wir die Differenz zwischen den Zahlen der einzelnen Wochen. Wir erhalten diese Zahl von der Weeknum-Funktion, die uns die Wochennummer vom Jahresanfang liefert. Wenn wir diese Differenz mit zwei multiplizieren, erhalten wir die Anzahl der vergangenen freien Tage.


Als nächstes zählt die Funktion HOURS_BETWEEN die Anzahl der Stunden zwischen unseren Daten. Wir dividieren das Ergebnis durch 24, um die Anzahl der Tage zu erhalten, und subtrahieren von dieser Zahl die freien Tage, sodass wir die Arbeitstage zwischen den Daten erhalten.


Definieren wir mit unserer neuen Funktion eine Reihe von Hilfsvariablen. Beachten Sie, dass es sich bei einigen Datumsangaben in den Definitionen um globale Variablen handelt, über die wir zu Beginn des Beispiels gesprochen haben.


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


Um das Lesen des Codes zu vereinfachen, definieren wir Variablen, die die Ergebnisse der Bedingungen speichern.


 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 :


Für die Variable isRecentCreated habe ich eine optionale Bedingung und not(resolution) hinzugefügt, was mir hilft, die zukünftige Zeile zu vereinfachen, denn wenn die Aufgabe bereits geschlossen ist, bin ich nicht an Informationen über ihre aktuelle Erstellung interessiert.


Das Endergebnis wird über die Funktion concat erstellt, die die Zeilen verkettet.


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


Es stellt sich heraus, dass das Emoji nur dann in der Zeile angezeigt wird, wenn die Variable in der Bedingung gleich 1 ist. Somit kann unsere Zeile gleichzeitig unabhängige Änderungen an der Aufgabe anzeigen.


Endgültige Ansicht der Spalte mit Änderungen (linke Seite)


Wir haben das Thema der Zählung von Arbeitstagen ohne arbeitsfreie Tage angesprochen. Damit verbunden gibt es noch ein weiteres Problem, das wir in unserem letzten Beispiel analysieren und uns gleichzeitig mit Arrays vertraut machen werden.


Berechnung der Arbeitszeit, ohne freie Tage

Beispiel für die endgültige Anzeige


Problem

Manchmal möchten wir wissen, wie lange eine Aufgabe ausgeführt wurde, außer an freien Tagen. Dies ist beispielsweise notwendig, um die freigegebene Version zu analysieren. Um zu verstehen, warum wir freie Tage brauchen. Außer, dass einer von Montag bis Donnerstag lief und der andere von Freitag bis Montag. In einer solchen Situation können wir nicht behaupten, dass die Aufgaben gleichwertig sind, obwohl der Unterschied in den Kalendertagen uns das Gegenteil sagt.


Leider weiß Structure „out of the box“ nicht, wie man arbeitsfreie Tage ignoriert, und das Feld mit der Option „Zeit im Status…“ liefert unabhängig von den Jira-Einstellungen ein Ergebnis – selbst wenn Samstag und Sonntag als arbeitsfreie Tage angegeben sind.


Unser Ziel ist es daher, die genaue Anzahl der Arbeitstage zu berechnen, arbeitsfreie Tage außer Acht zu lassen und die Auswirkungen von Statusübergängen auf diese Zeit zu berücksichtigen.


Und was haben Status damit zu tun? Lassen Sie mich antworten. Angenommen, wir haben berechnet, dass die Aufgabe zwischen dem 10. und 20. März drei Tage lang in Betrieb war. Aber von diesen drei Tagen war es einen Tag in der Pause und anderthalb Tage in der Überprüfung. Es stellte sich heraus, dass die Aufgabe nur einen halben Tag lang in Betrieb war.


Die Lösung aus dem vorherigen Beispiel passt wegen des Problems des Statuswechsels nicht zu uns, da die benutzerdefinierte Funktion workDaysBetween nur die Zeit zwischen zwei ausgewählten Daten berücksichtigt.


Vorgeschlagene Lösung

Dieses Problem kann auf unterschiedliche Weise gelöst werden. Die Methode im Beispiel ist hinsichtlich der Leistung die teuerste, aber hinsichtlich der Zählung freier Tage und Status am genauesten. Beachten Sie, dass die Implementierung nur in der Strukturversion älter als 7.4 (Dezember 2021) funktioniert.


Die Idee hinter der Formel ist also folgende:


  1. Wir müssen herausfinden, wie viele Tage vom Beginn bis zum Abschluss der Aufgabe vergangen sind
  2. Daraus erstellen wir ein Array, also eine Liste der Tage zwischen Beginn und Ende unserer Arbeit an der Aufgabe
  3. Behalten Sie in der Liste nur die freien Tage bei


Aus allen Terminen nur Wochenenden herausfiltern (sie können unterschiedliche Status haben)


  1. Von diesen freien Tagen behalten wir nur diejenigen, an denen sich die Aufgabe im Status „In Bearbeitung“ befand (hier hilft uns die Funktion „Historischer Wert“ aus Version 7.4).


Entfernen unnötiger Status, wobei der Status „In Bearbeitung“ bestehen bleibt


  1. Jetzt sind in der Liste nur die freien Tage aufgeführt, die mit dem „In Bearbeitung“-Zeitraum zusammenfielen
  2. Separat ermitteln wir die Gesamtdauer des Status „In Bearbeitung“ (über die integrierte Strukturoption „Zeit im Status…“);
  3. Subtrahieren Sie von dieser Zeit die Anzahl der zuvor erzielten freien Tage


Auf diese Weise erhalten wir den genauen Zeitpunkt der Bearbeitung der Aufgabe, wobei wir freie Tage und Übergänge zwischen zusätzlichen Status ignorieren.


Verwendete Strukturmerkmale

  1. Variablenzuordnung
  2. Expr-Sprachmethoden
  3. Lokale Variablen
  4. Bedingungen
  5. Eine interne Methode (unsere eigene Funktion)
  6. Arrays
  7. Zugriff auf den Verlauf der Aufgabe
  8. Text mit Formatierung


Ein Codebeispiel

 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}"""


Eine Analyse der Lösung


Bevor wir unseren Algorithmus auf den Code übertragen, erleichtern wir uns die Berechnungen für die Struktur.


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


Wenn es sich bei der Zeile nicht um eine Aufgabe handelt oder ihr Status „Offen“ ist, überspringen wir diese Zeilen. Wir sind nur an Aufgaben interessiert, die zur Arbeit gestartet wurden.


Um die Anzahl der Tage zwischen Datumsangaben zu berechnen, müssen wir zunächst diese Datumsangaben bestimmen: Enddatum und Startdatum.


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


Identifizieren der Status, die das logische Ende der Arbeit bedeuten


Wir gehen davon aus, dass das Abschlussdatum der Aufgabe (finishDate) ist:

  • Entweder das Datum, an dem die Aufgabe in den Status „QA“ überführt wurde
  • Entweder das Datum des Übergangs zu „Geschlossen“
  • Oder wenn sich die Aufgabe noch im Status „In Bearbeitung“ befindet, dann das heutige Datum (um zu verstehen, wie viel Zeit vergangen ist)


Das Startdatum der Arbeit startDate wird durch das Datum des Übergangs in den Status „In Bearbeitung“ bestimmt. Es gibt Fälle, in denen die Aufgabe geschlossen wird, ohne in die In-Work-Phase zu wechseln. In solchen Fällen betrachten wir das Abschlussdatum als Startdatum, sodass das Ergebnis 0 Tage ist.


Wie Sie vielleicht schon erraten haben, sind „toQA“, „toDone“ und „toProgress“ Variablen, die wie im ersten und vorherigen Beispiel den entsprechenden Status zugeordnet werden müssen.


Wir sehen auch die neue Funktion DEFAULT(toProgress, toDone). Es prüft, ob toProgress einen Wert hat, und wenn nicht, verwendet es den Wert der toDone-Variablen.


Als Nächstes folgt die Definition der benutzerdefinierten Funktion statusWeekendsCount. Wir werden jedoch später darauf zurückkommen, da sie eng mit Datumslisten zusammenhängt. Es ist besser, direkt mit der Definition dieser Liste fortzufahren, damit wir später verstehen, wie wir unsere Funktion darauf anwenden.


Wir möchten eine Liste von Daten in der folgenden Form erhalten: [Startdatum (sagen wir 11.03), 12.03, 13.03, 14.03 … Enddatum]. Es gibt keine einfache Funktion, die uns in Structure die ganze Arbeit abnehmen würde. Also greifen wir zu einem Trick:


  1. Wir erstellen eine einfache Liste aus einer Zahlenfolge von 0 bis zur Anzahl der Arbeitstage, also [0, 1, 2, 3 … n Arbeitstage]
  2. Fügen Sie zu jeder Zahl (z. B. Tag) das Startdatum der Aufgabe hinzu. Als Ergebnis erhalten wir eine Liste (Array) des erforderlichen Typs: [Start + 0 Tage, Start + 1 Tag, Start + 2 Tage … Start + n Arbeitstage].


Erstellen eines anfänglichen Datumsarrays vom Startdatum bis zum logischen Ende


Sehen wir uns nun an, wie wir es im Code implementieren können. Wir werden mit Arrays arbeiten.


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


Wir zählen, wie viele Tage die Bearbeitung einer Aufgabe in Anspruch nehmen wird. Wie im vorherigen Beispiel durch Division durch 24 und die Funktion „hours_between(startDate,finishDate)“. Das Ergebnis wird in die Variable „overallDays“ geschrieben.


Wir erstellen ein Array der Zahlenfolge in Form der Variable sequenceArray. Dieses Array wird über die Funktion SEQUENCE(0,overallDays) erstellt, die einfach ein Array der gewünschten Größe mit einer Sequenz von 0 bis OverallDays erstellt.


Als nächstes kommt die Magie. Eine der Array-Funktionen ist Map. Es wendet die angegebene Operation auf jedes Element des Arrays an.


Unsere Aufgabe besteht darin, zu jeder Zahl (also der Tageszahl) das Startdatum hinzuzufügen. Die Funktion DATE_ADD kann dies tun, sie addiert eine bestimmte Anzahl von Tagen, Monaten oder Jahren zum angegebenen Datum.


Wenn wir das wissen, entschlüsseln wir die Zeichenfolge:


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


Auf jedes Element im sequenceArray wendet die Funktion .map() DATE_ADD(startDate, $, „day“) an.


Sehen wir uns an, was in den Argumenten für DATE_ADD übergeben wird. Das erste ist startDate, das Datum, zu dem die gewünschte Zahl hinzugefügt wird. Diese Zahl wird durch das zweite Argument angegeben, aber wir sehen $.


Das $-Symbol bezeichnet ein Array-Element. Die Struktur versteht, dass die Funktion DATE_ADD auf ein Array angewendet wird und daher anstelle von $ das gewünschte Array-Element vorhanden ist (d. h. 0, 1, 2 …).


Das letzte Argument „Tag“ ist ein Hinweis darauf, dass wir einen Tag hinzufügen, da die Funktion je nach Angabe einen Tag, einen Monat und ein Jahr hinzufügen kann.


Somit speichert die Variable „datesArray“ ein Array von Daten vom Beginn der Arbeit bis zu ihrem Abschluss.


Kommen wir zurück zu der benutzerdefinierten Funktion, die wir verpasst haben. Überschüssige Tage werden herausgefiltert und der Rest berechnet. Wir haben diesen Algorithmus ganz am Anfang des Beispiels beschrieben, bevor wir den Code analysiert haben, und zwar in den Absätzen 3 und 4 über das Herausfiltern von Ruhetagen und Status.


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


Wir übergeben zwei Argumente an die benutzerdefinierte Funktion: ein Array von Datumsangaben, nennen wir es Datumsangaben, und den erforderlichen Status – Status. Wir wenden die Funktion .filter() auf das übertragene Datumsarray an, wodurch nur die Datensätze im Array gespeichert werden, die die Filterbedingung durchlaufen haben. In unserem Fall gibt es zwei davon, und sie werden durch und kombiniert. Nach dem Filter sehen wir .size(), es gibt die Größe des Arrays zurück, nachdem alle Operationen daran ausgeführt wurden.


Wenn wir den Ausdruck vereinfachen, erhalten wir etwa Folgendes: array.filter(condition1 and condition2).size(). Als Ergebnis erhielten wir die für uns geeignete Anzahl freier Tage, also die freien Tage, die die Bedingungen erfüllten.


Schauen wir uns beide Bedingungen genauer an:


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


Der Ausdruck x -> ist nur ein Teil der Filtersyntax und gibt an, dass wir das Element des Arrays x aufrufen. Daher erscheint x in jeder Bedingung (ähnlich wie bei $). Es stellt sich heraus, dass x jedes Datum aus dem übertragenen Datumsarray ist.


Die erste Bedingung, Wochentag(x) > 5, erfordert, dass der Wochentag des Datums x (d. h. jedes Elements) größer als 5 ist – es ist entweder Samstag (6) oder Sonntag (7).


Die zweite Bedingung verwendet Historical_value.


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


Das ist eine Funktion von Structure der Version 7.4.


Die Funktion greift auf den Verlauf der Aufgabe zu und sucht im angegebenen Feld nach einem bestimmten Datum. In diesem Fall suchen wir im Feld „Status“ nach dem Datum x. Die Variable this ist nur ein Teil der Funktionssyntax, sie wird automatisch zugeordnet und stellt die aktuelle Aufgabe in der Zeile dar.


Daher vergleichen wir in der Bedingung das übergebene Statusargument und das Feld „status“, das von der Funktion Historical_value für jedes Datum x im Array zurückgegeben wird. Bei Übereinstimmung verbleibt der Eintrag in der Liste.


Der letzte Schliff ist die Verwendung unserer Funktion, um die Anzahl der Tage im gewünschten Status zu zählen:


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


Lassen Sie uns zunächst herausfinden, wie viele freie Tage mit dem Status „In Bearbeitung“ in unserem DateArray enthalten sind. Das heißt, wir übergeben unsere Datumsliste und den gewünschten Status an die benutzerdefinierte Funktion statusWeekendsCount. Die Funktion entfernt alle Wochentage und alle freien Tage, an denen der Status der Aufgabe vom Status „in Bearbeitung“ abweicht, und gibt die Anzahl der verbleibenden Tage in der Liste zurück.


Anschließend subtrahieren wir diesen Betrag von der Variablen timeInProgress, die wir über die Option „Zeit im Status …“ zuordnen.


Die Zahl 86400000 ist der Divisor, der Millisekunden in Tage umwandelt. Die Funktion .round(1) wird benötigt, um das Ergebnis auf Zehntel zu runden, zum Beispiel auf „4,1“, andernfalls erhält man diesen Eintragstyp: „4,0999999 …“.


Um die Länge der Aufgabe anzuzeigen, führen wir die Farbvariable ein. Wir ändern es abhängig von der Anzahl der Tage, die für die Aufgabe aufgewendet wurden.


  • Grau – 0 Tage
  • Grün – mehr als 0, aber weniger als 2,5 Tage
  • Rot – 2,5 bis 4 Tage
  • Rot – mehr als 4 Tage


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


Und die letzte Zeile mit dem Ergebnis der berechneten Tage:


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


Unser Ergebnis wird wie im Bild unten aussehen.


Endgültige Ansicht des Felds „Arbeitszeit“.


Übrigens können Sie in derselben Formel die Uhrzeit eines beliebigen Status anzeigen. Wenn wir beispielsweise den Status „Pause“ an unsere benutzerdefinierte Funktion übergeben und die Variable timeInProgress durch „Zeit in … – Pause“ zuordnen, berechnen wir die genaue Zeit in der Pause.


Sie können Status kombinieren und einen Eintrag wie „wip: 3.2d |“ erstellen rev: 12d“, das heißt, berechnen Sie die Zeit in der Arbeit und die Zeit in der Überprüfung. Sie sind nur durch Ihre Vorstellungskraft und Ihren Arbeitsablauf begrenzt.


Abschluss

Wir haben eine umfassende Anzahl von Funktionen dieser Formelsprache vorgestellt, die Ihnen dabei helfen werden, etwas Ähnliches zu tun oder etwas völlig Neues und Interessantes für die Analyse von Jira-Aufgaben zu schreiben.


Ich hoffe, der Artikel hat Ihnen geholfen, die Formeln herauszufinden, oder zumindest Ihr Interesse für dieses Thema geweckt. Ich behaupte nicht, dass ich „den besten Code und Algorithmus“ habe. Wenn Sie also Ideen zur Verbesserung der Beispiele haben, würde ich mich freuen, wenn Sie sie teilen!


Natürlich müssen Sie verstehen, dass Ihnen niemand besser über Formeln erzählen kann als die ALM Works-Entwickler. Daher füge ich Links zu deren Dokumentation und Webinaren bei. Und wenn Sie anfangen, mit benutzerdefinierten Feldern zu arbeiten, schauen Sie sich diese regelmäßig an, um zu sehen, welche anderen Funktionen Sie verwenden können.