paint-brush
数式と構造 Jira プラグインで時間と神経を節約@ipolubentcev
769 測定値
769 測定値

数式と構造 Jira プラグインで時間と神経を節約

Ivan Polubentsev36m2023/10/29
Read on Terminal Reader

長すぎる; 読むには

Jira Structure プラグインを使用した数式は驚くべきものになる可能性があります。テーブルを作成し、タスクの作業を簡素化し、リリースとプロジェクトを分析することでゲームが向上します。
featured image - 数式と構造 Jira プラグインで時間と神経を節約
Ivan Polubentsev HackerNoon profile picture
0-item
1-item

Jira の Structure プラグインは、タスクとその分析を伴う日常的な作業に非常に役立ちます。 Jira チケットの視覚化と構造化を新しいレベルに引き上げ、何も設定しなくてもすぐに実行できます。


そして、誰もがそれを知っているわけではありませんが、構造式の機能は単にあなたの心を驚かせる可能性があります。数式を使用すると、タスクの作業を大幅に簡素化できる非常に便利なテーブルを作成できます。そして最も重要なのは、リリース、エピック、プロジェクトのより深い分析を実行するのに役立ちます。


バーンダウン チャートを表示したり、タスクを含むテーブルにチケットの健全性を表示したりしてはどうでしょうか?


この記事では、最も単純な例から始めて、複雑ではあるがかなり便利なケースで終わる独自の数式を作成する方法を説明します。


では、この文章は誰に向けたものなのでしょうか? ALM Works Web サイトの公式ドキュメントがすぐそこにあり、読者が読み込むのを待っているのに、なぜ記事を書くのかと疑問に思う人もいるかもしれません。それは本当です。しかし、私は、Structure にこれほど広範な機能が隠されているということをまったく知らなかった人間の 1 人です。「待てよ、これは最初からオプションだったのか?!」この気づきをきっかけに、数式や構造体を使ってどのようなことができるのかをまだ知らない人もいるのではないかと考えました。


この記事は、数式にすでに慣れている人にも役立ちます。カスタム フィールドを使用するための興味深い実用的なオプションをいくつか学び、おそらくその一部を自分のプロジェクトに借用することができます。ところで、独自の興味深い例がある場合は、コメントで共有していただければ幸いです。 。


各例題は、問題の解説からコードの説明に至るまで、疑問が残らないほど詳細に分析されています。もちろん、説明に加えて、各例はコードによって示されており、分析を深く掘り下げることなく自分で試すことができます。


読む気はないが数式に興味がある場合は、 ALM Works ウェビナーをチェックしてください。基本を 40 分で説明します。情報は非常に圧縮された方法でそこに表示されます。


例を理解するために追加の知識は必要ないため、Jira と Structure を使用したことがある人なら誰でも、問題なく表内の例を繰り返すことができます。


開発者は、Expr 言語でかなり柔軟な構文を提供しました。基本的に、ここでの哲学は「好きなように書けばうまくいく」です。


それでは、始めましょう!


なぜ数式が必要なのでしょうか?

では、そもそもなぜ数式を使用する必要があるのでしょうか?場合によっては、「担当者」や「ストーリー ポイント」などの標準的な Jira フィールドが不足していることが判明することがあります。または、特定のフィールドの量を計算し、バージョンごとに残りの容量を表示し、タスクのステータスが何回変更されたかを調べる必要があります。構造を読みやすくするために、複数のフィールドを 1 つにマージしたい場合もあります。


これらの問題を解決するには数式が必要なので、それを使用してカスタム フィールドを作成します。


最初に行う必要があるのは、数式がどのように機能するかを理解することです。これにより、文字列に何らかの操作を適用できるようになります。多くのタスクを構造にアップロードしているため、式はテーブル全体の各行に適用されます。通常、そのすべての操作は、これらの行のタスクを処理することを目的としています。


したがって、たとえば「担当者」などの Jira フィールドを表示するように数式を設定すると、その数式が各タスクに適用され、別の「担当者」列が作成されます。


数式は、いくつかの基本的なエンティティで構成されます。

  • 変数 - Jira フィールドにアクセスし、中間結果を保存するための変数
  • 組み込み関数 - これらは、日付間の時間数をカウントしたり、配列内のデータをフィルターしたりするなど、事前定義された操作を実行します。
  • カスタム関数 - 独自の計算が必要な場合
  • 結果を表示するさまざまな形式 (オプションとして、「日付/時刻」、「期間」、「数値」、または「Wiki マークアップ」など)。


公式を知る

いくつかの例を通じて数式とその構文についてさらに詳しくなり、6 つの実際的なケースを見ていきます。


各例を確認する前に、どの Structure 機能を使用しているかを示します。まだ説明されていない新機能は太字で表示されます。以下の各例は、複雑さのレベルが増していきます。重要な公式の機能を徐々に紹介するために配置されています。


毎回表示される基本的な構造は次のとおりです。

  • 問題
  • 提案された解決策
  • 使用される構造機能
  • コード例
  • ソリューションの分析


これらの例では、変数マッピングから複雑な配列に至るまでのトピックを取り上げています。

  • タスクの作業の開始日と終了日を表示する 2 つの例 (表示が異なるオプション)
  • 親タスク — 親タスクのタイプと名前の表示
  • サブタスクのストーリー ポイントの合計とこれらの評価のステータス
  • タスクのステータスの最近の変化を示すもの
  • 休日(週末)および特別なステータスを除いた労働時間の計算


数式の作成

まず、数式を使用してカスタム フィールドを作成する方法を見てみましょう。構造の右上部分、すべての列の最後に「+」アイコンがありますので、それをクリックします。表示されるフィールドに「数式…」と入力し、適切な項目を選択します。


数式の作成


数式の保存

数式の保存について説明しましょう。残念ながら、特定の数式を別の場所に保存することはまだできません (私のようにノートブックにのみ保存することのみ)。 ALM Works ウェビナーでチームは、数式のバンクに取り組んでいると述べましたが、現時点では、数式を保存する唯一の方法は、ビュー全体を数式とともに保存することです。


数式の作業が完了したら、構造のビュー (おそらく青いアスタリスクが付いています) をクリックし、[保存] をクリックして現在のビューを上書きする必要があります。または、「名前を付けて保存…」をクリックして新しいビューを作成することもできます。 (新しいビューはデフォルトでプライベートであるため、他の Jira ユーザーが利用できるようにすることを忘れないでください。)


数式は特定のビューの残りのフィールドに保存され、「詳細の表示」メニューの「詳細」タブで確認できます。


バージョン 8.2 以降、Structure には 3 回のクイッククリックで数式を保存できる機能が追加されました。

保存ダイアログは数式編集ウィンドウから利用できます。このウィンドウが開いていない場合は、目的の列にある三角形の ▼ アイコンをクリックしてください。


数式の保存


編集ウィンドウには「保存された列」フィールドが表示され、その右側には青い通知が付いたアイコンがあります。これは、数式の変更が保存されていないことを意味します。このアイコンをクリックし、「名前を付けて保存」オプションを選択します。


保存された列


次に、列 (数式) の名前を入力し、保存するスペースを選択します。個人リストに保存したい場合は「My Columns」。 「グローバル」。式は一般リストに保存され、構造のすべてのユーザーが編集できます。 「保存」をクリックします。


「保存」をクリックします


これで数式が保存されました。任意の構造にロードしたり、どこからでも再保存したりできます。式を再保存すると、それが使用されているすべての構造で式が更新されます。


変数のマッピングも式とともに保存されますが、マッピングについては後ほど説明します。


それでは、例に移りましょう。


タスクの作業の開始日と終了日の表示

最後の 2 列のカスタム日付

問題

タスクのリストと、それらのタスクに取り組む開始日と終了日を含むテーブルが必要です。別の Excel ガントにエクスポートするためのテーブルも必要です。残念ながら、Jira と Structure は、そのままではそのような日付を提供する方法を知りません。

提案されたソリューション

開始日と終了日は、特定のステータスに移行する日です。この場合、これらは「進行中」と「終了」です。これらの日付を取得し、それぞれを別のフィールドに表示する必要があります (これはガントへのさらなるエクスポートに必要です)。したがって、2 つのフィールド (2 つの数式) が存在します。


使用される構造機能

  1. 変数マッピング
  2. 表示形式を調整する機能


コード例

開始日のフィールド:

 firstTransitionToStart


終了日のフィールド:

 latestTransitionToDone


ソリューションの分析

この場合、コードは開始日フィールドの単一変数 firstTransitionToStart と、2 番目のフィールドのlatestTransitionToDone です。


ここでは開始日フィールドに注目してみましょう。私たちの目標は、タスクが「進行中」ステータスに移行した日付を取得することです (これはタスクの論理的な開始に対応します)。そのため、後で推測する必要がないように、変数には「最初の移行」という名前が明示的に付けられています。始める"。


日付を変数にするには、変数のマッピングに移ります。 「保存」ボタンをクリックして数式を保存しましょう。


クリックして数式を保存します


私たちの変数は「変数」セクションに表示され、その横に感嘆符が付いています。構造は、変数を Jira のフィールドにリンクできないことを示しており、それを自分で行う (つまりマッピングする) 必要があります。


変数をクリックして、マッピング インターフェイスに移動します。フィールドまたは必要な操作を選択します。操作「移行日…」を探します。そのためには、選択フィールドに「トランジション」と入力します。一度にいくつかのオプションが提供されますが、そのうちの 1 つが「進行中への最初の移行」です。ただし、マッピングがどのように機能するかを示すために、「移行日…」オプションを選択してみましょう。


マッピング設定


その後、遷移が発生したステータスと、この遷移の順序 (最初または最後) を選択する必要があります。


「ステータス」 - 「ステータス: 進行中」 (またはワークフロー内の対応するステータス) を選択または入力し、「移行」 - 「ステータスへの最初の移行」 (タスクの作業の開始が最初の移行であるため) を選択または入力します。対応するステータスにします。


希望のカテゴリを選択してください



「移行日…」の代わりに最初に提案されたオプション「最初の移行中への移行」を選択した場合、結果はほぼ同じになります。Structure が必要なパラメーターを選択します。唯一のことは、「ステータス: 進行中」の代わりに「カテゴリ: 進行中」になることです。


ステータスカテゴリとステータスの違い


重要な機能に注意してください。ステータスとカテゴリは 2 つの異なるものです。ステータスは特定のステータスであり、明確ですが、カテゴリには複数のステータスを含めることができます。カテゴリは「To Do」、「進行中」、「完了」の 3 つだけです。 Jira では、通常、それぞれグレー、青、緑の色でマークされます。ステータスはこれらのカテゴリのいずれかに属している必要があります。

このような場合、同じカテゴリのステータスとの混同を避けるために、特定のステータスを示すことをお勧めします。たとえば、プロジェクトの「To Do」カテゴリには、「オープン」と「QA キュー」という 2 つのステータスがあります。


例に戻りましょう。


必要なオプションを選択したら、「< 変数リストに戻る」をクリックして、firstTransitionToStart 変数のマッピング オプションを完了します。すべて正しく行うと、緑色のチェック マークが表示されます。


[全般] にはデフォルト値 (ミリ秒単位) が表示されます。


同時に、カスタム フィールドには、まったく日付とは思えない奇妙な数字がいくつか表示されます。この場合、数式の結果は firstTransitionToStart 変数の値になり、その値は 1970 年 1 月からのミリ秒です。正しい日付を取得するには、特定の数式表示形式を選択する必要があります。


形式の選択は編集ウィンドウの最下部にあります。デフォルトでは「一般」が選択されています。日付を正しく表示するには「日付/時刻」が必要です。


「一般」ではなく「日付/時刻」を選択します


2 番目のフィールド、latestTransitionToDone についても、同じことを行います。唯一の違いは、マッピング時にステータスではなく「完了」カテゴリを選択できることです (通常、明確なタスク完了ステータスは 1 つだけであるため)。 「完了」カテゴリへの最新の遷移に興味があるため、遷移パラメータとして「最新の遷移」を選択します。


2 つのフィールドの最終結果は次のようになります。


日付を含む最終ビュー


次に、独自の表示形式を使用して同じ結果を達成する方法を見てみましょう。


独自フォーマットによる日付表示

カスタムフォーマットの例


問題

ガント テーブルには特別な日付表示形式 (「01.01.2022」) が必要なので、前の例の日付表示形式では満足できません。


提案されたソリューション

Structure に組み込まれている関数を使用して、適切な形式を指定して日付を表示してみましょう。


使用される構造の特徴

  1. 変数マッピング
  2. Expr 関数


コード例

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


ソリューションの分析

開発者は、独自の形式で日付を表示するための別の関数を含む、さまざまな関数を提供しています。それを使います。この関数は、日付と目的の形式の文字列という 2 つの引数を使用します。


前の例と同じマッピング ルールを使用して、firstTransitionToStart 変数 (最初の引数) を設定します。 2 番目の引数は形式を指定する文字列で、「dd.MM.yyyy」のように定義します。これは、希望する形式「01.01.2022」に対応します。


したがって、私たちの式はすぐに望ましい形式の結果をもたらします。したがって、フィールド設定で「一般」オプションを維持できます。


作業の終了日を含む 2 番目のフィールドも同様に行われます。その結果、構造は下の図のようになります。


変換後の最終フィード


原則として、数式構文を使用するのに大きな困難はありません。変数が必要な場合は、その名前を記述します。関数が必要な場合は、その名前を記述し、引数 (必要な場合) を渡すだけです。


Structure は未知の名前に遭遇すると、それを変数であると想定し、それ自体をマップしようとするか、私たちに助けを求めます。


ちなみに、重要な注意事項: 構造体では大文字と小文字が区別されないため、firstTransitionToStart、firsttransitiontostart、および firSttrAnsItiontOStarT は同じ変数です。同じルールが関数にも適用されます。明確なコード スタイルを実現するために、例では MSDN の大文字表記規則の規則に従うように努めます。


次に、構文を詳しく調べて、結果を表示するための特別な形式を見てみましょう。


親タスクの名前を表示する

親の名前が概要の前に表示されます


問題

私たちは通常のタスク (タスク、バグなど) と、サブタスクを持つストーリー タイプのタスクを処理します。ある時点で、従業員が特定の期間にどのようなタスクとサブタスクに取り組んだかを調べる必要があります。


問題は、多くのサブタスクが「ストーリーの作業」、「セットアップ」、または「エフェクトのアクティブ化」などと呼ばれるため、ストーリー自体に関する情報を提供しないことです。そして、一定期間のタスクのリストをリクエストすると、他に有用な情報なしで「ストーリーの作業中」という名前のタスクが 12 件も表示されます。


タスクと親タスクの 2 つの列に分割されたリストを含むビューが必要です。これにより、将来的にはそのようなリストを従業員ごとにグループ化できるようになります。


提案されたソリューション

私たちのプロジェクトでは、タスクが親を持つことができる場合、2 つのオプションがあります。

  1. タスクはサブタスクであり、その親はストーリーのみです
  2. タスクは通常のタスク (タスク、バグなど) であり、エピックがある場合とない場合があります。その場合、タスクには親がまったくありません。


したがって、次のことを行う必要があります。

  1. タスクに親があるかどうかを確認する
  2. この親のタイプを調べます
  3. 「[親タイプ] 親名」というスキームに従って、このタスクのタイプと名前を計算します。


情報の認識を簡素化するために、タスク タイプのテキスト、つまり「[ストーリー]」または「[エピック]」のテキストに色を付けます。


使用するもの:

  1. 変数マッピング
  2. 状態
  3. タスクフィールドへのアクセス
  4. 表示形式 — wiki マークアップ


コード例

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


ソリューションの分析

文字列を出力してそこにタスクの種類と名前を挿入するだけでよいのに、なぜ数式が if 条件で始まるのでしょうか?タスクフィールドにアクセスするための普遍的な方法はありませんか?はい。ただし、タスクとエピックでは、これらのフィールドの名前が異なり、アクセスする方法も異なります。これは Jira の機能です。


違いは親検索のレベルから始まります。サブタスクの場合、親は「親課題」Jira フィールドに存在し、通常のタスクの場合、エピックは「エピック リンク」フィールドにある親になります。したがって、これらのフィールドにアクセスするには 2 つの異なるオプションを記述する必要があります。


ここで if 条件が必要になります。 Expr 言語には、条件を処理するさまざまな方法があります。どちらを選択するかは好みの問題です。


「Excel に似た」メソッドがあります。

 if (condition1; result1; condition2; result2 … )


または、より「コードに似た」メソッド:

 if condition1 : result1 else if condition2 : result2 else result3


この例では、最初のオプションを使用しました。次に、コードを簡略化して見てみましょう。

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


明らかな条件が 2 つあります。

  • Parent.Issuetype = “ストーリー”
  • エピックリンク


それらが何をするのかを理解して、最初の Parent.Issuetype=”Story” から始めましょう。


この場合、Parent は「Parent Issue」フィールドに自動的にマッピングされる変数です。上で説明したように、これはサブタスクの親が存在する場所です。ドット表記 (.) を使用して、この親のプロパティ、特に「課題タイプ」Jira フィールドに対応する課題タイプ プロパティにアクセスします。親タスクが存在する場合、Parent.Issuetype 行全体が親タスクのタイプを返すことがわかります。


さらに、開発者がすでに最善を尽くしてくれたため、何も定義したりマップしたりする必要はありませんでした。たとえば、ここには言語で事前定義されているすべてのプロパティ (Jira フィールドを含む) へのリンクがあり、すべての標準変数のリストが表示されます。これも追加の設定なしで安全にアクセスできます。


したがって、最初の条件は、親タスクのタイプが Story であるかどうかを確認することです。最初の条件が満たされない場合、親タスクのタイプはストーリーではないか、親タスクがまったく存在しません。そして、これが 2 番目の条件、EpicLink につながります。


実際、これは「Epic Link」Jira フィールドが入力されているかどうかを確認するときです (つまり、その存在を確認します)。 EpicLink 変数も標準であるため、マッピングする必要はありません。タスクに Epic Link がある場合、条件が満たされることがわかります。


3 番目のオプションは、どの条件も満たされない場合、つまりタスクに親も Epic Link もない場合です。この場合、何も表示せず、フィールドを空のままにします。結果は何も取得されないため、これは自動的に行われます。


条件はわかったので、結果に移りましょう。どちらの場合も、これはテキストと特別な書式設定を含む文字列です。


結果 1 (親が Story の場合):

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


結果 2 (エピック リンクがある場合):

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


どちらの結果も構造が似ています。両方とも、出力文字列の先頭と末尾の三重引用符「"」、開始 {color: COLOR} ブロックと終了 {color} ブロックの色の指定、および$記号。三重引用符は、内部に変数、演算、または書式設定ブロック (色など) があることを構造に伝えます。


最初の条件の結果については、次のようになります。

  1. 親タスクのタイプを転送 ${Parent.Issuetype}
  2. 角括弧「[…]」で囲みます。
  3. すべてを緑色で強調表示し、この式 [${Parent.Issuetype}] を色選択ブロック {color:green}…{color} にラップします。ここには「green」と書きました。
  4. 最後に、スペースで区切られた親タスクの名前 ${Parent.summary} を追加します。


したがって、「[ストーリー] 何らかのタスク名」という文字列が得られます。ご想像のとおり、 Summary も標準変数です。このような文字列を構築するスキームをより明確にするために、公式ドキュメントから画像を共有しましょう。


公式ドキュメントからのカスタム行スキーム


同様の方法で 2 番目の結果の文字列を収集しますが、色は 16 進コードで設定します。エピックの色は「#713A82」であることがわかりました(ちなみに、コメントでエピックのより正確な色を提案できます)。 Epic で変更されるフィールド (プロパティ) を忘れないでください。 「概要」の代わりに「EpicName」を使用し、「親」の代わりに「EpicLink」を使用します。


結果として、私たちの式のスキームは条件の表として表すことができます。


条件:親タスクが存在し、そのタイプがストーリーです。

結果:親タスクの緑色のタイプとその名前を含む行。

条件: [エピック リンク] フィールドが入力されています。

結果:タイプのエピックカラーとその名前を含む行。


デフォルトでは、フィールドで「一般」表示オプションが選択されており、これを変更しない場合、結果は色が変更されず、ブロックが識別されないプレーンテキストのように見えます。表示形式を「Wikiマークアップ」に変更すると文字が変形します。


1) 一般の表示 - デフォルトでは、プレーンテキストをそのまま表示します2) 一般を Wiki マークアップに置き換えます



ここで、Jira フィールドに関連しない変数、つまりローカル変数について見てみましょう。


ストーリーポイントの量を色で表示して計算する

ストーリー ポイントの合計はさまざまな色で強調表示されます


問題

前の例から、サブタスクを持つストーリー タイプのタスクを処理していることがわかりました。これにより、推定に関する特殊なケースが生じます。ストーリー スコアを取得するには、そのサブタスクのスコアを要約し、抽象的なストーリー ポイントで推定します。


このアプローチは異例ですが、私たちにとってはうまくいきました。したがって、ストーリーに見積もりがなくても、サブタスクには見積もりがある場合は問題ありませんが、ストーリーとサブタスクの両方に見積もりがある場合、Structure の標準オプションである「Σ Story Points」が正しく機能しません。


これは、ストーリーの見積もりがサブタスクの合計に追加されるためです。その結果、ストーリーに間違った金額が表示されてしまいます。これを回避し、ストーリーで確立された見積もりとサブタスクの合計との不一致の兆候を追加したいと考えています。


提案されたソリューション

すべてはストーリーで推定が設定されているかどうかに依存するため、いくつかの条件が必要です。


したがって、条件は次のとおりです。


ストーリーに見積もりがない場合、サブタスクの見積もりの合計がオレンジ色で表示され、この値がストーリーにまだ設定されていないことを示します


Story に見積もりがある場合は、それがサブタスクの見積もりの合計に一致するかどうかを確認します。

  • 一致しない場合は、見積もりを赤で色付けし、その横に正しい金額を括弧内に書き込みます。
  • 見積もりと合計が一致する場合は、見積もりを緑色で記入してください


これらの条件の表現は混乱を招く可能性があるため、スキームで表現しましょう。


テキスト表示オプションを選択するためのアルゴリズム


使用される構造の特徴

  1. 変数マッピング
  2. ローカル変数
  3. 集計方法
  4. 条件
  5. 書式付きのテキスト


コード例

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


ソリューションの分析

コードに入る前に、必要な変数を理解するために、スキームをより「コードに近い」方法に変換しましょう。


同じアルゴリズムを変数で書き換えたもの


このスキームから、次のものが必要であることがわかります。


条件変数:

  • isestimate (推定の可用性)
  • isError (ストーリー推定と合計の対応)


テキストの色の 1 つの変数— color


推定の 2 つの変数:

  • sum (サブタスク推定の合計)
  • sp(ストーリーポイント)


さらに、色の変数は、見積もりの入手可能性やライン内のタスクの種類など、多くの条件にも依存します (以下のスキームを参照)。


色を選択するためのアルゴリズム


したがって、色を決定するには、タスク タイプが Story かどうかを示す別の条件変数 isStory が必要になります。


sp 変数 (ストーリーポイント) は標準になります。つまり、適切な Jira フィールドに自動的にマッピングされます。残りの変数は自分で定義する必要があり、それらはローカルになります。


次に、コードでスキームを実装してみましょう。まず、すべての変数を定義しましょう。


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


これらの行は、同じ構文スキーム (with キーワード、変数名、行末のコロン記号「:」) によって結合されます。


ローカル変数宣言の構文


with キーワードは、ローカル変数 (およびカスタム関数、ただし詳細は別の例で) を示すために使用されます。これにより、次にマップする必要のない変数が入ることが式に指示されます。コロン「:」は変数定義の終わりを示します。


したがって、isestimate 変数を作成します (大文字と小文字は重要ではないことに注意してください)。ストーリー ポイント フィールドが入力されているかどうかに応じて、1 または 0 を格納します。これまでに同じ名前のローカル変数を作成していないため (たとえば、storypoints = … :)、storypoints 変数は自動的にマップされます。


未定義変数は、何かが存在しないことを示します (他の言語での null、NaN など)。したがって、式 storypoints != unknown は、「ストーリー ポイント フィールドは記入されていますか?」という質問として読み取ることができます。


次に、すべての子タスクのストーリー ポイントの合計を決定する必要があります。これを行うには、ローカル変数childrenSumを作成します。


 with childrenSum = sum#children{storypoints}:


この合計は、集計関数によって計算されます。 (このような関数については、 公式ドキュメントで読むことができます。) 簡単に言うと、Structure は、現在のビューの階層を考慮して、タスクを使用してさまざまな操作を実行できます。


sum 関数を使用し、それに加えて「#」記号を使用して明確化の子を渡します。これにより、合計の計算が現在の行の子タスクのみに制限されます。中括弧で、どのフィールドを要約するかを示します。ストーリーポイントで見積もりが必要です。


次のローカル変数 isStory には、現在の行のタスク タイプがストーリーであるかどうかという条件が格納されます。


 with isStory = issueType = "Story":


過去の例でおなじみの issueType 変数、つまり、目的のフィールドにそれ自体をマップするタスクのタイプに注目します。これは標準変数であり、以前に with を通じて定義していないため、これを行っています。


次に、isErr 変数を定義しましょう。これは、サブタスクの合計とストーリーの推定値の間の不一致を示します。


 with isErr = isStory AND childrenSum != storypoints:


ここでは、前に作成した isStory ローカル変数と ChildrenSum ローカル変数を使用しています。エラーを通知するには、2 つの条件が同時に満たされる必要があります。課題タイプがストーリー (isStory) であり、かつ (AND) 子ポイントの合計 (childrenSum) がタスク内の設定された推定値 (storypoints) と等しくありません (!=)。 )。 JQL と同様に、AND や OR などの条件を作成するときにリンク ワードを使用できます。


各ローカル変数の行末に「:」記号があることに注意してください。これは、変数を定義するすべての操作の後の最後にある必要があります。たとえば、変数の定義を複数の行に分割する必要がある場合、コロン「:」は最後の操作の後にのみ配置されます。 color 変数の例と同様に、テキストの色です。


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


ここには「:」がたくさんありますが、それらは異なる役割を果たします。 if isStory の後のコロンは、isStory 条件の結果です。 「if 条件 : 結果」という構造を思い出してください。この構造を、変数を定義するより複雑な形式で表現してみましょう。


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


if 条件 2 : 結果 2 else 結果 3 は、いわば最初の条件の結果であることがわかり、最後にコロン「:」があり、変数の定義が完了します。


一見すると、色の定義は複雑に見えるかもしれませんが、実際には、例の冒頭で示した色の定義スキームをここで説明しました。最初の条件の結果として、別の条件 (ネストされた条件、およびその中に別の条件) が開始されるだけです。


ただし、最終結果は以前に提示されたスキームとは少し異なります。


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


スキームのようにコード内で「{color}$sp」を 2 回記述する必要はありません。私たちは物事についてもっと賢くなるでしょう。ブランチでは、タスクに見積もりがある場合、常に {color: $color}$storypoints{color} (つまり、必要な色のストーリー ポイントの見積もりのみ) を表示します。エラーがある場合は、スペースの後に、サブタスクの推定値の合計を行に追加します: ($childrenSum)。


エラーがない場合は追加されません。また、変数を定義するのではなく、条件を通じて最終結果を表示するため、「:」記号がないことにも注目してください。


以下の画像の「∑SP (mod)」フィールドで作業を評価できます。スクリーンショットには、具体的に 2 つの追加フィールドが示されています。


  • 「ストーリー ポイント」 — ストーリー ポイントの推定値 (標準 Jira フィールド)。
  • 「∑ ストーリー ポイント」 — Structure の標準カスタム フィールド。金額が正しく計算されません。


フィールドの最終ビューと、標準のストーリー ポイントおよび ∑ ストーリー ポイント フィールドとの比較


これらの例を参考にして、ほとんどの問題の解決に役立つ構造言語の主な機能を分析しました。次に、さらに 2 つの便利な機能、関数と配列を見てみましょう。独自のカスタム関数を作成する方法を見てみましょう。


最終変更

左側の絵文字に注目してください – これはカスタムフィールドを表しています


問題

場合によっては、スプリント内に多くのタスクがあり、それらの小さな変更を見逃す可能性があります。たとえば、新しいサブタスクや、ストーリーの 1 つが次の段階に移行したという事実を見逃してしまう可能性があります。タスクの最新の重要な変更を通知するツールがあれば便利です。


提案されたソリューション

昨日以降に発生した 3 種類のタスク ステータスの変化に注目します。タスクの作業を開始した、新しいタスクが表示された、タスクが終了した、です。さらに、タスクが「実行しない」という解決策で終了していることを確認すると便利です。


これを行うには、最新の変更を担当する絵文字の文字列を含むフィールドを作成します。たとえば、タスクが昨日作成され、作業を開始した場合、「進行中」と「新しいタスク」という 2 つの絵文字が表示されます。


たとえば、「進行中」ステータスへの移行日や別の「解決」フィールドなど、いくつかの追加フィールドを表示できるのに、なぜこのようなカスタム フィールドが必要なのでしょうか。答えは簡単です。人々は、さまざまな分野に存在し、分析する必要があるテキストよりも絵文字を簡単かつ迅速に認識します。この数式はすべてを 1 か所に収集して分析してくれるため、より有益な作業に費やす労力と時間を節約できます。


さまざまな絵文字が何を担当するかを判断してみましょう。

  • *️⃣ は新しいタスクにマークを付ける最も一般的な方法です
  • ✅ 完了したタスクをマークします
  • ❌ は、キャンセルすることに決めたタスク (「やらない」) を示します。
  • 🚀 は、そのタスクに取り組み始めることを決定したことを意味します (この絵文字は私たちのチームに適していますが、あなたにとっては異なるかもしれません)


使用される構造の特徴

  1. 変数マッピング
  2. Expr 言語メソッド
  3. ローカル変数
  4. 条件
  5. 私たち独自の機能


コード例

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

ソリューションの分析


まず、関心のあるイベントを決定するために必要なグローバル変数について考えてみましょう。昨日以降かどうかを知る必要があります。

  • タスクが作成されました
  • ステータスが「進行中」に変わりました
  • 解決策が見つかりました (そしてその解決策は何ですか)


新しいマッピング変数と一緒に既存の変数を使用すると、これらすべての条件をチェックするのに役立ちます。

  • created — タスクの作成日
  • latestTransitionToProgress — 「進行中」ステータスへの移行の最新の日付 (前の例と同様にマップします)
  • solutionDate — タスクの完了日
  • 解像度 — 解像度テキスト


コードに進みましょう。最初の行は、タスク タイプが存在するかどうかを確認する条件で始まります。


 if defined(issueType):


これは、指定されたフィールドの存在をチェックする組み込みの定義関数を通じて行われます。このチェックは、式の計算を最適化するために行われます。


行がタスクでない場合は、無駄な計算を含む Structure をロードしません。 if の後のコードはすべて結果、つまり if (condition : result) 構造の 2 番目の部分であることがわかります。条件が満たされない場合、コードも機能しません。


now = now(): を含む次の行も、計算を最適化するために必要です。コードのさらに奥では、さまざまな日付と現在の日付を数回比較する必要があります。同じ計算を何度も行わないように、この日付を 1 回計算してローカル変数にします。


「昨日」を分けて保存するのもいいでしょう。便利な「昨日」は経験的に1.3日になりました。これを変数にしましょう: daysScope = 1.3:。


ここで、2 つの日付の間の日数を数回計算する必要があります。たとえば、現在の日付と作業開始日の間です。もちろん、組み込みの DAYS_BETWEEN 関数があり、これが私たちに適しているようです。ただし、たとえばタスクが金曜日に作成された場合、実際には 1.3 日以上経過しているため、月曜日には新しいタスクの通知は表示されません。さらに、DAYS_BETWEEN 関数は合計日数のみをカウントする (つまり、0.5 日が 0 日になる) ため、これも私たちには適していません。


要件を形成しました。これらの日付の間の正確な営業日数を計算する必要があります。カスタム関数がこれに役立ちます。


宣言ローカル関数の構文


その定義構文は、ローカル変数を定義する構文と非常に似ています。唯一の違いと唯一の追加は、最初の括弧内の引数のオプションの列挙です。 2 番目の括弧には、関数が呼び出されたときに実行される操作が含まれています。この関数の定義が唯一の可能な定義ではありませんが、ここではこれを使用します (他の定義は公式ドキュメントにあります)。


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


カスタム workDaysBetween 関数は、引数として渡される今日と開始日の間の営業日を計算します。関数のロジックは非常に単純です。休日の日数を数え、日付間の合計日数からそれを減算します。


休日の日数を計算するには、今日から何週間経過したかを調べる必要があります。これを行うために、各週の数値の差を計算します。この数値は、年の初めからの週番号を提供する Weeknum 関数から取得します。この差に 2 を掛けると、経過した休暇の日数が得られます。


次に、HOURS_BETWEEN 関数は、日付の間の時間数をカウントします。結果を 24 で割って日数を取得し、この数値から休日を差し引くことで、日付間の就業日が得られます。


新しい関数を使用して、一連の補助変数を定義しましょう。定義内の日付の一部はグローバル変数であることに注意してください。これについては、例の冒頭で説明しました。


 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 :


isRecentCreated 変数には、オプションの条件と not(resolution) を追加しました。これは、タスクがすでに閉じられている場合、最近の作成に関する情報には興味がないため、今後の行を簡素化するのに役立ちます。


最終結果は、concat 関数を使用して行を連結して構築されます。


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


条件内の変数が 1 に等しい場合にのみ絵文字が行に含まれることがわかります。したがって、この行ではタスクに対する独立した変更を同時に表示できます。


変更を加えた列の最終ビュー (左側)


休日のない労働日の数え方については触れました。これに関連する別の問題があります。これについては最後の例で分析し、同時に配列について説明します。


休日を除いた労働時間の計算

最終的な表示例


問題

休日を除いて、タスクの実行時間が知りたい場合があります。これは、リリースされたバージョンを分析する場合などに必要です。なぜ休日が必要なのかを理解するため。ただし、1 つは月曜日から木曜日まで実行され、もう 1 つは金曜日から月曜日まで実行されました。このような状況では、暦日の違いから逆のことがわかりますが、タスクが同等であるとは言えません。


残念ながら、「そのままでは」Structure は休日を無視する方法を知りません。「ステータスの時間…」オプションを含むフィールドは、Jira の設定に関係なく、たとえ土曜日と日曜日が休日として指定されていたとしても、結果を生成します。


その結果、私たちの目標は、休日を無視して正確な稼働日数を計算し、ステータスの遷移がこの時間に及ぼす影響を考慮することです。


そしてステータスはそれと何の関係があるのでしょうか?答えさせてください。 3 月 10 日から 3 月 20 日までの間に、タスクが 3 日間稼働していたと計算したとします。ただし、この 3 日間のうち、1 日は停止、1 日半はレビュー中でした。結局、この任務は半日しか働いていなかったことが判明した。


カスタム workDaysBetween 関数は選択した 2 つの日付の間の時間のみを考慮するため、ステータス間の切り替えに問題があるため、前の例の解決策は適していません。


提案されたソリューション

この問題はさまざまな方法で解決できます。この例の方法は、パフォーマンスの点では最も高価ですが、休日やステータスのカウントという点では最も正確です。その実装は、7.4 (2021 年 12 月) より古い Structure バージョンでのみ機能することに注意してください。


したがって、この式の背後にある考え方は次のとおりです。


  1. タスクの開始から完了までに経過した日数を調べる必要があります
  2. これから配列、つまりタスクの作業の開始から終了までの日数のリストを作成します。
  3. 休日のみをリストに含める


すべての日付から週末のみをフィルタリングします (ステータスが異なる場合があります)


  1. これらの休日のうち、タスクが「進行中」ステータスにあったときの休日のみを保持します (バージョン 7.4 の機能「履歴値」がここで役立ちます)。


「進行中」ステータスを残したまま不要なステータスを削除する


  1. 現在のリストには、「進行中」期間と一致する休日のみが含まれています
  2. これとは別に、「進行中」ステータスの合計時間を調べます (組み込みの構造オプション「ステータスの時間…」を通じて)。
  3. この時間から以前に取得した休日の日数を差し引きます。


したがって、休日や追加のステータス間の移行を無視して、タスクの正確な作業時間を取得できます。


使用される構造の特徴

  1. 変数マッピング
  2. Expr 言語メソッド
  3. ローカル変数
  4. 条件
  5. 内部メソッド(独自関数)
  6. 配列
  7. タスクの履歴へのアクセス
  8. 書式付きのテキスト


コード例

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


ソリューションの分析


アルゴリズムをコードに転送する前に、構造の計算を容易にしましょう。


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


行がタスクではない場合、またはそのステータスが「オープン」の場合、それらの行はスキップされます。私たちが関心があるのは、動作するために起動されたタスクのみです。


日付間の日数を計算するには、まず、finishDate と startDate の日付を決定する必要があります。


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


論理的な作業の終了を示すステータスの特定


タスクの完了日 (finishDate) が次であると仮定します。

  • タスクが「QA」ステータスに転送された日付のいずれか
  • 「クローズ」への移行日のいずれか
  • または、タスクがまだ「進行中」の場合は、今日の日付 (経過時間を把握するため)


作業開始日 startDate は、「進行中」ステータスに移行した日付によって決まります。作業中段階に移行せずにタスクが終了してしまう場合があります。このような場合、終了日が開始日とみなされるため、結果は 0 日になります。


ご想像のとおり、toQA、toDone および toProgress は、最初の例と前の例のように、適切なステータスにマップする必要がある変数です。


新しい DEFAULT(toProgress, toDone) 関数も表示されます。 toProgress に値があるかどうかを確認し、ない場合は toDone 変数の値を使用します。


次に、statusWeekendsCount カスタム関数の定義が続きますが、これは日付のリストと密接に関連しているため、後で説明します。後でこのリストに関数を適用する方法を理解できるように、このリストの定義に直接進むことをお勧めします。


次の形式で日付のリストを取得したいとします: [startDate (11.03 としましょう), 12.03, 13.03, 14.03 …finishDate]。 Structure には、すべての作業を行ってくれる単純な関数はありません。そこで、次のようなトリックに頼ってみましょう。


  1. 0 から勤務日数までの一連の数字、つまり [0、1、2、3 … n 勤務日数] から単純なリストを作成します。
  2. タスクの開始日を各数値 (つまり、日) に加算します。結果として、必要なタイプのリスト (配列) を取得します: [開始 + 0 日、開始 + 1 日、開始 + 2 日…開始 + n 日間の作業]。


開始日から論理的な終了日までの日付の初期配列を作成する


次に、それをコードに実装する方法を見てみましょう。配列を使って作業していきます。


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


タスクの作業に何日かかるかを計算します。前の例と同様に、24 による除算と hours_between(startDate,finishDate) 関数を使用します。結果は、overallDays 変数に書き込まれます。


sequenceArray 変数の形式で数値シーケンスの配列を作成します。この配列は SEQUENCE(0,overallDays) 関数によって構築されます。この関数は、0 から totalDays までのシーケンスで必要なサイズの配列を作成するだけです。


次は魔法です。配列関数の 1 つは、map です。指定された操作を配列の各要素に適用します。


私たちのタスクは、各数値 (つまり、その日の数値) に開始日を追加することです。 DATE_ADD 関数はこれを行うことができ、指定された日付に特定の日数、月数、または年数を追加します。


これを理解した上で、文字列を復号してみましょう。


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


sequenceArray 内の各要素に、.map() 関数は DATE_ADD(startDate, $, “day”) を適用します。


DATE_ADD の引数に何が渡されるかを見てみましょう。最初のものは startDate で、目的の数値が追加される日付です。この数値は 2 番目の引数で指定されますが、$ が表示されます。


$ 記号は配列要素を示します。この構造は、DATE_ADD 関数が配列に適用されることを理解しているため、$ の代わりに必要な配列要素 (つまり、0、1、2 ...) が存在します。


最後の引数「day」は、日を追加することを示します。これは、関数が指定内容に応じて日、月、年を追加できるためです。


したがって、datesArray 変数には、作業の開始から完了までの日付の配列が格納されます。


見逃したカスタム関数に戻りましょう。余分な日を除外し、残りを計算します。このアルゴリズムについては、コードを分析する前の例の最初、つまり休日とステータスのフィルタリングに関する段落 3 と 4 で説明しました。


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


カスタム関数に 2 つの引数を渡します。日付の配列 (日付と呼びます) と、必要なステータス (ステータス) です。転送された日付配列に .filter() 関数を適用します。これにより、フィルター条件を通過したレコードのみが配列内に保持されます。この場合、それらは 2 つあり、 と を介して結合されます。フィルターの後に .size() が表示されます。これは、配列に対するすべての操作が完了した後の配列のサイズを返します。


式を単純化すると、array.filter(condition1 およびcondition2).size() のようになります。で、結果的に自分に合った休日、つまり条件をクリアした休日を取得することができました。


両方の条件を詳しく見てみましょう。


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


式 x -> はフィルター構文の一部にすぎず、配列の要素を x と呼ぶことを示します。したがって、x は各条件に表示されます ($ の場合と同様)。 x は、転送された日付配列からの各日付であることがわかります。


最初の条件、weekday(x) > 5 では、日付 x (つまり、各要素) の曜日が 5 より大きく、土曜日 (6) または日曜日 (7) であることが必要です。


2 番目の条件では、history_value を使用します。


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


これはバージョン 7.4 の Structure の機能です。


この関数はタスクの履歴にアクセスし、指定されたフィールドで特定の日付を検索します。この場合、「ステータス」フィールドで日付 x を検索します。 this 変数は関数構文の一部にすぎず、自動的にマップされ、行内の現在のタスクを表します。


したがって、条件では、転送されたステータス引数と、配列内の各日付 x に対してhistory_value 関数によって返される「ステータス」フィールドを比較します。それらが一致する場合、エントリはリストに残ります。


最後の仕上げは、関数を使用して、目的のステータスの日数をカウントすることです。


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


まず、datesArray で「進行中」ステータスの休日が何日あるかを調べてみましょう。つまり、日付のリストと必要なステータスをカスタム関数 statusWeekendsCount に渡します。この関数は、タスクのステータスが「進行中」ステータスと異なるすべての平日とすべての休日を取り除き、リストに残っている日数を返します。


次に、この量を timeInProgress 変数から減算し、「Time in status …」オプションを通じてマップします。


数値 86400000 は、ミリ秒を日に換算する約数です。 .round(1) 関数は、結果を 10 分の 1 に丸める (たとえば「4.1」) ために必要です。そうでない場合は、「4.0999999 …」というタイプのエントリが得られます。


タスクの長さを示すために、color 変数を導入します。タスクに費やした日数に応じて変更します。


  • グレー — 0日
  • 緑 — 0 日を超え 2.5 日未満
  • 赤 — 2.5 ~ 4 日
  • 赤 - 4日以上


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


結果は下の画像のようになります。


[作業時間] フィールドの最終ビュー


ちなみに、同じ計算式で任意のステータスの時間を表示することもできます。たとえば、「一時停止」ステータスをカスタム関数に渡し、「Time in … — Pause」を通じて timeInProgress 変数をマップすると、一時停止中の正確な時間が計算されます。


ステータスを組み合わせて、「wip: 3.2d | wip: 3.2d |」のようなエントリを作成できます。 rev: 12d」、つまり、作業時間とレビュー時間を計算します。制限されるのはあなたの想像力とワークフローだけです。


結論

私たちは、Jira タスクを分析するために、同様のことを実行したり、まったく新しい興味深いものを作成したりするのに役立つ、この数式言語の徹底的な数の機能を紹介しました。


この記事が数式を理解するのに役立つか、少なくともこのトピックに興味を持っていただければ幸いです。私は自分が「最高のコードとアルゴリズム」を持っているとは主張しません。そのため、例を改善する方法についてのアイデアがある場合は、共有していただければ幸いです。


もちろん、ALM Works の開発者以上に数式について教えてくれる人はいないということを理解する必要があります。したがって、ドキュメントとウェビナーへのリンクを添付します。また、カスタム フィールドの使用を開始した場合は、頻繁にチェックして、他にどのような機能が使用できるかを確認してください。