feat(admin): add pre-registrant alert detectors
This commit is contained in:
parent
824364d526
commit
4bae4b0ec3
2 changed files with 104 additions and 1 deletions
|
|
@ -116,4 +116,49 @@ export async function detectSuspendedMembers() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function preRegItem(preReg, sublabel) {
|
||||||
|
return {
|
||||||
|
id: String(preReg._id),
|
||||||
|
label: preReg.name || preReg.email,
|
||||||
|
sublabel,
|
||||||
|
href: '/admin/pre-registrants'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function detectPreRegistrantSelectedNotInvited() {
|
||||||
|
await connectDB()
|
||||||
|
const cutoff = daysAgo(ALERT_THRESHOLDS.PREREG_SELECTED_DAYS)
|
||||||
|
const preRegs = await PreRegistration
|
||||||
|
.find({
|
||||||
|
status: 'selected',
|
||||||
|
updatedAt: { $lte: cutoff }
|
||||||
|
})
|
||||||
|
.select('name email updatedAt')
|
||||||
|
.lean()
|
||||||
|
return {
|
||||||
|
type: 'preregistrant_selected_not_invited',
|
||||||
|
severity: 'attention',
|
||||||
|
title: 'Pre-registrants selected but not invited',
|
||||||
|
items: preRegs.map((p) =>
|
||||||
|
preRegItem(p, `${p.email} — ${daysSince(p.updatedAt)} days selected`)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function detectPreRegistrantExpired() {
|
||||||
|
await connectDB()
|
||||||
|
const preRegs = await PreRegistration
|
||||||
|
.find({ status: 'expired' })
|
||||||
|
.select('name email updatedAt')
|
||||||
|
.lean()
|
||||||
|
return {
|
||||||
|
type: 'preregistrant_expired',
|
||||||
|
severity: 'attention',
|
||||||
|
title: 'Expired pre-registrant invitations',
|
||||||
|
items: preRegs.map((p) =>
|
||||||
|
preRegItem(p, `${p.email} — expired ${daysSince(p.updatedAt)} days ago`)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Aggregator lands in task 8.
|
// Aggregator lands in task 8.
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,12 @@ import {
|
||||||
detectSlackInviteFailed,
|
detectSlackInviteFailed,
|
||||||
detectNoSlackHandleAfterWeek,
|
detectNoSlackHandleAfterWeek,
|
||||||
detectStuckPendingPayment,
|
detectStuckPendingPayment,
|
||||||
detectSuspendedMembers
|
detectSuspendedMembers,
|
||||||
|
detectPreRegistrantSelectedNotInvited,
|
||||||
|
detectPreRegistrantExpired
|
||||||
} from '../../../server/utils/adminAlerts.js'
|
} from '../../../server/utils/adminAlerts.js'
|
||||||
import Member from '../../../server/models/member.js'
|
import Member from '../../../server/models/member.js'
|
||||||
|
import PreRegistration from '../../../server/models/preRegistration.js'
|
||||||
|
|
||||||
describe('adminAlerts module shell', () => {
|
describe('adminAlerts module shell', () => {
|
||||||
describe('ALERT_THRESHOLDS', () => {
|
describe('ALERT_THRESHOLDS', () => {
|
||||||
|
|
@ -193,4 +196,59 @@ describe('adminAlerts module shell', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('pre-registrant alerts', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
function mockPreRegFind(result) {
|
||||||
|
PreRegistration.find.mockReturnValue({
|
||||||
|
select: vi.fn().mockReturnValue({
|
||||||
|
lean: vi.fn().mockResolvedValue(result)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('detectPreRegistrantSelectedNotInvited', () => {
|
||||||
|
it('returns selected pre-registrants older than 3 days', async () => {
|
||||||
|
const fourDaysAgo = new Date(Date.now() - 4 * 86400000)
|
||||||
|
mockPreRegFind([
|
||||||
|
{ _id: 'p1', name: 'Fin', email: 'fin@example.com', updatedAt: fourDaysAgo }
|
||||||
|
])
|
||||||
|
|
||||||
|
const alert = await detectPreRegistrantSelectedNotInvited()
|
||||||
|
|
||||||
|
expect(alert.type).toBe('preregistrant_selected_not_invited')
|
||||||
|
expect(alert.severity).toBe('attention')
|
||||||
|
expect(alert.items).toHaveLength(1)
|
||||||
|
expect(alert.items[0]).toEqual({
|
||||||
|
id: 'p1',
|
||||||
|
label: 'Fin',
|
||||||
|
sublabel: 'fin@example.com — 4 days selected',
|
||||||
|
href: '/admin/pre-registrants'
|
||||||
|
})
|
||||||
|
|
||||||
|
const [query] = PreRegistration.find.mock.calls[0]
|
||||||
|
expect(query.status).toBe('selected')
|
||||||
|
expect(query.updatedAt).toEqual({ $lte: expect.any(Date) })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('detectPreRegistrantExpired', () => {
|
||||||
|
it('returns expired pre-registrants', async () => {
|
||||||
|
const expiredAt = new Date(Date.now() - 2 * 86400000)
|
||||||
|
mockPreRegFind([
|
||||||
|
{ _id: 'p2', name: 'Gus', email: 'gus@example.com', updatedAt: expiredAt }
|
||||||
|
])
|
||||||
|
|
||||||
|
const alert = await detectPreRegistrantExpired()
|
||||||
|
|
||||||
|
expect(alert.type).toBe('preregistrant_expired')
|
||||||
|
expect(alert.severity).toBe('attention')
|
||||||
|
expect(alert.items[0].label).toBe('Gus')
|
||||||
|
expect(PreRegistration.find).toHaveBeenCalledWith({ status: 'expired' })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue