Redmineチケットの「コメント」と「関係しているリビジョン」を同時に表示してみた

本記事は、[Redmine Advent Calendar 2021](https://adventar.org/calendars/6283)の12/14の記事です。

# 結果
Redmineのチケットの表示を下記のようにすることが出来ました。
・コメントとGit等のリビジョンを統合して時系列表示
・「履歴」「コメント」「関係しているリビジョン」等のタブ選択を非表示

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15871/24514f90-ab51-1eff-def2-74cbd07c5e09.png)

# View Customizeスクリプト
[View Customizeプラグイン](https://github.com/onozaty/redmine-view-customize)を使って実現しました。

パスのパターン: /issues/[0-9]+
挿入位置: 全ページのヘッダ
種別: JavaScript

redmine4のとき
``` javascript:
$(function () {
  issue_id = ViewCustomize.context.issue.id;

  //「関係しているリビジョン」をクリックした時の処理を行ってリビジョンの表示要素をDOMに取得
  getRemoteTab('changesets', 
               '/issues/'+issue_id+'/tab/changesets', 
               '/issues/'+issue_id); 

  // 表示を「履歴」(コメント一覧)に戻す
  showIssueHistory("history", this.href); 

  // タブを非表示
  $('#history .tabs').hide(); 

  // 以下、時間差実行
  setTimeout(function(){ 
    // コメントの表示要素のリストを取得
    histories = $('#tab-content-history').children("div");
    historyIndex = 0; // コメントを順に確認するポインタ

    // コメントの0番目と1番目の時刻を比較して昇順表示か降順表示かを判定(1コの時は無視)
    if(histories.eq(0).find('a[data-absolute-date]').attr("data-absolute-date") >
      histories.eq(1).find('a[data-absolute-date]').attr("data-absolute-date")) {
      reverse = 1; // 降順表示
    } else {
      reverse = 0; // 昇順表示
    }
    
    // リビジョンの表示要素を順に処理
    $('#tab-content-changesets').children("div").each(function() { 
      // リビジョンの時刻を取得
      changesetDate = $(this).find('a[data-absolute-date]')
                             .attr("data-absolute-date"); 

      // リビジョン表示要素を挿入するコメント表示要素の位置を探索
      while(1) {
        // ポインタ位置のコメントの時刻を取得
        historyDate = histories.eq(historyIndex).find('a[data-absolute-date]')
                                                .attr("data-absolute-date"); 

        // コメントポインタがケツまで来てたら→探索を終了
        if(!historyDate) { break; } 

        // 昇順でコメントがリビジョンより後なら→探索を終了
        if(reverse == 0 && historyDate > changesetDate) { break; } 
        // 降順でコメントがリビジョンより前なら→探索を終了
        if(reverse == 1 && historyDate < changesetDate) { break; } 

        historyIndex ++; // コメントポインタを前進
      };

      if(!historyDate) { // コメントポインタがケツまで来てたら
        // 履歴表示の最後にリビジョン表示要素を追加
        $('#tab-content-history').append($(this)); 
      }
      else { // それ以外＝コメントの途中なら
        // コメント表示要素の前にリビジョン表示要素を追加
        histories.eq(historyIndex).before($(this)); 
      }

    });
  },500);
});
```

redmine5のとき
``` javascript:

function mergeRevisonTab() {
    // コメントの表示要素のリストを取得
    histories = $('#tab-content-history').children("div");
    historyIndex = 0; // コメントを順に確認するポインタ

    // コメントの0番目と1番目の時刻を比較して昇順表示か降順表示かを判定(1コの時は無視)
    if(histories.eq(0).find('a[title^=20]').attr("title") >
      histories.eq(1).find('a[title^=20]').attr("title")) {
      reverse = 1; // 降順表示
    } else {
      reverse = 0; // 昇順表示
    }

    // リビジョンの表示要素を順に処理
    $('#tab-content-changesets').children("div").each(function() { 
      // リビジョンの時刻を取得
      changesetDate = $(this).find('a[title^=20]')
                             .attr("title"); 

      // リビジョン表示要素を挿入するコメント表示要素の位置を探索
      while(1) {
        // ポインタ位置のコメントの時刻を取得
        historyDate = histories.eq(historyIndex).find('a[title^=20]')
                                                .attr("title"); 

        // コメントポインタがケツまで来てたら→探索を終了
        if(!historyDate) { break; } 

        // 昇順でコメントがリビジョンより後なら→探索を終了
        if(reverse == 0 && historyDate > changesetDate) { break; } 
        // 降順でコメントがリビジョンより前なら→探索を終了
        if(reverse == 1 && historyDate < changesetDate) { break; } 

        historyIndex ++; // コメントポインタを前進
      };

      if(!historyDate) { // コメントポインタがケツまで来てたら
        // 履歴表示の最後にリビジョン表示要素を追加
        $('#tab-content-history').append($(this)); 
      }
      else { // それ以外＝コメントの途中なら
        // コメント表示要素の前にリビジョン表示要素を追加
        histories.eq(historyIndex).before($(this)); 
      }
    });
}

$(window).on('load', function() {
  issue_id = ViewCustomize.context.issue.id;

  //「関係しているリビジョン」をクリックした時の処理を行ってリビジョンの表示要素をDOMに取得
  getRemoteTab('changesets', 
               '/issues/'+issue_id+'/tab/changesets', 
               '/issues/'+issue_id); 
  // 表示を「履歴」(コメント一覧)に戻す
  showIssueHistory("history", this.href); 

  setTimeout(function() {
    mergeRevisonTab();
  }, 500);
});
```

# 背景・動機
久しぶりにRedmineを新規構築したら、チケットに連携したGit等のリビジョン履歴をタブをクリックして表示する仕様になってました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15871/644395ee-ddc5-f210-6d5b-cc388d6d3cc5.png)

そうすると、チケットを開いた時にリビジョン履歴がすぐ目に入らず、自分としては状況がわかりにくいので、チケットコメントの時系列とリビジョン履歴を同時に見れるようにしたい！
【これは人類の気付きをサポートする重要なことだ！】(夜更しハイテンション)
という思いが沸々と湧き上がり、夜なべをしてViewCustomizesスクリプトを書きました。という共有です。

今回、Redmineを初めて使う人たちが使うので、最初どう使って良いか解りづらいと言われているRedmineをシンプルに使ってほしい、という思いもありました。

# 環境情報

```
Environment:
  RedMica version                1.3.1.stable (based on Redmine 4.2.1.devel.20401)
  Ruby version                   2.7.4-p191 (2021-07-07) [x86_64-linux]
  Rails version                  5.2.6
  Environment                    production
  Database adapter               Mysql2
  Mailer queue                   ActiveJob::QueueAdapters::AsyncAdapter
  Mailer delivery                smtp
SCM:
  Subversion                     1.7.14
  Git                            1.8.3.1
  Filesystem                     
Plugins:
  clipboard_image_paste          1.13
  full_text_search               1.0.4
  redmine_banner                 0.3.4
  redmine_ckeditor               1.2.3
  redmine_hangouts_chat_integration 0.0.4
  redmine_issue_templates        1.1.0
  redmine_message_customize      0.1.1
  redmine_omniauth_saml          0.0.1
  redmine_wiki_lists             0.0.11
  redmine_work_time              0.4.1
  view_customize                 3.0.0
```

# おわりに
ちょっと思い立って、試行錯誤しながら、、ブラウザの開発者画面と にらめっこして、js/jQueryの書き方をググりまくって、たくさんの人の恩恵をうけて出来た事なので、もし何かの参考になれば とても幸いです。
もっと良いやり方とか、マズイ部分とか、思った事を言ってもらえると嬉しいです。