diff --git a/app/assets/javascripts/discourse/app/initializers/user-tips.js b/app/assets/javascripts/discourse/app/initializers/user-tips.js new file mode 100644 index 00000000000..5b4168c0343 --- /dev/null +++ b/app/assets/javascripts/discourse/app/initializers/user-tips.js @@ -0,0 +1,29 @@ +export default { + name: "user-tips", + after: "message-bus", + + initialize(container) { + const currentUser = container.lookup("service:current-user"); + if (!currentUser) { + return; + } + + const messageBus = container.lookup("service:message-bus"); + const site = container.lookup("service:site"); + + messageBus.subscribe("/user-tips", function (seenUserTips) { + currentUser.set("seen_popups", seenUserTips); + if (!currentUser.user_option) { + currentUser.set("user_option", {}); + } + currentUser.set("user_option.seen_popups", seenUserTips); + (seenUserTips || []).forEach((userTipId) => { + currentUser.hideUserTipForever( + Object.keys(site.user_tips).find( + (id) => site.user_tips[id] === userTipId + ) + ); + }); + }); + }, +}; diff --git a/app/assets/javascripts/discourse/app/models/user.js b/app/assets/javascripts/discourse/app/models/user.js index ad38126f8e4..1a35abf31c1 100644 --- a/app/assets/javascripts/discourse/app/models/user.js +++ b/app/assets/javascripts/discourse/app/models/user.js @@ -1173,27 +1173,28 @@ const User = RestModel.extend({ return; } - // Hide any shown user tips. + // Hide user tips and maybe show the next one. + if (userTipId) { + hideUserTip(userTipId); + showNextUserTip(); + } else { + hideAllUserTips(); + } + + // Update list of seen user tips. let seenUserTips = this.seen_popups || []; if (userTipId) { if (seenUserTips.includes(userTips[userTipId])) { return; } - - hideUserTip(userTipId); seenUserTips.push(userTips[userTipId]); } else { if (seenUserTips.includes(-1)) { return; } - - hideAllUserTips(); seenUserTips = [-1]; } - // Show next user tip in queue. - showNextUserTip(); - // Save seen user tips on the server. if (!this.user_option) { this.set("user_option", {}); diff --git a/app/assets/javascripts/discourse/tests/unit/models/user-test.js b/app/assets/javascripts/discourse/tests/unit/models/user-test.js index 0c6b77e8218..a0a25c22601 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/user-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/user-test.js @@ -6,6 +6,7 @@ import { settled } from "@ember/test-helpers"; import User from "discourse/models/user"; import pretender, { response } from "discourse/tests/helpers/create-pretender"; import { getOwner } from "discourse-common/lib/get-owner"; +import * as userTips from "discourse/lib/user-tips"; module("Unit | Model | user", function (hooks) { setupTest(hooks); @@ -199,12 +200,11 @@ module("Unit | Model | user", function (hooks) { test("hideUserTipForever() makes a single request", async function (assert) { const site = getOwner(this).lookup("service:site"); site.set("user_tips", { first_notification: 1 }); - const store = getOwner(this).lookup("service:store"); - const user = store.createRecord("user", { username: "test" }); + const user = store.createRecord("user", { username: "eviltrout" }); let requestsCount = 0; - pretender.put("/u/test.json", () => { + pretender.put("/u/eviltrout.json", () => { requestsCount += 1; return response(200, { user: { @@ -221,4 +221,30 @@ module("Unit | Model | user", function (hooks) { await user.hideUserTipForever("first_notification"); assert.strictEqual(requestsCount, 1); }); + + test("hideUserTipForever() can hide the user tip", async function (assert) { + const site = getOwner(this).lookup("service:site"); + site.set("user_tips", { first_notification: 1 }); + const store = getOwner(this).lookup("service:store"); + const user = store.createRecord("user", { username: "eviltrout" }); + + const hideSpy = sinon.spy(userTips, "hideUserTip"); + const showNextSpy = sinon.spy(userTips, "showNextUserTip"); + await user.hideUserTipForever("first_notification"); + + assert.ok(hideSpy.calledWith("first_notification")); + assert.ok(showNextSpy.calledWith()); + }); + + test("hideUserTipForever() can hide all the user tips", async function (assert) { + const site = getOwner(this).lookup("service:site"); + site.set("user_tips", { first_notification: 1 }); + const store = getOwner(this).lookup("service:store"); + const user = store.createRecord("user", { username: "eviltrout" }); + + const hideAllSpy = sinon.spy(userTips, "hideAllUserTips"); + await user.hideUserTipForever(); + + assert.ok(hideAllSpy.calledWith()); + }); }); diff --git a/app/services/user_updater.rb b/app/services/user_updater.rb index dccdf49daee..a3eacf46148 100644 --- a/app/services/user_updater.rb +++ b/app/services/user_updater.rb @@ -248,6 +248,13 @@ class UserUpdater user_notification_schedule.create_do_not_disturb_timings(delete_existing: true) : user_notification_schedule.destroy_scheduled_timings end + if attributes.key?(:seen_popups) || attributes.key?(:skip_new_user_tips) + MessageBus.publish( + '/user-tips', + user.user_option.seen_popups, + user_ids: [user.id] + ) + end DiscourseEvent.trigger(:user_updated, user) end diff --git a/spec/services/user_updater_spec.rb b/spec/services/user_updater_spec.rb index 95fd5f39d0d..82da2c0721b 100644 --- a/spec/services/user_updater_spec.rb +++ b/spec/services/user_updater_spec.rb @@ -526,16 +526,33 @@ RSpec.describe UserUpdater do end context 'when skip_new_user_tips is edited' do - it 'updates all fields' do - UserUpdater.new(Discourse.system_user, user).update(skip_new_user_tips: true) + it 'updates seen_popups too' do + messages = MessageBus.track_publish('/user-tips') do + UserUpdater.new(Discourse.system_user, user).update(skip_new_user_tips: true) + end expect(user.user_option.skip_new_user_tips).to eq(true) expect(user.user_option.seen_popups).to eq([-1]) + expect(messages.map(&:data)).to contain_exactly([-1]) - UserUpdater.new(Discourse.system_user, user).update(skip_new_user_tips: false) + messages = MessageBus.track_publish('/user-tips') do + UserUpdater.new(Discourse.system_user, user).update(skip_new_user_tips: false) + end expect(user.user_option.skip_new_user_tips).to eq(false) expect(user.user_option.seen_popups).to eq(nil) + expect(messages.map(&:data)).to contain_exactly(nil) + end + end + + context 'when seen_popups is edited' do + it 'publishes a message' do + messages = MessageBus.track_publish('/user-tips') do + UserUpdater.new(Discourse.system_user, user).update(seen_popups: [1]) + end + + expect(user.user_option.seen_popups).to eq([1]) + expect(messages.map(&:data)).to contain_exactly([1]) end end