discourse/lib/discourse_updates.rb
Osama Sayegh 1c03d6f9b9
FEATURE: Send notifications to admins when new features are released (#19460)
This commit adds a new notification that gets sent to admins when the site gets new features after an upgrade/deploy. Clicking on the notification takes the admin to the admin dashboard at `/admin` where they can see the new features under the "New Features" section.

Internal topic: t/87166.
2022-12-15 20:12:53 +03:00

236 lines
6.6 KiB
Ruby

# frozen_string_literal: true
module DiscourseUpdates
class << self
def check_version
attrs = {
installed_version: Discourse::VERSION::STRING,
installed_sha: (Discourse.git_version == 'unknown' ? nil : Discourse.git_version),
installed_describe: Discourse.full_version,
git_branch: Discourse.git_branch,
updated_at: updated_at,
}
unless updated_at.nil?
attrs.merge!(
latest_version: latest_version,
critical_updates: critical_updates_available?,
missing_versions_count: missing_versions_count
)
end
version_info = DiscourseVersionCheck.new(attrs)
# replace -commit_count with +commit_count
if version_info.installed_describe =~ /-(\d+)-/
version_info.installed_describe = version_info.installed_describe.gsub(/-(\d+)-.*/, " +#{$1}")
end
if SiteSetting.version_checks?
is_stale_data =
(version_info.missing_versions_count == 0 && version_info.latest_version != version_info.installed_version) ||
(version_info.missing_versions_count != 0 && version_info.latest_version == version_info.installed_version)
# Handle cases when version check data is old so we report something that makes sense
if version_info.updated_at.nil? || # never performed a version check
last_installed_version != Discourse::VERSION::STRING || # upgraded since the last version check
is_stale_data
Jobs.enqueue(:version_check, all_sites: true)
version_info.version_check_pending = true
unless version_info.updated_at.nil?
version_info.missing_versions_count = 0
version_info.critical_updates = false
end
end
version_info.stale_data =
version_info.version_check_pending ||
(updated_at && updated_at < 48.hours.ago) ||
is_stale_data
end
version_info
end
# last_installed_version is the installed version at the time of the last version check
def last_installed_version
Discourse.redis.get last_installed_version_key
end
def last_installed_version=(arg)
Discourse.redis.set(last_installed_version_key, arg)
end
def latest_version
Discourse.redis.get latest_version_key
end
def latest_version=(arg)
Discourse.redis.set(latest_version_key, arg)
end
def missing_versions_count
Discourse.redis.get(missing_versions_count_key).try(:to_i)
end
def missing_versions_count=(arg)
Discourse.redis.set(missing_versions_count_key, arg)
end
def critical_updates_available?
(Discourse.redis.get(critical_updates_available_key) || false) == 'true'
end
def critical_updates_available=(arg)
Discourse.redis.set(critical_updates_available_key, arg)
end
def updated_at
t = Discourse.redis.get(updated_at_key)
t ? Time.zone.parse(t) : nil
end
def updated_at=(time_with_zone)
Discourse.redis.set updated_at_key, time_with_zone.as_json
end
def missing_versions=(versions)
# delete previous list from redis
prev_keys = Discourse.redis.lrange(missing_versions_list_key, 0, 4)
if prev_keys
Discourse.redis.del prev_keys
Discourse.redis.del(missing_versions_list_key)
end
if versions.present?
# store the list in redis
version_keys = []
versions[0, 5].each do |v|
key = "#{missing_versions_key_prefix}:#{v['version']}"
Discourse.redis.mapped_hmset key, v
version_keys << key
end
Discourse.redis.rpush missing_versions_list_key, version_keys
end
versions || []
end
def missing_versions
keys = Discourse.redis.lrange(missing_versions_list_key, 0, 4) # max of 5 versions
keys.present? ? keys.map { |k| Discourse.redis.hgetall(k) } : []
end
def current_version
last_installed_version || Discourse::VERSION::STRING
end
def new_features_payload
response = Excon.new(new_features_endpoint).request(expects: [200], method: :Get)
response.body
end
def update_new_features(payload = nil)
payload ||= new_features_payload
Discourse.redis.set(new_features_key, payload)
end
def new_features
entries = JSON.parse(Discourse.redis.get(new_features_key)) rescue nil
return nil if entries.nil?
entries.select! do |item|
item["discourse_version"].nil? || Discourse.has_needed_version?(current_version, item["discourse_version"]) rescue nil
end
entries.sort_by { |item| Time.zone.parse(item["created_at"]).to_i }.reverse
end
def has_unseen_features?(user_id)
entries = new_features
return false if entries.nil?
last_seen = new_features_last_seen(user_id)
if last_seen.present?
entries.select! { |item| Time.zone.parse(item["created_at"]) > last_seen }
end
entries.size > 0
end
def new_features_last_seen(user_id)
last_seen = Discourse.redis.get new_features_last_seen_key(user_id)
return nil if last_seen.blank?
Time.zone.parse(last_seen)
end
def mark_new_features_as_seen(user_id)
entries = JSON.parse(Discourse.redis.get(new_features_key)) rescue nil
return nil if entries.nil?
last_seen = entries.max_by { |x| x["created_at"] }
Discourse.redis.set(new_features_last_seen_key(user_id), last_seen["created_at"])
end
def get_last_viewed_feature_date(user_id)
date = Discourse.redis.hget(last_viewed_feature_dates_for_users_key, user_id.to_s)
return if date.blank?
Time.zone.parse(date)
end
def bump_last_viewed_feature_date(user_id, feature_date)
Discourse.redis.hset(last_viewed_feature_dates_for_users_key, user_id.to_s, feature_date)
end
private
def last_installed_version_key
'last_installed_version'
end
def latest_version_key
'discourse_latest_version'
end
def critical_updates_available_key
'critical_updates_available'
end
def missing_versions_count_key
'missing_versions_count'
end
def updated_at_key
'last_version_check_at'
end
def missing_versions_list_key
'missing_versions'
end
def missing_versions_key_prefix
'missing_version'
end
def new_features_endpoint
'https://meta.discourse.org/new-features.json'
end
def new_features_key
'new_features'
end
def new_features_last_seen_key(user_id)
"new_features_last_seen_user_#{user_id}"
end
def last_viewed_feature_dates_for_users_key
"last_viewed_feature_dates_for_users_hash"
end
end
end