FEATURE: Advanced search filters for assigned topics (#102)
Adds three new search modifiers: - in:assigned for assigned topics - in:unassigned for unassigned topics - assigned:{username} to list topics assigned to a specific user These modifiers are all made available in the advanced search sidebar
This commit is contained in:
parent
3e3dc3815b
commit
303274eae3
|
@ -0,0 +1,12 @@
|
|||
<div class="control-group pull-left">
|
||||
<label class="control-label" for="search-assigned-to">{{i18n "search.advanced.assigned.label"}}</label>
|
||||
<div class="controls">
|
||||
{{user-selector
|
||||
excludeCurrentUser=false
|
||||
usernames=searchedTerms.assigned
|
||||
single=true
|
||||
canReceiveUpdates=true
|
||||
class="user-selector-assigned"
|
||||
}}
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,5 @@
|
|||
export default {
|
||||
shouldRender(args, component) {
|
||||
return component.currentUser && component.currentUser.can_assign;
|
||||
},
|
||||
};
|
|
@ -1,12 +1,14 @@
|
|||
import { renderAvatar } from "discourse/helpers/user-avatar";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import computed from "discourse-common/utils/decorators";
|
||||
import { observes } from "discourse-common/utils/decorators";
|
||||
import { iconHTML, iconNode } from "discourse-common/lib/icon-library";
|
||||
import { h } from "virtual-dom";
|
||||
import { queryRegistry } from "discourse/widgets/widget";
|
||||
import { getOwner } from "discourse-common/lib/get-owner";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import getURL from "discourse-common/lib/get-url";
|
||||
import SearchAdvancedOptions from "discourse/components/search-advanced-options";
|
||||
import { addBulkButton } from "discourse/controllers/topic-bulk-actions";
|
||||
import TopicButtonAction from "discourse/controllers/topic-bulk-actions";
|
||||
import { inject } from "@ember/controller";
|
||||
|
@ -122,6 +124,23 @@ function initialize(api) {
|
|||
before: "top",
|
||||
});
|
||||
|
||||
api.addAdvancedSearchOptions(
|
||||
api.getCurrentUser() && api.getCurrentUser().can_assign
|
||||
? {
|
||||
inOptionsForUsers: [
|
||||
{
|
||||
name: I18n.t("search.advanced.in.assigned"),
|
||||
value: "assigned",
|
||||
},
|
||||
{
|
||||
name: I18n.t("search.advanced.in.unassigned"),
|
||||
value: "unassigned",
|
||||
},
|
||||
],
|
||||
}
|
||||
: {}
|
||||
);
|
||||
|
||||
// You can't act on flags claimed by another user
|
||||
api.modifyClass(
|
||||
"component:flagged-post",
|
||||
|
@ -315,6 +334,8 @@ function initialize(api) {
|
|||
api.addKeyboardShortcut("g a", "", { path: "/my/activity/assigned" });
|
||||
}
|
||||
|
||||
const REGEXP_USERNAME_PREFIX = /^(assigned:)/gi;
|
||||
|
||||
export default {
|
||||
name: "extend-for-assign",
|
||||
initialize(container) {
|
||||
|
@ -322,9 +343,47 @@ export default {
|
|||
if (!siteSettings.assign_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentUser = container.lookup("current-user:main");
|
||||
if (currentUser.can_assign) {
|
||||
if (currentUser && currentUser.can_assign) {
|
||||
SearchAdvancedOptions.reopen({
|
||||
_init() {
|
||||
this._super();
|
||||
|
||||
this.set("searchedTerms.assigned", "");
|
||||
},
|
||||
|
||||
@observes("searchedTerms.assigned")
|
||||
updateSearchTermForAssignedUsername() {
|
||||
const match = this.filterBlocks(REGEXP_USERNAME_PREFIX);
|
||||
const userFilter = this.get("searchedTerms.assigned");
|
||||
let searchTerm = this.searchTerm || "";
|
||||
let keyword = "assigned";
|
||||
if (userFilter && userFilter.length !== 0) {
|
||||
if (match.length !== 0) {
|
||||
searchTerm = searchTerm.replace(
|
||||
match[0],
|
||||
`${keyword}:${userFilter}`
|
||||
);
|
||||
} else {
|
||||
searchTerm += ` ${keyword}:${userFilter}`;
|
||||
}
|
||||
|
||||
this.set("searchTerm", searchTerm.trim());
|
||||
} else if (match.length !== 0) {
|
||||
searchTerm = searchTerm.replace(match[0], "");
|
||||
this.set("searchTerm", searchTerm.trim());
|
||||
}
|
||||
},
|
||||
|
||||
_update() {
|
||||
this._super(...arguments);
|
||||
this.setSearchedTermValue(
|
||||
"searchedTerms.assigned",
|
||||
REGEXP_USERNAME_PREFIX
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
TopicButtonAction.reopen({
|
||||
assignUser: inject("assign-user"),
|
||||
actions: {
|
||||
|
@ -347,7 +406,8 @@ export default {
|
|||
class: "btn-default",
|
||||
});
|
||||
}
|
||||
withPluginApi("0.8.11", (api) => initialize(api, container));
|
||||
|
||||
withPluginApi("0.11.0", (api) => initialize(api, container));
|
||||
withPluginApi("0.8.28", (api) =>
|
||||
registerTopicFooterButtons(api, container)
|
||||
);
|
||||
|
|
|
@ -53,6 +53,13 @@ en:
|
|||
assign_event:
|
||||
name: "Assign Event"
|
||||
details: "When a user assigns or unassigns a topic."
|
||||
search:
|
||||
advanced:
|
||||
in:
|
||||
assigned: "are assigned"
|
||||
unassigned: "are unassigned"
|
||||
assigned:
|
||||
label: "Assigned to"
|
||||
topics:
|
||||
bulk:
|
||||
unassign: "Unassign Topics"
|
||||
|
|
36
plugin.rb
36
plugin.rb
|
@ -544,4 +544,40 @@ after_initialize do
|
|||
end
|
||||
end
|
||||
|
||||
register_search_advanced_filter(/in:assigned/) do |posts|
|
||||
if @guardian.can_assign?
|
||||
posts.where("topics.id IN (
|
||||
SELECT tc.topic_id
|
||||
FROM topic_custom_fields tc
|
||||
WHERE tc.name = 'assigned_to_id' AND
|
||||
tc.value IS NOT NULL
|
||||
)")
|
||||
end
|
||||
end
|
||||
|
||||
register_search_advanced_filter(/in:unassigned/) do |posts|
|
||||
if @guardian.can_assign?
|
||||
posts.where("topics.id NOT IN (
|
||||
SELECT tc.topic_id
|
||||
FROM topic_custom_fields tc
|
||||
WHERE tc.name = 'assigned_to_id' AND
|
||||
tc.value IS NOT NULL
|
||||
)")
|
||||
end
|
||||
end
|
||||
|
||||
register_search_advanced_filter(/assigned:(.+)$/) do |posts, match|
|
||||
if @guardian.can_assign?
|
||||
if user_id = User.find_by_username(match)&.id
|
||||
posts.where("topics.id IN (
|
||||
SELECT tc.topic_id
|
||||
FROM topic_custom_fields tc
|
||||
WHERE tc.name = 'assigned_to_id' AND
|
||||
tc.value IS NOT NULL AND
|
||||
tc.value::int = #{user_id}
|
||||
)")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
require_relative '../support/assign_allowed_group'
|
||||
|
||||
describe Search do
|
||||
|
||||
fab!(:user) { Fabricate(:active_user) }
|
||||
fab!(:user2) { Fabricate(:user) }
|
||||
|
||||
before do
|
||||
SearchIndexer.enable
|
||||
SiteSetting.assign_enabled = true
|
||||
end
|
||||
|
||||
context 'Advanced search' do
|
||||
include_context 'A group that is allowed to assign'
|
||||
|
||||
let(:post1) { Fabricate(:post) }
|
||||
let(:post2) { Fabricate(:post) }
|
||||
let(:post3) { Fabricate(:post) }
|
||||
|
||||
before do
|
||||
add_to_assign_allowed_group(user)
|
||||
add_to_assign_allowed_group(user2)
|
||||
|
||||
TopicAssigner.new(post1.topic, user).assign(user)
|
||||
TopicAssigner.new(post2.topic, user).assign(user2)
|
||||
TopicAssigner.new(post3.topic, user).assign(user)
|
||||
end
|
||||
|
||||
it 'can find by status' do
|
||||
expect(Search.execute('in:assigned', guardian: Guardian.new(user)).posts.length).to eq(3)
|
||||
|
||||
TopicAssigner.new(post3.topic, user).unassign
|
||||
|
||||
expect(Search.execute('in:unassigned', guardian: Guardian.new(user)).posts.length).to eq(1)
|
||||
expect(Search.execute("assigned:#{user.username}", guardian: Guardian.new(user)).posts.length).to eq(1)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,91 @@
|
|||
import selectKit from "helpers/select-kit-helper";
|
||||
import { acceptance, waitFor, updateCurrentUser } from "helpers/qunit-helpers";
|
||||
|
||||
acceptance("Search - Full Page", {
|
||||
settings: { assign_enabled: true },
|
||||
loggedIn: true,
|
||||
});
|
||||
QUnit.test(
|
||||
"update in:assigned filter through advanced search ui",
|
||||
async (assert) => {
|
||||
updateCurrentUser({ can_assign: true });
|
||||
const inSelector = selectKit(".search-advanced-options .select-kit#in");
|
||||
|
||||
await visit("/search");
|
||||
|
||||
await fillIn(".search-query", "none");
|
||||
await inSelector.expand();
|
||||
await inSelector.selectRowByValue("assigned");
|
||||
assert.equal(
|
||||
inSelector.header().label(),
|
||||
"are assigned",
|
||||
'has "are assigned" populated'
|
||||
);
|
||||
assert.equal(
|
||||
find(".search-query").val(),
|
||||
"none in:assigned",
|
||||
'has updated search term to "none in:assinged"'
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"update in:unassigned filter through advanced search ui",
|
||||
async (assert) => {
|
||||
updateCurrentUser({ can_assign: true });
|
||||
const inSelector = selectKit(".search-advanced-options .select-kit#in");
|
||||
|
||||
await visit("/search");
|
||||
|
||||
await fillIn(".search-query", "none");
|
||||
await inSelector.expand();
|
||||
await inSelector.selectRowByValue("unassigned");
|
||||
assert.equal(
|
||||
inSelector.header().label(),
|
||||
"are unassigned",
|
||||
'has "are unassigned" populated'
|
||||
);
|
||||
assert.equal(
|
||||
find(".search-query").val(),
|
||||
"none in:unassigned",
|
||||
'has updated search term to "none in:unassinged"'
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.skip("update assigned to through advanced search ui", async (assert) => {
|
||||
updateCurrentUser({ can_assign: true });
|
||||
await visit("/search");
|
||||
await fillIn(".search-query", "none");
|
||||
await fillIn(".search-advanced-options .user-selector-assigned", "admin");
|
||||
await click(".search-advanced-options .user-selector-assigned");
|
||||
await keyEvent(
|
||||
".search-advanced-options .user-selector-assigned",
|
||||
"keydown",
|
||||
8
|
||||
);
|
||||
waitFor(assert, async () => {
|
||||
assert.ok(
|
||||
visible(".search-advanced-options .autocomplete"),
|
||||
'"autocomplete" popup is visible'
|
||||
);
|
||||
assert.ok(
|
||||
exists(
|
||||
'.search-advanced-options .autocomplete ul li a span.username:contains("admin")'
|
||||
),
|
||||
'"autocomplete" popup has an entry for "admin"'
|
||||
);
|
||||
|
||||
await click(".search-advanced-options .autocomplete ul li a:first");
|
||||
|
||||
assert.ok(
|
||||
exists('.search-advanced-options span:contains("admin")'),
|
||||
'has "admin" pre-populated'
|
||||
);
|
||||
assert.equal(
|
||||
find(".search-query").val(),
|
||||
"none assigned:admin",
|
||||
'has updated search term to "none assigned:admin"'
|
||||
);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue