mirror of
https://github.com/discourse/discourse.git
synced 2025-05-24 03:36:18 +08:00
DEV: Remove experimental support for query string on /filter
route (#20632)
This commit is contained in:

committed by
GitHub

parent
520d4f504b
commit
b06e31f8e7
@ -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"
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
@ -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",
|
||||||
|
@ -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);
|
||||||
|
@ -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>
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
Reference in New Issue
Block a user