Tag groups can belong to groups (#10854)

This commit is contained in:
jbrw 2020-10-14 13:15:54 -04:00 committed by GitHub
parent e22370a8e1
commit 099bf97dca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 330 additions and 71 deletions

View File

@ -4,32 +4,109 @@ import { isEmpty } from "@ember/utils";
import Component from "@ember/component"; import Component from "@ember/component";
import { bufferedProperty } from "discourse/mixins/buffered-content"; import { bufferedProperty } from "discourse/mixins/buffered-content";
import PermissionType from "discourse/models/permission-type"; import PermissionType from "discourse/models/permission-type";
import Group from "discourse/models/group";
import bootbox from "bootbox"; import bootbox from "bootbox";
export default Component.extend(bufferedProperty("model"), { export default Component.extend(bufferedProperty("model"), {
tagName: "", tagName: "",
allGroups: null,
@discourseComputed("buffered.isSaving", "buffered.name", "buffered.tag_names") init() {
savingDisabled(isSaving, name, tagNames) { this._super(...arguments);
return isSaving || isEmpty(name) || isEmpty(tagNames); this.setGroupOptions();
},
setGroupOptions() {
Group.findAll().then((groups) => {
this.set("allGroups", groups);
});
},
@discourseComputed(
"buffered.isSaving",
"buffered.name",
"buffered.tag_names",
"buffered.permissions"
)
savingDisabled(isSaving, name, tagNames, permissions) {
return (
isSaving ||
isEmpty(name) ||
isEmpty(tagNames) ||
(!this.everyoneSelected(permissions) &&
isEmpty(this.selectedGroupNames(permissions)))
);
},
@discourseComputed("buffered.permissions")
showPrivateChooser(permissions) {
if (!permissions) {
return true;
}
return permissions.everyone !== PermissionType.READONLY;
},
@discourseComputed("buffered.permissions", "allGroups")
selectedGroupIds(permissions, allGroups) {
if (!permissions || !allGroups) {
return [];
}
const selectedGroupNames = Object.keys(permissions);
let groupIds = [];
allGroups.forEach((group) => {
if (selectedGroupNames.includes(group.name)) {
groupIds.push(group.id);
}
});
return groupIds;
},
everyoneSelected(permissions) {
if (!permissions) {
return true;
}
return permissions.everyone === PermissionType.FULL;
},
selectedGroupNames(permissions) {
if (!permissions) {
return [];
}
return Object.keys(permissions).filter((name) => name !== "everyone");
}, },
actions: { actions: {
setPermissions(permissionName) { setPermissionsType(permissionName) {
let updatedPermissions = Object.assign(
{},
this.buffered.get("permissions")
);
if (permissionName === "private") { if (permissionName === "private") {
this.buffered.set("permissions", { delete updatedPermissions.everyone;
staff: PermissionType.FULL,
});
} else if (permissionName === "visible") { } else if (permissionName === "visible") {
this.buffered.set("permissions", { updatedPermissions.everyone = PermissionType.READONLY;
staff: PermissionType.FULL,
everyone: PermissionType.READONLY,
});
} else { } else {
this.buffered.set("permissions", { updatedPermissions.everyone = PermissionType.FULL;
everyone: PermissionType.FULL,
});
} }
this.buffered.set("permissions", updatedPermissions);
},
setPermissionsGroups(groupIds) {
let permissions = {};
this.allGroups.forEach((group) => {
if (groupIds.includes(group.id)) {
permissions[group.name] = PermissionType.FULL;
}
});
this.buffered.set("permissions", permissions);
}, },
save() { save() {
@ -41,6 +118,14 @@ export default Component.extend(bufferedProperty("model"), {
"permissions" "permissions"
); );
// If 'everyone' is set to full, we can remove any groups.
if (
!attrs.permissions ||
attrs.permissions.everyone === PermissionType.FULL
) {
attrs.permissions = { everyone: PermissionType.FULL };
}
this.model.save(attrs).then(() => { this.model.save(attrs).then(() => {
this.commitBuffer(); this.commitBuffer();

View File

@ -38,7 +38,7 @@
value="public" value="public"
id="public-permission" id="public-permission"
selection=buffered.permissionName selection=buffered.permissionName
onChange=(action "setPermissions")}} onChange=(action "setPermissionsType")}}
<label class="radio" for="public-permission"> <label class="radio" for="public-permission">
{{i18n "tagging.groups.everyone_can_use"}} {{i18n "tagging.groups.everyone_can_use"}}
@ -51,11 +51,20 @@
value="visible" value="visible"
id="visible-permission" id="visible-permission"
selection=buffered.permissionName selection=buffered.permissionName
onChange=(action "setPermissions")}} onChange=(action "setPermissionsType")}}
<label class="radio" for="visible-permission"> <label class="radio" for="visible-permission">
{{i18n "tagging.groups.usable_only_by_staff"}} {{i18n "tagging.groups.usable_only_by_groups"}}
</label> </label>
<div class="group-access-control {{if showPrivateChooser "hidden"}}">
{{group-chooser
content=allGroups
value=selectedGroupIds
labelProperty="name"
onChange=(action "setPermissionsGroups")
}}
</div>
</div> </div>
<div> <div>
{{radio-button {{radio-button
@ -64,12 +73,20 @@
value="private" value="private"
id="private-permission" id="private-permission"
selection=buffered.permissionName selection=buffered.permissionName
onChange=(action "setPermissions")}} onChange=(action "setPermissionsType")}}
<label class="radio" for="private-permission"> <label class="radio" for="private-permission">
{{i18n "tagging.groups.visible_only_to_staff"}} {{i18n "tagging.groups.visible_only_to_groups"}}
</label> </label>
</div> </div>
<div class="group-access-control {{unless showPrivateChooser "hidden"}}">
{{group-chooser
content=allGroups
value=selectedGroupIds
labelProperty="name"
onChange=(action "setPermissionsGroups")}}
</div>
</section> </section>
{{d-button {{d-button

View File

@ -18,6 +18,19 @@ acceptance("Tag Groups", {
}, },
}); });
}); });
server.get("/groups/search.json", () => {
return helper.response([
{
id: 88,
name: "tl1",
},
{
id: 89,
name: "tl2",
},
]);
});
}, },
}); });
@ -36,3 +49,26 @@ test("tag groups can be saved and deleted", async (assert) => {
await click(".tag-chooser .choice:first"); await click(".tag-chooser .choice:first");
assert.ok(!find(".tag-group-content .btn.btn-danger")[0].disabled); assert.ok(!find(".tag-group-content .btn.btn-danger")[0].disabled);
}); });
QUnit.test(
"tag groups can have multiple groups added to them",
async (assert) => {
const tags = selectKit(".tag-chooser");
const groups = selectKit(".group-chooser");
await visit("/tag_groups");
await click(".content-list .btn");
await fillIn(".tag-group-content h1 input", "test tag group");
await tags.expand();
await tags.selectRowByValue("monkey");
await click("#private-permission");
assert.ok(find(".tag-group-content .btn.btn-default:disabled").length);
await groups.expand();
await groups.selectRowByIndex(1);
await groups.selectRowByIndex(0);
assert.ok(!find(".tag-group-content .btn.btn-default")[0].disabled);
}
);

View File

@ -256,6 +256,10 @@ header .discourse-tag {
h1 input { h1 input {
width: 100%; width: 100%;
} }
.group-access-control {
margin-left: 44px;
margin-bottom: 10px;
}
} }
.group-tags-list .tag-chooser { .group-tags-list .tag-chooser {
width: 100%; width: 100%;

View File

@ -82,19 +82,13 @@ class TagGroupsController < ApplicationController
tag_group = params.delete(:tag_group) tag_group = params.delete(:tag_group)
params.merge!(tag_group.permit!) if tag_group params.merge!(tag_group.permit!) if tag_group
if permissions = params[:permissions]
permissions.each do |k, v|
permissions[k] = v.to_i
end
end
result = params.permit( result = params.permit(
:id, :id,
:name, :name,
:one_per_topic, :one_per_topic,
tag_names: [], tag_names: [],
parent_tag_name: [], parent_tag_name: [],
permissions: permissions&.keys, permissions: {}
) )
result[:tag_names] ||= [] result[:tag_names] ||= []

View File

@ -3409,8 +3409,8 @@ en:
delete: "Delete" delete: "Delete"
confirm_delete: "Are you sure you want to delete this tag group?" confirm_delete: "Are you sure you want to delete this tag group?"
everyone_can_use: "Tags can be used by everyone" everyone_can_use: "Tags can be used by everyone"
usable_only_by_staff: "Tags are visible to everyone, but only staff can use them" usable_only_by_groups: "Tags are visible to everyone, but only the following groups can use them"
visible_only_to_staff: "Tags are visible only to staff" visible_only_to_groups: "Tags are visible only to the following groups"
topics: topics:
none: none:

View File

@ -4564,8 +4564,8 @@ en:
tags: tags:
title: "Tags" title: "Tags"
staff_tag_disallowed: 'The tag "%{tag}" may only be applied by staff.' restricted_tag_disallowed: 'You cannot apply the tag "%{tag}".'
staff_tag_remove_disallowed: 'The tag "%{tag}" may only be removed by staff.' restricted_tag_remove_disallowed: 'You cannot remove the tag "%{tag}".'
minimum_required_tags: minimum_required_tags:
one: "You must select at least %{count} tag." one: "You must select at least %{count} tag."
other: "You must select at least %{count} tags." other: "You must select at least %{count} tags."

View File

@ -23,31 +23,47 @@ module DiscourseTagging
end end
end end
# tags currently on the topic
old_tag_names = topic.tags.pluck(:name) || [] old_tag_names = topic.tags.pluck(:name) || []
# tags we're trying to add to the topic
new_tag_names = tag_names - old_tag_names new_tag_names = tag_names - old_tag_names
# tag names being removed from the topic
removed_tag_names = old_tag_names - tag_names removed_tag_names = old_tag_names - tag_names
# Protect staff-only tags # tag names which are visible, but not usable, by *some users*
unless guardian.is_staff? readonly_tags = DiscourseTagging.readonly_tag_names(guardian)
all_staff_tags = DiscourseTagging.staff_tag_names # tags names which are not visibile or usuable by *some users*
hidden_tags = DiscourseTagging.hidden_tag_names hidden_tags = DiscourseTagging.hidden_tag_names(guardian)
staff_tags = new_tag_names & all_staff_tags # tag names which ARE permitted by *this user*
staff_tags += new_tag_names & hidden_tags permitted_tags = DiscourseTagging.permitted_tag_names(guardian)
if staff_tags.present?
topic.errors.add(:base, I18n.t("tags.staff_tag_disallowed", tag: staff_tags.join(" ")))
return false
end
staff_tags = removed_tag_names & all_staff_tags # If this user has explicit permission to use certain tags,
if staff_tags.present? # we need to ensure those tags are removed from the list of
topic.errors.add(:base, I18n.t("tags.staff_tag_remove_disallowed", tag: staff_tags.join(" "))) # restricted tags
return false if permitted_tags.present?
end readonly_tags = readonly_tags - permitted_tags
hidden_tags = hidden_tags - permitted_tags
tag_names += removed_tag_names & hidden_tags
end end
# visible, but not usable, tags this user is trying to use
disallowed_tags = new_tag_names & readonly_tags
# hidden tags this user is trying to use
disallowed_tags += new_tag_names & hidden_tags
if disallowed_tags.present?
topic.errors.add(:base, I18n.t("tags.restricted_tag_disallowed", tag: disallowed_tags.join(" ")))
return false
end
removed_readonly_tags = removed_tag_names & readonly_tags
if removed_readonly_tags.present?
topic.errors.add(:base, I18n.t("tags.restricted_tag_remove_disallowed", tag: removed_readonly_tags.join(" ")))
return false
end
tag_names += removed_tag_names & hidden_tags
category = topic.category category = topic.category
tag_names = tag_names + old_tag_names if append tag_names = tag_names + old_tag_names if append
@ -182,8 +198,7 @@ module DiscourseTagging
INNER JOIN tag_group_memberships tgm ON tgm.tag_id = t.id /*and_name_like*/ INNER JOIN tag_group_memberships tgm ON tgm.tag_id = t.id /*and_name_like*/
INNER JOIN tag_groups tg ON tg.id = tgm.tag_group_id INNER JOIN tag_groups tg ON tg.id = tgm.tag_group_id
INNER JOIN tag_group_permissions tgp INNER JOIN tag_group_permissions tgp
ON tg.id = tgp.tag_group_id ON tg.id = tgp.tag_group_id /*and_group_ids*/
AND tgp.group_id = #{Group::AUTO_GROUPS[:everyone]}
AND tgp.permission_type = #{TagGroupPermission.permission_types[:full]} AND tgp.permission_type = #{TagGroupPermission.permission_types[:full]}
) )
SQL SQL
@ -217,6 +232,8 @@ module DiscourseTagging
sql = +"WITH #{TAG_GROUP_RESTRICTIONS_SQL}, #{CATEGORY_RESTRICTIONS_SQL}" sql = +"WITH #{TAG_GROUP_RESTRICTIONS_SQL}, #{CATEGORY_RESTRICTIONS_SQL}"
if (opts[:for_input] || opts[:for_topic]) && filter_for_non_staff if (opts[:for_input] || opts[:for_topic]) && filter_for_non_staff
sql << ", #{PERMITTED_TAGS_SQL} " sql << ", #{PERMITTED_TAGS_SQL} "
builder_params[:group_ids] = permitted_group_ids(guardian)
sql.gsub!("/*and_group_ids*/", "AND group_id IN (:group_ids)")
end end
outer_join = category.nil? || category.allow_global_tags || !category_has_restricted_tags outer_join = category.nil? || category.allow_global_tags || !category_has_restricted_tags
@ -296,12 +313,23 @@ module DiscourseTagging
end end
if filter_for_non_staff if filter_for_non_staff
# remove hidden tags group_ids = permitted_group_ids(guardian)
builder.where(<<~SQL, Group::AUTO_GROUPS[:everyone])
builder.where(<<~SQL, group_ids, group_ids)
id NOT IN ( id NOT IN (
SELECT tag_id (SELECT tgm.tag_id
FROM tag_group_memberships tgm FROM tag_group_permissions tgp
WHERE tag_group_id NOT IN (SELECT tag_group_id FROM tag_group_permissions WHERE group_id = ?) INNER JOIN tag_groups tg ON tgp.tag_group_id = tg.id
INNER JOIN tag_group_memberships tgm ON tg.id = tgm.tag_group_id
WHERE tgp.group_id NOT IN (?))
EXCEPT
(SELECT tgm.tag_id
FROM tag_group_permissions tgp
INNER JOIN tag_groups tg ON tgp.tag_group_id = tg.id
INNER JOIN tag_group_memberships tgm ON tg.id = tgm.tag_group_id
WHERE tgp.group_id IN (?))
) )
SQL SQL
end end
@ -348,16 +376,52 @@ module DiscourseTagging
guardian&.is_staff? ? [] : hidden_tags_query.pluck(:name) guardian&.is_staff? ? [] : hidden_tags_query.pluck(:name)
end end
# most restrictive level of tag groups
def self.hidden_tags_query def self.hidden_tags_query
Tag.joins(:tag_groups) query = Tag.joins(:tag_groups)
.where('tag_groups.id NOT IN ( .where('tag_groups.id NOT IN (
SELECT tag_group_id SELECT tag_group_id
FROM tag_group_permissions FROM tag_group_permissions
WHERE group_id = ?)', WHERE group_id = ?)',
Group::AUTO_GROUPS[:everyone] Group::AUTO_GROUPS[:everyone]
) )
query
end end
def self.permitted_group_ids(guardian = nil)
group_ids = [Group::AUTO_GROUPS[:everyone]]
if guardian.authenticated?
group_ids.concat(guardian.user.groups.pluck(:id))
end
group_ids
end
# read-only tags for this user
def self.readonly_tag_names(guardian = nil)
return [] if guardian&.is_staff?
query = Tag.joins(tag_groups: :tag_group_permissions)
.where('tag_group_permissions.permission_type = ?',
TagGroupPermission.permission_types[:readonly])
query.pluck(:name)
end
# explicit permissions to use these tags
def self.permitted_tag_names(guardian = nil)
query = Tag.joins(tag_groups: :tag_group_permissions)
.where('tag_group_permissions.group_id IN (?) AND tag_group_permissions.permission_type = ?',
permitted_group_ids(guardian),
TagGroupPermission.permission_types[:full]
)
query.pluck(:name).uniq
end
# middle level of tag group restrictions
def self.staff_tag_names def self.staff_tag_names
tag_names = Discourse.cache.read(TAGS_STAFF_CACHE_KEY) tag_names = Discourse.cache.read(TAGS_STAFF_CACHE_KEY)

View File

@ -85,6 +85,38 @@ describe DiscourseTagging do
end end
end end
context 'with tags visible only to non-admin group' do
fab!(:hidden_tag) { Fabricate(:tag) }
fab!(:group) { Fabricate(:group, name: "my-group") }
let!(:user_tag_group) { Fabricate(:tag_group, permissions: { "my-group" => 1 }, tag_names: [hidden_tag.name]) }
before do
group.add(user)
end
it 'should return all tags to member of group' do
tags = DiscourseTagging.filter_allowed_tags(Guardian.new(user)).to_a
expect(sorted_tag_names(tags)).to eq(sorted_tag_names([tag1, tag2, tag3, hidden_tag]))
end
it 'should allow a tag group to have multiple group permissions' do
group2 = Fabricate(:group, name: "another-group")
user2 = Fabricate(:user)
user3 = Fabricate(:user)
group2.add(user2)
user_tag_group.update!(permissions: { "my-group" => 1, "another-group" => 1 })
tags = DiscourseTagging.filter_allowed_tags(Guardian.new(user)).to_a
expect(sorted_tag_names(tags)).to eq(sorted_tag_names([tag1, tag2, tag3, hidden_tag]))
tags = DiscourseTagging.filter_allowed_tags(Guardian.new(user2)).to_a
expect(sorted_tag_names(tags)).to eq(sorted_tag_names([tag1, tag2, tag3, hidden_tag]))
tags = DiscourseTagging.filter_allowed_tags(Guardian.new(user3)).to_a
expect(sorted_tag_names(tags)).to eq(sorted_tag_names([tag1, tag2, tag3]))
end
end
context 'with required tags from tag group' do context 'with required tags from tag group' do
fab!(:tag_group) { Fabricate(:tag_group, tags: [tag1, tag2]) } fab!(:tag_group) { Fabricate(:tag_group, tags: [tag1, tag2]) }
fab!(:category) { Fabricate(:category, required_tag_group: tag_group, min_tags_from_required_group: 1) } fab!(:category) { Fabricate(:category, required_tag_group: tag_group, min_tags_from_required_group: 1) }
@ -199,7 +231,7 @@ describe DiscourseTagging do
end end
it 'returns staff only tags to everyone' do it 'returns staff only tags to everyone' do
create_staff_tags(['important']) create_staff_only_tags(['important'])
staff_tag = Tag.where(name: 'important').first staff_tag = Tag.where(name: 'important').first
topic.tags << staff_tag topic.tags << staff_tag
tags = DiscourseTagging.filter_visible(topic.tags, Guardian.new(user)) tags = DiscourseTagging.filter_visible(topic.tags, Guardian.new(user))
@ -209,17 +241,17 @@ describe DiscourseTagging do
end end
describe 'tag_topic_by_names' do describe 'tag_topic_by_names' do
context 'staff-only tags' do context 'visible but restricted tags' do
fab!(:topic) { Fabricate(:topic) } fab!(:topic) { Fabricate(:topic) }
before do before do
create_staff_tags(['alpha']) create_staff_only_tags(['alpha'])
end end
it "regular users can't add staff-only tags" do it "regular users can't add staff-only tags" do
valid = DiscourseTagging.tag_topic_by_names(topic, Guardian.new(user), ['alpha']) valid = DiscourseTagging.tag_topic_by_names(topic, Guardian.new(user), ['alpha'])
expect(valid).to eq(false) expect(valid).to eq(false)
expect(topic.errors[:base]&.first).to eq(I18n.t("tags.staff_tag_disallowed", tag: 'alpha')) expect(topic.errors[:base]&.first).to eq(I18n.t("tags.restricted_tag_disallowed", tag: 'alpha'))
end end
it 'staff can add staff-only tags' do it 'staff can add staff-only tags' do
@ -227,6 +259,29 @@ describe DiscourseTagging do
expect(valid).to eq(true) expect(valid).to eq(true)
expect(topic.errors[:base]).to be_empty expect(topic.errors[:base]).to be_empty
end end
context 'non-staff users in tag group groups' do
fab!(:non_staff_group) { Fabricate(:group, name: 'non_staff_group') }
before do
create_limited_tags('Group for Non-Staff', non_staff_group.id, ['alpha'])
end
it 'can use hidden tag if in correct group' do
non_staff_group.add(user)
valid = DiscourseTagging.tag_topic_by_names(topic, Guardian.new(user), ['alpha'])
expect(valid).to eq(true)
expect(topic.errors[:base]).to be_empty
end
it 'will return error if user is not in correct group' do
user2 = Fabricate(:user)
valid = DiscourseTagging.tag_topic_by_names(topic, Guardian.new(user2), ['alpha'])
expect(valid).to eq(false)
end
end
end end
it 'respects category allow_global_tags setting' do it 'respects category allow_global_tags setting' do

View File

@ -787,14 +787,14 @@ describe PostRevisor do
end end
it "can't add staff-only tags" do it "can't add staff-only tags" do
create_staff_tags(['important']) create_staff_only_tags(['important'])
result = subject.revise!(user, raw: "lets totally update the body", tags: ['important', 'stuff']) result = subject.revise!(user, raw: "lets totally update the body", tags: ['important', 'stuff'])
expect(result).to eq(false) expect(result).to eq(false)
expect(post.topic.errors.present?).to eq(true) expect(post.topic.errors.present?).to eq(true)
end end
it "staff can add staff-only tags" do it "staff can add staff-only tags" do
create_staff_tags(['important']) create_staff_only_tags(['important'])
result = subject.revise!(admin, raw: "lets totally update the body", tags: ['important', 'stuff']) result = subject.revise!(admin, raw: "lets totally update the body", tags: ['important', 'stuff'])
expect(result).to eq(true) expect(result).to eq(true)
post.reload post.reload
@ -803,7 +803,7 @@ describe PostRevisor do
context "with staff-only tags" do context "with staff-only tags" do
before do before do
create_staff_tags(['important']) create_staff_only_tags(['important'])
topic = post.topic topic = post.topic
topic.tags = [Fabricate(:tag, name: "super"), Tag.where(name: "important").first, Fabricate(:tag, name: "stuff")] topic.tags = [Fabricate(:tag, name: "super"), Tag.where(name: "important").first, Fabricate(:tag, name: "stuff")]
end end

View File

@ -952,7 +952,7 @@ describe Search do
end end
it 'shows staff tags' do it 'shows staff tags' do
create_staff_tags(["#{tag.name}9"]) create_staff_only_tags(["#{tag.name}9"])
expect(Search.execute(tag.name, guardian: Guardian.new(admin)).tags.map(&:name)).to eq([tag.name, "#{tag.name}9"]) expect(Search.execute(tag.name, guardian: Guardian.new(admin)).tags.map(&:name)).to eq([tag.name, "#{tag.name}9"])
expect(search.tags.map(&:name)).to eq([tag.name, "#{tag.name}9"]) expect(search.tags.map(&:name)).to eq([tag.name, "#{tag.name}9"])

View File

@ -103,7 +103,7 @@ describe TopicCreator do
context 'staff-only tags' do context 'staff-only tags' do
before do before do
create_staff_tags(['alpha']) create_staff_only_tags(['alpha'])
end end
it "regular users can't add staff-only tags" do it "regular users can't add staff-only tags" do

View File

@ -200,7 +200,7 @@ describe TagUser do
staff = Fabricate(:admin) staff = Fabricate(:admin)
topic = create_post.topic topic = create_post.topic
create_staff_tags(['foo']) create_staff_only_tags(['foo'])
result = DiscourseTagging.tag_topic_by_names(topic, Guardian.new(user), ["foo"]) result = DiscourseTagging.tag_topic_by_names(topic, Guardian.new(user), ["foo"])
expect(result).to eq(false) expect(result).to eq(false)

View File

@ -93,16 +93,20 @@ module Helpers
fixture_file("emails/#{email_name}.eml") fixture_file("emails/#{email_name}.eml")
end end
def create_staff_tags(tag_names) def create_staff_only_tags(tag_names)
tag_group = Fabricate(:tag_group, name: 'Staff Tags') create_limited_tags('Staff Tags', Group::AUTO_GROUPS[:staff], tag_names)
TagGroupPermission.create!( end
def create_limited_tags(tag_group_name, group_id, tag_names)
tag_group = Fabricate(:tag_group, name: tag_group_name)
TagGroupPermission.where(
tag_group: tag_group, tag_group: tag_group,
group_id: Group::AUTO_GROUPS[:everyone], group_id: Group::AUTO_GROUPS[:everyone],
permission_type: TagGroupPermission.permission_types[:readonly] permission_type: TagGroupPermission.permission_types[:full]
) ).update(permission_type: TagGroupPermission.permission_types[:readonly])
TagGroupPermission.create!( TagGroupPermission.create!(
tag_group: tag_group, tag_group: tag_group,
group_id: Group::AUTO_GROUPS[:staff], group_id: group_id,
permission_type: TagGroupPermission.permission_types[:full] permission_type: TagGroupPermission.permission_types[:full]
) )
tag_names.each do |name| tag_names.each do |name|