Skip to content

Commit 2cd3898

Browse files
committed
#4032 slack event msgs + admin tools disable w/ tooltip
1 parent 701b1a4 commit 2cd3898

4 files changed

Lines changed: 48 additions & 19 deletions

File tree

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

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export default class NotificationsService {
118118
}
119119

120120
/**
121-
* Sends the design review slack notifications for all design reviews scheduled for today
121+
* Sends Slack notifications for all events scheduled for today whose event type has sendSlackNotifications enabled
122122
*/
123123
static async sendEventSlackNotifications() {
124124
const endOfToday = startOfDayTomorrow();
@@ -142,6 +142,8 @@ export default class NotificationsService {
142142
optionalMembers: { include: { userSettings: true } },
143143
userCreated: { include: { userSettings: true } },
144144
scheduledTimes: true,
145+
teams: true,
146+
eventType: true,
145147
workPackages: {
146148
include: {
147149
wbsElement: true,
@@ -156,12 +158,18 @@ export default class NotificationsService {
156158
}
157159
});
158160

159-
const desginReviewEventTeamMap = new Map<string, EventWithAttendees[]>();
161+
const eventTeamMap = new Map<string, EventWithAttendees[]>();
160162

161163
events.forEach((event) => {
162-
// Get all unique teams from all work packages associated with this event
164+
// Collect unique team Slack IDs: first from teams directly on the event, then from work packages
163165
const teamSlackIds = new Set<string>();
164166

167+
event.teams.forEach((team) => {
168+
if (team.slackId) {
169+
teamSlackIds.add(team.slackId);
170+
}
171+
});
172+
165173
event.workPackages.forEach((workPackage) => {
166174
workPackage.project.teams.forEach((team) => {
167175
if (team.slackId) {
@@ -171,7 +179,7 @@ export default class NotificationsService {
171179
});
172180

173181
teamSlackIds.forEach((teamSlackId) => {
174-
const currentEvents = desginReviewEventTeamMap.get(teamSlackId);
182+
const currentEvents = eventTeamMap.get(teamSlackId);
175183
const eventWithAttendees = {
176184
...event,
177185
attendees: event.requiredMembers.concat(event.optionalMembers).concat(event.userCreated),
@@ -181,20 +189,20 @@ export default class NotificationsService {
181189
if (currentEvents) {
182190
currentEvents.push(eventWithAttendees);
183191
} else {
184-
desginReviewEventTeamMap.set(teamSlackId, [eventWithAttendees]);
192+
eventTeamMap.set(teamSlackId, [eventWithAttendees]);
185193
}
186194
});
187195
});
188196

189-
// Send the notifications to each team for their respective design reviews
190-
const promises = Array.from(desginReviewEventTeamMap).map(async ([slackId, events]) => {
197+
// Send the notifications to each team for their respective events
198+
const promises = Array.from(eventTeamMap).map(async ([slackId, events]) => {
191199
const messageBlock = events
192200
.map((event) => {
193201
const zoomLink = event.zoomLink ? `<${event.zoomLink}|Zoom Link>\n` : '';
194202
const questionDocLink = event.questionDocumentLink ? `<${event.questionDocumentLink}|Question Doc Link>\n` : '';
195203

196-
// Get work package names for this event
197204
const workPackageNames = event.workPackages.map((wp) => wp.wbsElement.name).join(', ');
205+
const workPackagesPart = workPackageNames ? ` (${workPackageNames})` : '';
198206

199207
// Get the earliest scheduled start time for display
200208
const [earliestSlot] = event.scheduledTimes
@@ -203,17 +211,17 @@ export default class NotificationsService {
203211
const timeDisplay = earliestSlot ? formatTimeForSlack(new Date(earliestSlot.startTime!)) : 'TBD';
204212

205213
return (
206-
`${usersToSlackPings(event.attendees ?? [])} ${event.title} (${workPackageNames}) ` +
214+
`${usersToSlackPings(event.attendees ?? [])} *${event.eventType.name}*: ${event.title}${workPackagesPart} ` +
207215
`will be having an event today at ${timeDisplay} ET! ` +
208216
zoomLink +
209217
questionDocLink
210218
);
211219
})
212220
.join('\n\n');
213221

214-
// messageBlock will be empty if there are design reviews with no attendees
222+
// messageBlock will be empty if there are events with no attendees
215223
if (messageBlock !== '')
216-
await sendMessage(slackId, ':calendar: :clock9: Upcoming Design Reviews! :clock9: :calendar: \n\n\n' + messageBlock);
224+
await sendMessage(slackId, ':calendar: :clock9: Upcoming Events! :clock9: :calendar: \n\n\n' + messageBlock);
217225
});
218226

219227
await Promise.all(promises);

src/backend/src/utils/notifications.utils.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Task as Prisma_Task, WBS_Element, Event, Work_Package } from '@prisma/client';
1+
import { Task as Prisma_Task, WBS_Element, Event, Work_Package, Team, Event_Type } from '@prisma/client';
22
import { UserWithSettings } from './auth.utils.js';
33
import { ScheduleSlot } from 'shared';
44

@@ -10,14 +10,16 @@ export type TaskWithAssignees = Prisma_Task & {
1010
export type EventWithAttendees = Event & {
1111
attendees: UserWithSettings[];
1212
scheduledTimes: ScheduleSlot[];
13+
teams: Team[];
14+
eventType: Event_Type;
1315
workPackages: (Work_Package & {
1416
wbsElement: WBS_Element;
1517
})[];
1618
};
1719

1820
export const usersToSlackPings = (users: UserWithSettings[]) => {
1921
// https://api.slack.com/reference/surfaces/formatting#mentioning-users
20-
return users.map(userToSlackPing).join(' ');
22+
return users.filter((user) => user.userSettings?.slackId).map(userToSlackPing).join(' ');
2123
};
2224

2325
export const userToSlackPing = (user: UserWithSettings) => {

src/frontend/src/pages/AdminToolsPage/ScheduleConfig/EventType/EventTypeFormModal.tsx

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { Box, FormHelperText, Typography, Checkbox, FormControl, Select, MenuItem } from '@mui/material';
1+
import { Box, FormHelperText, Typography, Checkbox, FormControl, Select, MenuItem, Tooltip } from '@mui/material';
22
import NERFormModal from '../../../../components/NERFormModal';
33
import ReactHookTextField from '../../../../components/ReactHookTextField';
44
import { useToast } from '../../../../hooks/toasts.hooks';
55
import { useForm, Controller } from 'react-hook-form';
66
import * as yup from 'yup';
77
import { yupResolver } from '@hookform/resolvers/yup';
8-
import React from 'react';
8+
import React, { useEffect } from 'react';
99
import { EventType } from 'shared';
1010
import useFormPersist from 'react-hook-form-persist';
1111
import { FormStorageKey } from '../../../../utils/form';
@@ -116,6 +116,16 @@ export const EventTypeFormModal: React.FC<EventTypeFormModalProps> = ({ open, on
116116
setValue
117117
});
118118

119+
const watchTeams = watch('teams');
120+
const watchWorkPackage = watch('workPackage');
121+
const notificationsDisabled = !watchTeams && !watchWorkPackage;
122+
123+
useEffect(() => {
124+
if (notificationsDisabled) {
125+
setValue('sendSlackNotifications', false);
126+
}
127+
}, [notificationsDisabled, setValue]);
128+
119129
const onFormSubmit = async (data: EventTypeFormValues) => {
120130
try {
121131
await onSubmit(data);
@@ -829,12 +839,22 @@ export const EventTypeFormModal: React.FC<EventTypeFormModalProps> = ({ open, on
829839
control={control}
830840
name="sendSlackNotifications"
831841
render={({ field: { onChange, value } }) => (
832-
<Checkbox checked={value} onChange={onChange} sx={{ color: 'white', '&.Mui-checked': { color: 'white' } }} />
842+
<Checkbox
843+
checked={value}
844+
onChange={onChange}
845+
disabled={notificationsDisabled}
846+
sx={{ color: 'white', '&.Mui-checked': { color: 'white' } }}
847+
/>
833848
)}
834849
/>
835-
<NotificationsIcon sx={{ color: 'white', mr: 1 }} />
850+
<Tooltip
851+
title={notificationsDisabled ? 'Slack notifications cannot be sent without teams or work packages enabled' : ''}
852+
placement="right"
853+
>
854+
<NotificationsIcon sx={{ color: notificationsDisabled ? 'rgba(255,255,255,0.3)' : 'white', mr: 1 }} />
855+
</Tooltip>
836856
<Box sx={{ flex: 1 }}>
837-
<Typography variant="body2" sx={{ color: 'white' }}>
857+
<Typography variant="body2" sx={{ color: notificationsDisabled ? 'rgba(255,255,255,0.3)' : 'white' }}>
838858
Send Slack Notifications
839859
</Typography>
840860
</Box>

src/frontend/src/pages/CalendarPage/Components/EventModal.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ import Tooltip from '@mui/material/Tooltip';
5555
import { convertDayToInt, convertIntToDay } from '../../../utils/calendar.utils';
5656
import EditSeriesConfirmationModal from './EditSeriesConfirmationModal';
5757
import NotificationsIcon from '@mui/icons-material/Notifications';
58-
import NERSwitch from '../../../components/NERSwitch';
5958
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
6059

6160
export interface EventFormValues {

0 commit comments

Comments
 (0)