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を初めて使う方向けに、マニュアルを公開しています。
ファーエンドテクノロジーからのお知らせ(2021/03/03更新)
プロジェクト管理ツール Redmine互換のオープンソースソフトウェア「RedMica」のすべての管理機能のリファレンスを掲載した『RedMica ユーザーズガイド』を公開しました。
Redmineのクラウドサービス『My Redmine』の提供ソフトウェアをRedMica 1.2にアップデート。二要素認証機能などが追加
『My Redmine』で提供するソフトウェア「RedMica」のバージョンを1.1からを最新バージョンの1.2へアップデートしました。
「FAREND NEWS」2021年第1号 発行
広報紙「FAREND NEWS」2021年第1号を発行しました。弊社サービスの運用・サポートに携わっているスタッフや弊社の取り組みをご紹介します。
オープンソースソフトウェア Redmineで始めるプロジェクト管理(シェルスクリプトマガジン Vol.69 掲載)
誰でも自由に使うことのできるオープンソースのプロジェクト管理ツールRedmineの始め方、使い方をご紹介する記事がシェルスクリプトマガジンに掲載されました。
Redmineの最新情報をメールでお知らせする「Redmine News」配信中
新バージョンやセキュリティ修正のリリース情報、そのほか最新情報を迅速にお届け