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

randx's avatar
randx committed
3
module ProjectsHelper
4
5
  prepend_if_ee('::EE::ProjectsHelper') # rubocop: disable Cop/InjectEnterpriseEditionModule

6
  def link_to_project(project)
7
    link_to namespace_project_path(namespace_id: project.namespace, id: project), title: h(project.name) do
8
      title = content_tag(:span, project.name, class: 'project-name')
9
10

      if project.namespace
11
        namespace = content_tag(:span, "#{project.namespace.human_name} / ", class: 'namespace-name')
12
13
14
15
16
        title = namespace + title
      end

      title
    end
17
  end
randx's avatar
randx committed
18

Jacob Schatz's avatar
Jacob Schatz committed
19
  def link_to_member_avatar(author, opts = {})
20
    default_opts = { size: 16, lazy_load: false }
Jacob Schatz's avatar
Jacob Schatz committed
21
    opts = default_opts.merge(opts)
22
23

    classes = %W[avatar avatar-inline s#{opts[:size]}]
Maxim Rydkin's avatar
Maxim Rydkin committed
24
25
    classes << opts[:avatar_class] if opts[:avatar_class]

26
    avatar = avatar_icon_for_user(author, opts[:size])
27
28
29
    src = opts[:lazy_load] ? nil : avatar

    image_tag(src, width: opts[:size], class: classes, alt: '', "data-src" => avatar)
Jacob Schatz's avatar
Jacob Schatz committed
30
31
  end

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
  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

47
48
    # NOTE: ActionView::Helpers::TagHelper#content_tag HTML escapes username
    content_tag(:span, username, name_tag_options)
49
50
  end

Phil Hughes's avatar
Phil Hughes committed
51
  def link_to_member(project, author, opts = {}, &block)
52
    default_opts = { avatar: true, name: true, title: ":name" }
53
54
    opts = default_opts.merge(opts)

55
56
57
58
59
60
    data_attrs = {
      user_id: author.id,
      username: author.username,
      name: author.name
    }

61
62
    return "(deleted)" unless author

63
    author_html = []
64

65
    # Build avatar image tag
66
    author_html << link_to_member_avatar(author, opts) if opts[:avatar]
67

68
    # Build name span tag
69
    author_html << author_content_tag(author, opts) if opts[:name]
70

Phil Hughes's avatar
Phil Hughes committed
71
72
    author_html << capture(&block) if block

73
    author_html = author_html.join.html_safe
74

75
    if opts[:name]
76
      link_to(author_html, user_path(author), class: "author-link js-user-link #{"#{opts[:extra_class]}" if opts[:extra_class]} #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}", data: data_attrs).html_safe
77
    else
78
      title = opts[:title].sub(":name", sanitize(author.name))
79
      link_to(author_html, user_path(author), class: "author-link has-tooltip", title: title, data: { container: 'body', qa_selector: 'assignee_link' }).html_safe
80
    end
randx's avatar
randx committed
81
  end
82

83
  def project_title(project)
84
85
    namespace_link =
      if project.group
Phil Hughes's avatar
Phil Hughes committed
86
        group_title(project.group, nil, nil)
87
88
89
      else
        owner = project.namespace.owner
        link_to(simple_sanitize(owner.name), user_path(owner))
90
      end
91

Phil Hughes's avatar
Phil Hughes committed
92
    project_link = link_to project_path(project) do
93
94
      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
Phil Hughes's avatar
Phil Hughes committed
95
    end
96

Phil Hughes's avatar
Phil Hughes committed
97
98
    namespace_link = breadcrumb_list_item(namespace_link) unless project.group
    project_link = breadcrumb_list_item project_link
Phil Hughes's avatar
Phil Hughes committed
99

Phil Hughes's avatar
Phil Hughes committed
100
    "#{namespace_link} #{project_link}".html_safe
101
  end
102
103

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

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

113
114
  def remove_fork_project_description_message(project)
    source = visible_fork_source(project)
115

116
    if source
117
      msg = _('This will remove the fork relationship between this project and %{fork_source}.') %
118
        { fork_source: link_to(source.full_name, project_path(source)) }
119
120

      msg.html_safe
121
    else
122
      _('This will remove the fork relationship between this project and other projects in the fork network.')
123
    end
124
125
  end

126
127
128
129
130
131
132
133
134
  def remove_fork_project_warning_message(project)
    _("You are going to remove the fork relationship from %{project_full_name}. Are you ABSOLUTELY sure?") %
      { project_full_name: project.full_name }
  end

  def visible_fork_source(project)
    project.fork_source if project.fork_source && can?(current_user, :read_project, project.fork_source)
  end

135
136
137
138
  def project_nav_tabs
    @nav_tabs ||= get_project_nav_tabs(@project, current_user)
  end

139
140
141
142
143
144
  def project_search_tabs?(tab)
    abilities = Array(search_tab_ability_map[tab])

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

145
146
147
148
  def project_nav_tab?(name)
    project_nav_tabs.include? name
  end

Douwe Maan's avatar
Douwe Maan committed
149
  def project_for_deploy_key(deploy_key)
150
    if deploy_key.has_access_to?(@project)
Douwe Maan's avatar
Douwe Maan committed
151
152
      @project
    else
Lin Jen-Shin's avatar
Lin Jen-Shin committed
153
154
155
      deploy_key.projects.find do |project|
        can?(current_user, :read_project, project)
      end
Douwe Maan's avatar
Douwe Maan committed
156
157
158
    end
  end

Valery Sizov's avatar
Valery Sizov committed
159
160
161
  def can_change_visibility_level?(project, current_user)
    return false unless can?(current_user, :change_visibility_level, project)

162
163
    if project.fork_source
      project.fork_source.visibility_level > Gitlab::VisibilityLevel::PRIVATE
Valery Sizov's avatar
Valery Sizov committed
164
165
166
167
168
    else
      true
    end
  end

169
170
171
  def can_disable_emails?(project, current_user)
    return false if project.group&.emails_disabled?

172
    can?(current_user, :set_emails_disabled, project)
173
174
  end

175
  def last_push_event
176
    current_user&.recent_push(@project)
177
178
  end

179
  def link_to_autodeploy_doc
180
    link_to _('About auto deploy'), help_page_path('autodevops/index.md#auto-deploy'), target: '_blank'
181
182
  end

Valery Sizov's avatar
Valery Sizov committed
183
  def autodeploy_flash_notice(branch_name)
184
185
186
    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
Valery Sizov's avatar
Valery Sizov committed
187
188
  end

189
  def project_list_cache_key(project, pipeline_status: true)
190
    key = [
191
      project.route.cache_key,
192
      project.cache_key,
193
      project.last_activity_date,
194
195
      controller.controller_name,
      controller.action_name,
196
      Gitlab::CurrentSettings.cache_key,
197
      "cross-project:#{can?(current_user, :read_cross_project)}",
198
      max_project_member_access_cache_key(project),
199
      pipeline_status,
200
      Gitlab::I18n.locale,
Mark Chao's avatar
Mark Chao committed
201
      'v2.6'
202
203
    ]

204
    key << pipeline_status_cache_key(project.pipeline_status) if pipeline_status && project.pipeline_status.has_status?
205
206
207
208

    key
  end

209
  def load_pipeline_status(projects)
210
211
    Gitlab::Cache::Ci::ProjectPipelineStatus
      .load_in_batch_for_projects(projects)
212
213
  end

214
  def show_no_ssh_key_message?
215
216
217
218
    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?
219
220
221
222
  end

  def show_no_password_message?
    cookies[:hide_no_password_message].blank? && !current_user.hide_no_password &&
223
      current_user.require_extra_setup_for_git_auth?
224
225
  end

226
227
  def show_auto_devops_implicitly_enabled_banner?(project, user)
    return false unless user_can_see_auto_devops_implicitly_enabled_banner?(project, user)
228

229
    cookies["hide_auto_devops_implicitly_enabled_banner_#{project.id}".to_sym].blank?
230
231
  end

232
  def link_to_set_password
233
    if current_user.require_password_creation_for_git?
234
235
236
237
238
239
      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

240
241
242
243
244
245
246
247
248
  # 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)
249
250
    return projects.any? if projects.is_a?(Array)

251
252
253
254
255
256
257
    if projects.limit_value
      projects.to_a.any?
    else
      projects.except(:offset).any?
    end
  end

258
  # TODO: Remove this method when removing the feature flag
259
  # https://gitlab.com/gitlab-org/gitlab/merge_requests/11209#note_162234863
260
  # make sure to remove from the EE specific controller as well: ee/app/controllers/ee/dashboard/projects_controller.rb
261
  def show_projects?(projects, params)
262
    Feature.enabled?(:project_list_filter_bar) || !!(params[:personal] || params[:name] || any_projects?(projects))
263
264
  end

265
266
267
268
269
270
271
272
273
274
275
  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

276
277
278
279
280
281
282
283
  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

284
285
286
287
  def link_to_bfg
    link_to 'BFG', 'https://rtyley.github.io/bfg-repo-cleaner/', target: '_blank', rel: 'noopener noreferrer'
  end

288
289
290
291
292
293
294
295
296
297
298
299
300
301
  def explore_projects_tab?
    current_page?(explore_projects_path) ||
      current_page?(trending_explore_projects_path) ||
      current_page?(starred_explore_projects_path)
  end

  def show_merge_request_count?(disabled: false, compact_mode: false)
    !disabled && !compact_mode && Feature.enabled?(:project_list_show_mr_count, default_enabled: true)
  end

  def show_issue_count?(disabled: false, compact_mode: false)
    !disabled && !compact_mode && Feature.enabled?(:project_list_show_issue_count, default_enabled: true)
  end

302
303
  # overridden in EE
  def settings_operations_available?
304
    can?(current_user, :read_environment, @project)
305
306
  end

Reuben Pereira's avatar
Reuben Pereira committed
307
308
309
310
311
312
313
314
315
316
317
318
319
320
  def error_tracking_setting_project_json
    setting = @project.error_tracking_setting

    return if setting.blank? || setting.project_slug.blank? ||
        setting.organization_slug.blank?

    {
      name: setting.project_name,
      organization_name: setting.organization_name,
      organization_slug: setting.organization_slug,
      slug: setting.project_slug
    }.to_json
  end

321
322
323
324
  def directory?
    @path.present?
  end

325
326
327
328
329
330
331
332
333
334
  def external_classification_label_help_message
    default_label = ::Gitlab::CurrentSettings.current_application_settings
                      .external_authorization_service_default_label

    s_(
      "ExternalAuthorizationService|When no classification label is set the "\
        "default label `%{default_label}` will be used."
    ) % { default_label: default_label }
  end

335
336
337
338
  def can_import_members?
    Ability.allowed?(current_user, :admin_project_member, @project)
  end

339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
  def project_can_be_shared?
    !membership_locked? || @project.allowed_to_share_with_group?
  end

  def membership_locked?
    false
  end

  def share_project_description(project)
    share_with_group   = project.allowed_to_share_with_group?
    share_with_members = !membership_locked?

    description =
      if share_with_group && share_with_members
        _("You can invite a new member to <strong>%{project_name}</strong> or invite another group.")
      elsif share_with_group
        _("You can invite another group to <strong>%{project_name}</strong>.")
      elsif share_with_members
        _("You can invite a new member to <strong>%{project_name}</strong>.")
      end

    description.html_safe % { project_name: project.name }
  end

363
364
365
366
  def metrics_external_dashboard_url
    @project.metrics_setting_external_dashboard_url
  end

367
368
369
370
  def grafana_integration_url
    @project.grafana_integration&.grafana_url
  end

371
372
  def grafana_integration_masked_token
    @project.grafana_integration&.masked_token
373
374
  end

375
376
377
378
  def grafana_integration_enabled?
    @project.grafana_integration&.enabled?
  end

379
380
381
  private

  def get_project_nav_tabs(project, current_user)
382
    nav_tabs = [:home]
383

384
385
386
    unless project.empty_repo?
      nav_tabs << [:files, :commits, :network, :graphs, :forks] if can?(current_user, :download_code, project)
      nav_tabs << :releases if can?(current_user, :read_release, project)
387
388
    end

389
    if project.repo_exists? && can?(current_user, :read_merge_request, project)
390
391
392
      nav_tabs << :merge_requests
    end

393
    if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project)
394
      nav_tabs << :container_registry
Kamil Trzcinski's avatar
Kamil Trzcinski committed
395
396
    end

397
398
    # Pipelines feature is tied to presence of builds
    if can?(current_user, :read_build, project)
399
      nav_tabs << :pipelines
400
401
402
    end

    if can?(current_user, :read_environment, project) || can?(current_user, :read_cluster, project)
403
      nav_tabs << :operations
404
405
    end

406
407
408
409
410
411
    tab_ability_map.each do |tab, ability|
      if can?(current_user, ability, project)
        nav_tabs << tab
      end
    end

412
413
    nav_tabs << external_nav_tabs(project)

414
415
416
    nav_tabs.flatten
  end

417
418
419
  def external_nav_tabs(project)
    [].tap do |tabs|
      tabs << :external_issue_tracker if project.external_issue_tracker
420
      tabs << :external_wiki if project.external_wiki
421
422
423
    end
  end

424
425
  def tab_ability_map
    {
426
427
428
429
430
      environments:     :read_environment,
      milestones:       :read_milestone,
      snippets:         :read_project_snippet,
      settings:         :admin_project,
      builds:           :read_build,
431
      clusters:         :read_cluster,
432
      serverless:       :read_cluster,
433
      error_tracking:   :read_sentry_issue,
434
435
436
437
      labels:           :read_label,
      issues:           :read_issue,
      project_members:  :read_project_member,
      wiki:             :read_wiki
438
    }
439
  end
440

441
442
443
444
445
  def search_tab_ability_map
    @search_tab_ability_map ||= tab_ability_map.merge(
      blobs:          :download_code,
      commits:        :download_code,
      merge_requests: :read_merge_request,
446
447
      notes:          [:read_merge_request, :download_code, :read_issue, :read_project_snippet],
      members:        :read_project_member
448
    )
449
  end
450

451
452
  def project_lfs_status(project)
    if project.lfs_enabled?
453
      content_tag(:span, class: 'lfs-enabled') do
454
        s_('LFSStatus|Enabled')
455
456
      end
    else
457
      content_tag(:span, class: 'lfs-disabled') do
458
        s_('LFSStatus|Disabled')
459
460
461
462
      end
    end
  end

463
464
  def git_user_name
    if current_user
465
      current_user.name.gsub('"', '\"')
466
    else
467
      _("Your name")
468
469
470
471
472
    end
  end

  def git_user_email
    if current_user
473
      current_user.commit_email
474
475
476
477
    else
      "your@email.com"
    end
  end
478

479
  def default_url_to_repo(project = @project)
480
481
    case default_clone_protocol
    when 'ssh'
482
483
      project.ssh_url_to_repo
    else
484
      project.http_url_to_repo
485
    end
486
  end
487

488
489
490
491
  def default_clone_label
    _("Copy %{protocol} clone URL") % { protocol: default_clone_protocol.upcase }
  end

492
  def default_clone_protocol
493
494
    if allowed_protocols_present?
      enabled_protocol
495
    else
496
497
498
499
500
501
502
503
504
      extra_default_clone_protocol
    end
  end

  def extra_default_clone_protocol
    if !current_user || current_user.require_ssh_key?
      gitlab_config.protocol
    else
      'ssh'
505
    end
506
  end
507

Matija Čupić's avatar
Matija Čupić committed
508
509
510
511
  def sidebar_operations_link_path(project = @project)
    metrics_project_environments_path(project) if can?(current_user, :read_environment, project)
  end

512
513
  def project_last_activity(project)
    if project.last_activity_at
514
      time_ago_with_tooltip(project.last_activity_at, placement: 'bottom', html_class: 'last_activity_time_ago')
515
    else
516
      s_("ProjectLastActivity|Never")
517
518
    end
  end
519

520
521
  def project_wiki_path_with_version(proj, page, version, is_newest)
    url_params = is_newest ? {} : { version_id: version }
522
    project_wiki_path(proj, page, url_params)
523
  end
524

Valery Sizov's avatar
Valery Sizov committed
525
526
527
  def project_status_css_class(status)
    case status
    when "started"
528
      "table-active"
Valery Sizov's avatar
Valery Sizov committed
529
    when "failed"
530
      "table-danger"
Valery Sizov's avatar
Valery Sizov committed
531
    when "finished"
532
      "table-success"
Valery Sizov's avatar
Valery Sizov committed
533
534
    end
  end
535

536
  def readme_cache_key
537
    sha = @project.commit.try(:sha) || 'nil'
538
    [@project.full_path, sha, "readme"].join('-')
539
  end
540

541
542
543
  def current_ref
    @ref || @repository.try(:root_ref)
  end
544

545
546
547
  def project_child_container_class(view_path)
    view_path == "projects/issues/issues" ? "prepend-top-default" : "project-show-#{view_path}"
  end
548
549
550
551

  def project_issues(project)
    IssuesFinder.new(current_user, project_id: project.id).execute
  end
Luke "Jared" Bennett's avatar
Luke "Jared" Bennett committed
552

553
554
555
  def restricted_levels
    return [] if current_user.admin?

556
    Gitlab::CurrentSettings.restricted_visibility_levels || []
Luke "Jared" Bennett's avatar
Luke "Jared" Bennett committed
557
  end
558

559
560
561
562
563
564
565
566
567
568
569
  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,
570
      pagesAccessLevel: feature.pages_access_level,
571
      containerRegistryEnabled: !!project.container_registry_enabled,
572
573
      lfsEnabled: !!project.lfs_enabled,
      emailsDisabled: project.emails_disabled?
574
575
576
577
    }
  end

  def project_permissions_panel_data(project)
578
    {
579
      currentSettings: project_permissions_settings(project),
580
      canDisableEmails: can_disable_emails?(project, current_user),
581
582
583
584
      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,
585
      registryHelpPath: help_page_path('user/packages/container_registry/index'),
586
      lfsAvailable: Gitlab.config.lfs.enabled,
587
588
589
      lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs'),
      pagesAvailable: Gitlab.config.pages.enabled,
      pagesAccessControlEnabled: Gitlab.config.pages.access_control,
590
      pagesHelpPath: help_page_path('user/project/pages/introduction', anchor: 'gitlab-pages-access-control-core')
591
    }
592
  end
593

594
595
  def project_permissions_panel_data_json(project)
    project_permissions_panel_data(project).to_json.html_safe
596
597
598
599
600
601
602
603
  end

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

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

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

    project_find_file_path(@project, ref)
  end
611
612
613
614

  def can_show_last_commit_in_list?(project)
    can?(current_user, :read_cross_project) && project.commit
  end
Rob Watson's avatar
Rob Watson committed
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632

  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
633

634
635
636
637
638
  def filter_starrer_path(options = {})
    options = params.slice(:sort).merge(options).permit!
    "#{request.path}?#{options.to_param}"
  end

639
640
641
642
  def sidebar_projects_paths
    %w[
      projects#show
      projects#activity
Filipa Lacerda's avatar
Filipa Lacerda committed
643
      releases#index
644
645
646
647
      cycle_analytics#show
    ]
  end

648
649
650
651
652
653
654
655
  def sidebar_settings_paths
    %w[
      projects#edit
      project_members#index
      integrations#show
      services#edit
      repository#show
      ci_cd#show
656
      operations#show
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
      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
      graphs
      network
    ]
  end
680
681
682
683
684

  def sidebar_operations_paths
    %w[
      environments
      clusters
685
      functions
686
      error_tracking
687
688
      user
      gcp
689
      logs
690
691
    ]
  end
692
693
694
695
696
697
698

  def user_can_see_auto_devops_implicitly_enabled_banner?(project, user)
    Ability.allowed?(user, :admin_project, project) &&
      project.has_auto_devops_implicitly_enabled? &&
      project.builds_enabled? &&
      !project.repository.gitlab_ci_yml
  end
699
700

  def vue_file_list_enabled?
701
    Feature.enabled?(:vue_file_list, @project)
702
  end
703
704
705
706

  def show_visibility_confirm_modal?(project)
    project.unlink_forks_upon_visibility_decrease_enabled? && project.visibility_level > Gitlab::VisibilityLevel::PRIVATE && project.forks_count > 0
  end
randx's avatar
randx committed
707
end