mirror of
https://github.com/discourse/discourse.git
synced 2025-01-19 10:32:45 +08:00
DEV: Add group messages and group_message_summary notifications in the messages tab in the user menu (#18390)
This commit adds non-archived group messages and `group_message_summary` notifications in the messages tab in the user menu. With this change, the messages tab in the user menu now includes 3 types of items: 1. Unread `private_message` notifications (notifications when you receive a reply in a PM) 2. Unread and read `group_message_summary` notifications (notifications when there's a new message in a group inbox that you track) 3. Non-archived personal and group messages Unread `private_message` notifications are always shown first, followed by unread `group_message_summary` notifications, and then everything else (messages and read `group_message_summary` notifications) sorted by recency (most recent first). Internal topic: t/72976.
This commit is contained in:
parent
6ebd2cecda
commit
5a5625460b
|
@ -170,7 +170,7 @@ const CORE_TOP_TABS = [
|
||||||
}
|
}
|
||||||
|
|
||||||
get notificationTypes() {
|
get notificationTypes() {
|
||||||
return ["private_message"];
|
return ["private_message", "group_message_summary"];
|
||||||
}
|
}
|
||||||
|
|
||||||
get linkWhenActive() {
|
get linkWhenActive() {
|
||||||
|
|
|
@ -7,9 +7,21 @@ import UserMenuNotificationItem from "discourse/lib/user-menu/notification-item"
|
||||||
import UserMenuMessageItem from "discourse/lib/user-menu/message-item";
|
import UserMenuMessageItem from "discourse/lib/user-menu/message-item";
|
||||||
import Topic from "discourse/models/topic";
|
import Topic from "discourse/models/topic";
|
||||||
|
|
||||||
|
function parseDateString(date) {
|
||||||
|
if (date) {
|
||||||
|
return new Date(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initializeNotifications(rawList) {
|
||||||
|
const notifications = rawList.map((n) => Notification.create(n));
|
||||||
|
await Notification.applyTransformations(notifications);
|
||||||
|
return notifications;
|
||||||
|
}
|
||||||
|
|
||||||
export default class UserMenuMessagesList extends UserMenuNotificationsList {
|
export default class UserMenuMessagesList extends UserMenuNotificationsList {
|
||||||
get dismissTypes() {
|
get dismissTypes() {
|
||||||
return ["private_message"];
|
return this.filterByTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
get showAllHref() {
|
get showAllHref() {
|
||||||
|
@ -51,9 +63,10 @@ export default class UserMenuMessagesList extends UserMenuNotificationsList {
|
||||||
);
|
);
|
||||||
const content = [];
|
const content = [];
|
||||||
|
|
||||||
const notifications = data.notifications.map((n) => Notification.create(n));
|
const unreadNotifications = await initializeNotifications(
|
||||||
await Notification.applyTransformations(notifications);
|
data.unread_notifications
|
||||||
notifications.forEach((notification) => {
|
);
|
||||||
|
unreadNotifications.forEach((notification) => {
|
||||||
content.push(
|
content.push(
|
||||||
new UserMenuNotificationItem({
|
new UserMenuNotificationItem({
|
||||||
notification,
|
notification,
|
||||||
|
@ -66,12 +79,39 @@ export default class UserMenuMessagesList extends UserMenuNotificationsList {
|
||||||
|
|
||||||
const topics = data.topics.map((t) => Topic.create(t));
|
const topics = data.topics.map((t) => Topic.create(t));
|
||||||
await Topic.applyTransformations(topics);
|
await Topic.applyTransformations(topics);
|
||||||
content.push(
|
|
||||||
...topics.map((topic) => {
|
const readNotifications = await initializeNotifications(
|
||||||
return new UserMenuMessageItem({ message: topic });
|
data.read_notifications
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let latestReadNotificationDate = parseDateString(
|
||||||
|
readNotifications[0]?.created_at
|
||||||
|
);
|
||||||
|
let latestMessageDate = parseDateString(topics[0]?.bumped_at);
|
||||||
|
|
||||||
|
while (latestReadNotificationDate || latestMessageDate) {
|
||||||
|
if (
|
||||||
|
!latestReadNotificationDate ||
|
||||||
|
(latestMessageDate && latestReadNotificationDate < latestMessageDate)
|
||||||
|
) {
|
||||||
|
content.push(new UserMenuMessageItem({ message: topics[0] }));
|
||||||
|
topics.shift();
|
||||||
|
latestMessageDate = parseDateString(topics[0]?.bumped_at);
|
||||||
|
} else {
|
||||||
|
content.push(
|
||||||
|
new UserMenuNotificationItem({
|
||||||
|
notification: readNotifications[0],
|
||||||
|
currentUser: this.currentUser,
|
||||||
|
siteSettings: this.siteSettings,
|
||||||
|
site: this.site,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
readNotifications.shift();
|
||||||
|
latestReadNotificationDate = parseDateString(
|
||||||
|
readNotifications[0]?.created_at
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -805,7 +805,7 @@ acceptance("User menu - Dismiss button", function (needs) {
|
||||||
const copy = cloneJSON(
|
const copy = cloneJSON(
|
||||||
UserMenuFixtures["/u/:username/user-menu-private-messages"]
|
UserMenuFixtures["/u/:username/user-menu-private-messages"]
|
||||||
);
|
);
|
||||||
copy.notifications = [];
|
copy.unread_notifications = [];
|
||||||
return helper.response(copy);
|
return helper.response(copy);
|
||||||
} else {
|
} else {
|
||||||
return helper.response(
|
return helper.response(
|
||||||
|
@ -941,7 +941,7 @@ acceptance("User menu - Dismiss button", function (needs) {
|
||||||
assert.ok(markRead, "mark-read request is sent");
|
assert.ok(markRead, "mark-read request is sent");
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
markReadRequestBody,
|
markReadRequestBody,
|
||||||
"dismiss_types=private_message",
|
"dismiss_types=private_message%2Cgroup_message_summary",
|
||||||
"mark-read request specifies private_message types"
|
"mark-read request specifies private_message types"
|
||||||
);
|
);
|
||||||
assert.notOk(exists(".user-menu .notifications-dismiss"));
|
assert.notOk(exists(".user-menu .notifications-dismiss"));
|
||||||
|
|
|
@ -64,79 +64,80 @@ export default {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"/u/:username/user-menu-private-messages": {
|
"/u/:username/user-menu-private-messages": {
|
||||||
notifications: [
|
unread_notifications: [
|
||||||
{
|
{
|
||||||
id: 8315,
|
id: 8315,
|
||||||
user_id: 1,
|
user_id: 1,
|
||||||
notification_type: 6,
|
notification_type: 6,
|
||||||
read: false,
|
read: false,
|
||||||
high_priority: true,
|
high_priority: true,
|
||||||
created_at: "2022-08-05T17:27:24.873Z",
|
created_at: "2022-08-05T17:27:24.873Z",
|
||||||
post_number: 1,
|
post_number: 1,
|
||||||
topic_id: 249,
|
topic_id: 249,
|
||||||
fancy_title: "Very secret message!",
|
fancy_title: "Very secret message!",
|
||||||
slug: "very-secret-message",
|
slug: "very-secret-message",
|
||||||
data: {
|
data: {
|
||||||
topic_title: "very secret message!",
|
topic_title: "very secret message!",
|
||||||
original_post_id: 1043,
|
original_post_id: 1043,
|
||||||
original_post_type: 1,
|
original_post_type: 1,
|
||||||
original_username: "osama",
|
original_username: "osama",
|
||||||
revision_number: null,
|
revision_number: null,
|
||||||
display_username: "osama"
|
display_username: "osama"
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
topics: [
|
],
|
||||||
{
|
topics: [
|
||||||
id: 8092,
|
{
|
||||||
title: "BUG: Can not render emoji properly :/",
|
id: 8092,
|
||||||
fancy_title: "BUG: Can not render emoji properly :confused:",
|
title: "BUG: Can not render emoji properly :/",
|
||||||
slug: "bug-can-not-render-emoji-properly",
|
fancy_title: "BUG: Can not render emoji properly :confused:",
|
||||||
posts_count: 1,
|
slug: "bug-can-not-render-emoji-properly",
|
||||||
reply_count: 0,
|
posts_count: 1,
|
||||||
highest_post_number: 2,
|
reply_count: 0,
|
||||||
image_url: null,
|
highest_post_number: 2,
|
||||||
created_at: "2019-07-26T01:29:24.008Z",
|
image_url: null,
|
||||||
last_posted_at: "2019-07-26T01:29:24.177Z",
|
created_at: "2019-07-26T01:29:24.008Z",
|
||||||
bumped: true,
|
last_posted_at: "2019-07-26T01:29:24.177Z",
|
||||||
bumped_at: "2019-07-26T01:29:24.177Z",
|
bumped: true,
|
||||||
unseen: false,
|
bumped_at: "2019-07-26T01:29:24.177Z",
|
||||||
last_read_post_number: 2,
|
unseen: false,
|
||||||
unread_posts: 0,
|
last_read_post_number: 2,
|
||||||
pinned: false,
|
unread_posts: 0,
|
||||||
unpinned: null,
|
pinned: false,
|
||||||
visible: true,
|
unpinned: null,
|
||||||
closed: false,
|
visible: true,
|
||||||
archived: false,
|
closed: false,
|
||||||
notification_level: 3,
|
archived: false,
|
||||||
bookmarked: false,
|
notification_level: 3,
|
||||||
bookmarks: [],
|
bookmarked: false,
|
||||||
liked: false,
|
bookmarks: [],
|
||||||
views: 5,
|
liked: false,
|
||||||
like_count: 0,
|
views: 5,
|
||||||
has_summary: false,
|
like_count: 0,
|
||||||
archetype: "private_message",
|
has_summary: false,
|
||||||
last_poster_username: "mixtape",
|
archetype: "private_message",
|
||||||
category_id: null,
|
last_poster_username: "mixtape",
|
||||||
pinned_globally: false,
|
category_id: null,
|
||||||
featured_link: null,
|
pinned_globally: false,
|
||||||
posters: [
|
featured_link: null,
|
||||||
{
|
posters: [
|
||||||
extras: "latest single",
|
{
|
||||||
description: "Original Poster, Most Recent Poster",
|
extras: "latest single",
|
||||||
user_id: 13,
|
description: "Original Poster, Most Recent Poster",
|
||||||
primary_group_id: null,
|
user_id: 13,
|
||||||
},
|
primary_group_id: null,
|
||||||
],
|
},
|
||||||
participants: [
|
],
|
||||||
{
|
participants: [
|
||||||
extras: "latest",
|
{
|
||||||
description: null,
|
extras: "latest",
|
||||||
user_id: 13,
|
description: null,
|
||||||
primary_group_id: null,
|
user_id: 13,
|
||||||
},
|
primary_group_id: null,
|
||||||
],
|
},
|
||||||
}
|
],
|
||||||
],
|
}
|
||||||
|
],
|
||||||
|
read_notifications: [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,128 @@ import { render, settled } from "@ember/test-helpers";
|
||||||
import { NOTIFICATION_TYPES } from "discourse/tests/fixtures/concerns/notification-types";
|
import { NOTIFICATION_TYPES } from "discourse/tests/fixtures/concerns/notification-types";
|
||||||
import { hbs } from "ember-cli-htmlbars";
|
import { hbs } from "ember-cli-htmlbars";
|
||||||
import pretender, { response } from "discourse/tests/helpers/create-pretender";
|
import pretender, { response } from "discourse/tests/helpers/create-pretender";
|
||||||
|
import { cloneJSON, deepMerge } from "discourse-common/lib/object";
|
||||||
|
import UserMenuFixtures from "discourse/tests/fixtures/user-menu";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
|
|
||||||
|
function getMessage(overrides = {}) {
|
||||||
|
return deepMerge(
|
||||||
|
{
|
||||||
|
id: 8092,
|
||||||
|
title: "Test ToPic 4422",
|
||||||
|
fancy_title: "Test topic 4422",
|
||||||
|
slug: "test-topic-4422",
|
||||||
|
posts_count: 1,
|
||||||
|
reply_count: 0,
|
||||||
|
highest_post_number: 2,
|
||||||
|
image_url: null,
|
||||||
|
created_at: "2019-07-26T01:29:24.008Z",
|
||||||
|
last_posted_at: "2019-07-26T01:29:24.177Z",
|
||||||
|
bumped: true,
|
||||||
|
bumped_at: "2019-07-26T01:29:24.177Z",
|
||||||
|
unseen: false,
|
||||||
|
last_read_post_number: 2,
|
||||||
|
unread_posts: 0,
|
||||||
|
pinned: false,
|
||||||
|
unpinned: null,
|
||||||
|
visible: true,
|
||||||
|
closed: false,
|
||||||
|
archived: false,
|
||||||
|
notification_level: 3,
|
||||||
|
bookmarked: false,
|
||||||
|
bookmarks: [],
|
||||||
|
liked: false,
|
||||||
|
views: 5,
|
||||||
|
like_count: 0,
|
||||||
|
has_summary: false,
|
||||||
|
archetype: "private_message",
|
||||||
|
last_poster_username: "mixtape",
|
||||||
|
category_id: null,
|
||||||
|
pinned_globally: false,
|
||||||
|
featured_link: null,
|
||||||
|
posters: [
|
||||||
|
{
|
||||||
|
extras: "latest single",
|
||||||
|
description: "Original Poster, Most Recent Poster",
|
||||||
|
user_id: 13,
|
||||||
|
primary_group_id: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
participants: [
|
||||||
|
{
|
||||||
|
extras: "latest",
|
||||||
|
description: null,
|
||||||
|
user_id: 13,
|
||||||
|
primary_group_id: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
overrides
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGroupMessageSummaryNotification(overrides = {}) {
|
||||||
|
return deepMerge(
|
||||||
|
{
|
||||||
|
id: 9492,
|
||||||
|
user_id: 1,
|
||||||
|
notification_type: 16,
|
||||||
|
read: true,
|
||||||
|
high_priority: false,
|
||||||
|
created_at: "2022-08-05T17:27:24.873Z",
|
||||||
|
post_number: null,
|
||||||
|
topic_id: null,
|
||||||
|
fancy_title: null,
|
||||||
|
slug: null,
|
||||||
|
data: {
|
||||||
|
group_id: 1,
|
||||||
|
group_name: "jokers",
|
||||||
|
inbox_count: 4,
|
||||||
|
username: "joker.leader",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
overrides
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
module("Integration | Component | user-menu | messages-list", function (hooks) {
|
module("Integration | Component | user-menu | messages-list", function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
const template = hbs`<UserMenu::MessagesList/>`;
|
const template = hbs`<UserMenu::MessagesList/>`;
|
||||||
|
|
||||||
test("renders notifications on top and messages on bottom", async function (assert) {
|
test("renders unread PM notifications first followed by messages and read group_message_summary notifications", async function (assert) {
|
||||||
|
pretender.get("/u/eviltrout/user-menu-private-messages", () => {
|
||||||
|
const copy = cloneJSON(
|
||||||
|
UserMenuFixtures["/u/:username/user-menu-private-messages"]
|
||||||
|
);
|
||||||
|
copy.read_notifications = [getGroupMessageSummaryNotification()];
|
||||||
|
return response(copy);
|
||||||
|
});
|
||||||
|
await render(template);
|
||||||
|
const items = queryAll("ul li");
|
||||||
|
|
||||||
|
assert.strictEqual(items.length, 3);
|
||||||
|
|
||||||
|
assert.ok(items[0].classList.contains("notification"));
|
||||||
|
assert.ok(items[0].classList.contains("unread"));
|
||||||
|
assert.ok(items[0].classList.contains("private-message"));
|
||||||
|
|
||||||
|
assert.ok(items[1].classList.contains("notification"));
|
||||||
|
assert.ok(items[1].classList.contains("read"));
|
||||||
|
assert.ok(items[1].classList.contains("group-message-summary"));
|
||||||
|
|
||||||
|
assert.ok(items[2].classList.contains("message"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("does not error when there are no group_message_summary notifications", async function (assert) {
|
||||||
|
pretender.get("/u/eviltrout/user-menu-private-messages", () => {
|
||||||
|
const copy = cloneJSON(
|
||||||
|
UserMenuFixtures["/u/:username/user-menu-private-messages"]
|
||||||
|
);
|
||||||
|
copy.read_notifications = [];
|
||||||
|
return response(copy);
|
||||||
|
});
|
||||||
|
|
||||||
await render(template);
|
await render(template);
|
||||||
const items = queryAll("ul li");
|
const items = queryAll("ul li");
|
||||||
|
|
||||||
|
@ -25,6 +139,117 @@ module("Integration | Component | user-menu | messages-list", function (hooks) {
|
||||||
assert.ok(items[1].classList.contains("message"));
|
assert.ok(items[1].classList.contains("message"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("does not error when there are no messages", async function (assert) {
|
||||||
|
pretender.get("/u/eviltrout/user-menu-private-messages", () => {
|
||||||
|
const copy = cloneJSON(
|
||||||
|
UserMenuFixtures["/u/:username/user-menu-private-messages"]
|
||||||
|
);
|
||||||
|
copy.topics = [];
|
||||||
|
copy.read_notifications = [getGroupMessageSummaryNotification()];
|
||||||
|
return response(copy);
|
||||||
|
});
|
||||||
|
|
||||||
|
await render(template);
|
||||||
|
const items = queryAll("ul li");
|
||||||
|
|
||||||
|
assert.strictEqual(items.length, 2);
|
||||||
|
|
||||||
|
assert.ok(items[0].classList.contains("notification"));
|
||||||
|
assert.ok(items[0].classList.contains("unread"));
|
||||||
|
assert.ok(items[0].classList.contains("private-message"));
|
||||||
|
|
||||||
|
assert.ok(items[1].classList.contains("notification"));
|
||||||
|
assert.ok(items[1].classList.contains("read"));
|
||||||
|
assert.ok(items[1].classList.contains("group-message-summary"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("merge-sorts group_message_summary notifications and messages", async function (assert) {
|
||||||
|
pretender.get("/u/eviltrout/user-menu-private-messages", () => {
|
||||||
|
const copy = cloneJSON(
|
||||||
|
UserMenuFixtures["/u/:username/user-menu-private-messages"]
|
||||||
|
);
|
||||||
|
copy.unread_notifications = [];
|
||||||
|
copy.topics = [
|
||||||
|
getMessage({
|
||||||
|
bumped_at: "2014-07-26T01:29:24.177Z",
|
||||||
|
fancy_title: "Test Topic 0003",
|
||||||
|
}),
|
||||||
|
getMessage({
|
||||||
|
bumped_at: "2012-07-26T01:29:24.177Z",
|
||||||
|
fancy_title: "Test Topic 0002",
|
||||||
|
}),
|
||||||
|
getMessage({
|
||||||
|
bumped_at: "2010-07-26T01:29:24.177Z",
|
||||||
|
fancy_title: "Test Topic 0001",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
copy.read_notifications = [
|
||||||
|
getGroupMessageSummaryNotification({
|
||||||
|
created_at: "2015-07-26T01:29:24.177Z",
|
||||||
|
data: {
|
||||||
|
inbox_count: 13,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
getGroupMessageSummaryNotification({
|
||||||
|
created_at: "2013-07-26T01:29:24.177Z",
|
||||||
|
data: {
|
||||||
|
inbox_count: 12,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
getGroupMessageSummaryNotification({
|
||||||
|
created_at: "2011-07-26T01:29:24.177Z",
|
||||||
|
data: {
|
||||||
|
inbox_count: 11,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
return response(copy);
|
||||||
|
});
|
||||||
|
await render(template);
|
||||||
|
const items = queryAll("ul li");
|
||||||
|
|
||||||
|
assert.strictEqual(items.length, 6);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
items[0].textContent.trim(),
|
||||||
|
I18n.t("notifications.group_message_summary", {
|
||||||
|
count: 13,
|
||||||
|
group_name: "jokers",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
items[1].textContent.trim().replaceAll(/\s+/g, " "),
|
||||||
|
"mixtape Test Topic 0003"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
items[2].textContent.trim(),
|
||||||
|
I18n.t("notifications.group_message_summary", {
|
||||||
|
count: 12,
|
||||||
|
group_name: "jokers",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
items[3].textContent.trim().replaceAll(/\s+/g, " "),
|
||||||
|
"mixtape Test Topic 0002"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
items[4].textContent.trim(),
|
||||||
|
I18n.t("notifications.group_message_summary", {
|
||||||
|
count: 11,
|
||||||
|
group_name: "jokers",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
items[5].textContent.trim().replaceAll(/\s+/g, " "),
|
||||||
|
"mixtape Test Topic 0001"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("show all link", async function (assert) {
|
test("show all link", async function (assert) {
|
||||||
await render(template);
|
await render(template);
|
||||||
const link = query(".panel-body-bottom .show-all");
|
const link = query(".panel-body-bottom .show-all");
|
||||||
|
@ -66,7 +291,11 @@ module("Integration | Component | user-menu | messages-list", function (hooks) {
|
||||||
|
|
||||||
test("empty state (aka blank page syndrome)", async function (assert) {
|
test("empty state (aka blank page syndrome)", async function (assert) {
|
||||||
pretender.get("/u/eviltrout/user-menu-private-messages", () => {
|
pretender.get("/u/eviltrout/user-menu-private-messages", () => {
|
||||||
return response({ notifications: [], topics: [] });
|
return response({
|
||||||
|
unread_notifications: [],
|
||||||
|
topics: [],
|
||||||
|
read_notifications: [],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
await render(template);
|
await render(template);
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
|
|
|
@ -1746,11 +1746,10 @@ class UsersController < ApplicationController
|
||||||
raise Discourse::InvalidAccess.new("username doesn't match current_user's username")
|
raise Discourse::InvalidAccess.new("username doesn't match current_user's username")
|
||||||
end
|
end
|
||||||
|
|
||||||
reminder_notifications = Notification.unread_type(
|
reminder_notifications = Notification
|
||||||
current_user,
|
.for_user_menu(current_user.id, limit: USER_MENU_LIST_LIMIT)
|
||||||
Notification.types[:bookmark_reminder],
|
.unread
|
||||||
USER_MENU_LIST_LIMIT
|
.where(notification_type: Notification.types[:bookmark_reminder])
|
||||||
)
|
|
||||||
|
|
||||||
if reminder_notifications.size < USER_MENU_LIST_LIMIT
|
if reminder_notifications.size < USER_MENU_LIST_LIMIT
|
||||||
exclude_bookmark_ids = reminder_notifications
|
exclude_bookmark_ids = reminder_notifications
|
||||||
|
@ -1803,29 +1802,37 @@ class UsersController < ApplicationController
|
||||||
raise Discourse::InvalidAccess.new("personal messages are disabled.")
|
raise Discourse::InvalidAccess.new("personal messages are disabled.")
|
||||||
end
|
end
|
||||||
|
|
||||||
message_notifications = Notification.unread_type(
|
unread_notifications = Notification
|
||||||
current_user,
|
.for_user_menu(current_user.id, limit: USER_MENU_LIST_LIMIT)
|
||||||
Notification.types[:private_message],
|
.unread
|
||||||
USER_MENU_LIST_LIMIT
|
.where(notification_type: [Notification.types[:private_message], Notification.types[:group_message_summary]])
|
||||||
)
|
.to_a
|
||||||
|
|
||||||
if message_notifications.size < USER_MENU_LIST_LIMIT
|
if unread_notifications.size < USER_MENU_LIST_LIMIT
|
||||||
exclude_topic_ids = message_notifications.map(&:topic_id).uniq
|
exclude_topic_ids = unread_notifications.filter_map(&:topic_id).uniq
|
||||||
|
limit = USER_MENU_LIST_LIMIT - unread_notifications.size
|
||||||
messages_list = TopicQuery.new(
|
messages_list = TopicQuery.new(
|
||||||
current_user,
|
current_user,
|
||||||
per_page: USER_MENU_LIST_LIMIT - message_notifications.size
|
per_page: limit
|
||||||
).list_private_messages(current_user) do |query|
|
).list_private_messages_direct_and_groups(current_user) do |query|
|
||||||
if exclude_topic_ids.present?
|
if exclude_topic_ids.present?
|
||||||
query.where("topics.id NOT IN (?)", exclude_topic_ids)
|
query.where("topics.id NOT IN (?)", exclude_topic_ids)
|
||||||
else
|
else
|
||||||
query
|
query
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
read_notifications = Notification
|
||||||
|
.for_user_menu(current_user.id, limit: limit)
|
||||||
|
.where(
|
||||||
|
read: true,
|
||||||
|
notification_type: Notification.types[:group_message_summary],
|
||||||
|
)
|
||||||
|
.to_a
|
||||||
end
|
end
|
||||||
|
|
||||||
if message_notifications.present?
|
if unread_notifications.present?
|
||||||
serialized_notifications = ActiveModel::ArraySerializer.new(
|
serialized_unread_notifications = ActiveModel::ArraySerializer.new(
|
||||||
message_notifications,
|
unread_notifications,
|
||||||
each_serializer: NotificationSerializer,
|
each_serializer: NotificationSerializer,
|
||||||
scope: guardian
|
scope: guardian
|
||||||
)
|
)
|
||||||
|
@ -1840,8 +1847,17 @@ class UsersController < ApplicationController
|
||||||
)[:topics]
|
)[:topics]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if read_notifications.present?
|
||||||
|
serialized_read_notifications = ActiveModel::ArraySerializer.new(
|
||||||
|
read_notifications,
|
||||||
|
each_serializer: NotificationSerializer,
|
||||||
|
scope: guardian
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
render json: {
|
render json: {
|
||||||
notifications: serialized_notifications || [],
|
unread_notifications: serialized_unread_notifications || [],
|
||||||
|
read_notifications: serialized_read_notifications || [],
|
||||||
topics: serialized_messages || []
|
topics: serialized_messages || []
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -2027,13 +2043,4 @@ class UsersController < ApplicationController
|
||||||
|
|
||||||
{ users: ActiveModel::ArraySerializer.new(users, each_serializer: each_serializer).as_json }
|
{ users: ActiveModel::ArraySerializer.new(users, each_serializer: each_serializer).as_json }
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_unread_notifications_of_type(type, limit)
|
|
||||||
current_user
|
|
||||||
.notifications
|
|
||||||
.visible
|
|
||||||
.includes(:topic)
|
|
||||||
.where(read: false, notification_type: type)
|
|
||||||
.limit(limit)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,14 +15,26 @@ class Notification < ActiveRecord::Base
|
||||||
scope :recent, lambda { |n = nil| n ||= 10; order('notifications.created_at desc').limit(n) }
|
scope :recent, lambda { |n = nil| n ||= 10; order('notifications.created_at desc').limit(n) }
|
||||||
scope :visible , lambda { joins('LEFT JOIN topics ON notifications.topic_id = topics.id')
|
scope :visible , lambda { joins('LEFT JOIN topics ON notifications.topic_id = topics.id')
|
||||||
.where('topics.id IS NULL OR topics.deleted_at IS NULL') }
|
.where('topics.id IS NULL OR topics.deleted_at IS NULL') }
|
||||||
scope :unread_type, ->(user, type, limit = 20) do
|
scope :unread_type, ->(user, type, limit = 30) do
|
||||||
where(user_id: user.id, read: false, notification_type: type).visible.includes(:topic).limit(limit)
|
unread_types(user, [type], limit)
|
||||||
end
|
end
|
||||||
scope :prioritized, ->(limit = nil) do
|
scope :unread_types, ->(user, types, limit = 30) do
|
||||||
|
where(user_id: user.id, read: false, notification_type: types)
|
||||||
|
.visible
|
||||||
|
.includes(:topic)
|
||||||
|
.limit(limit)
|
||||||
|
end
|
||||||
|
scope :prioritized, ->() do
|
||||||
order("notifications.high_priority AND NOT notifications.read DESC")
|
order("notifications.high_priority AND NOT notifications.read DESC")
|
||||||
.order("NOT notifications.read DESC")
|
.order("NOT notifications.read DESC")
|
||||||
.order("notifications.created_at DESC")
|
.order("notifications.created_at DESC")
|
||||||
.limit(limit || 30)
|
end
|
||||||
|
scope :for_user_menu, ->(user_id, limit: 30) do
|
||||||
|
where(user_id: user_id)
|
||||||
|
.visible
|
||||||
|
.prioritized
|
||||||
|
.includes(:topic)
|
||||||
|
.limit(limit)
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_accessor :skip_send_email
|
attr_accessor :skip_send_email
|
||||||
|
@ -226,7 +238,8 @@ class Notification < ActiveRecord::Base
|
||||||
notifications = user.notifications
|
notifications = user.notifications
|
||||||
.includes(:topic)
|
.includes(:topic)
|
||||||
.visible
|
.visible
|
||||||
.prioritized(count)
|
.prioritized
|
||||||
|
.limit(count)
|
||||||
|
|
||||||
if types.present?
|
if types.present?
|
||||||
notifications = notifications.where(notification_type: types)
|
notifications = notifications.where(notification_type: types)
|
||||||
|
|
|
@ -5,14 +5,16 @@ class TopicQuery
|
||||||
def list_private_messages(user, &blk)
|
def list_private_messages(user, &blk)
|
||||||
list = private_messages_for(user, :user)
|
list = private_messages_for(user, :user)
|
||||||
list = not_archived(list, user)
|
list = not_archived(list, user)
|
||||||
|
list = have_posts_from_others(list, user)
|
||||||
|
|
||||||
list = list.where(<<~SQL)
|
create_list(:private_messages, {}, list, &blk)
|
||||||
NOT (
|
end
|
||||||
topics.participant_count = 1
|
|
||||||
AND topics.user_id = #{user.id.to_i}
|
def list_private_messages_direct_and_groups(user, &blk)
|
||||||
AND topics.moderator_posts_count = 0
|
list = private_messages_for(user, :all)
|
||||||
)
|
list = not_archived(list, user)
|
||||||
SQL
|
list = not_archived_in_groups(list)
|
||||||
|
list = have_posts_from_others(list, user)
|
||||||
|
|
||||||
create_list(:private_messages, {}, list, &blk)
|
create_list(:private_messages, {}, list, &blk)
|
||||||
end
|
end
|
||||||
|
@ -252,6 +254,20 @@ class TopicQuery
|
||||||
.where('um.user_id IS NULL')
|
.where('um.user_id IS NULL')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def not_archived_in_groups(list)
|
||||||
|
list.left_joins(:group_archived_messages).where(group_archived_messages: { id: nil })
|
||||||
|
end
|
||||||
|
|
||||||
|
def have_posts_from_others(list, user)
|
||||||
|
list.where(<<~SQL)
|
||||||
|
NOT (
|
||||||
|
topics.participant_count = 1
|
||||||
|
AND topics.user_id = #{user.id.to_i}
|
||||||
|
AND topics.moderator_posts_count = 0
|
||||||
|
)
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
def group
|
def group
|
||||||
@group ||= begin
|
@group ||= begin
|
||||||
Group
|
Group
|
||||||
|
|
|
@ -143,6 +143,25 @@ Fabricator(:private_message_post, from: :post) do
|
||||||
raw "Ssshh! This is our secret conversation!"
|
raw "Ssshh! This is our secret conversation!"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Fabricator(:group_private_message_post, from: :post) do
|
||||||
|
transient :recipients
|
||||||
|
user
|
||||||
|
topic do |attrs|
|
||||||
|
Fabricate(:private_message_topic,
|
||||||
|
user: attrs[:user],
|
||||||
|
created_at: attrs[:created_at],
|
||||||
|
subtype: TopicSubtype.user_to_user,
|
||||||
|
topic_allowed_users: [
|
||||||
|
Fabricate.build(:topic_allowed_user, user: attrs[:user]),
|
||||||
|
],
|
||||||
|
topic_allowed_groups: [
|
||||||
|
Fabricate.build(:topic_allowed_group, group: attrs[:recipients] || Fabricate(:group))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
raw "Ssshh! This is our group secret conversation!"
|
||||||
|
end
|
||||||
|
|
||||||
Fabricator(:private_message_post_one_user, from: :post) do
|
Fabricator(:private_message_post_one_user, from: :post) do
|
||||||
user
|
user
|
||||||
topic do |attrs|
|
topic do |attrs|
|
||||||
|
|
|
@ -5797,9 +5797,21 @@ RSpec.describe UsersController do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#user_menu_messages" do
|
describe "#user_menu_messages" do
|
||||||
|
fab!(:group1) { Fabricate(:group, has_messages: true, users: [user]) }
|
||||||
|
fab!(:group2) { Fabricate(:group, has_messages: true, users: [user, user1]) }
|
||||||
|
fab!(:group3) { Fabricate(:group, has_messages: true, users: [user1]) }
|
||||||
|
|
||||||
fab!(:message_without_notification) { Fabricate(:private_message_post, recipient: user).topic }
|
fab!(:message_without_notification) { Fabricate(:private_message_post, recipient: user).topic }
|
||||||
fab!(:message_with_read_notification) { Fabricate(:private_message_post, recipient: user).topic }
|
fab!(:message_with_read_notification) { Fabricate(:private_message_post, recipient: user).topic }
|
||||||
fab!(:message_with_unread_notification) { Fabricate(:private_message_post, recipient: user).topic }
|
fab!(:message_with_unread_notification) { Fabricate(:private_message_post, recipient: user).topic }
|
||||||
|
fab!(:archived_message) { Fabricate(:private_message_post, recipient: user).topic }
|
||||||
|
|
||||||
|
fab!(:group_message1) { Fabricate(:group_private_message_post, recipients: group1).topic }
|
||||||
|
fab!(:group_message2) { Fabricate(:group_private_message_post, recipients: group2).topic }
|
||||||
|
fab!(:group_message3) { Fabricate(:group_private_message_post, recipients: group3).topic }
|
||||||
|
|
||||||
|
fab!(:archived_group_message1) { Fabricate(:group_private_message_post, recipients: group1).topic }
|
||||||
|
fab!(:archived_group_message2) { Fabricate(:group_private_message_post, recipients: group2).topic }
|
||||||
|
|
||||||
fab!(:user1_message_without_notification) do
|
fab!(:user1_message_without_notification) do
|
||||||
Fabricate(:private_message_post, recipient: user1).topic
|
Fabricate(:private_message_post, recipient: user1).topic
|
||||||
|
@ -5810,16 +5822,18 @@ RSpec.describe UsersController do
|
||||||
fab!(:user1_message_with_unread_notification) do
|
fab!(:user1_message_with_unread_notification) do
|
||||||
Fabricate(:private_message_post, recipient: user1).topic
|
Fabricate(:private_message_post, recipient: user1).topic
|
||||||
end
|
end
|
||||||
|
fab!(:user1_archived_message) { Fabricate(:private_message_post, recipient: user1).topic }
|
||||||
|
|
||||||
fab!(:unread_notification) do
|
fab!(:unread_pm_notification) do
|
||||||
Fabricate(
|
Fabricate(
|
||||||
:private_message_notification,
|
:private_message_notification,
|
||||||
read: false,
|
read: false,
|
||||||
user: user,
|
user: user,
|
||||||
topic: message_with_unread_notification
|
topic: message_with_unread_notification,
|
||||||
|
created_at: 4.minutes.ago
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
fab!(:read_notification) do
|
fab!(:read_pm_notification) do
|
||||||
Fabricate(
|
Fabricate(
|
||||||
:private_message_notification,
|
:private_message_notification,
|
||||||
read: true,
|
read: true,
|
||||||
|
@ -5828,7 +5842,27 @@ RSpec.describe UsersController do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
fab!(:user1_unread_notification) do
|
fab!(:unread_group_message_summary_notification) do
|
||||||
|
Fabricate(
|
||||||
|
:notification,
|
||||||
|
read: false,
|
||||||
|
user: user,
|
||||||
|
notification_type: Notification.types[:group_message_summary],
|
||||||
|
created_at: 2.minutes.ago
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
fab!(:read_group_message_summary_notification) do
|
||||||
|
Fabricate(
|
||||||
|
:notification,
|
||||||
|
read: true,
|
||||||
|
user: user,
|
||||||
|
notification_type: Notification.types[:group_message_summary],
|
||||||
|
created_at: 1.minutes.ago
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
fab!(:user1_unread_pm_notification) do
|
||||||
Fabricate(
|
Fabricate(
|
||||||
:private_message_notification,
|
:private_message_notification,
|
||||||
read: false,
|
read: false,
|
||||||
|
@ -5836,7 +5870,7 @@ RSpec.describe UsersController do
|
||||||
topic: user1_message_with_unread_notification
|
topic: user1_message_with_unread_notification
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
fab!(:user1_read_notification) do
|
fab!(:user1_read_pm_notification) do
|
||||||
Fabricate(
|
Fabricate(
|
||||||
:private_message_notification,
|
:private_message_notification,
|
||||||
read: true,
|
read: true,
|
||||||
|
@ -5845,6 +5879,30 @@ RSpec.describe UsersController do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
fab!(:user1_unread_group_message_summary_notification) do
|
||||||
|
Fabricate(
|
||||||
|
:notification,
|
||||||
|
read: false,
|
||||||
|
user: user1,
|
||||||
|
notification_type: Notification.types[:group_message_summary],
|
||||||
|
)
|
||||||
|
end
|
||||||
|
fab!(:user1_read_group_message_summary_notification) do
|
||||||
|
Fabricate(
|
||||||
|
:notification,
|
||||||
|
read: true,
|
||||||
|
user: user1,
|
||||||
|
notification_type: Notification.types[:group_message_summary],
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
UserArchivedMessage.archive!(user.id, archived_message)
|
||||||
|
UserArchivedMessage.archive!(user1.id, user1_archived_message)
|
||||||
|
GroupArchivedMessage.archive!(group1.id, archived_group_message1)
|
||||||
|
GroupArchivedMessage.archive!(group2.id, archived_group_message2)
|
||||||
|
end
|
||||||
|
|
||||||
context "when logged out" do
|
context "when logged out" do
|
||||||
it "responds with 404" do
|
it "responds with 404" do
|
||||||
get "/u/#{user.username}/user-menu-private-messages"
|
get "/u/#{user.username}/user-menu-private-messages"
|
||||||
|
@ -5873,33 +5931,62 @@ RSpec.describe UsersController do
|
||||||
get "/u/#{user.username}/user-menu-private-messages"
|
get "/u/#{user.username}/user-menu-private-messages"
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
notifications = response.parsed_body["notifications"]
|
unread_notifications = response.parsed_body["unread_notifications"]
|
||||||
expect(notifications.map { |notification| notification["id"] }).to contain_exactly(
|
expect(unread_notifications.map { |notification| notification["id"] }).to eq([
|
||||||
unread_notification.id
|
unread_pm_notification.id,
|
||||||
|
unread_group_message_summary_notification.id
|
||||||
|
])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "sends an array of read group_message_summary notifications" do
|
||||||
|
read_group_message_summary_notification2 = Fabricate(
|
||||||
|
:notification,
|
||||||
|
read: true,
|
||||||
|
user: user,
|
||||||
|
notification_type: Notification.types[:group_message_summary],
|
||||||
|
created_at: 5.minutes.ago
|
||||||
)
|
)
|
||||||
|
get "/u/#{user.username}/user-menu-private-messages"
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
read_notifications = response.parsed_body["read_notifications"]
|
||||||
|
expect(read_notifications.map { |notification| notification["id"] }).to eq([
|
||||||
|
read_group_message_summary_notification.id,
|
||||||
|
read_group_message_summary_notification2.id
|
||||||
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
it "responds with an array of PM topics that are not associated with any of the unread private_message notifications" do
|
it "responds with an array of PM topics that are not associated with any of the unread private_message notifications" do
|
||||||
|
group_message1.update!(bumped_at: 1.minutes.ago)
|
||||||
|
message_without_notification.update!(bumped_at: 3.minutes.ago)
|
||||||
|
group_message2.update!(bumped_at: 6.minutes.ago)
|
||||||
|
message_with_read_notification.update!(bumped_at: 10.minutes.ago)
|
||||||
|
read_group_message_summary_notification.destroy!
|
||||||
|
|
||||||
get "/u/#{user.username}/user-menu-private-messages"
|
get "/u/#{user.username}/user-menu-private-messages"
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
topics = response.parsed_body["topics"]
|
topics = response.parsed_body["topics"]
|
||||||
expect(topics.map { |topic| topic["id"] }).to contain_exactly(
|
expect(topics.map { |topic| topic["id"] }).to eq([
|
||||||
|
group_message1.id,
|
||||||
message_without_notification.id,
|
message_without_notification.id,
|
||||||
|
group_message2.id,
|
||||||
message_with_read_notification.id
|
message_with_read_notification.id
|
||||||
)
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
it "fills up the remaining of the USER_MENU_LIST_LIMIT limit with PM topics" do
|
it "fills up the remaining of the USER_MENU_LIST_LIMIT limit with PM topics" do
|
||||||
stub_const(UsersController, "USER_MENU_LIST_LIMIT", 2) do
|
stub_const(UsersController, "USER_MENU_LIST_LIMIT", 3) do
|
||||||
get "/u/#{user.username}/user-menu-private-messages"
|
get "/u/#{user.username}/user-menu-private-messages"
|
||||||
end
|
end
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
notifications = response.parsed_body["notifications"]
|
unread_notifications = response.parsed_body["unread_notifications"]
|
||||||
expect(notifications.size).to eq(1)
|
expect(unread_notifications.size).to eq(2)
|
||||||
|
|
||||||
topics = response.parsed_body["topics"]
|
topics = response.parsed_body["topics"]
|
||||||
|
read_notifications = response.parsed_body["read_notifications"]
|
||||||
expect(topics.size).to eq(1)
|
expect(topics.size).to eq(1)
|
||||||
|
expect(read_notifications.size).to eq(1)
|
||||||
|
|
||||||
message2 = Fabricate(:private_message_post, recipient: user).topic
|
message2 = Fabricate(:private_message_post, recipient: user).topic
|
||||||
Fabricate(
|
Fabricate(
|
||||||
|
@ -5913,11 +6000,13 @@ RSpec.describe UsersController do
|
||||||
get "/u/#{user.username}/user-menu-private-messages"
|
get "/u/#{user.username}/user-menu-private-messages"
|
||||||
end
|
end
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
notifications = response.parsed_body["notifications"]
|
unread_notifications = response.parsed_body["unread_notifications"]
|
||||||
expect(notifications.size).to eq(2)
|
expect(unread_notifications.size).to eq(2)
|
||||||
|
|
||||||
topics = response.parsed_body["topics"]
|
topics = response.parsed_body["topics"]
|
||||||
|
read_notifications = response.parsed_body["read_notifications"]
|
||||||
expect(topics.size).to eq(0)
|
expect(topics.size).to eq(0)
|
||||||
|
expect(read_notifications.size).to eq(0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue
Block a user