fix(mod queue): dedupe board filters

This commit is contained in:
Tommaso Casaburi
2026-05-23 15:36:39 +07:00
parent cf96d3b1ae
commit d13f2240e3
4 changed files with 277 additions and 63 deletions
@@ -1,13 +1,22 @@
import { describe, expect, it } from 'vitest';
import {
filterVisibleModQueueFeed,
getModQueueBoardFilterGroups,
getModQueueCommentRoute,
getModQueueSelectedBoardAddresses,
getModeratedCommunityAddresses,
getQueuedCommentRouteState,
getVisibleQueuedCommentHistory,
shouldKeepQueuedCommentHistory,
} from '../mod-queue-utils';
describe('mod queue utils', () => {
const directories = [
{ address: 'anime-primary.bso', publicKey: 'a-primary', directoryCode: 'a', title: '/a/ - Anime & Manga' },
{ address: 'anime-backup.bso', publicKey: 'a-backup', directoryCode: 'a', title: '/a/ - Anime & Manga' },
{ address: 'tech-primary.bso', publicKey: 'g-primary', directoryCode: 'g', title: '/g/ - Technology' },
];
it('keeps all queue comments unless they were locally dismissed', () => {
const feed = [
{ cid: 'pending', communityAddress: 'tech.eth', pendingApproval: true },
@@ -37,6 +46,57 @@ describe('mod queue utils', () => {
]);
});
it('applies grouped board filters to all directory aliases', () => {
const feed = [
{ cid: 'a-primary-pending', communityAddress: 'a-primary', pendingApproval: true },
{ cid: 'a-backup-pending', communityAddress: 'a-backup', pendingApproval: true },
{ cid: 'g-pending', communityAddress: 'g-primary', pendingApproval: true },
];
expect(filterVisibleModQueueFeed(feed, 'a', new Set(['a-primary-pending']), ['a-primary', 'a-backup'])).toEqual([
{ cid: 'a-backup-pending', communityAddress: 'a-backup', pendingApproval: true },
]);
});
it('dedupes mod queue board filters by directory path', () => {
expect(getModQueueBoardFilterGroups(['a-backup', 'custom.eth', 'g-primary', 'a-primary'], directories, [['g', 'a']])).toEqual([
{
addresses: ['g-primary'],
boardPath: 'g',
filterKey: 'g',
isDirectory: true,
},
{
addresses: ['a-backup', 'a-primary'],
boardPath: 'a',
filterKey: 'a',
isDirectory: true,
},
{
addresses: ['custom.eth'],
boardPath: 'custom.eth',
filterKey: 'custom.eth',
isDirectory: false,
},
]);
});
it('expands selected directory filters to every matching moderated address', () => {
expect(getModQueueSelectedBoardAddresses(['a-primary', 'g-primary', 'a-backup'], 'a', directories)).toEqual(['a-primary', 'a-backup']);
expect(getModQueueSelectedBoardAddresses(['a-primary', 'g-primary', 'a-backup'], 'a-primary', directories)).toEqual(['a-primary', 'a-backup']);
});
it('adds live role communities that are not cached on the account', () => {
expect(
getModeratedCommunityAddresses({
accountAddress: 'plebeius.bso',
accountCommunityAddresses: ['tech-primary.bso'],
candidateCommunityAddresses: ['tech-primary.bso', 'paranormal-posting.bso', 'viewer-board.bso'],
communities: [undefined, { roles: { 'plebeius.bso': { role: 'moderator' } } }, { roles: { 'plebeius.bso': { role: 'viewer' } } }],
}),
).toEqual(['tech-primary.bso', 'paranormal-posting.bso']);
});
it('keeps only terminal local moderation states in queue history', () => {
expect(shouldKeepQueuedCommentHistory({ cid: 'pending', pendingApproval: true })).toBe(false);
expect(shouldKeepQueuedCommentHistory({ cid: 'published', pendingApproval: false })).toBe(false);
+135 -2
View File
@@ -1,7 +1,9 @@
import type { Comment } from '@bitsocial/bitsocial-react-hooks';
import type { DirectoryCommunity } from '../../hooks/use-directories';
import { getCommentCommunityAddress } from './comment-utils';
import { hasModQueueAccessRole } from './mod-access';
import { isPendingApprovalRejected } from './pending-approval-moderation';
import { areSameBoardAddress } from './route-utils';
import { areSameBoardAddress, getBoardPath } from './route-utils';
import { getThreadTopNavigationState } from './thread-scroll-utils';
type ModQueueCommentLike = {
@@ -59,9 +61,130 @@ export type QueuedCommentSnapshot = {
title?: Comment['title'];
};
export interface ModQueueBoardFilterGroup {
addresses: string[];
boardPath: string;
filterKey: string;
isDirectory: boolean;
}
interface ModQueueCommunityRoleSource {
roles?: Record<string, { role?: string } | undefined>;
}
export const getModQueueCommentRoute = (boardPath: string | undefined, commentCid: string | undefined): string | undefined =>
boardPath && commentCid ? `/${boardPath}/thread/${commentCid}` : undefined;
export const getModQueueBoardFilterKey = (communityAddress: string, directories: DirectoryCommunity[]): string => {
const boardPath = getBoardPath(communityAddress, directories);
return boardPath !== communityAddress ? boardPath : communityAddress;
};
const addUniqueBoardAddress = (addresses: string[], address: string) => {
if (!addresses.some((existingAddress) => areSameBoardAddress(existingAddress, address))) {
addresses.push(address);
}
};
export const getModQueueBoardFilterGroups = (
accountCommunityAddresses: readonly string[],
directories: DirectoryCommunity[],
boardCodeGroups: readonly (readonly string[])[],
): ModQueueBoardFilterGroup[] => {
const groupsByFilterKey = new Map<string, ModQueueBoardFilterGroup>();
const filterKeyByAddress = new Map<string, string>();
for (const address of accountCommunityAddresses) {
const boardPath = getBoardPath(address, directories);
const filterKey = boardPath !== address ? boardPath : address;
const existingGroup = groupsByFilterKey.get(filterKey);
if (existingGroup) {
addUniqueBoardAddress(existingGroup.addresses, address);
} else {
groupsByFilterKey.set(filterKey, {
addresses: [address],
boardPath,
filterKey,
isDirectory: boardPath !== address,
});
}
filterKeyByAddress.set(address, filterKey);
}
const orderedGroups: ModQueueBoardFilterGroup[] = [];
const seenFilterKeys = new Set<string>();
const addGroup = (filterKey: string | undefined) => {
if (!filterKey || seenFilterKeys.has(filterKey)) {
return;
}
const group = groupsByFilterKey.get(filterKey);
if (!group) {
return;
}
orderedGroups.push(group);
seenFilterKeys.add(filterKey);
};
for (const group of boardCodeGroups) {
for (const code of group) {
addGroup(code);
}
}
for (const address of accountCommunityAddresses) {
addGroup(filterKeyByAddress.get(address));
}
return orderedGroups;
};
export const getModQueueSelectedBoardAddresses = (
communityAddresses: readonly string[],
selectedBoardFilter: string | null,
directories: DirectoryCommunity[],
): string[] | null => {
if (!selectedBoardFilter) {
return null;
}
const selectedFilterKey = getModQueueBoardFilterKey(selectedBoardFilter, directories);
const selectedAddresses = communityAddresses.filter((address) => {
if (areSameBoardAddress(address, selectedBoardFilter)) {
return true;
}
return getModQueueBoardFilterKey(address, directories) === selectedFilterKey;
});
return selectedAddresses.length > 0 ? selectedAddresses : [selectedBoardFilter];
};
export const getModeratedCommunityAddresses = ({
accountAddress,
accountCommunityAddresses,
candidateCommunityAddresses,
communities,
}: {
accountAddress: string | undefined;
accountCommunityAddresses: readonly string[];
candidateCommunityAddresses: readonly string[];
communities: readonly (ModQueueCommunityRoleSource | undefined)[];
}): string[] => {
const moderatedAddresses = [...accountCommunityAddresses];
if (!accountAddress) {
return moderatedAddresses;
}
candidateCommunityAddresses.forEach((candidateAddress, index) => {
const role = communities[index]?.roles?.[accountAddress]?.role;
if (hasModQueueAccessRole(role)) {
addUniqueBoardAddress(moderatedAddresses, candidateAddress);
}
});
return moderatedAddresses;
};
export const getQueuedCommentSnapshot = (comment: ModQueueCommentLike | undefined): QueuedCommentSnapshot | undefined => {
if (!comment?.cid) {
return undefined;
@@ -129,6 +252,7 @@ export const filterVisibleModQueueFeed = <T extends ModQueueCommentLike>(
feed: T[],
selectedBoardFilter: string | null,
dismissedCommentCids: ReadonlySet<string> = emptyDismissedCommentCids,
selectedBoardFilterAddresses: readonly string[] | null = null,
): T[] =>
feed.filter((comment) => {
if (comment.cid && dismissedCommentCids.has(comment.cid)) {
@@ -139,5 +263,14 @@ export const filterVisibleModQueueFeed = <T extends ModQueueCommentLike>(
return true;
}
return getCommentCommunityAddress(comment) === selectedBoardFilter;
const commentCommunityAddress = getCommentCommunityAddress(comment);
if (!commentCommunityAddress) {
return false;
}
if (selectedBoardFilterAddresses?.length) {
return selectedBoardFilterAddresses.some((address) => areSameBoardAddress(address, commentCommunityAddress));
}
return areSameBoardAddress(commentCommunityAddress, selectedBoardFilter);
});