diff --git a/app/assets/javascripts/discourse/app/components/software-update-prompt.js b/app/assets/javascripts/discourse/app/components/software-update-prompt.js new file mode 100644 index 00000000000..c8c91d42708 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/software-update-prompt.js @@ -0,0 +1,56 @@ +import getURL from "discourse-common/lib/get-url"; +import { later } from "@ember/runloop"; +import discourseComputed, { on } from "discourse-common/utils/decorators"; +import Component from "@ember/component"; + +export default Component.extend({ + showPrompt: false, + + classNameBindings: ["getClassNames"], + attributeBindings: ["isHidden:aria-hidden"], + + @discourseComputed + rootUrl() { + return getURL("/"); + }, + + @discourseComputed("showPrompt") + getClassNames(showPrompt) { + let classes = ["software-update-prompt"]; + + if (showPrompt) { + classes.push("require-software-refresh"); + } + + return classes.join(" "); + }, + + @discourseComputed("showPrompt") + isHidden(showPrompt) { + return !showPrompt; + }, + + @on("init") + initSubscribtions() { + let timeout; + + this.messageBus.subscribe("/refresh_client", () => { + this.session.requiresRefresh = true; + }); + + let updatePrompt = this; + this.messageBus.subscribe("/global/asset-version", (version) => { + if (this.session.assetVersion !== version) { + this.session.requiresRefresh = true; + } + + if (!timeout && this.session.requiresRefresh) { + // Since we can do this transparently for people browsing the forum + // hold back the message 24 hours. + timeout = later(() => { + updatePrompt.set("showPrompt", true); + }, 1000 * 60 * 24 * 60); + } + }); + }, +}); diff --git a/app/assets/javascripts/discourse/app/initializers/asset-version.js b/app/assets/javascripts/discourse/app/initializers/asset-version.js deleted file mode 100644 index c9017c6bccf..00000000000 --- a/app/assets/javascripts/discourse/app/initializers/asset-version.js +++ /dev/null @@ -1,40 +0,0 @@ -import I18n from "I18n"; -import bootbox from "bootbox"; -import { later } from "@ember/runloop"; - -// Subscribe to "asset-version" change events via the Message Bus -export default { - name: "asset-version", - after: "message-bus", - - initialize(container) { - let timeout; - const messageBus = container.lookup("message-bus:main"); - if (!messageBus) { - return; - } - - let session = container.lookup("session:main"); - messageBus.subscribe("/refresh_client", () => { - session.requiresRefresh = true; - }); - - messageBus.subscribe("/global/asset-version", function (version) { - if (session.assetVersion !== version) { - session.requiresRefresh = true; - } - - if (!timeout && session.requiresRefresh) { - // Since we can do this transparently for people browsing the forum - // hold back the message 24 hours. - timeout = later(() => { - bootbox.confirm(I18n.t("assets_changed_confirm"), function (result) { - if (result) { - document.location.reload(); - } - }); - }, 1000 * 60 * 24 * 60); - } - }); - }, -}; diff --git a/app/assets/javascripts/discourse/app/templates/application.hbs b/app/assets/javascripts/discourse/app/templates/application.hbs index 2f44d80833f..0d304c549a6 100644 --- a/app/assets/javascripts/discourse/app/templates/application.hbs +++ b/app/assets/javascripts/discourse/app/templates/application.hbs @@ -7,6 +7,7 @@ toggleMobileView=(route-action "toggleMobileView") toggleAnonymous=(route-action "toggleAnonymous") logout=(route-action "logout")}} +{{software-update-prompt id="software-update-prompt"}} {{plugin-outlet name="below-site-header" tagName="" args=(hash currentPath=router._router.currentPath)}} diff --git a/app/assets/javascripts/discourse/app/templates/components/software-update-prompt.hbs b/app/assets/javascripts/discourse/app/templates/components/software-update-prompt.hbs new file mode 100644 index 00000000000..0186225acd2 --- /dev/null +++ b/app/assets/javascripts/discourse/app/templates/components/software-update-prompt.hbs @@ -0,0 +1,5 @@ +
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/tests/acceptance/topic-discovery-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-discovery-test.js index f71e25020d7..05131a72430 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/topic-discovery-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/topic-discovery-test.js @@ -1,10 +1,10 @@ import { acceptance, exists, + publishToMessageBus, queryAll, } from "discourse/tests/helpers/qunit-helpers"; import DiscourseURL from "discourse/lib/url"; -import MessageBus from "message-bus-client"; import selectKit from "discourse/tests/helpers/select-kit-helper"; import sinon from "sinon"; import { test } from "qunit"; @@ -94,19 +94,16 @@ acceptance("Topic Discovery", function (needs) { "shows the topic unread" ); - // Mimic a messagebus message - MessageBus.callbacks.filterBy("channel", "/latest").map((c) => - c.func({ - message_type: "read", + publishToMessageBus("/latest", { + message_type: "read", + topic_id: 11995, + payload: { + highest_post_number: 1, + last_read_post_number: 2, + notification_level: 1, topic_id: 11995, - payload: { - highest_post_number: 1, - last_read_post_number: 2, - notification_level: 1, - topic_id: 11995, - }, - }) - ); + }, + }); await visit("/"); // We're already there, but use this to wait for re-render diff --git a/app/assets/javascripts/discourse/tests/helpers/component-test.js b/app/assets/javascripts/discourse/tests/helpers/component-test.js index 4486553f4b0..e44e71b8587 100644 --- a/app/assets/javascripts/discourse/tests/helpers/component-test.js +++ b/app/assets/javascripts/discourse/tests/helpers/component-test.js @@ -1,4 +1,5 @@ import { TestModuleForComponent, render } from "@ember/test-helpers"; +import MessageBus from "message-bus-client"; import EmberObject from "@ember/object"; import { setupRenderingTest as EmberSetupRenderingTest } from "ember-qunit"; import Session from "discourse/models/session"; @@ -67,6 +68,9 @@ export default function (name, opts) { instantiate: false, }); this.registry.register("capabilities:main", EmberObject); + this.registry.register("message-bus:main", MessageBus, { + instantiate: false, + }); this.registry.register("site:main", this.site, { instantiate: false }); this.registry.register("session:main", this.session, { instantiate: false, @@ -80,6 +84,7 @@ export default function (name, opts) { this.registry.injection("component", "capabilities", "capabilities:main"); this.registry.injection("component", "site", "site:main"); this.registry.injection("component", "session", "session:main"); + this.registry.injection("component", "messageBus", "message-bus:main"); this.siteSettings = currentSettings(); store = createStore(); diff --git a/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js b/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js index 0a6ddd53565..8d0d251c706 100644 --- a/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js +++ b/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js @@ -1,4 +1,5 @@ import QUnit, { module } from "qunit"; +import MessageBus from "message-bus-client"; import { clearCache as clearOutletCache, resetExtraClasses, @@ -437,3 +438,9 @@ export function count(selector) { export function exists(selector) { return count(selector) > 0; } + +export function publishToMessageBus(channelPath, ...args) { + MessageBus.callbacks + .filterBy("channel", channelPath) + .map((c) => c.func(...args)); +} diff --git a/app/assets/javascripts/discourse/tests/integration/widgets/software-update-prompt-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/software-update-prompt-test.js new file mode 100644 index 00000000000..301cfc9fa7a --- /dev/null +++ b/app/assets/javascripts/discourse/tests/integration/widgets/software-update-prompt-test.js @@ -0,0 +1,60 @@ +import componentTest, { + setupRenderingTest, +} from "discourse/tests/helpers/component-test"; +import sinon from "sinon"; +import { + discourseModule, + fakeTime, + publishToMessageBus, + queryAll, +} from "discourse/tests/helpers/qunit-helpers"; +import hbs from "htmlbars-inline-precompile"; + +let clock = null; +discourseModule( + "Integration | Component | software-update-prompt", + function (hooks) { + setupRenderingTest(hooks); + + hooks.beforeEach(function () { + clock = fakeTime("2019-12-10T08:00:00", "Australia/Brisbane", true); + }); + + hooks.afterEach(function () { + clock.restore(); + sinon.restore(); + }); + + componentTest( + "software-update-prompt gets correct CSS class after messageBus message", + { + template: hbs`{{software-update-prompt}}`, + + test(assert) { + assert.ok( + queryAll("div.software-update-prompt.require-software-refresh") + .length === 0, + "it does not have the class to show the prompt" + ); + assert.equal( + queryAll("div.software-update-prompt")[0].getAttribute( + "aria-hidden" + ), + "", + "it does have the aria-hidden attribute" + ); + + publishToMessageBus("/global/asset-version", "somenewversion"); + + clock.tick(1000 * 60 * 24 * 60 + 10); + + assert.ok( + queryAll("div.software-update-prompt.require-software-refresh") + .length === 1, + "it does have the class to show the prompt" + ); + }, + } + ); + } +); diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss index 964b2c1529e..3a73cda6034 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -10,4 +10,5 @@ @import "common/printer-friendly"; @import "common/base/_index"; @import "common/d-editor"; +@import "common/software-update-prompt"; @import "common/topic-timeline"; diff --git a/app/assets/stylesheets/common/software-update-prompt.scss b/app/assets/stylesheets/common/software-update-prompt.scss new file mode 100644 index 00000000000..0871146d255 --- /dev/null +++ b/app/assets/stylesheets/common/software-update-prompt.scss @@ -0,0 +1,35 @@ +.software-update-prompt { + position: fixed; + flex: 1; + right: 0; + left: 0; + background-color: var(--tertiary-low); + color: var(--tertiary); + max-height: 0; + visibility: hidden; + transition: max-height 1s; + box-shadow: shadow("header"); + z-index: z("header") + 10; + + a { + display: block; + padding: 0.5em 0; + } + + span { + text-decoration: underline; + } + + .d-icon { + margin-right: 0.33em; + font-size: 0.9em; + } + + &.require-software-refresh { + visibility: visible; + overflow: hidden; + max-height: 300px; + margin-left: auto; + margin-right: auto; + } +} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 87706042279..4fc3fbf58f8 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -178,6 +178,8 @@ en: wizard_required: "Welcome to your new Discourse! Let’s get started with the setup wizard ✨" emails_are_disabled: "All outgoing email has been globally disabled by an administrator. No email notifications of any kind will be sent." + software_update_prompt: "We've updated this site, please refresh, or you may experience unexpected behaviour." + bootstrap_mode_enabled: one: "To make launching your new site easier, you are in bootstrap mode. All new users will be granted trust level 1 and have daily email summary emails enabled. This will be automatically turned off when %{count} user has joined." other: "To make launching your new site easier, you are in bootstrap mode. All new users will be granted trust level 1 and have daily email summary emails enabled. This will be automatically turned off when %{count} users have joined." @@ -1025,7 +1027,7 @@ en: no_messages_title: "You don’t have any messages" no_messages_body: > Need to have a direct personal conversation with someone, outside the normal conversational flow?