Skip to content

Commit 81ad03f

Browse files
committed
edit slack ids for teams
1 parent a60d2ac commit 81ad03f

9 files changed

Lines changed: 211 additions & 5 deletions

File tree

src/backend/src/controllers/teams.controllers.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,17 @@ export default class TeamsController {
7171
}
7272
}
7373

74+
static async editSlackId(req: Request, res: Response, next: NextFunction) {
75+
try {
76+
const { slackId } = req.body;
77+
78+
const team = await TeamsService.editSlackId(req.currentUser, req.organization, req.params.teamId, slackId);
79+
res.status(200).json(team);
80+
} catch (error: unknown) {
81+
next(error);
82+
}
83+
}
84+
7485
static async setTeamHead(req: Request, res: Response, next: NextFunction) {
7586
try {
7687
const { userId } = req.body;

src/backend/src/routes/teams.routes.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ teamsRouter.post(
3333
TeamsController.editDescription
3434
);
3535

36+
teamsRouter.post(
37+
'/:teamId/edit-slackid',
38+
nonEmptyString(body('slackId')),
39+
validateInputs,
40+
TeamsController.editSlackId
41+
)
42+
3643
teamsRouter.post('/:teamId/set-head', nonEmptyString(body('userId')), validateInputs, TeamsController.setTeamHead);
3744
teamsRouter.post('/:teamId/delete', TeamsController.deleteTeam);
3845
teamsRouter.post(

src/backend/src/services/teams.services.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,37 @@ export default class TeamsService {
172172
return teamTransformer(updateTeam);
173173
}
174174

175+
/**
176+
* Updates the slack id of a given team
177+
* @param updater the user updating
178+
* @param organization the organizaiton
179+
* @param teamId the id of the team
180+
* @param slackId the new slack id
181+
* @returns a preview of the updated team
182+
*/
183+
static async editSlackId(updater: User, organization: Organization, teamId: string, slackId: string) {
184+
const team = await TeamsService.getSingleTeam(teamId, organization);
185+
if (team.dateArchived) throw new HttpException(400, 'Cannot edit the slack id of an archived team');
186+
187+
if (
188+
!(
189+
(await userHasPermission(updater.userId, organization.organizationId, isAdmin)) ||
190+
updater.userId === team.head.userId
191+
)
192+
)
193+
throw new AccessDeniedException('you must be an admin or the team head to update the slack id!');
194+
195+
const updatedTeam = await prisma.team.update({
196+
where: { teamId },
197+
data: {
198+
slackId
199+
},
200+
...getTeamPreviewQueryArgs(organization.organizationId)
201+
});
202+
203+
return teamPreviewTransformer(updatedTeam);
204+
}
205+
175206
/**
176207
* Update the team's head of the given team to the given user
177208
* @param submitter The submitter of this request

src/frontend/src/apis/teams.api.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ export const setTeamDescription = (id: string, description: string) => {
4444
});
4545
};
4646

47+
export const setTeamSlackId = (id: string, slackId: string) => {
48+
return axios.post<TeamPreview>(apiUrls.teamsSetSlackId(id), {
49+
slackId
50+
});
51+
};
52+
4753
export const setTeamHead = (id: string, userId: string) => {
4854
return axios.post<Team>(apiUrls.teamsSetHead(id), {
4955
userId

src/frontend/src/hooks/teams.hooks.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import {
1717
archiveTeam,
1818
getAllArchivedTeams,
1919
getMyTeamsWorkpackages,
20-
getUsersTeams
20+
getUsersTeams,
21+
setTeamSlackId
2122
} from '../apis/teams.api';
2223

2324
export interface CreateTeamPayload {
@@ -120,6 +121,22 @@ export const useEditTeamDescription = (teamId: string) => {
120121
);
121122
};
122123

124+
export const useEditTeamSlackId = (teamId: string) => {
125+
const queryClient = useQueryClient();
126+
return useMutation<TeamPreview, Error, string>(
127+
['teams', 'edit'],
128+
async (slackId: string) => {
129+
const { data } = await setTeamSlackId(teamId, slackId);
130+
return data;
131+
},
132+
{
133+
onSuccess: () => {
134+
queryClient.invalidateQueries(['teams']);
135+
}
136+
}
137+
);
138+
};
139+
123140
export const useDeleteTeam = () => {
124141
const queryClient = useQueryClient();
125142
return useMutation<{ message: string }, Error, string>(

src/frontend/src/pages/AdminToolsPage/AdminToolsSlackIds.tsx

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import { NERButton } from '../../components/NERButton';
7-
import { Box, Link, TextField, Typography } from '@mui/material';
7+
import { Box, Grid, Link, TableCell, TableRow, TextField, Typography } from '@mui/material';
88
import { useState } from 'react';
99
import { useToast } from '../../hooks/toasts.hooks';
1010
import {
@@ -14,8 +14,12 @@ import {
1414
} from '../../hooks/organizations.hooks';
1515
import LoadingIndicator from '../../components/LoadingIndicator';
1616
import ErrorPage from '../ErrorPage';
17-
import { Organization } from 'shared';
17+
import { Organization, TeamPreview } from 'shared';
1818
import HelpIcon from '@mui/icons-material/Help';
19+
import { useAllTeams } from '../../hooks/teams.hooks';
20+
import { fullNamePipe } from '../../utils/pipes';
21+
import AdminToolTable from './AdminToolTable';
22+
import EditTeamSlackIdFormModal from './TeamConfig/EditTeamSlackIdFormModal';
1923

2024
interface AdminToolsWorkspaceIdViewProps {
2125
organization: Organization;
@@ -37,9 +41,31 @@ const AdminToolsSlackIdsView: React.FC<AdminToolsWorkspaceIdViewProps> = ({ orga
3741
const [sponsorshipChannelId, setSponsorshipChannelId] = useState(
3842
organization.sponsorshipNotificationsSlackChannelId ?? ''
3943
);
44+
const { data: allTeams, isLoading: allTeamsIsLoading, isError: allTeamsIsError, error: allTeamsError } = useAllTeams();
45+
const [clickedTeam, setClickedTeam] = useState<TeamPreview>();
46+
const [showEditModal, setShowEditModal] = useState<boolean>(false);
47+
48+
if (!allTeams || allTeamsIsLoading) return <LoadingIndicator />;
49+
50+
if (allTeamsIsError) {
51+
return <ErrorPage message={allTeamsError.message} />;
52+
}
4053

4154
if (isLoading) return <LoadingIndicator />;
4255

56+
const teamTableRows = allTeams.map((team, index) => (
57+
<TableRow
58+
onClick={() => {
59+
setClickedTeam(team);
60+
setShowEditModal(true);
61+
}}
62+
sx={{ color: 'inherit', textDecoration: 'none' }}
63+
>
64+
<TableCell sx={{ borderBottom: index === allTeams.length - 1 ? 'none' : 'default' }}>{team.teamName}</TableCell>
65+
<TableCell sx={{ borderBottom: index === allTeams.length - 1 ? 'none' : 'default' }}>{team.slackId}</TableCell>
66+
</TableRow>
67+
));
68+
4369
const handleSubmitWorkspaceId = async () => {
4470
try {
4571
await setWorkspaceIdMutateAsync(workspaceId);
@@ -63,7 +89,7 @@ const AdminToolsSlackIdsView: React.FC<AdminToolsWorkspaceIdViewProps> = ({ orga
6389
};
6490

6591
return (
66-
<Box>
92+
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
6793
<Typography variant="h5" gutterBottom color={'#ef4345'} borderBottom={1} borderColor={'white'}>
6894
{organization.name} Slack Workspace & Channel Ids
6995
</Typography>
@@ -113,6 +139,25 @@ const AdminToolsSlackIdsView: React.FC<AdminToolsWorkspaceIdViewProps> = ({ orga
113139
</NERButton>
114140
</Box>
115141
</Box>
142+
<Box>
143+
<Typography variant="h5" gutterBottom borderBottom={1} color="#ef4345" borderColor={'white'}>
144+
Team Slack IDs
145+
</Typography>
146+
<Grid container columnSpacing={2}>
147+
<Grid item xs={12} md={6} sx={{ marginTop: '24px' }}>
148+
<AdminToolTable columns={[{ name: 'Team Name' }, { name: 'Slack Channel ID' }]} rows={teamTableRows} />
149+
</Grid>
150+
</Grid>
151+
{clickedTeam && (
152+
<EditTeamSlackIdFormModal
153+
open={!!clickedTeam}
154+
handleClose={() => {
155+
setClickedTeam(undefined);
156+
}}
157+
team={clickedTeam}
158+
/>
159+
)}
160+
</Box>
116161
</Box>
117162
);
118163
};
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { useForm } from 'react-hook-form';
2+
import NERFormModal from '../../../components/NERFormModal';
3+
import { FormControl, FormLabel, FormHelperText, Tooltip, Typography, Link } from '@mui/material';
4+
import ReactHookTextField from '../../../components/ReactHookTextField';
5+
import * as yup from 'yup';
6+
import { yupResolver } from '@hookform/resolvers/yup';
7+
import { Box } from '@mui/system';
8+
import HelpIcon from '@mui/icons-material/Help';
9+
import { TeamPreview, TeamType } from 'shared';
10+
import { CreateTeamTypePayload } from '../../../hooks/team-types.hooks';
11+
import useFormPersist from 'react-hook-form-persist';
12+
import { FormStorageKey } from '../../../utils/form';
13+
import { useEffect } from 'react';
14+
import { useEditTeamSlackId } from '../../../hooks/teams.hooks';
15+
import LoadingIndicator from '../../../components/LoadingIndicator';
16+
import { useToast } from '../../../hooks/toasts.hooks';
17+
18+
interface EditTeamSlackIdFormModalProps {
19+
open: boolean;
20+
handleClose: () => void;
21+
team: TeamPreview;
22+
}
23+
24+
const schema = yup.object().shape({
25+
slackId: yup.string().required('Slack id is required')
26+
});
27+
28+
const EditTeamSlackIdFormModal: React.FC<EditTeamSlackIdFormModalProps> = ({ open, handleClose, team }) => {
29+
const { isLoading, mutateAsync } = useEditTeamSlackId(team.teamId);
30+
31+
const toast = useToast();
32+
33+
const onFormSubmit = async (data: { slackId: string }) => {
34+
try {
35+
await mutateAsync(data.slackId);
36+
toast.success('Slack id updated successfully!');
37+
} catch (e) {
38+
if (e instanceof Error) {
39+
toast.error(e.message);
40+
}
41+
}
42+
handleClose();
43+
};
44+
45+
const {
46+
handleSubmit,
47+
control,
48+
reset,
49+
formState: { errors }
50+
} = useForm({
51+
resolver: yupResolver(schema),
52+
defaultValues: {
53+
slackId: team.slackId
54+
}
55+
});
56+
57+
useEffect(() => {
58+
reset({
59+
slackId: team.slackId
60+
});
61+
}, [team, reset]);
62+
63+
if (isLoading) {
64+
return <LoadingIndicator />;
65+
}
66+
67+
return (
68+
<NERFormModal
69+
open={open}
70+
onHide={handleClose}
71+
title={`${team.teamName} Slack ID`}
72+
reset={() => reset({ slackId: team.slackId })}
73+
handleUseFormSubmit={handleSubmit}
74+
onFormSubmit={onFormSubmit}
75+
formId="team-slackId-form"
76+
showCloseButton
77+
>
78+
<FormControl>
79+
<FormLabel>Slack ID</FormLabel>
80+
<ReactHookTextField name="slackId" control={control} sx={{ width: 1 }} />
81+
<FormHelperText error>{errors.slackId?.message}</FormHelperText>
82+
</FormControl>
83+
</NERFormModal>
84+
);
85+
};
86+
87+
export default EditTeamSlackIdFormModal;

src/frontend/src/utils/urls.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ const completeOnboarding = () => `${teams()}/teamType/complete-onboarding`;
137137
const teamsSetHead = (id: string) => `${teamsById(id)}/set-head`;
138138
const teamsArchive = (id: string) => `${teamsById(id)}/archive`;
139139
const teamsSetDescription = (id: string) => `${teamsById(id)}/edit-description`;
140+
const teamsSetSlackId = (id: string) => `${teamsById(id)}/edit-slackid`;
140141
const teamsCreate = () => `${teams()}/create`;
141142
const teamsSetLeads = (id: string) => `${teamsById(id)}/set-leads`;
142143
const usersTeams = () => `${teams()}/users-teams`;
@@ -515,6 +516,7 @@ export const apiUrls = {
515516
teamsArchive,
516517
teamsSetHead,
517518
teamsSetDescription,
519+
teamsSetSlackId,
518520
teamsCreate,
519521
teamsSetLeads,
520522
usersTeams,

src/shared/src/types/team-types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ export interface Team {
2121
teamType?: TeamType;
2222
}
2323

24-
export type TeamPreview = Pick<Team, 'teamId' | 'teamName' | 'members' | 'head' | 'leads' | 'teamType'>;
24+
export type TeamPreview = Pick<Team, 'teamId' | 'teamName' | 'members' | 'head' | 'leads' | 'teamType' | 'slackId'>;

0 commit comments

Comments
 (0)