summaryrefslogtreecommitdiff
path: root/app/helpers/storage_helper.rb
blob: 9e516d726c1b65c33e5267efb8687e9fe25e2ac9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# frozen_string_literal: true

module StorageHelper
  def storage_counter(size_in_bytes)
    return s_('StorageSize|Unknown') unless size_in_bytes

    precision = size_in_bytes < 1.megabyte ? 0 : 1

    number_to_human_size(size_in_bytes, delimiter: ',', precision: precision, significant: false)
  end

  def storage_counters_details(statistics)
    counters = {
      counter_repositories: storage_counter(statistics.repository_size),
      counter_wikis: storage_counter(statistics.wiki_size),
      counter_build_artifacts: storage_counter(statistics.build_artifacts_size),
      counter_pipeline_artifacts: storage_counter(statistics.pipeline_artifacts_size),
      counter_lfs_objects: storage_counter(statistics.lfs_objects_size),
      counter_snippets: storage_counter(statistics.snippets_size),
      counter_packages: storage_counter(statistics.packages_size),
      counter_uploads: storage_counter(statistics.uploads_size)
    }

    _("Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / Pipeline Artifacts: %{counter_pipeline_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets} / Packages: %{counter_packages} / Uploads: %{counter_uploads}") % counters
  end

  def storage_enforcement_banner_info(context)
    root_ancestor = context.root_ancestor

    return unless should_show_storage_enforcement_banner?(context, current_user, root_ancestor)

    text_args = storage_enforcement_banner_text_args(root_ancestor, context)

    text_paragraph_2 = if root_ancestor.user_namespace?
                         html_escape_once(s_("UsageQuota|The namespace is currently using %{strong_start}%{used_storage}%{strong_end} of namespace storage. " \
                           "View and manage your usage from %{strong_start}User settings &gt; Usage quotas%{strong_end}. %{docs_link_start}Learn more%{link_end} " \
                           "about how to reduce your storage.")).html_safe % text_args[:p2]
                       else
                         html_escape_once(s_("UsageQuota|The namespace is currently using %{strong_start}%{used_storage}%{strong_end} of namespace storage. " \
                                             "Group owners can view namespace storage usage and purchase more from %{strong_start}Group settings &gt; Usage quotas%{strong_end}. %{docs_link_start}Learn more.%{link_end}" \
                                            )).html_safe % text_args[:p2]
                       end

    {
      text_paragraph_1: html_escape_once(s_("UsageQuota|Effective %{storage_enforcement_date}, namespace storage limits will apply " \
            "to the %{strong_start}%{namespace_name}%{strong_end} namespace. %{extra_message}" \
            "View the %{rollout_link_start}rollout schedule for this change%{link_end}.")).html_safe % text_args[:p1],
      text_paragraph_2: text_paragraph_2,
      text_paragraph_3: html_escape_once(s_("UsageQuota|See our %{faq_link_start}FAQ%{link_end} for more information.")).html_safe % text_args[:p3],
      variant: 'warning',
      namespace_id: root_ancestor.id,
      callouts_path: root_ancestor.user_namespace? ? callouts_path : group_callouts_path,
      callouts_feature_name: storage_enforcement_banner_user_callouts_feature_name(root_ancestor)
    }
  end

  private

  def should_show_storage_enforcement_banner?(context, current_user, root_ancestor)
    return false unless user_allowed_storage_enforcement_banner?(context, current_user, root_ancestor)
    return false if root_ancestor.paid?
    return false unless future_enforcement_date?(root_ancestor)
    return false if user_dismissed_storage_enforcement_banner?(root_ancestor)

    ::Feature.enabled?(:namespace_storage_limit_show_preenforcement_banner, root_ancestor)
  end

  def user_allowed_storage_enforcement_banner?(context, current_user, root_ancestor)
    return can?(current_user, :maintainer_access, context) unless context.respond_to?(:user_namespace?) && context.user_namespace?

    can?(current_user, :owner_access, context)
  end

  def storage_enforcement_banner_text_args(root_ancestor, context)
    strong_tags = {
      strong_start: "<strong>".html_safe,
      strong_end: "</strong>".html_safe
    }

    extra_message = if context.is_a?(Project)
                      html_escape_once(s_("UsageQuota|The %{strong_start}%{context_name}%{strong_end} project will be affected by this. "))
                        .html_safe % strong_tags.merge(context_name: context.name)
                    elsif !context.root?
                      html_escape_once(s_("UsageQuota|The %{strong_start}%{context_name}%{strong_end} group will be affected by this. "))
                        .html_safe % strong_tags.merge(context_name: context.name)
                    else
                      ''
                    end

    {
      p1: {
        storage_enforcement_date: root_ancestor.storage_enforcement_date,
        namespace_name: root_ancestor.name,
        extra_message: extra_message,
        rollout_link_start: '<a href="%{url}" >'.html_safe % { url: help_page_path('user/usage_quotas', anchor: 'namespace-storage-limit-enforcement-schedule') },
        link_end: "</a>".html_safe
      }.merge(strong_tags),
      p2: {
        used_storage: storage_counter(root_ancestor.root_storage_statistics&.storage_size || 0),
        docs_link_start: '<a href="%{url}" >'.html_safe % { url: help_page_path('user/usage_quotas', anchor: 'manage-your-storage-usage') },
        link_end: "</a>".html_safe
      }.merge(strong_tags),
      p3: {
        faq_link_start: '<a href="%{url}" >'.html_safe % { url: "#{Gitlab::Saas.about_pricing_url}faq-efficient-free-tier/#storage-limits-on-gitlab-saas-free-tier" },
        link_end: "</a>".html_safe
      }
    }
  end

  def storage_enforcement_banner_user_callouts_feature_name(namespace)
    "storage_enforcement_banner_#{storage_enforcement_banner_threshold(namespace)}_enforcement_threshold"
  end

  def storage_enforcement_banner_threshold(namespace)
    days_to_enforcement_date = (namespace.storage_enforcement_date - Date.today)

    return :first if days_to_enforcement_date > 30
    return :second if days_to_enforcement_date > 15 && days_to_enforcement_date <= 30
    return :third if days_to_enforcement_date > 7 && days_to_enforcement_date <= 15
    return :fourth if days_to_enforcement_date >= 0 && days_to_enforcement_date <= 7
  end

  def user_dismissed_storage_enforcement_banner?(namespace)
    return false unless current_user

    if namespace.user_namespace?
      current_user.dismissed_callout?(feature_name: storage_enforcement_banner_user_callouts_feature_name(namespace))
    else
      current_user.dismissed_callout_for_group?(
        feature_name: storage_enforcement_banner_user_callouts_feature_name(namespace),
        group: namespace
      )
    end
  end

  def future_enforcement_date?(namespace)
    return true if ::Feature.enabled?(:namespace_storage_limit_bypass_date_check, namespace)

    namespace.storage_enforcement_date.present? && namespace.storage_enforcement_date >= Date.today
  end
end