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
|
# frozen_string_literal: true
class ReleaseHighlight
CACHE_DURATION = 1.hour
FREE_PACKAGE = 'Free'
PREMIUM_PACKAGE = 'Premium'
ULTIMATE_PACKAGE = 'Ultimate'
def self.paginated(page: 1)
result = self.paginated_query(page: page)
result = self.paginated_query(page: result.next_page) while next_page?(result)
result
end
def self.paginated_query(page:)
key = self.cache_key("items:page-#{page}")
Rails.cache.fetch(key, expires_in: CACHE_DURATION) do
items = self.load_items(page: page)
next if items.nil?
QueryResult.new(items: items, next_page: next_page(current_page: page))
end
end
def self.load_items(page:)
index = page - 1
file_path = file_paths[index]
return if file_path.nil?
file = File.read(file_path)
items = YAML.safe_load(file, permitted_classes: [Date])
items&.map! do |item|
next unless include_item?(item)
begin
item.tap { |i| i['description'] = Banzai.render(i['description'], { project: nil }) }
rescue StandardError => e
Gitlab::ErrorTracking.track_exception(e, file_path: file_path)
next
end
end
items&.compact
rescue Psych::Exception => e
Gitlab::ErrorTracking.track_exception(e, file_path: file_path)
[]
end
def self.whats_new_path
Rails.root.join('data/whats_new/*.yml')
end
def self.file_paths
@file_paths ||= self.relative_file_paths.map { |path| path.prepend(Rails.root.to_s) }
end
def self.relative_file_paths
Rails.cache.fetch(self.cache_key('file_paths'), expires_in: CACHE_DURATION) do
Dir.glob(whats_new_path).sort.reverse.map { |path| path.delete_prefix(Rails.root.to_s) }
end
end
def self.cache_key(key)
variant = Gitlab::CurrentSettings.current_application_settings.whats_new_variant
['release_highlight', variant, key, Gitlab.revision].join(':')
end
def self.next_page(current_page: 1)
next_page = current_page + 1
next_index = next_page - 1
next_page if self.file_paths[next_index]
end
def self.most_recent_item_count
key = self.cache_key('recent_item_count')
Gitlab::ProcessMemoryCache.cache_backend.fetch(key, expires_in: CACHE_DURATION) do
self.paginated&.items&.count
end
end
def self.most_recent_version_digest
key = self.cache_key('most_recent_version_digest')
Gitlab::ProcessMemoryCache.cache_backend.fetch(key, expires_in: CACHE_DURATION) do
version = self.paginated&.items&.first&.[]('release')&.to_s
next if version.nil?
Digest::SHA256.hexdigest(version)
end
end
QueryResult = Struct.new(:items, :next_page, keyword_init: true) do
include Enumerable
delegate :each, to: :items
end
def self.current_package
return FREE_PACKAGE unless defined?(License)
case License.current&.plan&.downcase
when License::PREMIUM_PLAN
PREMIUM_PACKAGE
when License::ULTIMATE_PLAN
ULTIMATE_PACKAGE
else
FREE_PACKAGE
end
end
def self.include_item?(item)
platform = Gitlab.com? ? 'gitlab-com' : 'self-managed'
return false unless item[platform]
return true unless Gitlab::CurrentSettings.current_application_settings.whats_new_variant_current_tier?
item['available_in']&.include?(current_package)
end
def self.next_page?(result)
return false unless result
# if all items for the current page doesn't belong to the current tier
# or failed to parse current YAML, loading next page
result.items == [] && result.next_page.present?
end
end
ReleaseHighlight.prepend_mod
|