Module: ProjectsHelper

Included in:
GitlabRoutingHelper, MergeRequestWidgetEntity
Defined in:
app/helpers/projects_helper.rb

Instance Method Summary collapse

Instance Method Details

#able_to_see_issues?(project, user) ⇒ Boolean

Returns:

  • (Boolean)

423
424
425
# File 'app/helpers/projects_helper.rb', line 423

def able_to_see_issues?(project, user)
  project.issues_enabled? && can?(user, :read_issue, project)
end

#able_to_see_merge_requests?(project, user) ⇒ Boolean

Returns:

  • (Boolean)

427
428
429
# File 'app/helpers/projects_helper.rb', line 427

def able_to_see_merge_requests?(project, user)
  project.merge_requests_enabled? && can?(user, :read_merge_request, project)
end

#any_projects?(projects) ⇒ Boolean

Returns true if any projects are present.

If the relation has a LIMIT applied we'll cast the relation to an Array since repeated any? checks would otherwise result in multiple COUNT queries being executed.

If no limit is applied we'll just issue a COUNT since the result set could be too large to load into memory.

Returns:

  • (Boolean)

244
245
246
247
248
249
250
251
252
# File 'app/helpers/projects_helper.rb', line 244

def any_projects?(projects)
  return projects.any? if projects.is_a?(Array)

  if projects.limit_value
    projects.to_a.any?
  else
    projects.except(:offset).any?
  end
end

#author_content_tag(author, opts = {}) ⇒ Object


34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'app/helpers/projects_helper.rb', line 34

def (author, opts = {})
  default_opts = { author_class: 'author', tooltip: false, by_username: false }
  opts = default_opts.merge(opts)

  has_tooltip = !opts[:by_username] && opts[:tooltip]

  username = opts[:by_username] ? author.to_reference : author.name
  name_tag_options = { class: [opts[:author_class]] }

  if has_tooltip
    name_tag_options[:title] = author.to_reference
    name_tag_options[:data] = { placement: 'top' }
    name_tag_options[:class] << 'has-tooltip'
  end

  # NOTE: ActionView::Helpers::TagHelper#content_tag HTML escapes username
  (:span, username, name_tag_options)
end

#autodeploy_flash_notice(branch_name) ⇒ Object


166
167
168
169
# File 'app/helpers/projects_helper.rb', line 166

def autodeploy_flash_notice(branch_name)
  html_escape(_("Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}")) %
    { branch_name: tag.strong(truncate(sanitize(branch_name))), link_to_autodeploy_doc: link_to_autodeploy_doc }
end

#can_admin_project_member?(project) ⇒ Boolean

Returns:

  • (Boolean)

322
323
324
# File 'app/helpers/projects_helper.rb', line 322

def can_admin_project_member?(project)
  Ability.allowed?(current_user, :admin_project_member, project) && !membership_locked?
end

#can_change_visibility_level?(project, current_user) ⇒ Boolean

Returns:

  • (Boolean)

148
149
150
# File 'app/helpers/projects_helper.rb', line 148

def can_change_visibility_level?(project, current_user)
  can?(current_user, :change_visibility_level, project)
end

#can_disable_emails?(project, current_user) ⇒ Boolean

Returns:

  • (Boolean)

152
153
154
155
156
# File 'app/helpers/projects_helper.rb', line 152

def can_disable_emails?(project, current_user)
  return false if project.group&.emails_disabled?

  can?(current_user, :set_emails_disabled, project)
end

#delete_confirm_phrase(project) ⇒ Object

Returns the confirm phrase the user needs to type in order to delete the project

Thus the phrase should include the namespace to make it very clear to the user which project is subject to deletion. Relevant issue: gitlab.com/gitlab-org/gitlab/-/issues/343591


419
420
421
# File 'app/helpers/projects_helper.rb', line 419

def delete_confirm_phrase(project)
  project.path_with_namespace
end

#directory?Boolean

Returns:

  • (Boolean)

308
309
310
# File 'app/helpers/projects_helper.rb', line 308

def directory?
  @path.present?
end

#error_tracking_setting_project_jsonObject


294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'app/helpers/projects_helper.rb', line 294

def error_tracking_setting_project_json
  setting = @project.error_tracking_setting

  return if setting.blank? || setting.project_slug.blank? ||
      setting.organization_slug.blank?

  {
    name: setting.project_name,
    organization_name: setting.organization_name,
    organization_slug: setting.organization_slug,
    slug: setting.project_slug
  }.to_json
end

#explore_projects_tab?Boolean

Returns:

  • (Boolean)

280
281
282
283
284
# File 'app/helpers/projects_helper.rb', line 280

def explore_projects_tab?
  current_page?(explore_projects_path) ||
    current_page?(trending_explore_projects_path) ||
    current_page?(starred_explore_projects_path)
end

#external_classification_label_help_messageObject


312
313
314
315
316
317
318
319
320
# File 'app/helpers/projects_helper.rb', line 312

def external_classification_label_help_message
  default_label = ::Gitlab::CurrentSettings.current_application_settings
                    .external_authorization_service_default_label

  s_(
    "ExternalAuthorizationService|When no classification label is set the "\
      "default label `%{default_label}` will be used."
  ) % { default_label: default_label }
end

#fork_button_disabled_tooltip(project) ⇒ Object


431
432
433
434
435
436
437
438
439
# File 'app/helpers/projects_helper.rb', line 431

def fork_button_disabled_tooltip(project)
  return unless current_user

  if !current_user.can?(:fork_project, project)
    s_("ProjectOverview|You don't have permission to fork this project")
  elsif !current_user.can?(:create_fork)
    s_('ProjectOverview|You have reached your project limit')
  end
end

#grafana_integration_enabled?Boolean

Returns:

  • (Boolean)

366
367
368
# File 'app/helpers/projects_helper.rb', line 366

def grafana_integration_enabled?
  @project.grafana_integration&.enabled?
end

#grafana_integration_masked_tokenObject


362
363
364
# File 'app/helpers/projects_helper.rb', line 362

def grafana_integration_masked_token
  @project.grafana_integration&.masked_token
end

#grafana_integration_urlObject


358
359
360
# File 'app/helpers/projects_helper.rb', line 358

def grafana_integration_url
  @project.grafana_integration&.grafana_url
end

#import_from_bitbucket_messageObject


441
442
443
# File 'app/helpers/projects_helper.rb', line 441

def import_from_bitbucket_message
  configure_oauth_import_message('Bitbucket', help_page_path("integration/bitbucket"))
end

#import_from_gitlab_messageObject


445
446
447
# File 'app/helpers/projects_helper.rb', line 445

def import_from_gitlab_message
  configure_oauth_import_message('GitLab.com', help_page_path("integration/gitlab"))
end

#last_push_eventObject


158
159
160
# File 'app/helpers/projects_helper.rb', line 158

def last_push_event
  current_user&.recent_push(@project)
end

162
163
164
# File 'app/helpers/projects_helper.rb', line 162

def link_to_autodeploy_doc
  link_to _('About auto deploy'), help_page_path('topics/autodevops/stages.md', anchor: 'auto-deploy'), target: '_blank', rel: 'noopener'
end

53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'app/helpers/projects_helper.rb', line 53

def link_to_member(project, author, opts = {}, &block)
  default_opts = { avatar: true, name: true, title: ":name" }
  opts = default_opts.merge(opts)

  return "(deleted)" unless author

  data_attrs = {
    user_id: author.id,
    username: author.username,
    name: author.name
  }

  inject_classes = ["author-link", opts[:extra_class]]

  if opts[:name]
    inject_classes.concat(["js-user-link", opts[:mobile_classes]])
  else
    inject_classes.append( "has-tooltip" )
  end

  inject_classes = inject_classes.compact.join(" ")

  author_html = []
  # Build avatar image tag
  author_html << link_to_member_avatar(author, opts) if opts[:avatar]
  # Build name span tag
  author_html << (author, opts) if opts[:name]
  author_html << capture(&block) if block
  author_html = author_html.join.html_safe

  if opts[:name]
    link_to(author_html, user_path(author), class: inject_classes, data: data_attrs).html_safe
  else
    title = opts[:title].sub(":name", sanitize(author.name))
    link_to(author_html, user_path(author), class: inject_classes, title: title, data: { container: 'body', qa_selector: 'assignee_link' }).html_safe
  end
end

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

def link_to_member_avatar(author, opts = {})
  default_opts = { size: 16 }
  opts = default_opts.merge(opts)

  classes = %W[avatar avatar-inline s#{opts[:size]}]
  classes << opts[:avatar_class] if opts[:avatar_class]

  avatar = avatar_icon_for_user(author, opts[:size])

  image_tag(avatar, width: opts[:size], class: classes, alt: '')
end

9
10
11
12
13
14
15
16
17
18
19
20
# File 'app/helpers/projects_helper.rb', line 9

def link_to_project(project)
  link_to namespace_project_path(namespace_id: project.namespace, id: project), title: h(project.name), class: 'gl-link' do
    title = (:span, project.name, class: 'project-name')

    if project.namespace
      namespace = (:span, "#{project.namespace.human_name} / ", class: 'namespace-name')
      title = namespace + title
    end

    title
  end
end

#load_pipeline_status(projects) ⇒ Object


191
192
193
194
# File 'app/helpers/projects_helper.rb', line 191

def load_pipeline_status(projects)
  Gitlab::Cache::Ci::ProjectPipelineStatus
    .load_in_batch_for_projects(projects)
end

#membership_locked?Boolean

Returns:

  • (Boolean)

330
331
332
# File 'app/helpers/projects_helper.rb', line 330

def membership_locked?
  false
end

#metrics_dashboard_timezoneObject


354
355
356
# File 'app/helpers/projects_helper.rb', line 354

def metrics_dashboard_timezone
  @project.metrics_setting_dashboard_timezone
end

#metrics_external_dashboard_urlObject


350
351
352
# File 'app/helpers/projects_helper.rb', line 350

def metrics_external_dashboard_url
  @project.metrics_setting_external_dashboard_url
end

#no_password_messageObject


214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'app/helpers/projects_helper.rb', line 214

def no_password_message
  push_pull_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('topics/git/terminology', anchor: 'pull-and-push') }
  clone_with_https_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('gitlab-basics/start-using-git', anchor: 'clone-with-https') }
  set_password_link_start = '<a href="%{url}">'.html_safe % { url: edit_profile_password_path }
  set_up_pat_link_start = '<a href="%{url}">'.html_safe % { url: profile_personal_access_tokens_path }

  message = if current_user.require_password_creation_for_git?
              _('Your account is authenticated with SSO or SAML. To %{push_pull_link_start}push and pull%{link_end} over %{protocol} with Git using this account, you must %{set_password_link_start}set a password%{link_end} or %{set_up_pat_link_start}set up a Personal Access Token%{link_end} to use instead of a password. For more information, see %{clone_with_https_link_start}Clone with HTTPS%{link_end}.')
            else
              _('Your account is authenticated with SSO or SAML. To %{push_pull_link_start}push and pull%{link_end} over %{protocol} with Git using this account, you must %{set_up_pat_link_start}set up a Personal Access Token%{link_end} to use instead of a password. For more information, see %{clone_with_https_link_start}Clone with HTTPS%{link_end}.')
            end

  html_escape(message) % {
    push_pull_link_start: push_pull_link_start,
    protocol: gitlab_config.protocol.upcase,
    clone_with_https_link_start: clone_with_https_link_start,
    set_password_link_start: set_password_link_start,
    set_up_pat_link_start: set_up_pat_link_start,
    link_end: '</a>'.html_safe
  }
end

#project_can_be_shared?Boolean

Returns:

  • (Boolean)

326
327
328
# File 'app/helpers/projects_helper.rb', line 326

def project_can_be_shared?
  !membership_locked? || @project.allowed_to_share_with_group?
end

#project_classes(project) ⇒ Object


408
409
410
411
412
# File 'app/helpers/projects_helper.rb', line 408

def project_classes(project)
  return "project-highlight-puc" if project.warn_about_potentially_unwanted_characters?

  ""
end

#project_incident_management_settingObject


4
5
6
7
# File 'app/helpers/projects_helper.rb', line 4

def project_incident_management_setting
  @project_incident_management_setting ||= @project.incident_management_setting ||
    @project.build_incident_management_setting
end

#project_license_name(project) ⇒ Object


370
371
372
373
374
375
376
377
378
379
# File 'app/helpers/projects_helper.rb', line 370

def project_license_name(project)
  key = "project:#{project.id}:license_name"

  Gitlab::SafeRequestStore.fetch(key) { project.repository.license&.name }
rescue GRPC::Unavailable, GRPC::DeadlineExceeded, Gitlab::Git::CommandError => e
  Gitlab::ErrorTracking.track_exception(e)
  Gitlab::SafeRequestStore[key] = nil

  nil
end

#project_list_cache_key(project, pipeline_status: true) ⇒ Object


171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'app/helpers/projects_helper.rb', line 171

def project_list_cache_key(project, pipeline_status: true)
  key = [
    project.route.cache_key,
    project.cache_key,
    project.last_activity_date,
    controller.controller_name,
    controller.action_name,
    Gitlab::CurrentSettings.cache_key,
    "cross-project:#{can?(current_user, :read_cross_project)}",
    max_project_member_access_cache_key(project),
    pipeline_status,
    Gitlab::I18n.locale,
    'v2.6'
  ]

  key << pipeline_status_cache_key(project.pipeline_status) if pipeline_status && project.pipeline_status.has_status?

  key
end

#project_permissions_panel_data(project) ⇒ Object


385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'app/helpers/projects_helper.rb', line 385

def project_permissions_panel_data(project)
  {
    packagesAvailable: ::Gitlab.config.packages.enabled,
    packagesHelpPath: help_page_path('user/packages/index'),
    currentSettings: project_permissions_settings(project),
    canDisableEmails: can_disable_emails?(project, current_user),
    canChangeVisibilityLevel: can_change_visibility_level?(project, current_user),
    allowedVisibilityOptions: project_allowed_visibility_levels(project),
    visibilityHelpPath: help_page_path('public_access/public_access'),
    registryAvailable: Gitlab.config.registry.enabled,
    registryHelpPath: help_page_path('user/packages/container_registry/index'),
    lfsAvailable: Gitlab.config.lfs.enabled,
    lfsHelpPath: help_page_path('topics/git/lfs/index'),
    lfsObjectsExist: project.lfs_objects.exists?,
    lfsObjectsRemovalHelpPath: help_page_path('topics/git/lfs/index', anchor: 'removing-objects-from-lfs'),
    pagesAvailable: Gitlab.config.pages.enabled,
    pagesAccessControlEnabled: Gitlab.config.pages.access_control,
    pagesAccessControlForced: ::Gitlab::Pages.access_control_is_forced?,
    pagesHelpPath: help_page_path('user/project/pages/introduction', anchor: 'gitlab-pages-access-control'),
    issuesHelpPath: help_page_path('user/project/issues/index')
  }
end

#project_search_tabs?(tab) ⇒ Boolean

Returns:

  • (Boolean)

142
143
144
145
146
# File 'app/helpers/projects_helper.rb', line 142

def project_search_tabs?(tab)
  abilities = Array(search_tab_ability_map[tab])

  abilities.any? { |ability| can?(current_user, ability, @project) }
end

#project_title(project) ⇒ Object


91
92
93
94
95
96
97
98
99
# File 'app/helpers/projects_helper.rb', line 91

def project_title(project)
  namespace_link = build_namespace_breadcrumb_link(project)
  project_link = build_project_breadcrumb_link(project)

  namespace_link = breadcrumb_list_item(namespace_link) unless project.group
  project_link = breadcrumb_list_item project_link

  "#{namespace_link} #{project_link}".html_safe
end

#push_to_create_project_command(user = current_user) ⇒ Object


261
262
263
264
265
266
267
268
269
270
# File 'app/helpers/projects_helper.rb', line 261

def push_to_create_project_command(user = current_user)
  repository_url =
    if Gitlab::CurrentSettings.current_application_settings.enabled_git_access_protocol == 'http'
      user_url(user)
    else
      Gitlab.config.gitlab_shell.ssh_path_prefix + user.username
    end

  "git push --set-upstream #{repository_url}/$(git rev-parse --show-toplevel | xargs basename).git $(git rev-parse --abbrev-ref HEAD)"
end

#remove_fork_project_confirm_json(project, remove_form_id) ⇒ Object


129
130
131
132
133
134
135
136
# File 'app/helpers/projects_helper.rb', line 129

def remove_fork_project_confirm_json(project, remove_form_id)
  {
    remove_form_id: remove_form_id,
    button_text: _('Remove fork relationship'),
    confirm_danger_message: remove_fork_project_warning_message(project),
    phrase: @project.path
  }
end

#remove_fork_project_description_message(project) ⇒ Object


111
112
113
114
115
116
117
118
119
120
121
122
# File 'app/helpers/projects_helper.rb', line 111

def remove_fork_project_description_message(project)
  source = visible_fork_source(project)

  if source
    msg = _('This will remove the fork relationship between this project and %{fork_source}.') %
      { fork_source: link_to(source.full_name, project_path(source)) }

    msg.html_safe
  else
    _('This will remove the fork relationship between this project and other projects in the fork network.')
  end
end

#remove_fork_project_warning_message(project) ⇒ Object


124
125
126
127
# File 'app/helpers/projects_helper.rb', line 124

def remove_fork_project_warning_message(project)
  _("You are going to remove the fork relationship from %{project_full_name}. Are you ABSOLUTELY sure?") %
    { project_full_name: project.full_name }
end

#remove_project_message(project) ⇒ Object


101
102
103
104
# File 'app/helpers/projects_helper.rb', line 101

def remove_project_message(project)
  _("You are going to delete %{project_full_name}. Deleted projects CANNOT be restored! Are you ABSOLUTELY sure?") %
    { project_full_name: project.full_name }
end

#share_project_description(project) ⇒ Object


334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'app/helpers/projects_helper.rb', line 334

def share_project_description(project)
  share_with_group   = project.allowed_to_share_with_group?
  share_with_members = !membership_locked?

  description =
    if share_with_group && share_with_members
      _("You can invite a new member to %{project_name} or invite another group.")
    elsif share_with_group
      _("You can invite another group to %{project_name}.")
    elsif share_with_members
      _("You can invite a new member to %{project_name}.")
    end

  html_escape(description) % { project_name: tag.strong(project.name) }
end

#show_auto_devops_implicitly_enabled_banner?(project, user) ⇒ Boolean

Returns:

  • (Boolean)

208
209
210
211
212
# File 'app/helpers/projects_helper.rb', line 208

def show_auto_devops_implicitly_enabled_banner?(project, user)
  return false unless user_can_see_auto_devops_implicitly_enabled_banner?(project, user)

  cookies["hide_auto_devops_implicitly_enabled_banner_#{project.id}".to_sym].blank?
end

#show_issue_count?(disabled: false, compact_mode: false) ⇒ Boolean

Returns:

  • (Boolean)

290
291
292
# File 'app/helpers/projects_helper.rb', line 290

def show_issue_count?(disabled: false, compact_mode: false)
  !disabled && !compact_mode
end

#show_merge_request_count?(disabled: false, compact_mode: false) ⇒ Boolean

Returns:

  • (Boolean)

286
287
288
# File 'app/helpers/projects_helper.rb', line 286

def show_merge_request_count?(disabled: false, compact_mode: false)
  !disabled && !compact_mode
end

#show_no_password_message?Boolean

Returns:

  • (Boolean)

203
204
205
206
# File 'app/helpers/projects_helper.rb', line 203

def show_no_password_message?
  cookies[:hide_no_password_message].blank? && !current_user.hide_no_password &&
    current_user.require_extra_setup_for_git_auth?
end

#show_no_ssh_key_message?Boolean

Returns:

  • (Boolean)

196
197
198
199
200
201
# File 'app/helpers/projects_helper.rb', line 196

def show_no_ssh_key_message?
  Gitlab::CurrentSettings.user_show_add_ssh_key_message? &&
    cookies[:hide_no_ssh_message].blank? &&
    !current_user.hide_no_ssh_key &&
    current_user.require_ssh_key?
end

#show_projects?(projects, params) ⇒ Boolean

TODO: Remove this method when removing the feature flag gitlab.com/gitlab-org/gitlab/merge_requests/11209#note_162234863 make sure to remove from the EE specific controller as well: ee/app/controllers/ee/dashboard/projects_controller.rb

Returns:

  • (Boolean)

257
258
259
# File 'app/helpers/projects_helper.rb', line 257

def show_projects?(projects, params)
  Feature.enabled?(:project_list_filter_bar) || !!(params[:personal] || params[:name] || any_projects?(projects))
end

#show_terraform_banner?(project) ⇒ Boolean

Returns:

  • (Boolean)

381
382
383
# File 'app/helpers/projects_helper.rb', line 381

def show_terraform_banner?(project)
  Feature.enabled?(:show_terraform_banner, type: :ops) && project.repository_languages.with_programming_language('HCL').exists? && project.terraform_states.empty?
end

#show_xcode_link?(project = @project) ⇒ Boolean

Returns:

  • (Boolean)

272
273
274
# File 'app/helpers/projects_helper.rb', line 272

def show_xcode_link?(project = @project)
  browser.platform.mac? && project.repository.xcode_project?
end

#transfer_project_message(project) ⇒ Object


106
107
108
109
# File 'app/helpers/projects_helper.rb', line 106

def transfer_project_message(project)
  _("You are going to transfer %{project_full_name} to another namespace. Are you ABSOLUTELY sure?") %
    { project_full_name: project.full_name }
end

#visible_fork_source(project) ⇒ Object


138
139
140
# File 'app/helpers/projects_helper.rb', line 138

def visible_fork_source(project)
  project.fork_source if project.fork_source && can?(current_user, :read_project, project.fork_source)
end

#xcode_uri_to_repo(project = @project) ⇒ Object


276
277
278
# File 'app/helpers/projects_helper.rb', line 276

def xcode_uri_to_repo(project = @project)
  "xcode://clone?repo=#{CGI.escape(default_url_to_repo(project))}"
end