mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 15:06:26 +08:00
FEATURE: Add setting & preference for search sort default order (#24428)
This commit adds a new `search_default_sort_order` site setting, set to "relevance" by default, that controls the default sort order for the full page /search route. If the user changes the order in the dropdown on that page, we remember their preference automatically, and it takes precedence over the site setting as a default from then on. This way people who prefer e.g. Latest Post as their default can make it so.
This commit is contained in:
parent
186e415e38
commit
146da75fd7
|
@ -57,6 +57,8 @@ export default Controller.extend({
|
|||
composer: service(),
|
||||
modal: service(),
|
||||
appEvents: service(),
|
||||
siteSettings: service(),
|
||||
searchPreferencesManager: service(),
|
||||
|
||||
bulkSelectEnabled: null,
|
||||
loading: false,
|
||||
|
@ -86,6 +88,12 @@ export default Controller.extend({
|
|||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.set(
|
||||
"sortOrder",
|
||||
this.searchPreferencesManager.sortOrder ||
|
||||
this.siteSettings.search_default_sort_order
|
||||
);
|
||||
|
||||
const searchTypes = [
|
||||
{ name: I18n.t("search.type.default"), id: SEARCH_TYPE_DEFAULT },
|
||||
{
|
||||
|
@ -486,6 +494,12 @@ export default Controller.extend({
|
|||
});
|
||||
},
|
||||
|
||||
@action
|
||||
setSortOrder(value) {
|
||||
this.set("sortOrder", value);
|
||||
this.searchPreferencesManager.sortOrder = value;
|
||||
},
|
||||
|
||||
actions: {
|
||||
selectAll() {
|
||||
this.selected.addObjects(this.get("searchResultPosts").mapBy("topic"));
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import Service from "@ember/service";
|
||||
import KeyValueStore from "discourse/lib/key-value-store";
|
||||
|
||||
export default class SearchPreferencesManager extends Service {
|
||||
STORE_NAMESPACE = "discourse_search_preferences_manager_";
|
||||
|
||||
store = new KeyValueStore(this.STORE_NAMESPACE);
|
||||
|
||||
get sortOrder() {
|
||||
return this.store.getObject("sortOrder");
|
||||
}
|
||||
|
||||
set sortOrder(value) {
|
||||
this.store.setObject({ key: "sortOrder", value });
|
||||
}
|
||||
}
|
|
@ -145,7 +145,7 @@
|
|||
<ComboBox
|
||||
@value={{this.sortOrder}}
|
||||
@content={{this.sortOrders}}
|
||||
@onChange={{action (mut this.sortOrder)}}
|
||||
@onChange={{this.setSortOrder}}
|
||||
@id="search-sort-by"
|
||||
@options={{hash castInteger=true}}
|
||||
/>
|
||||
|
|
|
@ -332,6 +332,43 @@ acceptance("Search - Anonymous", function (needs) {
|
|||
});
|
||||
});
|
||||
|
||||
acceptance("Search - Default sort order", function (needs) {
|
||||
needs.user();
|
||||
needs.settings({
|
||||
search_default_sort_order: 1, // "latest"
|
||||
});
|
||||
needs.hooks.beforeEach(function () {
|
||||
this.searchPreferencesManager = this.container.lookup(
|
||||
"service:search-preferences-manager"
|
||||
);
|
||||
this.searchPreferencesManager.sortOrder = null;
|
||||
});
|
||||
needs.hooks.afterEach(function () {
|
||||
this.searchPreferencesManager.sortOrder = null;
|
||||
});
|
||||
|
||||
test("Default sort order is used if there is no preference in user key value store", async function (assert) {
|
||||
await visit("/search?q=discourse");
|
||||
|
||||
const searchSortByDropdown = selectKit("#search-sort-by");
|
||||
await searchSortByDropdown.expand();
|
||||
assert.strictEqual(searchSortByDropdown.header().value(), "1");
|
||||
});
|
||||
|
||||
test("User preference from SearchPreferencesManager key value store is used if present", async function (assert) {
|
||||
this.searchPreferencesManager = this.container.lookup(
|
||||
"service:search-preferences-manager"
|
||||
);
|
||||
this.searchPreferencesManager.sortOrder = 2; // "likes"
|
||||
|
||||
await visit("/search?q=discourse");
|
||||
|
||||
const searchSortByDropdown = selectKit("#search-sort-by");
|
||||
await searchSortByDropdown.expand();
|
||||
assert.strictEqual(searchSortByDropdown.header().value(), "2");
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("Search - Authenticated", function (needs) {
|
||||
needs.user();
|
||||
needs.settings({
|
||||
|
|
|
@ -358,7 +358,11 @@ export function applyDefaultHandlers(pretender) {
|
|||
pretender.post("/clicks/track", success);
|
||||
|
||||
pretender.get("/search", (request) => {
|
||||
if (request.queryParams.q === "discourse") {
|
||||
if (
|
||||
request.queryParams.q === "discourse" ||
|
||||
request.queryParams.q === "discourse order:latest" ||
|
||||
request.queryParams.q === "discourse order:likes"
|
||||
) {
|
||||
return response(fixturesByUrl["/search.json"]);
|
||||
} else if (request.queryParams.q === "discourse visited") {
|
||||
const obj = JSON.parse(JSON.stringify(fixturesByUrl["/search.json"]));
|
||||
|
|
29
app/models/search_sort_order_site_setting.rb
Normal file
29
app/models/search_sort_order_site_setting.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class SearchSortOrderSiteSetting < EnumSiteSetting
|
||||
def self.valid_value?(val)
|
||||
val.to_i.to_s == val.to_s && values.any? { |v| v[:value] == val.to_i }
|
||||
end
|
||||
|
||||
def self.values
|
||||
@values ||= [
|
||||
{ name: "search.relevance", value: 0, id: :relevance },
|
||||
{ name: "search.latest_post", value: 1, id: :latest },
|
||||
{ name: "search.most_liked", value: 2, id: :likes },
|
||||
{ name: "search.most_viewed", value: 3, id: :views },
|
||||
{ name: "search.latest_topic", value: 4, id: :latest_topic },
|
||||
]
|
||||
end
|
||||
|
||||
def self.value_from_id(id)
|
||||
values.find { |v| v[:id] == id }[:value]
|
||||
end
|
||||
|
||||
def self.id_from_value(value)
|
||||
values.find { |v| v[:value] == value }[:id]
|
||||
end
|
||||
|
||||
def self.translate_names?
|
||||
true
|
||||
end
|
||||
end
|
|
@ -1551,6 +1551,7 @@ en:
|
|||
search_query_log_max_size: "Maximum amount of search queries to keep"
|
||||
search_query_log_max_retention_days: "Maximum amount of time to keep search queries, in days."
|
||||
search_ignore_accents: "Ignore accents when searching for text."
|
||||
search_default_sort_order: "Default sort order for full-page search"
|
||||
category_search_priority_low_weight: "Weight applied to ranking for low category search priority."
|
||||
category_search_priority_high_weight: "Weight applied to ranking for high category search priority."
|
||||
default_composer_category: "The category used to pre-populate the category dropdown when creating a new topic."
|
||||
|
|
|
@ -2389,6 +2389,11 @@ search:
|
|||
search_page_size:
|
||||
default: 50
|
||||
hidden: true
|
||||
search_default_sort_order:
|
||||
default: 0 # "relevance"
|
||||
client: true
|
||||
type: enum
|
||||
enum: "SearchSortOrderSiteSetting"
|
||||
|
||||
uncategorized:
|
||||
version_checks:
|
||||
|
|
|
@ -236,6 +236,11 @@ class Search
|
|||
@in_title = false
|
||||
|
||||
term = process_advanced_search!(term)
|
||||
if !@order &&
|
||||
SiteSetting.search_default_sort_order !=
|
||||
SearchSortOrderSiteSetting.value_from_id(:relevance)
|
||||
@order = SearchSortOrderSiteSetting.id_from_value(SiteSetting.search_default_sort_order)
|
||||
end
|
||||
|
||||
if term.present?
|
||||
@term = Search.prepare_data(term, Topic === @search_context ? :topic : nil)
|
||||
|
|
|
@ -2003,7 +2003,30 @@ RSpec.describe Search do
|
|||
expect(Search.execute("with:images").posts.map(&:id)).to contain_exactly(post_uploaded.id)
|
||||
end
|
||||
|
||||
it "can find by latest" do
|
||||
it "defaults to search_default_sort_order when no order is provided" do
|
||||
topic1 = Fabricate(:topic, title: "I do not like that Sam I am", created_at: 1.minute.ago)
|
||||
post1 = Fabricate(:post, topic: topic1, created_at: 10.minutes.ago)
|
||||
post2 =
|
||||
Fabricate(
|
||||
:post,
|
||||
raw: "that Sam I am, that Sam I am",
|
||||
created_at: 5.minutes.ago,
|
||||
topic: Fabricate(:topic, created_at: 1.hour.ago),
|
||||
)
|
||||
|
||||
SiteSetting.search_default_sort_order = SearchSortOrderSiteSetting.value_from_id(:latest)
|
||||
|
||||
expect(Search.execute("sam").posts.map(&:id)).to eq([post2.id, post1.id])
|
||||
expect(Search.execute("sam ORDER:LATEST").posts.map(&:id)).to eq([post2.id, post1.id])
|
||||
|
||||
SiteSetting.search_default_sort_order =
|
||||
SearchSortOrderSiteSetting.value_from_id(:latest_topic)
|
||||
|
||||
expect(Search.execute("sam").posts.map(&:id)).to eq([post1.id, post2.id])
|
||||
expect(Search.execute("sam ORDER:LATEST_TOPIC").posts.map(&:id)).to eq([post1.id, post2.id])
|
||||
end
|
||||
|
||||
it "can order by latest" do
|
||||
topic1 = Fabricate(:topic, title: "I do not like that Sam I am")
|
||||
post1 = Fabricate(:post, topic: topic1, created_at: 10.minutes.ago)
|
||||
post2 = Fabricate(:post, raw: "that Sam I am, that Sam I am", created_at: 5.minutes.ago)
|
||||
|
@ -2014,7 +2037,7 @@ RSpec.describe Search do
|
|||
expect(Search.execute("l sam").posts.map(&:id)).to eq([post2.id, post1.id])
|
||||
end
|
||||
|
||||
it "can find by oldest" do
|
||||
it "can order by oldest" do
|
||||
topic1 = Fabricate(:topic, title: "I do not like that Sam I am")
|
||||
post1 = Fabricate(:post, topic: topic1, raw: "sam is a sam sam sam") # score higher
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user