Add recent outreach rejections stream
This commit is contained in:
1 parent
0c21202253
commit
94049ed9c4
9 files changed
+170
-16
No files matched your search
@@ -245,6 +245,21 @@ describe('CandidateQueueRepository', () => {
|
||||
expect(reasons).toEqual([{ last_error: 'Cleared manually by operator' }]);
|
||||
});
|
||||
|
||||
it('lists the latest 50 rejected candidates by their terminal update time', () => {
|
||||
queue.enqueue([post('older', 'alice'), post('newer', 'bob')], 'image', 1, 1_000);
|
||||
const older = queue.claimForScreening('older-worker', 1_000, 1_001)!;
|
||||
queue.reject(older.postId, 'older-worker', 'No gender tag in title/body', 1_002);
|
||||
const newer = queue.claimForScreening('newer-worker', 1_000, 1_003)!;
|
||||
queue.reject(newer.postId, 'newer-worker', 'Seller signals detected', 1_004);
|
||||
queue.enqueue([post('manual', 'carol')], 'image', 1, 1_005);
|
||||
queue.cancelUnsent('manual', 1_006);
|
||||
|
||||
expect(queue.listRecentRejected()).toEqual([
|
||||
expect.objectContaining({ postId: 'newer', lastError: 'Seller signals detected', updatedAt: 1_004 }),
|
||||
expect.objectContaining({ postId: 'older', lastError: 'No gender tag in title/body', updatedAt: 1_002 }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('edits only prepared unsent messages and clears generated attribution', () => {
|
||||
queue.enqueue([post('editable', 'alice', 200), post('waiting', 'bob', 100)], 'image', 1, 1_000);
|
||||
const editable = queue.claimForScreening('screen', 1_000, 1_001)!;
|
||||
|
||||
@@ -130,6 +130,11 @@ describe('dashboard foundation', () => {
|
||||
stages: { discovered: 1, screening: 0, ready: 1, sending: 0, retryable: 1 },
|
||||
outcomes: { sent: 12, rejected: 7 },
|
||||
rejectionReasons: [{ reason: 'Seller signals detected', count: 5 }, { reason: 'No gender tag in title/body', count: 2 }],
|
||||
recentRejections: [{
|
||||
postId: 'rejected-1', subreddit: 'example', author: 'bob', title: '[24F] Rejected post',
|
||||
postUrl: 'https://www.reddit.com/r/example/comments/rejected-1/', sourceType: 'chat',
|
||||
sourceContent: 'This is the content that was screened.', rejectedAt: Date.parse('2026-08-09T07:45:00Z'), reason: 'Seller signals detected',
|
||||
}],
|
||||
oldestActiveAt: Date.parse('2026-08-09T07:00:00Z'),
|
||||
candidates: [{
|
||||
postId: 'candidate-1', subreddit: 'example', author: 'alice', title: '[25F] A potential post',
|
||||
@@ -170,9 +175,12 @@ describe('dashboard foundation', () => {
|
||||
|
||||
expect(await screen.findByRole('heading', { name: 'Outreach funnel' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('heading', { name: '3 active posts' })).toBeInTheDocument();
|
||||
await user.click(screen.getByRole('tab', { name: 'Rejections 7' }));
|
||||
expect(screen.getByRole('tabpanel', { name: 'Rejections 7' })).toHaveTextContent('Seller signals detected');
|
||||
await user.click(screen.getByRole('tab', { name: 'Rejection reasons 7' }));
|
||||
expect(screen.getByRole('tabpanel', { name: 'Rejection reasons 7' })).toHaveTextContent('Seller signals detected');
|
||||
expect(screen.getByRole('list', { name: 'Outreach rejection reasons' })).toHaveTextContent('5');
|
||||
await user.click(screen.getByRole('tab', { name: 'Recent rejections 1' }));
|
||||
expect(screen.getByRole('list', { name: 'Recent rejected outreach candidates' })).toHaveTextContent('This is the content that was screened.');
|
||||
expect(screen.getByText('Rejected because')).toBeInTheDocument();
|
||||
await user.click(screen.getByRole('tab', { name: 'Active queue 3' }));
|
||||
expect(screen.getByRole('list', { name: 'Active outreach candidates' })).toHaveTextContent('A potential post');
|
||||
expect(screen.getAllByRole('link', { name: '[25F] A potential post' }))
|
||||
@@ -225,6 +233,7 @@ describe('dashboard foundation', () => {
|
||||
stages: { discovered: cleared ? 0 : 2, screening: 0, ready: 0, sending: 1, retryable: 0 },
|
||||
outcomes: { sent: 12, rejected: cleared ? 9 : 7 },
|
||||
rejectionReasons: [{ reason: 'Cleared manually by operator', count: cleared ? 2 : 0 }].filter(({ count }) => count > 0),
|
||||
recentRejections: [],
|
||||
oldestActiveAt: Date.now() - 60_000,
|
||||
candidates: [],
|
||||
})),
|
||||
|
||||
@@ -35,6 +35,14 @@ describe('outreach route', () => {
|
||||
rejectionReasons: [{ reason: 'Seller signals detected', count: 1 }],
|
||||
})),
|
||||
getOldestActiveAt: vi.fn(() => 1_000),
|
||||
listRecentRejected: vi.fn(() => [{
|
||||
postId: 'rejected-1', subreddit: 'example', author: 'bob', sourceType: 'chat',
|
||||
post: {
|
||||
id: 'rejected-1', subreddit: 'example', author: 'bob', title: 'A rejected post',
|
||||
permalink: '/r/example/comments/rejected-1/', selftext: 'Scanned post content',
|
||||
},
|
||||
updatedAt: 3_000, lastError: 'Seller signals detected',
|
||||
}]),
|
||||
listActive: vi.fn(() => [{
|
||||
postId: 'post-1', subreddit: 'example', author: 'alice', sourceType: 'image',
|
||||
post: {
|
||||
@@ -89,6 +97,11 @@ describe('outreach route', () => {
|
||||
stages: { discovered: 2, screening: 1, ready: 1, sending: 1, retryable: 1 },
|
||||
outcomes: { sent: 2, rejected: 1 },
|
||||
rejectionReasons: [{ reason: 'Seller signals detected', count: 1 }],
|
||||
recentRejections: [{
|
||||
postId: 'rejected-1', subreddit: 'example', author: 'bob', title: 'A rejected post',
|
||||
postUrl: 'https://www.reddit.com/r/example/comments/rejected-1/', sourceType: 'chat',
|
||||
sourceContent: 'Scanned post content', rejectedAt: 3_000, reason: 'Seller signals detected',
|
||||
}],
|
||||
candidates: [{
|
||||
postId: 'post-1', status: 'sending', hasPreparedMessage: true,
|
||||
postUrl: 'https://www.reddit.com/r/example/comments/post-1/hello/',
|
||||
|
||||
Reference in new issue
Block a user