Welcome to mirror list, hosted at ThFree Co, Russian Federation.

broadcast_message.rb « system « models « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 06f0115ade6869e1be6ab5d6ea0f13243cb832c9 (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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# frozen_string_literal: true

module System
  class BroadcastMessage < MainClusterwide::ApplicationRecord
    include CacheMarkdownField
    include Sortable

    ALLOWED_TARGET_ACCESS_LEVELS = [
      Gitlab::Access::GUEST,
      Gitlab::Access::REPORTER,
      Gitlab::Access::DEVELOPER,
      Gitlab::Access::MAINTAINER,
      Gitlab::Access::OWNER
    ].freeze

    cache_markdown_field :message, pipeline: :broadcast_message, whitelisted: true

    validates :message,   presence: true
    validates :starts_at, presence: true
    validates :ends_at,   presence: true
    validates :broadcast_type, presence: true
    validates :target_access_levels, inclusion: { in: ALLOWED_TARGET_ACCESS_LEVELS }
    validates :show_in_cli, allow_nil: false, inclusion: { in: [true, false], message: N_('must be a boolean value') }

    validates :color, allow_blank: true, color: true
    validates :font,  allow_blank: true, color: true

    attribute :color, default: '#E75E40'
    attribute :font, default: '#FFFFFF'

    scope :current_and_future_messages, -> { where('ends_at > :now', now: Time.current).order_id_asc }

    CACHE_KEY = 'broadcast_message_current_json'
    BANNER_CACHE_KEY = 'broadcast_message_current_banner_json'
    NOTIFICATION_CACHE_KEY = 'broadcast_message_current_notification_json'

    after_commit :flush_redis_cache

    enum theme: {
      indigo: 0,
      'light-indigo': 1,
      blue: 2,
      'light-blue': 3,
      green: 4,
      'light-green': 5,
      red: 6,
      'light-red': 7,
      dark: 8,
      light: 9
    }, _default: 0, _prefix: true

    enum broadcast_type: {
      banner: 1,
      notification: 2
    }

    class << self
      def current_banner_messages(current_path: nil, user_access_level: nil)
        fetch_messages BANNER_CACHE_KEY, current_path, user_access_level do
          current_and_future_messages.banner
        end
      end

      def current_show_in_cli_banner_messages
        current_banner_messages.select(&:show_in_cli?)
      end

      def current_notification_messages(current_path: nil, user_access_level: nil)
        fetch_messages NOTIFICATION_CACHE_KEY, current_path, user_access_level do
          current_and_future_messages.notification
        end
      end

      def current(current_path: nil, user_access_level: nil)
        fetch_messages CACHE_KEY, current_path, user_access_level do
          current_and_future_messages
        end
      end

      def cache
        ::Gitlab::SafeRequestStore.fetch(:broadcast_message_json_cache) do
          Gitlab::Cache::JsonCaches::JsonKeyed.new
        end
      end

      def cache_expires_in
        2.weeks
      end

      private

      def fetch_messages(cache_key, current_path, user_access_level, &block)
        messages = cache.fetch(cache_key, as: System::BroadcastMessage, expires_in: cache_expires_in, &block)

        now_or_future = messages.select(&:now_or_future?)

        # If there are cached entries but they don't match the ones we are
        # displaying we'll refresh the cache so we don't need to keep filtering.
        cache.expire(cache_key) if now_or_future != messages

        messages = now_or_future.select(&:now?)
        messages = messages.select do |message|
          message.matches_current_user_access_level?(user_access_level)
        end
        messages.select do |message|
          message.matches_current_path(current_path)
        end
      end
    end

    def active?
      started? && !ended?
    end

    def started?
      Time.current >= starts_at
    end

    def ended?
      ends_at < Time.current
    end

    def now?
      (starts_at..ends_at).cover?(Time.current)
    end

    def future?
      starts_at.future?
    end

    def now_or_future?
      now? || future?
    end

    def matches_current_user_access_level?(user_access_level)
      return true unless target_access_levels.present?

      target_access_levels.include? user_access_level
    end

    def matches_current_path(current_path)
      return false if current_path.blank? && target_path.present?
      return true if current_path.blank? || target_path.blank?

      # Ensure paths are consistent across callers.
      # This fixes a mismatch between requests in the GUI and CLI
      #
      # This has to be reassigned due to frozen strings being provided.
      current_path = "/#{current_path}" unless current_path.start_with?("/")

      escaped = Regexp.escape(target_path).gsub('\\*', '.*')
      regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE

      regexp.match(current_path)
    end

    def flush_redis_cache
      [CACHE_KEY, BANNER_CACHE_KEY, NOTIFICATION_CACHE_KEY].each do |key|
        self.class.cache.expire(key)
      end
    end
  end
end

System::BroadcastMessage.prepend_mod_with('System::BroadcastMessage')