Module: IssuablesHelper

Includes:
GitlabRoutingHelper, IssuablesDescriptionTemplatesHelper, Sidebars::Concerns::HasPill
Included in:
Notify
Defined in:
app/helpers/issuables_helper.rb

Instance Method Summary collapse

Methods included from Sidebars::Concerns::HasPill

#format_cached_count, #has_pill?, #pill_count, #pill_html_options

Methods included from IssuablesDescriptionTemplatesHelper

#available_service_desk_templates_for, #issuable_templates, #selected_template, #template_dropdown_tag, #template_names_path

Methods included from Routing::PseudonymizationHelper

#masked_page_url

Methods included from Routing::GraphqlHelper

#graphql_etag_pipeline_path, #graphql_etag_pipeline_sha_path, #graphql_etag_project_on_demand_scan_counts_path

Methods included from Routing::WikiHelper

#wiki_page_path, #wiki_path

Methods included from Routing::SnippetsHelper

#gitlab_dashboard_snippets_path, #gitlab_raw_snippet_blob_path, #gitlab_raw_snippet_blob_url, #gitlab_raw_snippet_path, #gitlab_raw_snippet_url, #gitlab_snippet_note_path, #gitlab_snippet_note_url, #gitlab_snippet_notes_path, #gitlab_snippet_notes_url, #gitlab_snippet_path, #gitlab_snippet_url, #gitlab_toggle_award_emoji_snippet_note_path, #gitlab_toggle_award_emoji_snippet_note_url, #gitlab_toggle_award_emoji_snippet_path, #gitlab_toggle_award_emoji_snippet_url, #preview_markdown_path, #toggle_award_emoji_personal_snippet_path, #toggle_award_emoji_project_project_snippet_path, #toggle_award_emoji_project_project_snippet_url

Methods included from Routing::PipelineSchedulesHelper

#edit_pipeline_schedule_path, #pipeline_schedule_path, #pipeline_schedules_path, #play_pipeline_schedule_path, #take_ownership_pipeline_schedule_path

Methods included from Routing::ArtifactsHelper

#artifacts_action_path, #expose_fast_artifacts_path, #fast_browse_project_job_artifacts_path, #fast_download_project_job_artifacts_path, #fast_keep_project_job_artifacts_path

Methods included from Routing::MembersHelper

#source_members_url

Methods included from Routing::Groups::MembersHelper

#approve_access_request_group_member_path, #group_member_path, #group_members_url, #leave_group_members_path, #request_access_group_members_path, #resend_invite_group_member_path

Methods included from Routing::Projects::MembersHelper

#approve_access_request_project_member_path, #leave_project_members_path, #project_member_path, #project_members_url, #request_access_project_members_path, #resend_invite_project_member_path

Methods included from Routing::ProjectsHelper

#commit_url, #commits_url, #edit_milestone_path, #environment_delete_path, #environment_path, #issue_path, #issue_url, #merge_request_path, #merge_request_url, #pipeline_job_url, #pipeline_path, #pipeline_url, #project_commits_path, #project_ref_path, #project_tree_path, #release_url, #toggle_subscription_path

Methods included from API::Helpers::RelatedResourcesHelpers

#expose_path, #expose_url, #issues_available?, #mrs_available?

Methods included from ApplicationSettingsHelper

#all_protocols_enabled?, #allowed_protocols_present?, #deprecated_attributes, #enabled_protocol, #enabled_protocol_button, #expanded_by_default?, #external_authorization_client_certificate_help_text, #external_authorization_client_key_help_text, #external_authorization_client_pass_help_text, #external_authorization_client_url_help_text, #external_authorization_description, #external_authorization_service_attributes, #external_authorization_timeout_help_text, #external_authorization_url_help_text, #http_enabled?, #import_sources_checkboxes, #instance_clusters_enabled?, #integration_expanded?, #key_restriction_options_for_select, #kroki_available_formats, #oauth_providers_checkboxes, #omnibus_protected_paths_throttle?, #pending_user_count, #registration_features_can_be_prompted?, #repository_storages_options_json, #restricted_level_checkboxes, #self_monitoring_project_data, #sidekiq_job_limiter_mode_help_text, #sidekiq_job_limiter_modes_for_select, #signup_enabled?, #ssh_enabled?, #storage_weights, #user_oauth_applications?, #valid_runner_registrars, #visible_attributes

Methods included from ProjectsHelper

#able_to_see_issues?, #able_to_see_merge_requests?, #any_projects?, #author_content_tag, #autodeploy_flash_notice, #can_admin_project_member?, #can_change_visibility_level?, #can_disable_emails?, #delete_confirm_phrase, #directory?, #error_tracking_setting_project_json, #explore_projects_tab?, #external_classification_label_help_message, #fork_button_disabled_tooltip, #grafana_integration_enabled?, #grafana_integration_masked_token, #grafana_integration_url, #import_from_bitbucket_message, #import_from_gitlab_message, #last_push_event, #link_to_autodeploy_doc, #link_to_member, #link_to_member_avatar, #link_to_project, #load_pipeline_status, #membership_locked?, #metrics_dashboard_timezone, #metrics_external_dashboard_url, #no_password_message, #project_can_be_shared?, #project_classes, #project_incident_management_setting, #project_license_name, #project_list_cache_key, #project_permissions_panel_data, #project_search_tabs?, #project_title, #push_to_create_project_command, #remove_fork_project_confirm_json, #remove_fork_project_description_message, #remove_fork_project_warning_message, #remove_project_message, #share_project_description, #show_auto_devops_implicitly_enabled_banner?, #show_issue_count?, #show_merge_request_count?, #show_no_password_message?, #show_no_ssh_key_message?, #show_projects?, #show_terraform_banner?, #show_xcode_link?, #transfer_project_message, #visible_fork_source, #xcode_uri_to_repo

Methods included from Gitlab::Utils::StrongMemoize

#clear_memoization, #strong_memoize, #strong_memoized?

Instance Method Details

#assigned_issuables_count(issuable_type) ⇒ Object


210
211
212
213
214
215
216
217
218
219
# File 'app/helpers/issuables_helper.rb', line 210

def assigned_issuables_count(issuable_type)
  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
end

#assignee_sidebar_data(assignee, merge_request: nil) ⇒ Object


322
323
324
325
326
327
# File 'app/helpers/issuables_helper.rb', line 322

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
    data[:availability] = assignee.status.availability if assignee.association(:status).loaded? && assignee.status&.availability
  end
end

#assignees_label(issuable, include_value: true) ⇒ Object


23
24
25
26
27
28
29
30
31
32
# File 'app/helpers/issuables_helper.rb', line 23

def assignees_label(issuable, include_value: true)
  assignees = issuable.assignees

  if include_value
    sanitized_list = sanitize_name(issuable.assignee_list)
    ns_('NotificationEmail|Assignee: %{users}', 'NotificationEmail|Assignees: %{users}', assignees.count) % { users: sanitized_list }
  else
    ns_('NotificationEmail|Assignee', 'NotificationEmail|Assignees', assignees.count)
  end
end

#close_issuable_path(issuable) ⇒ Object


293
294
295
# File 'app/helpers/issuables_helper.rb', line 293

def close_issuable_path(issuable)
  issuable_path(issuable, close_reopen_params(issuable, :close))
end

#due_date_with_remaining_days(due_date, start_date = nil) ⇒ Object


48
49
50
51
52
# File 'app/helpers/issuables_helper.rb', line 48

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)})"
end

#group_dropdown_label(group_id, default_label) ⇒ Object

rubocop: disable CodeReuse/ActiveRecord


122
123
124
125
126
127
128
129
130
131
132
133
# File 'app/helpers/issuables_helper.rb', line 122

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

#has_filter_bar_param?Boolean

Returns:

  • (Boolean)

318
319
320
# File 'app/helpers/issuables_helper.rb', line 318

def has_filter_bar_param?
  finder.class.scalar_params.any? { |p| params[p].present? }
end

#issuable_author_is_current_user(issuable) ⇒ Object


305
306
307
# File 'app/helpers/issuables_helper.rb', line 305

def issuable_author_is_current_user(issuable)
  issuable.author == current_user
end

#issuable_display_type(issuable) ⇒ Object


309
310
311
312
313
314
315
316
# File 'app/helpers/issuables_helper.rb', line 309

def issuable_display_type(issuable)
  case issuable
  when Issue
    issuable.issue_type.downcase
  when MergeRequest
    issuable.model_name.human.downcase
  end
end

#issuable_initial_data(issuable) ⇒ Object


229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'app/helpers/issuables_helper.rb', line 229

def issuable_initial_data(issuable)
  data = {
    endpoint: issuable_path(issuable),
    updateEndpoint: "#{issuable_path(issuable)}.json",
    canUpdate: can?(current_user, :"update_#{issuable.to_ability_name}", issuable),
    canDestroy: can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable),
    issuableRef: issuable.to_reference,
    markdownPreviewPath: preview_markdown_path(parent, target_type: issuable.model_name, target_id: issuable.iid),
    markdownDocsPath: help_page_path('user/markdown'),
    lockVersion: issuable.lock_version,
    issuableTemplateNamesPath: template_names_path(parent, issuable),
    initialTitleHtml: markdown_field(issuable, :title),
    initialTitleText: issuable.title,
    initialDescriptionHtml: markdown_field(issuable, :description),
    initialDescriptionText: issuable.description,
    initialTaskStatus: issuable.task_status
  }
  data.merge!(issue_only_initial_data(issuable))
  data.merge!(path_data(parent))
  data.merge!(updated_at_by(issuable))

  data
end

#issuable_meta(issuable, project) ⇒ Object


153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'app/helpers/issuables_helper.rb', line 153

def issuable_meta(issuable, project)
  output = []
  output << "Created #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe

  if issuable.is_a?(Issue) && issuable.service_desk_reply_to
    output << "#{html_escape(issuable.service_desk_reply_to)} via "
  end

  output << (:strong) do
    author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "d-none d-sm-inline")
    author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "d-inline d-sm-none")

    author_output << issuable_meta_author_slot(issuable.author, css_class: 'ml-1')
    author_output << issuable_meta_author_status(issuable.author)

    author_output
  end

  if access = project.team.human_max_access(issuable.author_id)
    output << (: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 })
  elsif project.team.contributor?(issuable.author_id)
    output << (: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

  output << (: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!'))

  output << (:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "d-none d-md-inline-block gl-ml-3")
  output << (:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "d-md-none")

  output.join.html_safe
end

#issuable_meta_author_slot(author, css_class: nil) ⇒ Object

This is a dummy method, and has an override defined in ee


186
187
188
# File 'app/helpers/issuables_helper.rb', line 186

def issuable_meta_author_slot(author, css_class: nil)
  nil
end

#issuable_meta_author_status(author) ⇒ Object


147
148
149
150
151
# File 'app/helpers/issuables_helper.rb', line 147

def issuable_meta_author_status(author)
  return "" unless show_status_emoji?(author&.status) && status = user_status(author)

  "#{status}".html_safe
end

#issuable_path(issuable, *options) ⇒ Object


301
302
303
# File 'app/helpers/issuables_helper.rb', line 301

def issuable_path(issuable, *options)
  polymorphic_path(issuable, *options)
end

#issuable_project_reference(issuable) ⇒ Object


225
226
227
# File 'app/helpers/issuables_helper.rb', line 225

def issuable_project_reference(issuable)
  "#{issuable.project.full_name} #{issuable.to_reference}"
end

#issuable_reference(issuable) ⇒ Object


221
222
223
# File 'app/helpers/issuables_helper.rb', line 221

def issuable_reference(issuable)
  @show_full_reference ? issuable.to_reference(full: true) : issuable.to_reference(@group || @project)
end

#issuable_squash_option?(issuable, project) ⇒ Boolean

Returns:

  • (Boolean)

335
336
337
338
339
340
341
# File 'app/helpers/issuables_helper.rb', line 335

def issuable_squash_option?(issuable, project)
  if issuable.persisted?
    issuable.squash
  else
    project.squash_enabled_by_default?
  end
end

#issuables_count_for_state(issuable_type, state) ⇒ Object


289
290
291
# File 'app/helpers/issuables_helper.rb', line 289

def issuables_count_for_state(issuable_type, state)
  Gitlab::IssuablesCountForState.new(finder, store_in_redis_cache: true)[state]
end

#issuables_state_counter_text(issuable_type, state, display_count) ⇒ Object


190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'app/helpers/issuables_helper.rb', line 190

def issuables_state_counter_text(issuable_type, state, display_count)
  titles = {
    opened: _("Open"),
    closed: _("Closed"),
    merged: _("Merged"),
    all: _("All")
  }
  state_title = titles[state] || state.to_s.humanize
  html = (:span, state_title)

  return html.html_safe unless display_count

  count = issuables_count_for_state(issuable_type, state)
  if count != -1
    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" })
  end

  html.html_safe
end

#issue_only_initial_data(issuable) ⇒ Object


253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'app/helpers/issuables_helper.rb', line 253

def issue_only_initial_data(issuable)
  return {} unless issuable.is_a?(Issue)

  {
    hasClosingMergeRequest: issuable.merge_requests_count(current_user) != 0,
    issueType: issuable.issue_type,
    zoomMeetingUrl: ZoomMeeting.canonical_meeting_url(issuable),
    sentryIssueIdentifier: SentryIssue.find_by(issue: issuable)&.sentry_issue_identifier, # rubocop:disable CodeReuse/ActiveRecord
    iid: issuable.iid.to_s,
    isHidden: issue_hidden?(issuable),
    canCreateIncident: create_issue_type_allowed?(issuable.project, :incident)
  }
end

#milestone_dropdown_label(milestone_title, default_label = _('Milestone')) ⇒ Object

rubocop: enable CodeReuse/ActiveRecord


136
137
138
139
140
141
142
143
144
145
# File 'app/helpers/issuables_helper.rb', line 136

def milestone_dropdown_label(milestone_title, default_label = _('Milestone'))
  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

  h(title || default_label)
end

#multi_label_name(current_labels, default_label) ⇒ Object


54
55
56
57
58
59
60
61
62
63
64
# File 'app/helpers/issuables_helper.rb', line 54

def multi_label_name(current_labels, default_label)
  return default_label if current_labels.blank?

  title = current_labels.first.try(:title) || current_labels.first[:title]

  if current_labels.size > 1
    "#{title} +#{current_labels.size - 1} more"
  else
    title
  end
end

#path_data(parent) ⇒ Object


267
268
269
270
271
272
273
274
275
# File 'app/helpers/issuables_helper.rb', line 267

def path_data(parent)
  return { groupPath: parent.path } if parent.is_a?(Group)

  {
    projectPath: ref_project.path,
    projectId: ref_project.id,
    projectNamespace: ref_project.namespace.full_path
  }
end

#project_dropdown_label(project_id, default_label) ⇒ Object

rubocop: disable CodeReuse/ActiveRecord


107
108
109
110
111
112
113
114
115
116
117
118
# File 'app/helpers/issuables_helper.rb', line 107

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
    project.full_name
  else
    default_label
  end
end

#reopen_issuable_path(issuable) ⇒ Object


297
298
299
# File 'app/helpers/issuables_helper.rb', line 297

def reopen_issuable_path(issuable)
  issuable_path(issuable, close_reopen_params(issuable, :reopen))
end

#reviewer_sidebar_data(reviewer, merge_request: nil) ⇒ Object


329
330
331
332
333
# File 'app/helpers/issuables_helper.rb', line 329

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

#serialize_issuable(issuable, opts = {}) ⇒ Object


66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'app/helpers/issuables_helper.rb', line 66

def serialize_issuable(issuable, opts = {})
  serializer_klass = case issuable
                     when Issue
                       IssueSerializer
                     when MergeRequest
                       MergeRequestSerializer
                     end

  serializer_klass
    .new(current_user: current_user, project: issuable.project)
    .represent(issuable, opts)
    .to_json
end

44
45
46
# File 'app/helpers/issuables_helper.rb', line 44

def sidebar_due_date_tooltip_label(due_date)
  [_('Due date'), due_date_with_remaining_days(due_date)].compact.join('<br/>')
end

15
16
17
# File 'app/helpers/issuables_helper.rb', line 15

def sidebar_gutter_collapsed_class
  "right-sidebar-#{sidebar_gutter_collapsed? ? 'collapsed' : 'expanded'}"
end

8
9
10
11
12
13
# File 'app/helpers/issuables_helper.rb', line 8

def sidebar_gutter_toggle_icon
  (: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
end

19
20
21
# File 'app/helpers/issuables_helper.rb', line 19

def sidebar_gutter_tooltip_text
  sidebar_gutter_collapsed? ? _('Expand sidebar') : _('Collapse sidebar')
end

40
41
42
# File 'app/helpers/issuables_helper.rb', line 40

def sidebar_milestone_remaining_days(milestone)
  due_date_with_remaining_days(milestone[:due_date], milestone[:start_date])
end

34
35
36
37
38
# File 'app/helpers/issuables_helper.rb', line 34

def sidebar_milestone_tooltip_label(milestone)
  return _('Milestone') unless milestone.present?

  [escape_once(milestone[:title]), sidebar_milestone_remaining_days(milestone) || _('Milestone')].join('<br/>')
end

#state_name_with_icon(issuable) ⇒ Object


343
344
345
346
347
348
349
350
351
352
353
# File 'app/helpers/issuables_helper.rb', line 343

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

#updated_at_by(issuable) ⇒ Object


277
278
279
280
281
282
283
284
285
286
287
# File 'app/helpers/issuables_helper.rb', line 277

def updated_at_by(issuable)
  return {} unless issuable.edited?

  {
    updatedAt: issuable.last_edited_at.to_time.iso8601,
    updatedBy: {
      name: issuable.last_edited_by.name,
      path: user_path(issuable.last_edited_by)
    }
  }
end

#user_dropdown_label(user_id, default_label) ⇒ Object

rubocop: disable CodeReuse/ActiveRecord


92
93
94
95
96
97
98
99
100
101
102
103
# File 'app/helpers/issuables_helper.rb', line 92

def user_dropdown_label(user_id, default_label)
  return default_label if user_id.nil?
  return "Unassigned" if user_id == "0"

  user = User.find_by(id: user_id)

  if user
    user.name
  else
    default_label
  end
end

#users_dropdown_label(selected_users) ⇒ Object


80
81
82
83
84
85
86
87
88
89
# File 'app/helpers/issuables_helper.rb', line 80

def users_dropdown_label(selected_users)
  case selected_users.length
  when 0
    _('Unassigned')
  when 1
    selected_users[0].name
  else
    "#{selected_users[0].name} + #{selected_users.length - 1} more"
  end
end