FEATURE: Improve group settings and members management (#11878)
This pull requests contains a series of improvements to groups settings and member management such as: - Showing which users have set a group as primary - Moving similar settings together under Effects - Adding bulk select and actions to members page
This commit is contained in:
parent
0cc178d58b
commit
901cee55cd
|
@ -11,7 +11,56 @@ export default DropdownSelectBoxComponent.extend({
|
||||||
showFullTitle: false,
|
showFullTitle: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
content: computed("member.owner", function () {
|
contentBulk() {
|
||||||
|
const items = [];
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
id: "removeMembers",
|
||||||
|
name: I18n.t("groups.members.remove_members"),
|
||||||
|
description: I18n.t("groups.members.remove_members_description"),
|
||||||
|
icon: "user-times",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.bulkSelection.some((m) => !m.owner)) {
|
||||||
|
items.push({
|
||||||
|
id: "makeOwners",
|
||||||
|
name: I18n.t("groups.members.make_owners"),
|
||||||
|
description: I18n.t("groups.members.make_owners_description"),
|
||||||
|
icon: "shield-alt",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.bulkSelection.some((m) => m.owner)) {
|
||||||
|
items.push({
|
||||||
|
id: "removeOwners",
|
||||||
|
name: I18n.t("groups.members.remove_owners"),
|
||||||
|
description: I18n.t("groups.members.remove_owners_description"),
|
||||||
|
icon: "shield-alt",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.bulkSelection.some((m) => !m.primary)) {
|
||||||
|
items.push({
|
||||||
|
id: "setPrimary",
|
||||||
|
name: I18n.t("groups.members.make_all_primary"),
|
||||||
|
description: I18n.t("groups.members.make_all_primary_description"),
|
||||||
|
icon: "id-card",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.bulkSelection.some((m) => m.primary)) {
|
||||||
|
items.push({
|
||||||
|
id: "unsetPrimary",
|
||||||
|
name: I18n.t("groups.members.remove_all_primary"),
|
||||||
|
description: I18n.t("groups.members.remove_all_primary_description"),
|
||||||
|
icon: "id-card",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
},
|
||||||
|
|
||||||
|
contentSingle() {
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
id: "removeMember",
|
id: "removeMember",
|
||||||
|
@ -45,6 +94,39 @@ export default DropdownSelectBoxComponent.extend({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.currentUser.staff) {
|
||||||
|
if (this.member.primary) {
|
||||||
|
items.push({
|
||||||
|
id: "removePrimary",
|
||||||
|
name: I18n.t("groups.members.remove_primary"),
|
||||||
|
description: I18n.t("groups.members.remove_primary_description", {
|
||||||
|
username: this.get("member.username"),
|
||||||
|
}),
|
||||||
|
icon: "id-card",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
items.push({
|
||||||
|
id: "makePrimary",
|
||||||
|
name: I18n.t("groups.members.make_primary"),
|
||||||
|
description: I18n.t("groups.members.make_primary_description", {
|
||||||
|
username: this.get("member.username"),
|
||||||
|
}),
|
||||||
|
icon: "id-card",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}),
|
},
|
||||||
|
|
||||||
|
content: computed(
|
||||||
|
"bulkSelection.[]",
|
||||||
|
"member.owner",
|
||||||
|
"member.primary",
|
||||||
|
function () {
|
||||||
|
return this.bulkSelection !== undefined
|
||||||
|
? this.contentBulk()
|
||||||
|
: this.contentSingle();
|
||||||
|
}
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import Controller, { inject as controller } from "@ember/controller";
|
import Controller, { inject as controller } from "@ember/controller";
|
||||||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
import discourseDebounce from "discourse-common/lib/debounce";
|
import discourseDebounce from "discourse-common/lib/debounce";
|
||||||
import { gt } from "@ember/object/computed";
|
import { gt } from "@ember/object/computed";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
@ -16,8 +17,11 @@ export default Controller.extend({
|
||||||
filterInput: null,
|
filterInput: null,
|
||||||
|
|
||||||
loading: false,
|
loading: false,
|
||||||
|
isBulk: false,
|
||||||
showActions: false,
|
showActions: false,
|
||||||
|
|
||||||
|
bulkSelection: null,
|
||||||
|
|
||||||
@observes("filterInput")
|
@observes("filterInput")
|
||||||
_setFilter() {
|
_setFilter() {
|
||||||
discourseDebounce(
|
discourseDebounce(
|
||||||
|
@ -51,6 +55,10 @@ export default Controller.extend({
|
||||||
this.model.members.length >= this.model.user_count,
|
this.model.members.length >= this.model.user_count,
|
||||||
loading: false,
|
loading: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.refresh) {
|
||||||
|
this.set("bulkSelection", []);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -97,6 +105,68 @@ export default Controller.extend({
|
||||||
case "removeOwner":
|
case "removeOwner":
|
||||||
this.removeOwner(member);
|
this.removeOwner(member);
|
||||||
break;
|
break;
|
||||||
|
case "makePrimary":
|
||||||
|
member
|
||||||
|
.setPrimaryGroup(this.model.id)
|
||||||
|
.then(() => member.set("primary", true));
|
||||||
|
break;
|
||||||
|
case "removePrimary":
|
||||||
|
member.setPrimaryGroup(null).then(() => member.set("primary", false));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
actOnSelection(selection, actionId) {
|
||||||
|
if (!selection || selection.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (actionId) {
|
||||||
|
case "removeMembers":
|
||||||
|
return ajax(`/groups/${this.model.id}/members.json`, {
|
||||||
|
type: "DELETE",
|
||||||
|
data: { user_ids: selection.map((u) => u.id).join(",") },
|
||||||
|
}).then(() => {
|
||||||
|
this.model.findMembers(this.memberParams, true);
|
||||||
|
this.set("isBulk", false);
|
||||||
|
});
|
||||||
|
|
||||||
|
case "makeOwners":
|
||||||
|
return ajax(`/admin/groups/${this.model.id}/owners.json`, {
|
||||||
|
type: "PUT",
|
||||||
|
data: {
|
||||||
|
group: { usernames: selection.map((u) => u.username).join(",") },
|
||||||
|
},
|
||||||
|
}).then(() => {
|
||||||
|
selection.forEach((s) => s.set("owner", true));
|
||||||
|
this.set("isBulk", false);
|
||||||
|
});
|
||||||
|
|
||||||
|
case "removeOwners":
|
||||||
|
return ajax(`/admin/groups/${this.model.id}/owners.json`, {
|
||||||
|
type: "DELETE",
|
||||||
|
data: {
|
||||||
|
group: { usernames: selection.map((u) => u.username).join(",") },
|
||||||
|
},
|
||||||
|
}).then(() => {
|
||||||
|
selection.forEach((s) => s.set("owner", false));
|
||||||
|
this.set("isBulk", false);
|
||||||
|
});
|
||||||
|
|
||||||
|
case "setPrimary":
|
||||||
|
case "unsetPrimary":
|
||||||
|
const primary = actionId === "setPrimary";
|
||||||
|
return ajax(`/admin/groups/${this.model.id}/primary.json`, {
|
||||||
|
type: "PUT",
|
||||||
|
data: {
|
||||||
|
group: { usernames: selection.map((u) => u.username).join(",") },
|
||||||
|
primary,
|
||||||
|
},
|
||||||
|
}).then(() => {
|
||||||
|
selection.forEach((s) => s.set("primary", primary));
|
||||||
|
this.set("isBulk", false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -124,4 +194,33 @@ export default Controller.extend({
|
||||||
.catch(popupAjaxError);
|
.catch(popupAjaxError);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
toggleBulkSelect() {
|
||||||
|
this.setProperties({
|
||||||
|
isBulk: !this.isBulk,
|
||||||
|
bulkSelection: [],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
bulkSelectAll() {
|
||||||
|
$("input.bulk-select:not(:checked)").click();
|
||||||
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
bulkClearAll() {
|
||||||
|
$("input.bulk-select:checked").click();
|
||||||
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
selectMember(member, e) {
|
||||||
|
this.set("bulkSelection", this.bulkSelection || []);
|
||||||
|
|
||||||
|
if (e.target.checked) {
|
||||||
|
this.bulkSelection.pushObject(member);
|
||||||
|
} else {
|
||||||
|
this.bulkSelection.removeObject(member);
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -56,6 +56,7 @@ const Group = RestModel.extend({
|
||||||
members.pushObjects(
|
members.pushObjects(
|
||||||
result.members.map((member) => {
|
result.members.map((member) => {
|
||||||
member.owner = ownerIds.has(member.id);
|
member.owner = ownerIds.has(member.id);
|
||||||
|
member.primary = member.primary_group_name === this.name;
|
||||||
return User.create(member);
|
return User.create(member);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -960,6 +960,13 @@ const User = RestModel.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setPrimaryGroup(primaryGroupId) {
|
||||||
|
return ajax(`/admin/users/${this.id}/primary_group`, {
|
||||||
|
type: "PUT",
|
||||||
|
data: { primary_group_id: primaryGroupId },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
enterDoNotDisturbFor(duration) {
|
enterDoNotDisturbFor(duration) {
|
||||||
return ajax({
|
return ajax({
|
||||||
url: "/do-not-disturb.json",
|
url: "/do-not-disturb.json",
|
||||||
|
|
|
@ -1,3 +1,45 @@
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label">{{i18n "groups.manage.membership.access"}}</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
{{input type="checkbox"
|
||||||
|
class="group-form-public-admission"
|
||||||
|
checked=model.public_admission
|
||||||
|
disabled=disablePublicSetting}}
|
||||||
|
|
||||||
|
{{i18n "groups.public_admission"}}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
{{input type="checkbox"
|
||||||
|
class="group-form-public-exit"
|
||||||
|
checked=model.public_exit}}
|
||||||
|
|
||||||
|
{{i18n "groups.public_exit"}}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
{{input type="checkbox"
|
||||||
|
class="group-form-allow-membership-requests"
|
||||||
|
checked=model.allow_membership_requests
|
||||||
|
disabled=disableMembershipRequestSetting}}
|
||||||
|
|
||||||
|
{{i18n "groups.allow_membership_requests"}}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{{#if model.allow_membership_requests}}
|
||||||
|
<div>
|
||||||
|
<label for="membership-request-template">
|
||||||
|
{{i18n "groups.membership_request_template"}}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{{expanding-text-area name="membership-request-template"
|
||||||
|
class="group-form-membership-request-template input-xxlarge"
|
||||||
|
value=model.membership_request_template}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
{{#if model.can_admin_group}}
|
{{#if model.can_admin_group}}
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label class="control-label">{{i18n "admin.groups.manage.membership.automatic"}}</label>
|
<label class="control-label">{{i18n "admin.groups.manage.membership.automatic"}}</label>
|
||||||
|
@ -41,48 +83,21 @@
|
||||||
|
|
||||||
{{i18n "admin.groups.manage.membership.primary_group"}}
|
{{i18n "admin.groups.manage.membership.primary_group"}}
|
||||||
</label>
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="title">
|
||||||
|
{{i18n "admin.groups.default_title"}}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{{input value=model.title name="title" class="input-xxlarge"}}
|
||||||
|
|
||||||
|
<div class="control-instructions">
|
||||||
|
{{i18n "admin.groups.default_title_description"}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
{{group-flair-inputs model=model}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label">{{i18n "groups.manage.membership.access"}}</label>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
{{input type="checkbox"
|
|
||||||
class="group-form-public-admission"
|
|
||||||
checked=model.public_admission
|
|
||||||
disabled=disablePublicSetting}}
|
|
||||||
|
|
||||||
{{i18n "groups.public_admission"}}
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
{{input type="checkbox"
|
|
||||||
class="group-form-public-exit"
|
|
||||||
checked=model.public_exit}}
|
|
||||||
|
|
||||||
{{i18n "groups.public_exit"}}
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
{{input type="checkbox"
|
|
||||||
class="group-form-allow-membership-requests"
|
|
||||||
checked=model.allow_membership_requests
|
|
||||||
disabled=disableMembershipRequestSetting}}
|
|
||||||
|
|
||||||
{{i18n "groups.allow_membership_requests"}}
|
|
||||||
</label>
|
|
||||||
|
|
||||||
{{#if model.allow_membership_requests}}
|
|
||||||
<div>
|
|
||||||
<label for="membership-request-template">
|
|
||||||
{{i18n "groups.membership_request_template"}}
|
|
||||||
</label>
|
|
||||||
|
|
||||||
{{expanding-text-area name="membership-request-template"
|
|
||||||
class="group-form-membership-request-template input-xxlarge"
|
|
||||||
value=model.membership_request_template}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -19,20 +19,6 @@
|
||||||
class="input-xxlarge group-form-full-name"
|
class="input-xxlarge group-form-full-name"
|
||||||
value=model.full_name}}
|
value=model.full_name}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#if this.currentUser.can_create_group}}
|
|
||||||
<div class="control-group">
|
|
||||||
<label class="control-label" for="title">
|
|
||||||
{{i18n "admin.groups.default_title"}}
|
|
||||||
</label>
|
|
||||||
|
|
||||||
{{input value=model.title name="title" class="input-xxlarge"}}
|
|
||||||
|
|
||||||
<div class="control-instructions">
|
|
||||||
{{i18n "admin.groups.default_title_description"}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
|
@ -43,9 +29,5 @@
|
||||||
{{#if canEdit}}
|
{{#if canEdit}}
|
||||||
{{yield}}
|
{{yield}}
|
||||||
|
|
||||||
<div class="control-group">
|
|
||||||
{{group-flair-inputs model=model}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{plugin-outlet name="group-edit" args=(hash group=model)}}
|
{{plugin-outlet name="group-edit" args=(hash group=model)}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -23,26 +23,50 @@
|
||||||
{{#load-more selector=".group-members tr" action=(action "loadMore")}}
|
{{#load-more selector=".group-members tr" action=(action "loadMore")}}
|
||||||
<table class="group-members">
|
<table class="group-members">
|
||||||
<thead>
|
<thead>
|
||||||
{{table-header-toggle order=order asc=asc field="username_lower" labelKey="username"}}
|
<th class="bulk-select">
|
||||||
<th class="group-owner">{{i18n "groups.members.owner"}}</th>
|
{{flat-button class="bulk-select" icon="list" action=(action "toggleBulkSelect") title="topics.bulk.toggle"}}
|
||||||
|
</th>
|
||||||
|
{{#if isBulk}}
|
||||||
|
<th class="bulk-select-buttons">
|
||||||
|
{{d-button action=(action "bulkSelectAll") label="topics.bulk.select_all"}}
|
||||||
|
{{d-button action=(action "bulkClearAll") label="topics.bulk.clear_all"}}
|
||||||
|
</th>
|
||||||
|
{{/if}}
|
||||||
|
{{table-header-toggle order=order asc=asc field="username_lower" labelKey="username" class="username"}}
|
||||||
|
<th class="group-owner"></th>
|
||||||
{{table-header-toggle order=order asc=asc field="added_at" labelKey="groups.member_added"}}
|
{{table-header-toggle order=order asc=asc field="added_at" labelKey="groups.member_added"}}
|
||||||
{{table-header-toggle order=order asc=asc field="last_posted_at" labelKey="last_post"}}
|
{{table-header-toggle order=order asc=asc field="last_posted_at" labelKey="last_post"}}
|
||||||
{{table-header-toggle order=order asc=asc field="last_seen_at" labelKey="last_seen"}}
|
{{table-header-toggle order=order asc=asc field="last_seen_at" labelKey="last_seen"}}
|
||||||
<th></th>
|
<th>
|
||||||
|
{{#if isBulk}}
|
||||||
|
{{group-member-dropdown
|
||||||
|
bulkSelection=bulkSelection
|
||||||
|
canAdminGroup=model.can_admin_group
|
||||||
|
onChange=(action "actOnSelection" bulkSelection)
|
||||||
|
}}
|
||||||
|
{{/if}}
|
||||||
|
</th>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{{#each model.members as |m|}}
|
{{#each model.members as |m|}}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="avatar">
|
{{#if isBulk}}
|
||||||
|
<td class="bulk-select">
|
||||||
|
{{input type="checkbox" class="bulk-select" click=(action "selectMember" m)}}
|
||||||
|
</td>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<td class="avatar" colspan="2">
|
||||||
{{user-info user=m skipName=skipName}}
|
{{user-info user=m skipName=skipName}}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="group-owner">
|
<td class="group-owner">
|
||||||
{{#if m.owner}}
|
{{#if m.owner}}
|
||||||
<strong class="group-owner-label">
|
{{d-icon "shield-alt"}} {{i18n "groups.members.owner"}}<br>
|
||||||
{{d-icon "shield-alt"}}
|
{{/if}}
|
||||||
</strong>
|
{{#if m.primary}}
|
||||||
|
{{i18n "groups.members.primary"}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
} from "discourse/tests/helpers/qunit-helpers";
|
} from "discourse/tests/helpers/qunit-helpers";
|
||||||
import { click, visit } from "@ember/test-helpers";
|
import { click, visit } from "@ember/test-helpers";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
|
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||||
import { test } from "qunit";
|
import { test } from "qunit";
|
||||||
|
|
||||||
acceptance("Group Members - Anonymous", function () {
|
acceptance("Group Members - Anonymous", function () {
|
||||||
|
@ -34,6 +35,12 @@ acceptance("Group Members - Anonymous", function () {
|
||||||
acceptance("Group Members", function (needs) {
|
acceptance("Group Members", function (needs) {
|
||||||
needs.user();
|
needs.user();
|
||||||
|
|
||||||
|
needs.pretender((server, helper) => {
|
||||||
|
server.put("/admin/groups/47/owners.json", () => {
|
||||||
|
return helper.response({ success: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("Viewing Members as a group owner", async function (assert) {
|
test("Viewing Members as a group owner", async function (assert) {
|
||||||
updateCurrentUser({ moderator: false, admin: false });
|
updateCurrentUser({ moderator: false, admin: false });
|
||||||
|
|
||||||
|
@ -61,4 +68,18 @@ acceptance("Group Members", function (needs) {
|
||||||
"it should display the right filter placehodler"
|
"it should display the right filter placehodler"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Shows bulk actions", async function (assert) {
|
||||||
|
await visit("/g/discourse");
|
||||||
|
|
||||||
|
assert.ok(count("button.bulk-select") > 0);
|
||||||
|
await click("button.bulk-select");
|
||||||
|
|
||||||
|
await click("input.bulk-select:nth(0)");
|
||||||
|
await click("input.bulk-select:nth(1)");
|
||||||
|
|
||||||
|
const memberDropdown = selectKit(".group-member-dropdown:first");
|
||||||
|
await memberDropdown.expand();
|
||||||
|
await memberDropdown.selectRowByValue("makeOwners");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -50,6 +50,11 @@ acceptance("Managing Group Membership", function (needs) {
|
||||||
"it should disable group allow_membership_request input"
|
"it should disable group allow_membership_request input"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
queryAll(".group-flair-inputs").length === 1,
|
||||||
|
"it should display avatar flair inputs"
|
||||||
|
);
|
||||||
|
|
||||||
await click(".group-form-public-admission");
|
await click(".group-form-public-admission");
|
||||||
await click(".group-form-allow-membership-requests");
|
await click(".group-form-allow-membership-requests");
|
||||||
|
|
||||||
|
|
|
@ -24,10 +24,6 @@ acceptance("Managing Group Profile", function (needs) {
|
||||||
test("As an admin", async function (assert) {
|
test("As an admin", async function (assert) {
|
||||||
await visit("/g/discourse/manage/profile");
|
await visit("/g/discourse/manage/profile");
|
||||||
|
|
||||||
assert.ok(
|
|
||||||
queryAll(".group-flair-inputs").length === 1,
|
|
||||||
"it should display avatar flair inputs"
|
|
||||||
);
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
queryAll(".group-form-bio").length === 1,
|
queryAll(".group-form-bio").length === 1,
|
||||||
"it should display group bio input"
|
"it should display group bio input"
|
||||||
|
|
|
@ -107,39 +107,48 @@ table.group-members {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
|
|
||||||
th:first-child {
|
|
||||||
width: 30%;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
th:last-child {
|
|
||||||
width: 5%;
|
|
||||||
}
|
|
||||||
|
|
||||||
th.group-members-actions {
|
|
||||||
width: 5%;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
th {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
&.bulk-select {
|
||||||
|
height: 30px;
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.bulk-select-buttons {
|
||||||
|
text-align: left;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.username {
|
||||||
|
text-align: left;
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.bulk-select-buttons + .username {
|
||||||
|
width: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.group-members-actions {
|
||||||
|
width: 5%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tr {
|
td {
|
||||||
.user-info {
|
color: var(--primary-medium);
|
||||||
display: block;
|
padding: 0.8em 0;
|
||||||
.avatar-flair {
|
text-align: center;
|
||||||
color: var(--primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
td:first-child {
|
&.avatar {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
td {
|
.user-info {
|
||||||
text-align: center;
|
display: block;
|
||||||
color: var(--primary-medium);
|
|
||||||
padding: 0.8em 0;
|
.avatar-flair {
|
||||||
|
color: var(--primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,15 +125,35 @@ class Admin::GroupsController < Admin::AdminController
|
||||||
return can_not_modify_automatic if group.automatic
|
return can_not_modify_automatic if group.automatic
|
||||||
guardian.ensure_can_edit_group!(group)
|
guardian.ensure_can_edit_group!(group)
|
||||||
|
|
||||||
user = User.find(params[:user_id].to_i)
|
if params[:user_id].present?
|
||||||
group.group_users.where(user_id: user.id).update_all(owner: false)
|
users = [User.find_by(id: params[:user_id].to_i)]
|
||||||
GroupActionLogger.new(current_user, group).log_remove_user_as_group_owner(user)
|
elsif usernames = group_params[:usernames].presence
|
||||||
|
users = User.where(username: usernames.split(","))
|
||||||
|
else
|
||||||
|
raise Discourse::InvalidParameters.new(:user_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
users.each do |user|
|
||||||
|
group.group_users.where(user_id: user.id).update_all(owner: false)
|
||||||
|
GroupActionLogger.new(current_user, group).log_remove_user_as_group_owner(user)
|
||||||
|
end
|
||||||
|
|
||||||
Group.reset_counters(group.id, :group_users)
|
Group.reset_counters(group.id, :group_users)
|
||||||
|
|
||||||
render json: success_json
|
render json: success_json
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_primary
|
||||||
|
group = Group.find_by(id: params.require(:id))
|
||||||
|
raise Discourse::NotFound unless group
|
||||||
|
|
||||||
|
users = User.where(username: group_params[:usernames].split(","))
|
||||||
|
users.each { |user| guardian.ensure_can_change_primary_group!(user) }
|
||||||
|
users.update_all(primary_group_id: params[:primary] == "true" ? group.id : nil)
|
||||||
|
|
||||||
|
render json: success_json
|
||||||
|
end
|
||||||
|
|
||||||
def automatic_membership_count
|
def automatic_membership_count
|
||||||
domains = Group.get_valid_email_domains(params.require(:automatic_membership_email_domains))
|
domains = Group.get_valid_email_domains(params.require(:automatic_membership_email_domains))
|
||||||
group_id = params[:id]
|
group_id = params[:id]
|
||||||
|
|
|
@ -785,7 +785,22 @@ en:
|
||||||
make_owner_description: "Make <b>%{username}</b> an owner of this group"
|
make_owner_description: "Make <b>%{username}</b> an owner of this group"
|
||||||
remove_owner: "Remove as Owner"
|
remove_owner: "Remove as Owner"
|
||||||
remove_owner_description: "Remove <b>%{username}</b> as an owner of this group"
|
remove_owner_description: "Remove <b>%{username}</b> as an owner of this group"
|
||||||
|
make_primary: "Make Primary"
|
||||||
|
make_primary_description: "Make this the primary group for <b>%{username}</b>"
|
||||||
|
remove_primary: "Remove as Primary"
|
||||||
|
remove_primary_description: "Remove this as the primary group for <b>%{username}</b>"
|
||||||
|
remove_members: "Remove Members"
|
||||||
|
remove_members_description: "Remove selected users from this group"
|
||||||
|
make_owners: "Make Owners"
|
||||||
|
make_owners_description: "Make selected users owners of this group"
|
||||||
|
remove_owners: "Remove Owners"
|
||||||
|
remove_owners_description: "Remove selected users as owners of this group"
|
||||||
|
make_all_primary: "Make All Primary"
|
||||||
|
make_all_primary_description: "Make this the primary group for all selected users"
|
||||||
|
remove_all_primary: "Remove as Primary"
|
||||||
|
remove_all_primary_description: "Remove this group as primary"
|
||||||
owner: "Owner"
|
owner: "Owner"
|
||||||
|
primary: "Primary"
|
||||||
forbidden: "You're not allowed to view the members."
|
forbidden: "You're not allowed to view the members."
|
||||||
topics: "Topics"
|
topics: "Topics"
|
||||||
posts: "Posts"
|
posts: "Posts"
|
||||||
|
|
|
@ -95,6 +95,7 @@ Discourse::Application.routes.draw do
|
||||||
member do
|
member do
|
||||||
put "owners" => "groups#add_owners"
|
put "owners" => "groups#add_owners"
|
||||||
delete "owners" => "groups#remove_owner"
|
delete "owners" => "groups#remove_owner"
|
||||||
|
put "primary" => "groups#set_primary"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
resources :groups, except: [:create], constraints: AdminConstraint.new do
|
resources :groups, except: [:create], constraints: AdminConstraint.new do
|
||||||
|
|
|
@ -157,6 +157,9 @@ RSpec.describe Admin::GroupsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#remove_owner' do
|
describe '#remove_owner' do
|
||||||
|
let(:user2) { Fabricate(:user) }
|
||||||
|
let(:user3) { Fabricate(:user) }
|
||||||
|
|
||||||
it 'should work' do
|
it 'should work' do
|
||||||
group.add_owner(user)
|
group.add_owner(user)
|
||||||
|
|
||||||
|
@ -168,6 +171,20 @@ RSpec.describe Admin::GroupsController do
|
||||||
expect(group.group_users.where(owner: true)).to eq([])
|
expect(group.group_users.where(owner: true)).to eq([])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'should work with multiple users' do
|
||||||
|
group.add_owner(user)
|
||||||
|
group.add_owner(user3)
|
||||||
|
|
||||||
|
delete "/admin/groups/#{group.id}/owners.json", params: {
|
||||||
|
group: {
|
||||||
|
usernames: "#{user.username},#{user2.username},#{user3.username}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(group.group_users.where(owner: true)).to eq([])
|
||||||
|
end
|
||||||
|
|
||||||
it 'returns not-found error when there is no group' do
|
it 'returns not-found error when there is no group' do
|
||||||
group.destroy!
|
group.destroy!
|
||||||
|
|
||||||
|
@ -190,6 +207,36 @@ RSpec.describe Admin::GroupsController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#set_primary" do
|
||||||
|
let(:user2) { Fabricate(:user) }
|
||||||
|
let(:user3) { Fabricate(:user) }
|
||||||
|
|
||||||
|
it 'sets with multiple users' do
|
||||||
|
user2.update!(primary_group_id: group.id)
|
||||||
|
|
||||||
|
put "/admin/groups/#{group.id}/primary.json", params: {
|
||||||
|
group: { usernames: "#{user.username},#{user2.username},#{user3.username}" },
|
||||||
|
primary: "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(User.where(primary_group_id: group.id).size).to eq(3)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'unsets with multiple users' do
|
||||||
|
user.update!(primary_group_id: group.id)
|
||||||
|
user3.update!(primary_group_id: group.id)
|
||||||
|
|
||||||
|
put "/admin/groups/#{group.id}/primary.json", params: {
|
||||||
|
group: { usernames: "#{user.username},#{user2.username},#{user3.username}" },
|
||||||
|
primary: "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(User.where(primary_group_id: group.id).size).to eq(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "#bulk_perform" do
|
describe "#bulk_perform" do
|
||||||
fab!(:group) do
|
fab!(:group) do
|
||||||
Fabricate(:group,
|
Fabricate(:group,
|
||||||
|
|
Loading…
Reference in New Issue