From 9d52602bc7868a83bea21b8101e7e79e8f21b02a Mon Sep 17 00:00:00 2001 From: ishikawa999 <14245262+ishikawa999@users.noreply.github.com> Date: Fri, 3 Jul 2026 15:29:47 +0900 Subject: [PATCH] Add REST API support for show, update, and destroy on custom fields --- app/controllers/custom_fields_controller.rb | 39 ++++++++-- app/views/custom_fields/show.api.rsb | 53 +++++++++++++ config/routes.rb | 2 +- .../custom_fields_controller_test.rb | 5 ++ .../api_test/custom_fields_test.rb | 75 +++++++++++++++++++ 5 files changed, 166 insertions(+), 8 deletions(-) create mode 100644 app/views/custom_fields/show.api.rsb diff --git a/app/controllers/custom_fields_controller.rb b/app/controllers/custom_fields_controller.rb index 66f2b12b9..476ff13d8 100644 --- a/app/controllers/custom_fields_controller.rb +++ b/app/controllers/custom_fields_controller.rb @@ -23,8 +23,8 @@ class CustomFieldsController < ApplicationController before_action :require_admin before_action :build_new_custom_field, :only => [:new, :create] - before_action :find_custom_field, :only => [:edit, :update, :destroy] - accept_api_auth :index + before_action :find_custom_field, :only => [:show, :edit, :update, :destroy] + accept_api_auth :index, :show, :update, :destroy def index respond_to do |format| @@ -44,6 +44,13 @@ class CustomFieldsController < ApplicationController end end + def show + respond_to do |format| + format.html {render_404} + format.api + end + end + def new @custom_field.field_format = 'string' if @custom_field.field_format.blank? @custom_field.default_value = nil @@ -76,24 +83,42 @@ class CustomFieldsController < ApplicationController redirect_back_or_default edit_custom_field_path(@custom_field) end format.js {head :ok} + format.api {render_api_ok} end else respond_to do |format| format.html {render :action => 'edit'} format.js {head :unprocessable_content} + format.api {render_validation_errors(@custom_field)} end end end def destroy + error = false begin - if @custom_field.destroy - flash[:notice] = l(:notice_successful_delete) - end + error = true unless @custom_field.destroy rescue - flash[:error] = l(:error_can_not_delete_custom_field) + error = true + end + + respond_to do |format| + format.html do + if error + flash[:error] = l(:error_can_not_delete_custom_field) + else + flash[:notice] = l(:notice_successful_delete) + end + redirect_to custom_fields_path(:tab => @custom_field.class.name) + end + format.api do + if error + render_api_errors(l(:error_can_not_delete_custom_field)) + else + render_api_ok + end + end end - redirect_to custom_fields_path(:tab => @custom_field.class.name) end private diff --git a/app/views/custom_fields/show.api.rsb b/app/views/custom_fields/show.api.rsb new file mode 100644 index 000000000..c3bcf36ac --- /dev/null +++ b/app/views/custom_fields/show.api.rsb @@ -0,0 +1,53 @@ +api.custom_field do + api.id @custom_field.id + api.name @custom_field.name + api.description @custom_field.description + api.customized_type @custom_field.class.customized_class.name.underscore if @custom_field.class.customized_class + api.field_format @custom_field.field_format + api.regexp @custom_field.regexp + api.min_length @custom_field.min_length + api.max_length @custom_field.max_length + api.is_required @custom_field.is_required? + api.is_for_all @custom_field.is_for_all? + api.is_filter @custom_field.is_filter? + api.searchable @custom_field.searchable + api.multiple @custom_field.multiple? + api.default_value @custom_field[:default_value] + api.default_value_mode(@custom_field.default_value_mode.presence || 'fixed_date') if @custom_field.field_format == 'date' + api.visible @custom_field.visible? + api.editable @custom_field.editable? + + values = @custom_field.possible_values_options + if values.present? + api.array :possible_values do + values.each do |label, value| + api.possible_value do + api.value value || label + api.label label + end + end + end + end + + if @custom_field.is_a?(IssueCustomField) + api.array :projects do + @custom_field.projects.each do |project| + api.project :id => project.id, :name => project.name + end + end + + api.array :trackers do + @custom_field.trackers.each do |tracker| + api.tracker :id => tracker.id, :name => tracker.name + end + end + end + + if %w(IssueCustomField TimeEntryCustomField ProjectCustomField VersionCustomField).include?(@custom_field.class.name) + api.array :roles do + @custom_field.roles.each do |role| + api.role :id => role.id, :name => role.name + end + end + end +end diff --git a/config/routes.rb b/config/routes.rb index ce15da420..07e2e6a12 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -359,7 +359,7 @@ Rails.application.routes.draw do post 'update_issue_done_ratio' end end - resources :custom_fields, :except => :show do + resources :custom_fields do resources :enumerations, :controller => 'custom_field_enumerations', :except => [:show, :new, :edit] put 'enumerations', :to => 'custom_field_enumerations#update_each' end diff --git a/test/functional/custom_fields_controller_test.rb b/test/functional/custom_fields_controller_test.rb index 9ed072d45..a11bc1f41 100644 --- a/test/functional/custom_fields_controller_test.rb +++ b/test/functional/custom_fields_controller_test.rb @@ -31,6 +31,11 @@ class CustomFieldsControllerTest < Redmine::ControllerTest assert_select 'table.custom_fields' end + def test_show_html_format_should_respond_with_404 + get :show, :params => {:id => 1} + assert_response :not_found + end + def test_new_without_type_should_render_select_type get :new assert_response :success diff --git a/test/integration/api_test/custom_fields_test.rb b/test/integration/api_test/custom_fields_test.rb index c476d0466..b9178eb5e 100644 --- a/test/integration/api_test/custom_fields_test.rb +++ b/test/integration/api_test/custom_fields_test.rb @@ -145,4 +145,79 @@ class Redmine::ApiTest::CustomFieldsTest < Redmine::ApiTest::Base assert_equal '-3', element.at('default_value').text end end + + test "GET /custom_fields/:id.xml should return the custom field" do + get '/custom_fields/1.xml', :headers => credentials('admin') + assert_response :success + assert_equal 'application/xml', response.media_type + + assert_select 'custom_field' do + assert_select 'id', :text => '1' + assert_select 'name', :text => 'Database' + assert_select 'customized_type', :text => 'issue' + assert_select 'possible_values[type=array]' + end + end + + test "GET /custom_fields/:id.json should return the custom field" do + get '/custom_fields/1.json', :headers => credentials('admin') + assert_response :success + + json = ActiveSupport::JSON.decode(response.body) + assert_equal 'Database', json['custom_field']['name'] + end + + test "GET /custom_fields/:id.xml should respond with 404 for an invalid custom field" do + get '/custom_fields/999.xml', :headers => credentials('admin') + assert_response :not_found + end + + test "GET /custom_fields/:id.xml should respond with 403 for a non administrator" do + get '/custom_fields/1.xml', :headers => credentials('jsmith') + assert_response :forbidden + end + + test "PUT /custom_fields/:id.xml should update the custom field" do + put( + '/custom_fields/1.xml', + :params => {:custom_field => {:name => 'Renamed'}}, + :headers => credentials('admin') + ) + assert_response :no_content + assert_equal 'Renamed', CustomField.find(1).name + end + + test "PUT /custom_fields/:id.xml with invalid parameters should respond with errors" do + put( + '/custom_fields/1.xml', + :params => {:custom_field => {:name => ''}}, + :headers => credentials('admin') + ) + assert_response :unprocessable_content + assert_select 'errors error', :text => "Name cannot be blank" + end + + test "PUT /custom_fields/:id.xml should respond with 403 for a non administrator" do + put( + '/custom_fields/1.xml', + :params => {:custom_field => {:name => 'Renamed'}}, + :headers => credentials('jsmith') + ) + assert_response :forbidden + end + + test "DELETE /custom_fields/:id.xml should destroy the custom field" do + assert_difference 'CustomField.count', -1 do + delete '/custom_fields/1.xml', :headers => credentials('admin') + end + assert_response :no_content + assert_nil CustomField.find_by_id(1) + end + + test "DELETE /custom_fields/:id.xml should respond with 403 for a non administrator" do + assert_no_difference 'CustomField.count' do + delete '/custom_fields/1.xml', :headers => credentials('jsmith') + end + assert_response :forbidden + end end -- 2.54.0