Redmine wikiマクロを作成する方法６（プロジェクトに所属する全てのユーザーのチケット状態の確認）

# Redmine wikiマクロを作成する方法６（プロジェクトに所属する全てのユーザーのチケット状態の確認）

[Redmine Advent Calendar 2024](https://adventar.org/calendars/10311)の16日目の記事として作成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3633321/5261d4bb-31c0-571c-4dad-9d1bf9ce56cf.png)


今年のRedmine Advent Calendarに空白が多く残っており、クリスマスが過ぎる前までになんとか空白の日付の記事を書いて、空白の日付を埋めることを優先して記事を作成しました。そのため、濃くない内容の記事になるかもしれませんが、ご容赦ください。
(やっと[Redmine Advent Calendar 2024](https://adventar.org/calendars/10311)の全ての枠が埋まりました。今年の[Redmine Advent Calendar 2024](https://adventar.org/calendars/10311)への投稿はこれが最後です。)


## 完成形
前回の[Redmine wikiマクロを作成する方法５（特定のユーザーのチケット状態の確認）](https://qiita.com/yam_asaki/items/4138dc71505c7c33f63f)では、wikiマクロの引数にユーザーのログインIDを引数にして、該当のユーザーが担当者になっているチケットの状態を確認しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3633321/262979e0-a2b6-c43e-5d05-7c46f3eabccd.png)

そのため、複数のユーザーがプロジェクトに所属している場合、各ユーザーごとにwikiマクロを指定する必要があり、煩雑さがあった。今回の開発では、下図のように、wikiマクロでプロジェクトIDを引数にして、該当プロジェクトに所属している全てのユーザーのチケット状態を表示します。これにより、各ユーザーごとにwikiマクロを指定する必要がなくなり、かつ、該当プロジェクトのチケット情報のみ表示されるので、前回は該当プロジェクト以外のプロジェクトのチケット情報も表示されてしまい、セキュリティ上の懸念事項があったが、それも解消された。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3633321/4cb7369f-8164-ed0f-abc0-0f8e1754aea3.png)

[Redmine wikiマクロを作成する方法５（特定のユーザーのチケット状態の確認）](https://qiita.com/yam_asaki/items/4138dc71505c7c33f63f)をベースに開発をしたため、[Redmine wikiマクロを作成する方法５（特定のユーザーのチケット状態の確認）](https://qiita.com/yam_asaki/items/4138dc71505c7c33f63f)で紹介したプログラムソースを流用できる部分は流用している。

* * *
## 背景

Redmineには自作のwikiマクロを作成して登録することができます。
その方法としてはプラグインを作成しますがwikiマクロの登録のためのプラグインは比較的開発が簡単であるため、プラグインを作ったことがない方でも簡単に試してみることができるメリットがあります。

wikiマクロでは色々な処理をすることができるためRedmineの構造を理解したり、Rubyを理解したり、プラグインの仕組みを理解したりするためには良い教材になるかと思います。
今年のRedmine Advent Calendarを通して、何回かに分けてwikiマクロの作り方を紹介したいと思います。

今回の記事では「Redmine wikiマクロを作成する方法６」として「プロジェクトに所属する全てのユーザーのチケット状態の確認」する方法を説明します。


## 前提条件：
使用するwikiマクロ登録用プラグインは[Redmine wikiマクロを作成する方法１](https://qiita.com/yam_asaki/items/1dfd349e078fc6d0f764)で説明したプラグインを使用します。/plugins/wiki_macro_plugin/init.rbの「init.rb」ファイルにwikiマクロを登録します。


---

### 1. **マクロを実装**

1. /plugins/wiki_macro_plugin/init.rbの「`init.rb`」ファイルを編集して、Wikiマクロを登録します：
   ```ruby
   Redmine::Plugin.register :wiki_macro_plugin do
    省略
   end

    Redmine::WikiFormatting::Macros.register do
      desc "Display task lists for all members of a project, including unassigned tasks with task numbers"
      macro :project_members_tasks do |obj, args|
        # プロジェクトの取得
        project = Project.find_by_identifier(args.first || obj.project.identifier)
        return "プロジェクトが見つかりません。" unless project
    
        # プロジェクトメンバーの取得
        members = project.members
        content = "#{project.name}のメンバーとタスク一覧<br>"
    
        # メンバーごとのタスクを収集
        members.each do |member|
          user = member.user
          content << "<h3>#{user.name}のタスク</h3>"
    
          # ユーザーのタスクを取得
          issues = project.issues.where(assigned_to_id: user.id)
          if issues.any?
            content << "<table border='1' style='border-collapse: collapse; width: 100%;'>"
            content << "<tr><th>No</th><th>#</th><th>題名</th><th>開始日</th><th>期日</th><th>状態</th><th>進捗率</th></tr>"
    
            issues.each_with_index do |issue, index|
              days_info = if issue.due_date
                            days_left = (issue.due_date - Date.today).to_i
                            if days_left > 0
                              "期日まで #{days_left}日"
                            elsif days_left == 0
                              "今日が期日"
                            else
                              "<font color='red'>#{days_left.abs}日 遅れ</font>"
                            end
                          else
                            "期日未設定"
                          end
    
              content << "<tr>"
              content << "<td>#{index + 1}</td>"
              content << "<td>#{link_to("##{issue.id}", issue_path(issue))}</td>"
              content << "<td>#{issue.subject}</td>"
              content << "<td>#{issue.start_date || 'N/A'}</td>"
              content << "<td>#{issue.due_date || 'N/A'}</td>"
              content << "<td>#{days_info}</td>"
              content << "<td>#{issue.done_ratio}%</td>"
              content << "</tr>"
            end
    
            content << "</table>"
          else
            content << "タスクがありません。"
          end
        end
    
        # 担当者が未指定のタスク
        unassigned_issues = project.issues.where(assigned_to_id: nil)
        if unassigned_issues.any?
          content << "<h3>担当者未指定</h3>"
          content << "<table border='1' style='border-collapse: collapse; width: 100%;'>"
          content << "<tr><th>No</th><th>#</th><th>題名</th><th>開始日</th><th>期日</th><th>状態</th><th>進捗率</th></tr>"
    
          unassigned_issues.each_with_index do |issue, index|
            days_info = if issue.due_date
                          days_left = (issue.due_date - Date.today).to_i
                          if days_left > 0
                            "期日まで #{days_left}日"
                          elsif days_left == 0
                            "今日が期日"
                          else
                            "<font color='red'>#{days_left.abs}日 遅れ</font>"
                          end
                        else
                          "期日未設定"
                        end
    
            content << "<tr>"
            content << "<td>#{index + 1}</td>"
            content << "<td>#{link_to("##{issue.id}", issue_path(issue))}</td>"
            content << "<td>#{issue.subject}</td>"
            content << "<td>#{issue.start_date || 'N/A'}</td>"
            content << "<td>#{issue.due_date || 'N/A'}</td>"
            content << "<td>#{days_info}</td>"
            content << "<td>#{issue.done_ratio}%</td>"
            content << "</tr>"
          end
    
          content << "</table>"
        end
    
        content.html_safe
      end
    end
   ```

以下、コードを1行ずつ解説します。

---

```ruby
Redmine::WikiFormatting::Macros.register do
```
- **説明**: Redmineに新しいWikiマクロを登録するための宣言。このブロック内でカスタムマクロを定義します。

---

```ruby
  desc "Display task lists for all members of a project, including unassigned tasks with task numbers"
```
- **説明**: マクロの説明文を設定します。

---

```ruby
  macro :project_members_tasks do |obj, args|
```
- **説明**: `:project_members_tasks`という名前のWikiマクロを定義します。
- **引数**:
  - `obj`: 現在のWikiページやプロジェクトなどのコンテキストオブジェクト。
  - `args`: マクロに渡された引数の配列（プロジェクト識別子を引数とします。）。

---

```ruby
    project = Project.find_by_identifier(args.first || obj.project.identifier)
```
- **説明**: 引数または現在のプロジェクトからプロジェクトを取得します。

---

```ruby
    return "プロジェクトが見つかりません。" unless project
```
- **説明**: プロジェクトが見つからない場合、エラーメッセージを返して処理を終了します。

---

```ruby
    members = project.members
```
- **説明**: 指定されたプロジェクトのメンバー（ユーザー情報を含む）を取得します。

---

```ruby
    content = "#{project.name}のメンバーとタスク一覧<br>"
```
- **説明**: 出力の冒頭に、プロジェクト名を含む見出しを設定します。

---

```ruby
    members.each do |member|
```
- **説明**: プロジェクトの各メンバーについて繰り返し処理を開始します。

---

```ruby
      user = member.user
```
- **説明**: メンバー情報からユーザーオブジェクトを取得します。

---

```ruby
      content << "<h3>#{user.name}のタスク</h3>"
```
- **説明**: メンバーごとにユーザー名を見出しとして表示します。

---

```ruby
      issues = project.issues.where(assigned_to_id: user.id)
```
- **説明**: 指定されたユーザーに割り当てられたプロジェクト内のチケットを取得します。

---

```ruby
      if issues.any?
```
- **説明**: 該当するチケットがある場合、処理を進めます。

---

```ruby
        content << "<table border='1' style='border-collapse: collapse; width: 100%;'>"
```
- **説明**: HTMLテーブルの開始タグを作成します。

---

```ruby
        content << "<tr><th>No</th><th>#</th><th>題名</th><th>開始日</th><th>期日</th><th>状態</th><th>進捗率</th></tr>"
```
- **説明**: テーブルヘッダー行を追加します。列名はタスク番号、ID、題名、開始日、期日、状態、進捗率です。

---

```ruby
        issues.each_with_index do |issue, index|
```
- **説明**: ユーザーのチケットごとに処理を繰り返し、チケットにインデックスを付けます。

---

```ruby
          days_info = if issue.due_date
```
- **説明**: チケットに期日が設定されているかを確認します。

---

```ruby
            days_left = (issue.due_date - Date.today).to_i
```
- **説明**: 今日の日付を基準に、期日までの日数を計算します。

---

```ruby
            if days_left > 0
              "期日まで #{days_left}日"
```
- **説明**: 期日が将来の場合、残りの日数を表示します。

---

```ruby
            elsif days_left == 0
              "今日が期日"
```
- **説明**: 期日が今日の場合、「今日が期日」と表示します。

---

```ruby
            else
              "<font color='red'>#{days_left.abs}日 遅れ</font>"
```
- **説明**: 期日が過ぎている場合、遅延日数を赤字で表示します。

---

```ruby
          else
            "期日未設定"
```
- **説明**: 期日が設定されていない場合、「期日未設定」と表示します。

---

```ruby
          content << "<tr>"
```
- **説明**: HTMLテーブルの新しい行を開始します。

---

```ruby
          content << "<td>#{index + 1}</td>"
```
- **説明**: タスクの番号を表示します（インデックス＋1）。

---

```ruby
          content << "<td>#{link_to("##{issue.id}", issue_path(issue))}</td>"
```
- **説明**: チケットIDをリンクとして表示します。

---

```ruby
          content << "<td>#{issue.subject}</td>"
```
- **説明**: チケットの題名を表示します。

---

```ruby
          content << "<td>#{issue.start_date || 'N/A'}</td>"
```
- **説明**: チケットの開始日を表示します。未設定の場合は「N/A」と表示します。

---

```ruby
          content << "<td>#{issue.due_date || 'N/A'}</td>"
```
- **説明**: チケットの期日を表示します。未設定の場合は「N/A」と表示します。

---

```ruby
          content << "<td>#{days_info}</td>"
```
- **説明**: 期日までの残り日数や遅延情報を表示します。

---

```ruby
          content << "<td>#{issue.done_ratio}%</td>"
```
- **説明**: チケットの進捗率をパーセントで表示します。

---

```ruby
          content << "</tr>"
```
- **説明**: HTMLテーブルの行を終了します。

---

```ruby
        end
```
- **説明**: チケットのループ処理を終了します。

---

```ruby
        content << "</table>"
```
- **説明**: HTMLテーブルの終了タグを追加します。

---

```ruby
      else
        content << "タスクがありません。"
```
- **説明**: 該当するチケットがない場合にメッセージを表示します。

---

```ruby
    end
```
- **説明**: メンバーごとのループ処理を終了します。

---

```ruby
    unassigned_issues = project.issues.where(assigned_to_id: nil)
```
- **説明**: 担当者が未設定のチケットを取得します。

---

```ruby
    if unassigned_issues.any?
```
- **説明**: 未割り当てのチケットが存在する場合の処理を進めます。

---

以降、未割り当てチケットの処理はメンバーごとのチケット処理と同様の形式で進みます。

---

```ruby
    content.html_safe
```
- **説明**: 生成したHTMLをそのまま出力するよう指定します。

---

```ruby
  end
end
```
- **説明**: マクロ定義を終了します。












---

### 2. **マクロの使い方:**

wikiページで下記の形式でwikiマクロを実行します。
   
   ```ruby
    {{project_members_tasks(project_identifier)}}
   ```

   - project_identifierにプロジェクトIDを引数として渡します。引数を省略すると現在のwikiが所属しているプロジェクトが引数として渡されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3633321/001bf0fe-9432-f928-326c-89b3433c3188.png)

下図のようにwikiマクロの実行結果が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3633321/991a1e65-0403-81f4-41c1-39c348ede06c.png)

「＃」欄のチケット番号をクリックすると、該当のチケットの詳細ページに移動できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3633321/054baeb6-72c6-4b8d-8763-a5f47ea2ce77.png)

* * *
## 感想

* wikiマクロ登録用のプラグインがあれば、簡単にwikiマクロを登録できることが分かります。
* プロジェクトに所属している全てのユーザーが各々担当しているチケットの開始日、期日、状態、進捗率を一覧で確認することができます。
* プロジェクトに所属している全てのユーザーのチケットの取得など、Redmineプラグインで必要な要素技術の習得に寄与します。
* 該当チケットの詳細ページに簡単に移動することが可能。
* 引数としてプロジェクト情報を与えているため、該当プロジェクトに所属するチケットだけを表示させます。他のプロジェクトのチケット情報は表示しません。


* * *
### この記事の作成者の紹介
* * *
山崎進
* Redmine、Jquery、JavaScript,Rails、Ruby、SQL、VBA、RPAの開発を行なっています。
* 自動化、業務の効率化に高い関心があります。
* 下記の媒体で情報を発信しています。Redmineのプラグインの開発などに対応が可能ですので、お気軽にご連絡ください。

　* Qiita：https://qiita.com/ankosoft
　* Twitter：https://twitter.com/yamasaki24
　* Redmine Advent Calendarで記事投稿
　* redmine.tokyoで講演多数
　* Redmine Japan Vol.1 前夜祭、Redmine Japan Vol.3で講演
　* https://ankosoft.co.jp/blog/
　* https://technology.ankosoft.co.jp/

* * *
### 関連記事（2024年のRedmine Advent Calendarの記事です。）

 *　[Redmine wikiマクロを作成する方法１](https://qiita.com/yam_asaki/items/1dfd349e078fc6d0f764)

 *　[Redmine wikiマクロを作成する方法２（完了チケットの割合をグラフで表示）](https://qiita.com/yam_asaki/items/5e09143f93104c5590a1)

 *　[Redmine wikiマクロを作成する方法３（ステータスごとのチケットの割合をグラフで表示）](https://qiita.com/yam_asaki/items/ff108e081dc421fe2b10)

 *　[Redmine wikiマクロを作成する方法４（ステータスごとのチケットの割合を一つのグラフで表示）](https://qiita.com/yam_asaki/items/d0f0bba1b0caa892bd46)

 *　[Redmine wikiマクロを作成する方法５（特定のユーザーのチケット状態の確認）](https://qiita.com/yam_asaki/items/4138dc71505c7c33f63f)

 *　[Redmine wikiマクロを作成する方法６（プロジェクトに所属する全てのユーザーのチケット状態の確認）](https://qiita.com/yam_asaki/items/e80f9252a50caee14f1d)



### 関連記事（2023年のRedmine Advent Calendarの記事です。）

 *　[JqueryでRedmineのメニューにアイコンを入れる方法](https://qiita.com/yam_asaki/items/32097598cb0a653dd916)

 *　[JqueryでRedmineのメニューを閉じたり開いたりする方法](https://qiita.com/yam_asaki/items/5ac4a1127beeed53059c)

　*　[JqueryでRedmineの「活動ページ」をもっと便利に(タイトルを開閉したり、曜日を入れたり)](https://qiita.com/yam_asaki/items/718513b52b30b30f65bc)

　*　[JqueryでRedmineの「活動ページ」をもっと便利に②(フィルタリング機能と移動機能)](https://qiita.com/yam_asaki/items/2428610fd89bc73ce0ab)

　*　[JqueryでRedmineの「wikiページ」をもっと便利に(フィルタリング機能と移動機能)](https://qiita.com/yam_asaki/items/a6a32c45dbbc72a28836)

　*　[JqueryでRedmineの「チケットページ」をもっと便利に(年ごと、月ごと、四半期ごとの集計機能の開発)](https://qiita.com/yam_asaki/items/176d07588793b3e66b5d)

　*　[RedmineのパスワードやログインIDを忘れた時の復旧方法](https://qiita.com/yam_asaki/items/6881c92b30dc85f589ad)

　*　[「rails console」を使ってRedmine上のスケジュールを一括変更する方法](https://qiita.com/yam_asaki/items/1d41dc8653773f3522e6)