Skip to content

Commit 5a0b281

Browse files
authored
Merge pull request #2683 from Northeastern-Electric-Racing/Send-Weekly-Deadline-Checks
Send Weekly Deadlines and Overdue Task Reminders
2 parents da0f15a + 7109462 commit 5a0b281

5 files changed

Lines changed: 53 additions & 26 deletions

File tree

src/backend/src/controllers/notifications.controllers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { NextFunction, Request, Response } from 'express';
22
import NotificationsService from '../services/notifications.services';
33

44
export default class NotificationsController {
5-
static async sendTaskDeadlineSlackNotifications(_req: Request, res: Response, next: NextFunction) {
5+
static async sendDailySlackNotifications(_req: Request, res: Response, next: NextFunction) {
66
try {
7-
await NotificationsService.sendTaskDeadlineSlackNotifications();
7+
await NotificationsService.sendDailySlackNotifications();
88

99
res.status(200).json({ message: 'Successfully sent task deadline notifications!' });
1010
} catch (error: unknown) {

src/backend/src/routes/notifications.routes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ import NotificationsController from '../controllers/notifications.controllers';
33

44
const notificationsRouter = express.Router();
55

6-
notificationsRouter.post('/task-deadlines', NotificationsController.sendTaskDeadlineSlackNotifications);
6+
notificationsRouter.post('/task-deadlines', NotificationsController.sendDailySlackNotifications);
77

88
export default notificationsRouter;

src/backend/src/services/notifications.services.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,40 @@ import {
33
TaskWithAssignees,
44
endOfDayTomorrow,
55
getTeamFromTaskAssignees,
6-
startOfDayTomorrow,
76
usersToSlackPings
87
} from '../utils/notifications.utils';
98
import { sendMessage } from '../integrations/slack';
9+
import { daysBetween } from 'shared';
10+
import { buildDueString } from '../utils/slack.utils';
11+
import WorkPackagesService from './work-packages.services';
12+
import { addWeeksToDate } from 'shared';
13+
import { HttpException } from '../utils/errors.utils';
1014

1115
export default class NotificationsService {
16+
static async sendDailySlackNotifications() {
17+
await NotificationsService.sendTaskDeadlineSlackNotifications();
18+
const date = new Date();
19+
if (date.getDay() === 1) {
20+
const nextWeek = addWeeksToDate(date, 1);
21+
const ADMIN = process.env.ADMIN_USER_ID;
22+
const admin = await prisma.user.findUnique({ where: { userId: ADMIN } });
23+
if (!admin) throw new HttpException(404, 'Admin user not found');
24+
const organizations = await prisma.organization.findMany();
25+
for (const organization of organizations) {
26+
await WorkPackagesService.slackMessageUpcomingDeadlines(admin, nextWeek, organization.organizationId);
27+
}
28+
}
29+
}
30+
1231
/**
13-
* Sends the task deadline slack notifications for all tasks with a deadline of tomorrow
32+
* Sends the task deadline slack notifications for all tasks with a deadline of tomorrow or before that are not done
1433
*/
1534
static async sendTaskDeadlineSlackNotifications() {
16-
const startOfDay = startOfDayTomorrow();
1735
const endOfDay = endOfDayTomorrow();
1836

1937
const tasks = await prisma.task.findMany({
2038
where: {
2139
deadline: {
22-
gte: startOfDay,
2340
lt: endOfDay
2441
},
2542
status: {
@@ -61,10 +78,13 @@ export default class NotificationsService {
6178
// send the notifications to each team for their respective tasks
6279
teamTaskMap.forEach((tasks, slackId) => {
6380
const messageBlock = tasks
64-
.map(
65-
(task) =>
66-
`${usersToSlackPings(task.assignees ?? [])} ${task.title} due tomorrow in project ${task.wbsElement?.name}`
67-
)
81+
.map((task) => {
82+
const daysUntilDeadline = daysBetween(task.deadline, new Date());
83+
84+
return `${usersToSlackPings(task.assignees ?? [])} ${task.title} ${buildDueString(daysUntilDeadline)} in project ${
85+
task.wbsElement?.name
86+
}`;
87+
})
6888
.join('\n\n');
6989

7090
// messageBlock will be empty if there are tasks with no assignees

src/backend/src/services/work-packages.services.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Prisma, User, WBS_Element, WBS_Element_Status } from '@prisma/client';
22
import {
3+
calculateEndDate,
34
DescriptionBulletPreview,
45
getDay,
56
isAdmin,
@@ -544,9 +545,10 @@ export default class WorkPackagesService {
544545
});
545546

546547
const upcomingWorkPackages = workPackages
547-
.map(workPackageTransformer)
548-
.filter((wp) => getDay(wp.endDate) <= getDay(deadline))
549-
.sort((a, b) => a.endDate.getTime() - b.endDate.getTime());
548+
.filter((wp) => getDay(calculateEndDate(wp.startDate, wp.duration)) <= getDay(deadline))
549+
.sort(
550+
(a, b) => calculateEndDate(a.startDate, a.duration).getTime() - calculateEndDate(b.startDate, b.duration).getTime()
551+
);
550552

551553
// have to do it like this so it goes sequentially and we can sleep between each because of rate limiting
552554
await upcomingWorkPackages.reduce(

src/backend/src/utils/slack.utils.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ChangeRequest, daysBetween, Task, UserPreview, wbsPipe, WorkPackage } from 'shared';
1+
import { ChangeRequest, daysBetween, Task, UserPreview, wbsPipe, calculateEndDate } from 'shared';
22
import { User } from '@prisma/client';
33
import { editMessage, reactToMessage, replyToMessageInThread, sendMessage } from '../integrations/slack';
44
import { getUserFullName, getUserSlackId } from './users.utils';
@@ -8,9 +8,12 @@ import { Change_Request, Design_Review, Team, WBS_Element } from '@prisma/client
88
import { UserWithSettings } from './auth.utils';
99
import { usersToSlackPings, userToSlackPing } from './notifications.utils';
1010
import { addHours, meetingStartTimePipe } from './design-reviews.utils';
11+
import { WorkPackageQueryArgs } from '../prisma-query-args/work-packages.query-args';
12+
import { Prisma } from '@prisma/client';
13+
import { userTransformer } from '../transformers/user.transformer';
1114

1215
// build the "due" string for the upcoming deadlines slack message
13-
const buildDueString = (daysUntilDeadline: number): string => {
16+
export const buildDueString = (daysUntilDeadline: number): string => {
1417
if (daysUntilDeadline < 0) return `was due *${daysUntilDeadline * -1} days ago!*`;
1518
else if (daysUntilDeadline === 0) return `is due today!`;
1619
return `is due in ${daysUntilDeadline} days!`;
@@ -24,25 +27,27 @@ const buildUserString = (lead?: UserPreview, slackId?: string): string => {
2427
return '(no project lead)';
2528
};
2629

27-
export const sendSlackUpcomingDeadlineNotification = async (workPackage: WorkPackage): Promise<void> => {
30+
export const sendSlackUpcomingDeadlineNotification = async (
31+
workPackage: Prisma.Work_PackageGetPayload<WorkPackageQueryArgs>
32+
): Promise<void> => {
2833
if (process.env.NODE_ENV !== 'production') return; // don't send msgs unless in prod
34+
const endDate = calculateEndDate(workPackage.startDate, workPackage.duration);
2935

30-
const { LEAD_CHANNEL_SLACK_ID } = process.env;
31-
if (!LEAD_CHANNEL_SLACK_ID) return;
32-
33-
const { lead } = workPackage;
36+
const { lead } = workPackage.wbsElement;
3437
const slackId = await getUserSlackId(lead?.userId);
35-
const daysUntilDeadline = daysBetween(workPackage.endDate, new Date());
38+
const daysUntilDeadline = daysBetween(endDate, new Date());
3639

37-
const userString = buildUserString(lead, slackId);
40+
const userString = lead ? buildUserString(userTransformer(lead), slackId) : 'No Lead Set';
3841
const dueString = buildDueString(daysUntilDeadline);
3942

40-
const wbsNumber: string = wbsPipe(workPackage.wbsNum);
43+
const wbsNumber: string = wbsPipe(workPackage.wbsElement);
4144
const wbsString = `<https://finishlinebyner.com/projects/${wbsNumber}|${wbsNumber}>`;
4245

43-
const fullMsg = `${userString} ${wbsString}: ${workPackage.projectName} - ${workPackage.name} ${dueString}`;
46+
const fullMsg = `${userString} ${wbsString}: ${workPackage.project.wbsElement.name} - ${workPackage.wbsElement.name} ${dueString}`;
4447

45-
await sendMessage(LEAD_CHANNEL_SLACK_ID, fullMsg);
48+
const promises = workPackage.project.teams.map(async (team) => await sendMessage(team.slackId, fullMsg));
49+
50+
await Promise.all(promises);
4651
};
4752

4853
/**

0 commit comments

Comments
 (0)