DEV: Remove experimental support for query string on /filter route (#20632)

This commit is contained in:
Alan Guo Xiang Tan
2023-03-22 10:04:57 +08:00
committed by GitHub
parent 520d4f504b
commit b06e31f8e7
11 changed files with 92 additions and 100 deletions

View File

@ -3,11 +3,11 @@
<Input <Input
class="topic-query-filter__input" class="topic-query-filter__input"
@value={{this.queryString}} @value={{this.queryString}}
@enter={{route-action "changeQueryString" this.queryString}} @enter={{action @updateTopicsListQueryParams this.queryString}}
/> />
<DButton <DButton
@action={{route-action "changeQueryString" this.queryString}} @action={{action @updateTopicsListQueryParams this.queryString}}
@icon="filter" @icon="filter"
@class="btn-primary topic-query-filter__button" @class="btn-primary topic-query-filter__button"
@label="filters.filter.button.label" @label="filters.filter.button.label"

View File

@ -6,12 +6,18 @@ import { NotificationLevels } from "discourse/lib/notification-levels";
import { getOwner } from "discourse-common/lib/get-owner"; import { getOwner } from "discourse-common/lib/get-owner";
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { alias, equal } from "@ember/object/computed"; import { equal } from "@ember/object/computed";
export default Component.extend(FilterModeMixin, { export default Component.extend(FilterModeMixin, {
router: service(), router: service(),
dialog: service(), dialog: service(),
tagName: "", tagName: "",
queryString: "",
init() {
this._super(...arguments);
this.queryString = this.filterQueryString;
},
// Should be a `readOnly` instead but some themes/plugins still pass // Should be a `readOnly` instead but some themes/plugins still pass
// the `categories` property into this component // the `categories` property into this component
@ -142,7 +148,6 @@ export default Component.extend(FilterModeMixin, {
}, },
isQueryFilterMode: equal("filterMode", "filter"), isQueryFilterMode: equal("filterMode", "filter"),
queryString: alias("router.currentRoute.queryParams.q"),
actions: { actions: {
changeCategoryNotificationLevel(notificationLevel) { changeCategoryNotificationLevel(notificationLevel) {

View File

@ -1,4 +1,6 @@
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import discourseComputed from "discourse-common/utils/decorators";
import { action } from "@ember/object";
// Just add query params here to have them automatically passed to topic list filters. // Just add query params here to have them automatically passed to topic list filters.
export const queryParams = { export const queryParams = {
@ -27,6 +29,31 @@ export const queryParams = {
const controllerOpts = { const controllerOpts = {
discoveryTopics: controller("discovery/topics"), discoveryTopics: controller("discovery/topics"),
queryParams: Object.keys(queryParams), queryParams: Object.keys(queryParams),
@discourseComputed(...Object.keys(queryParams))
queryString() {
let paramStrings = [];
this.queryParams.forEach((key) => {
if (this[key]) {
paramStrings.push(`${key}:${this[key]}`);
}
});
return paramStrings.join(" ");
},
@action
updateTopicsListQueryParams(queryString) {
for (const match of queryString.matchAll(/(\w+):([^:\s]+)/g)) {
const key = match[1];
const value = match[2];
if (controllerOpts.queryParams.includes(key)) {
this.set(key, value);
}
}
},
}; };
// Default to `undefined` // Default to `undefined`
@ -34,10 +61,6 @@ controllerOpts.queryParams.forEach((p) => {
controllerOpts[p] = queryParams[p].default; controllerOpts[p] = queryParams[p].default;
}); });
export function changeQueryString(queryString) {
this.controller.set("q", queryString);
}
export function changeSort(sortBy) { export function changeSort(sortBy) {
let model = this.controllerFor("discovery.topics").model; let model = this.controllerFor("discovery.topics").model;

View File

@ -6,6 +6,7 @@ import { TRACKED_QUERY_PARAM_VALUE } from "discourse/lib/topic-list-tracked-filt
export default Controller.extend(FilterModeMixin, { export default Controller.extend(FilterModeMixin, {
discovery: controller(), discovery: controller(),
discoveryFilter: controller("discovery.filter"),
router: service(), router: service(),
@discourseComputed("router.currentRoute.queryParams.f") @discourseComputed("router.currentRoute.queryParams.f")

View File

@ -10,17 +10,13 @@ export default {
after: "inject-discourse-objects", after: "inject-discourse-objects",
name: "dynamic-route-builders", name: "dynamic-route-builders",
initialize(container, app) { initialize(_container, app) {
const siteSettings = container.lookup("service:site-settings");
if (siteSettings.experimental_topics_filter) {
app.register( app.register(
"controller:discovery.filter", "controller:discovery.filter",
DiscoverySortableController.extend() DiscoverySortableController.extend()
); );
app.register("route:discovery.filter", buildTopicRoute("filter")); app.register("route:discovery.filter", buildTopicRoute("filter"));
}
app.register( app.register(
"controller:discovery.category", "controller:discovery.category",

View File

@ -1,5 +1,4 @@
import { import {
changeQueryString,
changeSort, changeSort,
queryParams, queryParams,
resetParams, resetParams,
@ -168,11 +167,6 @@ export default function (filter, extras) {
changeSort.call(this, sortBy); changeSort.call(this, sortBy);
}, },
@action
changeQueryString(queryString) {
changeQueryString.call(this, queryString);
},
@action @action
resetParams(skipParams = []) { resetParams(skipParams = []) {
resetParams.call(this, skipParams); resetParams.call(this, skipParams);

View File

@ -9,5 +9,7 @@
@hasDraft={{this.currentUser.has_topic_draft}} @hasDraft={{this.currentUser.has_topic_draft}}
@createTopic={{route-action "createTopic"}} @createTopic={{route-action "createTopic"}}
@skipCategoriesNavItem={{this.skipCategoriesNavItem}} @skipCategoriesNavItem={{this.skipCategoriesNavItem}}
@filterQueryString={{this.discoveryFilter.queryString}}
@updateTopicsListQueryParams={{this.discoveryFilter.updateTopicsListQueryParams}}
/> />
</DSection> </DSection>

View File

@ -848,17 +848,12 @@ class TopicQuery
end end
if status = options[:status] if status = options[:status]
options[:q] ||= +""
options[:q] << " status:#{status}"
end
if options[:q].present?
result = result =
TopicsFilter.new( TopicsFilter.new(
scope: result, scope: result,
guardian: @guardian, guardian: @guardian,
category_id: options[:category], category_id: options[:category],
).filter(options[:q]) ).filter(status: options[:status])
end end
if (filter = (options[:filter] || options[:f])) && @user if (filter = (options[:filter] || options[:f])) && @user

View File

@ -1,51 +1,31 @@
# frozen_string_literal: true # frozen_string_literal: true
class TopicsFilter class TopicsFilter
def self.register_filter(matcher, &block)
self.filters[matcher] = block
end
def self.filters
@@filters ||= {}
end
register_filter(/\Astatus:([a-zA-Z]+)\z/i) do |topics, match|
case match
when "open"
topics.where("NOT topics.closed AND NOT topics.archived")
when "closed"
topics.where("topics.closed")
when "archived"
topics.where("topics.archived")
when "deleted"
if @guardian.can_see_deleted_topics?(@category)
topics.unscope(where: :deleted_at).where("topics.deleted_at IS NOT NULL")
end
end
end
def initialize(guardian:, scope: Topic, category_id: nil) def initialize(guardian:, scope: Topic, category_id: nil)
@guardian = guardian @guardian = guardian
@scope = scope @scope = scope
@category = category_id.present? ? Category.find_by(id: category_id) : nil @category = category_id.present? ? Category.find_by(id: category_id) : nil
end end
def filter(input) def filter(status: nil)
input filter_status(@scope, status) if status
.to_s
.scan(/(([^" \t\n\x0B\f\r]+)?(("[^"]+")?))/)
.to_a
.map do |(word, _)|
next if word.blank?
self.class.filters.each do |matcher, block|
cleaned = word.gsub(/["']/, "")
new_scope = instance_exec(@scope, $1, &block) if cleaned =~ matcher
@scope = new_scope if !new_scope.nil?
end
end
@scope @scope
end end
private
def filter_status(scope, status)
case status
when "open"
@scope = @scope.where("NOT topics.closed AND NOT topics.archived")
when "closed"
@scope = @scope.where("topics.closed")
when "archived"
@scope = @scope.where("topics.archived")
when "deleted"
if @guardian.can_see_deleted_topics?(@category)
@scope = @scope.unscope(where: :deleted_at).where("topics.deleted_at IS NOT NULL")
end
end
end
end end

View File

@ -9,41 +9,37 @@ RSpec.describe TopicsFilter do
describe "#filter" do describe "#filter" do
it "should return all topics when input is blank" do it "should return all topics when input is blank" do
expect(TopicsFilter.new(guardian: Guardian.new).filter("").pluck(:id)).to contain_exactly( expect(TopicsFilter.new(guardian: Guardian.new).filter.pluck(:id)).to contain_exactly(
topic.id, topic.id,
closed_topic.id, closed_topic.id,
archived_topic.id, archived_topic.id,
) )
end end
it "should return all topics when input does not match any filters" do context "when filtering by topic's status" do
expect(
TopicsFilter.new(guardian: Guardian.new).filter("randomstring").pluck(:id),
).to contain_exactly(topic.id, closed_topic.id, archived_topic.id)
end
it "should only return topics that have not been closed or archived when input is `status:open`" do it "should only return topics that have not been closed or archived when input is `status:open`" do
expect( expect(
TopicsFilter.new(guardian: Guardian.new).filter("status:open").pluck(:id), TopicsFilter.new(guardian: Guardian.new).filter(status: "open").pluck(:id),
).to contain_exactly(topic.id) ).to contain_exactly(topic.id)
end end
it "should only return topics that have been deleted when input is `status:deleted` and user can see deleted topics" do it "should only return topics that have been deleted when input is `status:deleted` and user can see deleted topics" do
expect( expect(
TopicsFilter.new(guardian: Guardian.new(admin)).filter("status:deleted").pluck(:id), TopicsFilter.new(guardian: Guardian.new(admin)).filter(status: "deleted").pluck(:id),
).to contain_exactly(deleted_topic_id) ).to contain_exactly(deleted_topic_id)
end end
it "should status filter when input is `status:deleted` and user cannot see deleted topics" do it "should status filter when input is `status:deleted` and user cannot see deleted topics" do
expect( expect(
TopicsFilter.new(guardian: Guardian.new).filter("status:deleted").pluck(:id), TopicsFilter.new(guardian: Guardian.new).filter(status: "deleted").pluck(:id),
).to contain_exactly(topic.id, closed_topic.id, archived_topic.id) ).to contain_exactly(topic.id, closed_topic.id, archived_topic.id)
end end
it "should only return topics that have been archived when input is `status:archived`" do it "should only return topics that have been archived when input is `status:archived`" do
expect( expect(
TopicsFilter.new(guardian: Guardian.new).filter("status:archived").pluck(:id), TopicsFilter.new(guardian: Guardian.new).filter(status: "archived").pluck(:id),
).to contain_exactly(archived_topic.id) ).to contain_exactly(archived_topic.id)
end end
end end
end
end end

View File

@ -8,7 +8,7 @@ describe "Filtering topics", type: :system, js: true do
before { SiteSetting.experimental_topics_filter = true } before { SiteSetting.experimental_topics_filter = true }
it "should allow users to enter a custom query string to filter through topics" do it "should allow users to input a custom query string to filter through topics" do
sign_in(user) sign_in(user)
visit("/filter") visit("/filter")
@ -21,19 +21,19 @@ describe "Filtering topics", type: :system, js: true do
expect(topic_list).to have_topic(topic) expect(topic_list).to have_topic(topic)
expect(topic_list).to have_no_topic(closed_topic) expect(topic_list).to have_no_topic(closed_topic)
expect(page).to have_current_path("/filter?q=status%3Aopen") expect(page).to have_current_path("/filter?status=open")
topic_query_filter.fill_in("status:closed") topic_query_filter.fill_in("status:closed")
expect(topic_list).to have_no_topic(topic) expect(topic_list).to have_no_topic(topic)
expect(topic_list).to have_topic(closed_topic) expect(topic_list).to have_topic(closed_topic)
expect(page).to have_current_path("/filter?q=status%3Aclosed") expect(page).to have_current_path("/filter?status=closed")
end end
it "should filter topics when 'q' query params is present" do it "should filter topics when 'status' query params is present" do
sign_in(user) sign_in(user)
visit("/filter?q=status:open") visit("/filter?status=open")
expect(topic_list).to have_topic(topic) expect(topic_list).to have_topic(topic)
expect(topic_list).to have_no_topic(closed_topic) expect(topic_list).to have_no_topic(closed_topic)