Redmineでは、チケットの属性として任意の入力項目を「カスタムフィールド」として追加することができる。選択リスト形式や文字列、数字など、様々なフォーマットを指定したり必須にするかどうかなども設定でき、また、どのトラッカーで利用するのかも設定できる。かなりなことは、設定できるが、今一つ足りないのが、ステータス別に必須にするかどうかを設定できないことだ。
例えば、(企業組織ではPDCAを回すために、)バグ管理票なるもので、そのバグの原因や修正区分、そのバグの混入工程や発見工程なんかの属性を管理することがある。これをチケットで管理する場合、カスタムフィールドでこれらの属性を定義することになる。そこで問題となるのが、Redmineのカスタムフィールドで「必須」にするとどのステータスにおいても必須になってしまう仕様だ。
Redmineの場合、必須チェックはステータスと連動させることができない。例えば前述の「原因」項目の場合、チケット登録時(バグ発見時)には不明な場合がほとんどで選択できない。ただ、チケット入力時には不要な情報でも、クローズする時には必須にしたい項目でもある。
このような、プロジェクト管理要素の強い属性は、チケットを処理している時には、(エンジニアにとってあまり重要に感じられないこともあり、)必須にしないと選択し忘れたり、後で入れようと思ったりされてしまう。ただ、数千ものバグを抱えるようなプロジェクトでは、前述の属性を後でまとめて入力するのは不可能に近い。やはりこれらは遅くともチケットクローズ時に確実に付加させてておくべき情報だ。
そのためには、ステータス別にカスタムフィールドの入力チェックをでききるようにしなければならない。以下がその改造。
<Redmineでの設定>
- データベースマイグレートが必要。(rake db:migrate RAILS_ENV="production")
- 管理→カスタムフィールで、チケットのカスタムフィールドに対し、デフォルト(プロジェクト登録時)で必須にするかどうかを設定する。プロジェクト登録時に必須/任意の設定は可能。
- プロジェクトメニューの設定→概要 で、ステータス別に必須にするカスタムフィールドにチェックを入れることで、必須設定する。
<Redmineソースの改造>(v.1.3.0ベース)
- 管理→カスタムフィールド の設定画面に、デフォルトで必須にするステータスを選択するチェックボックスを表示する。
- プロジェクトメニューの設定→概要 の画面に、チケットのカスタムフィールド毎に、ステータス別に必須/任意設定するチェックボックスを表示する。
- チケット登録・更新時に、ステータスに応じて必須設定されたカスタムフィールドの入力チェックを行う。
- custom_fieldsテーブルに、デフォルトで入力必須にするかどうかを管理するカラムを追加。
- プロジェクト別にステータス別必須カスタムフィールドを管理する新規テーブルを作成。
end
def create
+ return unless validate_required_fields(@issue.status.id)
call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
if @issue.save
attachments = Attachment.attach_files(@issue, params[:attachments])
call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
:
:
def update
update_issue_from_params
+ return unless validate_required_fields(params[:issue][:status_id].to_i)
if @issue.save_issue_with_child_records(params, @time_entry)
render_attachment_warning_if_needed(@issue)
flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
attributes
end
:
:
+ def validate_required_fields(issue_status)
+ if params.nil? || params.empty? || ((params.include? 'issue') == false) || ((params[:issue].include? 'custom_field_values') == false)
+ return true
+ end
+ required_custom_fields = get_required_custom_field
+ issue_custom_fields = IssueCustomField.find(:all)
+
+ #check issue_status.to_s if this is required_custom_fields's Hash key or not.
+ tmpkeys = required_custom_fields.keys
+ tmpflg = 0
+ for i in 0 .. tmpkeys.length-1
+ if tmpkeys[i] == issue_status.to_s
+ tmpflg = 1
+ break
+ end
+ end
+ if tmpflg == 0
+ # issue_status.to_s is not Hash key
+ return true
+ end
+
+ if required_custom_fields[issue_status.to_s].size == 0 || issue_custom_fields == nil
+ return true
+ end
+ required_custom_fields[issue_status.to_s].each do |required|
+ issue_custom_fields.each do |issue_custom_field|
+ if required == issue_custom_field.name
+ unless (params[:issue][:custom_field_values].include? issue_custom_field.id.to_s)
+ break
+ end
+ if params[:issue][:custom_field_values][issue_custom_field.id.to_s] == ""
+ tmp = t 'activerecord.errors.messages.blank'
+ flash.now[:error] = required + " " + tmp
+ @priorities = IssuePriority.all
+ if @action_name == "update"
+ render :template => 'issues/edit', :layout => !request.xhr?
+ elsif @action_name == "create"
+ render :template => 'issues/new', :layout => !request.xhr?
+ end
+ return false
+ end
+ end
+ end
+ end
+ return true
+ end
end
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.all
@project = Project.new(params[:project])
+ @required_custom_field = get_required_custom_field
end
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
:
:
:conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
:order => 'name')
@source_project = Project.find(params[:id])
+ @issue_status = IssueStatus.find( :all ,:order => 'position')
+ @required_custom_field = get_required_custom_field
if request.get?
@project = Project.copy_from(@source_project)
if @project
:
:
@project.safe_attributes = params[:project]
if validate_parent_id && @project.copy(@source_project, :only => params[:only])
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
+ save_required_custom_field
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'projects', :action => 'settings', :id => @project
elsif !@project.new_record?
:
:
@trackers = Tracker.all
@repository ||= @project.repository
@wiki ||= @project.wiki
+ @required_custom_field = get_required_custom_field
end
def edit
:
:
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
respond_to do |format|
format.html {
+ save_required_custom_field
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'settings', :id => @project
}
:
:
end
true
end
+
+ def save_required_custom_field
+ hash_others = Hash.new
+ tmp_array = []
+ hash_others = {}
+ @issue_status = IssueStatus.find( :all ,:order => 'position')
+ for tmp_issue_status in @issue_status
+ tmp_array = []
+ if params[:required_custom_field] != nil
+ if params[:required_custom_field].include? tmp_issue_status.id.to_s
+ tmp_array = params[:required_custom_field][tmp_issue_status.id.to_s].keys
+ end
+ end
+ hash_others[tmp_issue_status.id.to_s] = tmp_array
+ end
+
+ required_custom_field = RequiredCustomField.find(:first, :conditions => ["project_id = ?", @project.id])
+ if required_custom_field == nil
+ required_custom_field = RequiredCustomField.new(:project_id => @project.id,
+ :others => hash_others)
+ else
+ required_custom_field.others = hash_others
+ end
+ if required_custom_field.save == false
+ required_custom_field.connection.rollback_db_transaction
+ end
+ end
+
end
sharing = 'none' unless Version::VERSION_SHARINGS.include?(sharing)
l("label_version_sharing_#{sharing}")
end
+
+ def required_custom_field_checkbox_tag(project, custom_field)
+ s = ''
+ @issue_status.each do |status|
+ custom_field.is_required ? checked = true : checked = false
+ unless @required_custom_field[status.id.to_s] == nil
+ @required_custom_field[status.id.to_s].each do |cf|
+ checked = true if cf == custom_field.name
+ end
+ end
+ s << "\n"
+ s << check_box_tag('required_custom_field[' + status.id.to_s + '][' + custom_field.name + ']',
+ custom_field.id,
+ checked,
+ :disabled => custom_field.is_required?) + "\n | \n"
+ end
+ s
+ end
+
+ def get_required_custom_field
+ required_custom_field = RequiredCustomField.find(:first, :conditions => ["project_id = ?", @project.id])
+ @issue_status = IssueStatus.find( :all ,:order => 'position')
+ required_custom_field_other = Hash.new
+ if required_custom_field.nil?
+ # Registering new project, set default required custom feilds.
+ @issue_status.each do |status|
+ cfnames = []
+ @issue_custom_fields.each do |cf|
+ cfnames << cf.name if !cf.required_issue_status_ids.nil? && YAML.load(cf.required_issue_status_ids).include?(status.id.to_s)
+ end
+ required_custom_field_other[status.id.to_s] = cfnames
+ end
+ else
+ # On existing project's setting
+ @issue_status.each do |status|
+ required_custom_field_other[status.id.to_s] = required_custom_field[status.id.to_s]
+ end
+ end
+ required_custom_field_other
+ end
end
+class RequiredCustomField < ActiveRecord::Base
+ serialize :others
+ def [](attr_name)
+ if attribute_present? attr_name
+ super
+ else
+ others ? others[attr_name] : nil
+ end
+ end
+end
<% end %>
<%= hidden_field_tag "custom_field[tracker_ids][]", '' %>
</fieldset>
- <p><%= f.check_box :is_required %></p>
+ <p><%= f.check_box :is_required %>
+ <%= l(:label_all) %>: <i><%= l(:text_required_all) %></i><br />
+ <%=l(:label_applied_status)%>: <i><%= l(:text_required_specific) %></i><br />
+ <% for status in IssueStatus.find(:all ,:order => 'position') -%>
+ <%= check_box_tag "custom_field[required_issue_status_ids][]",
+ status.id.to_s,
+ (YAML.load(@custom_field.required_issue_status_ids).include?(status.id.to_s) unless @custom_field.required_issue_status_ids.nil?),
+ :onchange => 'if (this.checked){$("custom_field_is_required").checked = false;}'
+ %>
+ <%= h(status.name) %><br />
+ <% end -%>
+ </p>
+
<p><%= f.check_box :is_for_all %></p>
<p><%= f.check_box :is_filter %></p>
<p><%= f.check_box :searchable %></p>
</fieldset>
<% end %>
<% end %>
+
+<fieldset class="box"><legend><%=l(:field_is_required)%></legend>
+ <table class="list">
+ <thead>
+ <tr>
+ <th><%=l(:label_custom_field)%></th>
+ <% for tmp_issue_status in @issue_status %>
+ <th><%=tmp_issue_status%></th>
+ <% end %>
+ </tr>
+ </thead>
+ <tbody>
+ <% @issue_custom_fields.each do |custom_field| %>
+ <tr class="<%= cycle("odd", "even") %>">
+ <td class="name" align="left"><%= custom_field.name %></td>
+ <%= required_custom_field_checkbox_tag(@project, custom_field) %>
+ </tr>
+ <% end %>
+ </tbody>
+ </table>
+</fieldset>
+
<!--[eoform:project]-->
text_scm_command_version: Version
text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it.
text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
+ text_required_all: "Will be required field in all satatuses, and be unchangeable from requierd field in the project setting."
+ text_required_specific: "Will be default required field in checked statuses, and be changeable for requierd or not in the project setting."
default_role_manager: Manager
default_role_developer: Developer
text_scm_command_version: バージョン
text_scm_config: バージョン管理システムのコマンドをconfig/configuration.ymlで設定できます。設定後、Redmineを再起動してください。
text_scm_command_not_available: バージョン管理システムのコマンドが利用できません。管理画面にて設定を確認してください。
+ text_required_all: "すべてのステータスで必須項目とし、プロジェクトの設定では変更できないようになります。"
+ text_required_specific: "チェックしたステータスをデフォルトの必須項目とし、プロジェクトの設定で変更できるようになります。"
default_role_manager: 管理者
default_role_developer: 開発者
+class AddCustomFieldsRequiredIssueStatusIds < ActiveRecord::Migration
+ def self.up
+ add_column :custom_fields, :required_issue_status_ids, :text
+ end
+
+ def self.down
+ remove_column :custom_fields, :required_issue_status_ids
+ end
+end
+class CreateRequiredCustomFields < ActiveRecord::Migration
+ def self.up
+ create_table :required_custom_fields do |t|
+ t.column "project_id", :integer, :default => 0, :null => false
+ t.column "others", :text
+ end
+ add_index :required_custom_fields, :project_id
+ end
+
+ def self.down
+ drop_table :required_custom_fields
+ end
+end