mirror of
https://github.com/discourse/discourse.git
synced 2025-04-25 02:44:30 +08:00
DEV: upgrade grant badge modal to glimmer (#23526)
* DEV: upgrade grant badge modal to glimmer * DEV: add unit tests for grant badge utils * DEV: replace grant-badge-controller mixin with grant-badge-utils in admin-user-badges controller * DEV: remove GrantBadgeController mixin
This commit is contained in:
parent
7d4c47195a
commit
a4238a3726
@ -1,16 +1,15 @@
|
||||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { alias, sort } from "@ember/object/computed";
|
||||
import { alias, empty, sort } from "@ember/object/computed";
|
||||
import Controller, { inject as controller } from "@ember/controller";
|
||||
import GrantBadgeController from "discourse/mixins/grant-badge-controller";
|
||||
import UserBadge from "discourse/models/user-badge";
|
||||
import { grantableBadges } from "discourse/lib/grant-badge-utils";
|
||||
import I18n from "I18n";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { next } from "@ember/runloop";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
|
||||
export default class AdminUserBadgesController extends Controller.extend(
|
||||
GrantBadgeController
|
||||
) {
|
||||
export default class AdminUserBadgesController extends Controller {
|
||||
@service dialog;
|
||||
@controller adminUser;
|
||||
|
||||
@ -18,9 +17,14 @@ export default class AdminUserBadgesController extends Controller.extend(
|
||||
@alias("model") userBadges;
|
||||
@alias("badges") allBadges;
|
||||
@sort("model", "badgeSortOrder") sortedBadges;
|
||||
@empty("availableBadges") noAvailableBadges;
|
||||
|
||||
badgeSortOrder = ["granted_at:desc"];
|
||||
|
||||
@discourseComputed("allBadges.[]", "userBadges.[]")
|
||||
availableBadges() {
|
||||
return grantableBadges(this.get("allBadges"), this.get("userBadges"));
|
||||
}
|
||||
@discourseComputed("model", "model.[]", "model.expandedBadges.[]")
|
||||
groupedBadges() {
|
||||
const allBadges = this.model;
|
||||
@ -60,7 +64,6 @@ export default class AdminUserBadgesController extends Controller.extend(
|
||||
|
||||
return expanded.sortBy("granted_at").reverse();
|
||||
}
|
||||
|
||||
@action
|
||||
expandGroup(userBadge) {
|
||||
const model = this.model;
|
||||
@ -70,16 +73,12 @@ export default class AdminUserBadgesController extends Controller.extend(
|
||||
|
||||
@action
|
||||
performGrantBadge() {
|
||||
this.grantBadge(
|
||||
this.selectedBadgeId,
|
||||
this.get("user.username"),
|
||||
this.badgeReason
|
||||
).then(
|
||||
() => {
|
||||
this.set("badgeReason", "");
|
||||
UserBadge.grant(this.selectedBadgeId, this.get("user.username")).then(
|
||||
(newBadge) => {
|
||||
this.userBadges.pushObject(newBadge);
|
||||
next(() => {
|
||||
// Update the selected badge ID after the combobox has re-rendered.
|
||||
const newSelectedBadge = this.grantableBadges[0];
|
||||
const newSelectedBadge = this.availableBadges[0];
|
||||
if (newSelectedBadge) {
|
||||
this.set("selectedBadgeId", newSelectedBadge.get("id"));
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ export default class AdminUserBadgesRoute extends DiscourseRoute {
|
||||
Badge.findAll().then(function (badges) {
|
||||
controller.set("badges", badges);
|
||||
if (badges.length > 0) {
|
||||
let grantableBadges = controller.get("grantableBadges");
|
||||
let grantableBadges = controller.get("availableBadges");
|
||||
if (grantableBadges.length > 0) {
|
||||
controller.set("selectedBadgeId", grantableBadges[0].get("id"));
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
<div class="admin-container user-badges">
|
||||
<h2>{{i18n "admin.badges.grant_badge"}}</h2>
|
||||
<br />
|
||||
{{#if this.noGrantableBadges}}
|
||||
{{#if this.noAvailableBadges}}
|
||||
<p>{{i18n "admin.badges.no_badges"}}</p>
|
||||
{{else}}
|
||||
<form class="form-horizontal">
|
||||
@ -21,7 +21,7 @@
|
||||
<label>{{i18n "admin.badges.badge"}}</label>
|
||||
<ComboBox
|
||||
@value={{this.selectedBadgeId}}
|
||||
@content={{this.grantableBadges}}
|
||||
@content={{this.availableBadges}}
|
||||
@onChange={{action (mut this.selectedBadgeId)}}
|
||||
@options={{hash filterable=true}}
|
||||
/>
|
||||
|
@ -9,13 +9,13 @@
|
||||
>
|
||||
<:body>
|
||||
<ConditionalLoadingSpinner @condition={{this.loading}}>
|
||||
{{#if this.noGrantableBadges}}
|
||||
{{#if this.noAvailableBadges}}
|
||||
<p>{{i18n "admin.badges.no_badges"}}</p>
|
||||
{{else}}
|
||||
<p>
|
||||
<ComboBox
|
||||
@value={{this.selectedBadgeId}}
|
||||
@content={{this.grantableBadges}}
|
||||
@content={{this.availableBadges}}
|
||||
@onChange={{action (mut this.selectedBadgeId)}}
|
||||
@options={{hash filterable=true none="badges.none"}}
|
||||
/>
|
||||
|
@ -1,80 +1,82 @@
|
||||
import { action } from "@ember/object";
|
||||
import Component from "@ember/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import Component from "@glimmer/component";
|
||||
import Badge from "discourse/models/badge";
|
||||
import GrantBadgeController from "discourse/mixins/grant-badge-controller";
|
||||
import I18n from "I18n";
|
||||
import UserBadge from "discourse/models/user-badge";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { extractError } from "discourse/lib/ajax-error";
|
||||
import getURL from "discourse-common/lib/get-url";
|
||||
import {
|
||||
grantableBadges,
|
||||
isBadgeGrantable,
|
||||
} from "discourse/lib/grant-badge-utils";
|
||||
|
||||
export default class GrantBadgeModal extends Component.extend(
|
||||
GrantBadgeController
|
||||
) {
|
||||
loading = true;
|
||||
saving = false;
|
||||
selectedBadgeId = null;
|
||||
flash = null;
|
||||
flashType = null;
|
||||
allBadges = [];
|
||||
userBadges = [];
|
||||
export default class GrantBadgeModal extends Component {
|
||||
@tracked loading = true;
|
||||
@tracked saving = false;
|
||||
@tracked selectedBadgeId = null;
|
||||
@tracked flash = null;
|
||||
@tracked flashType = null;
|
||||
@tracked allBadges = [];
|
||||
@tracked userBadges = [];
|
||||
@tracked availableBadges = [];
|
||||
|
||||
@discourseComputed("model.selectedPost")
|
||||
post() {
|
||||
return this.get("model.selectedPost");
|
||||
get noAvailableBadges() {
|
||||
!this.availableBadges.length;
|
||||
}
|
||||
|
||||
@discourseComputed("saving", "selectedBadgeGrantable")
|
||||
buttonDisabled(saving, selectedBadgeGrantable) {
|
||||
return saving || !selectedBadgeGrantable;
|
||||
get post() {
|
||||
return this.args.model.selectedPost;
|
||||
}
|
||||
|
||||
get buttonDisabled() {
|
||||
return (
|
||||
this.saving ||
|
||||
!isBadgeGrantable(this.selectedBadgeId, this.availableBadges)
|
||||
);
|
||||
}
|
||||
|
||||
#updateAvailableBadges() {
|
||||
this.availableBadges = grantableBadges(this.allBadges, this.userBadges);
|
||||
}
|
||||
|
||||
@action
|
||||
async loadBadges() {
|
||||
this.set("loading", true);
|
||||
this.loading = true;
|
||||
try {
|
||||
const allBadges = await Badge.findAll();
|
||||
const userBadges = await UserBadge.findByUsername(
|
||||
this.get("post.username")
|
||||
);
|
||||
this.setProperties({
|
||||
allBadges,
|
||||
userBadges,
|
||||
});
|
||||
this.allBadges = await Badge.findAll();
|
||||
this.userBadges = await UserBadge.findByUsername(this.post.username);
|
||||
this.#updateAvailableBadges();
|
||||
} catch (e) {
|
||||
this.setProperties({
|
||||
flash: extractError(e),
|
||||
flashType: "error",
|
||||
});
|
||||
this.flash = extractError(e);
|
||||
this.flashType = "error";
|
||||
} finally {
|
||||
this.set("loading", false);
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
@action
|
||||
async performGrantBadge() {
|
||||
try {
|
||||
this.set("saving", true);
|
||||
const username = this.get("post.username");
|
||||
const newBadge = await this.grantBadge(
|
||||
this.saving = true;
|
||||
const username = this.post.username;
|
||||
const newBadge = await UserBadge.grant(
|
||||
this.selectedBadgeId,
|
||||
username,
|
||||
getURL(this.get("post.url"))
|
||||
getURL(this.post.url)
|
||||
);
|
||||
this.set("selectedBadgeId", null);
|
||||
this.setProperties({
|
||||
flash: I18n.t("badges.successfully_granted", {
|
||||
username,
|
||||
badge: newBadge.get("badge.name"),
|
||||
}),
|
||||
flashType: "success",
|
||||
this.userBadges.pushObject(newBadge);
|
||||
this.#updateAvailableBadges();
|
||||
this.selectedBadgeId = null;
|
||||
this.flash = I18n.t("badges.successfully_granted", {
|
||||
username,
|
||||
badge: newBadge.get("badge.name"),
|
||||
});
|
||||
this.flashType = "success";
|
||||
} catch (e) {
|
||||
this.setProperties({
|
||||
flash: extractError(e),
|
||||
flashType: "error",
|
||||
});
|
||||
this.flash = extractError(e);
|
||||
this.flashType = "error";
|
||||
} finally {
|
||||
this.set("saving", false);
|
||||
this.saving = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
import { convertIconClass } from "discourse-common/lib/icon-library";
|
||||
|
||||
export function grantableBadges(allBadges, userBadges) {
|
||||
const granted = userBadges.reduce((map, badge) => {
|
||||
map[badge.get("badge_id")] = true;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
return allBadges
|
||||
.filter((badge) => {
|
||||
return (
|
||||
badge.get("enabled") &&
|
||||
badge.get("manually_grantable") &&
|
||||
(!granted[badge.get("id")] || badge.get("multiple_grant"))
|
||||
);
|
||||
})
|
||||
.map((badge) => {
|
||||
if (badge.get("icon")) {
|
||||
badge.set("icon", convertIconClass(badge.icon));
|
||||
}
|
||||
return badge;
|
||||
})
|
||||
.sort((a, b) => a.get("name").localeCompare(b.get("name")));
|
||||
}
|
||||
|
||||
export function isBadgeGrantable(badgeId, availableBadges) {
|
||||
return (
|
||||
availableBadges && availableBadges.some((b) => b.get("id") === badgeId)
|
||||
);
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
import Mixin from "@ember/object/mixin";
|
||||
import UserBadge from "discourse/models/user-badge";
|
||||
import { convertIconClass } from "discourse-common/lib/icon-library";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { empty } from "@ember/object/computed";
|
||||
|
||||
export default Mixin.create({
|
||||
@discourseComputed("allBadges.[]", "userBadges.[]")
|
||||
grantableBadges(allBadges, userBadges) {
|
||||
const granted = userBadges.reduce((map, badge) => {
|
||||
map[badge.get("badge_id")] = true;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
return allBadges
|
||||
.filter((badge) => {
|
||||
return (
|
||||
badge.get("enabled") &&
|
||||
badge.get("manually_grantable") &&
|
||||
(!granted[badge.get("id")] || badge.get("multiple_grant"))
|
||||
);
|
||||
})
|
||||
.map((badge) => {
|
||||
if (badge.get("icon")) {
|
||||
badge.set("icon", convertIconClass(badge.icon));
|
||||
}
|
||||
return badge;
|
||||
})
|
||||
.sort((a, b) => a.get("name").localeCompare(b.get("name")));
|
||||
},
|
||||
|
||||
noGrantableBadges: empty("grantableBadges"),
|
||||
|
||||
@discourseComputed("selectedBadgeId", "grantableBadges")
|
||||
selectedBadgeGrantable(selectedBadgeId, grantableBadges) {
|
||||
return (
|
||||
grantableBadges &&
|
||||
grantableBadges.find((badge) => badge.get("id") === selectedBadgeId)
|
||||
);
|
||||
},
|
||||
|
||||
grantBadge(selectedBadgeId, username, badgeReason) {
|
||||
return UserBadge.grant(selectedBadgeId, username, badgeReason).then(
|
||||
(newBadge) => {
|
||||
this.userBadges.pushObject(newBadge);
|
||||
return newBadge;
|
||||
},
|
||||
(error) => {
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
@ -5,7 +5,7 @@ import Badge from "discourse/models/badge";
|
||||
module("Unit | Controller | admin-user-badges", function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test("grantableBadges", function (assert) {
|
||||
test("availableBadges", function (assert) {
|
||||
const badgeFirst = Badge.create({
|
||||
id: 3,
|
||||
name: "A Badge",
|
||||
@ -50,7 +50,7 @@ module("Unit | Controller | admin-user-badges", function (hooks) {
|
||||
});
|
||||
|
||||
const sortedNames = [badgeFirst.name, badgeMiddle.name, badgeLast.name];
|
||||
const badgeNames = controller.grantableBadges.map((badge) => badge.name);
|
||||
const badgeNames = controller.availableBadges.map((badge) => badge.name);
|
||||
|
||||
assert.notOk(
|
||||
badgeNames.includes(badgeDisabled),
|
||||
|
@ -0,0 +1,96 @@
|
||||
import { module, test } from "qunit";
|
||||
import Badge from "discourse/models/badge";
|
||||
import {
|
||||
grantableBadges,
|
||||
isBadgeGrantable,
|
||||
} from "discourse/lib/grant-badge-utils";
|
||||
module("Unit | Utility | Grant Badge", function (hooks) {
|
||||
hooks.beforeEach(() => {
|
||||
const firstBadge = Badge.create({
|
||||
id: 3,
|
||||
name: "A Badge",
|
||||
enabled: true,
|
||||
manually_grantable: true,
|
||||
});
|
||||
const middleBadge = Badge.create({
|
||||
id: 1,
|
||||
name: "My Badge",
|
||||
enabled: true,
|
||||
manually_grantable: true,
|
||||
});
|
||||
const lastBadge = Badge.create({
|
||||
id: 2,
|
||||
name: "Zoo Badge",
|
||||
enabled: true,
|
||||
manually_grantable: true,
|
||||
multiple_grant: true,
|
||||
});
|
||||
const grantedBadge = Badge.create({
|
||||
id: 6,
|
||||
name: "Grant Badge",
|
||||
enabled: true,
|
||||
manually_grantable: true,
|
||||
multiple_grant: false,
|
||||
});
|
||||
const disabledBadge = Badge.create({
|
||||
id: 4,
|
||||
name: "Disabled Badge",
|
||||
enabled: false,
|
||||
manually_grantable: true,
|
||||
});
|
||||
const automaticBadge = Badge.create({
|
||||
id: 5,
|
||||
name: "Automatic Badge",
|
||||
enabled: true,
|
||||
manually_grantable: false,
|
||||
});
|
||||
|
||||
const allBadges = [
|
||||
lastBadge,
|
||||
firstBadge,
|
||||
middleBadge,
|
||||
grantedBadge,
|
||||
disabledBadge,
|
||||
automaticBadge,
|
||||
];
|
||||
const userBadges = [lastBadge, grantedBadge];
|
||||
|
||||
test("grantableBadges", function (assert) {
|
||||
const sortedNames = [firstBadge.name, middleBadge.name, lastBadge.name];
|
||||
|
||||
const result = grantableBadges(allBadges, userBadges);
|
||||
const badgeNames = result.map((b) => b.name);
|
||||
|
||||
assert.deepEqual(badgeNames, sortedNames, "sorts badges by name");
|
||||
assert.notOk(
|
||||
badgeNames.includes(grantedBadge.name),
|
||||
"excludes already granted badges"
|
||||
);
|
||||
assert.notOk(
|
||||
badgeNames.includes(disabledBadge.name),
|
||||
"excludes disabled badges"
|
||||
);
|
||||
assert.notOk(
|
||||
badgeNames.includes(automaticBadge.name),
|
||||
"excludes automatic badges"
|
||||
);
|
||||
assert.ok(
|
||||
badgeNames.includes(lastBadge.name),
|
||||
"includes granted badges that can be granted multiple times"
|
||||
);
|
||||
});
|
||||
|
||||
test("isBadgeGrantable", function (assert) {
|
||||
const badges = [firstBadge, lastBadge];
|
||||
assert.ok(isBadgeGrantable(firstBadge.id, badges));
|
||||
assert.notOk(
|
||||
isBadgeGrantable(disabledBadge.id, badges),
|
||||
"returns false when badgeId is not that of any badge in availableBadges"
|
||||
);
|
||||
assert.notOk(
|
||||
isBadgeGrantable(firstBadge.id),
|
||||
"returns false if no availableBadges is defined"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,81 +0,0 @@
|
||||
import { module, test } from "qunit";
|
||||
import Badge from "discourse/models/badge";
|
||||
import Controller from "@ember/controller";
|
||||
import GrantBadgeControllerMixin from "discourse/mixins/grant-badge-controller";
|
||||
|
||||
module("Unit | Mixin | grant-badge-controller", function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
this.GrantBadgeController = Controller.extend(GrantBadgeControllerMixin);
|
||||
|
||||
this.badgeFirst = Badge.create({
|
||||
id: 3,
|
||||
name: "A Badge",
|
||||
enabled: true,
|
||||
manually_grantable: true,
|
||||
});
|
||||
this.badgeMiddle = Badge.create({
|
||||
id: 1,
|
||||
name: "My Badge",
|
||||
enabled: true,
|
||||
manually_grantable: true,
|
||||
});
|
||||
this.badgeLast = Badge.create({
|
||||
id: 2,
|
||||
name: "Zoo Badge",
|
||||
enabled: true,
|
||||
manually_grantable: true,
|
||||
});
|
||||
this.badgeDisabled = Badge.create({
|
||||
id: 4,
|
||||
name: "Disabled Badge",
|
||||
enabled: false,
|
||||
manually_grantable: true,
|
||||
});
|
||||
this.badgeAutomatic = Badge.create({
|
||||
id: 5,
|
||||
name: "Automatic Badge",
|
||||
enabled: true,
|
||||
manually_grantable: false,
|
||||
});
|
||||
|
||||
this.subject = this.GrantBadgeController.create({
|
||||
userBadges: [],
|
||||
allBadges: [
|
||||
this.badgeLast,
|
||||
this.badgeFirst,
|
||||
this.badgeMiddle,
|
||||
this.badgeDisabled,
|
||||
this.badgeAutomatic,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("grantableBadges", function (assert) {
|
||||
const sortedNames = [
|
||||
this.badgeFirst.name,
|
||||
this.badgeMiddle.name,
|
||||
this.badgeLast.name,
|
||||
];
|
||||
const badgeNames = this.subject
|
||||
.get("grantableBadges")
|
||||
.map((badge) => badge.name);
|
||||
|
||||
assert.notOk(
|
||||
badgeNames.includes(this.badgeDisabled),
|
||||
"excludes disabled badges"
|
||||
);
|
||||
assert.notOk(
|
||||
badgeNames.includes(this.badgeAutomatic),
|
||||
"excludes automatic badges"
|
||||
);
|
||||
assert.deepEqual(badgeNames, sortedNames, "sorts badges by name");
|
||||
});
|
||||
|
||||
test("selectedBadgeGrantable", function (assert) {
|
||||
this.subject.set("selectedBadgeId", this.badgeDisabled.id);
|
||||
assert.notOk(this.subject.get("selectedBadgeGrantable"));
|
||||
|
||||
this.subject.set("selectedBadgeId", this.badgeFirst.id);
|
||||
assert.ok(this.subject.get("selectedBadgeGrantable"));
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user