FEATURE: add filter to show topics that have not been tagged

This commit is contained in:
Neil Lalonde 2016-07-20 16:21:43 -04:00
parent 8e87a727ef
commit 7c092b0fe0
7 changed files with 70 additions and 26 deletions

View File

@ -45,6 +45,20 @@ export default Ember.Component.extend({
return I18n.t("tagging.selector_all_tags"); return I18n.t("tagging.selector_all_tags");
}.property('tag'), }.property('tag'),
@computed('firstCategory', 'secondCategory')
noTagsUrl() {
var url = '/tags';
if (this.get('currentCategory')) {
url += this.get('currentCategory.url');
}
return url + '/none';
},
@computed('tag')
noTagsLabel() {
return I18n.t("tagging.selector_no_tags");
},
dropdownButtonClass: function() { dropdownButtonClass: function() {
var result = 'badge-category category-dropdown-button'; var result = 'badge-category category-dropdown-button';
if (Em.isNone(this.get('tag'))) { if (Em.isNone(this.get('tag'))) {

View File

@ -11,7 +11,7 @@ export default Discourse.Route.extend({
}, },
model(params) { model(params) {
var tag = this.store.createRecord("tag", { id: Handlebars.Utils.escapeExpression(params.tag_id) }), var tag = (params.tag_id === 'none' ? null : this.store.createRecord("tag", { id: Handlebars.Utils.escapeExpression(params.tag_id) })),
f = ''; f = '';
if (params.category) { if (params.category) {
@ -25,8 +25,8 @@ export default Discourse.Route.extend({
if (params.category) { this.set('categorySlug', params.category); } if (params.category) { this.set('categorySlug', params.category); }
if (params.parent_category) { this.set('parentCategorySlug', params.parent_category); } if (params.parent_category) { this.set('parentCategorySlug', params.parent_category); }
if (this.get("currentUser")) { if (tag && this.get("currentUser")) {
// If logged in, we should get the tag"s user settings // If logged in, we should get the tag's user settings
return this.store.find("tagNotification", tag.get("id")).then(tn => { return this.store.find("tagNotification", tag.get("id")).then(tn => {
this.set("tagNotification", tn); this.set("tagNotification", tn);
return tag; return tag;
@ -45,18 +45,19 @@ export default Discourse.Route.extend({
const categorySlug = this.get('categorySlug'); const categorySlug = this.get('categorySlug');
const parentCategorySlug = this.get('parentCategorySlug'); const parentCategorySlug = this.get('parentCategorySlug');
const filter = this.get('navMode'); const filter = this.get('navMode');
const tag_id = (tag ? tag.id : 'none');
if (categorySlug) { if (categorySlug) {
var category = Discourse.Category.findBySlug(categorySlug, parentCategorySlug); var category = Discourse.Category.findBySlug(categorySlug, parentCategorySlug);
if (parentCategorySlug) { if (parentCategorySlug) {
params.filter = `tags/c/${parentCategorySlug}/${categorySlug}/${tag.id}/l/${filter}`; params.filter = `tags/c/${parentCategorySlug}/${categorySlug}/${tag_id}/l/${filter}`;
} else { } else {
params.filter = `tags/c/${categorySlug}/${tag.id}/l/${filter}`; params.filter = `tags/c/${categorySlug}/${tag_id}/l/${filter}`;
} }
this.set('category', category); this.set('category', category);
} else { } else {
params.filter = `tags/${tag.id}/l/${filter}`; params.filter = `tags/${tag_id}/l/${filter}`;
this.set('category', null); this.set('category', null);
} }
@ -74,10 +75,18 @@ export default Discourse.Route.extend({
const filterText = I18n.t('filters.' + this.get('navMode').replace('/', '.') + '.title'), const filterText = I18n.t('filters.' + this.get('navMode').replace('/', '.') + '.title'),
controller = this.controllerFor('tags.show'); controller = this.controllerFor('tags.show');
if (this.get('category')) { if (controller.get('model.id')) {
return I18n.t('tagging.filters.with_category', { filter: filterText, tag: controller.get('model.id'), category: this.get('category.name')}); if (this.get('category')) {
return I18n.t('tagging.filters.with_category', { filter: filterText, tag: controller.get('model.id'), category: this.get('category.name')});
} else {
return I18n.t('tagging.filters.without_category', { filter: filterText, tag: controller.get('model.id')});
}
} else { } else {
return I18n.t('tagging.filters.without_category', { filter: filterText, tag: controller.get('model.id')}); if (this.get('category')) {
return I18n.t('tagging.filters.untagged_with_category', { filter: filterText, category: this.get('category.name')});
} else {
return I18n.t('tagging.filters.untagged_without_category', { filter: filterText});
}
} }
}, },

View File

@ -2,6 +2,7 @@
{{#if tagId}} {{#if tagId}}
<a href {{action "expand"}} class="badge-category {{tagClass}}">{{tagId}}</a> <a href {{action "expand"}} class="badge-category {{tagClass}}">{{tagId}}</a>
{{else}} {{else}}
<!-- TODO: how to detect "no tags" is currently selected -->
<a href {{action "expand"}} class="badge-category {{tagClass}} home">{{allTagsLabel}}</a> <a href {{action "expand"}} class="badge-category {{tagClass}} home">{{allTagsLabel}}</a>
{{/if}} {{/if}}
@ -9,6 +10,7 @@
<a href {{action "expand"}} class={{dropdownButtonClass}}><i class={{iconClass}}></i></a> <a href {{action "expand"}} class={{dropdownButtonClass}}><i class={{iconClass}}></i></a>
<section class="{{unless expanded 'hidden'}} category-dropdown-menu chooser"> <section class="{{unless expanded 'hidden'}} category-dropdown-menu chooser">
<div class='cat'><a href={{allTagsUrl}} data-drop-close="true" class='badge-category home'>{{allTagsLabel}}</a></div> <div class='cat'><a href={{allTagsUrl}} data-drop-close="true" class='badge-category home'>{{allTagsLabel}}</a></div>
<div class='cat'><a href={{noTagsUrl}} data-drop-close="true" class='badge-category home'>{{noTagsLabel}}</a></div>
{{#if renderTags}} {{#if renderTags}}
{{#each tags as |t|}} {{#each tags as |t|}}
<div class='cat'> <div class='cat'>

View File

@ -57,7 +57,7 @@ class TagsController < ::ApplicationController
@list.more_topics_url = list_by_tag_path(tag_id: @tag_id, page: page + 1) @list.more_topics_url = list_by_tag_path(tag_id: @tag_id, page: page + 1)
@rss = "tag" @rss = "tag"
if @list.topics.size == 0 && !Tag.where(name: @tag_id).exists? if @list.topics.size == 0 && params[:tag_id] != 'none' && !Tag.where(name: @tag_id).exists?
raise Discourse::NotFound raise Discourse::NotFound
else else
respond_with_list(@list) respond_with_list(@list)
@ -203,7 +203,6 @@ class TagsController < ::ApplicationController
topic_ids: param_to_integer_list(:topic_ids), topic_ids: param_to_integer_list(:topic_ids),
exclude_category_ids: params[:exclude_category_ids], exclude_category_ids: params[:exclude_category_ids],
category: params[:category], category: params[:category],
tags: [params[:tag_id]],
order: params[:order], order: params[:order],
ascending: params[:ascending], ascending: params[:ascending],
min_posts: params[:min_posts], min_posts: params[:min_posts],
@ -217,6 +216,12 @@ class TagsController < ::ApplicationController
options[:no_subcategories] = true if params[:no_subcategories] == 'true' options[:no_subcategories] = true if params[:no_subcategories] == 'true'
options[:slow_platform] = true if slow_platform? options[:slow_platform] = true if slow_platform?
if params[:tag_id] == 'none'
options[:no_tags] = true
else
options[:tags] = [params[:tag_id]]
end
options options
end end

View File

@ -2123,6 +2123,7 @@ en:
tagging: tagging:
all_tags: "All Tags" all_tags: "All Tags"
selector_all_tags: "all tags" selector_all_tags: "all tags"
selector_no_tags: "no tags"
changed: "tags changed:" changed: "tags changed:"
tags: "Tags" tags: "Tags"
choose_for_topic: "choose optional tags for this topic" choose_for_topic: "choose optional tags for this topic"
@ -2139,6 +2140,8 @@ en:
filters: filters:
without_category: "%{filter} %{tag} topics" without_category: "%{filter} %{tag} topics"
with_category: "%{filter} %{tag} topics in %{category}" with_category: "%{filter} %{tag} topics in %{category}"
untagged_without_category: "%{filter} untagged topics"
untagged_with_category: "%{filter} untagged topics in %{category}"
notifications: notifications:
watching: watching:

View File

@ -20,6 +20,7 @@ class TopicQuery
visible visible
category category
tags tags
no_tags
order order
ascending ascending
no_subcategories no_subcategories
@ -465,6 +466,9 @@ class TopicQuery
else else
result = result.where("tags.name in (?)", @options[:tags]) 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"))
result = result.where.not(:id => TopicTag.select(:topic_id).uniq)
end end
end end

View File

@ -122,25 +122,32 @@ describe TopicQuery do
SiteSetting.tagging_enabled = true SiteSetting.tagging_enabled = true
end end
it "returns topics with the tag when filtered to it" do context "no category filter" do
tagged_topic1 = Fabricate(:topic, {tags: [tag]}) # create some topics before each test:
tagged_topic2 = Fabricate(:topic, {tags: [other_tag]}) let!(:tagged_topic1) { Fabricate(:topic, {tags: [tag]}) }
tagged_topic3 = Fabricate(:topic, {tags: [tag, other_tag]}) let!(:tagged_topic2) { Fabricate(:topic, {tags: [other_tag]}) }
no_tags_topic = Fabricate(:topic) let!(:tagged_topic3) { Fabricate(:topic, {tags: [tag, other_tag]}) }
let!(:no_tags_topic) { Fabricate(:topic) }
expect(TopicQuery.new(moderator, tags: [tag.name]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic3.id].sort) it "returns topics with the tag when filtered to it" do
expect(TopicQuery.new(moderator, tags: [tag.id]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic3.id].sort) expect(TopicQuery.new(moderator, tags: [tag.name]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic3.id].sort)
expect(TopicQuery.new(moderator, tags: [tag.id]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic3.id].sort)
two_tag_topic = TopicQuery.new(moderator, tags: [tag.name]).list_latest.topics.find { |t| t.id == tagged_topic3.id } two_tag_topic = TopicQuery.new(moderator, tags: [tag.name]).list_latest.topics.find { |t| t.id == tagged_topic3.id }
expect(two_tag_topic.tags.size).to eq(2) expect(two_tag_topic.tags.size).to eq(2)
# topics with ANY of the given tags: # topics with ANY of the given tags:
expect(TopicQuery.new(moderator, tags: [tag.name, other_tag.name]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic2.id, tagged_topic3.id].sort) expect(TopicQuery.new(moderator, tags: [tag.name, other_tag.name]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic2.id, tagged_topic3.id].sort)
expect(TopicQuery.new(moderator, tags: [tag.id, other_tag.id]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic2.id, tagged_topic3.id].sort) expect(TopicQuery.new(moderator, tags: [tag.id, other_tag.id]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic2.id, tagged_topic3.id].sort)
# TODO: topics with ALL of the given tags: # TODO: topics with ALL of the given tags:
# expect(TopicQuery.new(moderator, tags: [tag.name, other_tag.name]).list_latest.topics.map(&:id)).to eq([tagged_topic3.id].sort) # expect(TopicQuery.new(moderator, tags: [tag.name, other_tag.name]).list_latest.topics.map(&:id)).to eq([tagged_topic3.id].sort)
# expect(TopicQuery.new(moderator, tags: [tag.id, other_tag.id]).list_latest.topics.map(&:id)).to eq([tagged_topic3.id].sort) # 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 no tags" do
expect(TopicQuery.new(moderator, no_tags: true).list_latest.topics.map(&:id)).to eq([no_tags_topic.id])
end
end end
context "and categories too" do context "and categories too" do