Merge pull request #4896 from dmacjam/multiple-tags-logical-and-search

FEATURE: Advanced search supports logical AND of multiple tags
This commit is contained in:
Neil Lalonde 2017-06-14 14:39:23 -04:00 committed by GitHub
commit b98a930364
5 changed files with 56 additions and 15 deletions

View File

@ -77,7 +77,8 @@ export default Em.Component.extend({
likes: false,
private: false,
seen: false
}
},
all_tags: false
},
status: '',
min_post_count: '',
@ -230,13 +231,15 @@ export default Em.Component.extend({
const match = this.filterBlocks(REGEXP_TAGS_PREFIX);
const tags = this.get('searchedTerms.tags');
const contain_all_tags = this.get('searchedTerms.special.all_tags');
if (match.length !== 0) {
const existingInput = _.isArray(tags) ? tags.join(',') : tags;
const join_char = contain_all_tags ? '+' : ',';
const existingInput = _.isArray(tags) ? tags.join(join_char) : tags;
const userInput = match[0].replace(REGEXP_TAGS_REPLACE, '');
if (existingInput !== userInput) {
this.set('searchedTerms.tags', (userInput.length !== 0) ? userInput.split(',') : []);
this.set('searchedTerms.tags', (userInput.length !== 0) ? userInput.split(join_char) : []);
}
} else if (tags.length !== 0) {
this.set('searchedTerms.tags', []);
@ -365,14 +368,16 @@ export default Em.Component.extend({
}
},
@observes('searchedTerms.tags')
@observes('searchedTerms.tags', 'searchedTerms.special.all_tags')
updateSearchTermForTags() {
const match = this.filterBlocks(REGEXP_TAGS_PREFIX);
const tagFilter = this.get('searchedTerms.tags');
let searchTerm = this.get('searchTerm') || '';
const contain_all_tags = this.get('searchedTerms.special.all_tags');
if (tagFilter && tagFilter.length !== 0) {
const tags = tagFilter.join(',');
const join_char = contain_all_tags ? '+' : ',';
const tags = tagFilter.join(join_char);
if (match.length !== 0) {
searchTerm = searchTerm.replace(match[0], `tags:${tags}`);

View File

@ -41,6 +41,9 @@
<label class="control-label" for="search-with-tags">{{i18n "search.advanced.with_tags.label"}}</label>
<div class="controls">
{{tag-chooser tags=searchedTerms.tags blacklist=searchedTerms.tags allowCreate=false placeholder="" everyTag="true" unlimitedTagCount="true" width="70%"}}
<section class="field">
<label>{{ input type="checkbox" class="all-tags" checked=searchedTerms.special.all_tags}} {{i18n "search.advanced.filters.all_tags"}} </label>
</section>
</div>
</div>
</div>

View File

@ -1338,6 +1338,7 @@ en:
seen: I've read
unseen: I've not read
wiki: are wiki
all_tags: Contains all tags
statuses:
label: Where topics
open: are open

View File

@ -447,15 +447,27 @@ class Search
end
end
advanced_filter(/tags?:([a-zA-Z0-9,\-_]+)/) do |posts, match|
tags = match.split(",")
advanced_filter(/tags?:([a-zA-Z0-9,\-_+]+)/) do |posts, match|
if match.include?('+')
tags = match.split('+')
posts.where("topics.id IN (
posts.where("topics.id IN (
SELECT tt.topic_id
FROM topic_tags tt, tags
WHERE tt.tag_id = tags.id
GROUP BY tt.topic_id
HAVING to_tsvector(#{query_locale}, array_to_string(array_agg(tags.name), ' ')) @@ to_tsquery(#{query_locale}, ?)
)", tags.join('&'))
else
tags = match.split(",")
posts.where("topics.id IN (
SELECT DISTINCT(tt.topic_id)
FROM topic_tags tt, tags
WHERE tt.tag_id = tags.id
AND tags.name in (?)
)", tags)
end
end
private

View File

@ -706,15 +706,35 @@ describe Search do
expect(Search.execute('this is a test #beta').posts.size).to eq(0)
end
it "can find with tag" do
topic1 = Fabricate(:topic, title: 'Could not, would not, on a boat')
topic1.tags = [Fabricate(:tag, name: 'eggs'), Fabricate(:tag, name: 'ham')]
Fabricate(:post, topic: topic1)
post2 = Fabricate(:post, topic: topic1, raw: "It probably doesn't help that they're green...")
context 'tags' do
let(:tag1) { Fabricate(:tag, name: 'lunch') }
let(:tag2) { Fabricate(:tag, name: 'eggs') }
let(:topic1) { Fabricate(:topic, tags: [tag2, Fabricate(:tag)]) }
let(:topic2) { Fabricate(:topic, tags: [tag2]) }
let(:topic3) { Fabricate(:topic, tags: [tag1, tag2]) }
let!(:post1) { Fabricate(:post, topic: topic1)}
let!(:post2) { Fabricate(:post, topic: topic2)}
let!(:post3) { Fabricate(:post, topic: topic3)}
expect(Search.execute('green tags:eggs').posts.map(&:id)).to eq([post2.id])
expect(Search.execute('green tags:plants').posts.size).to eq(0)
it 'can find posts with tag' do
post4 = Fabricate(:post, topic: topic3, raw: "It probably doesn't help that they're green...")
expect(Search.execute('green tags:eggs').posts.map(&:id)).to eq([post4.id])
expect(Search.execute('tags:plants').posts.size).to eq(0)
end
it 'can find posts with any tag from multiple tags' do
Fabricate(:post)
expect(Search.execute('tags:eggs,lunch').posts.map(&:id).sort).to eq([post1.id, post2.id, post3.id].sort)
end
it 'can find posts which contains all provided tags' do
expect(Search.execute('tags:lunch+eggs').posts.map(&:id)).to eq([post3.id])
expect(Search.execute('tags:eggs+lunch').posts.map(&:id)).to eq([post3.id])
end
end
end
it 'can parse complex strings using ts_query helper' do