projects_helper.rb 17.6 KB
Newer Older
R
randx 已提交
1
module ProjectsHelper
2 3
  include Gitlab::CurrentSettings

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 25 26 27
    avatar = avatar_icon(author, opts[:size])
    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 45 46 47
  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 已提交
48
  def link_to_member(project, author, opts = {}, &block)
49
    default_opts = { avatar: true, name: true, title: ":name" }
50 51
    opts = default_opts.merge(opts)

52 53
    return "(deleted)" unless author

54
    author_html = ""
55

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

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

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

64
    author_html = author_html.html_safe
65

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

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

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

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

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

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

  def remove_project_message(project)
B
Bob Van Landuyt 已提交
102
    _("You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?") %
103
      { project_name_with_namespace: project.name_with_namespace }
104
  end
105

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

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

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

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

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

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

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

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

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

158
  def license_short_name(project)
D
Douwe Maan 已提交
159 160
    license = project.repository.license
    license&.nickname || license&.name || 'LICENSE'
161 162
  end

163
  def last_push_event
164
    current_user&.recent_push(@project)
165 166
  end

F
Felipe Artur 已提交
167 168 169 170
  def project_feature_access_select(field)
    # Don't show option "everyone with access" if project is private
    options = project_feature_options

171 172
    level = @project.project_feature.public_send(field) # rubocop:disable GitlabSecurity/PublicSend

F
Felipe Artur 已提交
173
    if @project.private?
174 175
      disabled_option = ProjectFeature::ENABLED
      highest_available_option = ProjectFeature::PRIVATE if level == disabled_option
F
Felipe Artur 已提交
176 177
    end

178
    options = options_for_select(
179
      options.invert,
180
      selected: highest_available_option || level,
181 182
      disabled: disabled_option
    )
183

184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
    content_tag :div, class: "select-wrapper" do
      concat(
        content_tag(
          :select,
          options,
          name: "project[project_feature_attributes][#{field}]",
          id: "project_project_feature_attributes_#{field}",
          class: "pull-right form-control select-control #{repo_children_classes(field)} ",
          data: { field: field }
        )
      )
      concat(
        icon('chevron-down')
      )
    end.html_safe
F
Felipe Artur 已提交
199 200
  end

201
  def link_to_autodeploy_doc
202
    link_to _('About auto deploy'), help_page_path('ci/autodeploy/index'), target: '_blank'
203 204
  end

V
Valery Sizov 已提交
205
  def autodeploy_flash_notice(branch_name)
206 207 208
    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 已提交
209 210
  end

211
  def project_list_cache_key(project)
212
    key = [
213
      project.route.cache_key,
214 215 216 217
      project.cache_key,
      controller.controller_name,
      controller.action_name,
      current_application_settings.cache_key,
B
Bob Van Landuyt 已提交
218
      'v2.5'
219 220
    ]

221 222 223 224 225
    key << pipeline_status_cache_key(project.pipeline_status) if project.pipeline_status.has_status?

    key
  end

226
  def load_pipeline_status(projects)
227 228
    Gitlab::Cache::Ci::ProjectPipelineStatus
      .load_in_batch_for_projects(projects)
229 230
  end

231 232 233 234 235 236
  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 &&
237
      current_user.require_extra_setup_for_git_auth?
238 239 240
  end

  def link_to_set_password
241
    if current_user.require_password_creation_for_git?
242 243 244 245 246 247
      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

248 249 250 251 252 253 254 255 256
  # 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)
257 258
    return projects.any? if projects.is_a?(Array)

259 260 261 262 263 264 265
    if projects.limit_value
      projects.to_a.any?
    else
      projects.except(:offset).any?
    end
  end

266 267
  def show_projects?(projects, params)
    !!(params[:personal] || params[:name] || any_projects?(projects))
268 269
  end

270 271
  private

272 273 274 275 276 277 278 279 280 281
  def repo_children_classes(field)
    needs_repo_check = [:merge_requests_access_level, :builds_access_level]
    return unless needs_repo_check.include?(field)

    classes = "project-repo-select js-repo-select"
    classes << " disabled" unless @project.feature_available?(:repository, current_user)

    classes
  end

282
  def get_project_nav_tabs(project, current_user)
283
    nav_tabs = [:home]
284

285
    if !project.empty_repo? && can?(current_user, :download_code, project)
286
      nav_tabs << [:files, :commits, :network, :graphs, :forks]
287 288
    end

289
    if project.repo_exists? && can?(current_user, :read_merge_request, project)
290 291 292
      nav_tabs << :merge_requests
    end

293
    if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project)
294
      nav_tabs << :container_registry
K
Kamil Trzcinski 已提交
295 296
    end

297 298 299 300
    if project.builds_enabled? && can?(current_user, :read_pipeline, project)
      nav_tabs << :pipelines
    end

301 302 303 304 305 306 307 308 309 310 311
    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
    {
312 313 314 315 316
      environments:     :read_environment,
      milestones:       :read_milestone,
      snippets:         :read_project_snippet,
      settings:         :admin_project,
      builds:           :read_build,
317
      clusters:         :read_cluster,
318 319 320 321
      labels:           :read_label,
      issues:           :read_issue,
      project_members:  :read_project_member,
      wiki:             :read_wiki
322
    }
323
  end
324

325 326 327 328 329 330 331
  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]
    )
332
  end
333

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

346 347
  def git_user_name
    if current_user
348
      current_user.name.gsub('"', '\"')
349
    else
350
      _("Your name")
351 352 353 354 355 356 357 358 359 360
    end
  end

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

362
  def default_url_to_repo(project = @project)
363 364
    case default_clone_protocol
    when 'ssh'
365 366
      project.ssh_url_to_repo
    else
367
      project.http_url_to_repo
368
    end
369
  end
370

371
  def default_clone_protocol
372 373
    if allowed_protocols_present?
      enabled_protocol
374
    else
375 376 377 378 379
      if !current_user || current_user.require_ssh_key?
        gitlab_config.protocol
      else
        'ssh'
      end
380
    end
381
  end
382 383 384

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

D
Douwe Maan 已提交
391
  def add_special_file_path(project, file_name:, commit_message: nil, branch_name: nil, context: nil)
392
    commit_message ||= s_("CommitMessage|Add %{file_name}") % { file_name: file_name }
393
    project_new_blob_path(
394 395 396
      project,
      project.default_branch || 'master',
      file_name:      file_name,
397
      commit_message: commit_message,
D
Douwe Maan 已提交
398
      branch_name: branch_name,
399
      context: context
400
    )
401 402
  end

403
  def add_koding_stack_path(project)
404
    project_new_blob_path(
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
      project,
      project.default_branch || 'master',
      file_name:      '.koding.yml',
      commit_message: "Add Koding stack script",
      content: <<-CONTENT.strip_heredoc
        provider:
          aws:
            access_key: '${var.aws_access_key}'
            secret_key: '${var.aws_secret_key}'
        resource:
          aws_instance:
            #{project.path}-vm:
              instance_type: t2.nano
              user_data: |-

                # Created by GitLab UI for :>

                echo _KD_NOTIFY_@Installing Base packages...@

                apt-get update -y
                apt-get install git -y

                echo _KD_NOTIFY_@Cloning #{project.name}...@

                export KODING_USER=${var.koding_user_username}
                export REPO_URL=#{root_url}${var.koding_queryString_repo}.git
                export BRANCH=${var.koding_queryString_branch}

                sudo -i -u $KODING_USER git clone $REPO_URL -b $BRANCH

                echo _KD_NOTIFY_@#{project.name} cloned.@
      CONTENT
    )
  end

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

444
      repo = project.full_path
445 446 447 448 449 450 451 452 453 454 455
      branch ||= project.default_branch
      sha ||= project.commit.short_id

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

      return URI.join(current_application_settings.koding_url, path).to_s
    end

    current_application_settings.koding_url
  end

456
  def contribution_guide_path(project)
457
    if project && contribution_guide = project.repository.contribution_guide
458
      project_blob_path(
V
Vinnie Okada 已提交
459 460
        project,
        tree_join(project.default_branch,
461 462 463 464 465
                  contribution_guide.name)
      )
    end
  end

466 467 468 469
  def readme_path(project)
    filename_path(project, :readme)
  end

470
  def changelog_path(project)
471
    filename_path(project, :changelog)
472 473
  end

474
  def license_path(project)
475
    filename_path(project, :license_blob)
476 477
  end

478
  def version_path(project)
479
    filename_path(project, :version)
D
Dmitriy Zaporozhets 已提交
480
  end
481

482 483 484 485
  def ci_configuration_path(project)
    filename_path(project, :gitlab_ci_yml)
  end

486 487
  def project_wiki_path_with_version(proj, page, version, is_newest)
    url_params = is_newest ? {} : { version_id: version }
488
    project_wiki_path(proj, page, url_params)
489
  end
490

V
Valery Sizov 已提交
491 492 493 494 495 496 497 498 499 500
  def project_status_css_class(status)
    case status
    when "started"
      "active"
    when "failed"
      "danger"
    when "finished"
      "success"
    end
  end
501

502
  def readme_cache_key
503
    sha = @project.commit.try(:sha) || 'nil'
504
    [@project.full_path, sha, "readme"].join('-')
505
  end
506

507 508 509
  def current_ref
    @ref || @repository.try(:root_ref)
  end
510

511
  def filename_path(project, filename)
512
    if project && blob = project.repository.public_send(filename) # rubocop:disable GitlabSecurity/PublicSend
513
      project_blob_path(
G
Gabriel Mazetto 已提交
514 515
        project,
        tree_join(project.default_branch, blob.name)
516 517 518
      )
    end
  end
519

520
  def sanitize_repo_path(project, message)
521 522
    return '' unless message.present?

523 524 525 526
    exports_path = File.join(Settings.shared['path'], 'tmp/project_exports')
    filtered_message = message.strip.gsub(exports_path, "[REPO EXPORT PATH]")

    filtered_message.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]")
527
  end
F
Felipe Artur 已提交
528 529 530

  def project_feature_options
    {
531 532 533
      ProjectFeature::DISABLED => s_('ProjectFeature|Disabled'),
      ProjectFeature::PRIVATE => s_('ProjectFeature|Only team members'),
      ProjectFeature::ENABLED => s_('ProjectFeature|Everyone with access')
F
Felipe Artur 已提交
534 535
    }
  end
536 537 538 539

  def project_child_container_class(view_path)
    view_path == "projects/issues/issues" ? "prepend-top-default" : "project-show-#{view_path}"
  end
540 541 542 543

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

  def visibility_select_options(project, selected_level)
546 547 548 549
    level_options = Gitlab::VisibilityLevel.values.each_with_object([]) do |level, level_options|
      next if restricted_levels.include?(level)

      level_options << [
L
Luke "Jared" Bennett 已提交
550 551 552 553 554
        visibility_level_label(level),
        { data: { description: visibility_level_description(level, project) } },
        level
      ]
    end
555 556 557 558 559 560 561 562

    options_for_select(level_options, selected_level)
  end

  def restricted_levels
    return [] if current_user.admin?

    current_application_settings.restricted_visibility_levels || []
L
Luke "Jared" Bennett 已提交
563
  end
564

565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601
  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'),
      lfsAvailable: Gitlab.config.lfs.enabled && current_user.admin?,
      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

602 603 604 605 606 607 608
  def find_file_path
    return unless @project && !@project.empty_repo?

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

    project_find_file_path(@project, ref)
  end
R
randx 已提交
609
end