diff --git a/app/contracts/project_custom_field_project_mappings/base_contract.rb b/app/contracts/project_custom_field_project_mappings/base_contract.rb
index 106abf73b07f..0ed19cf6d5a1 100644
--- a/app/contracts/project_custom_field_project_mappings/base_contract.rb
+++ b/app/contracts/project_custom_field_project_mappings/base_contract.rb
@@ -46,7 +46,7 @@ def not_required
# enabling a custom field which is required happens in an after_save hook within the custom field model itself
return if model.project_custom_field.nil? || !model.project_custom_field.required?
- errors.add :custom_field_id, :invalid
+ errors.add :custom_field_id, :cannot_delete_mapping
end
def visbile_to_user
diff --git a/app/controllers/admin/settings/project_custom_fields_controller.rb b/app/controllers/admin/settings/project_custom_fields_controller.rb
index a23139a70ddc..188395ad404a 100644
--- a/app/controllers/admin/settings/project_custom_fields_controller.rb
+++ b/app/controllers/admin/settings/project_custom_fields_controller.rb
@@ -30,6 +30,8 @@ module Admin::Settings
class ProjectCustomFieldsController < ::Admin::SettingsController
include CustomFields::SharedActions
include OpTurbo::ComponentStream
+ include ApplicationComponentStreams
+ include FlashMessagesOutputSafetyHelper
include Admin::Settings::ProjectCustomFields::ComponentStreams
menu_item :project_custom_fields_settings
@@ -41,6 +43,7 @@ class ProjectCustomFieldsController < ::Admin::SettingsController
before_action :prepare_custom_option_position, only: %i(update create)
before_action :find_custom_option, only: :delete_option
before_action :project_custom_field_mappings_query, only: %i[project_mappings unlink]
+ before_action :find_unlink_project_custom_field_mapping, only: :unlink
# rubocop:enable Rails/LexicallyScopedActionFilter
def show_local_breadcrumb
@@ -68,13 +71,18 @@ def edit; end
def project_mappings; end
def unlink
- project = Project.find(permitted_params.project_custom_field_project_mapping[:project_id])
- project_custom_field_mapping = @custom_field.project_custom_field_project_mappings.find_by(project:)
delete_service = ProjectCustomFieldProjectMappings::DeleteService
- .new(user: current_user, model: project_custom_field_mapping)
+ .new(user: current_user, model: @project_custom_field_mapping)
.call
- delete_service.on_success { render_unlink_response(project:) }
+ delete_service.on_success { render_unlink_response(project: @project) }
+
+ delete_service.on_failure do
+ update_flash_message_via_turbo_stream(
+ message: join_flash_messages(delete_service.errors.full_messages),
+ full: true, dismiss_scheme: :hide, scheme: :danger
+ )
+ end
respond_to_with_turbo_streams(status: delete_service.success? ? :ok : :unprocessable_entity)
end
@@ -150,6 +158,23 @@ def set_sections
.all
end
+ def find_unlink_project_custom_field_mapping
+ @project = Project.find(permitted_params.project_custom_field_project_mapping[:project_id])
+ @project_custom_field_mapping = @custom_field.project_custom_field_project_mappings.find_by!(project: @project)
+ rescue ActiveRecord::RecordNotFound
+ update_flash_message_via_turbo_stream(
+ message: t(:notice_file_not_found), full: true, dismiss_scheme: :hide, scheme: :danger
+ )
+ replace_via_turbo_stream(
+ component: Settings::ProjectCustomFields::ProjectCustomFieldMapping::TableComponent.new(
+ query: project_custom_field_mappings_query,
+ params: { custom_field: @custom_field }
+ )
+ )
+
+ respond_with_turbo_streams
+ end
+
def find_custom_field
@custom_field = ProjectCustomField.find(params[:id])
rescue ActiveRecord::RecordNotFound
diff --git a/app/helpers/flash_messages_helper.rb b/app/helpers/flash_messages_helper.rb
index 0fc868657ed8..36321e3ae4f7 100644
--- a/app/helpers/flash_messages_helper.rb
+++ b/app/helpers/flash_messages_helper.rb
@@ -31,8 +31,7 @@ module FlashMessagesHelper
extend ActiveSupport::Concern
included do
- # For .safe_join in join_flash_messages
- include ActionView::Helpers::OutputSafetyHelper
+ include FlashMessagesOutputSafetyHelper
end
def render_primer_banner_message?
@@ -45,6 +44,11 @@ def render_primer_banner_message
render(BannerMessageComponent.new(**flash[:primer_banner].to_hash))
end
+ # Primer's flash message component wrapped in a component which is empty initially but can be updated via turbo stream
+ def render_streameable_primer_banner_message
+ render(FlashMessageComponent.new)
+ end
+
# Renders flash messages
def render_flash_messages
return if render_primer_banner_message?
@@ -63,14 +67,6 @@ def render_flash_messages
safe_join messages, "\n"
end
- def join_flash_messages(messages)
- if messages.respond_to?(:join)
- safe_join(messages, "
".html_safe)
- else
- messages
- end
- end
-
def render_flash_message(type, message, html_options = {}) # rubocop:disable Metrics/AbcSize
if type.to_s == "notice"
type = "success"
diff --git a/app/contracts/project_custom_field_project_mappings/delete_contract.rb b/app/helpers/flash_messages_output_safety_helper.rb
similarity index 78%
rename from app/contracts/project_custom_field_project_mappings/delete_contract.rb
rename to app/helpers/flash_messages_output_safety_helper.rb
index 7392d8d7bdf0..563079419797 100644
--- a/app/contracts/project_custom_field_project_mappings/delete_contract.rb
+++ b/app/helpers/flash_messages_output_safety_helper.rb
@@ -25,9 +25,21 @@
#
# See COPYRIGHT and LICENSE files for more details.
#++
+#
+
+module FlashMessagesOutputSafetyHelper
+ extend ActiveSupport::Concern
+
+ included do
+ # For .safe_join in join_flash_messages
+ include ActionView::Helpers::OutputSafetyHelper
+ end
-module ProjectCustomFieldProjectMappings
- class DeleteContract < ::DeleteContract
- delete_permission :select_project_custom_fields
+ def join_flash_messages(messages)
+ if messages.respond_to?(:join)
+ safe_join(messages, "
".html_safe)
+ else
+ messages
+ end
end
end
diff --git a/app/services/project_custom_field_project_mappings/delete_service.rb b/app/services/project_custom_field_project_mappings/delete_service.rb
index 3e0b4501a185..5c3a03feef8c 100644
--- a/app/services/project_custom_field_project_mappings/delete_service.rb
+++ b/app/services/project_custom_field_project_mappings/delete_service.rb
@@ -28,5 +28,10 @@
module ProjectCustomFieldProjectMappings
class DeleteService < ::BaseServices::Delete
+ # Mappings have custom deletion rules that are similar to the update rules all derived from the base contract
+ # Reuse the update contract to ensure that the deletion rules are consistent with the update rules
+ def default_contract_class
+ ProjectCustomFieldProjectMappings::UpdateContract
+ end
end
end
diff --git a/app/views/layouts/base.html.erb b/app/views/layouts/base.html.erb
index a311e7f001d4..4f844ca25f2c 100644
--- a/app/views/layouts/base.html.erb
+++ b/app/views/layouts/base.html.erb
@@ -118,6 +118,7 @@ See COPYRIGHT and LICENSE files for more details.