From 920f01ea7528ed5b98e0f271d5bdb4f67179f342 Mon Sep 17 00:00:00 2001 From: "Tore.Brede" <tore.brede@uib.no> Date: Fri, 29 Jul 2022 09:24:20 +0200 Subject: [PATCH] GREG-255: Doing some changes on the edit role screen --- frontend/src/interfaces/index.ts | 2 + .../guest/guestRoleInfo/index.test.tsx | 58 ++++++++++++++++--- .../sponsor/guest/guestRoleInfo/index.tsx | 32 +++++++--- frontend/src/utils/index.ts | 1 + gregui/api/serializers/role.py | 5 ++ gregui/tests/api/serializers/test_guest.py | 1 + 6 files changed, 84 insertions(+), 15 deletions(-) diff --git a/frontend/src/interfaces/index.ts b/frontend/src/interfaces/index.ts index 4e5766da..311b4170 100644 --- a/frontend/src/interfaces/index.ts +++ b/frontend/src/interfaces/index.ts @@ -56,6 +56,7 @@ export type Role = { contact_person_unit: string | null comments: string | null sponsor_name: string + ou_id: number } export type FetchedRole = { @@ -70,6 +71,7 @@ export type FetchedRole = { contact_person_unit: string | null comments: string | null sponsor_name: string + ou_id: number } export type ConsentType = { diff --git a/frontend/src/routes/sponsor/guest/guestRoleInfo/index.test.tsx b/frontend/src/routes/sponsor/guest/guestRoleInfo/index.test.tsx index 5556a8e7..4ef2255c 100644 --- a/frontend/src/routes/sponsor/guest/guestRoleInfo/index.test.tsx +++ b/frontend/src/routes/sponsor/guest/guestRoleInfo/index.test.tsx @@ -5,7 +5,8 @@ import { LocalizationProvider } from '@mui/lab' // eslint-disable-next-line import/no-extraneous-dependencies import { Guest } from 'interfaces' import parse from 'date-fns/parse' -import { BrowserRouter } from 'react-router-dom' +import { MemoryRouter, Route, Routes } from 'react-router-dom' +import { addDays } from 'date-fns/fp' import GuestRoleInfo from './index' import { waitFor } from '../../../../test-utils' @@ -30,11 +31,12 @@ const guest: Guest = { name_en: 'Guest role', name_nb: 'Gjesterolle', start_date: parse('2021-08-10', 'yyyy-MM-dd', new Date()), - end_date: parse('2021-08-16', 'yyyy-MM-dd', new Date()), + end_date: addDays(10)(new Date()), contact_person_unit: 'Test contact person', max_days: 100, comments: 'Test comment', sponsor_name: 'Test', + ou_id: 1, }, { id: '201', @@ -43,22 +45,41 @@ const guest: Guest = { name_en: 'Test role', name_nb: 'Testrolle', start_date: parse('2021-09-06', 'yyyy-MM-dd', new Date()), - end_date: parse('2021-09-20', 'yyyy-MM-dd', new Date()), + end_date: addDays(10)(new Date()), contact_person_unit: 'Test contact person', max_days: 100, comments: 'Test comment', sponsor_name: 'Test', + ou_id: 2, }, ], } -test('Button state correct on load', async () => { +// Mock useOus so that it looks like the user is a sponsor for +// the unit with ID 1 +jest.mock('hooks/useOus', () => () => ({ + ous: [ + { + id: 1, + nb: 'Test', + en: 'Test2', + }, + ], + loading: false, +})) + +test('End now enabled for role that is not expired and in unit where user is host', async () => { render( - <BrowserRouter> + <MemoryRouter initialEntries={['/sponsor/guest/1/roles/200']}> <LocalizationProvider dateAdapter={AdapterDateFns}> - <GuestRoleInfo guest={guest} reloadGuest={() => {}} /> + <Routes> + <Route + path="sponsor/guest/:pid/roles/:id" + element={<GuestRoleInfo guest={guest} reloadGuest={() => {}} />} + /> + </Routes> </LocalizationProvider> - </BrowserRouter> + </MemoryRouter> ) await waitFor( @@ -69,3 +90,26 @@ test('Button state correct on load', async () => { { timeout: 5000 } ) }, 10000) + +test('End now disabled for role that is not expired and in unit where user is not host', async () => { + render( + <MemoryRouter initialEntries={['/sponsor/guest/1/roles/201']}> + <LocalizationProvider dateAdapter={AdapterDateFns}> + <Routes> + <Route + path="sponsor/guest/:pid/roles/:id" + element={<GuestRoleInfo guest={guest} reloadGuest={() => {}} />} + /> + </Routes> + </LocalizationProvider> + </MemoryRouter> + ) + + await waitFor( + () => { + expect(screen.getByText('button.save')).toBeDisabled() + expect(screen.getByText('sponsor.endNow')).toBeDisabled() + }, + { timeout: 5000 } + ) +}, 10000) diff --git a/frontend/src/routes/sponsor/guest/guestRoleInfo/index.tsx b/frontend/src/routes/sponsor/guest/guestRoleInfo/index.tsx index c8293c72..9bcba27e 100644 --- a/frontend/src/routes/sponsor/guest/guestRoleInfo/index.tsx +++ b/frontend/src/routes/sponsor/guest/guestRoleInfo/index.tsx @@ -18,6 +18,7 @@ import { Controller, SubmitHandler, useForm } from 'react-hook-form' import { getRoleName, getRoleOuName, submitJsonOpts } from 'utils' import { useFeatureContext } from 'contexts/featureContext' import ConfirmDialog from '../../../../components/confirmDialog' +import useOus from '../../../../hooks/useOus' interface GuestRoleInfoProps { guest: Guest @@ -101,6 +102,7 @@ export default function GuestRoleInfo({ reloadGuest, }: GuestRoleInfoProps) { const { pid, id } = useParams<GuestRoleInfoParams>() + const { ous } = useOus() const [t] = useTranslation('common') const { displayContactAtUnit, displayComment } = useFeatureContext() const navigate = useNavigate() @@ -116,6 +118,7 @@ export default function GuestRoleInfo({ contact_person_unit: null, comments: null, sponsor_name: '', + ou_id: -1, }) // Prepare min and max date values const today = new Date() @@ -141,6 +144,13 @@ export default function GuestRoleInfo({ formState: { isDirty, isValid }, } = useForm<RoleFormData>({ mode: 'onChange' }) + // A sponsor can only edit roles belonging to departments where he is a sponsor. + // Look at the unit where the role is registered and see if that unit is present + // in the list of units where a sponsor can assign roles to determine if + // he can edit this role + const allowEdit = + ous === undefined ? false : ous.some((value) => value.id === role.ou_id) + // Submit function for the save button const submit: SubmitHandler<RoleFormData> = (data) => { const payload: { start_date?: string; end_date: string } = { @@ -198,12 +208,16 @@ export default function GuestRoleInfo({ to={`/sponsor/guest/${pid}`} name={`${guest.first} ${guest.last}`} /> - <Typography sx={{ marginBottom: '1rem' }} variant="h2"> - {t('sponsor.roleInfoHead')} - </Typography> - <Typography sx={{ marginBottom: '1rem' }} variant="body1"> - {t('sponsor.roleInfoText')} - </Typography> + {allowEdit && ( + <> + <Typography sx={{ marginBottom: '1rem' }} variant="h2"> + {t('sponsor.roleInfoHead')} + </Typography> + <Typography sx={{ marginBottom: '1rem' }} variant="body1"> + {t('sponsor.roleInfoText')} + </Typography> + </> + )} <form onSubmit={onSubmit}> <TableContainer> <Table sx={{ minWidth: 650 }} aria-label="simple table"> @@ -239,6 +253,7 @@ export default function GuestRoleInfo({ inputFormat="yyyy-MM-dd" onChange={onChange} renderInput={(params) => <TextField {...params} />} + disabled={!allowEdit} /> )} /> @@ -257,6 +272,7 @@ export default function GuestRoleInfo({ inputFormat="yyyy-MM-dd" onChange={onChange} renderInput={(params) => <TextField {...params} />} + disabled={!allowEdit} /> )} /> @@ -301,7 +317,7 @@ export default function GuestRoleInfo({ variant="contained" color="success" type="submit" - disabled={!isDirty || !isValid} + disabled={!isDirty || !isValid || !allowEdit} > {t('button.save')} </Button>{' '} @@ -309,7 +325,7 @@ export default function GuestRoleInfo({ <Button aria-label={t('sponsor.endNow')} color="primary" - disabled={role.end_date < today} + disabled={role.end_date < today || !allowEdit} onClick={() => setShowEndRoleConfirmationDialog(true)} > {t('sponsor.endNow')} diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts index dd3d3fe3..645504e5 100644 --- a/frontend/src/utils/index.ts +++ b/frontend/src/utils/index.ts @@ -150,6 +150,7 @@ export function parseRole(role: FetchedRole): Role { contact_person_unit: role.contact_person_unit, comments: role.comments, sponsor_name: role.sponsor_name, + ou_id: role.ou_id } } diff --git a/gregui/api/serializers/role.py b/gregui/api/serializers/role.py index 4353e4b5..c80bad5e 100644 --- a/gregui/api/serializers/role.py +++ b/gregui/api/serializers/role.py @@ -104,6 +104,7 @@ class ExtendedRoleSerializer(serializers.ModelSerializer): ou_en = SerializerMethodField(source="orgunit") max_days = SerializerMethodField(source="type") sponsor_name = SerializerMethodField(source="sponsor") + ou_id = SerializerMethodField(source="orgunit") def get_name_nb(self, obj): return obj.type.name_nb @@ -123,6 +124,9 @@ class ExtendedRoleSerializer(serializers.ModelSerializer): def get_sponsor_name(self, obj): return f"{obj.sponsor.first_name} {obj.sponsor.last_name}" + def get_ou_id(self, obj): + return obj.orgunit.id + class Meta: model = Role fields = [ @@ -137,5 +141,6 @@ class ExtendedRoleSerializer(serializers.ModelSerializer): "contact_person_unit", "comments", "sponsor_name", + "ou_id", ] read_only_fields = ["contact_person_unit", "sponsor_name"] diff --git a/gregui/tests/api/serializers/test_guest.py b/gregui/tests/api/serializers/test_guest.py index ed473128..6798cc40 100644 --- a/gregui/tests/api/serializers/test_guest.py +++ b/gregui/tests/api/serializers/test_guest.py @@ -35,6 +35,7 @@ def test_serialize_guest(invited_person): "contact_person_unit": "", "comments": "", "sponsor_name": "Sponsor Bar", + "ou_id": 1, }, ], "verified": False, -- GitLab