projects_helper.rb 15.0 KB
Newer Older
R
randx 已提交
1
module ProjectsHelper
2
  def link_to_project(project)
J
Escape!  
Josh Frye 已提交
3
    link_to [project.namespace.becomes(Namespace), project], title: h(project.name) do
4
      title = content_tag(:span, project.name, class: 'project-name')
5 6

      if project.namespace
D
Dmitriy Zaporozhets 已提交
7
        namespace = content_tag(:span, "#{project.namespace.human_name} / ", class: 'namespace-name')
8 9 10 11 12
        title = namespace + title
      end

      title
    end
13
  end
R
randx 已提交
14

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

    classes = %W[avatar avatar-inline s#{opts[:size]}]
M
Maxim Rydkin 已提交
20 21
    classes << opts[:avatar_class] if opts[:avatar_class]

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

    image_tag(src, width: opts[:size], class: classes, alt: '', "data-src" => avatar)
J
Jacob Schatz 已提交
26 27
  end

28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
  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

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

P
Phil Hughes 已提交
47
  def link_to_member(project, author, opts = {}, &block)
48
    default_opts = { avatar: true, name: true, title: ":name" }
49 50
    opts = default_opts.merge(opts)

51 52
    return "(deleted)" unless author

53
    author_html = ""
54

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

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

P
Phil Hughes 已提交
61 62
    author_html << capture(&block) if block

63
    author_html = author_html.html_safe
64

65
    if opts[:name]
66
      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
67
    else
68
      title = opts[:title].sub(":name", sanitize(author.name))
69
      link_to(author_html, user_path(author), class: "author-link has-tooltip", title: title, data: { container: 'body' }).html_safe
70
    end
R
randx 已提交
71
  end
72

73
  def project_title(project)
74 75
    namespace_link =
      if project.group
P
Phil Hughes 已提交
76
        group_title(project.group, nil, nil)
77 78 79
      else
        owner = project.namespace.owner
        link_to(simple_sanitize(owner.name), user_path(owner))
80
      end
81

P
Phil Hughes 已提交
82
    project_link = link_to project_path(project) do
P
Phil Hughes 已提交
83
      output =
84
        if project.avatar_url && !Rails.env.test?
85
          project_icon(project, alt: project.name, class: 'avatar-tile', width: 15, height: 15)
P
Phil Hughes 已提交
86 87 88 89
        else
          ""
        end

P
Phil Hughes 已提交
90
      output << content_tag("span", simple_sanitize(project.name), class: "breadcrumb-item-text js-breadcrumb-item-text")
P
Phil Hughes 已提交
91 92
      output.html_safe
    end
93

P
Phil Hughes 已提交
94 95
    namespace_link = breadcrumb_list_item(namespace_link) unless project.group
    project_link = breadcrumb_list_item project_link
P
Phil Hughes 已提交
96

P
Phil Hughes 已提交
97
    "#{namespace_link} #{project_link}".html_safe
98
  end
99 100

  def remove_project_message(project)
101 102
    _("You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?") %
      { project_full_name: project.full_name }
103
  end
104

105
  def transfer_project_message(project)
106 107
    _("You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?") %
      { project_full_name: project.full_name }
108 109
  end

110
  def remove_fork_project_message(project)
111
    _("You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?") %
112 113 114 115 116 117 118 119 120
      { 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
121 122
  end

123 124 125 126
  def project_nav_tabs
    @nav_tabs ||= get_project_nav_tabs(@project, current_user)
  end

127 128 129 130 131 132
  def project_search_tabs?(tab)
    abilities = Array(search_tab_ability_map[tab])

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

133 134 135 136
  def project_nav_tab?(name)
    project_nav_tabs.include? name
  end

D
Douwe Maan 已提交
137
  def project_for_deploy_key(deploy_key)
138
    if deploy_key.has_access_to?(@project)
D
Douwe Maan 已提交
139 140
      @project
    else
L
Lin Jen-Shin 已提交
141 142 143
      deploy_key.projects.find do |project|
        can?(current_user, :read_project, project)
      end
D
Douwe Maan 已提交
144 145 146
    end
  end

V
Valery Sizov 已提交
147 148 149
  def can_change_visibility_level?(project, current_user)
    return false unless can?(current_user, :change_visibility_level, project)

150 151
    if project.fork_source
      project.fork_source.visibility_level > Gitlab::VisibilityLevel::PRIVATE
V
Valery Sizov 已提交
152 153 154 155 156
    else
      true
    end
  end

157
  def last_push_event
158
    current_user&.recent_push(@project)
159 160
  end

161
  def link_to_autodeploy_doc
162
    link_to _('About auto deploy'), help_page_path('ci/autodeploy/index'), target: '_blank'
163 164
  end

V
Valery Sizov 已提交
165
  def autodeploy_flash_notice(branch_name)
166 167 168
    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 已提交
169 170
  end

171
  def project_list_cache_key(project)
172
    key = [
173
      project.route.cache_key,
174
      project.cache_key,
175
      project.last_activity_date,
176 177
      controller.controller_name,
      controller.action_name,
178
      Gitlab::CurrentSettings.cache_key,
179
      "cross-project:#{can?(current_user, :read_cross_project)}",
180
      max_project_member_access_cache_key(project),
M
Mark Chao 已提交
181
      'v2.6'
182 183
    ]

184 185 186 187 188
    key << pipeline_status_cache_key(project.pipeline_status) if project.pipeline_status.has_status?

    key
  end

189
  def load_pipeline_status(projects)
190 191
    Gitlab::Cache::Ci::ProjectPipelineStatus
      .load_in_batch_for_projects(projects)
192 193
  end

194
  def show_no_ssh_key_message?
195 196 197 198
    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?
199 200 201 202
  end

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

  def link_to_set_password
207
    if current_user.require_password_creation_for_git?
208 209 210 211 212 213
      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

214 215 216 217 218 219 220 221 222
  # 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)
223 224
    return projects.any? if projects.is_a?(Array)

225 226 227 228 229 230 231
    if projects.limit_value
      projects.to_a.any?
    else
      projects.except(:offset).any?
    end
  end

232 233
  def show_projects?(projects, params)
    !!(params[:personal] || params[:name] || any_projects?(projects))
234 235
  end

236 237 238 239 240 241 242 243 244 245 246
  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 已提交
247 248 249 250 251 252 253 254
  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

255 256 257
  private

  def get_project_nav_tabs(project, current_user)
258
    nav_tabs = [:home]
259

260
    if !project.empty_repo? && can?(current_user, :download_code, project)
261
      nav_tabs << [:files, :commits, :network, :graphs, :forks]
262 263
    end

264
    if project.repo_exists? && can?(current_user, :read_merge_request, project)
265 266 267
      nav_tabs << :merge_requests
    end

268
    if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project)
269
      nav_tabs << :container_registry
K
Kamil Trzcinski 已提交
270 271
    end

272 273
    if project.builds_enabled? && can?(current_user, :read_pipeline, project)
      nav_tabs << :pipelines
274 275 276
    end

    if can?(current_user, :read_environment, project) || can?(current_user, :read_cluster, project)
277
      nav_tabs << :operations
278 279
    end

280 281 282 283
    if project.external_issue_tracker
      nav_tabs << :external_issue_tracker
    end

284 285 286 287 288 289 290 291 292 293 294
    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
    {
295 296 297 298 299
      environments:     :read_environment,
      milestones:       :read_milestone,
      snippets:         :read_project_snippet,
      settings:         :admin_project,
      builds:           :read_build,
300
      clusters:         :read_cluster,
301 302 303 304
      labels:           :read_label,
      issues:           :read_issue,
      project_members:  :read_project_member,
      wiki:             :read_wiki
305
    }
306
  end
307

308 309 310 311 312 313 314
  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]
    )
315
  end
316

317 318
  def project_lfs_status(project)
    if project.lfs_enabled?
319
      content_tag(:span, class: 'lfs-enabled') do
320
        s_('LFSStatus|Enabled')
321 322
      end
    else
323
      content_tag(:span, class: 'lfs-disabled') do
324
        s_('LFSStatus|Disabled')
325 326 327 328
      end
    end
  end

329 330
  def git_user_name
    if current_user
331
      current_user.name.gsub('"', '\"')
332
    else
333
      _("Your name")
334 335 336 337 338 339 340 341 342 343
    end
  end

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

345
  def default_url_to_repo(project = @project)
346 347
    case default_clone_protocol
    when 'ssh'
348 349
      project.ssh_url_to_repo
    else
350
      project.http_url_to_repo
351
    end
352
  end
353

354
  def default_clone_protocol
355 356
    if allowed_protocols_present?
      enabled_protocol
357
    else
L
Lukas Eipert 已提交
358 359 360 361 362 363 364 365 366
      extra_default_clone_protocol
    end
  end

  def extra_default_clone_protocol
    if !current_user || current_user.require_ssh_key?
      gitlab_config.protocol
    else
      'ssh'
367
    end
368
  end
369 370 371

  def project_last_activity(project)
    if project.last_activity_at
372
      time_ago_with_tooltip(project.last_activity_at, placement: 'bottom', html_class: 'last_activity_time_ago')
373
    else
374
      s_("ProjectLastActivity|Never")
375 376
    end
  end
D
Dmitriy Zaporozhets 已提交
377

378 379 380 381
  def koding_project_url(project = nil, branch = nil, sha = nil)
    if project
      import_path = "/Home/Stacks/import"

382
      repo = project.full_path
383 384 385 386 387
      branch ||= project.default_branch
      sha ||= project.commit.short_id

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

388
      return URI.join(Gitlab::CurrentSettings.koding_url, path).to_s
389 390
    end

391
    Gitlab::CurrentSettings.koding_url
392 393
  end

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

V
Valery Sizov 已提交
399 400 401
  def project_status_css_class(status)
    case status
    when "started"
S
Stan Hu 已提交
402
      "table-active"
V
Valery Sizov 已提交
403
    when "failed"
S
Stan Hu 已提交
404
      "table-danger"
V
Valery Sizov 已提交
405
    when "finished"
S
Stan Hu 已提交
406
      "table-success"
V
Valery Sizov 已提交
407 408
    end
  end
409

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

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

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

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

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

430
    Gitlab::CurrentSettings.restricted_visibility_levels || []
L
Luke "Jared" Bennett 已提交
431
  end
432

433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
  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)
450
    {
451 452 453 454 455 456
      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'),
457
      lfsAvailable: Gitlab.config.lfs.enabled,
458 459
      lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
    }
460
  end
461

462 463
  def project_permissions_panel_data_json(project)
    project_permissions_panel_data(project).to_json.html_safe
464 465 466 467 468 469 470 471
  end

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

472 473 474 475 476 477 478
  def find_file_path
    return unless @project && !@project.empty_repo?

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

    project_find_file_path(@project, ref)
  end
479 480 481 482

  def can_show_last_commit_in_list?(project)
    can?(current_user, :read_cross_project) && project.commit
  end
R
Rob Watson 已提交
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500

  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 已提交
501

502 503 504 505 506 507 508 509
  def sidebar_projects_paths
    %w[
      projects#show
      projects#activity
      cycle_analytics#show
    ]
  end

L
Lukas Eipert 已提交
510 511 512 513 514 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
  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 已提交
542
end