Redmine改善活動では、実装済のコードを読んで学べることもあります


My Redmine

原田です。今回は既存のコードを参考にすることで機能を知ることができることについて書きます。

公式サイトには様々なチケットが登録されています

Redmineの公式サイトには世界各地の皆さんから登録された新機能要望(Feature)・改善(Patch)・不具合(Defect)など様々なチケットが存在します。弊社ではこれらのチケットを基にしてRedmine改善を実施しています。


Redmine公式サイトのチケット一覧画面

作業時間カスタムフィールドに機能を追加しています

追加する機能:プロジェクトごとにカスタムフィールドの有効・無効の切り替えを可能にする

今回は作業時間のカスタムフィールドをプロジェクトごとに有効・無効の切り替えを可能にする機能を追加します。すでにチケットのカスタムフィールドには実装されている機能ですが、作業時間のカスタムフィールドには実装されていません。

この機能はRedmine公式サイトのチケット Feature #18358: Make spent time custom fields usable by project に記載されていますが、 Feature #1767: Make spent time - & project custom fields configurable/switchable per project と内容が重複しているためクローズされています。#1767は作業時間とプロジェクトの2種類のカスタムフィールドに機能を追加したいとの要望なのでパッチが大きくなってしまい、あまりにもパッチの適用範囲が広くなるとRedmine本体に取り込まれなくなる恐れがあります。そこですでにクローズされている#18358に記載の作業時間カスタムフィールドに対してパッチを作成しようと思いました。

またMy Redmineのお客様からも作業時間のカスタムフィールドにもプロジェクトごとの有効/無効の切り替えを可能にしてほしいとご要望を頂いておりました。

同機能をチケットカスタムフィールドの実装済コードから抽出します

今回の作業は0から始めるのではなく、チケットカスタムフィールドに実装済みのコードを参考にして作成します。そこで以下のgrepコマンドを使用してチケットカスタムフィールド(IssueCustomField)に関する処理を抽出します。抽出した結果をそのまま利用することはできませんけど、その多くは今回追加する機能を実装する上で参考になります。

クリックでコードを表示 IssueCustomField を抽出:grepコマンド(一部抜粋)
% grep -rn "IssueCustomField" * --exclude-dir={vendor,public,tmp,doc,log,test,config/locales,files,coverage,spec,node_modules}
app/models/project.rb:58:                          :class_name => 'IssueCustomField',
app/models/project.rb:640:      @all_issue_custom_fields ||= IssueCustomField.
app/models/project.rb:644:      @all_issue_custom_fields ||= IssueCustomField.
app/models/project.rb:658:      @rolled_up_custom_fields ||= IssueCustomField.
app/models/issue_custom_field.rb:20:class IssueCustomField < CustomField
app/models/custom_field.rb:115:    if %w(IssueCustomField TimeEntryCustomField ProjectCustomField VersionCustomField).include?(self.class.name)
app/models/custom_field.rb:118:    if self.is_a?(IssueCustomField)
app/models/issue.rb:692:      IssueCustomField.where(:visible => false).
app/models/query.rb:631:      IssueCustomField.sorted
app/controllers/custom_fields_controller.rb:34:          IssueCustomField.where(is_for_all: false).joins(:projects).group(:custom_field_id).count
app/controllers/projects_controller.rb:97:    @issue_custom_fields = IssueCustomField.sorted.to_a
app/controllers/projects_controller.rb:104:    @issue_custom_fields = IssueCustomField.sorted.to_a
app/controllers/projects_controller.rb:141:    @issue_custom_fields = IssueCustomField.sorted.to_a
app/controllers/projects_controller.rb:200:    @issue_custom_fields = IssueCustomField.sorted.to_a
app/views/custom_fields/_index.html.erb:6:    <% if tab[:name] == 'IssueCustomField' %>
app/views/custom_fields/_index.html.erb:19:      <% if tab[:name] == 'IssueCustomField' %>
app/views/custom_fields/_index.html.erb:21:      <td><%= l(:label_x_projects, :count => @custom_fields_projects_count[custom_field.id] || 0) if custom_field.is_a? IssueCustomField and !custom_field.is_for_all? %></td>
app/views/custom_fields/formats/_text.html.erb:3:<% if @custom_field.class.name == "IssueCustomField" %>
app/views/custom_fields/_form.html.erb:45:    <% if %w(IssueCustomField UserCustomField ProjectCustomField VersionCustomField GroupCustomField TimeEntryCustomField).include?(@custom_field.class.name) &&
app/views/custom_fields/_form.html.erb:50:    <% if %w(IssueCustomField ProjectCustomField).include?(@custom_field.class.name) && @custom_field.format.searchable_supported %>
app/views/custom_fields/_form.html.erb:56:  <% if %w(IssueCustomField TimeEntryCustomField ProjectCustomField VersionCustomField).include?(@custom_field.class.name) %>
app/views/custom_fields/_form.html.erb:60:  <% if @custom_field.is_a?(IssueCustomField) %>
app/views/custom_fields/index.api.rsb:30:      if field.is_a?(IssueCustomField)
app/views/trackers/_form.html.erb:26:<% @issue_custom_fields = IssueCustomField.sorted %>
app/helpers/custom_fields_helper.rb:23:    {:name => 'IssueCustomField', :partial => 'custom_fields/index',
app/helpers/custom_fields_helper.rb:218:      default_type = 'IssueCustomField'
lib/redmine/helpers/time_report.rb:139:        custom_fields += @project.nil? ? IssueCustomField.visible.for_all : @project.all_issue_custom_fields.visible

作業時間カスタムフィールドに組み込むべき機能の洗い出し

上記抽出結果を参考にして実際のチケットカスタムフィールドの動きを確認しました。今回作業時間カスタムフィールド(TimeEntryCustomField)に追加・見直しが必要な画面は以下のものと判断しました。

  1. 作業時間カスタムフィールドの編集画面:プロジェクトごとの有効/無効設定
    作業時間カスタムフィールドの編集画面
  2. 作業時間カスタムフィールドの一覧表示:有効になっているプロジェクト数の表示
    作業時間カスタムフィールドの一覧表示
  3. プロジェクトの設定画面(時間管理):作業時間カスタムフィールドの有効/無効設定
    プロジェクトの設定画面(時間管理)
  4. チケット編集画面・作業時間編集画面:プロジェクトに連動し作業時間カスタムフィールドの表示/非表示の切り替え

上記に加え、内部処理の見直し・テストの作成等も必要になります。

多くの人が試行錯誤して創り出したコードを理解し貢献したい

今回は既に実装されている機能を別のカスタムフィールドに移行しました。実装済のコードを読むことで機能の詳細を知ることができました。このことからもRedmineには多くの人が関与し試行錯誤して創り出したコードが存在していることが分かります。これらのコードを理解・参考にしてRedmine改善活動に貢献したいです。


My Redmine

こちらの記事もオススメです!
他人の書いたコードを読むこと(コードリーディング)は楽しい
Redmine公式サイトに投稿する前に、各自作成したコードをほかの人がコードレビューをします。
学ばず使えるAdobe Premiere Rushはみんなにやさしい
「Adobe Premiere Rush」は初心者にはおすすめの画編集ソフト。
Dockerを使ってRedmineのテーマ作成に挑戦中です
Dockerを使って簡単に環境構築。Redmineのテーマ作成に挑戦しました。
クラウドサービス「My Redmine」 2020年の改善まとめ
2020年中にファーエンドテクノロジーのサービス「My Redmine」で行われた改善をいくつかピックアップして紹介します。
My Redmineを初めて使う方向けにマニュアルを公開しました!
My Redmineを初めて使う方向けに、マニュアルを公開しています。
ファーエンドテクノロジーからのお知らせ(2024/04/17更新)
入門Redmine 第6版 出版記念セミナー「Redmine 8年分の新機能ふりかえり」【2024/4/18開催】
入門Redmine 第6版(2024年3月23日発売)に掲載された新機能に関する内容を執筆者・監修者が紹介します。
My Redmine 初回ご契約で「入門Redmine 第6版」プレゼントのお知らせ
Redmineのクラウドサービス「My Redmine」を初めてご契約いただいたお客様にRedmine解説書「入門Redmine 第6版」を進呈いたします。
2024年度ブランドパートナーに島根県在住のモデル ユイさんを継続起用
ユイさん(モデルスタジオミューズ所属)をファーエンドテクノロジーの2024年度ブランドパートナーとして継続して起用します。
My Redmine スタンダードプランおよびAdminサポートデスクプランの料金改定のお知らせ【2024年4月ご利用分より】
2024年4月ご利用分より、My Redmine スタンダードプラン(民間企業・個人向け及び官公庁向け)とAdminサポートデスクプランの料金を改定いたします。
Redmineの最新情報をメールでお知らせする「Redmine News」配信中
新バージョンやセキュリティ修正のリリース情報、そのほか最新情報を迅速にお届け