From 0dc1b6ddbc2041933d3a078184a5b969902b39df Mon Sep 17 00:00:00 2001 From: Jennie Robinson Faber Date: Wed, 8 Apr 2026 11:12:52 +0100 Subject: [PATCH] feat(admin): add pending tag suggestions detector --- server/utils/adminAlerts.js | 31 +++++++++++++++++++ tests/server/utils/adminAlerts.test.js | 41 ++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/server/utils/adminAlerts.js b/server/utils/adminAlerts.js index ec778d2..90999f6 100644 --- a/server/utils/adminAlerts.js +++ b/server/utils/adminAlerts.js @@ -226,4 +226,35 @@ export async function detectEventsNearCapacity() { } } +export async function detectPendingTagSuggestions() { + await connectDB() + const suggestions = await TagSuggestion + .find({ status: 'pending' }) + .select('_id') + .lean() + if (suggestions.length === 0) { + return { + type: 'tag_suggestions_pending', + severity: 'attention', + title: 'Pending tag suggestions', + items: [] + } + } + return { + type: 'tag_suggestions_pending', + severity: 'attention', + title: 'Pending tag suggestions', + items: [ + { + id: 'tag-suggestions', + label: `${suggestions.length} pending tag suggestion${suggestions.length === 1 ? '' : 's'}`, + sublabel: 'Review and approve in Tags', + href: '/admin/tags' + } + ], + // signature is computed from the underlying suggestion ids, not the rendered item id + signatureIds: suggestions.map((s) => String(s._id)) + } +} + // Aggregator lands in task 8. diff --git a/tests/server/utils/adminAlerts.test.js b/tests/server/utils/adminAlerts.test.js index f26ac81..1462bc0 100644 --- a/tests/server/utils/adminAlerts.test.js +++ b/tests/server/utils/adminAlerts.test.js @@ -61,6 +61,8 @@ import { import Member from '../../../server/models/member.js' import PreRegistration from '../../../server/models/preRegistration.js' import Event from '../../../server/models/event.js' +import { detectPendingTagSuggestions } from '../../../server/utils/adminAlerts.js' +import TagSuggestion from '../../../server/models/tagSuggestion.js' describe('adminAlerts module shell', () => { describe('ALERT_THRESHOLDS', () => { @@ -349,4 +351,43 @@ describe('adminAlerts module shell', () => { }) }) }) + + describe('tag suggestion alert', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + function mockTagSuggestionFind(result) { + TagSuggestion.find.mockReturnValue({ + select: vi.fn().mockReturnValue({ + lean: vi.fn().mockResolvedValue(result) + }) + }) + } + + it('returns a single aggregated item with the count', async () => { + mockTagSuggestionFind([ + { _id: 't1' }, { _id: 't2' }, { _id: 't3' } + ]) + + const alert = await detectPendingTagSuggestions() + + expect(alert.type).toBe('tag_suggestions_pending') + expect(alert.severity).toBe('attention') + expect(alert.items).toHaveLength(1) + expect(alert.items[0]).toEqual({ + id: 'tag-suggestions', + label: '3 pending tag suggestions', + sublabel: 'Review and approve in Tags', + href: '/admin/tags' + }) + expect(TagSuggestion.find).toHaveBeenCalledWith({ status: 'pending' }) + }) + + it('returns an empty list when nothing is pending', async () => { + mockTagSuggestionFind([]) + const alert = await detectPendingTagSuggestions() + expect(alert.items).toEqual([]) + }) + }) })