mirror of
https://github.com/discourse/discourse.git
synced 2025-01-19 04:42:55 +08:00
FEATURE: ability to restrict some tags to a category while allowing all others
A new checkbox has been added to the Tags tab of the category settings modal which is used when some tags and/or tag groups are restricted to the category, and all other unrestricted tags should also be allowed. Default is the same as the previous behaviour: only allow the specified set of tags and tag groups in the category.
This commit is contained in:
parent
9faebfcfe0
commit
83996fc8ea
|
@ -1,3 +1,11 @@
|
|||
import { buildCategoryPanel } from "discourse/components/edit-category-panel";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
|
||||
export default buildCategoryPanel("tags", {});
|
||||
export default buildCategoryPanel("tags", {
|
||||
allowedTagsEmpty: Ember.computed.empty("category.allowed_tags"),
|
||||
allowedTagGroupsEmpty: Ember.computed.empty("category.allowed_tag_groups"),
|
||||
disableAllowGlobalTags: Ember.computed.and(
|
||||
"allowedTagsEmpty",
|
||||
"allowedTagGroupsEmpty"
|
||||
)
|
||||
});
|
||||
|
|
|
@ -118,6 +118,7 @@ const Category = RestModel.extend({
|
|||
all_topics_wiki: this.get("all_topics_wiki"),
|
||||
allowed_tags: this.get("allowed_tags"),
|
||||
allowed_tag_groups: this.get("allowed_tag_groups"),
|
||||
allow_global_tags: this.get("allow_global_tags"),
|
||||
sort_order: this.get("sort_order"),
|
||||
sort_ascending: this.get("sort_ascending"),
|
||||
topic_featured_link_allowed: this.get("topic_featured_link_allowed"),
|
||||
|
|
|
@ -11,6 +11,14 @@
|
|||
<section class="field">
|
||||
<label for="category-allowed-tag-groups">{{i18n 'category.tags_allowed_tag_groups'}}</label>
|
||||
{{tag-group-chooser id="category-allowed-tag-groups" tagGroups=category.allowed_tag_groups}}
|
||||
{{#link-to 'tagGroups'}}{{i18n 'category.manage_tag_groups_link'}}{{/link-to}}
|
||||
</section>
|
||||
|
||||
<section class='field'>
|
||||
<label>
|
||||
{{input type="checkbox" checked=category.allow_global_tags id="allow_global_tags" disabled=disableAllowGlobalTags}}
|
||||
{{i18n 'category.allow_global_tags_label'}}
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<section class='field'>
|
||||
|
|
|
@ -295,6 +295,7 @@ class CategoriesController < ApplicationController
|
|||
:minimum_required_tags,
|
||||
:navigate_to_first_post_after_read,
|
||||
:search_priority,
|
||||
:allow_global_tags,
|
||||
custom_fields: [params[:custom_fields].try(:keys)],
|
||||
permissions: [*p.try(:keys)],
|
||||
allowed_tags: [],
|
||||
|
|
|
@ -18,6 +18,7 @@ class CategorySerializer < BasicCategorySerializer
|
|||
:custom_fields,
|
||||
:allowed_tags,
|
||||
:allowed_tag_groups,
|
||||
:allow_global_tags,
|
||||
:topic_featured_link_allowed,
|
||||
:search_priority
|
||||
|
||||
|
|
|
@ -2483,11 +2483,13 @@ en:
|
|||
settings: "Settings"
|
||||
topic_template: "Topic Template"
|
||||
tags: "Tags"
|
||||
tags_allowed_tags: "Only allow these tags to be used in this category:"
|
||||
tags_allowed_tag_groups: "Only allow tags from these groups to be used in this category:"
|
||||
tags_allowed_tags: "Restrict these tags to this category:"
|
||||
tags_allowed_tag_groups: "Restrict these tag groups to this category:"
|
||||
tags_placeholder: "(Optional) list of allowed tags"
|
||||
tags_tab_description: "Tags and tag groups specified here will only be available in this category and other categories that also specify them. They won't be available for use in other categories."
|
||||
tag_groups_placeholder: "(Optional) list of allowed tag groups"
|
||||
manage_tag_groups_link: "Manage tag groups here."
|
||||
allow_global_tags_label: "Also allow other tags"
|
||||
topic_featured_link_allowed: "Allow featured links in this category"
|
||||
delete: "Delete Category"
|
||||
create: "New Category"
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class AddAllowGlobalTagsToCategories < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
add_column :categories, :allow_global_tags, :boolean, default: false, null: false
|
||||
end
|
||||
end
|
|
@ -100,21 +100,35 @@ module DiscourseTagging
|
|||
category = opts[:category]
|
||||
|
||||
if category && (category.tags.count > 0 || category.tag_groups.count > 0)
|
||||
if category.tags.count > 0 && category.tag_groups.count > 0
|
||||
tag_group_ids = category.tag_groups.pluck(:id)
|
||||
|
||||
query = query.where(
|
||||
"tags.id IN (SELECT tag_id FROM category_tags WHERE category_id = ?
|
||||
if category.allow_global_tags
|
||||
# Select tags that:
|
||||
# * are restricted to the given category
|
||||
# * belong to no tag groups and aren't restricted to other categories
|
||||
# * belong to tag groups that are not restricted to any categories
|
||||
query = query.where(<<~SQL, category.tag_groups.pluck(:id), category.id)
|
||||
tags.id IN (
|
||||
SELECT t.id FROM tags t
|
||||
LEFT JOIN category_tags ct ON t.id = ct.tag_id
|
||||
LEFT JOIN (
|
||||
SELECT xtgm.tag_id, xtgm.tag_group_id
|
||||
FROM tag_group_memberships xtgm
|
||||
INNER JOIN category_tag_groups ctg
|
||||
ON xtgm.tag_group_id = ctg.tag_group_id
|
||||
) AS tgm ON t.id = tgm.tag_id
|
||||
WHERE (tgm.tag_group_id IS NULL AND ct.category_id IS NULL)
|
||||
OR tgm.tag_group_id IN (?)
|
||||
OR ct.category_id = ?
|
||||
)
|
||||
SQL
|
||||
else
|
||||
# Select only tags that are restricted to the given category
|
||||
query = query.where(<<~SQL, category.id, category.tag_groups.pluck(:id))
|
||||
tags.id IN (
|
||||
SELECT tag_id FROM category_tags WHERE category_id = ?
|
||||
UNION
|
||||
SELECT tag_id FROM tag_group_memberships WHERE tag_group_id IN (?))",
|
||||
category.id, tag_group_ids
|
||||
)
|
||||
elsif category.tags.count > 0
|
||||
query = query.where("tags.id IN (SELECT tag_id FROM category_tags WHERE category_id = ?)", category.id)
|
||||
else # category.tag_groups.count > 0
|
||||
tag_group_ids = category.tag_groups.pluck(:id)
|
||||
|
||||
query = query.where("tags.id IN (SELECT tag_id FROM tag_group_memberships WHERE tag_group_id IN (?))", tag_group_ids)
|
||||
SELECT tag_id FROM tag_group_memberships WHERE tag_group_id IN (?)
|
||||
)
|
||||
SQL
|
||||
end
|
||||
elsif opts[:for_input] || opts[:for_topic] || category
|
||||
# exclude tags that are restricted to other categories
|
||||
|
|
|
@ -28,8 +28,8 @@ describe "category tag restrictions" do
|
|||
end
|
||||
|
||||
context "tags restricted to one category" do
|
||||
let(:category_with_tags) { Fabricate(:category) }
|
||||
let(:other_category) { Fabricate(:category) }
|
||||
let!(:category_with_tags) { Fabricate(:category) }
|
||||
let!(:other_category) { Fabricate(:category) }
|
||||
|
||||
before do
|
||||
category_with_tags.tags = [tag1, tag2]
|
||||
|
@ -49,8 +49,13 @@ describe "category tag restrictions" do
|
|||
expect(filter_allowed_tags(for_input: true)).to contain_exactly(tag3, tag4)
|
||||
expect(filter_allowed_tags(for_input: true, category: category_with_tags, selected_tags: [tag1.name])).to contain_exactly(tag2)
|
||||
expect(filter_allowed_tags(for_input: true, category: category_with_tags, selected_tags: [tag1.name], term: 'tag')).to contain_exactly(tag2)
|
||||
expect(filter_allowed_tags(for_input: true, category: other_category)).to contain_exactly(tag3, tag4)
|
||||
expect(filter_allowed_tags(for_input: true, category: other_category, selected_tags: [tag3.name])).to contain_exactly(tag4)
|
||||
expect(filter_allowed_tags(for_input: true, category: other_category, selected_tags: [tag3.name], term: 'tag')).to contain_exactly(tag4)
|
||||
end
|
||||
|
||||
|
||||
|
||||
it "can't create new tags in a restricted category" do
|
||||
post = create_post(category: category_with_tags, tags: [tag1.name, "newtag"])
|
||||
expect(post.topic.tags).to contain_exactly(tag1)
|
||||
|
@ -67,21 +72,44 @@ describe "category tag restrictions" do
|
|||
expect { other_category.update(allowed_tags: ['newtag']) }.to change { Tag.count }.by(1)
|
||||
expect { other_category.update(allowed_tags: [tag1.name, 'tag-stuff', tag2.name, 'another-tag']) }.to change { Tag.count }.by(2)
|
||||
end
|
||||
|
||||
context 'category allows other tags to be used' do
|
||||
before do
|
||||
category_with_tags.update_attributes!(allow_global_tags: true)
|
||||
end
|
||||
|
||||
it "search can show the permitted tags" do
|
||||
expect(filter_allowed_tags.count).to eq(Tag.count)
|
||||
expect(filter_allowed_tags(for_input: true, category: category_with_tags)).to contain_exactly(tag1, tag2, tag3, tag4)
|
||||
expect(filter_allowed_tags(for_input: true)).to contain_exactly(tag3, tag4)
|
||||
expect(filter_allowed_tags(for_input: true, category: category_with_tags, selected_tags: [tag1.name])).to contain_exactly(tag2, tag3, tag4)
|
||||
expect(filter_allowed_tags(for_input: true, category: category_with_tags, selected_tags: [tag1.name], term: 'tag')).to contain_exactly(tag2, tag3, tag4)
|
||||
expect(filter_allowed_tags(for_input: true, category: other_category)).to contain_exactly(tag3, tag4)
|
||||
expect(filter_allowed_tags(for_input: true, category: other_category, selected_tags: [tag3.name])).to contain_exactly(tag4)
|
||||
expect(filter_allowed_tags(for_input: true, category: other_category, selected_tags: [tag3.name], term: 'tag')).to contain_exactly(tag4)
|
||||
end
|
||||
|
||||
it "works if no tags are restricted to the category" do
|
||||
other_category.update_attributes!(allow_global_tags: true)
|
||||
expect(filter_allowed_tags(for_input: true, category: other_category)).to contain_exactly(tag3, tag4)
|
||||
expect(filter_allowed_tags(for_input: true, category: other_category, selected_tags: [tag3.name])).to contain_exactly(tag4)
|
||||
expect(filter_allowed_tags(for_input: true, category: other_category, selected_tags: [tag3.name], term: 'tag')).to contain_exactly(tag4)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "tag groups restricted to a category" do
|
||||
let!(:tag_group1) { Fabricate(:tag_group) }
|
||||
let(:category) { Fabricate(:category) }
|
||||
let(:other_category) { Fabricate(:category) }
|
||||
let!(:category) { Fabricate(:category) }
|
||||
let!(:other_category) { Fabricate(:category) }
|
||||
|
||||
before do
|
||||
tag_group1.tags = [tag1, tag2]
|
||||
category.allowed_tag_groups = [tag_group1.name]
|
||||
category.reload
|
||||
end
|
||||
|
||||
it "tags in the group are used by category tag restrictions" do
|
||||
category.allowed_tag_groups = [tag_group1.name]
|
||||
category.reload
|
||||
|
||||
expect(filter_allowed_tags(for_input: true, category: category)).to contain_exactly(tag1, tag2)
|
||||
expect(filter_allowed_tags(for_input: true)).to contain_exactly(tag3, tag4)
|
||||
|
||||
|
@ -92,7 +120,6 @@ describe "category tag restrictions" do
|
|||
end
|
||||
|
||||
it "groups and individual tags can be mixed" do
|
||||
category.allowed_tag_groups = [tag_group1.name]
|
||||
category.allowed_tags = [tag4.name]
|
||||
category.reload
|
||||
|
||||
|
@ -102,11 +129,72 @@ describe "category tag restrictions" do
|
|||
end
|
||||
|
||||
it "enforces restrictions when creating a topic" do
|
||||
category.allowed_tag_groups = [tag_group1.name]
|
||||
category.reload
|
||||
post = create_post(category: category, tags: [tag1.name, "newtag"])
|
||||
expect(post.topic.tags.map(&:name)).to eq([tag1.name])
|
||||
end
|
||||
|
||||
context 'category allows other tags to be used' do
|
||||
before do
|
||||
category.update_attributes!(allow_global_tags: true)
|
||||
end
|
||||
|
||||
it 'filters tags correctly' do
|
||||
expect(filter_allowed_tags(for_input: true, category: category)).to contain_exactly(tag1, tag2, tag3, tag4)
|
||||
expect(filter_allowed_tags(for_input: true)).to contain_exactly(tag3, tag4)
|
||||
expect(filter_allowed_tags(for_input: true, category: other_category)).to contain_exactly(tag3, tag4)
|
||||
|
||||
tag_group1.tags = [tag2, tag3, tag4]
|
||||
expect(filter_allowed_tags(for_input: true, category: category)).to contain_exactly(tag1, tag2, tag3, tag4)
|
||||
expect(filter_allowed_tags(for_input: true)).to contain_exactly(tag1)
|
||||
expect(filter_allowed_tags(for_input: true, category: other_category)).to contain_exactly(tag1)
|
||||
end
|
||||
|
||||
it "works if no tags are restricted to the category" do
|
||||
other_category.update_attributes!(allow_global_tags: true)
|
||||
expect(filter_allowed_tags(for_input: true, category: other_category)).to contain_exactly(tag3, tag4)
|
||||
tag_group1.tags = [tag2, tag3, tag4]
|
||||
expect(filter_allowed_tags(for_input: true, category: other_category)).to contain_exactly(tag1)
|
||||
end
|
||||
|
||||
context 'another category has restricted tags using groups' do
|
||||
let(:category2) { Fabricate(:category) }
|
||||
let(:tag_group2) { Fabricate(:tag_group) }
|
||||
|
||||
before do
|
||||
tag_group2.tags = [tag2, tag3]
|
||||
category2.allowed_tag_groups = [tag_group2.name]
|
||||
category2.reload
|
||||
end
|
||||
|
||||
it 'filters tags correctly' do
|
||||
expect(filter_allowed_tags(for_input: true, category: category2)).to contain_exactly(tag2, tag3)
|
||||
expect(filter_allowed_tags(for_input: true)).to contain_exactly(tag4)
|
||||
expect(filter_allowed_tags(for_input: true, category: other_category)).to contain_exactly(tag4)
|
||||
expect(filter_allowed_tags(for_input: true, category: category)).to contain_exactly(tag1, tag2, tag4)
|
||||
end
|
||||
|
||||
it "doesn't care about tags in a group that isn't used in a category" do
|
||||
unused_tag_group = Fabricate(:tag_group)
|
||||
unused_tag_group.tags = [tag4]
|
||||
expect(filter_allowed_tags(for_input: true, category: category2)).to contain_exactly(tag2, tag3)
|
||||
expect(filter_allowed_tags(for_input: true)).to contain_exactly(tag4)
|
||||
expect(filter_allowed_tags(for_input: true, category: other_category)).to contain_exactly(tag4)
|
||||
expect(filter_allowed_tags(for_input: true, category: category)).to contain_exactly(tag1, tag2, tag4) # tag4 missing
|
||||
end
|
||||
end
|
||||
|
||||
context 'another category has restricted tags' do
|
||||
let(:category2) { Fabricate(:category) }
|
||||
|
||||
it "doesn't filter tags that are also restricted in another category" do
|
||||
category2.tags = [tag2, tag3]
|
||||
expect(filter_allowed_tags(for_input: true, category: category2)).to contain_exactly(tag2, tag3)
|
||||
expect(filter_allowed_tags(for_input: true)).to contain_exactly(tag4)
|
||||
expect(filter_allowed_tags(for_input: true, category: other_category)).to contain_exactly(tag4)
|
||||
expect(filter_allowed_tags(for_input: true, category: category)).to contain_exactly(tag1, tag2, tag4) # tag2 missing
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "tag groups with parent tag" do
|
||||
|
|
|
@ -311,7 +311,7 @@ describe CategoriesController do
|
|||
end
|
||||
|
||||
describe "success" do
|
||||
it "updates the group correctly" do
|
||||
it "updates attributes correctly" do
|
||||
readonly = CategoryGroup.permission_types[:readonly]
|
||||
create_post = CategoryGroup.permission_types[:create_post]
|
||||
|
||||
|
@ -328,7 +328,8 @@ describe CategoriesController do
|
|||
custom_fields: {
|
||||
"dancing" => "frogs"
|
||||
},
|
||||
minimum_required_tags: ""
|
||||
minimum_required_tags: "",
|
||||
allow_global_tags: 'true'
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
|
@ -342,6 +343,7 @@ describe CategoriesController do
|
|||
expect(category.auto_close_hours).to eq(72)
|
||||
expect(category.custom_fields).to eq("dancing" => "frogs")
|
||||
expect(category.minimum_required_tags).to eq(0)
|
||||
expect(category.allow_global_tags).to eq(true)
|
||||
end
|
||||
|
||||
it 'logs the changes correctly' do
|
||||
|
|
Loading…
Reference in New Issue
Block a user