projects_helper.rb 14.4 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 43 44 45
  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

    content_tag(:span, sanitize(username), name_tag_options)
  end

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

50 51
    return "(deleted)" unless author

52
    author_html = ""
53

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

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

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

62
    author_html = author_html.html_safe
63

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

170
  def project_list_cache_key(project)
171
    key = [
172
      project.route.cache_key,
173 174 175
      project.cache_key,
      controller.controller_name,
      controller.action_name,
176
      Gitlab::CurrentSettings.cache_key,
177
      "cross-project:#{can?(current_user, :read_cross_project)}",
B
Bob Van Landuyt 已提交
178
      'v2.5'
179 180
    ]

181 182 183 184 185
    key << pipeline_status_cache_key(project.pipeline_status) if project.pipeline_status.has_status?

    key
  end

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

191 192 193 194 195 196
  def show_no_ssh_key_message?
    cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key?
  end

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

  def link_to_set_password
201
    if current_user.require_password_creation_for_git?
202 203 204 205 206 207
      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

208 209 210 211 212 213 214 215 216
  # 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)
217 218
    return projects.any? if projects.is_a?(Array)

219 220 221 222 223 224 225
    if projects.limit_value
      projects.to_a.any?
    else
      projects.except(:offset).any?
    end
  end

226 227
  def show_projects?(projects, params)
    !!(params[:personal] || params[:name] || any_projects?(projects))
228 229
  end

230 231 232 233 234 235 236 237 238 239 240
  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 已提交
241 242 243 244 245 246 247 248
  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

249 250 251
  private

  def get_project_nav_tabs(project, current_user)
252
    nav_tabs = [:home]
253

254
    if !project.empty_repo? && can?(current_user, :download_code, project)
255
      nav_tabs << [:files, :commits, :network, :graphs, :forks]
256 257
    end

258
    if project.repo_exists? && can?(current_user, :read_merge_request, project)
259 260 261
      nav_tabs << :merge_requests
    end

262
    if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project)
263
      nav_tabs << :container_registry
K
Kamil Trzcinski 已提交
264 265
    end

266 267
    if project.builds_enabled? && can?(current_user, :read_pipeline, project)
      nav_tabs << :pipelines
268 269 270
    end

    if can?(current_user, :read_environment, project) || can?(current_user, :read_cluster, project)
271
      nav_tabs << :operations
272 273
    end

274 275 276 277
    if project.external_issue_tracker
      nav_tabs << :external_issue_tracker
    end

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

302 303 304 305 306 307 308
  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]
    )
309
  end
310

311 312
  def project_lfs_status(project)
    if project.lfs_enabled?
313
      content_tag(:span, class: 'lfs-enabled') do
314
        s_('LFSStatus|Enabled')
315 316
      end
    else
317
      content_tag(:span, class: 'lfs-disabled') do
318
        s_('LFSStatus|Disabled')
319 320 321 322
      end
    end
  end

323 324
  def git_user_name
    if current_user
325
      current_user.name.gsub('"', '\"')
326
    else
327
      _("Your name")
328 329 330 331 332 333 334 335 336 337
    end
  end

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

339
  def default_url_to_repo(project = @project)
340 341
    case default_clone_protocol
    when 'ssh'
342 343
      project.ssh_url_to_repo
    else
344
      project.http_url_to_repo
345
    end
346
  end
347

348
  def default_clone_protocol
349 350
    if allowed_protocols_present?
      enabled_protocol
351
    else
352 353 354 355 356
      if !current_user || current_user.require_ssh_key?
        gitlab_config.protocol
      else
        'ssh'
      end
357
    end
358
  end
359 360 361

  def project_last_activity(project)
    if project.last_activity_at
362
      time_ago_with_tooltip(project.last_activity_at, placement: 'bottom', html_class: 'last_activity_time_ago')
363
    else
364
      s_("ProjectLastActivity|Never")
365 366
    end
  end
D
Dmitriy Zaporozhets 已提交
367

368 369 370 371
  def koding_project_url(project = nil, branch = nil, sha = nil)
    if project
      import_path = "/Home/Stacks/import"

372
      repo = project.full_path
373 374 375 376 377
      branch ||= project.default_branch
      sha ||= project.commit.short_id

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

378
      return URI.join(Gitlab::CurrentSettings.koding_url, path).to_s
379 380
    end

381
    Gitlab::CurrentSettings.koding_url
382 383
  end

384 385
  def project_wiki_path_with_version(proj, page, version, is_newest)
    url_params = is_newest ? {} : { version_id: version }
386
    project_wiki_path(proj, page, url_params)
387
  end
388

V
Valery Sizov 已提交
389 390 391 392 393 394 395 396 397 398
  def project_status_css_class(status)
    case status
    when "started"
      "active"
    when "failed"
      "danger"
    when "finished"
      "success"
    end
  end
399

400
  def readme_cache_key
401
    sha = @project.commit.try(:sha) || 'nil'
402
    [@project.full_path, sha, "readme"].join('-')
403
  end
404

405 406 407
  def current_ref
    @ref || @repository.try(:root_ref)
  end
408

409
  def sanitize_repo_path(project, message)
410 411
    return '' unless message.present?

412 413 414
    exports_path = File.join(Settings.shared['path'], 'tmp/project_exports')
    filtered_message = message.strip.gsub(exports_path, "[REPO EXPORT PATH]")

415 416
    disk_path = Gitlab.config.repositories.storages[project.repository_storage].legacy_disk_path
    filtered_message.gsub(disk_path.chomp('/'), "[REPOS PATH]")
417
  end
F
Felipe Artur 已提交
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 450 451 452 453 454 455 456
  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)
    data = {
      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 460 461 462 463 464 465 466 467 468 469
      lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
    }

    data.to_json.html_safe
  end

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

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

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

    project_find_file_path(@project, ref)
  end
477 478 479 480

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

  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
R
randx 已提交
499
end