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

R
randx 已提交
3
module ProjectsHelper
4
  def link_to_project(project)
J
Escape!  
Josh Frye 已提交
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
D
Dmitriy Zaporozhets 已提交
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
R
randx 已提交
16

J
Jacob Schatz 已提交
17
  def link_to_member_avatar(author, opts = {})
18
    default_opts = { size: 16, lazy_load: false }
J
Jacob Schatz 已提交
19
    opts = default_opts.merge(opts)
20 21

    classes = %W[avatar avatar-inline s#{opts[:size]}]
M
Maxim Rydkin 已提交
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)
J
Jacob Schatz 已提交
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

P
Phil Hughes 已提交
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

P
Phil Hughes 已提交
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
R
randx 已提交
73
  end
74

75
  def project_title(project)
76 77
    namespace_link =
      if project.group
P
Phil Hughes 已提交
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

P
Phil Hughes 已提交
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
P
Phil Hughes 已提交
87
    end
88

P
Phil Hughes 已提交
89 90
    namespace_link = breadcrumb_list_item(namespace_link) unless project.group
    project_link = breadcrumb_list_item project_link
P
Phil Hughes 已提交
91

P
Phil Hughes 已提交
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

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

V
Valery Sizov 已提交
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
V
Valery Sizov 已提交
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

V
Valery Sizov 已提交
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
V
Valery Sizov 已提交
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),
M
Mark Chao 已提交
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 225
  # 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.
  def any_projects?(projects)
226 227
    return projects.any? if projects.is_a?(Array)

228 229 230 231 232 233 234
    if projects.limit_value
      projects.to_a.any?
    else
      projects.except(:offset).any?
    end
  end

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

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

A
André Luís 已提交
250 251 252 253 254 255 256 257
  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

258 259 260 261
  def legacy_render_context(params)
    params[:legacy_render] ? { markdown_engine: :redcarpet } : {}
  end

262 263 264
  private

  def get_project_nav_tabs(project, current_user)
265
    nav_tabs = [:home]
266

267
    if !project.empty_repo? && can?(current_user, :download_code, project)
268
      nav_tabs << [:files, :commits, :network, :graphs, :forks]
269 270
    end

271
    if project.repo_exists? && can?(current_user, :read_merge_request, project)
272 273 274
      nav_tabs << :merge_requests
    end

275
    if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project)
276
      nav_tabs << :container_registry
K
Kamil Trzcinski 已提交
277 278
    end

279 280
    if project.builds_enabled? && can?(current_user, :read_pipeline, project)
      nav_tabs << :pipelines
281 282 283
    end

    if can?(current_user, :read_environment, project) || can?(current_user, :read_cluster, project)
284
      nav_tabs << :operations
285 286
    end

287 288 289 290
    if project.external_issue_tracker
      nav_tabs << :external_issue_tracker
    end

291 292 293 294 295 296 297 298 299 300 301
    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
    {
302 303 304 305 306
      environments:     :read_environment,
      milestones:       :read_milestone,
      snippets:         :read_project_snippet,
      settings:         :admin_project,
      builds:           :read_build,
307
      clusters:         :read_cluster,
308 309 310 311
      labels:           :read_label,
      issues:           :read_issue,
      project_members:  :read_project_member,
      wiki:             :read_wiki
312
    }
313
  end
314

315 316 317 318 319 320 321
  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]
    )
322
  end
323

324 325
  def project_lfs_status(project)
    if project.lfs_enabled?
326
      content_tag(:span, class: 'lfs-enabled') do
327
        s_('LFSStatus|Enabled')
328 329
      end
    else
330
      content_tag(:span, class: 'lfs-disabled') do
331
        s_('LFSStatus|Disabled')
332 333 334 335
      end
    end
  end

336 337
  def git_user_name
    if current_user
338
      current_user.name.gsub('"', '\"')
339
    else
340
      _("Your name")
341 342 343 344 345 346 347 348 349 350
    end
  end

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

352
  def default_url_to_repo(project = @project)
353 354
    case default_clone_protocol
    when 'ssh'
355 356
      project.ssh_url_to_repo
    else
357
      project.http_url_to_repo
358
    end
359
  end
360

361 362 363 364
  def default_clone_label
    _("Copy %{protocol} clone URL") % { protocol: default_clone_protocol.upcase }
  end

365
  def default_clone_protocol
366 367
    if allowed_protocols_present?
      enabled_protocol
368
    else
L
Lukas Eipert 已提交
369 370 371 372 373 374 375 376 377
      extra_default_clone_protocol
    end
  end

  def extra_default_clone_protocol
    if !current_user || current_user.require_ssh_key?
      gitlab_config.protocol
    else
      'ssh'
378
    end
379
  end
380 381 382

  def project_last_activity(project)
    if project.last_activity_at
383
      time_ago_with_tooltip(project.last_activity_at, placement: 'bottom', html_class: 'last_activity_time_ago')
384
    else
385
      s_("ProjectLastActivity|Never")
386 387
    end
  end
D
Dmitriy Zaporozhets 已提交
388

389 390 391 392
  def koding_project_url(project = nil, branch = nil, sha = nil)
    if project
      import_path = "/Home/Stacks/import"

393
      repo = project.full_path
394 395 396 397 398
      branch ||= project.default_branch
      sha ||= project.commit.short_id

      path = "#{import_path}?repo=#{repo}&branch=#{branch}&sha=#{sha}"

399
      return URI.join(Gitlab::CurrentSettings.koding_url, path).to_s
400 401
    end

402
    Gitlab::CurrentSettings.koding_url
403 404
  end

405 406
  def project_wiki_path_with_version(proj, page, version, is_newest)
    url_params = is_newest ? {} : { version_id: version }
407
    project_wiki_path(proj, page, url_params)
408
  end
409

V
Valery Sizov 已提交
410 411 412
  def project_status_css_class(status)
    case status
    when "started"
S
Stan Hu 已提交
413
      "table-active"
V
Valery Sizov 已提交
414
    when "failed"
S
Stan Hu 已提交
415
      "table-danger"
V
Valery Sizov 已提交
416
    when "finished"
S
Stan Hu 已提交
417
      "table-success"
V
Valery Sizov 已提交
418 419
    end
  end
420

421
  def readme_cache_key
422
    sha = @project.commit.try(:sha) || 'nil'
423
    [@project.full_path, sha, "readme"].join('-')
424
  end
425

426 427 428
  def current_ref
    @ref || @repository.try(:root_ref)
  end
429

430 431 432
  def project_child_container_class(view_path)
    view_path == "projects/issues/issues" ? "prepend-top-default" : "project-show-#{view_path}"
  end
433 434 435 436

  def project_issues(project)
    IssuesFinder.new(current_user, project_id: project.id).execute
  end
L
Luke "Jared" Bennett 已提交
437

438 439 440
  def restricted_levels
    return [] if current_user.admin?

441
    Gitlab::CurrentSettings.restricted_visibility_levels || []
L
Luke "Jared" Bennett 已提交
442
  end
443

444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
  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,
      containerRegistryEnabled: !!project.container_registry_enabled,
      lfsEnabled: !!project.lfs_enabled
    }
  end

  def project_permissions_panel_data(project)
461
    {
462 463 464 465 466 467
      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'),
468
      lfsAvailable: Gitlab.config.lfs.enabled,
469 470
      lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
    }
471
  end
472

473 474
  def project_permissions_panel_data_json(project)
    project_permissions_panel_data(project).to_json.html_safe
475 476 477 478 479 480 481 482
  end

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

483 484 485 486 487 488 489
  def find_file_path
    return unless @project && !@project.empty_repo?

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

    project_find_file_path(@project, ref)
  end
490 491 492 493

  def can_show_last_commit_in_list?(project)
    can?(current_user, :read_cross_project) && project.commit
  end
R
Rob Watson 已提交
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511

  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
L
Lukas Eipert 已提交
512

513 514 515 516 517 518 519 520
  def sidebar_projects_paths
    %w[
      projects#show
      projects#activity
      cycle_analytics#show
    ]
  end

L
Lukas Eipert 已提交
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 547 548 549 550 551 552
  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
R
randx 已提交
553
end