2018-08-18 07:19:57 -04:00
# frozen_string_literal: true
2016-02-15 09:19:23 -05:00
module IssuablesHelper
2017-03-08 19:14:16 -05:00
include GitlabRoutingHelper
2016-02-15 09:19:23 -05:00
def sidebar_gutter_toggle_icon
2017-03-01 05:36:15 -05:00
sidebar_gutter_collapsed? ? icon ( 'angle-double-left' , { 'aria-hidden' : 'true' } ) : icon ( 'angle-double-right' , { 'aria-hidden' : 'true' } )
2016-02-15 09:19:23 -05:00
end
def sidebar_gutter_collapsed_class
" right-sidebar- #{ sidebar_gutter_collapsed? ? 'collapsed' : 'expanded' } "
end
2018-04-19 10:43:20 -04:00
def sidebar_gutter_tooltip_text
sidebar_gutter_collapsed? ? _ ( 'Expand sidebar' ) : _ ( 'Collapse sidebar' )
end
2019-04-07 14:35:16 -04:00
def assignees_label ( issuable , include_value : true )
label = 'Assignee' . pluralize ( issuable . assignees . count )
if include_value
sanitized_list = sanitize_name ( issuable . assignee_list )
" #{ label } : #{ sanitized_list } "
2018-04-19 10:43:20 -04:00
else
2019-04-07 14:35:16 -04:00
label
2018-04-19 10:43:20 -04:00
end
end
2018-11-27 11:53:16 -05:00
def sidebar_milestone_tooltip_label ( milestone )
2018-11-29 23:03:35 -05:00
return _ ( 'Milestone' ) unless milestone . present?
2020-08-05 17:09:40 -04:00
[ escape_once ( milestone [ :title ] ) , sidebar_milestone_remaining_days ( milestone ) || _ ( 'Milestone' ) ] . join ( '<br/>' )
2018-04-19 10:43:20 -04:00
end
2018-11-27 11:53:16 -05:00
def sidebar_milestone_remaining_days ( milestone )
2018-11-29 23:03:35 -05:00
due_date_with_remaining_days ( milestone [ :due_date ] , milestone [ :start_date ] )
2018-11-27 11:53:16 -05:00
end
def sidebar_due_date_tooltip_label ( due_date )
2018-11-29 23:03:35 -05:00
[ _ ( 'Due date' ) , due_date_with_remaining_days ( due_date ) ] . compact . join ( '<br/>' )
2018-11-27 11:53:16 -05:00
end
2018-11-29 23:03:35 -05:00
def due_date_with_remaining_days ( due_date , start_date = nil )
return unless due_date
" #{ due_date . to_s ( :medium ) } ( #{ remaining_days_in_words ( due_date , start_date ) } ) "
2018-11-27 11:53:16 -05:00
end
def sidebar_label_filter_path ( base_path , label_name )
2018-11-29 23:03:35 -05:00
query_params = { label_name : [ label_name ] } . to_query
2018-04-19 10:43:20 -04:00
2018-11-27 11:53:16 -05:00
" #{ base_path } ? #{ query_params } "
2018-04-19 10:43:20 -04:00
end
2016-07-19 05:26:49 -04:00
def multi_label_name ( current_labels , default_label )
2018-11-29 23:03:35 -05:00
return default_label if current_labels . blank?
2018-11-27 11:53:16 -05:00
2018-11-29 23:03:35 -05:00
title = current_labels . first . try ( :title ) || current_labels . first [ :title ]
if current_labels . size > 1
" #{ title } + #{ current_labels . size - 1 } more "
2016-04-19 12:22:55 -04:00
else
2018-11-29 23:03:35 -05:00
title
2016-04-07 14:57:21 -04:00
end
end
2018-11-27 11:53:16 -05:00
def issuable_json_path ( issuable )
2016-03-25 08:00:16 -04:00
project = issuable . project
2017-02-22 12:25:50 -05:00
if issuable . is_a? ( MergeRequest )
2018-11-27 11:53:16 -05:00
project_merge_request_path ( project , issuable . iid , :json )
2016-03-25 08:00:16 -04:00
else
2018-11-27 11:53:16 -05:00
project_issue_path ( project , issuable . iid , :json )
2016-03-25 08:00:16 -04:00
end
end
2019-06-20 18:18:51 -04:00
def serialize_issuable ( issuable , opts = { } )
2017-10-31 12:15:03 -04:00
serializer_klass = case issuable
when Issue
IssueSerializer
when MergeRequest
MergeRequestSerializer
end
serializer_klass
. new ( current_user : current_user , project : issuable . project )
2019-06-20 18:18:51 -04:00
. represent ( issuable , opts )
2017-10-31 12:15:03 -04:00
. to_json
2016-12-23 00:44:02 -05:00
end
2016-11-02 13:52:04 -04:00
def template_dropdown_tag ( issuable , & block )
title = selected_template ( issuable ) || " Choose a template "
options = {
toggle_class : 'js-issuable-selector' ,
title : title ,
filter : true ,
placeholder : 'Filter' ,
footer_content : true ,
data : {
data : issuable_templates ( issuable ) ,
field_name : 'issuable_template' ,
selected : selected_template ( issuable ) ,
project_path : ref_project . path ,
2017-02-23 18:55:01 -05:00
namespace_path : ref_project . namespace . full_path
2016-11-02 13:52:04 -04:00
}
}
dropdown_tag ( title , options : options ) do
capture ( & block )
end
end
2017-05-04 08:11:15 -04:00
def users_dropdown_label ( selected_users )
2017-05-08 10:58:42 -04:00
case selected_users . length
when 0
2017-05-04 08:11:15 -04:00
" Unassigned "
2017-05-08 10:58:42 -04:00
when 1
2017-05-04 08:11:15 -04:00
selected_users [ 0 ] . name
else
" #{ selected_users [ 0 ] . name } + #{ selected_users . length - 1 } more "
end
end
2018-08-27 11:31:01 -04:00
# rubocop: disable CodeReuse/ActiveRecord
2016-03-15 13:17:05 -04:00
def user_dropdown_label ( user_id , default_label )
2016-04-19 10:58:20 -04:00
return default_label if user_id . nil?
2016-03-15 13:17:05 -04:00
return " Unassigned " if user_id == " 0 "
2016-04-19 10:58:20 -04:00
user = User . find_by ( id : user_id )
2016-03-15 13:17:05 -04:00
if user
user . name
else
default_label
end
end
2018-08-27 11:31:01 -04:00
# rubocop: enable CodeReuse/ActiveRecord
2016-03-15 13:17:05 -04:00
2018-08-27 11:31:01 -04:00
# rubocop: disable CodeReuse/ActiveRecord
2016-08-27 18:45:01 -04:00
def project_dropdown_label ( project_id , default_label )
return default_label if project_id . nil?
return " Any project " if project_id == " 0 "
project = Project . find_by ( id : project_id )
if project
2018-03-05 09:15:26 -05:00
project . full_name
2016-08-27 18:45:01 -04:00
else
default_label
end
end
2018-08-27 11:31:01 -04:00
# rubocop: enable CodeReuse/ActiveRecord
2016-08-27 18:45:01 -04:00
2018-08-27 11:31:01 -04:00
# rubocop: disable CodeReuse/ActiveRecord
2018-07-16 09:35:19 -04:00
def group_dropdown_label ( group_id , default_label )
return default_label if group_id . nil?
return " Any group " if group_id == " 0 "
group = :: Group . find_by ( id : group_id )
if group
group . full_name
else
default_label
end
end
2018-08-27 11:31:01 -04:00
# rubocop: enable CodeReuse/ActiveRecord
2018-07-16 09:35:19 -04:00
2016-03-29 10:26:12 -04:00
def milestone_dropdown_label ( milestone_title , default_label = " Milestone " )
2017-03-10 14:01:05 -05:00
title =
case milestone_title
when Milestone :: Upcoming . name then Milestone :: Upcoming . title
when Milestone :: Started . name then Milestone :: Started . title
else milestone_title . presence
end
2016-03-29 07:19:09 -04:00
2017-03-10 14:01:05 -05:00
h ( title || default_label )
2016-03-24 11:20:35 -04:00
end
2017-03-08 19:14:16 -05:00
def to_url_reference ( issuable )
case issuable
when Issue
link_to issuable . to_reference , issue_url ( issuable )
when MergeRequest
link_to issuable . to_reference , merge_request_url ( issuable )
else
issuable . to_reference
end
end
2016-04-01 07:20:54 -04:00
def issuable_meta ( issuable , project , text )
2018-08-18 07:19:57 -04:00
output = [ ]
2017-09-06 07:19:03 -04:00
output << " Opened #{ time_ago_with_tooltip ( issuable . created_at ) } by " . html_safe
2018-08-18 07:19:57 -04:00
2016-04-01 07:20:54 -04:00
output << content_tag ( :strong ) do
2018-12-07 22:12:23 -05:00
author_output = link_to_member ( project , issuable . author , size : 24 , mobile_classes : " d-none d-sm-inline " )
2019-02-17 20:23:12 -05:00
author_output << link_to_member ( project , issuable . author , size : 24 , by_username : true , avatar : false , mobile_classes : " d-inline d-sm-none " )
2018-07-16 12:18:52 -04:00
2020-04-21 17:09:38 -04:00
author_output << issuable_meta_author_slot ( issuable . author , css_class : 'ml-1' )
2020-04-07 02:09:25 -04:00
2018-07-16 12:18:52 -04:00
if status = user_status ( issuable . author )
2018-08-17 11:54:40 -04:00
author_output << " #{ status } " . html_safe
2018-07-16 12:18:52 -04:00
end
author_output
2016-04-01 07:20:54 -04:00
end
2016-09-26 17:36:11 -04:00
2020-09-16 11:09:32 -04:00
if access = project . team . human_max_access ( issuable . author_id )
2020-09-24 08:09:37 -04:00
output << content_tag ( :span , access , class : " user-access-role has-tooltip d-none d-xl-inline-block gl-ml-3 " , title : _ ( " This user has the %{access} role in the %{name} project. " ) % { access : access . downcase , name : project . name } )
2020-09-16 11:09:32 -04:00
elsif project . team . contributor? ( issuable . author_id )
output << content_tag ( :span , _ ( " Contributor " ) , class : " user-access-role has-tooltip d-none d-xl-inline-block gl-ml-3 " , title : _ ( " This user has previously committed to the %{name} project. " ) % { name : project . name } )
end
2020-08-10 14:09:54 -04:00
output << content_tag ( :span , ( sprite_icon ( 'first-contribution' , css_class : 'gl-icon gl-vertical-align-middle' ) if issuable . first_contribution? ) , class : 'has-tooltip gl-ml-2' , title : _ ( '1st contribution!' ) )
2017-07-29 11:04:42 -04:00
2020-05-28 08:08:10 -04:00
output << content_tag ( :span , ( issuable . task_status if issuable . tasks? ) , id : " task_status " , class : " d-none d-sm-none d-md-inline-block gl-ml-3 " )
2018-06-07 16:46:14 -04:00
output << content_tag ( :span , ( issuable . task_status_short if issuable . tasks? ) , id : " task_status_short " , class : " d-md-none " )
2016-09-26 17:36:11 -04:00
2018-08-18 07:19:57 -04:00
output . join . html_safe
2016-04-01 07:20:54 -04:00
end
2020-04-21 17:09:38 -04:00
# This is a dummy method, and has an override defined in ee
def issuable_meta_author_slot ( author , css_class : nil )
nil
end
2016-08-18 06:38:12 -04:00
def issuable_labels_tooltip ( labels , limit : 5 )
2019-01-16 07:09:29 -05:00
first , last = labels . partition . with_index { | _ , i | i < limit }
2016-07-13 06:53:27 -04:00
2018-04-19 10:43:20 -04:00
if labels && labels . any?
2018-11-29 23:03:35 -05:00
label_names = first . collect { | label | label . fetch ( :title ) }
2018-04-19 10:43:20 -04:00
label_names << " and #{ last . size } more " unless last . empty?
2016-07-13 06:53:27 -04:00
2018-04-19 10:43:20 -04:00
label_names . join ( ', ' )
else
_ ( " Labels " )
end
2016-07-13 06:53:27 -04:00
end
2018-03-23 10:27:15 -04:00
def issuables_state_counter_text ( issuable_type , state , display_count )
2016-09-26 13:12:16 -04:00
titles = {
opened : " Open "
}
state_title = titles [ state ] || state . to_s . humanize
html = content_tag ( :span , state_title )
2018-03-23 10:27:15 -04:00
if display_count
count = issuables_count_for_state ( issuable_type , state )
2018-04-09 13:05:46 -04:00
html << " " << content_tag ( :span , number_with_delimiter ( count ) , class : 'badge badge-pill' )
2018-03-23 10:27:15 -04:00
end
2016-09-26 13:12:16 -04:00
html . html_safe
end
2017-04-19 11:17:46 -04:00
def assigned_issuables_count ( issuable_type )
2017-08-10 12:39:26 -04:00
case issuable_type
when :issues
current_user . assigned_open_issues_count
when :merge_requests
current_user . assigned_open_merge_requests_count
else
raise ArgumentError , " invalid issuable ` #{ issuable_type } ` "
end
2016-11-09 01:59:51 -05:00
end
2017-01-12 07:28:37 -05:00
def issuable_reference ( issuable )
@show_full_reference ? issuable . to_reference ( full : true ) : issuable . to_reference ( @group || @project )
end
2017-05-15 09:51:40 -04:00
def issuable_initial_data ( issuable )
2017-05-31 09:03:32 -04:00
data = {
2017-11-01 13:35:14 -04:00
endpoint : issuable_path ( issuable ) ,
2017-11-29 04:29:06 -05:00
updateEndpoint : " #{ issuable_path ( issuable ) } .json " ,
2017-11-01 13:35:14 -04:00
canUpdate : can? ( current_user , :" update_ #{ issuable . to_ability_name } " , issuable ) ,
canDestroy : can? ( current_user , :" destroy_ #{ issuable . to_ability_name } " , issuable ) ,
2017-05-24 07:25:53 -04:00
issuableRef : issuable . to_reference ,
2020-06-16 14:09:01 -04:00
issuableStatus : issuable . state ,
2017-11-01 13:35:14 -04:00
markdownPreviewPath : preview_markdown_path ( parent ) ,
2017-08-17 13:25:56 -04:00
markdownDocsPath : help_page_path ( 'user/markdown' ) ,
2018-12-20 19:19:17 -05:00
lockVersion : issuable . lock_version ,
2019-09-18 10:02:45 -04:00
issuableTemplateNamesPath : template_names_path ( parent , issuable ) ,
2017-05-24 07:25:53 -04:00
initialTitleHtml : markdown_field ( issuable , :title ) ,
initialTitleText : issuable . title ,
initialDescriptionHtml : markdown_field ( issuable , :description ) ,
2017-06-20 05:56:18 -04:00
initialDescriptionText : issuable . description ,
initialTaskStatus : issuable . task_status
2017-05-31 09:03:32 -04:00
}
2019-12-10 16:08:01 -05:00
data . merge! ( issue_only_initial_data ( issuable ) )
data . merge! ( path_data ( parent ) )
data . merge! ( updated_at_by ( issuable ) )
2017-05-31 09:03:32 -04:00
2019-12-10 16:08:01 -05:00
data
end
2019-07-17 02:41:26 -04:00
2019-12-10 16:08:01 -05:00
def issue_only_initial_data ( issuable )
return { } unless issuable . is_a? ( Issue )
2017-11-01 13:35:14 -04:00
2019-12-10 16:08:01 -05:00
{
hasClosingMergeRequest : issuable . merge_requests_count ( current_user ) != 0 ,
2020-09-02 05:10:23 -04:00
issueType : issuable . issue_type ,
2019-12-10 16:08:01 -05:00
zoomMeetingUrl : ZoomMeeting . canonical_meeting_url ( issuable ) ,
2020-09-10 14:08:54 -04:00
sentryIssueIdentifier : SentryIssue . find_by ( issue : issuable ) & . sentry_issue_identifier , # rubocop:disable CodeReuse/ActiveRecord
iid : issuable . iid . to_s
2019-12-10 16:08:01 -05:00
}
end
2017-06-02 12:30:58 -04:00
2019-12-10 16:08:01 -05:00
def path_data ( parent )
return { groupPath : parent . path } if parent . is_a? ( Group )
{
2020-09-02 05:10:23 -04:00
projectPath : ref_project . path ,
projectNamespace : ref_project . namespace . full_path
2019-12-10 16:08:01 -05:00
}
2017-05-31 09:03:32 -04:00
end
def updated_at_by ( issuable )
2017-08-24 12:26:24 -04:00
return { } unless issuable . edited?
2017-05-31 09:03:32 -04:00
{
2018-01-11 11:22:00 -05:00
updatedAt : issuable . last_edited_at . to_time . iso8601 ,
2017-06-02 12:17:54 -04:00
updatedBy : {
2017-05-31 09:03:32 -04:00
name : issuable . last_edited_by . name ,
path : user_path ( issuable . last_edited_by )
}
}
end
2017-08-31 07:21:39 -04:00
def issuables_count_for_state ( issuable_type , state )
2017-08-24 12:17:04 -04:00
Gitlab :: IssuablesCountForState . new ( finder ) [ state ]
2017-06-30 06:55:27 -04:00
end
2017-09-28 07:37:29 -04:00
def close_issuable_path ( issuable )
issuable_path ( issuable , close_reopen_params ( issuable , :close ) )
2017-06-07 06:18:35 -04:00
end
2017-09-28 07:37:29 -04:00
def reopen_issuable_path ( issuable )
issuable_path ( issuable , close_reopen_params ( issuable , :reopen ) )
2017-06-07 06:18:35 -04:00
end
2017-09-28 07:37:29 -04:00
def close_reopen_issuable_path ( issuable , should_inverse = false )
issuable . closed? ^ should_inverse ? reopen_issuable_path ( issuable ) : close_issuable_path ( issuable )
end
def issuable_path ( issuable , * options )
2017-11-01 13:35:14 -04:00
polymorphic_path ( issuable , * options )
2017-06-07 06:18:35 -04:00
end
2017-07-01 10:23:43 -04:00
def issuable_url ( issuable , * options )
2017-06-07 06:18:35 -04:00
case issuable
2017-07-06 10:35:07 -04:00
when Issue
issue_url ( issuable , * options )
when MergeRequest
merge_request_url ( issuable , * options )
2017-06-07 06:18:35 -04:00
end
end
2017-07-04 09:41:26 -04:00
def issuable_button_visibility ( issuable , closed )
2018-09-20 10:41:15 -04:00
return 'hidden' if issuable_button_hidden? ( issuable , closed )
end
def issuable_button_hidden? ( issuable , closed )
2017-07-04 09:41:26 -04:00
case issuable
2017-07-06 10:35:07 -04:00
when Issue
2018-09-20 10:41:15 -04:00
issue_button_hidden? ( issuable , closed )
2017-07-06 10:35:07 -04:00
when MergeRequest
2018-09-20 10:41:15 -04:00
merge_request_button_hidden? ( issuable , closed )
2017-07-04 09:41:26 -04:00
end
end
2017-07-06 10:35:07 -04:00
def issuable_author_is_current_user ( issuable )
issuable . author == current_user
end
def issuable_display_type ( issuable )
issuable . model_name . human . downcase
end
2018-11-19 06:59:08 -05:00
def has_filter_bar_param?
finder . class . scalar_params . any? { | p | params [ p ] . present? }
end
2019-09-24 14:06:05 -04:00
def assignee_sidebar_data ( assignee , merge_request : nil )
{ avatar_url : assignee . avatar_url , name : assignee . name , username : assignee . username } . tap do | data |
data [ :can_merge ] = merge_request . can_be_merged_by? ( assignee ) if merge_request
end
end
2020-09-29 05:09:49 -04:00
def reviewer_sidebar_data ( reviewer , merge_request : nil )
{ avatar_url : reviewer . avatar_url , name : reviewer . name , username : reviewer . username } . tap do | data |
data [ :can_merge ] = merge_request . can_be_merged_by? ( reviewer ) if merge_request
end
end
2020-07-06 02:09:59 -04:00
def issuable_squash_option? ( issuable , project )
if issuable . persisted?
issuable . squash
else
project . squash_enabled_by_default?
end
end
2016-02-15 09:19:23 -05:00
private
def sidebar_gutter_collapsed?
cookies [ :collapsed_gutter ] == 'true'
end
2016-11-02 13:52:04 -04:00
def issuable_templates ( issuable )
@issuable_templates || =
case issuable
when Issue
2017-10-11 11:48:43 -04:00
ref_project . repository . issue_template_names
2016-11-02 13:52:04 -04:00
when MergeRequest
2017-10-11 11:48:43 -04:00
ref_project . repository . merge_request_template_names
2016-11-02 13:52:04 -04:00
end
end
2019-12-02 16:06:51 -05:00
def issuable_templates_names ( issuable )
issuable_templates ( issuable ) . map { | template | template [ :name ] }
end
2016-11-02 13:52:04 -04:00
def selected_template ( issuable )
2017-08-09 05:52:22 -04:00
params [ :issuable_template ] if issuable_templates ( issuable ) . any? { | template | template [ :name ] == params [ :issuable_template ] }
2016-11-02 13:52:04 -04:00
end
2017-03-24 12:17:38 -04:00
2018-11-29 23:03:35 -05:00
def issuable_todo_button_data ( issuable , is_collapsed )
{
2019-07-04 11:45:54 -04:00
todo_text : _ ( 'Add a To Do' ) ,
mark_text : _ ( 'Mark as done' ) ,
2018-11-29 23:03:35 -05:00
todo_icon : sprite_icon ( 'todo-add' ) ,
mark_icon : sprite_icon ( 'todo-done' , css_class : 'todo-undone' ) ,
issuable_id : issuable [ :id ] ,
issuable_type : issuable [ :type ] ,
create_path : issuable [ :create_todo_path ] ,
delete_path : issuable . dig ( :current_user , :todo , :delete_path ) ,
placement : is_collapsed ? 'left' : nil ,
container : is_collapsed ? 'body' : nil ,
boundary : 'viewport' ,
2019-08-21 15:12:11 -04:00
is_collapsed : is_collapsed ,
track_label : " right_sidebar " ,
track_property : " update_todo " ,
track_event : " click_button " ,
track_value : " "
2018-11-29 23:03:35 -05:00
}
end
2017-07-04 09:41:26 -04:00
def close_reopen_params ( issuable , action )
2017-07-06 10:35:07 -04:00
{
issuable . model_name . to_s . underscore = > { state_event : action }
} . tap do | params |
params [ :format ] = :json if issuable . is_a? ( Issue )
end
2017-07-04 09:41:26 -04:00
end
2017-07-25 11:46:15 -04:00
2017-08-28 17:56:49 -04:00
def labels_path
if @project
project_labels_path ( @project )
elsif @group
group_labels_path ( @group )
end
end
2019-09-18 10:02:45 -04:00
def template_names_path ( parent , issuable )
return '' unless parent . is_a? ( Project )
project_template_names_path ( parent , template_type : issuable . class . name . underscore )
end
2018-11-29 23:03:35 -05:00
def issuable_sidebar_options ( issuable )
2017-07-25 11:46:15 -04:00
{
2018-11-29 23:03:35 -05:00
endpoint : " #{ issuable [ :issuable_json_path ] } ?serializer=sidebar_extras " ,
toggleSubscriptionEndpoint : issuable [ :toggle_subscription_path ] ,
moveIssueEndpoint : issuable [ :move_issue_path ] ,
projectsAutocompleteEndpoint : issuable [ :projects_autocomplete_path ] ,
editable : issuable . dig ( :current_user , :can_edit ) ,
currentUser : issuable [ :current_user ] ,
2017-07-25 11:46:15 -04:00
rootPath : root_path ,
2019-06-11 06:40:01 -04:00
fullPath : issuable [ :project_full_path ] ,
2020-03-13 11:09:21 -04:00
iid : issuable [ :iid ] ,
2020-09-10 14:08:54 -04:00
severity : issuable [ :severity ] ,
2019-06-11 06:40:01 -04:00
timeTrackingLimitToHours : Gitlab :: CurrentSettings . time_tracking_limit_to_hours
2017-07-25 11:46:15 -04:00
}
end
2017-11-01 13:35:14 -04:00
def parent
@project || @group
end
2016-02-15 09:19:23 -05:00
end
2019-09-13 09:26:31 -04:00
IssuablesHelper . prepend_if_ee ( 'EE::IssuablesHelper' )