DEV: Apply model transformer API on more models (#18087)

This commit extends the plugin API introduced in 40fd82e2d1 to the `Bookmark` and `Notification` models. It also refactors the code that's responsible for loading items in the experimental menu to use `async`...`await` instead of `Promise`s.
This commit is contained in:
Osama Sayegh
2022-08-26 00:44:06 +03:00
committed by GitHub
parent 14ab819c1d
commit 97f094e5ee
9 changed files with 143 additions and 65 deletions

View File

@@ -5,6 +5,7 @@ import showModal from "discourse/lib/show-modal";
import I18n from "I18n"; import I18n from "I18n";
import UserMenuNotificationItem from "discourse/lib/user-menu/notification-item"; import UserMenuNotificationItem from "discourse/lib/user-menu/notification-item";
import UserMenuBookmarkItem from "discourse/lib/user-menu/bookmark-item"; import UserMenuBookmarkItem from "discourse/lib/user-menu/bookmark-item";
import Bookmark from "discourse/models/bookmark";
export default class UserMenuBookmarksList extends UserMenuNotificationsList { export default class UserMenuBookmarksList extends UserMenuNotificationsList {
get dismissTypes() { get dismissTypes() {
@@ -44,29 +45,34 @@ export default class UserMenuBookmarksList extends UserMenuNotificationsList {
return this.currentUser.get(key) || 0; return this.currentUser.get(key) || 0;
} }
fetchItems() { async fetchItems() {
return ajax(`/u/${this.currentUser.username}/user-menu-bookmarks`).then( const data = await ajax(
(data) => { `/u/${this.currentUser.username}/user-menu-bookmarks`
const content = [];
data.notifications.forEach((rawNotification) => {
const notification = Notification.create(rawNotification);
content.push(
new UserMenuNotificationItem({
notification,
currentUser: this.currentUser,
siteSettings: this.siteSettings,
site: this.site,
})
);
});
content.push(
...data.bookmarks.map((bookmark) => {
return new UserMenuBookmarkItem({ bookmark });
})
);
return content;
}
); );
const content = [];
const notifications = data.notifications.map((n) => Notification.create(n));
await Notification.applyTransformations(notifications);
notifications.forEach((notification) => {
content.push(
new UserMenuNotificationItem({
notification,
currentUser: this.currentUser,
siteSettings: this.siteSettings,
site: this.site,
})
);
});
const bookmarks = data.bookmarks.map((b) => Bookmark.create(b));
await Bookmark.applyTransformations(bookmarks);
content.push(
...bookmarks.map((bookmark) => {
return new UserMenuBookmarkItem({ bookmark });
})
);
return content;
} }
dismissWarningModal() { dismissWarningModal() {

View File

@@ -28,33 +28,40 @@ export default class UserMenuItemsList extends Component {
return "user-menu/items-list-empty-state"; return "user-menu/items-list-empty-state";
} }
fetchItems() { async fetchItems() {
throw new Error( throw new Error(
`the fetchItems method must be implemented in ${this.constructor.name}` `the fetchItems method must be implemented in ${this.constructor.name}`
); );
} }
refreshList() { async refreshList() {
this.#load(); await this.#load();
} }
dismissWarningModal() { dismissWarningModal() {
return null; return null;
} }
#load() { async #load() {
const cached = this.#getCachedItems(); const cached = this.#getCachedItems();
if (cached?.length) { if (cached?.length) {
this.items = cached; this.items = cached;
} else { } else {
this.loading = true; this.loading = true;
} }
this.fetchItems() try {
.then((items) => { const items = await this.fetchItems();
this.#setCachedItems(items); this.#setCachedItems(items);
this.items = items; this.items = items;
}) } catch (err) {
.finally(() => (this.loading = false)); // eslint-disable-next-line no-console
console.error(
`an error occurred when loading items for ${this.constructor.name}`,
err
);
} finally {
this.loading = false;
}
} }
#getCachedItems() { #getCachedItems() {

View File

@@ -45,31 +45,34 @@ export default class UserMenuMessagesList extends UserMenuNotificationsList {
return this.currentUser.get(key) || 0; return this.currentUser.get(key) || 0;
} }
fetchItems() { async fetchItems() {
return ajax( const data = await ajax(
`/u/${this.currentUser.username}/user-menu-private-messages` `/u/${this.currentUser.username}/user-menu-private-messages`
).then(async (data) => { );
const content = []; const content = [];
data.notifications.forEach((rawNotification) => {
const notification = Notification.create(rawNotification); const notifications = data.notifications.map((n) => Notification.create(n));
content.push( await Notification.applyTransformations(notifications);
new UserMenuNotificationItem({ notifications.forEach((notification) => {
notification,
currentUser: this.currentUser,
siteSettings: this.siteSettings,
site: this.site,
})
);
});
const topics = data.topics.map((t) => Topic.create(t));
await Topic.applyTransformations(topics);
content.push( content.push(
...topics.map((topic) => { new UserMenuNotificationItem({
return new UserMenuMessageItem({ message: topic }); notification,
currentUser: this.currentUser,
siteSettings: this.siteSettings,
site: this.site,
}) })
); );
return content;
}); });
const topics = data.topics.map((t) => Topic.create(t));
await Topic.applyTransformations(topics);
content.push(
...topics.map((topic) => {
return new UserMenuMessageItem({ message: topic });
})
);
return content;
} }
dismissWarningModal() { dismissWarningModal() {

View File

@@ -6,6 +6,7 @@ import { postRNWebviewMessage } from "discourse/lib/utilities";
import showModal from "discourse/lib/show-modal"; import showModal from "discourse/lib/show-modal";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import UserMenuNotificationItem from "discourse/lib/user-menu/notification-item"; import UserMenuNotificationItem from "discourse/lib/user-menu/notification-item";
import Notification from "discourse/models/notification";
export default class UserMenuNotificationsList extends UserMenuItemsList { export default class UserMenuNotificationsList extends UserMenuItemsList {
@service currentUser; @service currentUser;
@@ -54,7 +55,7 @@ export default class UserMenuNotificationsList extends UserMenuItemsList {
} }
} }
fetchItems() { async fetchItems() {
const params = { const params = {
limit: 30, limit: 30,
recent: true, recent: true,
@@ -67,19 +68,19 @@ export default class UserMenuNotificationsList extends UserMenuItemsList {
params.filter_by_types = types.join(","); params.filter_by_types = types.join(",");
params.silent = true; params.silent = true;
} }
return this.store const collection = await this.store
.findStale("notification", params) .findStale("notification", params)
.refresh() .refresh();
.then((c) => { const notifications = collection.content;
return c.content.map((notification) => { await Notification.applyTransformations(notifications);
return new UserMenuNotificationItem({ return notifications.map((notification) => {
notification, return new UserMenuNotificationItem({
currentUser: this.currentUser, notification,
siteSettings: this.siteSettings, currentUser: this.currentUser,
site: this.site, siteSettings: this.siteSettings,
}); site: this.site,
});
}); });
});
} }
dismissWarningModal() { dismissWarningModal() {

View File

@@ -1952,7 +1952,7 @@ class PluginApi {
* @callback registerModelTransformerCallback * @callback registerModelTransformerCallback
* @param {Object[]} A list of model instances * @param {Object[]} A list of model instances
* *
* @param {string} modelName - Model type on which transformation should be applied. Currently the only valid type is "topic". * @param {string} modelName - Model type on which transformation should be applied. Currently valid types are "topic", "notification" and "bookmark".
* @param {registerModelTransformerCallback} transformer - Callback function that receives a list of model objects of the specified type and applies transformation on them. * @param {registerModelTransformerCallback} transformer - Callback function that receives a list of model objects of the specified type and applies transformation on them.
*/ */
registerModelTransformer(modelName, transformer) { registerModelTransformer(modelName, transformer) {

View File

@@ -12,6 +12,7 @@ import getURL from "discourse-common/lib/get-url";
import { longDate } from "discourse/lib/formatter"; import { longDate } from "discourse/lib/formatter";
import { none } from "@ember/object/computed"; import { none } from "@ember/object/computed";
import { capitalize } from "@ember/string"; import { capitalize } from "@ember/string";
import { applyModelTransformations } from "discourse/lib/model-transformers";
export const AUTO_DELETE_PREFERENCES = { export const AUTO_DELETE_PREFERENCES = {
NEVER: 0, NEVER: 0,
@@ -168,6 +169,10 @@ Bookmark.reopenClass({
args.user = User.create(args.user); args.user = User.create(args.user);
return this._super(args); return this._super(args);
}, },
async applyTransformations(bookmarks) {
await applyModelTransformations("bookmark", bookmarks);
},
}); });
export default Bookmark; export default Bookmark;

View File

@@ -1,6 +1,11 @@
import RestModel from "discourse/models/rest"; import RestModel from "discourse/models/rest";
import { tracked } from "@glimmer/tracking"; import { tracked } from "@glimmer/tracking";
import { applyModelTransformations } from "discourse/lib/model-transformers";
export default class Notification extends RestModel { export default class Notification extends RestModel {
static async applyTransformations(notifications) {
await applyModelTransformations("notification", notifications);
}
@tracked read; @tracked read;
} }

View File

@@ -189,6 +189,55 @@ acceptance("User menu", function (needs) {
); );
}); });
test("notifications tab applies model transformations registered by plugins", async function (assert) {
withPluginApi("0.1", (api) => {
api.registerModelTransformer("notification", (notifications) => {
notifications.forEach((notification, index) => {
if (notification.fancy_title) {
notification.fancy_title = `pluginNotificationTransformer ${index} ${notification.fancy_title}`;
}
});
});
});
await visit("/");
await click(".d-header-icons .current-user");
const notifications = queryAll(
"#quick-access-all-notifications ul li.notification"
);
assert.strictEqual(
notifications[0].textContent.replace(/\s+/g, " ").trim(),
"velesin pluginNotificationTransformer 0 edited topic 443"
);
assert.strictEqual(
notifications[1].textContent.replace(/\s+/g, " ").trim(),
"velesin pluginNotificationTransformer 1 some title"
);
});
test("bookmarks tab applies model transformations registered by plugins", async function (assert) {
withPluginApi("0.1", (api) => {
api.registerModelTransformer("bookmark", (bookmarks) => {
bookmarks.forEach((bookmark) => {
if (bookmark.title) {
bookmark.title = `pluginBookmarkTransformer ${bookmark.title}`;
}
});
});
});
await visit("/");
await click(".d-header-icons .current-user");
await click("#user-menu-button-bookmarks");
const bookmarks = queryAll("#quick-access-bookmarks ul li.bookmark");
assert.strictEqual(
bookmarks[0].textContent.replace(/\s+/g, " ").trim(),
"osama pluginBookmarkTransformer Test poll topic hello world"
);
});
test("messages tab applies model transformations registered by plugins", async function (assert) { test("messages tab applies model transformations registered by plugins", async function (assert) {
withPluginApi("0.1", (api) => { withPluginApi("0.1", (api) => {
api.registerModelTransformer("topic", (topics) => { api.registerModelTransformer("topic", (topics) => {

View File

@@ -10,6 +10,7 @@ export default {
post_number: 1, post_number: 1,
topic_id: 130, topic_id: 130,
slug: "lorem-ipsum-dolor-sit-amet", slug: "lorem-ipsum-dolor-sit-amet",
fancy_title: "edited topic 443",
data: { data: {
topic_title: "edited topic 443", topic_title: "edited topic 443",
display_username: "velesin", display_username: "velesin",
@@ -26,6 +27,7 @@ export default {
post_number: 1, post_number: 1,
topic_id: 1234, topic_id: 1234,
slug: "a-slug", slug: "a-slug",
fancy_title: "some title",
data: { topic_title: "some title", display_username: "velesin" }, data: { topic_title: "some title", display_username: "velesin" },
}, },
{ {