FEATURE: option to sort user and group private messages. ()

The UI will be the same as the one we're using in the topic list in "latest", "top" etc.,
This commit is contained in:
Vinoth Kannan 2024-01-10 13:33:30 +05:30 committed by GitHub
parent 9e758a3ae7
commit 992211350a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 173 additions and 33 deletions

@ -8,6 +8,9 @@
@bulkSelectHelper={{this.bulkSelectHelper}}
@canBulkSelect={{this.canBulkSelect}}
@tagsForUser={{this.tagsForUser}}
@changeSort={{this.changeSort}}
@order={{this.order}}
@ascending={{this.ascending}}
/>
{{else}}
{{#unless this.loadingMore}}

@ -1,7 +1,10 @@
import { tracked } from "@glimmer/tracking";
import Controller from "@ember/controller";
import { action } from "@ember/object";
import { or, reads } from "@ember/object/computed";
import { isNone } from "@ember/utils";
import BulkSelectHelper from "discourse/lib/bulk-select-helper";
import { defineTrackedProperty } from "discourse/lib/tracked-tools";
import Topic from "discourse/models/topic";
import {
NEW_FILTER,
@ -9,12 +12,20 @@ import {
} from "discourse/routes/build-private-messages-route";
import discourseComputed from "discourse-common/utils/decorators";
export const queryParams = {
ascending: { replace: true, refreshModel: true, default: false },
order: { replace: true, refreshModel: true },
};
// Lists of topics on a user's page.
export default class UserTopicsListController extends Controller {
@tracked model;
hideCategory = false;
showPosters = false;
channel = null;
tagsForUser = null;
queryParams = Object.keys(queryParams);
bulkSelectHelper = new BulkSelectHelper(this);
@ -23,6 +34,13 @@ export default class UserTopicsListController extends Controller {
@or("currentUser.canManageTopic", "showDismissRead", "showResetNew")
canBulkSelect;
constructor() {
super(...arguments);
for (const [name, info] of Object.entries(queryParams)) {
defineTrackedProperty(this, name, info.default);
}
}
get bulkSelectEnabled() {
return this.bulkSelectHelper.bulkSelectEnabled;
}
@ -54,6 +72,28 @@ export default class UserTopicsListController extends Controller {
this.pmTopicTrackingState.stopIncomingTracking();
}
@action
changeSort(sortBy) {
if (sortBy === this.resolvedOrder) {
this.ascending = !this.resolvedAscending;
} else {
this.ascending = false;
}
this.order = sortBy;
}
get resolvedAscending() {
if (isNone(this.ascending)) {
return this.model.get("params.ascending") === "true";
} else {
return this.ascending.toString() === "true";
}
}
get resolvedOrder() {
return this.order ?? this.model.get("params.order") ?? "activity";
}
@action
resetNew() {
const topicIds = this.selected

@ -728,3 +728,12 @@ export function allowOnlyNumericInput(event, allowNegative = false) {
}
}
}
export function cleanNullQueryParams(params) {
for (const [key, val] of Object.entries(params)) {
if (val === "undefined" || val === "null") {
params[key] = null;
}
}
return params;
}

@ -1,5 +1,6 @@
import { capitalize } from "@ember/string";
import { findOrResetCachedTopicList } from "discourse/lib/cached-topic-list";
import { cleanNullQueryParams } from "discourse/lib/utilities";
import createPMRoute from "discourse/routes/build-private-messages-route";
import I18n from "discourse-i18n";
@ -21,7 +22,7 @@ export default (inboxType, filter) => {
}
},
model() {
model(params = {}) {
const username = this.modelFor("user").get("username_lower");
const groupName = this.modelFor("userPrivateMessages.group").name;
@ -36,18 +37,25 @@ export default (inboxType, filter) => {
topicListFilter
);
return lastTopicList
? lastTopicList
: this.store
.findFiltered("topicList", { filter: topicListFilter })
.then((topicList) => {
// andrei: we agreed that this is an anti pattern,
// it's better to avoid mutating a rest model like this
// this place we'll be refactored later
// see https://github.com/discourse/discourse/pull/14313#discussion_r708784704
topicList.set("emptyState", this.emptyState());
return topicList;
});
if (lastTopicList) {
return lastTopicList;
}
params = cleanNullQueryParams(params);
return this.store
.findFiltered("topicList", {
filter: topicListFilter,
params,
})
.then((topicList) => {
// andrei: we agreed that this is an anti pattern,
// it's better to avoid mutating a rest model like this
// this place we'll be refactored later
// see https://github.com/discourse/discourse/pull/14313#discussion_r708784704
topicList.set("emptyState", this.emptyState());
return topicList;
});
},
afterModel(model) {

@ -1,6 +1,7 @@
import { action } from "@ember/object";
import { htmlSafe } from "@ember/template";
import { findOrResetCachedTopicList } from "discourse/lib/cached-topic-list";
import { cleanNullQueryParams } from "discourse/lib/utilities";
import UserAction from "discourse/models/user-action";
import UserTopicListRoute from "discourse/routes/user-topic-list";
import getURL from "discourse-common/lib/get-url";
@ -24,7 +25,7 @@ export default (inboxType, path, filter) => {
];
},
model() {
model(params = {}) {
const topicListFilter =
"topics/" + path + "/" + this.modelFor("user").get("username_lower");
@ -33,18 +34,25 @@ export default (inboxType, path, filter) => {
topicListFilter
);
return lastTopicList
? lastTopicList
: this.store
.findFiltered("topicList", { filter: topicListFilter })
.then((model) => {
// andrei: we agreed that this is an anti pattern,
// it's better to avoid mutating a rest model like this
// this place we'll be refactored later
// see https://github.com/discourse/discourse/pull/14313#discussion_r708784704
model.set("emptyState", this.emptyState());
return model;
});
if (lastTopicList) {
return lastTopicList;
}
params = cleanNullQueryParams(params);
return this.store
.findFiltered("topicList", {
filter: topicListFilter,
params,
})
.then((model) => {
// andrei: we agreed that this is an anti pattern,
// it's better to avoid mutating a rest model like this
// this place we'll be refactored later
// see https://github.com/discourse/discourse/pull/14313#discussion_r708784704
model.set("emptyState", this.emptyState());
return model;
});
},
setupController() {
@ -65,6 +73,17 @@ export default (inboxType, path, filter) => {
group: null,
inbox: inboxType,
});
let ascending = userTopicsListController.ascending;
if (ascending === "true") {
ascending = true;
} else if (ascending === "false") {
ascending = false;
}
userTopicsListController.setProperties({
ascending,
});
userTopicsListController.bulkSelectHelper.clear();
userTopicsListController.subscribe();

@ -4,7 +4,7 @@ import { isEmpty } from "@ember/utils";
import { queryParams, resetParams } from "discourse/controllers/discovery/list";
import { disableImplicitInjections } from "discourse/lib/implicit-injections";
import { setTopicList } from "discourse/lib/topic-list-tracker";
import { defaultHomepage } from "discourse/lib/utilities";
import { cleanNullQueryParams, defaultHomepage } from "discourse/lib/utilities";
import Session from "discourse/models/session";
import Site from "discourse/models/site";
import DiscourseRoute from "discourse/routes/discourse";
@ -60,11 +60,7 @@ export async function findTopicList(
if (!list) {
// Clean up any string parameters that might slip through
filterParams ||= {};
for (const [key, val] of Object.entries(filterParams)) {
if (val === "undefined" || val === "null") {
filterParams[key] = null;
}
}
filterParams = cleanNullQueryParams(filterParams);
list = await store.findFiltered("topicList", {
filter,

@ -1,3 +1,4 @@
import { queryParams } from "discourse/controllers/user-topics-list";
import { setTopicList } from "discourse/lib/topic-list-tracker";
import ViewingActionType from "discourse/mixins/viewing-action-type";
import DiscourseRoute from "discourse/routes/discourse";
@ -6,6 +7,8 @@ export default DiscourseRoute.extend(ViewingActionType, {
templateName: "user-topics-list",
controllerName: "user-topics-list",
queryParams,
setupController(controller, model) {
setTopicList(model);

@ -50,6 +50,9 @@
@tagsForUser={{this.tagsForUser}}
@canBulkSelect={{this.canBulkSelect}}
@bulkSelectHelper={{this.bulkSelectHelper}}
@changeSort={{this.changeSort}}
@order={{this.order}}
@ascending={{this.ascending}}
/>
<TopicDismissButtons

@ -298,6 +298,20 @@ const publishGroupNewToMessageBus = function (opts) {
);
};
acceptance("User Private Messages - sorting", function (needs) {
withGroupMessagesSetup(needs);
test("order by posts_count", async function (assert) {
await visit("/u/eviltrout/messages");
assert.ok(exists(".topic-list-header th.posts.sortable"), "is sortable");
await click(".topic-list-header th.posts.sortable");
assert.ok(exists(".topic-list-header th.posts.sortable.sorting"), "sorted");
});
});
acceptance(
"User Private Messages - user with group messages",
function (needs) {

@ -263,8 +263,8 @@ class TopicQuery
.joins(
"LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{user.id.to_i})",
)
.order("topics.bumped_at DESC")
result = apply_ordering(result, options) if !options[:skip_ordering]
result = result.includes(:tags) if SiteSetting.tagging_enabled
result = result.limit(options[:per_page]) unless options[:limit] == false
result = result.visible if options[:visible] || @user.nil? || @user.regular?

@ -398,6 +398,25 @@ RSpec.describe ListController do
expect(response.status).to eq(404)
end
it "should sort group private messages by posts_count" do
topic2 = Fabricate(:private_message_topic, allowed_groups: [group])
topic3 = Fabricate(:private_message_topic, allowed_groups: [group])
2.times { Fabricate(:post, topic: topic2) }
Fabricate(:post, topic: topic3)
sign_in(Fabricate(:admin))
get "/topics/private-messages-group/#{user.username}/#{group.name}.json",
params: {
order: "posts",
}
expect(response.status).to eq(200)
expect(response.parsed_body["topic_list"]["topics"].map { |t| t["id"] }).to eq(
[topic2.id, topic3.id, topic.id],
)
end
end
describe "with unicode_usernames" do
@ -859,6 +878,32 @@ RSpec.describe ListController do
json = response.parsed_body
expect(json["topic_list"]["topics"].size).to eq(1)
end
it "sorts private messages by activity" do
topic_ids = []
[1.year.ago, 1.week.ago, 1.month.ago].each do |date|
pm =
Fabricate(
:private_message_topic,
user: Fabricate(:user),
created_at: date,
bumped_at: date,
)
pm.topic_allowed_users.create!(user: user)
topic_ids << pm.id
end
sign_in(user)
get "/topics/private-messages/#{user.username}.json", params: { order: "activity" }
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["topic_list"]["topics"].pluck("id")).to eq(
[topic_ids[1], topic_ids[2], topic_ids[0]],
)
end
end
describe "private_messages_sent" do