DEV: Replace deprecated Ember's array uniq and uniqBy (#35227)

This commit replaces several instances of `.uniq`, `.uniqBy`, and related
array deduplication methods with a new utility function
`uniqueItemsFromArray`. This change ensures proper deduplication logic
across the codebase while addressing the deprecation issues.

**Main Changes:**

* Created new utility function `uniqueItemsFromArray`: Located in a new
file, `array-tools.js`, this function provides a reusable and
configurable alternative for deduplication.
* Replaced old methods: Updated multiple files to use the new utility
function instead of deprecated or custom implementations for
deduplication.
* Replaced manual deduplication: Replace uses of `[...new Set(array)]`
to standardize the deduplication logic across the codebase.
* Added unit tests: Introduced thorough test coverage
(`array-tools-test.js`) to validate functionality and edge cases for the
utility function.
* Updated deprecation workflow: Added logging for deprecated uniq and
uniqBy methods to the `deprecation-workflow.js`.

This change is primarily focused on code quality improvements, ensuring
future-proof deduplication, and maintaining alignment with deprecation
guidelines.
This commit is contained in:
Sérgio Saquetim
2025-10-13 16:47:36 -03:00
committed by GitHub
parent a225da4fd3
commit 0c4f285618
40 changed files with 457 additions and 111 deletions
@@ -1,5 +1,6 @@
import { cached, tracked } from "@glimmer/tracking";
import { setOwner } from "@ember/owner";
import { uniqueItemsFromArray } from "discourse/lib/array-tools";
export default class ChatMessagesManager {
@tracked messages = [];
@@ -27,10 +28,10 @@ export default class ChatMessagesManager {
}
addMessages(messages = []) {
this.messages = this.messages
.concat(messages)
.uniqBy("id")
.sort((a, b) => a.createdAt - b.createdAt);
this.messages = uniqueItemsFromArray(
this.messages.concat(messages),
"id"
).sort((a, b) => a.createdAt - b.createdAt);
}
findMessage(messageId) {
@@ -5,6 +5,7 @@ import { cancel, next } from "@ember/runloop";
import Service, { service } from "@ember/service";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { uniqueItemsFromArray } from "discourse/lib/array-tools";
import { bind } from "discourse/lib/decorators";
import deprecated from "discourse/lib/deprecated";
import discourseLater from "discourse/lib/later";
@@ -392,10 +393,11 @@ export default class Chat extends Service {
}
upsertDmChannelForUser(channel, user) {
const usernames = (channel.chatable.users || [])
.map((item) => item.username)
.concat(user.username)
.uniq();
const usernames = uniqueItemsFromArray(
(channel.chatable.users || [])
.map((item) => item.username)
.concat(user.username)
);
return this.upsertDmChannel({ usernames });
}
@@ -411,8 +413,12 @@ export default class Chat extends Service {
return ajax("/chat/api/direct-message-channels.json", {
method: "POST",
data: {
target_usernames: targets.usernames?.uniq(),
target_groups: targets.groups?.uniq(),
target_usernames: targets.usernames
? uniqueItemsFromArray(targets.usernames)
: null,
target_groups: targets.groups
? uniqueItemsFromArray(targets.groups)
: null,
upsert: opts.upsert,
name: opts.name,
},
@@ -434,7 +440,7 @@ export default class Chat extends Service {
// participant to fetch the channel for.
getDmChannelForUsernames(usernames) {
return ajax("/chat/direct_messages.json", {
data: { usernames: usernames.uniq().join(",") },
data: { usernames: uniqueItemsFromArray(usernames).join(",") },
});
}
@@ -9,6 +9,7 @@ import DButton from "discourse/components/d-button";
import categoryBadge from "discourse/helpers/category-badge";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { uniqueItemsFromArray } from "discourse/lib/array-tools";
import DMenu from "float-kit/components/d-menu";
export default class AiSplitTopicSuggester extends Component {
@@ -88,10 +89,10 @@ export default class AiSplitTopicSuggester extends Component {
if (this.args.currentValue) {
if (Array.isArray(this.args.currentValue)) {
const updatedTags = [...this.args.currentValue, suggestion];
this.args.updateAction([...new Set(updatedTags)]);
this.args.updateAction(uniqueItemsFromArray(updatedTags));
} else {
const updatedTags = [this.args.currentValue, suggestion];
this.args.updateAction([...new Set(updatedTags)]);
this.args.updateAction(uniqueItemsFromArray(updatedTags));
}
} else {
if (Array.isArray(suggestion)) {
@@ -1,3 +1,4 @@
import { uniqueItemsFromArray } from "discourse/lib/array-tools";
import { renderIcon } from "discourse/lib/icon-library";
import { i18n } from "discourse-i18n";
import DateWithZoneHelper from "./date-with-zone-helper";
@@ -141,7 +142,7 @@ export default class LocalDateBuilder {
});
});
return previewedTimezones.uniqBy("timezone");
return uniqueItemsFromArray(previewedTimezones, "timezone");
}
_isEqualZones(timezoneA, timezoneB) {
@@ -6,6 +6,7 @@ import { service } from "@ember/service";
import { TrackedObject } from "@ember-compat/tracked-built-ins";
import { and } from "truth-helpers";
import DButton from "discourse/components/d-button";
import { uniqueItemsFromArray } from "discourse/lib/array-tools";
import { bind } from "discourse/lib/decorators";
import closeOnClickOutside from "discourse/modifiers/close-on-click-outside";
import CustomReaction from "../models/discourse-reactions-custom-reaction";
@@ -26,7 +27,7 @@ export default class DiscourseReactionsCounter extends Component {
}
reactionsChanged(data) {
data.reactions.uniq().forEach((reaction) => {
uniqueItemsFromArray(data.reactions).forEach((reaction) => {
this.getUsers(reaction);
});
}
@@ -2,6 +2,7 @@
import Component from "@ember/component";
import { classNames, tagName } from "@ember-decorators/component";
import icon from "discourse/helpers/d-icon";
import { uniqueItemsFromArray } from "discourse/lib/array-tools";
import { afterRender } from "discourse/lib/decorators";
import { REPLACEMENTS } from "discourse/lib/icon-library";
import discourseLater from "discourse/lib/later";
@@ -22,7 +23,7 @@ export default class StyleguideIcons extends Component {
if (symbols.length > 0) {
let ids = Array.from(symbols).map((item) => item.id);
ids.push(...Object.keys(REPLACEMENTS));
this.set("iconIds", [...new Set(ids.sort())]);
this.set("iconIds", uniqueItemsFromArray(ids).sort());
} else {
// Let's try again a short time later if there are no svgs loaded yet
discourseLater(this, this.setIconIds, 1500);