projects_helper.rb 14.5 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
      project.cache_key,
174
      project.last_activity_date,
175 176
      controller.controller_name,
      controller.action_name,
177
      Gitlab::CurrentSettings.cache_key,
178
      "cross-project:#{can?(current_user, :read_cross_project)}",
M
Mark Chao 已提交
179
      'v2.6'
180 181
    ]

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

    key
  end

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

192 193 194 195 196 197
  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 &&
198
      current_user.require_extra_setup_for_git_auth?
199 200 201
  end

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

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

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

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

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

250 251 252
  private

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

382
    Gitlab::CurrentSettings.koding_url
383 384
  end

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

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

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

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

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

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

416 417 418 419
    disk_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
      Gitlab.config.repositories.storages[project.repository_storage].legacy_disk_path
    end

420
    filtered_message.gsub(disk_path.chomp('/'), "[REPOS PATH]")
421
  end
F
Felipe Artur 已提交
422

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

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

431 432 433
  def restricted_levels
    return [] if current_user.admin?

434
    Gitlab::CurrentSettings.restricted_visibility_levels || []
L
Luke "Jared" Bennett 已提交
435
  end
436

437 438 439 440 441 442 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)
    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'),
461
      lfsAvailable: Gitlab.config.lfs.enabled,
462 463 464 465 466 467 468 469 470 471 472 473
      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

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

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

    project_find_file_path(@project, ref)
  end
481 482 483 484

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

  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 已提交
503
end