Read about our upcoming Code of Conduct on this issue

projects_helper.rb 15.6 KB
Newer Older
1
2
# frozen_string_literal: true

randx's avatar
randx committed
3
module ProjectsHelper
4
  def link_to_project(project)
Josh Frye's avatar
Escape!    
Josh Frye committed
5
    link_to [project.namespace.becomes(Namespace), project], title: h(project.name) do
6
      title = content_tag(:span, project.name, class: 'project-name')
7
8

      if project.namespace
9
        namespace = content_tag(:span, "#{project.namespace.human_name} / ", class: 'namespace-name')
10
11
12
13
14
        title = namespace + title
      end

      title
    end
15
  end
randx's avatar
randx committed
16

Jacob Schatz's avatar
Jacob Schatz committed
17
  def link_to_member_avatar(author, opts = {})
18
    default_opts = { size: 16, lazy_load: false }
Jacob Schatz's avatar
Jacob Schatz committed
19
    opts = default_opts.merge(opts)
20
21

    classes = %W[avatar avatar-inline s#{opts[:size]}]
Maxim Rydkin's avatar
Maxim Rydkin committed
22
23
    classes << opts[:avatar_class] if opts[:avatar_class]

24
    avatar = avatar_icon_for_user(author, opts[:size])
25
26
27
    src = opts[:lazy_load] ? nil : avatar

    image_tag(src, width: opts[:size], class: classes, alt: '', "data-src" => avatar)
Jacob Schatz's avatar
Jacob Schatz committed
28
29
  end

30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
  def author_content_tag(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

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

Phil Hughes's avatar
Phil Hughes committed
49
  def link_to_member(project, author, opts = {}, &block)
50
    default_opts = { avatar: true, name: true, title: ":name" }
51
52
    opts = default_opts.merge(opts)

53
54
    return "(deleted)" unless author

55
    author_html = []
56

57
    # Build avatar image tag
58
    author_html << link_to_member_avatar(author, opts) if opts[:avatar]
59

60
    # Build name span tag
61
    author_html << author_content_tag(author, opts) if opts[:name]
62

Phil Hughes's avatar
Phil Hughes committed
63
64
    author_html << capture(&block) if block

65
    author_html = author_html.join.html_safe
66

67
    if opts[:name]
68
      link_to(author_html, user_path(author), class: "author-link #{"#{opts[:extra_class]}" if opts[:extra_class]} #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe
69
    else
70
      title = opts[:title].sub(":name", sanitize(author.name))
71
      link_to(author_html, user_path(author), class: "author-link has-tooltip", title: title, data: { container: 'body' }).html_safe
72
    end
randx's avatar
randx committed
73
  end
74

75
  def project_title(project)
76
77
    namespace_link =
      if project.group
Phil Hughes's avatar
Phil Hughes committed
78
        group_title(project.group, nil, nil)
79
80
81
      else
        owner = project.namespace.owner
        link_to(simple_sanitize(owner.name), user_path(owner))
82
      end
83

Phil Hughes's avatar
Phil Hughes committed
84
    project_link = link_to project_path(project) do
85
86
      icon = project_icon(project, alt: project.name, class: 'avatar-tile', width: 15, height: 15) if project.avatar_url && !Rails.env.test?
      [icon, content_tag("span", simple_sanitize(project.name), class: "breadcrumb-item-text js-breadcrumb-item-text")].join.html_safe
Phil Hughes's avatar
Phil Hughes committed
87
    end
88

Phil Hughes's avatar
Phil Hughes committed
89
90
    namespace_link = breadcrumb_list_item(namespace_link) unless project.group
    project_link = breadcrumb_list_item project_link
Phil Hughes's avatar
Phil Hughes committed
91

Phil Hughes's avatar
Phil Hughes committed
92
    "#{namespace_link} #{project_link}".html_safe
93
  end
94
95

  def remove_project_message(project)
96
97
    _("You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?") %
      { project_full_name: project.full_name }
98
  end
99

100
  def transfer_project_message(project)
101
102
    _("You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?") %
      { project_full_name: project.full_name }
103
104
  end

105
  def remove_fork_project_message(project)
106
    _("You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?") %
107
108
109
110
111
112
113
114
115
      { forked_from_project: fork_source_name(project) }
  end

  def fork_source_name(project)
    if @project.fork_source
      @project.fork_source.full_name
    else
      @project.fork_network&.deleted_root_project_name
    end
116
117
  end

118
119
120
121
  def project_nav_tabs
    @nav_tabs ||= get_project_nav_tabs(@project, current_user)
  end

122
123
124
125
126
127
  def project_search_tabs?(tab)
    abilities = Array(search_tab_ability_map[tab])

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

128
129
130
131
  def project_nav_tab?(name)
    project_nav_tabs.include? name
  end

Douwe Maan's avatar
Douwe Maan committed
132
  def project_for_deploy_key(deploy_key)
133
    if deploy_key.has_access_to?(@project)
Douwe Maan's avatar
Douwe Maan committed
134
135
      @project
    else
Lin Jen-Shin's avatar
Lin Jen-Shin committed
136
137
138
      deploy_key.projects.find do |project|
        can?(current_user, :read_project, project)
      end
Douwe Maan's avatar
Douwe Maan committed
139
140
141
    end
  end

Valery Sizov's avatar
Valery Sizov committed
142
143
144
  def can_change_visibility_level?(project, current_user)
    return false unless can?(current_user, :change_visibility_level, project)

145
146
    if project.fork_source
      project.fork_source.visibility_level > Gitlab::VisibilityLevel::PRIVATE
Valery Sizov's avatar
Valery Sizov committed
147
148
149
150
151
    else
      true
    end
  end

152
  def last_push_event
153
    current_user&.recent_push(@project)
154
155
  end

156
  def link_to_autodeploy_doc
157
    link_to _('About auto deploy'), help_page_path('ci/autodeploy/index'), target: '_blank'
158
159
  end

Valery Sizov's avatar
Valery Sizov committed
160
  def autodeploy_flash_notice(branch_name)
161
162
163
    translation = _("Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}") %
      { branch_name: truncate(sanitize(branch_name)), link_to_autodeploy_doc: link_to_autodeploy_doc }
    translation.html_safe
Valery Sizov's avatar
Valery Sizov committed
164
165
  end

166
  def project_list_cache_key(project)
167
    key = [
168
      project.route.cache_key,
169
      project.cache_key,
170
      project.last_activity_date,
171
172
      controller.controller_name,
      controller.action_name,
173
      Gitlab::CurrentSettings.cache_key,
174
      "cross-project:#{can?(current_user, :read_cross_project)}",
175
      max_project_member_access_cache_key(project),
Mark Chao's avatar
Mark Chao committed
176
      'v2.6'
177
178
    ]

179
180
181
182
183
    key << pipeline_status_cache_key(project.pipeline_status) if project.pipeline_status.has_status?

    key
  end

184
  def load_pipeline_status(projects)
185
186
    Gitlab::Cache::Ci::ProjectPipelineStatus
      .load_in_batch_for_projects(projects)
187
188
  end

189
  def show_no_ssh_key_message?
190
191
192
193
    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?
194
195
196
197
  end

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

201
202
203
204
205
206
207
208
  def show_auto_devops_implicitly_enabled_banner?(project)
    cookie_key = "hide_auto_devops_implicitly_enabled_banner_#{project.id}"

    project.has_auto_devops_implicitly_enabled? &&
      cookies[cookie_key.to_sym].blank? &&
      (project.owner == current_user || project.team.maintainer?(current_user))
  end

209
  def link_to_set_password
210
    if current_user.require_password_creation_for_git?
211
212
213
214
215
216
      link_to s_('SetPasswordToCloneLink|set a password'), edit_profile_password_path
    else
      link_to s_('CreateTokenToCloneLink|create a personal access token'), profile_personal_access_tokens_path
    end
  end

217
218
219
220
221
222
223
224
  # 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.
225
  # rubocop: disable CodeReuse/ActiveRecord
226
  def any_projects?(projects)
227
228
    return projects.any? if projects.is_a?(Array)

229
230
231
232
233
234
    if projects.limit_value
      projects.to_a.any?
    else
      projects.except(:offset).any?
    end
  end
235
  # rubocop: enable CodeReuse/ActiveRecord
236

237
238
  def show_projects?(projects, params)
    !!(params[:personal] || params[:name] || any_projects?(projects))
239
240
  end

241
242
243
244
245
246
247
248
249
250
251
  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

252
253
254
255
256
257
258
259
  def show_xcode_link?(project = @project)
    browser.platform.mac? && project.repository.xcode_project?
  end

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

260
261
262
263
  def link_to_bfg
    link_to 'BFG', 'https://rtyley.github.io/bfg-repo-cleaner/', target: '_blank', rel: 'noopener noreferrer'
  end

264
265
266
267
  def legacy_render_context(params)
    params[:legacy_render] ? { markdown_engine: :redcarpet } : {}
  end

268
269
270
  private

  def get_project_nav_tabs(project, current_user)
271
    nav_tabs = [:home]
272

273
    if !project.empty_repo? && can?(current_user, :download_code, project)
274
      nav_tabs << [:files, :commits, :network, :graphs, :forks]
275
276
    end

277
    if project.repo_exists? && can?(current_user, :read_merge_request, project)
278
279
280
      nav_tabs << :merge_requests
    end

281
    if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project)
282
      nav_tabs << :container_registry
Kamil Trzcinski's avatar
Kamil Trzcinski committed
283
284
    end

285
286
    if project.builds_enabled? && can?(current_user, :read_pipeline, project)
      nav_tabs << :pipelines
287
288
289
    end

    if can?(current_user, :read_environment, project) || can?(current_user, :read_cluster, project)
290
      nav_tabs << :operations
291
292
    end

293
294
295
296
    if project.external_issue_tracker
      nav_tabs << :external_issue_tracker
    end

297
298
299
300
301
302
303
304
305
306
307
    tab_ability_map.each do |tab, ability|
      if can?(current_user, ability, project)
        nav_tabs << tab
      end
    end

    nav_tabs.flatten
  end

  def tab_ability_map
    {
308
309
310
311
312
      environments:     :read_environment,
      milestones:       :read_milestone,
      snippets:         :read_project_snippet,
      settings:         :admin_project,
      builds:           :read_build,
313
      clusters:         :read_cluster,
314
315
316
317
      labels:           :read_label,
      issues:           :read_issue,
      project_members:  :read_project_member,
      wiki:             :read_wiki
318
    }
319
  end
320

321
322
323
324
325
326
327
  def search_tab_ability_map
    @search_tab_ability_map ||= tab_ability_map.merge(
      blobs:          :download_code,
      commits:        :download_code,
      merge_requests: :read_merge_request,
      notes:          [:read_merge_request, :download_code, :read_issue, :read_project_snippet]
    )
328
  end
329

330
331
  def project_lfs_status(project)
    if project.lfs_enabled?
332
      content_tag(:span, class: 'lfs-enabled') do
333
        s_('LFSStatus|Enabled')
334
335
      end
    else
336
      content_tag(:span, class: 'lfs-disabled') do
337
        s_('LFSStatus|Disabled')
338
339
340
341
      end
    end
  end

342
343
  def git_user_name
    if current_user
344
      current_user.name.gsub('"', '\"')
345
    else
346
      _("Your name")
347
348
349
350
351
352
353
354
355
356
    end
  end

  def git_user_email
    if current_user
      current_user.email
    else
      "your@email.com"
    end
  end
357

358
  def default_url_to_repo(project = @project)
359
360
    case default_clone_protocol
    when 'ssh'
361
362
      project.ssh_url_to_repo
    else
363
      project.http_url_to_repo
364
    end
365
  end
366

367
368
369
370
  def default_clone_label
    _("Copy %{protocol} clone URL") % { protocol: default_clone_protocol.upcase }
  end

371
  def default_clone_protocol
372
373
    if allowed_protocols_present?
      enabled_protocol
374
    else
375
376
377
378
379
380
381
382
383
      extra_default_clone_protocol
    end
  end

  def extra_default_clone_protocol
    if !current_user || current_user.require_ssh_key?
      gitlab_config.protocol
    else
      'ssh'
384
    end
385
  end
386
387
388

  def project_last_activity(project)
    if project.last_activity_at
389
      time_ago_with_tooltip(project.last_activity_at, placement: 'bottom', html_class: 'last_activity_time_ago')
390
    else
391
      s_("ProjectLastActivity|Never")
392
393
    end
  end
394

395
396
  def project_wiki_path_with_version(proj, page, version, is_newest)
    url_params = is_newest ? {} : { version_id: version }
397
    project_wiki_path(proj, page, url_params)
398
  end
399

Valery Sizov's avatar
Valery Sizov committed
400
401
402
  def project_status_css_class(status)
    case status
    when "started"
403
      "table-active"
Valery Sizov's avatar
Valery Sizov committed
404
    when "failed"
405
      "table-danger"
Valery Sizov's avatar
Valery Sizov committed
406
    when "finished"
407
      "table-success"
Valery Sizov's avatar
Valery Sizov committed
408
409
    end
  end
410

411
  def readme_cache_key
412
    sha = @project.commit.try(:sha) || 'nil'
413
    [@project.full_path, sha, "readme"].join('-')
414
  end
415

416
417
418
  def current_ref
    @ref || @repository.try(:root_ref)
  end
419

420
421
422
  def project_child_container_class(view_path)
    view_path == "projects/issues/issues" ? "prepend-top-default" : "project-show-#{view_path}"
  end
423
424
425
426

  def project_issues(project)
    IssuesFinder.new(current_user, project_id: project.id).execute
  end
Luke "Jared" Bennett's avatar
Luke "Jared" Bennett committed
427

428
429
430
  def restricted_levels
    return [] if current_user.admin?

431
    Gitlab::CurrentSettings.restricted_visibility_levels || []
Luke "Jared" Bennett's avatar
Luke "Jared" Bennett committed
432
  end
433

434
435
436
437
438
439
440
441
442
443
444
  def project_permissions_settings(project)
    feature = project.project_feature
    {
      visibilityLevel: project.visibility_level,
      requestAccessEnabled: !!project.request_access_enabled,
      issuesAccessLevel: feature.issues_access_level,
      repositoryAccessLevel: feature.repository_access_level,
      mergeRequestsAccessLevel: feature.merge_requests_access_level,
      buildsAccessLevel: feature.builds_access_level,
      wikiAccessLevel: feature.wiki_access_level,
      snippetsAccessLevel: feature.snippets_access_level,
445
      pagesAccessLevel: feature.pages_access_level,
446
447
448
449
450
451
      containerRegistryEnabled: !!project.container_registry_enabled,
      lfsEnabled: !!project.lfs_enabled
    }
  end

  def project_permissions_panel_data(project)
452
    {
453
454
455
456
457
458
      currentSettings: project_permissions_settings(project),
      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/project/container_registry'),
459
      lfsAvailable: Gitlab.config.lfs.enabled,
460
461
462
463
      lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs'),
      pagesAvailable: Gitlab.config.pages.enabled,
      pagesAccessControlEnabled: Gitlab.config.pages.access_control,
      pagesHelpPath: help_page_path('user/project/pages/index.md')
464
    }
465
  end
466

467
468
  def project_permissions_panel_data_json(project)
    project_permissions_panel_data(project).to_json.html_safe
469
470
471
472
473
474
475
476
  end

  def project_allowed_visibility_levels(project)
    Gitlab::VisibilityLevel.values.select do |level|
      project.visibility_level_allowed?(level) && !restricted_levels.include?(level)
    end
  end

477
478
479
480
481
482
483
  def find_file_path
    return unless @project && !@project.empty_repo?

    ref = @ref || @project.repository.root_ref

    project_find_file_path(@project, ref)
  end
484
485
486
487

  def can_show_last_commit_in_list?(project)
    can?(current_user, :read_cross_project) && project.commit
  end
Rob Watson's avatar
Rob Watson committed
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505

  def pages_https_only_disabled?
    !@project.pages_domains.all?(&:https?)
  end

  def pages_https_only_title
    return unless pages_https_only_disabled?

    "You must enable HTTPS for all your domains first"
  end

  def pages_https_only_label_class
    if pages_https_only_disabled?
      "list-label disabled"
    else
      "list-label"
    end
  end
506

507
508
509
510
511
512
513
514
  def sidebar_projects_paths
    %w[
      projects#show
      projects#activity
      cycle_analytics#show
    ]
  end

515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
  def sidebar_settings_paths
    %w[
      projects#edit
      project_members#index
      integrations#show
      services#edit
      repository#show
      ci_cd#show
      badges#index
      pages#show
    ]
  end

  def sidebar_repository_paths
    %w[
      tree
      blob
      blame
      edit_tree
      new_tree
      find_file
      commit
      commits
      compare
      projects/repositories
      tags
      branches
      releases
      graphs
      network
    ]
  end
547
548
549
550
551
552
553
554
555

  def sidebar_operations_paths
    %w[
      environments
      clusters
      user
      gcp
    ]
  end
randx's avatar
randx committed
556
end