Feature/weekly low standup report - #9
Conversation
- Create new automation task that runs every Monday at 9 AM - Identifies students with two consecutive standup scores under 2 from previous week - Posts formatted report to #stats Slack channel - Supports multiple active events with Slack integration - Uses Slack Block Kit for rich message formatting - Includes student Slack mentions when available - Follows existing codebase patterns and error handling
- Refactor filtering logic into testable pure functions - Export findConsecutiveLowScores and formatStudentList for testing - Add comprehensive manual test suite (12 test cases) - Fix TypeScript type errors with PickNonNullable - All tests passing ✅
- Remove duplicated function code from test file - Import findConsecutiveLowScores and formatStudentList from actual implementation - Add .env.test.example with minimal test environment variables - Update test documentation with setup instructions - Tests now stay in sync with implementation automatically
- Create testWeeklyStandupReport.ts manual testing script - Support dry-run mode to preview messages without posting - Support custom channel selection for testing - Use fake student data to avoid pinging real people - Add comprehensive testing documentation - Include troubleshooting guide Usage: npx ts-node scripts/testWeeklyStandupReport.ts --dry-run npx ts-node scripts/testWeeklyStandupReport.ts --channel=test-notifications
🤖 Augment PR SummarySummary: Adds an automated weekly Slack report to flag students who had two consecutive low standup ratings in the prior week. Changes:
Technical Notes: Uses Prisma for data access, Luxon for date windows, and Slack Web API for channel lookup and message posting. 🤖 Was this summary useful? React with 👍 or 👎 |
|
|
||
| const DEBUG = makeDebug('automation:tasks:weeklyLowStandupReport'); | ||
|
|
||
| // Run every Monday at 9 AM Pacific Time |
There was a problem hiding this comment.
The code/comment says this runs at “9 AM Pacific Time”, but CronJob in src/automation/index.ts doesn’t specify a timezone, so this will run in the server/process timezone (often UTC in production) which can shift the actual send time and the “previous week” window.
Severity: medium
Other Locations
docs/testing-weekly-standup-report.md:7docs/testing-weekly-standup-report.md:142
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
|
|
||
| // Find the stats channel | ||
| try { | ||
| const channelsList = await slack.conversations.list({ |
There was a problem hiding this comment.
slack.conversations.list is paginated; searching only the first page can miss #stats in workspaces with many channels (elsewhere the codebase uses slack.paginate(...) for this).
Severity: medium
Other Locations
scripts/testWeeklyStandupReport.ts:376
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| /** | ||
| * Manual test script for weeklyLowStandupReport | ||
| * | ||
| * Prerequisites: | ||
| * - Copy .env.test.example to .env (if you don't have a .env file): | ||
| * cp .env.test.example .env | ||
| * | ||
| * Run with: | ||
| * npx ts-node src/automation/tasks/weeklyLowStandupReport.manual-test.ts | ||
| * | ||
| * To test Slack channel lookup, set SLACK_BOT_TOKEN environment variable: | ||
| * SLACK_BOT_TOKEN=xoxb-your-token npx ts-node src/automation/tasks/weeklyLowStandupReport.manual-test.ts | ||
| * | ||
| * This tests the pure functions (findConsecutiveLowScores and formatStudentList) | ||
| * imported from the actual implementation file to ensure tests stay in sync with code. | ||
| * It also optionally tests Slack channel lookup if a token is provided. | ||
| */ | ||
|
|
||
| import 'reflect-metadata'; | ||
| import { findConsecutiveLowScores, formatStudentList } from './weeklyLowStandupReport'; |
There was a problem hiding this comment.
Manual test file placed in the auto-loaded tasks directory
src/automation/tasks/index.ts calls fs.readdirSync(__dirname) and loads every file in the directory that isn't index.ts/index.js. Because weeklyLowStandupReport.manual-test.ts lives in that same directory and has no default export, the task loader will throw Error: Task weeklyLowStandupReport.manual-test.ts does not include a default export. at startup. Additionally, the top-level synchronous test calls (lines 50–138) and the floating runAllTests() call at line 365 execute at require-time, printing test output and potentially exiting the process before the loader even checks for the missing export. This file should be moved outside the tasks directory (e.g., to src/automation/__tests__/ or scripts/) so the task auto-loader does not pick it up.
| const channelsList = await slack.conversations.list({ | ||
| exclude_archived: true, | ||
| types: 'public_channel,private_channel', | ||
| }); | ||
|
|
||
| const statsChannel = channelsList.channels?.find( | ||
| (c: any) => c.name === STATS_CHANNEL_NAME | ||
| ); | ||
|
|
||
| if (!statsChannel) { | ||
| DEBUG(`Channel #${STATS_CHANNEL_NAME} not found, skipping report.`); | ||
| return; | ||
| } |
There was a problem hiding this comment.
conversations.list not paginated — may silently miss #stats channel
Slack's conversations.list API returns at most 100 channels per page by default. For any workspace with more than 100 channels, the #stats channel may fall outside the first page, causing statsChannel to be undefined and the report to be silently dropped. The production code does not pass limit or handle response_metadata.next_cursor for subsequent pages, while even the test script at least passes limit: 100. A workspace that grows past the page boundary will silently stop receiving reports with no error logged.
| // Run every Monday at 9 AM Pacific Time | ||
| export const JOBSPEC = '0 9 * * 1'; |
There was a problem hiding this comment.
The cron expression
'0 9 * * 1' runs at 9 AM in whatever timezone the server uses, not necessarily Pacific Time. Cron has no built-in timezone support; the comment may mislead operators who configure the server in UTC (where this fires at 9 AM UTC, which is 1/2 AM Pacific). Consider documenting the expected server timezone or using a scheduler that supports explicit timezone configuration.
| // Run every Monday at 9 AM Pacific Time | |
| export const JOBSPEC = '0 9 * * 1'; | |
| // Run every Monday at 9 AM in the server's local timezone. | |
| // Ensure the server is configured to run in the intended timezone (e.g. America/Los_Angeles). | |
| export const JOBSPEC = '0 9 * * 1'; |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| for (let i = 0; i < ratings.length - 1; i++) { | ||
| const current = ratings[i]; | ||
| const next = ratings[i + 1]; | ||
|
|
||
| if (current !== null && next !== null && current < 2 && next < 2) { | ||
| const mentorById = new Map<string, { givenName: string; surname: string; slackId: string | null }>(); | ||
| for (const project of student.projects || []) { | ||
| for (const mentor of project.mentors) { | ||
| mentorById.set(mentor.id, { | ||
| givenName: mentor.givenName, | ||
| surname: mentor.surname, | ||
| slackId: mentor.slackId, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| studentId: student.id, | ||
| givenName: student.givenName, | ||
| surname: student.surname, | ||
| slackId: student.slackId, | ||
| assignedMentors: Array.from(mentorById.values()), | ||
| eventName, | ||
| consecutiveLowScores: 2, | ||
| lastTwoRatings: [current, next], | ||
| }; | ||
| } |
There was a problem hiding this comment.
findConsecutiveLowScores reports the first consecutive pair, not the most recent
The loop returns on the very first consecutive pair with scores < 2 it encounters, and lastTwoRatings captures those two scores. If a student had ratings [1, 1, 3, 3] (struggled early but recovered), they are still flagged, and the message shows the stale scores from the beginning of the week rather than a current picture. The PR description implies checking the most recent two consecutive scores. Consider scanning from the end of the sorted array (most recent entries first) to surface the latest trend.
Adds automation that flags students with 2 consecutive stand up scores less than 2 during a week and puts a message in a designated slack channel with a list of those flagged students.