discourse/app/assets/javascripts/admin/addon/controllers/admin-user/badges.js
Sérgio Saquetim 27bc2b1549
DEV: Replace deprecated Ember's native array.get and .set (#35000)
This commit introduces several updates across multiple files. The
main focus is improving array removal functionality, replacing outdated
patterns, and fixing Ember tracked properties for cleaner and modernized
code. It also includes updates to the deprecation workflow and newly
added unit tests for array removal.

**Main Changes:**

* Introduction of removeValueFromArray utility:
* Added array-tools.js, which provides a utility function to remove
elements from arrays (including tracked arrays).
* Added unit tests to validate removeValueFromArray behavior for various
scenarios.
* Refactor tracked property usage:
* Updated various properties (allLoaded, expandedBadges, etc.) to use
Ember's tracked primitives for better reactivity management.
* Refactoring of deprecated Ember patterns:
* Replaced the usage of .get() and .set() with modern JavaScript object
handling across files.
    * Removed redundant or outdated computed property decorators.
* Enhancements in badge-related functionality:
* Grouped badges handling is improved, and reactive array updates are
introduced.
* Added a proper initialization of expandedBadges and cleaned elements
using removeObject.
* Deprecation workflow adjustments:
* Added logging for additional deprecated native array extension
methods.
* Test updates:
* New unit tests verify the correctness of added array utility
functionalities (array-tools-test.js).

---------

Co-authored-by: Jarek Radosz <jradosz@gmail.com>
2025-10-08 16:21:14 -03:00

123 lines
3.4 KiB
JavaScript
Vendored

import Controller, { inject as controller } from "@ember/controller";
import { action } from "@ember/object";
import { alias, empty, sort } from "@ember/object/computed";
import { next } from "@ember/runloop";
import { service } from "@ember/service";
import { compare } from "@ember/utils";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { removeValueFromArray } from "discourse/lib/array-tools";
import discourseComputed from "discourse/lib/decorators";
import { grantableBadges } from "discourse/lib/grant-badge-utils";
import { trackedArray } from "discourse/lib/tracked-tools";
import UserBadge from "discourse/models/user-badge";
import { i18n } from "discourse-i18n";
import AdminUser from "admin/models/admin-user";
export default class AdminUserBadgesController extends Controller {
@service dialog;
@controller adminUser;
@trackedArray expandedBadges = [];
@trackedArray model;
@alias("adminUser.model") user;
@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"));
}
get groupedBadges() {
const allBadges = this.model;
let grouped = {};
allBadges.forEach((b) => {
grouped[b.badge_id] = grouped[b.badge_id] || [];
grouped[b.badge_id].push(b);
});
let expanded = [];
Object.values(grouped).forEach((badges) => {
let lastGranted = badges[0].granted_at;
badges.forEach((badge) => {
lastGranted =
lastGranted < badge.granted_at ? badge.granted_at : lastGranted;
});
if (
badges.length === 1 ||
this.expandedBadges.includes(badges[0].badge.id)
) {
badges.forEach((badge) => expanded.push(badge));
return;
}
let result = {
badge: badges[0].badge,
granted_at: lastGranted,
badges,
count: badges.length,
grouped: true,
};
expanded.push(result);
});
expanded.forEach((badgeGroup) => {
const user = badgeGroup.granted_by;
if (user) {
badgeGroup.granted_by = AdminUser.create(user);
}
});
return expanded.sort((a, b) => compare(b?.granted_at, a?.granted_at)); // sort descending
}
@action
expandGroup(userBadge) {
this.expandedBadges.push(userBadge.badge.id);
}
@action
performGrantBadge() {
UserBadge.grant(
this.selectedBadgeId,
this.get("user.username"),
this.badgeReason
).then(
(newBadge) => {
this.set("badgeReason", "");
this.userBadges.pushObject(newBadge);
next(() => {
// Update the selected badge ID after the combobox has re-rendered.
const newSelectedBadge = this.availableBadges[0];
if (newSelectedBadge) {
this.set("selectedBadgeId", newSelectedBadge.get("id"));
}
});
},
function (error) {
popupAjaxError(error);
}
);
}
@action
revokeBadge(userBadge) {
return this.dialog.yesNoConfirm({
message: i18n("admin.badges.revoke_confirm"),
didConfirm: () => {
return userBadge.revoke().then(() => {
removeValueFromArray(this.model, userBadge);
});
},
});
}
}