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
2021-02-08 13:09:49 -05:00
include IssuablesDescriptionTemplatesHelper
2021-09-01 11:10:20 -04:00
include :: Sidebars :: Concerns :: HasPill
2017-03-08 19:14:16 -05:00
2016-02-15 09:19:23 -05:00
def sidebar_gutter_toggle_icon
2020-10-02 23:08:48 -04:00
content_tag ( :span , class : 'js-sidebar-toggle-container' , data : { is_expanded : ! sidebar_gutter_collapsed? } ) do
sprite_icon ( 'chevron-double-lg-left' , css_class : " js-sidebar-expand #{ 'hidden' unless sidebar_gutter_collapsed? } " ) +
sprite_icon ( 'chevron-double-lg-right' , css_class : " js-sidebar-collapse #{ 'hidden' if sidebar_gutter_collapsed? } " )
end
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 )
2021-03-19 02:09:30 -04:00
assignees = issuable . assignees
2019-04-07 14:35:16 -04:00
if include_value
sanitized_list = sanitize_name ( issuable . assignee_list )
2021-03-19 02:09:30 -04:00
ns_ ( 'NotificationEmail|Assignee: %{users}' , 'NotificationEmail|Assignees: %{users}' , assignees . count ) % { users : sanitized_list }
2018-04-19 10:43:20 -04:00
else
2021-03-19 02:09:30 -04:00
ns_ ( 'NotificationEmail|Assignee' , 'NotificationEmail|Assignees' , assignees . count )
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
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
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
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
2021-12-03 04:10:57 -05: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
2021-12-03 04:10:57 -05: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
2020-12-09 04:09:47 -05:00
def issuable_meta_author_status ( author )
return " " unless show_status_emoji? ( author & . status ) && status = user_status ( author )
" #{ status } " . html_safe
end
2020-11-30 10:09:21 -05:00
def issuable_meta ( issuable , project )
2018-08-18 07:19:57 -04:00
output = [ ]
2021-03-09 19:09:13 -05:00
output << " Created #{ time_ago_with_tooltip ( issuable . created_at ) } by " . html_safe
2018-08-18 07:19:57 -04:00
2020-11-24 16:09:39 -05:00
if issuable . is_a? ( Issue ) && issuable . service_desk_reply_to
output << " #{ html_escape ( issuable . service_desk_reply_to ) } via "
end
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-12-09 04:09:47 -05:00
author_output << issuable_meta_author_status ( issuable . author )
2018-07-16 12:18:52 -04:00
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-11-12 13:09:26 -05:00
output << content_tag ( :span , ( issuable . task_status if issuable . tasks? ) , id : " task_status " , class : " d-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
2018-03-23 10:27:15 -04:00
def issuables_state_counter_text ( issuable_type , state , display_count )
2021-12-03 04:10:57 -05:00
titles = {
opened : _ ( " Open " ) ,
closed : _ ( " Closed " ) ,
merged : _ ( " Merged " ) ,
all : _ ( " All " )
}
2016-09-26 13:12:16 -04:00
state_title = titles [ state ] || state . to_s . humanize
html = content_tag ( :span , state_title )
2018-03-23 10:27:15 -04:00
2021-02-02 10:09:06 -05:00
return html . html_safe unless display_count
count = issuables_count_for_state ( issuable_type , state )
if count != - 1
2022-01-24 07:10:54 -05:00
html << " " << gl_badge_tag ( format_count ( issuable_type , count , Gitlab :: IssuablesCountForState :: THRESHOLD ) , { variant : :muted , size : :sm } , { class : " gl-tab-counter-badge gl-display-none gl-sm-display-inline-flex " } )
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
2021-06-09 08:10:27 -04:00
def issuable_project_reference ( issuable )
" #{ issuable . project . full_name } #{ issuable . to_reference } "
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 ,
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
2021-08-25 05:10:52 -04:00
iid : issuable . iid . to_s ,
2021-11-09 16:10:00 -05:00
isHidden : issue_hidden? ( issuable ) ,
canCreateIncident : create_issue_type_allowed? ( issuable . project , :incident )
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 ,
2021-02-08 13:09:49 -05:00
projectId : ref_project . id ,
2020-09-02 05:10:23 -04:00
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 )
2021-10-21 05:09:48 -04:00
Gitlab :: IssuablesCountForState . new ( finder , store_in_redis_cache : true ) [ 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 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-06 10:35:07 -04:00
def issuable_author_is_current_user ( issuable )
issuable . author == current_user
end
def issuable_display_type ( issuable )
2020-10-27 14:08:59 -04:00
case issuable
when Issue
issuable . issue_type . downcase
when MergeRequest
issuable . model_name . human . downcase
end
2017-07-06 10:35:07 -04:00
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
2021-01-28 13:09:27 -05:00
data [ :availability ] = assignee . status . availability if assignee . association ( :status ) . loaded? && assignee . status & . availability
2019-09-24 14:06:05 -04:00
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
2021-05-04 08:10:04 -04:00
def state_name_with_icon ( issuable )
if issuable . is_a? ( MergeRequest ) && issuable . merged?
[ _ ( " Merged " ) , " git-merge " ]
elsif issuable . is_a? ( MergeRequest ) && issuable . closed?
[ _ ( " Closed " ) , " close " ]
elsif issuable . closed?
[ _ ( " Closed " ) , " mobile-issue-close " ]
else
[ _ ( " Open " ) , " issue-open-m " ]
end
end
2016-02-15 09:19:23 -05:00
private
def sidebar_gutter_collapsed?
cookies [ :collapsed_gutter ] == 'true'
end
2018-11-29 23:03:35 -05:00
def issuable_todo_button_data ( issuable , is_collapsed )
{
2020-10-08 05:08:40 -04:00
todo_text : _ ( 'Add a to do' ) ,
2019-07-04 11:45:54 -04:00
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 " ,
2021-09-15 14:11:29 -04:00
track_action : " click_button " ,
2019-08-21 15:12:11 -04:00
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
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 ] ,
2021-04-26 05:09:53 -04:00
id : issuable [ :id ] ,
2020-09-10 14:08:54 -04:00
severity : issuable [ :severity ] ,
2021-01-04 22:10:19 -05:00
timeTrackingLimitToHours : Gitlab :: CurrentSettings . time_tracking_limit_to_hours ,
2021-03-01 13:11:21 -05:00
createNoteEmail : issuable [ :create_note_email ] ,
2021-04-29 05:10:11 -04:00
issuableType : issuable [ :type ]
2017-07-25 11:46:15 -04:00
}
end
2017-11-01 13:35:14 -04:00
2020-11-05 22:09:19 -05:00
def sidebar_labels_data ( issuable_sidebar , project )
{
allow_label_create : issuable_sidebar . dig ( :current_user , :can_admin_label ) . to_s ,
allow_scoped_labels : issuable_sidebar [ :scoped_labels_available ] . to_s ,
can_edit : issuable_sidebar . dig ( :current_user , :can_edit ) . to_s ,
iid : issuable_sidebar [ :iid ] ,
issuable_type : issuable_sidebar [ :type ] ,
labels_fetch_path : issuable_sidebar [ :project_labels_path ] ,
labels_manage_path : project_labels_path ( project ) ,
project_issues_path : issuable_sidebar [ :project_issuables_path ] ,
project_path : project . full_path ,
selected_labels : issuable_sidebar [ :labels ] . to_json
}
end
2021-07-09 08:08:17 -04:00
def sidebar_status_data ( issuable_sidebar , project )
{
iid : issuable_sidebar [ :iid ] ,
issuable_type : issuable_sidebar [ :type ] ,
full_path : project . full_path ,
can_edit : issuable_sidebar . dig ( :current_user , :can_edit ) . to_s
}
end
2017-11-01 13:35:14 -04:00
def parent
@project || @group
end
2021-09-01 11:10:20 -04:00
def format_count ( issuable_type , count , threshold )
2021-10-21 05:09:48 -04:00
if issuable_type == :issues && parent . is_a? ( Group )
2021-09-01 11:10:20 -04:00
format_cached_count ( threshold , count )
else
number_with_delimiter ( count )
end
end
2016-02-15 09:19:23 -05:00
end
2019-09-13 09:26:31 -04:00
2021-05-11 17:10:21 -04:00
IssuablesHelper . prepend_mod_with ( 'IssuablesHelper' )