Skip to content

Commit 5325992

Browse files
authored
Merge pull request #1359 from Northeastern-Electric-Racing/#1253-ping-task-asignees
#1253 Ping Task Assignees on Slack
2 parents 6334d51 + 3e63947 commit 5325992

4 files changed

Lines changed: 71 additions & 17 deletions

File tree

src/backend/src/services/tasks.services.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import teamQueryArgs from '../prisma-query-args/teams.query-args';
66
import prisma from '../prisma/prisma';
77
import taskTransformer from '../transformers/tasks.transformer';
88
import { NotFoundException, AccessDeniedException, HttpException, DeletedException } from '../utils/errors.utils';
9-
import { hasPermissionToEditTask } from '../utils/tasks.utils';
9+
import { hasPermissionToEditTask, sendSlackTaskAssignedNotificationToUsers } from '../utils/tasks.utils';
1010
import { areUsersPartOfTeams, isUserOnTeam } from '../utils/teams.utils';
1111
import { getUsers } from '../utils/users.utils';
1212
import { wbsNumOf } from '../utils/utils';
@@ -79,7 +79,11 @@ export default class TasksService {
7979
...taskQueryArgs
8080
});
8181

82-
return taskTransformer(createdTask);
82+
const newTask = taskTransformer(createdTask);
83+
84+
sendSlackTaskAssignedNotificationToUsers(newTask, assignees);
85+
86+
return newTask;
8387
}
8488

8589
/**
@@ -152,12 +156,16 @@ export default class TasksService {
152156
const originalTask = await prisma.task.findUnique({
153157
where: { taskId },
154158
include: {
155-
wbsElement: { include: { project: { ...projectQueryArgs } } }
159+
wbsElement: { include: { project: { ...projectQueryArgs } } },
160+
assignees: true
156161
}
157162
});
158163
if (!originalTask) throw new NotFoundException('Task', taskId);
159164
if (originalTask.dateDeleted) throw new DeletedException('Task', taskId);
160165

166+
const originalAssigneeIds = originalTask.assignees.map((assignee) => assignee.userId);
167+
const newAssigneeIds = assignees.filter((userId) => !originalAssigneeIds.includes(userId));
168+
161169
const hasPermission = await hasPermissionToEditTask(user, taskId);
162170
if (!hasPermission)
163171
throw new AccessDeniedException(
@@ -183,17 +191,21 @@ export default class TasksService {
183191
};
184192
});
185193

186-
const updatedTask = await prisma.task.update({
187-
where: { taskId },
188-
data: {
189-
assignees: {
190-
set: transformedAssigneeUsers
191-
}
192-
},
193-
...taskQueryArgs
194-
});
195-
196-
return taskTransformer(updatedTask);
194+
const updatedTask = taskTransformer(
195+
await prisma.task.update({
196+
where: { taskId },
197+
data: {
198+
assignees: {
199+
set: transformedAssigneeUsers
200+
}
201+
},
202+
...taskQueryArgs
203+
})
204+
);
205+
206+
await sendSlackTaskAssignedNotificationToUsers(updatedTask, newAssigneeIds);
207+
208+
return updatedTask;
197209
}
198210

199211
/**

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { ChangeRequest, daysBetween, User, wbsPipe, WorkPackage } from 'shared';
1+
import { ChangeRequest, daysBetween, Task, User, wbsPipe, WorkPackage } from 'shared';
22
import { sendMessage } from '../integrations/slack';
33
import { getUserSlackId } from './users.utils';
4+
import prisma from '../prisma/prisma';
45

56
// build the "due" string for the upcoming deadlines slack message
67
const buildDueString = (daysUntilDeadline: number): string => {
@@ -51,3 +52,18 @@ export const sendSlackRequestedReviewNotification = async (slackId: string, chan
5152
const fullMsg = `Your review has been requested on CR #${changeRequest.crId}: ${changeRequestLink}.`;
5253
await sendMessage(slackId, fullMsg);
5354
};
55+
56+
/**
57+
* Send Task assigned notification to assignee on Slack
58+
* @param slackId the slack id of the assignee
59+
* @param task the task they were assigned to
60+
*/
61+
export const sendSlackTaskAssignedNotification = async (slackId: string, task: Task): Promise<void> => {
62+
if (process.env.NODE_ENV !== 'production') return; // don't send msgs unless in prod
63+
64+
const project = await prisma.wBS_Element.findUnique({ where: { wbsNumber: task.wbsNum } });
65+
const msg = `You have been assigned to a task: ${task.title} on project ${wbsPipe(task.wbsNum)} - ${project?.name}`;
66+
const link = `https://finishlinebyner.com/projects/${wbsPipe(task.wbsNum)}/tasks`;
67+
const linkButtonText = 'View Task';
68+
await sendMessage(slackId, msg, link, linkButtonText);
69+
};

src/backend/src/utils/tasks.utils.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Task_Priority, Task_Status, User } from '@prisma/client';
2-
import { isHead, TaskPriority, TaskStatus } from 'shared';
2+
import { isHead, Task, TaskPriority, TaskStatus } from 'shared';
33
import prisma from '../prisma/prisma';
4+
import { sendSlackTaskAssignedNotification } from './slack.utils';
45

56
export const convertTaskPriority = (priority: Task_Priority): TaskPriority =>
67
({
@@ -103,3 +104,17 @@ export const hasPermissionToEditTask = async (user: User, taskId: string): Promi
103104

104105
return false;
105106
};
107+
108+
/**
109+
* Sends a task assigned notification to the specified users on Slack
110+
* @param task the task the users are assigned to
111+
* @param assigneeIds the user ids of the users assigned to the task
112+
*/
113+
export const sendSlackTaskAssignedNotificationToUsers = async (task: Task, assigneeIds: number[]) => {
114+
const assigneeSettings = await prisma.user_Settings.findMany({ where: { userId: { in: assigneeIds } } });
115+
assigneeSettings.forEach(async (settings) => {
116+
if (settings.slackId) {
117+
await sendSlackTaskAssignedNotification(settings.slackId, task);
118+
}
119+
});
120+
};

src/backend/tests/tasks.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,16 @@ import {
1616
taskSaveTheDayPrisma,
1717
taskSaveTheDayShared
1818
} from './test-data/tasks.test-data';
19-
import { aquaman, batman, greenlantern, superman, theVisitor, wonderwoman } from './test-data/users.test-data';
19+
import {
20+
aquaman,
21+
batman,
22+
batmanSettings,
23+
greenlantern,
24+
superman,
25+
supermanSettings,
26+
theVisitor,
27+
wonderwoman
28+
} from './test-data/users.test-data';
2029
import { prismaWbsElement1 } from './test-data/wbs-element.test-data';
2130
import { prismaProject1 } from './test-data/projects.test-data';
2231
import { justiceLeague, prismaTeam1 } from './test-data/teams.test-data';
@@ -129,6 +138,7 @@ describe('Tasks', () => {
129138
vi.spyOn(teamUtils, 'areUsersPartOfTeams').mockReturnValue(true);
130139
vi.spyOn(prisma.task, 'create').mockResolvedValue(taskSaveTheDayPrisma);
131140
vi.spyOn(prisma.user, 'findMany').mockResolvedValue([batman, wonderwoman]);
141+
vi.spyOn(prisma.user_Settings, 'findMany').mockResolvedValue([batmanSettings, supermanSettings]);
132142

133143
const task = await TasksService.createTask(batman, mockWBSNum, 'hellow world', '', mockDate, 'HIGH', 'DONE', [
134144
batman.userId,
@@ -243,6 +253,7 @@ describe('Tasks', () => {
243253
vi.spyOn(taskTransformer, 'default').mockReturnValue(taskSaveTheDayInProgressShared);
244254
vi.spyOn(userUtils, 'getUsers').mockResolvedValue([batman, wonderwoman]);
245255
vi.spyOn(teamUtils, 'areUsersPartOfTeams').mockReturnValue(true);
256+
vi.spyOn(prisma.user_Settings, 'findMany').mockResolvedValue([batmanSettings, supermanSettings]);
246257

247258
const taskId = '1';
248259
const userIds = [

0 commit comments

Comments
 (0)