2018-12-05 23:21:45 +05:30
# frozen_string_literal: true
2016-06-02 11:05:42 +05:30
module IssuableActions
extend ActiveSupport :: Concern
2018-12-13 13:39:08 +05:30
include Gitlab :: Utils :: StrongMemoize
2021-09-30 23:02:18 +05:30
include Gitlab :: Cache :: Helpers
2021-10-27 15:23:28 +05:30
include SpammableActions :: AkismetMarkAsSpamAction
include SpammableActions :: CaptchaCheck :: HtmlFormatActionsSupport
include SpammableActions :: CaptchaCheck :: JsonFormatActionsSupport
2016-06-02 11:05:42 +05:30
included do
before_action :authorize_destroy_issuable! , only : :destroy
2019-12-04 20:38:33 +05:30
before_action :check_destroy_confirmation! , only : :destroy
2016-09-29 09:46:39 +05:30
before_action :authorize_admin_issuable! , only : :bulk_update
2016-06-02 11:05:42 +05:30
end
2018-03-17 18:26:18 +05:30
def show
respond_to do | format |
2019-02-15 15:39:39 +05:30
format . html do
@issuable_sidebar = serializer . represent ( issuable , serializer : 'sidebar' ) # rubocop:disable Gitlab/ModuleWithInstanceVariables
2019-12-04 20:38:33 +05:30
render 'show'
2019-02-15 15:39:39 +05:30
end
2018-03-17 18:26:18 +05:30
format . json do
render json : serializer . represent ( issuable , serializer : params [ :serializer ] )
end
end
end
def update
2021-10-27 15:23:28 +05:30
updated_issuable = update_service . execute ( issuable )
# NOTE: We only assign the instance variable on this line, and use the local variable
# everywhere else in the method, to avoid having to add multiple `rubocop:disable` comments.
@issuable = updated_issuable # rubocop:disable Gitlab/ModuleWithInstanceVariables
# NOTE: This check for `is_a?(Spammable)` is necessary because not all
# possible `issuable` types implement Spammable. Once they all implement Spammable,
# this check can be removed.
if updated_issuable . is_a? ( Spammable )
respond_to do | format |
format . html do
if updated_issuable . valid?
2022-05-07 20:08:51 +05:30
# NOTE: This redirect is intentionally only performed in the case where the valid updated
# issuable is a spammable, and intentionally is not performed below in the
# valid non-spammable case. This preserves the legacy behavior of this action.
2021-10-27 15:23:28 +05:30
redirect_to spammable_path
else
2022-05-07 20:08:51 +05:30
with_captcha_check_html_format ( spammable : spammable ) { render :edit }
2021-10-27 15:23:28 +05:30
end
end
format . json do
2022-05-07 20:08:51 +05:30
with_captcha_check_json_format ( spammable : spammable ) { render_entity_json }
2021-10-27 15:23:28 +05:30
end
2018-03-17 18:26:18 +05:30
end
2021-10-27 15:23:28 +05:30
else
respond_to do | format |
format . html do
render :edit
end
format . json do
render_entity_json
end
2018-03-17 18:26:18 +05:30
end
end
rescue ActiveRecord :: StaleObjectError
render_conflict_response
end
def realtime_changes
Gitlab :: PollingInterval . set_header ( response , interval : 3_000 )
response = {
title : view_context . markdown_field ( issuable , :title ) ,
title_text : issuable . title ,
description : view_context . markdown_field ( issuable , :description ) ,
description_text : issuable . description ,
2023-06-20 00:43:36 +05:30
task_completion_status : issuable . task_completion_status ,
2019-03-02 22:35:43 +05:30
lock_version : issuable . lock_version
2018-03-17 18:26:18 +05:30
}
if issuable . edited?
response [ :updated_at ] = issuable . last_edited_at . to_time . iso8601
response [ :updated_by_name ] = issuable . last_edited_by . name
response [ :updated_by_path ] = user_path ( issuable . last_edited_by )
end
render json : response
end
2016-06-02 11:05:42 +05:30
def destroy
2023-04-23 21:23:45 +05:30
Issuable :: DestroyService . new ( container : issuable . project , current_user : current_user ) . execute ( issuable )
2016-06-02 11:05:42 +05:30
2017-08-17 22:00:37 +05:30
name = issuable . human_class_name
2016-06-02 11:05:42 +05:30
flash [ :notice ] = " The #{ name } was successfully deleted. "
2018-03-17 18:26:18 +05:30
index_path = polymorphic_path ( [ parent , issuable . class ] )
2017-09-10 17:25:29 +05:30
respond_to do | format |
format . html { redirect_to index_path }
format . json do
render json : {
web_url : index_path
}
end
end
2016-06-02 11:05:42 +05:30
end
2019-12-04 20:38:33 +05:30
def check_destroy_confirmation!
return true if params [ :destroy_confirm ]
error_message = " Destroy confirmation not provided for #{ issuable . human_class_name } "
exception = RuntimeError . new ( error_message )
2020-01-01 13:55:28 +05:30
Gitlab :: ErrorTracking . track_exception (
2019-12-04 20:38:33 +05:30
exception ,
2020-01-01 13:55:28 +05:30
project_path : issuable . project . full_path ,
issuable_type : issuable . class . name ,
issuable_id : issuable . id
2019-12-04 20:38:33 +05:30
)
index_path = polymorphic_path ( [ parent , issuable . class ] )
respond_to do | format |
format . html do
flash [ :notice ] = error_message
redirect_to index_path
end
format . json do
render json : { errors : error_message } , status : :unprocessable_entity
end
end
end
2016-09-29 09:46:39 +05:30
def bulk_update
2020-01-01 13:55:28 +05:30
result = Issuable :: BulkUpdateService . new ( parent , current_user , bulk_update_params ) . execute ( resource_name )
2016-09-29 09:46:39 +05:30
2020-07-28 23:09:34 +05:30
if result . success?
quantity = result . payload [ :count ]
render json : { notice : " #{ quantity } #{ resource_name . pluralize ( quantity ) } updated " }
elsif result . error?
render json : { errors : result . message } , status : result . http_status
end
2016-09-29 09:46:39 +05:30
end
2018-03-27 19:54:05 +05:30
def discussions
2023-01-13 00:05:48 +05:30
finder = Issuable :: DiscussionsListService . new ( current_user , issuable , finder_params_for_issuable )
discussion_notes = finder . execute
2018-03-27 19:54:05 +05:30
2023-03-04 22:38:38 +05:30
if finder . paginator . present? && finder . paginator . has_next_page?
response . headers [ 'X-Next-Page-Cursor' ] = finder . paginator . cursor_for_next_page
end
2021-12-11 22:18:48 +05:30
2023-01-13 00:05:48 +05:30
case issuable
2023-05-27 22:25:52 +05:30
when MergeRequest , Issue
2023-03-04 22:38:38 +05:30
if stale? ( etag : [ discussion_cache_context , discussion_notes ] )
render json : discussion_serializer . represent ( discussion_notes , context : self )
end
2021-09-30 23:02:18 +05:30
else
2023-01-13 00:05:48 +05:30
render json : discussion_serializer . represent ( discussion_notes , context : self )
2021-09-30 23:02:18 +05:30
end
2018-03-27 19:54:05 +05:30
end
2016-06-02 11:05:42 +05:30
private
2018-12-13 13:39:08 +05:30
def notes_filter
strong_memoize ( :notes_filter ) do
notes_filter_param = params [ :notes_filter ] & . to_i
# GitLab Geo does not expect database UPDATE or INSERT statements to happen
# on GET requests.
# This is just a fail-safe in case notes_filter is sent via GET request in GitLab Geo.
2019-10-12 21:52:04 +05:30
# In some cases, we also force the filter to not be persisted with the `persist_filter` param
if Gitlab :: Database . read_only? || params [ :persist_filter ] == 'false'
2018-12-13 13:39:08 +05:30
notes_filter_param || current_user & . notes_filter_for ( issuable )
else
notes_filter = current_user & . set_notes_filter ( notes_filter_param , issuable ) || notes_filter_param
# We need to invalidate the cache for polling notes otherwise it will
# ignore the filter.
# The ideal would be to invalidate the cache for each user.
issuable . expire_note_etag_cache if notes_filter_updated?
notes_filter
end
end
end
def notes_filter_updated?
current_user & . user_preference & . previous_changes & . any?
end
2021-12-11 22:18:48 +05:30
def discussion_cache_context
2023-06-20 00:43:36 +05:30
[ current_user & . cache_key , project . team . human_max_access ( current_user & . id ) , 'v2' ] . join ( ':' )
2021-12-11 22:18:48 +05:30
end
2018-05-09 12:01:36 +05:30
def discussion_serializer
DiscussionSerializer . new ( project : project , noteable : issuable , current_user : current_user , note_entity : ProjectNoteEntity )
end
2017-08-17 22:00:37 +05:30
def render_conflict_response
respond_to do | format |
format . html do
2018-03-17 18:26:18 +05:30
@conflict = true # rubocop:disable Gitlab/ModuleWithInstanceVariables
2017-08-17 22:00:37 +05:30
render :edit
end
format . json do
render json : {
errors : [
" Someone edited this #{ issuable . human_class_name } at the same time you did. Please refresh your browser and make sure your changes will not unintentionally remove theirs. "
]
2018-11-18 11:00:15 +05:30
} , status : :conflict
2017-08-17 22:00:37 +05:30
end
end
end
2016-06-02 11:05:42 +05:30
def authorize_destroy_issuable!
2023-03-04 22:38:38 +05:30
access_denied! unless can? ( current_user , :" destroy_ #{ issuable . to_ability_name } " , issuable )
2016-06-02 11:05:42 +05:30
end
2016-09-29 09:46:39 +05:30
def authorize_admin_issuable!
2023-03-04 22:38:38 +05:30
access_denied! unless can? ( current_user , :" admin_ #{ resource_name } " , parent )
2016-09-29 09:46:39 +05:30
end
2018-03-17 18:26:18 +05:30
def authorize_update_issuable!
render_404 unless can? ( current_user , :" update_ #{ resource_name } " , issuable )
end
2016-09-29 09:46:39 +05:30
def bulk_update_params
2023-04-23 21:23:45 +05:30
clean_bulk_update_params (
params . require ( :update ) . permit ( bulk_update_permitted_keys )
)
end
def clean_bulk_update_params ( permitted_params )
permitted_params . delete_if do | k , v |
next if k == :issuable_ids
if v . is_a? ( Array )
v . compact . empty?
else
v . blank?
end
end
2020-06-23 00:09:42 +05:30
end
2017-08-17 22:00:37 +05:30
2020-06-23 00:09:42 +05:30
def bulk_update_permitted_keys
[
:issuable_ids ,
:assignee_id ,
:milestone_id ,
:state_event ,
:subscription_event ,
assignee_ids : [ ] ,
add_label_ids : [ ] ,
remove_label_ids : [ ]
]
2016-09-29 09:46:39 +05:30
end
def resource_name
@resource_name || = controller_name . singularize
end
2018-03-17 18:26:18 +05:30
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def render_entity_json
if @issuable . valid?
render json : serializer . represent ( @issuable )
else
render json : { errors : @issuable . errors . full_messages } , status : :unprocessable_entity
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def serializer
raise NotImplementedError
end
def update_service
raise NotImplementedError
end
def parent
@project || @group # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
2019-10-12 21:52:04 +05:30
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def finder_params_for_issuable
{
2023-01-13 00:05:48 +05:30
notes_filter : notes_filter ,
cursor : params [ :cursor ] ,
per_page : params [ :per_page ]
}
2019-10-12 21:52:04 +05:30
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
2016-06-02 11:05:42 +05:30
end
2019-12-04 20:38:33 +05:30
2021-06-08 01:23:25 +05:30
IssuableActions . prepend_mod_with ( 'IssuableActions' )