Merge pull request #157 from paviliondev/add_notice_inbox
Add notice inbox
Dieser Commit ist enthalten in:
Commit
75d881de06
34 geänderte Dateien mit 1052 neuen und 364 gelöschten Zeilen
19
assets/javascripts/discourse/components/wizard-notice-row.js.es6
Normale Datei
19
assets/javascripts/discourse/components/wizard-notice-row.js.es6
Normale Datei
|
@ -0,0 +1,19 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import NoticeMessage from "../mixins/notice-message";
|
||||||
|
|
||||||
|
export default Component.extend(NoticeMessage, {
|
||||||
|
tagName: "tr",
|
||||||
|
attributeBindings: ["notice.id:data-notice-id"],
|
||||||
|
classNameBindings: [
|
||||||
|
":wizard-notice-row",
|
||||||
|
"notice.typeClass",
|
||||||
|
"notice.expired:expired",
|
||||||
|
"notice.dismissed:dismissed",
|
||||||
|
],
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
dismiss() {
|
||||||
|
this.notice.dismiss();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,35 +1,15 @@
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import NoticeMessage from "../mixins/notice-message";
|
||||||
import { not, notEmpty } from "@ember/object/computed";
|
|
||||||
import I18n from "I18n";
|
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend(NoticeMessage, {
|
||||||
|
attributeBindings: ["notice.id:data-notice-id"],
|
||||||
classNameBindings: [
|
classNameBindings: [
|
||||||
":wizard-notice",
|
":wizard-notice",
|
||||||
"notice.type",
|
"notice.typeClass",
|
||||||
"dismissed",
|
"notice.dismissed:dismissed",
|
||||||
"expired",
|
"notice.expired:expired",
|
||||||
"resolved",
|
"notice.hidden:hidden",
|
||||||
],
|
],
|
||||||
showFull: false,
|
|
||||||
resolved: notEmpty("notice.expired_at"),
|
|
||||||
dismissed: notEmpty("notice.dismissed_at"),
|
|
||||||
canDismiss: not("dismissed"),
|
|
||||||
|
|
||||||
@discourseComputed("notice.type")
|
|
||||||
title(type) {
|
|
||||||
return I18n.t(`admin.wizard.notice.title.${type}`);
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("notice.type")
|
|
||||||
icon(type) {
|
|
||||||
return {
|
|
||||||
plugin_status_warning: "exclamation-circle",
|
|
||||||
plugin_status_connection_error: "bolt",
|
|
||||||
subscription_messages_connection_error: "bolt",
|
|
||||||
info: "info-circle",
|
|
||||||
}[type];
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
dismiss() {
|
dismiss() {
|
||||||
|
@ -38,5 +18,12 @@ export default Component.extend({
|
||||||
this.set("dismissing", false);
|
this.set("dismissing", false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
this.set("hiding", true);
|
||||||
|
this.notice.hide().then(() => {
|
||||||
|
this.set("hiding", false);
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
{{#if notices}}
|
||||||
|
{{#each notices as |notice|}}
|
||||||
|
{{wizard-notice notice=notice showPlugin=true}}
|
||||||
|
{{/each}}
|
||||||
|
{{/if}}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { getOwner } from "discourse-common/lib/get-owner";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
shouldRender(attrs, ctx) {
|
||||||
|
return ctx.siteSettings.wizard_critical_notices_on_dashboard;
|
||||||
|
},
|
||||||
|
|
||||||
|
setupComponent(attrs, component) {
|
||||||
|
const controller = getOwner(this).lookup("controller:admin-dashboard");
|
||||||
|
|
||||||
|
component.set("notices", controller.get("customWizardCriticalNotices"));
|
||||||
|
controller.addObserver("customWizardCriticalNotices.[]", () => {
|
||||||
|
if (this._state === "destroying") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
component.set("notices", controller.get("customWizardCriticalNotices"));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,3 +0,0 @@
|
||||||
{{#if importantNotice}}
|
|
||||||
{{wizard-notice notice=importantNotice importantOnDashboard=true}}
|
|
||||||
{{/if}}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { getOwner } from "discourse-common/lib/get-owner";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
shouldRender(attrs, ctx) {
|
|
||||||
return ctx.siteSettings.wizard_important_notices_on_dashboard;
|
|
||||||
},
|
|
||||||
|
|
||||||
setupComponent() {
|
|
||||||
const controller = getOwner(this).lookup("controller:admin-dashboard");
|
|
||||||
const importantNotice = controller.get("customWizardImportantNotice");
|
|
||||||
|
|
||||||
if (importantNotice) {
|
|
||||||
this.set("importantNotice", importantNotice);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
import Controller from "@ember/controller";
|
||||||
|
import CustomWizardNotice from "../models/custom-wizard-notice";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
import { notEmpty } from "@ember/object/computed";
|
||||||
|
import { A } from "@ember/array";
|
||||||
|
import I18n from "I18n";
|
||||||
|
|
||||||
|
export default Controller.extend({
|
||||||
|
messageUrl: "https://thepavilion.io/t/3652",
|
||||||
|
messageKey: "info",
|
||||||
|
messageIcon: "info-circle",
|
||||||
|
messageClass: "info",
|
||||||
|
hasNotices: notEmpty("notices"),
|
||||||
|
page: 0,
|
||||||
|
loadingMore: false,
|
||||||
|
canLoadMore: true,
|
||||||
|
|
||||||
|
@discourseComputed("notices.[]", "notices.@each.dismissed")
|
||||||
|
allDismisssed(notices) {
|
||||||
|
return notices.every((n) => !n.canDismiss || n.dismissed);
|
||||||
|
},
|
||||||
|
|
||||||
|
loadMoreNotices() {
|
||||||
|
if (!this.canLoadMore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const page = this.get("page");
|
||||||
|
this.set("loadingMore", true);
|
||||||
|
|
||||||
|
CustomWizardNotice.list({ page, include_all: true })
|
||||||
|
.then((result) => {
|
||||||
|
if (result.notices.length === 0) {
|
||||||
|
this.set("canLoadMore", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.get("notices").pushObjects(
|
||||||
|
A(result.notices.map((notice) => CustomWizardNotice.create(notice)))
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.finally(() => this.set("loadingMore", false));
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
loadMore() {
|
||||||
|
if (this.canLoadMore) {
|
||||||
|
this.set("page", this.page + 1);
|
||||||
|
this.loadMoreNotices();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
dismissAll() {
|
||||||
|
bootbox.confirm(
|
||||||
|
I18n.t("admin.wizard.notice.dismiss_all.confirm"),
|
||||||
|
I18n.t("no_value"),
|
||||||
|
I18n.t("yes_value"),
|
||||||
|
(result) => {
|
||||||
|
if (result) {
|
||||||
|
this.set("loadingMore", true);
|
||||||
|
CustomWizardNotice.dismissAll().finally(() =>
|
||||||
|
this.set("loadingMore", false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,20 +1,26 @@
|
||||||
import Controller from "@ember/controller";
|
import Controller, { inject as controller } from "@ember/controller";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { isPresent } from "@ember/utils";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { A } from "@ember/array";
|
||||||
|
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
actions: {
|
adminWizardsNotices: controller(),
|
||||||
dismissNotice(noticeId) {
|
|
||||||
ajax(`/admin/wizards/notice/${this.id}`, {
|
unsubscribe() {
|
||||||
type: "DELETE",
|
this.messageBus.unsubscribe("/custom-wizard/notices");
|
||||||
})
|
},
|
||||||
.then((result) => {
|
|
||||||
if (result.success) {
|
subscribe() {
|
||||||
const notices = this.notices;
|
this.unsubscribe();
|
||||||
notices.removeObject(notices.findBy("id", noticeId));
|
this.messageBus.subscribe("/custom-wizard/notices", (data) => {
|
||||||
}
|
if (isPresent(data.active_notice_count)) {
|
||||||
})
|
this.set("activeNoticeCount", data.active_notice_count);
|
||||||
.catch(popupAjaxError);
|
this.adminWizardsNotices.setProperties({
|
||||||
},
|
notices: A(),
|
||||||
|
page: 0,
|
||||||
|
canLoadMore: true,
|
||||||
|
});
|
||||||
|
this.adminWizardsNotices.loadMoreNotices();
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -63,6 +63,11 @@ export default {
|
||||||
path: "/subscription",
|
path: "/subscription",
|
||||||
resetNamespace: true,
|
resetNamespace: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.route("adminWizardsNotices", {
|
||||||
|
path: "/notices",
|
||||||
|
resetNamespace: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
43
assets/javascripts/discourse/helpers/notice-badge.js.es6
Normale Datei
43
assets/javascripts/discourse/helpers/notice-badge.js.es6
Normale Datei
|
@ -0,0 +1,43 @@
|
||||||
|
import { autoUpdatingRelativeAge } from "discourse/lib/formatter";
|
||||||
|
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||||
|
import I18n from "I18n";
|
||||||
|
import { registerUnbound } from "discourse-common/lib/helpers";
|
||||||
|
import { htmlSafe } from "@ember/template";
|
||||||
|
|
||||||
|
registerUnbound("notice-badge", function (attrs) {
|
||||||
|
let tag = attrs.url ? "a" : "div";
|
||||||
|
let attrStr = "";
|
||||||
|
if (attrs.title) {
|
||||||
|
attrStr += `title='${I18n.t(attrs.title)}'`;
|
||||||
|
}
|
||||||
|
if (attrs.url) {
|
||||||
|
attrStr += `href='${attrs.url}'`;
|
||||||
|
}
|
||||||
|
let html = `<${tag} class="${
|
||||||
|
attrs.class ? `${attrs.class} ` : ""
|
||||||
|
}notice-badge" ${attrStr}>`;
|
||||||
|
if (attrs.icon) {
|
||||||
|
html += iconHTML(attrs.icon);
|
||||||
|
}
|
||||||
|
if (attrs.label) {
|
||||||
|
if (attrs.icon) {
|
||||||
|
html += " ";
|
||||||
|
}
|
||||||
|
html += `<span>${I18n.t(attrs.label)}</span>`;
|
||||||
|
}
|
||||||
|
if (attrs.date) {
|
||||||
|
if (attrs.icon || attrs.label) {
|
||||||
|
html += " ";
|
||||||
|
}
|
||||||
|
let dateAttrs = {};
|
||||||
|
if (attrs.leaveAgo) {
|
||||||
|
dateAttrs = {
|
||||||
|
format: "medium",
|
||||||
|
leaveAgo: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
html += autoUpdatingRelativeAge(new Date(attrs.date), dateAttrs);
|
||||||
|
}
|
||||||
|
html += `</${tag}>`;
|
||||||
|
return htmlSafe(html);
|
||||||
|
});
|
|
@ -1,6 +1,7 @@
|
||||||
import DiscourseURL from "discourse/lib/url";
|
import DiscourseURL from "discourse/lib/url";
|
||||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
import CustomWizardNotice from "../models/custom-wizard-notice";
|
import CustomWizardNotice from "../models/custom-wizard-notice";
|
||||||
|
import { isPresent } from "@ember/utils";
|
||||||
import { A } from "@ember/array";
|
import { A } from "@ember/array";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -22,35 +23,43 @@ export default {
|
||||||
|
|
||||||
withPluginApi("0.8.36", (api) => {
|
withPluginApi("0.8.36", (api) => {
|
||||||
api.modifyClass("route:admin-dashboard", {
|
api.modifyClass("route:admin-dashboard", {
|
||||||
afterModel() {
|
setupController(controller) {
|
||||||
return CustomWizardNotice.list().then((result) => {
|
this._super(...arguments);
|
||||||
if (result && result.length) {
|
|
||||||
this.set(
|
controller.loadCriticalNotices();
|
||||||
"notices",
|
controller.subscribe();
|
||||||
A(result.map((n) => CustomWizardNotice.create(n)))
|
},
|
||||||
);
|
});
|
||||||
|
|
||||||
|
api.modifyClass("controller:admin-dashboard", {
|
||||||
|
criticalNotices: A(),
|
||||||
|
|
||||||
|
unsubscribe() {
|
||||||
|
this.messageBus.unsubscribe("/custom-wizard/notices");
|
||||||
|
},
|
||||||
|
|
||||||
|
subscribe() {
|
||||||
|
this.unsubscribe();
|
||||||
|
this.messageBus.subscribe("/custom-wizard/notices", (data) => {
|
||||||
|
if (isPresent(data.active_notice_count)) {
|
||||||
|
this.loadCriticalNotices();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
setupController(controller) {
|
loadCriticalNotices() {
|
||||||
if (this.notices) {
|
CustomWizardNotice.list({
|
||||||
let pluginStatusConnectionError = this.notices.filter(
|
type: ["connection_error", "warning"],
|
||||||
(n) => n.type === "plugin_status_connection_error"
|
archetype: "plugin_status",
|
||||||
)[0];
|
visible: true,
|
||||||
let pluginStatusWarning = this.notices.filter(
|
}).then((result) => {
|
||||||
(n) => n.type === "plugin_status_warning"
|
if (result.notices && result.notices.length) {
|
||||||
)[0];
|
const criticalNotices = A(
|
||||||
|
result.notices.map((n) => CustomWizardNotice.create(n))
|
||||||
if (pluginStatusConnectionError || pluginStatusWarning) {
|
|
||||||
controller.set(
|
|
||||||
"customWizardImportantNotice",
|
|
||||||
pluginStatusConnectionError || pluginStatusWarning
|
|
||||||
);
|
);
|
||||||
|
this.set("customWizardCriticalNotices", criticalNotices);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
this._super(...arguments);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
68
assets/javascripts/discourse/mixins/notice-message.js.es6
Normale Datei
68
assets/javascripts/discourse/mixins/notice-message.js.es6
Normale Datei
|
@ -0,0 +1,68 @@
|
||||||
|
import Mixin from "@ember/object/mixin";
|
||||||
|
import { bind, scheduleOnce } from "@ember/runloop";
|
||||||
|
import { cookAsync } from "discourse/lib/text";
|
||||||
|
import { createPopper } from "@popperjs/core";
|
||||||
|
|
||||||
|
export default Mixin.create({
|
||||||
|
showCookedMessage: false,
|
||||||
|
|
||||||
|
didReceiveAttrs() {
|
||||||
|
const message = this.notice.message;
|
||||||
|
cookAsync(message).then((cooked) => {
|
||||||
|
this.set("cookedMessage", cooked);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
createMessageModal() {
|
||||||
|
let container = this.element.querySelector(".notice-message");
|
||||||
|
let modal = this.element.querySelector(".cooked-notice-message");
|
||||||
|
|
||||||
|
this._popper = createPopper(container, modal, {
|
||||||
|
strategy: "absolute",
|
||||||
|
placement: "bottom-start",
|
||||||
|
modifiers: [
|
||||||
|
{
|
||||||
|
name: "preventOverflow",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "offset",
|
||||||
|
options: {
|
||||||
|
offset: [0, 5],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
didInsertElement() {
|
||||||
|
$(document).on("click", bind(this, this.documentClick));
|
||||||
|
},
|
||||||
|
|
||||||
|
willDestroyElement() {
|
||||||
|
$(document).off("click", bind(this, this.documentClick));
|
||||||
|
},
|
||||||
|
|
||||||
|
documentClick(event) {
|
||||||
|
if (this._state === "destroying") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!event.target.closest(
|
||||||
|
`[data-notice-id="${this.notice.id}"] .notice-message`
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this.set("showCookedMessage", false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
toggleCookedMessage() {
|
||||||
|
this.toggleProperty("showCookedMessage");
|
||||||
|
|
||||||
|
if (this.showCookedMessage) {
|
||||||
|
scheduleOnce("afterRender", this, this.createMessageModal);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,12 +1,38 @@
|
||||||
import EmberObject from "@ember/object";
|
import EmberObject from "@ember/object";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
import { and, not, notEmpty } from "@ember/object/computed";
|
||||||
|
import { dasherize } from "@ember/string";
|
||||||
|
import I18n from "I18n";
|
||||||
|
|
||||||
const CustomWizardNotice = EmberObject.extend();
|
const CustomWizardNotice = EmberObject.extend({
|
||||||
|
expired: notEmpty("expired_at"),
|
||||||
|
dismissed: notEmpty("dismissed_at"),
|
||||||
|
hidden: notEmpty("hidden_at"),
|
||||||
|
notHidden: not("hidden"),
|
||||||
|
notDismissed: not("dismissed"),
|
||||||
|
canDismiss: and("dismissable", "notDismissed"),
|
||||||
|
canHide: and("can_hide", "notHidden"),
|
||||||
|
|
||||||
|
@discourseComputed("type")
|
||||||
|
typeClass(type) {
|
||||||
|
return dasherize(type);
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("type")
|
||||||
|
typeLabel(type) {
|
||||||
|
return I18n.t(`admin.wizard.notice.type.${type}`);
|
||||||
|
},
|
||||||
|
|
||||||
CustomWizardNotice.reopen({
|
|
||||||
dismiss() {
|
dismiss() {
|
||||||
return ajax(`/admin/wizards/notice/${this.id}`, { type: "PUT" })
|
if (!this.get("canDismiss")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ajax(`/admin/wizards/notice/${this.get("id")}/dismiss`, {
|
||||||
|
type: "PUT",
|
||||||
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.set("dismissed_at", result.dismissed_at);
|
this.set("dismissed_at", result.dismissed_at);
|
||||||
|
@ -14,11 +40,34 @@ CustomWizardNotice.reopen({
|
||||||
})
|
})
|
||||||
.catch(popupAjaxError);
|
.catch(popupAjaxError);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
if (!this.get("canHide")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ajax(`/admin/wizards/notice/${this.get("id")}/hide`, { type: "PUT" })
|
||||||
|
.then((result) => {
|
||||||
|
if (result.success) {
|
||||||
|
this.set("hidden_at", result.hidden_at);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(popupAjaxError);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
CustomWizardNotice.reopenClass({
|
CustomWizardNotice.reopenClass({
|
||||||
list() {
|
list(data = {}) {
|
||||||
return ajax("/admin/wizards/notice").catch(popupAjaxError);
|
return ajax("/admin/wizards/notice", {
|
||||||
|
type: "GET",
|
||||||
|
data,
|
||||||
|
}).catch(popupAjaxError);
|
||||||
|
},
|
||||||
|
|
||||||
|
dismissAll() {
|
||||||
|
return ajax("/admin/wizards/notice/dismiss", {
|
||||||
|
type: "PUT",
|
||||||
|
}).catch(popupAjaxError);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
17
assets/javascripts/discourse/routes/admin-wizards-notices.js.es6
Normale Datei
17
assets/javascripts/discourse/routes/admin-wizards-notices.js.es6
Normale Datei
|
@ -0,0 +1,17 @@
|
||||||
|
import CustomWizardNotice from "../models/custom-wizard-notice";
|
||||||
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
|
import { A } from "@ember/array";
|
||||||
|
|
||||||
|
export default DiscourseRoute.extend({
|
||||||
|
model() {
|
||||||
|
return CustomWizardNotice.list({ include_all: true });
|
||||||
|
},
|
||||||
|
|
||||||
|
setupController(controller, model) {
|
||||||
|
controller.setProperties({
|
||||||
|
notices: A(
|
||||||
|
model.notices.map((notice) => CustomWizardNotice.create(notice))
|
||||||
|
),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,6 +1,5 @@
|
||||||
import DiscourseRoute from "discourse/routes/discourse";
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
import { A } from "@ember/array";
|
|
||||||
|
|
||||||
export default DiscourseRoute.extend({
|
export default DiscourseRoute.extend({
|
||||||
model() {
|
model() {
|
||||||
|
@ -8,8 +7,16 @@ export default DiscourseRoute.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
setupController(controller, model) {
|
setupController(controller, model) {
|
||||||
controller.set("notices", A(model.notices));
|
|
||||||
controller.set("api_section", model.api_section);
|
controller.set("api_section", model.api_section);
|
||||||
|
|
||||||
|
if (model.active_notice_count) {
|
||||||
|
controller.set("activeNoticeCount", model.active_notice_count);
|
||||||
|
}
|
||||||
|
if (model.featured_notices) {
|
||||||
|
controller.set("featuredNotices", model.featured_notices);
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.subscribe();
|
||||||
},
|
},
|
||||||
|
|
||||||
afterModel(model, transition) {
|
afterModel(model, transition) {
|
||||||
|
|
49
assets/javascripts/discourse/templates/admin-wizards-notices.hbs
Normale Datei
49
assets/javascripts/discourse/templates/admin-wizards-notices.hbs
Normale Datei
|
@ -0,0 +1,49 @@
|
||||||
|
<div class="admin-wizard-controls">
|
||||||
|
<h3>{{i18n "admin.wizard.notices.title"}}</h3>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
{{d-button
|
||||||
|
label="admin.wizard.notice.dismiss_all.label"
|
||||||
|
title="admin.wizard.notice.dismiss_all.title"
|
||||||
|
action=(action "dismissAll")
|
||||||
|
disabled=allDismisssed
|
||||||
|
icon="check"}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{wizard-message
|
||||||
|
key=messageKey
|
||||||
|
url=messageUrl
|
||||||
|
type=messageType
|
||||||
|
opts=messageOpts
|
||||||
|
items=messageItems
|
||||||
|
loading=loading
|
||||||
|
component="notices"}}
|
||||||
|
|
||||||
|
<div class="wizard-table">
|
||||||
|
{{#load-more selector=".wizard-table tr" action=(action "loadMore")}}
|
||||||
|
{{#if hasNotices}}
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{I18n "admin.wizard.notice.time"}}</th>
|
||||||
|
<th>{{I18n "admin.wizard.notice.type.label"}}</th>
|
||||||
|
<th>{{I18n "admin.wizard.notice.title"}}</th>
|
||||||
|
<th>{{I18n "admin.wizard.notice.status"}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#each notices as |notice|}}
|
||||||
|
{{wizard-notice-row notice=notice}}
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{else}}
|
||||||
|
{{#unless loadingMore}}
|
||||||
|
<p>{{i18n "search.no_results"}}</p>
|
||||||
|
{{/unless}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{conditional-loading-spinner condition=loadingMore}}
|
||||||
|
{{/load-more}}
|
||||||
|
</div>
|
|
@ -10,6 +10,12 @@
|
||||||
{{nav-item route="adminWizardsSubscription" label="admin.wizard.subscription.nav_label"}}
|
{{nav-item route="adminWizardsSubscription" label="admin.wizard.subscription.nav_label"}}
|
||||||
|
|
||||||
<div class="admin-actions">
|
<div class="admin-actions">
|
||||||
|
<div class="wizard-notices-link">
|
||||||
|
{{nav-item tagName="div" route="adminWizardsNotices" label="admin.wizard.notices.nav_label"}}
|
||||||
|
{{#if activeNoticeCount}}
|
||||||
|
<span class="active-notice-count">{{activeNoticeCount}}</span>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
<a target="_blank" class="btn btn-pavilion-support" rel="noreferrer noopener" href="https://thepavilion.io/w/support" title={{i18n "admin.wizard.support_button.title"}}>
|
<a target="_blank" class="btn btn-pavilion-support" rel="noreferrer noopener" href="https://thepavilion.io/w/support" title={{i18n "admin.wizard.support_button.title"}}>
|
||||||
{{d-icon "far-life-ring"}}{{i18n "admin.wizard.support_button.label"}}
|
{{d-icon "far-life-ring"}}{{i18n "admin.wizard.support_button.label"}}
|
||||||
</a>
|
</a>
|
||||||
|
@ -17,8 +23,5 @@
|
||||||
{{/admin-nav}}
|
{{/admin-nav}}
|
||||||
|
|
||||||
<div class="admin-container">
|
<div class="admin-container">
|
||||||
{{#each notices as |notice|}}
|
|
||||||
{{wizard-notice notice=notice}}
|
|
||||||
{{/each}}
|
|
||||||
{{outlet}}
|
{{outlet}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
<td class="small">
|
||||||
|
{{#if notice.updated_at}}
|
||||||
|
{{notice-badge class="notice-updated-at" date=notice.updated_at label="admin.wizard.notice.updated_at" leaveAgo=true}}
|
||||||
|
{{else}}
|
||||||
|
{{notice-badge class="notice-created-at" date=notice.created_at label="admin.wizard.notice.created_at" leaveAgo=true}}
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
<td>{{notice.typeLabel}}</td>
|
||||||
|
<td class="notice-message">
|
||||||
|
<a role="button" {{action "toggleCookedMessage"}} class="show-notice-message">{{notice.title}}</a>
|
||||||
|
{{#if showCookedMessage}}
|
||||||
|
<span class="cooked-notice-message">{{cookedMessage}}</span>
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{#if notice.canDismiss}}
|
||||||
|
{{d-button
|
||||||
|
action="dismiss"
|
||||||
|
label="admin.wizard.notice.dismiss.label"
|
||||||
|
title="admin.wizard.notice.dismiss.title"
|
||||||
|
class="btn-dismiss"
|
||||||
|
icon="check"}}
|
||||||
|
{{else if notice.dismissed}}
|
||||||
|
<span>{{i18n "admin.wizard.notice.dismissed_at"}} {{format-date notice.dismissed_at leaveAgo="true"}}</span>
|
||||||
|
{{else if notice.expired}}
|
||||||
|
<span>{{i18n "admin.wizard.notice.expired_at"}} {{format-date notice.expired_at leaveAgo="true"}}</span>
|
||||||
|
{{else}}
|
||||||
|
<span>{{i18n "admin.wizard.notice.active"}}</span>
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
|
@ -1,51 +1,39 @@
|
||||||
<div class="notice-header">
|
<div class="notice-header">
|
||||||
{{#if resolved}}
|
<div class="notice-title notice-badge notice-message">
|
||||||
<div class="notice-expired-at notice-badge" title={{notice.expired_at}}>
|
<a role="button" {{action "toggleCookedMessage"}} class="show-notice-message">{{notice.title}}</a>
|
||||||
{{d-icon "check"}}
|
{{#if showCookedMessage}}
|
||||||
<span class="notice-resolved">{{i18n "admin.wizard.notice.resolved"}}</span>
|
<span class="cooked-notice-message">{{cookedMessage}}</span>
|
||||||
{{format-date notice.expired_at leaveAgo="true"}}
|
{{/if}}
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<div class="notice-title notice-badge" title={{title}}>
|
|
||||||
{{d-icon icon}}
|
|
||||||
<span>{{title}}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="notice-header-right">
|
||||||
|
{{#if notice.expired}}
|
||||||
|
{{notice-badge class="notice-expired-at" icon="check" label="admin.wizard.notice.expired_at" date=notice.expired_at}}
|
||||||
|
{{/if}}
|
||||||
|
{{#if showPlugin}}
|
||||||
|
{{notice-badge class="notice-plugin" icon="plug" title="admin.wizard.notice.plugin" label="admin.wizard.notice.plugin" url="/admin/wizards/notices"}}
|
||||||
|
{{/if}}
|
||||||
|
{{notice-badge class="notice-created-at" icon="far-clock" label="admin.wizard.notice.created_at" date=notice.created_at leaveAgo=true}}
|
||||||
|
{{#if notice.updated_at}}
|
||||||
|
{{notice-badge class="notice-updated-at" icon="far-clock" label="admin.wizard.notice.updated_at" date=notice.updated_at}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
<div class="notice-created-at notice-badge" title={{notice.created_at}}>
|
{{#if notice.canDismiss}}
|
||||||
{{d-icon "far-clock"}}
|
<div class="dismiss-notice-container">
|
||||||
<span class="notice-issued">{{i18n "admin.wizard.notice.issued"}}</span>
|
{{#if dismissing}}
|
||||||
{{format-date notice.created_at leaveAgo="true"}}
|
{{loading-spinner size="small"}}
|
||||||
</div>
|
{{else}}
|
||||||
|
<a {{action "dismiss"}} role="button" class="dismiss-notice">{{d-icon "times"}}</a>
|
||||||
{{#if notice.updated_at}}
|
{{/if}}
|
||||||
<div class="notice-updated-at notice-badge" title={{notice.updated_at}}>
|
</div>
|
||||||
{{d-icon "calendar-alt"}}
|
{{/if}}
|
||||||
<span class="notice-updated">{{i18n "admin.wizard.notice.updated"}}</span>
|
{{#if notice.canHide}}
|
||||||
{{format-date notice.updated_at leaveAgo="true"}}
|
<div class="hide-notice-container">
|
||||||
</div>
|
{{#if hiding}}
|
||||||
{{/if}}
|
{{loading-spinner size="small"}}
|
||||||
|
{{else}}
|
||||||
<div class="notice-plugin notice-badge" title={{i18n "admin.wizard.notice.plugin"}}>
|
<a {{action "hide"}} role="button" class="hide-notice">{{d-icon "far-eye-slash"}}</a>
|
||||||
{{d-icon "plug"}}
|
{{/if}}
|
||||||
<span>{{i18n "admin.wizard.notice.plugin"}}</span>
|
</div>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="notice-message">
|
|
||||||
{{html-safe notice.message}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#if importantOnDashboard}}
|
|
||||||
<a href="/admin/site_settings/category/all_results?filter=wizard_important_notices_on_dashboard" class="disable-important">
|
|
||||||
{{i18n "admin.wizard.notice.disable_important_on_dashboard"}}
|
|
||||||
</a>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if canDismiss}}
|
|
||||||
{{#if dismissing}}
|
|
||||||
{{loading-spinner size="small"}}
|
|
||||||
{{else}}
|
|
||||||
<a {{action "dismiss"}} role="button" class="dismiss-notice">{{d-icon "times"}}</a>
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
|
|
|
@ -4,15 +4,25 @@
|
||||||
@import "common/components/buttons";
|
@import "common/components/buttons";
|
||||||
@import "wizard/variables";
|
@import "wizard/variables";
|
||||||
|
|
||||||
|
$expired: #339b18;
|
||||||
|
$info: #038ae7;
|
||||||
|
$warning: #d47e00;
|
||||||
|
$error: #ef1700;
|
||||||
|
|
||||||
.admin-wizard-controls {
|
.admin-wizard-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
min-height: 34px;
|
||||||
|
|
||||||
& + .wizard-message + div {
|
& + .wizard-message + div {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.wizard-message {
|
.wizard-message {
|
||||||
|
@ -909,87 +919,142 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-wizards-notices {
|
||||||
|
.wizard-table {
|
||||||
|
overflow: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.wizard-notice {
|
.wizard-notice {
|
||||||
padding: 1em;
|
padding: 0.75em;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
border: 1px solid var(--primary);
|
border: 1px solid var(--primary-low);
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&.dismissed {
|
&.dismissed {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.resolved .notice-badge:not(.notice-expired-at),
|
&.expired .notice-badge:not(.notice-expired-at),
|
||||||
&.resolved a,
|
&.expired a,
|
||||||
&.resolved p {
|
&.expired p {
|
||||||
color: var(--primary-medium) !important;
|
color: var(--primary-medium) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.d-icon {
|
.notice-badge {
|
||||||
margin-right: 0.4em;
|
padding: 0 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-header {
|
.notice-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
|
||||||
|
|
||||||
.notice-badge {
|
|
||||||
border: 1px solid var(--primary);
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 0.5em;
|
|
||||||
margin-right: 1em;
|
|
||||||
font-size: 0.9em;
|
|
||||||
line-height: 25px;
|
|
||||||
min-height: 25px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
&:last-of-type {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.warning {
|
|
||||||
.notice-expired-at {
|
|
||||||
border: 1px solid var(--success);
|
|
||||||
background-color: rgba($success, 0.1);
|
|
||||||
color: var(--success);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notice-title {
|
.notice-title {
|
||||||
border: 1px solid var(--pavilion-warning);
|
padding: 0;
|
||||||
background-color: rgba($pavilion_warning, 0.1);
|
}
|
||||||
color: var(--pavilion-warning);
|
|
||||||
|
.notice-header-right {
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.notice-badge {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-issued,
|
.dismiss-notice-container,
|
||||||
.notice-resolved {
|
.hide-notice-container {
|
||||||
margin-right: 0.3em;
|
width: 40px;
|
||||||
}
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
.notice-message {
|
align-items: center;
|
||||||
p {
|
|
||||||
margin: 0.5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p:last-of-type {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dismiss-notice,
|
.dismiss-notice,
|
||||||
.spinner {
|
.hide-notice {
|
||||||
position: absolute;
|
display: flex;
|
||||||
top: 1em;
|
align-items: center;
|
||||||
right: 1em;
|
|
||||||
color: var(--primary-medium);
|
|
||||||
}
|
|
||||||
|
|
||||||
.disable-important {
|
.d-icon {
|
||||||
position: absolute;
|
margin-right: 0;
|
||||||
right: 3em;
|
color: var(--primary);
|
||||||
top: 1em;
|
}
|
||||||
color: var(--primary-medium);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 25px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: var(--primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wizard-notices-link {
|
||||||
|
position: relative;
|
||||||
|
margin-right: 10px;
|
||||||
|
|
||||||
|
div > a {
|
||||||
|
@include btn;
|
||||||
|
color: var(--secondary) !important;
|
||||||
|
background-color: var(--primary-medium);
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: var(--tertiary) !important;
|
||||||
|
color: var(--secondary) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-notice-count {
|
||||||
|
background-color: $danger;
|
||||||
|
color: $secondary;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
top: -8px;
|
||||||
|
right: -8px;
|
||||||
|
font-size: 0.7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.show-notice-message {
|
||||||
|
padding: 0.25em 0.5em;
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wizard-notice,
|
||||||
|
.wizard-notice-row:not(.expired):not(.dismissed) {
|
||||||
|
&.info {
|
||||||
|
background-color: rgba($info, 0.1);
|
||||||
|
border: 1px solid rgba($info, 0.5);
|
||||||
|
}
|
||||||
|
&.warning,
|
||||||
|
&.connection-error {
|
||||||
|
background-color: rgba($warning, 0.1);
|
||||||
|
border: 1px solid rgba($warning, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-message {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.cooked-notice-message {
|
||||||
|
background-color: var(--secondary);
|
||||||
|
padding: 1em;
|
||||||
|
z-index: 1;
|
||||||
|
box-shadow: shadow("dropdown");
|
||||||
|
border-top: 1px solid var(--primary-low);
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,6 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
|
|
@ -112,6 +112,9 @@ en:
|
||||||
select: "Select a wizard to see its logs"
|
select: "Select a wizard to see its logs"
|
||||||
viewing: "View recent logs for wizards on the forum"
|
viewing: "View recent logs for wizards on the forum"
|
||||||
documentation: "Check out the logs documentation"
|
documentation: "Check out the logs documentation"
|
||||||
|
notices:
|
||||||
|
info: "Plugin status and subscription notices"
|
||||||
|
documentation: Check out the notices documentation
|
||||||
|
|
||||||
editor:
|
editor:
|
||||||
show: "Show"
|
show: "Show"
|
||||||
|
@ -485,14 +488,31 @@ en:
|
||||||
|
|
||||||
notice:
|
notice:
|
||||||
plugin: Custom Wizard Plugin
|
plugin: Custom Wizard Plugin
|
||||||
issued: Issued
|
message: Message
|
||||||
update: Updated
|
time: Time
|
||||||
resolved: Resolved
|
status: Status
|
||||||
title:
|
title: Title
|
||||||
plugin_status_warning: Warning Notice
|
dismiss:
|
||||||
plugin_status_connection_error: Connection Notice
|
label: Dismiss
|
||||||
subscription_messages_connection_error: Connection Notice
|
title: Dismiss notice
|
||||||
disable_important_on_dashboard: disable
|
dismiss_all:
|
||||||
|
label: Dismiss All
|
||||||
|
title: Dismiss all informational Custom Wizard notices
|
||||||
|
confirm: Are you sure you want to dismiss all informational Custom Wizard notices?
|
||||||
|
active: active
|
||||||
|
created_at: issued
|
||||||
|
updated_at: updated
|
||||||
|
expired_at: expired
|
||||||
|
dismissed_at: dismissed
|
||||||
|
type:
|
||||||
|
label: Type
|
||||||
|
info: Information
|
||||||
|
warning: Warning
|
||||||
|
connection_error: Connection Error
|
||||||
|
|
||||||
|
notices:
|
||||||
|
nav_label: Notices
|
||||||
|
title: Plugin Notices
|
||||||
|
|
||||||
wizard_js:
|
wizard_js:
|
||||||
group:
|
group:
|
||||||
|
|
|
@ -54,21 +54,21 @@ en:
|
||||||
|
|
||||||
notice:
|
notice:
|
||||||
connection_error: "Failed to connect to http://%{domain}"
|
connection_error: "Failed to connect to http://%{domain}"
|
||||||
compatibility_issue: >
|
compatibility_issue:
|
||||||
The Custom Wizard Plugin has a compatibility issue with the latest version of Discourse.
|
title: The Custom Wizard Plugin is incompatibile with the latest version of Discourse.
|
||||||
Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse.
|
message: Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse.
|
||||||
plugin_status:
|
plugin_status:
|
||||||
connection_error_limit: >
|
connection_error:
|
||||||
We're unable to connect to the Pavilion Plugin Status Server. Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse or the plugin.
|
title: Unable to connect to the Custom Wizard Plugin status server
|
||||||
If this connection issue persists please contact <a href="mailto:support@thepavilion.io">support@thepavilion.io</a> for further assistance.
|
message: Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse. If this issue persists contact <a href="mailto:support@thepavilion.io">support@thepavilion.io</a> for further assistance.
|
||||||
subscription_messages:
|
subscription_message:
|
||||||
connection_error_limit: >
|
connection_error:
|
||||||
We're unable to connect to the Pavilion Subscription Server. This will not affect the operation of the plugin.
|
title: Unable to connect to the Custom Wizard Plugin subscription server
|
||||||
If this connection issue persists please contact <a href="mailto:support@thepavilion.io">support@thepavilion.io</a> for further assistance.
|
message: If this issue persists contact <a href="mailto:support@thepavilion.io">support@thepavilion.io</a> for further assistance.
|
||||||
|
|
||||||
site_settings:
|
site_settings:
|
||||||
custom_wizard_enabled: "Enable custom wizards."
|
custom_wizard_enabled: "Enable custom wizards."
|
||||||
wizard_redirect_exclude_paths: "Routes excluded from wizard redirects."
|
wizard_redirect_exclude_paths: "Routes excluded from wizard redirects."
|
||||||
wizard_recognised_image_upload_formats: "File types which will result in upload displaying an image preview"
|
wizard_recognised_image_upload_formats: "File types which will result in upload displaying an image preview"
|
||||||
wizard_apis_enabled: "Enable API features (experimental)."
|
wizard_apis_enabled: "Enable API features (experimental)."
|
||||||
wizard_important_notices_on_dashboard: "Show important notices about the custom wizard plugin on the admin dashboard."
|
wizard_critical_notices_on_dashboard: "Show critical notices about the custom wizard plugin on the admin dashboard."
|
||||||
|
|
|
@ -52,6 +52,9 @@ Discourse::Application.routes.append do
|
||||||
delete 'admin/wizards/subscription/authorize' => 'admin_subscription#destroy_authentication'
|
delete 'admin/wizards/subscription/authorize' => 'admin_subscription#destroy_authentication'
|
||||||
|
|
||||||
get 'admin/wizards/notice' => 'admin_notice#index'
|
get 'admin/wizards/notice' => 'admin_notice#index'
|
||||||
put 'admin/wizards/notice/:notice_id' => 'admin_notice#dismiss'
|
put 'admin/wizards/notice/:notice_id/dismiss' => 'admin_notice#dismiss'
|
||||||
|
put 'admin/wizards/notice/:notice_id/hide' => 'admin_notice#hide'
|
||||||
|
put 'admin/wizards/notice/dismiss' => 'admin_notice#dismiss_all'
|
||||||
|
get 'admin/wizards/notices' => 'admin_notice#index'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,8 +6,12 @@ class CustomWizard::AdminController < ::Admin::AdminController
|
||||||
render_json_dump(
|
render_json_dump(
|
||||||
#TODO replace with appropriate static?
|
#TODO replace with appropriate static?
|
||||||
api_section: ["business"].include?(CustomWizard::Subscription.type),
|
api_section: ["business"].include?(CustomWizard::Subscription.type),
|
||||||
notices: ActiveModel::ArraySerializer.new(
|
active_notice_count: CustomWizard::Notice.active_count,
|
||||||
CustomWizard::Notice.list,
|
featured_notices: ActiveModel::ArraySerializer.new(
|
||||||
|
CustomWizard::Notice.list(
|
||||||
|
type: CustomWizard::Notice.types[:info],
|
||||||
|
archetype: CustomWizard::Notice.archetypes[:subscription_message]
|
||||||
|
),
|
||||||
each_serializer: CustomWizard::NoticeSerializer
|
each_serializer: CustomWizard::NoticeSerializer
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,20 +1,66 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class CustomWizard::AdminNoticeController < CustomWizard::AdminController
|
class CustomWizard::AdminNoticeController < CustomWizard::AdminController
|
||||||
before_action :find_notice, only: [:dismiss]
|
before_action :find_notice, only: [:dismiss, :hide]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render_serialized(CustomWizard::Notice.list(include_recently_expired: true), CustomWizard::NoticeSerializer)
|
type = params[:type]
|
||||||
|
archetype = params[:archtype]
|
||||||
|
page = params[:page].to_i
|
||||||
|
include_all = ActiveRecord::Type::Boolean.new.cast(params[:include_all])
|
||||||
|
visible = ActiveRecord::Type::Boolean.new.cast(params[:visible])
|
||||||
|
|
||||||
|
if type
|
||||||
|
if type.is_a?(Array)
|
||||||
|
type = type.map { |t| CustomWizard::Notice.types[t.to_sym] }
|
||||||
|
else
|
||||||
|
type = CustomWizard::Notice.types[type.to_sym]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if archetype
|
||||||
|
if archetype.is_a?(Array)
|
||||||
|
archetype = archetype.map { |t| CustomWizard::Notice.archetypes[archetype.to_sym] }
|
||||||
|
else
|
||||||
|
archetype = CustomWizard::Notice.archetypes[archetype.to_sym]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
notices = CustomWizard::Notice.list(
|
||||||
|
include_all: include_all,
|
||||||
|
page: page,
|
||||||
|
type: type,
|
||||||
|
archetype: archetype,
|
||||||
|
visible: visible
|
||||||
|
)
|
||||||
|
|
||||||
|
render_serialized(notices, CustomWizard::NoticeSerializer, root: :notices)
|
||||||
end
|
end
|
||||||
|
|
||||||
def dismiss
|
def dismiss
|
||||||
if @notice.dismissable? && @notice.dismiss
|
if @notice.dismissable? && @notice.dismiss!
|
||||||
render json: success_json.merge(dismissed_at: @notice.dismissed_at)
|
render json: success_json.merge(dismissed_at: @notice.dismissed_at)
|
||||||
else
|
else
|
||||||
render json: failed_json
|
render json: failed_json
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def hide
|
||||||
|
if @notice.can_hide? && @notice.hide!
|
||||||
|
render json: success_json.merge(hidden_at: @notice.hidden_at)
|
||||||
|
else
|
||||||
|
render json: failed_json
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def dismiss_all
|
||||||
|
if CustomWizard::Notice.dismiss_all
|
||||||
|
render json: success_json
|
||||||
|
else
|
||||||
|
render json: failed_json
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def find_notice
|
def find_notice
|
||||||
@notice = CustomWizard::Notice.find(params[:notice_id])
|
@notice = CustomWizard::Notice.find(params[:notice_id])
|
||||||
raise Discourse::InvalidParameters.new(:notice_id) unless @notice
|
raise Discourse::InvalidParameters.new(:notice_id) unless @notice
|
||||||
|
|
|
@ -7,42 +7,67 @@ class CustomWizard::Notice
|
||||||
"tests-passed" => "plugins.discourse.pavilion.tech",
|
"tests-passed" => "plugins.discourse.pavilion.tech",
|
||||||
"stable" => "stable.plugins.discourse.pavilion.tech"
|
"stable" => "stable.plugins.discourse.pavilion.tech"
|
||||||
}
|
}
|
||||||
SUBSCRIPTION_MESSAGES_DOMAIN = "test.thepavilion.io"
|
SUBSCRIPTION_MESSAGE_DOMAIN = "test.thepavilion.io"
|
||||||
LOCALHOST_DOMAIN = "localhost:3000"
|
LOCALHOST_DOMAIN = "localhost:3000"
|
||||||
PLUGIN_STATUSES_TO_WARN = %w(incompatible tests_failing)
|
PLUGIN_STATUSES_TO_WARN = %w(incompatible tests_failing)
|
||||||
CHECK_PLUGIN_STATUS_ON_BRANCH = %w(tests-passed main stable)
|
CHECK_PLUGIN_STATUS_ON_BRANCH = %w(tests-passed main stable)
|
||||||
|
PAGE_LIMIT = 30
|
||||||
|
|
||||||
attr_reader :id,
|
attr_reader :id,
|
||||||
|
:title,
|
||||||
:message,
|
:message,
|
||||||
:type,
|
:type,
|
||||||
|
:archetype,
|
||||||
:created_at
|
:created_at
|
||||||
|
|
||||||
attr_accessor :retrieved_at,
|
attr_accessor :retrieved_at,
|
||||||
:updated_at,
|
:updated_at,
|
||||||
:dismissed_at,
|
:dismissed_at,
|
||||||
:expired_at
|
:expired_at,
|
||||||
|
:hidden_at
|
||||||
|
|
||||||
def initialize(attrs)
|
def initialize(attrs)
|
||||||
@id = Digest::SHA1.hexdigest(attrs[:message])
|
@id = self.class.generate_notice_id(attrs[:title], attrs[:created_at])
|
||||||
|
@title = attrs[:title]
|
||||||
@message = attrs[:message]
|
@message = attrs[:message]
|
||||||
@type = attrs[:type].to_i
|
@type = attrs[:type].to_i
|
||||||
|
@archetype = attrs[:archetype].to_i
|
||||||
@created_at = attrs[:created_at]
|
@created_at = attrs[:created_at]
|
||||||
@updated_at = attrs[:updated_at]
|
@updated_at = attrs[:updated_at]
|
||||||
@retrieved_at = attrs[:retrieved_at]
|
@retrieved_at = attrs[:retrieved_at]
|
||||||
@dismissed_at = attrs[:dismissed_at]
|
@dismissed_at = attrs[:dismissed_at]
|
||||||
@expired_at = attrs[:expired_at]
|
@expired_at = attrs[:expired_at]
|
||||||
|
@hidden_at = attrs[:hidden_at]
|
||||||
end
|
end
|
||||||
|
|
||||||
def dismiss
|
def dismiss!
|
||||||
if dismissable?
|
if dismissable?
|
||||||
self.dismissed_at = Time.now
|
self.dismissed_at = Time.now
|
||||||
self.save
|
self.save_and_publish
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def expire
|
def hide!
|
||||||
self.expired_at = Time.now
|
if can_hide?
|
||||||
self.save
|
self.hidden_at = Time.now
|
||||||
|
self.save_and_publish
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def expire!
|
||||||
|
if !expired?
|
||||||
|
self.expired_at = Time.now
|
||||||
|
self.save_and_publish
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def save_and_publish
|
||||||
|
if self.save
|
||||||
|
self.class.publish_notice_count
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def expired?
|
def expired?
|
||||||
|
@ -54,19 +79,37 @@ class CustomWizard::Notice
|
||||||
end
|
end
|
||||||
|
|
||||||
def dismissable?
|
def dismissable?
|
||||||
true
|
!expired? && !dismissed? && type === self.class.types[:info]
|
||||||
|
end
|
||||||
|
|
||||||
|
def hidden?
|
||||||
|
hidden_at.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def can_hide?
|
||||||
|
!hidden? && (
|
||||||
|
type === self.class.types[:connection_error] ||
|
||||||
|
type === self.class.types[:warning]
|
||||||
|
) && (
|
||||||
|
archetype === self.class.archetypes[:plugin_status]
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def save
|
def save
|
||||||
attrs = {
|
attrs = {
|
||||||
expired_at: expired_at,
|
expired_at: expired_at,
|
||||||
|
updated_at: updated_at,
|
||||||
|
retrieved_at: retrieved_at,
|
||||||
created_at: created_at,
|
created_at: created_at,
|
||||||
|
title: title,
|
||||||
message: message,
|
message: message,
|
||||||
type: type
|
type: type,
|
||||||
|
archetype: archetype
|
||||||
}
|
}
|
||||||
|
|
||||||
if current = self.class.find(self.id)
|
if current = self.class.find(self.id)
|
||||||
attrs[:dismissed_at] = current.dismissed_at || self.dismissed_at
|
attrs[:dismissed_at] = current.dismissed_at || self.dismissed_at
|
||||||
|
attrs[:hidden_at] = current.hidden_at || self.hidden_at
|
||||||
end
|
end
|
||||||
|
|
||||||
self.class.store(id, attrs)
|
self.class.store(id, attrs)
|
||||||
|
@ -75,9 +118,15 @@ class CustomWizard::Notice
|
||||||
def self.types
|
def self.types
|
||||||
@types ||= Enum.new(
|
@types ||= Enum.new(
|
||||||
info: 0,
|
info: 0,
|
||||||
plugin_status_warning: 1,
|
warning: 1,
|
||||||
plugin_status_connection_error: 2,
|
connection_error: 2
|
||||||
subscription_messages_connection_error: 3
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.archetypes
|
||||||
|
@archetypes ||= Enum.new(
|
||||||
|
subscription_message: 0,
|
||||||
|
plugin_status: 1
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -85,7 +134,7 @@ class CustomWizard::Notice
|
||||||
notices = []
|
notices = []
|
||||||
|
|
||||||
if !skip_subscription
|
if !skip_subscription
|
||||||
subscription_messages = request(:subscription_messages)
|
subscription_messages = request(:subscription_message)
|
||||||
|
|
||||||
if subscription_messages.present?
|
if subscription_messages.present?
|
||||||
subscription_notices = convert_subscription_messages_to_notices(subscription_messages[:messages])
|
subscription_notices = convert_subscription_messages_to_notices(subscription_messages[:messages])
|
||||||
|
@ -96,27 +145,43 @@ class CustomWizard::Notice
|
||||||
if !skip_plugin && request_plugin_status?
|
if !skip_plugin && request_plugin_status?
|
||||||
plugin_status = request(:plugin_status)
|
plugin_status = request(:plugin_status)
|
||||||
|
|
||||||
if plugin_status.present? && plugin_status[:status].present? && plugin_status[:status].is_a?(Hash)
|
if plugin_status.present? && plugin_status[:status].present?
|
||||||
plugin_notice = convert_plugin_status_to_notice(plugin_status[:status])
|
plugin_notice = convert_plugin_status_to_notice(plugin_status)
|
||||||
notices.push(plugin_notice) if plugin_notice
|
notices.push(plugin_notice) if plugin_notice
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
notices.each do |notice_data|
|
if notices.any?
|
||||||
notice = new(notice_data)
|
notices.each do |notice_data|
|
||||||
notice.retrieved_at = Time.now
|
notice = new(notice_data)
|
||||||
notice.save
|
notice.retrieved_at = Time.now
|
||||||
|
notice.save
|
||||||
|
end
|
||||||
|
|
||||||
|
publish_notice_count
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.publish_notice_count
|
||||||
|
payload = {
|
||||||
|
active_notice_count: CustomWizard::Notice.active_count
|
||||||
|
}
|
||||||
|
MessageBus.publish("/custom-wizard/notices", payload, group_ids: [Group::AUTO_GROUPS[:admins]])
|
||||||
|
end
|
||||||
|
|
||||||
def self.convert_subscription_messages_to_notices(messages)
|
def self.convert_subscription_messages_to_notices(messages)
|
||||||
messages.map do |message|
|
messages.reduce([]) do |result, message|
|
||||||
{
|
id = generate_notice_id(message[:title], message[:created_at])
|
||||||
|
result.push(
|
||||||
|
id: id,
|
||||||
|
title: message[:title],
|
||||||
message: message[:message],
|
message: message[:message],
|
||||||
type: types[message[:type].to_sym],
|
type: types[message[:type].to_sym],
|
||||||
|
archetype: archetypes[:subscription_message],
|
||||||
created_at: message[:created_at],
|
created_at: message[:created_at],
|
||||||
expired_at: message[:expired_at]
|
expired_at: message[:expired_at]
|
||||||
}
|
)
|
||||||
|
result
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -124,22 +189,32 @@ class CustomWizard::Notice
|
||||||
notice = nil
|
notice = nil
|
||||||
|
|
||||||
if PLUGIN_STATUSES_TO_WARN.include?(plugin_status[:status])
|
if PLUGIN_STATUSES_TO_WARN.include?(plugin_status[:status])
|
||||||
notice = {
|
title = I18n.t('wizard.notice.compatibility_issue.title')
|
||||||
message: I18n.t('wizard.notice.compatibility_issue', domain: plugin_status_domain),
|
created_at = plugin_status[:status_changed_at]
|
||||||
type: types[:plugin_status_warning],
|
id = generate_notice_id(title, created_at)
|
||||||
created_at: plugin_status[:status_changed_at]
|
|
||||||
}
|
unless exists?(id)
|
||||||
|
message = I18n.t('wizard.notice.compatibility_issue.message', domain: plugin_status_domain)
|
||||||
|
notice = {
|
||||||
|
id: id,
|
||||||
|
title: title,
|
||||||
|
message: message,
|
||||||
|
type: types[:warning],
|
||||||
|
archetype: archetypes[:plugin_status],
|
||||||
|
created_at: created_at
|
||||||
|
}
|
||||||
|
end
|
||||||
else
|
else
|
||||||
expire_notices(types[:plugin_status_warning])
|
expire_all(types[:warning], archetypes[:plugin_status])
|
||||||
end
|
end
|
||||||
|
|
||||||
notice
|
notice
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.notify_connection_errors(connection_type_key)
|
def self.notify_connection_errors(archetype)
|
||||||
domain = self.send("#{connection_type_key.to_s}_domain")
|
domain = self.send("#{archetype.to_s}_domain")
|
||||||
message = I18n.t("wizard.notice.#{connection_type_key.to_s}.connection_error_limit", domain: domain)
|
title = I18n.t("wizard.notice.#{archetype.to_s}.connection_error.title")
|
||||||
notices = list(type: types[:connection_error], message: message)
|
notices = list(type: types[:connection_error], archetype: archetypes[archetype.to_sym], title: title)
|
||||||
|
|
||||||
if notices.any?
|
if notices.any?
|
||||||
notice = notices.first
|
notice = notices.first
|
||||||
|
@ -147,28 +222,29 @@ class CustomWizard::Notice
|
||||||
notice.save
|
notice.save
|
||||||
else
|
else
|
||||||
notice = new(
|
notice = new(
|
||||||
message: message,
|
title: title,
|
||||||
type: types["#{connection_type_key}_connection_error".to_sym],
|
message: I18n.t("wizard.notice.#{archetype.to_s}.connection_error.message", domain: domain),
|
||||||
created_at: Time.now
|
archetype: archetypes[archetype.to_sym],
|
||||||
|
type: types[:connection_error],
|
||||||
|
created_at: Time.now,
|
||||||
|
updated_at: Time.now
|
||||||
)
|
)
|
||||||
notice.save
|
notice.save
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def self.expire_notices(type)
|
publish_notice_count
|
||||||
list(type: type).each(&:expire)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.request_plugin_status?
|
def self.request_plugin_status?
|
||||||
CHECK_PLUGIN_STATUS_ON_BRANCH.include?(Discourse.git_branch) || Rails.env.test? || Rails.env.development?
|
CHECK_PLUGIN_STATUS_ON_BRANCH.include?(Discourse.git_branch) || Rails.env.test? || Rails.env.development?
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.subscription_messages_domain
|
def self.subscription_message_domain
|
||||||
(Rails.env.test? || Rails.env.development?) ? LOCALHOST_DOMAIN : SUBSCRIPTION_MESSAGES_DOMAIN
|
(Rails.env.test? || Rails.env.development?) ? LOCALHOST_DOMAIN : SUBSCRIPTION_MESSAGE_DOMAIN
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.subscription_messages_url
|
def self.subscription_message_url
|
||||||
"http://#{subscription_messages_domain}/subscription-server/messages.json"
|
"http://#{subscription_message_domain}/subscription-server/messages.json"
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.plugin_status_domain
|
def self.plugin_status_domain
|
||||||
|
@ -180,14 +256,19 @@ class CustomWizard::Notice
|
||||||
"http://#{plugin_status_domain}/plugin-manager/status/discourse-custom-wizard"
|
"http://#{plugin_status_domain}/plugin-manager/status/discourse-custom-wizard"
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.request(type)
|
def self.request(archetype)
|
||||||
url = self.send("#{type.to_s}_url")
|
url = self.send("#{archetype.to_s}_url")
|
||||||
response = Excon.get(url)
|
|
||||||
connection_error = CustomWizard::Notice::ConnectionError.new(type)
|
|
||||||
|
|
||||||
if response.status == 200
|
begin
|
||||||
|
response = Excon.get(url)
|
||||||
|
rescue Excon::Error::Socket, Excon::Error::Timeout => e
|
||||||
|
response = nil
|
||||||
|
end
|
||||||
|
connection_error = CustomWizard::Notice::ConnectionError.new(archetype)
|
||||||
|
|
||||||
|
if response && response.status == 200
|
||||||
connection_error.expire!
|
connection_error.expire!
|
||||||
expire_notices(types["#{type}_connection_error".to_sym])
|
expire_all(types[:connection_error], archetypes[archetype.to_sym])
|
||||||
|
|
||||||
begin
|
begin
|
||||||
data = JSON.parse(response.body).deep_symbolize_keys
|
data = JSON.parse(response.body).deep_symbolize_keys
|
||||||
|
@ -198,8 +279,7 @@ class CustomWizard::Notice
|
||||||
data
|
data
|
||||||
else
|
else
|
||||||
connection_error.create!
|
connection_error.create!
|
||||||
notify_connection_errors(type) if connection_error.reached_limit?
|
notify_connection_errors(archetype) if connection_error.reached_limit?
|
||||||
|
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -213,20 +293,65 @@ class CustomWizard::Notice
|
||||||
new(raw.symbolize_keys) if raw.present?
|
new(raw.symbolize_keys) if raw.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.exists?(id)
|
||||||
|
PluginStoreRow.where(plugin_name: namespace, key: id).exists?
|
||||||
|
end
|
||||||
|
|
||||||
def self.store(id, raw_notice)
|
def self.store(id, raw_notice)
|
||||||
PluginStore.set(namespace, id, raw_notice)
|
PluginStore.set(namespace, id, raw_notice)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.list_query(type: nil, message: nil, include_recently_expired: false)
|
def self.list_query(type: nil, archetype: nil, title: nil, include_all: false, page: nil, visible: false)
|
||||||
query = PluginStoreRow.where(plugin_name: namespace)
|
query = PluginStoreRow.where(plugin_name: namespace)
|
||||||
query = query.where("(value::json->>'expired_at') IS NULL#{include_recently_expired ? " OR (value::json->>'expired_at')::date > now()::date - 1" : ""}")
|
query = query.where("(value::json->>'hidden_at') IS NULL") if visible
|
||||||
query = query.where("(value::json->>'type')::integer = ?", type) if type
|
query = query.where("(value::json->>'dismissed_at') IS NULL") unless include_all
|
||||||
query = query.where("(value::json->>'message')::text = ?", message) if message
|
query = query.where("(value::json->>'expired_at') IS NULL") unless include_all
|
||||||
query.order("value::json->>'created_at' DESC")
|
query = query.where("(value::json->>'archetype')::integer = ?", archetype) if archetype
|
||||||
|
if type
|
||||||
|
type_query_str = type.is_a?(Array) ? "(value::json->>'type')::integer IN (?)" : "(value::json->>'type')::integer = ?"
|
||||||
|
query = query.where(type_query_str, type)
|
||||||
|
end
|
||||||
|
query = query.where("(value::json->>'title')::text = ?", title) if title
|
||||||
|
query = query.limit(PAGE_LIMIT).offset(page.to_i * PAGE_LIMIT) if !page.nil?
|
||||||
|
query.order("value::json->>'expired_at' DESC, value::json->>'updated_at' DESC,value::json->>'dismissed_at' DESC, value::json->>'created_at' DESC")
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.list(type: nil, message: nil, include_recently_expired: false)
|
def self.list(type: nil, archetype: nil, title: nil, include_all: false, page: 0, visible: false)
|
||||||
list_query(type: type, message: message, include_recently_expired: include_recently_expired)
|
list_query(type: type, archetype: archetype, title: title, include_all: include_all, page: page, visible: visible)
|
||||||
.map { |r| self.new(JSON.parse(r.value).symbolize_keys) }
|
.map { |r| self.new(JSON.parse(r.value).symbolize_keys) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.active_count
|
||||||
|
list_query.count
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.dismiss_all
|
||||||
|
dismissed_count = PluginStoreRow.where("
|
||||||
|
plugin_name = '#{namespace}' AND
|
||||||
|
(value::json->>'type')::integer = #{types[:info]} AND
|
||||||
|
(value::json->>'expired_at') IS NULL AND
|
||||||
|
(value::json->>'dismissed_at') IS NULL
|
||||||
|
").update_all("
|
||||||
|
value = jsonb_set(value::jsonb, '{dismissed_at}', (to_json(now())::text)::jsonb, true)
|
||||||
|
")
|
||||||
|
publish_notice_count if dismissed_count.to_i > 0
|
||||||
|
dismissed_count
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.expire_all(type, archetype)
|
||||||
|
expired_count = PluginStoreRow.where("
|
||||||
|
plugin_name = '#{namespace}' AND
|
||||||
|
(value::json->>'type')::integer = #{type} AND
|
||||||
|
(value::json->>'archetype')::integer = #{archetype} AND
|
||||||
|
(value::json->>'expired_at') IS NULL
|
||||||
|
").update_all("
|
||||||
|
value = jsonb_set(value::jsonb, '{expired_at}', (to_json(now())::text)::jsonb, true)
|
||||||
|
")
|
||||||
|
publish_notice_count if expired_count.to_i > 0
|
||||||
|
expired_count
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.generate_notice_id(title, created_at)
|
||||||
|
Digest::SHA1.hexdigest("#{title}-#{created_at}")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,80 +2,76 @@
|
||||||
|
|
||||||
class CustomWizard::Notice::ConnectionError
|
class CustomWizard::Notice::ConnectionError
|
||||||
|
|
||||||
attr_reader :type_key
|
attr_reader :archetype
|
||||||
|
|
||||||
def initialize(type_key)
|
def initialize(archetype)
|
||||||
@type_key = type_key
|
@archetype = archetype
|
||||||
end
|
end
|
||||||
|
|
||||||
def create!
|
def create!
|
||||||
id = "#{type_key.to_s}_error"
|
if attrs = current_error
|
||||||
|
key = "#{archetype.to_s}_error_#{attrs[:id]}"
|
||||||
if attrs = PluginStore.get(namespace, id)
|
attrs[:updated_at] = Time.now
|
||||||
attrs['updated_at'] = Time.now
|
attrs[:count] = attrs[:count].to_i + 1
|
||||||
attrs['count'] = attrs['count'].to_i + 1
|
|
||||||
else
|
else
|
||||||
domain = CustomWizard::Notice.send("#{type_key.to_s}_domain")
|
domain = CustomWizard::Notice.send("#{archetype.to_s}_domain")
|
||||||
|
id = SecureRandom.hex(8)
|
||||||
attrs = {
|
attrs = {
|
||||||
|
id: id,
|
||||||
message: I18n.t("wizard.notice.connection_error", domain: domain),
|
message: I18n.t("wizard.notice.connection_error", domain: domain),
|
||||||
type: self.class.types[type_key],
|
archetype: CustomWizard::Notice.archetypes[archetype.to_sym],
|
||||||
created_at: Time.now,
|
created_at: Time.now,
|
||||||
count: 1
|
count: 1
|
||||||
}
|
}
|
||||||
|
key = "#{archetype.to_s}_error_#{id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
PluginStore.set(namespace, id, attrs)
|
PluginStore.set(namespace, key, attrs)
|
||||||
@errors = nil
|
|
||||||
|
@current_error = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def expire!
|
def expire!
|
||||||
if errors.exists?
|
if query = current_error(query_only: true)
|
||||||
errors.each do |error_row|
|
record = query.first
|
||||||
error = JSON.parse(error_row.value)
|
error = JSON.parse(record.value)
|
||||||
error['expired_at'] = Time.now
|
error['expired_at'] = Time.now
|
||||||
error_row.value = error.to_json
|
record.value = error.to_json
|
||||||
error_row.save
|
record.save
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.types
|
|
||||||
@types ||= Enum.new(
|
|
||||||
plugin_status: 0,
|
|
||||||
subscription_messages: 1
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def plugin_status_limit
|
def plugin_status_limit
|
||||||
5
|
5
|
||||||
end
|
end
|
||||||
|
|
||||||
def subscription_messages_limit
|
def subscription_message_limit
|
||||||
10
|
10
|
||||||
end
|
end
|
||||||
|
|
||||||
def limit
|
def limit
|
||||||
self.send("#{type_key.to_s}_limit")
|
self.send("#{archetype.to_s}_limit")
|
||||||
end
|
end
|
||||||
|
|
||||||
def reached_limit?
|
def reached_limit?
|
||||||
return false unless errors.exists?
|
return false unless current_error.present?
|
||||||
current_error['count'].to_i >= limit
|
current_error[:count].to_i >= limit
|
||||||
end
|
|
||||||
|
|
||||||
def current_error
|
|
||||||
JSON.parse(errors.first.value)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def namespace
|
def namespace
|
||||||
"#{CustomWizard::PLUGIN_NAME}_notice_connection"
|
"#{CustomWizard::PLUGIN_NAME}_notice_connection"
|
||||||
end
|
end
|
||||||
|
|
||||||
def errors
|
def current_error(query_only: false)
|
||||||
@errors ||= begin
|
@current_error ||= begin
|
||||||
query = PluginStoreRow.where(plugin_name: namespace)
|
query = PluginStoreRow.where(plugin_name: namespace)
|
||||||
query = query.where("(value::json->>'type')::integer = ?", self.class.types[type_key])
|
query = query.where("(value::json->>'archetype')::integer = ?", CustomWizard::Notice.archetypes[archetype.to_sym])
|
||||||
query.where("(value::json->>'expired_at') IS NULL")
|
query = query.where("(value::json->>'expired_at') IS NULL")
|
||||||
|
|
||||||
|
return nil if !query.exists?
|
||||||
|
return query if query_only
|
||||||
|
|
||||||
|
JSON.parse(query.first.value).deep_symbolize_keys
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -245,7 +245,10 @@ after_initialize do
|
||||||
end
|
end
|
||||||
|
|
||||||
AdminDashboardData.add_problem_check do
|
AdminDashboardData.add_problem_check do
|
||||||
warning_notices = CustomWizard::Notice.list(type: CustomWizard::Notice.types[:plugin_status_warning])
|
warning_notices = CustomWizard::Notice.list(
|
||||||
|
type: CustomWizard::Notice.types[:warning],
|
||||||
|
archetype: CustomWizard::Notice.archetypes[:plugin_status]
|
||||||
|
)
|
||||||
warning_notices.any? ? ActionView::Base.full_sanitizer.sanitize(warning_notices.first.message, tags: %w(a)) : nil
|
warning_notices.any? ? ActionView::Base.full_sanitizer.sanitize(warning_notices.first.message, tags: %w(a)) : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2,19 +2,27 @@
|
||||||
|
|
||||||
class CustomWizard::NoticeSerializer < ApplicationSerializer
|
class CustomWizard::NoticeSerializer < ApplicationSerializer
|
||||||
attributes :id,
|
attributes :id,
|
||||||
|
:title,
|
||||||
:message,
|
:message,
|
||||||
:type,
|
:type,
|
||||||
|
:archetype,
|
||||||
:created_at,
|
:created_at,
|
||||||
:expired_at,
|
:expired_at,
|
||||||
:updated_at,
|
:updated_at,
|
||||||
:dismissed_at,
|
:dismissed_at,
|
||||||
:retrieved_at,
|
:retrieved_at,
|
||||||
:dismissable
|
:hidden_at,
|
||||||
|
:dismissable,
|
||||||
|
:can_hide
|
||||||
|
|
||||||
def dismissable
|
def dismissable
|
||||||
object.dismissable?
|
object.dismissable?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def can_hide
|
||||||
|
object.can_hide?
|
||||||
|
end
|
||||||
|
|
||||||
def type
|
def type
|
||||||
CustomWizard::Notice.types.key(object.type)
|
CustomWizard::Notice.types.key(object.type)
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,7 @@ describe CustomWizard::Notice do
|
||||||
fab!(:user) { Fabricate(:user) }
|
fab!(:user) { Fabricate(:user) }
|
||||||
let(:subscription_message) {
|
let(:subscription_message) {
|
||||||
{
|
{
|
||||||
|
title: "Title of message about subscription",
|
||||||
message: "Message about subscription",
|
message: "Message about subscription",
|
||||||
type: "info",
|
type: "info",
|
||||||
created_at: Time.now - 3.day,
|
created_at: Time.now - 3.day,
|
||||||
|
@ -23,7 +24,7 @@ describe CustomWizard::Notice do
|
||||||
context "subscription message" do
|
context "subscription message" do
|
||||||
before do
|
before do
|
||||||
freeze_time
|
freeze_time
|
||||||
stub_request(:get, described_class.subscription_messages_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json)
|
stub_request(:get, described_class.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json)
|
||||||
described_class.update(skip_plugin: true)
|
described_class.update(skip_plugin: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -36,46 +37,73 @@ describe CustomWizard::Notice do
|
||||||
|
|
||||||
it "expires notice if subscription message is expired" do
|
it "expires notice if subscription message is expired" do
|
||||||
subscription_message[:expired_at] = Time.now
|
subscription_message[:expired_at] = Time.now
|
||||||
stub_request(:get, described_class.subscription_messages_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json)
|
stub_request(:get, described_class.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json)
|
||||||
described_class.update(skip_plugin: true)
|
described_class.update(skip_plugin: true)
|
||||||
|
|
||||||
notice = described_class.list(include_recently_expired: true).first
|
notice = described_class.list(include_all: true).first
|
||||||
expect(notice.expired?).to eq(true)
|
expect(notice.expired?).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "dismisses informational subscription notices" do
|
||||||
|
notice = described_class.list(include_all: true).first
|
||||||
|
expect(notice.dismissed?).to eq(false)
|
||||||
|
|
||||||
|
notice.dismiss!
|
||||||
|
expect(notice.dismissed?).to eq(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "dismisses all informational subscription notices" do
|
||||||
|
4.times do |index|
|
||||||
|
subscription_message[:title] += " #{index}"
|
||||||
|
stub_request(:get, described_class.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json)
|
||||||
|
described_class.update(skip_plugin: true)
|
||||||
|
end
|
||||||
|
expect(described_class.list.count).to eq(5)
|
||||||
|
described_class.dismiss_all
|
||||||
|
expect(described_class.list.count).to eq(0)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "plugin status" do
|
context "plugin status" do
|
||||||
before do
|
before do
|
||||||
freeze_time
|
freeze_time
|
||||||
stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json)
|
stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: plugin_status.to_json)
|
||||||
described_class.update(skip_subscription: true)
|
described_class.update(skip_subscription: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "converts warning into notice" do
|
it "converts warning into notice" do
|
||||||
notice = described_class.list.first
|
notice = described_class.list.first
|
||||||
expect(notice.type).to eq(described_class.types[:plugin_status_warning])
|
expect(notice.type).to eq(described_class.types[:warning])
|
||||||
expect(notice.message).to eq(I18n.t("wizard.notice.compatibility_issue", domain: described_class.plugin_status_domain))
|
expect(notice.message).to eq(I18n.t("wizard.notice.compatibility_issue.message", domain: described_class.plugin_status_domain))
|
||||||
expect(notice.created_at.to_datetime).to be_within(1.second).of (plugin_status[:status_changed_at].to_datetime)
|
expect(notice.created_at.to_datetime).to be_within(1.second).of (plugin_status[:status_changed_at].to_datetime)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "expires warning notices if status is recommended or compatible" do
|
it "expires warning notices if status is recommended or compatible" do
|
||||||
plugin_status[:status] = 'compatible'
|
plugin_status[:status] = 'compatible'
|
||||||
plugin_status[:status_changed_at] = Time.now
|
plugin_status[:status_changed_at] = Time.now
|
||||||
stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json)
|
stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: plugin_status.to_json)
|
||||||
described_class.update(skip_subscription: true)
|
described_class.update(skip_subscription: true)
|
||||||
|
|
||||||
notice = described_class.list(type: described_class.types[:plugin_status_warning], include_recently_expired: true).first
|
notice = described_class.list(type: described_class.types[:warning], include_all: true).first
|
||||||
expect(notice.expired?).to eq(true)
|
expect(notice.expired?).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "hides plugin status warnings" do
|
||||||
|
notice = described_class.list.first
|
||||||
|
expect(notice.hidden?).to eq(false)
|
||||||
|
|
||||||
|
notice.hide!
|
||||||
|
expect(notice.hidden?).to eq(true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "lists notices not expired more than a day ago" do
|
it "lists notices not expired more than a day ago" do
|
||||||
subscription_message[:expired_at] = Time.now - 8.hours
|
subscription_message[:expired_at] = Time.now - 8.hours
|
||||||
stub_request(:get, described_class.subscription_messages_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json)
|
stub_request(:get, described_class.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json)
|
||||||
stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json)
|
stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: plugin_status.to_json)
|
||||||
|
|
||||||
described_class.update
|
described_class.update
|
||||||
expect(described_class.list(include_recently_expired: true).length).to eq(2)
|
expect(described_class.list(include_all: true).length).to eq(2)
|
||||||
end
|
end
|
||||||
|
|
||||||
context "connection errors" do
|
context "connection errors" do
|
||||||
|
@ -84,47 +112,47 @@ describe CustomWizard::Notice do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates an error if connection to notice server fails" do
|
it "creates an error if connection to notice server fails" do
|
||||||
stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: { status: plugin_status }.to_json)
|
stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: plugin_status.to_json)
|
||||||
described_class.update(skip_subscription: true)
|
described_class.update(skip_subscription: true)
|
||||||
|
|
||||||
error = CustomWizard::Notice::ConnectionError.new(:plugin_status)
|
error = CustomWizard::Notice::ConnectionError.new(:plugin_status)
|
||||||
expect(error.errors.exists?).to eq(true)
|
expect(error.current_error.present?).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "only creates one connection error per type at a time" do
|
it "only creates one connection error per type at a time" do
|
||||||
stub_request(:get, described_class.subscription_messages_url).to_return(status: 400, body: { messages: [subscription_message] }.to_json)
|
stub_request(:get, described_class.subscription_message_url).to_return(status: 400, body: { messages: [subscription_message] }.to_json)
|
||||||
stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: { status: plugin_status }.to_json)
|
stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: plugin_status.to_json)
|
||||||
|
|
||||||
5.times { described_class.update }
|
5.times { described_class.update }
|
||||||
|
|
||||||
plugin_status_errors = CustomWizard::Notice::ConnectionError.new(:plugin_status)
|
plugin_status_errors = CustomWizard::Notice::ConnectionError.new(:plugin_status)
|
||||||
subscription_message_errors = CustomWizard::Notice::ConnectionError.new(:subscription_messages)
|
subscription_message_errors = CustomWizard::Notice::ConnectionError.new(:subscription_message)
|
||||||
|
|
||||||
expect(plugin_status_errors.errors.length).to eq(1)
|
expect(plugin_status_errors.current_error[:count]).to eq(5)
|
||||||
expect(subscription_message_errors.errors.length).to eq(1)
|
expect(subscription_message_errors.current_error[:count]).to eq(5)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates a connection error notice if connection errors reach limit" do
|
it "creates a connection error notice if connection errors reach limit" do
|
||||||
stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: { status: plugin_status }.to_json)
|
stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: plugin_status.to_json)
|
||||||
|
|
||||||
error = CustomWizard::Notice::ConnectionError.new(:plugin_status)
|
error = CustomWizard::Notice::ConnectionError.new(:plugin_status)
|
||||||
error.limit.times { described_class.update(skip_subscription: true) }
|
error.limit.times { described_class.update(skip_subscription: true) }
|
||||||
notice = described_class.list(type: described_class.types[:plugin_status_connection_error]).first
|
notice = described_class.list(type: described_class.types[:connection_error]).first
|
||||||
|
|
||||||
expect(error.current_error['count']).to eq(error.limit)
|
expect(error.current_error[:count]).to eq(error.limit)
|
||||||
expect(notice.type).to eq(described_class.types[:plugin_status_connection_error])
|
expect(notice.type).to eq(described_class.types[:connection_error])
|
||||||
end
|
end
|
||||||
|
|
||||||
it "expires a connection error notice if connection succeeds" do
|
it "expires a connection error notice if connection succeeds" do
|
||||||
stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: { status: plugin_status }.to_json)
|
stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: plugin_status.to_json)
|
||||||
error = CustomWizard::Notice::ConnectionError.new(:plugin_status)
|
error = CustomWizard::Notice::ConnectionError.new(:plugin_status)
|
||||||
error.limit.times { described_class.update(skip_subscription: true) }
|
error.limit.times { described_class.update(skip_subscription: true) }
|
||||||
|
|
||||||
stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json)
|
stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: plugin_status.to_json)
|
||||||
described_class.update(skip_subscription: true)
|
described_class.update(skip_subscription: true)
|
||||||
notice = described_class.list(type: described_class.types[:plugin_status_connection_error], include_recently_expired: true).first
|
notice = described_class.list(type: described_class.types[:connection_error], include_all: true).first
|
||||||
|
|
||||||
expect(notice.type).to eq(described_class.types[:plugin_status_connection_error])
|
expect(notice.type).to eq(described_class.types[:connection_error])
|
||||||
expect(notice.expired_at.present?).to eq(true)
|
expect(notice.expired_at.present?).to eq(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -186,7 +186,7 @@ describe CustomWizard::Wizard do
|
||||||
|
|
||||||
it "lists the site categories" do
|
it "lists the site categories" do
|
||||||
Site.clear_cache
|
Site.clear_cache
|
||||||
expect(@wizard.categories.length).to eq(1)
|
expect(@wizard.categories.length > 0).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
context "submissions" do
|
context "submissions" do
|
||||||
|
|
|
@ -20,8 +20,8 @@ describe Jobs::CustomWizardUpdateNotices do
|
||||||
}
|
}
|
||||||
|
|
||||||
it "updates the notices" do
|
it "updates the notices" do
|
||||||
stub_request(:get, CustomWizard::Notice.subscription_messages_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json)
|
stub_request(:get, CustomWizard::Notice.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json)
|
||||||
stub_request(:get, CustomWizard::Notice.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json)
|
stub_request(:get, CustomWizard::Notice.plugin_status_url).to_return(status: 200, body: plugin_status.to_json)
|
||||||
|
|
||||||
described_class.new.execute
|
described_class.new.execute
|
||||||
expect(CustomWizard::Notice.list.length).to eq(2)
|
expect(CustomWizard::Notice.list.length).to eq(2)
|
||||||
|
|
|
@ -3,29 +3,70 @@ require_relative '../../../plugin_helper'
|
||||||
|
|
||||||
describe CustomWizard::AdminNoticeController do
|
describe CustomWizard::AdminNoticeController do
|
||||||
fab!(:admin_user) { Fabricate(:user, admin: true) }
|
fab!(:admin_user) { Fabricate(:user, admin: true) }
|
||||||
|
let(:subscription_message_notice) {
|
||||||
|
{
|
||||||
|
title: "Title of message about subscription",
|
||||||
|
message: "Message about subscription",
|
||||||
|
type: 0,
|
||||||
|
created_at: Time.now.iso8601(3),
|
||||||
|
expired_at: nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let(:plugin_status_notice) {
|
||||||
|
{
|
||||||
|
title: "The Custom Wizard Plugin is incompatibile with the latest version of Discourse.",
|
||||||
|
message: "Please check the Custom Wizard Plugin status on [localhost:3000](http://localhost:3000) before updating Discourse.",
|
||||||
|
type: 1,
|
||||||
|
archetype: 1,
|
||||||
|
created_at: Time.now.iso8601(3),
|
||||||
|
expired_at: nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
before do
|
before do
|
||||||
sign_in(admin_user)
|
sign_in(admin_user)
|
||||||
@notice = CustomWizard::Notice.new(
|
|
||||||
message: "Message about subscription",
|
|
||||||
type: "info",
|
|
||||||
created_at: Time.now - 3.day,
|
|
||||||
expired_at: nil
|
|
||||||
)
|
|
||||||
@notice.save
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "lists notices" do
|
it "lists notices" do
|
||||||
|
@notice = CustomWizard::Notice.new(subscription_message_notice)
|
||||||
|
@notice.save
|
||||||
|
|
||||||
get "/admin/wizards/notice.json"
|
get "/admin/wizards/notice.json"
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
expect(response.parsed_body.length).to eq(1)
|
expect(response.parsed_body.length).to eq(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "dismisses notices" do
|
it "dismisses notices" do
|
||||||
put "/admin/wizards/notice/#{@notice.id}.json"
|
@notice = CustomWizard::Notice.new(subscription_message_notice)
|
||||||
|
@notice.save
|
||||||
|
|
||||||
|
put "/admin/wizards/notice/#{@notice.id}/dismiss.json"
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
updated = CustomWizard::Notice.find(@notice.id)
|
updated = CustomWizard::Notice.find(@notice.id)
|
||||||
expect(updated.dismissed?).to eq(true)
|
expect(updated.dismissed?).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "dismisses all notices" do
|
||||||
|
5.times do |index|
|
||||||
|
subscription_message_notice[:title] += " #{index}"
|
||||||
|
@notice = CustomWizard::Notice.new(subscription_message_notice)
|
||||||
|
@notice.save
|
||||||
|
end
|
||||||
|
|
||||||
|
put "/admin/wizards/notice/dismiss.json"
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(CustomWizard::Notice.list.size).to eq(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "hides notices" do
|
||||||
|
@notice = CustomWizard::Notice.new(plugin_status_notice)
|
||||||
|
@notice.save
|
||||||
|
|
||||||
|
put "/admin/wizards/notice/#{@notice.id}/hide.json"
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
updated = CustomWizard::Notice.find(@notice.id)
|
||||||
|
expect(updated.hidden?).to eq(true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Laden …
In neuem Issue referenzieren