diff --git a/app/assets/javascripts/discourse/app/lib/notification-types-manager.js b/app/assets/javascripts/discourse/app/lib/notification-types-manager.js index 7ee7878342b..32c7641fd4b 100644 --- a/app/assets/javascripts/discourse/app/lib/notification-types-manager.js +++ b/app/assets/javascripts/discourse/app/lib/notification-types-manager.js @@ -1,5 +1,6 @@ import NotificationTypeBase from "discourse/lib/notification-types/base"; +import AdminProblems from "discourse/lib/notification-types/admin-problems"; import BookmarkReminder from "discourse/lib/notification-types/bookmark-reminder"; import Custom from "discourse/lib/notification-types/custom"; import GrantedBadge from "discourse/lib/notification-types/granted-badge"; @@ -27,6 +28,7 @@ const CLASS_FOR_TYPE = { membership_request_consolidated: MembershipRequestConsolidated, moved_post: MovedPost, new_features: NewFeatures, + admin_problems: AdminProblems, watching_first_post: WatchingFirstPost, }; diff --git a/app/assets/javascripts/discourse/app/lib/notification-types/admin-problems.js b/app/assets/javascripts/discourse/app/lib/notification-types/admin-problems.js new file mode 100644 index 00000000000..9aadcbf6cca --- /dev/null +++ b/app/assets/javascripts/discourse/app/lib/notification-types/admin-problems.js @@ -0,0 +1,21 @@ +import NotificationTypeBase from "discourse/lib/notification-types/base"; +import getURL from "discourse-common/lib/get-url"; +import I18n from "I18n"; + +export default class extends NotificationTypeBase { + get label() { + return null; + } + + get description() { + return I18n.t("notifications.admin_problems"); + } + + get linkHref() { + return getURL("/admin"); + } + + get icon() { + return "gift"; + } +} diff --git a/app/jobs/scheduled/admin_problems.rb b/app/jobs/scheduled/admin_problems.rb new file mode 100644 index 00000000000..5e221066a32 --- /dev/null +++ b/app/jobs/scheduled/admin_problems.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Jobs + class AdminProblems < ::Jobs::Scheduled + every 30.minutes + + def execute(args) + Notification + .where(notification_type: Notification.types[:admin_problems]) + .where("created_at < ?", 7.days.ago) + .destroy_all + + return if !persistent_problems? + + notified_user_ids = + Notification.where(notification_type: Notification.types[:admin_problems]).pluck(:user_id) + + users = Group[:admins].users.where.not(id: notified_user_ids) + + users.each do |user| + Notification.create!( + notification_type: Notification.types[:admin_problems], + user_id: user.id, + data: "{}", + ) + end + end + + private + + def persistent_problems? + problems_started_at = AdminDashboardData.problems_started_at + problems_started_at && problems_started_at < 2.days.ago + end + end +end diff --git a/app/jobs/scheduled/dashboard_stats.rb b/app/jobs/scheduled/dashboard_stats.rb deleted file mode 100644 index a81d0b5d7b3..00000000000 --- a/app/jobs/scheduled/dashboard_stats.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module Jobs - class DashboardStats < ::Jobs::Scheduled - every 30.minutes - - def execute(args) - if persistent_problems? - # If there have been problems reported on the dashboard for a while, - # send a message to admins no more often than once per week. - group_message = - GroupMessage.new(Group[:admins].name, :dashboard_problems, limit_once_per: 7.days.to_i) - Topic.transaction do - group_message.delete_previous! - group_message.create - end - end - end - - private - - def persistent_problems? - problems_started_at = AdminDashboardData.problems_started_at - problems_started_at && problems_started_at < 2.days.ago - end - end -end diff --git a/app/models/notification.rb b/app/models/notification.rb index 825a566faf4..aed2dc7e684 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -152,6 +152,7 @@ class Notification < ActiveRecord::Base question_answer_user_commented: 35, # Used by https://github.com/discourse/discourse-question-answer watching_category_or_tag: 36, new_features: 37, + admin_problems: 38, following: 800, # Used by https://github.com/discourse/discourse-follow following_created_topic: 801, # Used by https://github.com/discourse/discourse-follow following_replied: 802, # Used by https://github.com/discourse/discourse-follow diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 86427f2212b..bfb22501eea 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2580,6 +2580,7 @@ en: reaction_2: "%{username}, %{username2} %{description}" votes_released: "%{description} - completed" new_features: "New features available!" + admin_problems: "New advice on your site dashboard" dismiss_confirmation: body: default: diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index f9b60dc6e0f..68bb82d6b8a 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -3538,16 +3538,6 @@ en: subject_template: "Downloading remote images disabled" text_body_template: "The `download_remote_images_to_local` setting was disabled because the disk space limit at `download_remote_images_threshold` was reached." - dashboard_problems: - title: "Dashboard Problems" - subject_template: "New advice on your site dashboard" - text_body_template: | - We have some new advice and recommendations for you based on your current site settings. - - [Visit your site dashboard](%{base_url}/admin) to see it. - - If nothing is visible on your dashboard, another staff member may have already acted on this advice. A list of staff actions can be found in your [Staff Action Logs](%{base_url}/admin/logs/staff_action_logs). - new_user_of_the_month: title: "You're a New User of the Month!" subject_template: "You're a New User of the Month!" diff --git a/spec/jobs/admin_problems_spec.rb b/spec/jobs/admin_problems_spec.rb new file mode 100644 index 00000000000..cd92c7737a2 --- /dev/null +++ b/spec/jobs/admin_problems_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +RSpec.describe ::Jobs::AdminProblems do + fab!(:admin) { Fabricate(:admin) } + + it "creates notification when problems persist for at least 2 days" do + Discourse.redis.setex(AdminDashboardData.problems_started_key, 14.days.to_i, Time.zone.now.to_s) + expect { described_class.new.execute({}) }.not_to change { Notification.count } + + Discourse.redis.setex(AdminDashboardData.problems_started_key, 14.days.to_i, 3.days.ago) + expect { described_class.new.execute({}) }.to change { Notification.count }.by(1) + end + + it "does not replace old notification created in last 7 days" do + Discourse.redis.setex(AdminDashboardData.problems_started_key, 14.days.to_i, 3.days.ago) + expect { described_class.new.execute({}) }.to change { Notification.count }.by(1) + old_notification = Notification.last + + expect { described_class.new.execute({}) }.not_to change { Notification.count } + new_notification = Notification.last + + expect(old_notification.id).to equal(new_notification.id) + end + + it "replace old notification created more than 7 days ago" do + Discourse.redis.setex(AdminDashboardData.problems_started_key, 14.days.to_i, 13.days.ago) + freeze_time 10.days.ago do + expect { described_class.new.execute({}) }.to change { Notification.count }.by(1) + end + old_notification = Notification.last + + expect { described_class.new.execute({}) }.not_to change { Notification.count } + new_notification = Notification.last + + expect(old_notification.id).not_to equal(new_notification.id) + end +end diff --git a/spec/jobs/dashboard_stats_spec.rb b/spec/jobs/dashboard_stats_spec.rb deleted file mode 100644 index 82aeaab1223..00000000000 --- a/spec/jobs/dashboard_stats_spec.rb +++ /dev/null @@ -1,60 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe ::Jobs::DashboardStats do - let(:group_message) do - GroupMessage.new(Group[:admins].name, :dashboard_problems, limit_once_per: 7.days.to_i) - end - - def clear_recently_sent! - # We won't immediately create new PMs due to the limit_once_per option, reset the value for testing purposes. - Discourse.redis.del(group_message.sent_recently_key) - end - - after { clear_recently_sent! } - - it "creates group message when problems are persistent for 2 days" do - Discourse.redis.setex(AdminDashboardData.problems_started_key, 14.days.to_i, Time.zone.now.to_s) - expect { described_class.new.execute({}) }.not_to change { Topic.count } - - Discourse.redis.setex(AdminDashboardData.problems_started_key, 14.days.to_i, 3.days.ago) - expect { described_class.new.execute({}) }.to change { Topic.count }.by(1) - end - - it "replaces old message" do - Discourse.redis.setex(AdminDashboardData.problems_started_key, 14.days.to_i, 3.days.ago) - expect { described_class.new.execute({}) }.to change { Topic.count }.by(1) - old_topic = Topic.last - clear_recently_sent! - - new_topic = described_class.new.execute({}).topic - expect(old_topic.reload.deleted_at.present?).to eq(true) - expect(new_topic.reload.deleted_at).to be_nil - expect(new_topic.title).to eq(old_topic.title) - end - - it "respects the sent_recently? check when deleting previous message" do - Discourse.redis.setex(AdminDashboardData.problems_started_key, 14.days.to_i, 3.days.ago) - expect { described_class.new.execute({}) }.to change { Topic.count }.by(1) - - expect { described_class.new.execute({}) }.not_to change { Topic.count } - end - - it "duplicates message if previous one has replies" do - Discourse.redis.setex(AdminDashboardData.problems_started_key, 14.days.to_i, 3.days.ago) - expect { described_class.new.execute({}) }.to change { Topic.count }.by(1) - clear_recently_sent! - - _reply_1 = Fabricate(:post, topic: Topic.last) - expect { described_class.new.execute({}) }.to change { Topic.count }.by(1) - end - - it "duplicates message if previous was 3 months ago" do - freeze_time 3.months.ago do - Discourse.redis.setex(AdminDashboardData.problems_started_key, 14.days.to_i, 3.days.ago) - expect { described_class.new.execute({}) }.to change { Topic.count }.by(1) - clear_recently_sent! - end - - expect { described_class.new.execute({}) }.to change { Topic.count }.by(1) - end -end diff --git a/spec/requests/api/schemas/json/site_response.json b/spec/requests/api/schemas/json/site_response.json index 09bc1621926..cc269f040e3 100644 --- a/spec/requests/api/schemas/json/site_response.json +++ b/spec/requests/api/schemas/json/site_response.json @@ -41,6 +41,9 @@ "new_features": { "type": "integer" }, + "admin_problems": { + "type": "integer" + }, "moved_post": { "type": "integer" },