mirror of
https://github.com/codeninjasllc/discourse.git
synced 2025-02-17 04:01:29 -05:00
Merge pull request #4387 from gdpelican/feature/tags-intersection
FEATURE: Tags intersection page
This commit is contained in:
commit
3b792054f2
9 changed files with 92 additions and 11 deletions
|
@ -43,6 +43,7 @@ export default Ember.Controller.extend(BulkTopicSelection, {
|
|||
needs: ["application"],
|
||||
|
||||
tag: null,
|
||||
additionalTags: null,
|
||||
list: null,
|
||||
canAdminTag: Ember.computed.alias("currentUser.staff"),
|
||||
filterMode: null,
|
||||
|
@ -72,8 +73,8 @@ export default Ember.Controller.extend(BulkTopicSelection, {
|
|||
}.property(),
|
||||
|
||||
showAdminControls: function() {
|
||||
return this.get('canAdminTag') && !this.get('category');
|
||||
}.property('canAdminTag', 'category'),
|
||||
return this.get('additionalTags') && this.get('canAdminTag') && !this.get('category');
|
||||
}.property('additionalTags', 'canAdminTag', 'category'),
|
||||
|
||||
loadMoreTopics() {
|
||||
return this.get("list").loadMore();
|
||||
|
|
|
@ -132,6 +132,7 @@ export default function() {
|
|||
this.route('showCategory' + filter.capitalize(), {path: '/c/:category/:tag_id/l/' + filter});
|
||||
this.route('showParentCategory' + filter.capitalize(), {path: '/c/:parent_category/:category/:tag_id/l/' + filter});
|
||||
});
|
||||
this.route('show', {path: 'intersection/:tag_id/*additional_tags'});
|
||||
});
|
||||
|
||||
this.resource('tagGroups', {path: '/tag_groups'}, function() {
|
||||
|
|
|
@ -14,6 +14,12 @@ export default Discourse.Route.extend({
|
|||
var tag = this.store.createRecord("tag", { id: Handlebars.Utils.escapeExpression(params.tag_id) }),
|
||||
f = '';
|
||||
|
||||
if (params.additional_tags) {
|
||||
this.set("additionalTags", params.additional_tags.split('/').map((t) => {
|
||||
return this.store.createRecord("tag", { id: Handlebars.Utils.escapeExpression(t) }).id;
|
||||
}));
|
||||
}
|
||||
|
||||
if (params.category) {
|
||||
f = 'c/';
|
||||
if (params.parent_category) { f += params.parent_category + '/'; }
|
||||
|
@ -56,6 +62,9 @@ export default Discourse.Route.extend({
|
|||
}
|
||||
|
||||
this.set('category', category);
|
||||
} else if (this.get("additionalTags")) {
|
||||
params.filter = `tags/intersection/${tag_id}/${this.get('additionalTags').join('/')}`;
|
||||
this.set('category', null);
|
||||
} else {
|
||||
params.filter = `tags/${tag_id}/l/${filter}`;
|
||||
this.set('category', null);
|
||||
|
@ -94,6 +103,7 @@ export default Discourse.Route.extend({
|
|||
this.controllerFor('tags.show').setProperties({
|
||||
model,
|
||||
tag: model,
|
||||
additionalTags: this.get('additionalTags'),
|
||||
category: this.get('category'),
|
||||
filterMode: this.get('filterMode'),
|
||||
navMode: this.get('navMode'),
|
||||
|
@ -123,7 +133,7 @@ export default Discourse.Route.extend({
|
|||
// Pre-fill the tags input field
|
||||
if (controller.get('model.id')) {
|
||||
var c = self.controllerFor('composer').get('model');
|
||||
c.set('tags', [controller.get('model.id')]);
|
||||
c.set('tags', _.flatten([controller.get('model.id')], controller.get('additionalTags')));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
<div class="list-controls">
|
||||
<div class="container">
|
||||
{{#if tagNotification}}
|
||||
{{tag-notifications-button action="changeTagNotification"
|
||||
notificationLevel=tagNotification.notification_level}}
|
||||
{{#unless additionalTags}}
|
||||
{{tag-notifications-button action="changeTagNotification"
|
||||
notificationLevel=tagNotification.notification_level}}
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
|
||||
{{#if showAdminControls}}
|
||||
|
@ -31,6 +33,10 @@
|
|||
{{#link-to 'tags'}}{{i18n "tagging.tags"}}{{/link-to}}
|
||||
{{fa-icon "angle-right"}}
|
||||
{{discourse-tag-bound tagRecord=tag style="simple"}}
|
||||
{{#each additionalTags as |tag|}}
|
||||
<span>&</span>
|
||||
{{discourse-tag-bound tagRecord=tag style="simple"}}
|
||||
{{/each}}
|
||||
</h2>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
|
|
@ -44,6 +44,7 @@ class TagsController < ::ApplicationController
|
|||
Discourse.filters.each do |filter|
|
||||
define_method("show_#{filter}") do
|
||||
@tag_id = DiscourseTagging.clean_tag(params[:tag_id])
|
||||
@additional_tags = params[:additional_tag_ids].to_s.split('/').map { |tag| DiscourseTagging.clean_tag(tag) }
|
||||
|
||||
page = params[:page].to_i
|
||||
list_opts = build_topic_list_options
|
||||
|
@ -71,7 +72,7 @@ class TagsController < ::ApplicationController
|
|||
@list.more_topics_url = construct_url_with(:next, list_opts)
|
||||
@list.prev_topics_url = construct_url_with(:prev, list_opts)
|
||||
@rss = "tag"
|
||||
@description_meta = I18n.t("rss_by_tag", tag: @tag_id)
|
||||
@description_meta = I18n.t("rss_by_tag", tag: tag_params.join(' & '))
|
||||
@title = @description_meta
|
||||
|
||||
canonical_url "#{Discourse.base_url_no_prefix}#{public_send(url_method(params.slice(:category, :parent_category)))}"
|
||||
|
@ -297,7 +298,8 @@ class TagsController < ::ApplicationController
|
|||
if params[:tag_id] == 'none'
|
||||
options[:no_tags] = true
|
||||
else
|
||||
options[:tags] = [params[:tag_id]]
|
||||
options[:tags] = tag_params
|
||||
options[:match_all_tags] = true
|
||||
end
|
||||
|
||||
options
|
||||
|
@ -315,4 +317,8 @@ class TagsController < ::ApplicationController
|
|||
raise Discourse::NotFound
|
||||
end
|
||||
end
|
||||
|
||||
def tag_params
|
||||
[@tag_id].concat(Array(@additional_tags))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -634,6 +634,7 @@ Discourse::Application.routes.draw do
|
|||
get '/:tag_id' => 'tags#show', as: 'tag_show'
|
||||
get '/c/:category/:tag_id' => 'tags#show', as: 'tag_category_show'
|
||||
get '/c/:parent_category/:category/:tag_id' => 'tags#show', as: 'tag_parent_category_category_show'
|
||||
get '/intersection/:tag_id/*additional_tag_ids' => 'tags#show', as: 'tag_intersection'
|
||||
get '/:tag_id/notifications' => 'tags#notifications'
|
||||
put '/:tag_id/notifications' => 'tags#update_notifications'
|
||||
put '/:tag_id' => 'tags#update'
|
||||
|
|
|
@ -20,6 +20,7 @@ class TopicQuery
|
|||
visible
|
||||
category
|
||||
tags
|
||||
match_all_tags
|
||||
no_tags
|
||||
order
|
||||
ascending
|
||||
|
@ -460,11 +461,27 @@ class TopicQuery
|
|||
|
||||
if @options[:tags] && @options[:tags].size > 0
|
||||
result = result.joins(:tags)
|
||||
# ANY of the given tags:
|
||||
if @options[:tags][0].is_a?(Integer)
|
||||
result = result.where("tags.id in (?)", @options[:tags])
|
||||
|
||||
if @options[:match_all_tags]
|
||||
# ALL of the given tags:
|
||||
tags_count = @options[:tags].length
|
||||
@options[:tags] = Tag.where(name: @options[:tags]).pluck(:id) unless @options[:tags][0].is_a?(Integer)
|
||||
|
||||
if tags_count == @options[:tags].length
|
||||
@options[:tags].each_with_index do |tag, index|
|
||||
sql_alias = ['t', index].join
|
||||
result = result.joins("INNER JOIN topic_tags #{sql_alias} ON #{sql_alias}.topic_id = topics.id AND #{sql_alias}.tag_id = #{tag}")
|
||||
end
|
||||
else
|
||||
result = result.none # don't return any results unless all tags exist in the database
|
||||
end
|
||||
else
|
||||
result = result.where("tags.name in (?)", @options[:tags])
|
||||
# ANY of the given tags:
|
||||
if @options[:tags][0].is_a?(Integer)
|
||||
result = result.where("tags.id in (?)", @options[:tags])
|
||||
else
|
||||
result = result.where("tags.name in (?)", @options[:tags])
|
||||
end
|
||||
end
|
||||
elsif @options[:no_tags]
|
||||
# the following will do: ("topics"."id" NOT IN (SELECT DISTINCT "topic_tags"."topic_id" FROM "topic_tags"))
|
||||
|
|
|
@ -145,6 +145,14 @@ describe TopicQuery do
|
|||
# expect(TopicQuery.new(moderator, tags: [tag.id, other_tag.id]).list_latest.topics.map(&:id)).to eq([tagged_topic3.id].sort)
|
||||
end
|
||||
|
||||
it "can return topics with all specified tags" do
|
||||
expect(TopicQuery.new(moderator, tags: [tag.name, other_tag.name], match_all_tags: true).list_latest.topics.map(&:id)).to eq([tagged_topic3.id])
|
||||
end
|
||||
|
||||
it "returns an empty relation when an invalid tag is passed" do
|
||||
expect(TopicQuery.new(moderator, tags: [tag.name, 'notatag'], match_all_tags: true).list_latest.topics).to be_empty
|
||||
end
|
||||
|
||||
it "can return topics with no tags" do
|
||||
expect(TopicQuery.new(moderator, no_tags: true).list_latest.topics.map(&:id)).to eq([no_tags_topic.id])
|
||||
end
|
||||
|
|
|
@ -3,9 +3,15 @@ require 'rails_helper'
|
|||
describe TagsController do
|
||||
describe 'show_latest' do
|
||||
let(:tag) { Fabricate(:tag) }
|
||||
let(:other_tag) { Fabricate(:tag) }
|
||||
let(:third_tag) { Fabricate(:tag) }
|
||||
let(:category) { Fabricate(:category) }
|
||||
let(:subcategory) { Fabricate(:category, parent_category_id: category.id) }
|
||||
|
||||
let(:single_tag_topic) { Fabricate(:topic, tags: [tag]) }
|
||||
let(:multi_tag_topic) { Fabricate(:topic, tags: [tag, other_tag]) }
|
||||
let(:all_tag_topic) { Fabricate(:topic, tags: [tag, other_tag, third_tag]) }
|
||||
|
||||
context 'tagging disabled' do
|
||||
it "returns 404" do
|
||||
xhr :get, :show_latest, tag_id: tag.name
|
||||
|
@ -23,6 +29,31 @@ describe TagsController do
|
|||
expect(response).to be_success
|
||||
end
|
||||
|
||||
it "can filter by two tags" do
|
||||
single_tag_topic; multi_tag_topic; all_tag_topic
|
||||
xhr :get, :show_latest, tag_id: tag.name, additional_tag_ids: other_tag.name
|
||||
expect(response).to be_success
|
||||
expect(assigns(:list).topics).to include all_tag_topic
|
||||
expect(assigns(:list).topics).to include multi_tag_topic
|
||||
expect(assigns(:list).topics).to_not include single_tag_topic
|
||||
end
|
||||
|
||||
it "can filter by multiple tags" do
|
||||
single_tag_topic; multi_tag_topic; all_tag_topic
|
||||
xhr :get, :show_latest, tag_id: tag.name, additional_tag_ids: "#{other_tag.name}/#{third_tag.name}"
|
||||
expect(response).to be_success
|
||||
expect(assigns(:list).topics).to include all_tag_topic
|
||||
expect(assigns(:list).topics).to_not include multi_tag_topic
|
||||
expect(assigns(:list).topics).to_not include single_tag_topic
|
||||
end
|
||||
|
||||
it "does not find any tags when a tag which doesn't exist is passed" do
|
||||
single_tag_topic
|
||||
xhr :get, :show_latest, tag_id: tag.name, additional_tag_ids: "notatag"
|
||||
expect(response).to be_success
|
||||
expect(assigns(:list).topics).to_not include single_tag_topic
|
||||
end
|
||||
|
||||
it "can filter by category and tag" do
|
||||
xhr :get, :show_latest, tag_id: tag.name, category: category.slug
|
||||
expect(response).to be_success
|
||||
|
|
Loading…
Reference in a new issue