Redmineを わかりやすくする

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

# はじめに
Redmineなどのプロジェクト管理システムはシステム開発を行う際には無くてはならないツールになっていますが、コミュニケーションと情報蓄積を融合して組織の力を強力に引き上げる効果から、システム開発以外でもチームワークの基盤として活用している企業も多いと思います。

ITから一般に広まった事と言うと、Googleのトップチームの要因調査から注目された「心理的安全性」が思い当たります。心理的安全性という言葉は今やエンジニアのチームビルディングの域を遥かに超えて、一般企業の人事部門で広く取り上げられているようです。IT・エンジニアの分野で得られた知恵を 越境して一般に利用することの価値は、思った以上に高いのかもしれません。

「心理的安全性」はチームの多様性を生かす(殺さない)為の【必須】の事だと思うのですが、昔はあまり言われなかったように思います。昔はトップの意思に従っていれば良かったし、システムに組織を合わせれば良かったので多様性はそれほど必要なかった、というか、むしろ多様性が邪魔な部分があったのではないかと思います。ところが今、より早く柔軟に変化できた組織だけが生き残る時代の中では、【多様性の中から勝機を見出す】ことが不可欠なのだと思います。

みんな得手・不得手がある中で、得意な部分で思う存分に貢献できれば良いのですが、突き抜けた考えは優秀な仲間でもすぐには解らないのが当然で、そのまま理解されずに忘れ去られたりしたら ものすごく勿体ないし、本人もモヤモヤしてあまりアイデアを発言しなくなったり、。しかし、そんな場面でRedmineがあれば、ちょっと書いて残しておく事ができて、後に理解が進んで大きな貢献になったり、本人も書き残すことで安心できてモヤモヤしなくて済むように思います。

というわけで、組織的な課題のへの対策を目論みつつ、「柔軟性・拡張性が青天井のRedmine」を利用して、チームの共有基盤を構築する機会がありましたので、その構成を共有させて頂きたいと思います。
お気づきの点などコメントやツイッターなどで情報交換・意見交換をさせて頂けると幸いです。

# Redmineを わかりやすくする
システムは利用されて初めて価値が生まれます。特に この手のツールは どれだけ書き込んでもらえるかで相乗的(爆発的)に有用性が違ってきますので、「使いやすさ・わかりやすさ・自分たちに合っている事」が メチャクチャ大事だと思います。

そして「使いやすさ・わかりやすさ・自分たちに合っている事」は、システムを作って「これで完了」という事は絶対にありえなくて、人が使う以上、必ず変わり続けるものだと思います。

ただでさえ新しいシステムにはアレルギーが出やすいものなので、いかに利用者に寄り添ったものに【し続けるか】が勝負かなと思います。
私にとって、継続して最速で改善が可能なのが Redmine ということで、今回もRedmineで構成しています。

メンバーが定期的にRedmineを使うような仕掛けをする事も わかりやすくする手段だと思います。変に強要してしまうとアレルギーが出てしまうので注意が必要ですが、「この事項だけは、必ずチケットに書くようにしませんか？」などとチームで相談できると良いです。
私の場合は、朝会で 各自がRedmineに書いたメモ欄を見ながら話すように出来たので、全員が毎日ログインすることを確保できました。そしてRedmineへの書き込みに少し慣れた頃に議事録回覧の運用をスタートし、これに慣れた頃に、会議で上がったToDoは原則チケット起票することを相談する、といった具合です。

例えば、「その情報は このリンクをクリックするとあります。」と説明しないと使えないようだとダメで、いろいろ前もって覚えなくても、最初に開いた画面を見た中で「このリンクの中だろうな」と感じて最短で目的のページに到達できるようにしたいです。
残念ながら素のRedmineはそのようなUXは弱く、正直わかりにくいのですが、オープンソースという無限の自由度によって、【今のオリジナルな自分達】に合せる事が出来るよ、というのが本記事の趣旨となります。

# 構成の概要
★1: 新たにパスワードを覚える必要が無いように、ADFSと連携してシングルサインオン
★2: マークダウンの書き方を覚えなくても使えるように、CKEditorプラグインでリッチテキスト編集
　→ プラスα： 画像をCtlr+Vで貼れるように自前でプラグインを拡張（これ重要っすよね？）
★3: 添付ファイルの内容も検索できるように、全文検索プラグインを導入
★4: 紛らわしい相対時間表記を無くして絶対時刻のみを表示
★5: 「トラッカー」は説明されないとわからないので「チケット種別」に変更するなど、文言変更
★6: ランディングページは「ホーム」ではなく「プロジェクト一覧」、「概要」ではなく「チケット一覧」
★7: チケット種別は「機能」「バグ」等ではなく「取組案件」「議事録」「台帳」のみ

# 具体的な構成
```
Environment:
  Redmine version                5.0.4.stable.21988
  Ruby version                   3.1.2-p20 (2022-04-12) [x86_64-linux]
  Rails version                  6.1.7
  Environment                    production
  Database adapter               Mysql2
  Mailer queue                   ActiveJob::QueueAdapters::AsyncAdapter
  Mailer delivery                smtp
Redmine settings:
  Redmine theme                  Default
SCM:
  Subversion                     1.13.0
  Git                            2.25.1
  Filesystem                     
Redmine plugins:
  full_text_search               1.0.4
  redmine_add_absolute_time      0.0.1
  redmine_ckeditor               1.2.4
  redmine_issue_templates        1.1.0
  redmine_message_customize      0.1.6
  redmine_omniauth_saml          0.0.1
  redmine_wiki_lists             0.0.11
  redmine_work_time              0.4.1
  view_customize                 3.1.0
```

## プラグイン: redmine_omniauth_saml          0.0.1
★1: 新たにパスワードを覚える必要が無いように、ADFSと連携してシングルサインオン
```
origin	https://github.com/yoshiokaCB/redmine_omniauth_saml.git
commit 068dc5e33aedf804cde6651459506e75abc69a7f
Author: Takayuki Yoshioka <yoshioka@farend.jp>
Date:   Tue Jul 5 09:47:39 2022 +0900
```
Redmine本体と Gemfileの衝突があったので、本体のGemfileの方をコメントアウトして通しています。
```
＜Gemfile＞
-gem "rexml", require: false if Gem.ruby_version >= Gem::Version.new('3.0')
+# gem "rexml", require: false if Gem.ruby_version >= Gem::Version.new('3.0')
```

ADFSだからなのか解りませんが(以前G-Suite連携では問題なかった)、私の環境では gem: omniauth-saml-cespiの中のname_idのチェックで引っかかってしまい、gemのローカルソースを直接修正する必要がありました。(涙ぐましいでしょ？ｗ)
```
＜/usr/local/rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/omniauth-saml-cespi-1.3.1/lib/omniauth/strategies/saml.rb＞
-        if @name_id.nil? || @name_id.empty?
-          raise OmniAuth::Strategies::SAML::ValidationError.new("SAML response missing 'name_id'")
-        end
+#        if @name_id.nil? || @name_id.empty?
+#          raise OmniAuth::Strategies::SAML::ValidationError.new("SAML response missing 'name_id'")
+#        end
```
↑参考: https://rohhie.net/ubuntu18-04-redmine-installation-with-docker/#Redmine-arekore-missing-nameid
このプラグインの肝は config/initializersフォルダに設置する次の設定ファイルですね。SAML認証に必要なURLや署名のフィンガープリント（または公開鍵）、連携する属性(メアドや名前)の識別情報を設定します。
```
＜config/initializers/saml.rb＞
require File.expand_path('../../../plugins/redmine_omniauth_saml/lib/redmine_omniauth_saml', __FILE__)
RedmineOmniauthSaml::Base.configure do |config|
  config.saml = {
    :assertion_consumer_service_url => "https://redmine.*****.jp/auth/saml/callback",
    :issuer                         => "https://redmine.*****.jp/auth/saml/metadata",

    :single_logout_service_url      => "https://redmine.*****.jp/auth/saml/sls",

    :idp_sso_target_url             => "https://*****.com/****/****/", # SSO login endpoint
    :idp_slo_target_url             => "https://*****.com/****/****/",
    :idp_cert_fingerprint           => "**:**:**:**:**:**:**:**:**:**:**:**:**:**:**:**:**:**:**:**", # SSO ssl certificate fingerprint
    # Alternatively, specify the full certifiate:
    #:idp_cert                       => "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",

    :name_identifier_format         => "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
    :name_identifier_value          => "mail", # Which redmine field is used as name_identifier_value for SAML logout

    :attribute_mapping              => {
      # How will we map attributes from SSO to redmine attributes
      :login     => 'extra raw_info http://*****.com/*****/mail',
      :mail      => 'extra raw_info http://*****.com/*****/mail',
      :firstname => 'extra raw_info http://*****.com/*****/givenName',
      :lastname  => 'extra raw_info http://*****.com/*****/sn'
    }
  }

  config.on_login do |omniauth_hash, user|
    # Implement any hook you want here
  end
end
```
↑ exampleファイルをコピーした形だと「uninitialized constant RedmineOmniauthSaml」と出て、Redmineが起動しなくなったので、１行目の require で無理やり突っ込んでいます。

ADFSのメタデータxmlがもらえたら、そのXMLをChromeにドラッグすると見やすく整形表示できます。
今回、その中からconfig/initializers/saml.rbに必要な下記の情報を読み出しました。
````
:idp_sso_target_url ADFSメタデータのIDPSSODescriptor→SingleSignOnService→Location
:idp_slo_target_url ADFSメタデータのIDPSSODescriptor→SingleLogoutService→Location

:login,:mail,:firstname,:lastname 
           ADFSメタデータのIDPSSODescriptor→Attribute→Name の中のそれっぽいもの

:idp_cert_fingerprint 
　　１．ADFSメタデータのIDPSSODescriptor→KeyDescriptor use="signing"→X509Certificateの長い文字列をコピー
　　２．エディタを開いて以下を書き込んで保存
　　　　１行目「-----BEGIN CERTIFICATE-----」
　　　　２行目 コピーした長い文字列
　　　　３行目「-----END CERTIFICATE-----」
　　３．Linuxで「openssl x509 -sha1 -subject -dates -fingerprint -noout -in 書き込んだファイル名」を実行
　　　　→ 「SHA1 Fingerprint=」の後ろがフィンガープリント
````

上記の config/initializers/saml.rb を設置してRedmineを起動したあと、
```
$ wget https://redmine.*****.jp/auth/saml/metadata -o redmine-metadata.xml
```
で Redmine側のメタデータが取得できるので、こちらをADFSの担当者に渡して受け入れ設定してもらいます。

以下の記事が大変参考になりました。
[SAML認証でSAML response missing 'name_id'](https://rohhie.net/ubuntu18-04-redmine-installation-with-docker/#Redmine-arekore-missing-nameid)
[SAML RequestとResponseの解析方法](https://qiita.com/lune0415/items/9e7feaa363d48c3c01f6)
[SAML Response の x.509 電子署名から公開鍵を取り出してみる](
https://qiita.com/Shinya-Yamaguchi/items/383a73e890d638666935)
[RedMica 2.1(Redmine 5.0互換)と Azure AD で SAML認証 をする方法](https://www.farend.co.jp/blog/2022/07/redmica-saml-azuread/)

## プラグイン: redmine_ckeditor               1.2.4
★2: マークダウンの書き方を覚えなくても使えるように、CKEditorプラグインでリッチテキスト編集
　　→ 画像をCtlr+Vで貼れるように自前でプラグインを拡張
```
origin	https://github.com/tkusukawa/redmine_ckeditor.git
commit 786a7cc667dc58a880abe9a58d800d50bf46f0a2
Author: Tomohisa Kusukawa <t@kusukawa.jp>
Date:   Thu Dec 8 05:41:51 2022 +0900
```
最初、クリップボードの画像添付がRedmineの標準機能になっている事を知らずに、ClipboardImagePasteプラグインを使っていたのですが、Redmine 4.1.0で ファイル添付するだけでなくイメージタグの挿入まで出来るようになっている事に気がついて、CKEditorでもクリップボードから画像をペーストできるようにプラグインをForkして拡張してみしました。

試行錯誤して無理やり実現していて、このForkはたぶんメンテしないので、有用そうだったら誰かまたForkするなりして更新して頂けると幸いです。

## プラグイン: full_text_search               1.0.4
★3: 添付ファイルの内容も検索できるように、全文検索プラグインを導入
```
origin	https://github.com/clear-code/redmine_full_text_search.git
commit aaf055fc52e829e27da1ab8bdecfa1d021dde05b
Author: Sutou Kouhei <kou@clear-code.com>
Date:   Wed Dec 14 10:48:20 2022 +0900
```
添付ファイルの解析の為にchupa-text-http-serverを使ってます。chupa-text-http-serverはRAILSなのでRedmineと同様にapache2の passenger_moduleで動かして使っています。
最初、PDFに対応していなくて困ったのですが、
https://github.com/ranguba/chupa-text-http-server/issues/17
で相談しながら調べたら Gemfile.localをexampleからコピーする必要があることが解りました。

## プラグイン: redmine_add_absolute_time      0.0.1
★4: 紛らわしい相対時間の表記を消して絶対時刻のみを表示
```
origin	https://github.com/kenkubomi/redmine_add_absolute_time 
commit 9109e8ebee31a7dedf5ffededb18613886b01f42
Author: Ken Kubomi <com.keniroya@gmail.com>
Date:   Wed Mar 31 10:26:02 2021 +0900
```
このプラグインのままだと、相対時間表記が残るのでローカルだけで次のソース修正をしています。
```
--- a/lib/application_helper_patch.rb
+++ b/lib/application_helper_patch.rb
@@ -1,10 +1,9 @@
 module ApplicationHelperPatch
   def time_tag(time)
-    text = distance_of_time_in_words(Time.now, time)
     if @project
-      " " + link_to("[#{format_time(time)}] #{text}", project_activity_path(@project, :from => User.current.time_to_date(time)), :title => format_time(time))
+      " " + link_to("[#{format_time(time)}]", project_activity_path(@project, :from => User.current.time_to_date(time)), :title => format_time(time))
     else
-      " " + content_tag('abbr', "[#{format_time(time)}] #{text}", :title => format_time(time))
+      " " + content_tag('abbr', "[#{format_time(time)}]", :title => format_time(time))
     end
   end
 end
```
そうすると後に続く文言に「～前に更新」が残っていて、ちぐはぐになるので、後述の redmine_message_customizeプラグインで「前」を除いた文言に修正しています。

## プラグイン: redmine_issue_templates        1.1.0
```
origin	https://github.com/agileware-jp/redmine_issue_templates.git
commit 80c9ebfb4ab882a3c2c1072a364a8cfa29ec80d4
Author: maimai77 <kohta0707@gmail.com>
Date:   Thu Jul 21 09:07:14 2022 +0900
```
いわずと知れた issue_templateプラグインです。
チケット本文に「＜完了条件＞」「＜背景・経緯＞」などの【なるべく書くべき項目】をガイドするようにしています。

## プラグイン: redmine_message_customize      0.1.6
```
origin	https://github.com/farend/redmine_message_customize.git
commit c407f80d9fe363f632654832e4f4d486ea495ddb
Author: Mizuki Ishikawa <remember929@gmail.com>
Date:   Fri Oct 7 13:26:51 2022 +0900
```
文言を変更できる便利なプラグインに感謝です。
「トラッカー」→「チケット種別」、「前に」→(時刻)「に」の為に沢山変更しています。あとは「説明」を「本文」に変えてますね。
```
    label_updated_time: &quot;%{value}に更新&quot;
    label_updated_time_by: &quot;%{author} さんが%{age}に更新&quot;
    label_feeds_access_key_created_on: Atomアクセスキーは%{value}に作成されました
    label_added_time_by: &quot;%{author} さんが%{age}に追加&quot;
    text_no_tracker_enabled: テンプレートはチケット種別毎に設定します。プロジェクトが利用するチケット種別の設定が必要です。
    unused_tracker_at_this_project: このプロジェクトでは利用されていないチケット種別です。必要に応じて、チケット種別とテンプレートの関連付けを修正して下さい。
    text_no_tracker_enabled_for_global: テンプレートはチケット種別毎に設定します。チケット種別の設定をお願いします。
    text_select_apply_tracker: このチケット種別を適用
    label_open_trackers_description: すべてのチケット種別の説明を表示
    error_no_tracker_allowed_for_new_issue_in_project: このプロジェクトにはチケットの追加が許可されているチケット種別がありません
    label_tracker_all: すべてのチケット種別
    setting_default_projects_tracker_ids: 新規プロジェクトにおいてデフォルトで有効になるチケット種別
    error_can_not_delete_tracker: このチケット種別は使用中です。削除できません。
    error_no_tracker_in_project: このプロジェクトにはチケット種別が登録されていません。プロジェクト設定を確認してください。
    text_tracker_no_workflow: このチケット種別にワークフローが定義されていません
    label_trackers_description: チケット種別の説明
    label_tracker_new: 新しいチケット種別
    label_tracker_plural: チケット種別
    label_tracker: チケット種別
    field_description: 本文
    field_tracker: チケット種別
```
## プラグイン: redmine_wiki_lists             0.0.11
```
origin	https://github.com/tkusukawa/redmine_wiki_lists.git
commit 49bb58122eb00e8cd8da096c0f9b1cf29ed998be
Author: tkusukawa <t@kusukawa.jp>
Date:   Sun Apr 18 12:06:37 2021 +0900
```
まだ特に使ってませんが、私の代表作プラグインの一つなので、即座に使えるように入れてますw

## プラグイン: redmine_work_time              0.4.1
```
origin	https://github.com/tkusukawa/redmine_work_time.git
commit cda44e067cae5beedfe59af196521dc8f0d1e9c9
Author: tkusukawa <t@kusukawa.jp>
Date:   Sun Dec 26 16:47:39 2021 +0900
```
これも私の代表作プラグイン。前述の朝会メモを書く場所として重宝しています。
今のところ自分だけが毎日工数を入力してるので、お一人様 no tikecket, no work 状態なのですが、それでも、チケットの工数で一日の業務時間が埋まるという事は 自分の業務が【漏れなくチケット起票されている】訳で、何とも言えない安心感を毎日感じています。

## プラグイン: view_customize                 3.1.0
```
origin	https://github.com/onozaty/redmine-view-customize.git
commit 8dbda2ab7d3aebc188f6cb1f2282335473a6a8a7
Author: onozaty <onozaty@gmail.com>
Date:   Sun Jun 26 22:31:52 2022 +0900
```
こちらも 言わずと知れた ViewCustomizeですね。
「使いやすさ・わかりやすさ・自分たちに合っている事」を作り込むための最重要プラグインです。
```
select * from redmine.view_customizes
```
にて、DBから全設定を引っこ抜きましたので以下に共有します。
（まだそんなに増えてないですw）
```
path_pattern: /$
project_pattern:
insertion_position: html_head
customize_type: javascript
comments: ホームをプロジェクトにリダイレクト
code:
$(function() {
    var ref = document.referrer;
    if(!ref.match(/\/projects$/)) { // projectsからでなければ
        location.href = &quot;/projects&quot;; // projectsにリダイレクト
    }
})
```
★6: ランディングページは「ホーム」ではなく「プロジェクト」、「概要」ではなく「チケット」
↑ うちは「ホーム」に情報が無くて、デフォルトでホームを表示されても利用者は「は？」ってなるので、ホームへのアクセスは一旦「プロジェクト一覧」にリダイレクト。
ただし、プロジェクト一覧からのホームへの遷移だったら「本当にホームを表示したいのだろう」ということでリダイレクトせずにホームを表示。
```
path_pattern: /projects/[^/]+$
project_pattern: 
insertion_position: html_head
customize_type: javascript
comments: プロジェクト トップはチケットにリダイレクト
code:
$(function() {
    var ref = document.referrer;
    var paths = location.pathname.match(/\/projects\/(.*)$/)
    if(paths[1] == 'new') return;

    if(!ref.match(/.*\/issues$/)) { // issueからでなければ
      location.href = &quot;/projects/&quot;+paths[1]+&quot;/issues&quot;; // issueにリダイレクト
    }
})
```
★6: ランディングページは「ホーム」ではなく「プロジェクト」、「概要」ではなく「チケット」
↑ うちはプロジェクトの「概要」タブに情報が無いので、デフォルトで「概要」を表示されても利用者は「は？」ってなるので、一旦チケット一覧にリダイレクト。
ただし、チケット一覧から概要への遷移なら「本当に概要を表示したいのだろう」としてリダイレクトせずに概要を表示。
（新規チケットの場合に同じパスパターンになって、チケット一覧に飛ばされると永遠にチケットを作れないので除外）
```
path_pattern: /issues
project_pattern:
insertion_position: html_head
customize_type: javascript
comments: エンターキャンセル
code:
$(function(){
    // form内のエンターキー入力を無視
    // トラッカー・ステータス操作時のDOM操作で破棄されないようdocumentにイベントを設定
    $(document).on('keypress', '#issue-form input[type=&quot;text&quot;]', function(event) {
        if(event.keyCode == 13) {
            return false;
        }
    });
});
```
↑ エンターキーによって不用意に「送信」にならないようにキャンセル

```
path_pattern: /issues/[0-9]+
project_pattern:
insertion_position: html_head
customize_type: javascript
comments: 会議録は最初から本文編集モード。リビジョンを履歴に表示。
code:
function showUpdateShowDescriptionEdit() {
  showAndScrollTo(&quot;update&quot;, &quot;issue_notes&quot;);
  $('#issue_description_and_toolbar').show();
  $('#issue_description_and_toolbar').prev().hide();
}

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

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

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

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

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

        // 昇順でコメントがリビジョンより後なら→探索を終了
        if(reverse == 0 &amp;&amp; historyDate &gt; changesetDate) { break; }
        // 降順でコメントがリビジョンより前なら→探索を終了
        if(reverse == 1 &amp;&amp; historyDate &lt; 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(&quot;history&quot;, this.href);

  setTimeout(function() {
    mergeRevisonTab();

    switch($('#issue_tracker_id').val()) {
      case &quot;5&quot;: // 会議トラッカー
        if($('#issue_status_id').val() != 5) { // 完了でなければ
          showUpdateShowDescriptionEdit();
        }
    }
  }, 500);
});
```
パスパターンが同じだったので混ぜてしまってますが、
１．
トラッカーが「議事録」だった場合は自動的に説明欄まで編集モードにする、
つまり「あなたは議事録への追記修正と回覧チェックを入れるんですよ」状態に自動でしてます。
２．
リポジトリのリビジョン履歴はコメントよりも重要で、視覚的に「いつ何をしたか」の時系列が目に入るようにしたかったので、リビジョン履歴を別タブ表示するのではなく、「履歴」にコメントと並べて表示するようにしてます。
→ [Redmineチケットの「コメント」と「関係しているリビジョン」を同時に表示してみた](https://qiita.com/tkusukawa/items/85924291887c41e8133e)

```
path_pattern: /my/page$
project_pattern: 
insertion_position: html_head
customize_type: javascript
comments: 外部から直接マイページだったらprojectsにリダイレクト
code:
$(function() {
   var ref = document.referrer;
    // alert(ref)

    if(!ref.match(/redmine.*****.jp/)) { // 外部から直接マイページだったら
        location.href = &quot;/projects&quot;; // projectsにリダイレクト
    }
})
```
↑ ADFSでのSSO SAML認証の後、何故かマイページにランディングするようになり、うちの場合はランディングはプロジェクト一覧のほうが良いので、プロジェクト一覧にリダイレクト。

# おわりに
今の自分に出来る限りのRedmineを構築する機会に恵まれましたので、構成を共有させて頂きました。
お気づきのことや感じたこと、「ここどうやってるの？」等がありましたら、コメントやツイッタでコミュニケーション頂けると幸いです。

# おまけ（概念が回帰して難解なので本文外）
【私にとって、継続して最速で改善が可能なのが Redmine】と書きました。その理由として「柔軟性・拡張性が青天井のRedmine」と書きました。実はそれに加えての理由として（だからこそでもあるのですが）【完全にバージョン管理を行う方法を知っている】という事が とても大きいです。

繰り返し最速でシステムに手を加えると、何をやったか解らない事が出てくるのは必至です。
それに対して、ちゃんとバージョン管理が出来ていて【変更点を即座に列挙できる状況】は ものすごい安心感になります。

具体的には、Redmine本体と各プラグインについて、元々subversionやGitで公開されているものなので、そのリポジトリのリビジョンIDと ローカルの変更Diffの内容を毎日出力していて、その結果自体をバージョン管理しています。
このことで、Redmine本体やプラグインに手を加えた内容/日時/理由・背景(関連チケット)、そしてそれ以降どこも変更してない状況であることを担保しています。

実際のチェックバッチは以下となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15871/69a1c888-ccd0-53e0-f6cc-a68a19353d03.png)
見て解るとおり、チェックバッチ自体もバージョン管理していて、毎日diffチェックされるので意図しない変更や改ざんが無いことを担保しています。

このチェックバッチの実行結果は以下です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15871/58dfd2a0-aae6-ccb5-04b3-ef2fb9affbca.png)
これも毎日diffチェックされるので、Redmineに意図しない不用意な変更(戻し忘れ)がないことが保証されます。

同様に、ViewCustomizeの設定内容をDBから抜いてバージョン管理したり、サーバの各サービスのステータスやパッケージリストをバージョン管理したり、Linux上のさまざまな設定ファイルやコードをYggdrasilというバージョン管理に乗せています。それらの内容が不意に変わった際には最後のコミットからのDiffの内容がアラート通知されるようにしています。
このようにすることで、管理対象としている全てのファイルについての過去の履歴と【今現在その時点から変更が無いこと】を確保しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15871/66bf63dd-7c7d-c719-bfdc-618a649cd25b.png)
このトレーサビリティの完全な把握の安心感があって初めて、ちょこまかと最速改善しても破綻しないのである、というお話でした。