Skip to content

Commit

Permalink
Merge branch 'dev' into HAI-1947
Browse files Browse the repository at this point in the history
  • Loading branch information
markohaarni committed Oct 6, 2023
2 parents 6a749bd + 65ac60a commit 9355206
Show file tree
Hide file tree
Showing 9 changed files with 338 additions and 43 deletions.
31 changes: 28 additions & 3 deletions src/domain/hanke/accessRights/AccessRightsView.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,43 @@

.table > div {
overflow-x: initial;
display: none;

@include respond-below(m) {
display: none;
@include respond-above(l) {
display: block;
}
}

.userCards {
@include respond-above(m) {
@include respond-above(l) {
display: none;
}
}

.accessRightSelect {
min-width: auto;

@include respond-above(m) {
min-width: 250px;
}

@include respond-above(l) {
min-width: 345px;
}
}

.invitationSendButtonContainer {
min-width: auto;

@include respond-above(xl) {
min-width: 300px;
}
}

.invitationSendButton {
vertical-align: middle;
}

.pagination {
margin-top: var(--spacing-xs);
margin-bottom: var(--spacing-s);
Expand Down
93 changes: 93 additions & 0 deletions src/domain/hanke/accessRights/AccessRightsView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import AccessRightsViewContainer from './AccessRightsViewContainer';
import { server } from '../../mocks/test-server';
import usersData from '../../mocks/data/users-data.json';
import { SignedInUser } from '../hankeUsers/hankeUser';
import * as hankeUsersApi from '../../hanke/hankeUsers/hankeUsersApi';

jest.setTimeout(40000);

Expand Down Expand Up @@ -302,3 +303,95 @@ test('Should not be able to remove all rights if user does not have all rights',

expect(screen.getByTestId('kayttooikeustaso-3').querySelector('button')).toBeDisabled();
});

test('Should show Käyttäjä tunnistautunut text for correct users', async () => {
render(<AccessRightsViewContainer hankeTunnus="HAI22-2" />);

await waitForLoadingToFinish();

expect(screen.getByTestId('tunnistautunut-0')).toHaveTextContent('Käyttäjä tunnistautunut');
expect(screen.getByTestId('tunnistautunut-1')).not.toHaveTextContent('Käyttäjä tunnistautunut');
expect(screen.getByTestId('tunnistautunut-2')).toHaveTextContent('Käyttäjä tunnistautunut');
expect(screen.getByTestId('tunnistautunut-6')).toHaveTextContent('Käyttäjä tunnistautunut');
expect(screen.getByTestId('tunnistautunut-7')).toHaveTextContent('Käyttäjä tunnistautunut');
expect(screen.getByTestId('tunnistautunut-8')).toHaveTextContent('Käyttäjä tunnistautunut');
});

test('Should send invitation to user when cliking the Lähetä kutsulinkki uudelleen button', async () => {
const { user } = render(<AccessRightsViewContainer hankeTunnus="HAI22-2" />);

await waitForLoadingToFinish();

const invitationButton = screen.getAllByRole('button', {
name: 'Lähetä kutsulinkki uudelleen',
})[0];
await user.click(invitationButton);

await waitFor(() => {
expect(
screen.queryByText('Kutsulinkki lähetetty osoitteeseen [email protected].'),
).toBeInTheDocument();
});
expect(invitationButton).toHaveTextContent('Kutsulinkki lähetetty');
expect(invitationButton).toBeDisabled();
});

test('Should not send multiple requests when clicking the Lähetä kutsulinkki uudelleen button many times', async () => {
const sendInvitation = jest.spyOn(hankeUsersApi, 'resendInvitation');
const { user } = render(<AccessRightsViewContainer hankeTunnus="HAI22-2" />);

await waitForLoadingToFinish();

const invitationButton = screen.getAllByRole('button', {
name: 'Lähetä kutsulinkki uudelleen',
})[0];
await user.click(invitationButton);
await user.click(invitationButton);
await user.click(invitationButton);

expect(sendInvitation).toHaveBeenCalledTimes(1);

sendInvitation.mockRestore();
});

test('Should show error notification if sending invitation fails', async () => {
server.use(
rest.post('/api/kayttajat/:kayttajaId/kutsu', async (req, res, ctx) => {
return res(ctx.status(500), ctx.json({ errorMessage: 'Failed for testing purposes' }));
}),
);

const { user } = render(<AccessRightsViewContainer hankeTunnus="HAI22-2" />);

await waitForLoadingToFinish();

const invitationButton = screen.getAllByRole('button', {
name: 'Lähetä kutsulinkki uudelleen',
})[1];
await user.click(invitationButton);

expect(screen.queryByText('Virhe linkin lähettämisessä')).toBeInTheDocument();
});

test('Should not show invitation buttons if user does not have permission to send invitation', async () => {
server.use(
rest.get('/api/hankkeet/:hankeTunnus/whoami', async (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json<SignedInUser>(
getSignedInUser({ kayttooikeustaso: 'KATSELUOIKEUS', kayttooikeudet: ['VIEW'] }),
),
);
}),
);

render(<AccessRightsViewContainer hankeTunnus="HAI22-2" />);

await waitForLoadingToFinish();

expect(
screen.queryAllByRole('button', {
name: 'Lähetä kutsulinkki uudelleen',
}),
).toHaveLength(0);
});
116 changes: 110 additions & 6 deletions src/domain/hanke/accessRights/AccessRightsView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import {
Accordion,
Button,
Expand All @@ -10,9 +10,11 @@ import {
Select,
Table,
Link as HDSLink,
IconEnvelope,
IconCheckCircleFill,
} from 'hds-react';
import { cloneDeep } from 'lodash';
import { Flex } from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/react';
import { useMutation, useQueryClient } from 'react-query';
import {
Column,
Expand All @@ -31,7 +33,7 @@ import styles from './AccessRightsView.module.scss';
import { Language } from '../../../common/types/language';
import { HankeUser, AccessRightLevel, SignedInUser } from '../hankeUsers/hankeUser';
import useHankeViewPath from '../hooks/useHankeViewPath';
import { updateHankeUsers } from '../hankeUsers/hankeUsersApi';
import { resendInvitation, updateHankeUsers } from '../hankeUsers/hankeUsersApi';
import Container from '../../../common/components/container/Container';
import UserCard from './UserCard';

Expand Down Expand Up @@ -59,6 +61,7 @@ type AccessRightLevelOption = {
const NAME_KEY = 'nimi';
const EMAIL_KEY = 'sahkoposti';
const ACCESS_RIGHT_LEVEL_KEY = 'kayttooikeustaso';
const USER_IDENTIFIED_KEY = 'tunnistautunut';

function AccessRightsView({ hankeUsers, hankeTunnus, hankeName, signedInUser }: Props) {
const queryClient = useQueryClient();
Expand All @@ -69,7 +72,12 @@ function AccessRightsView({ hankeUsers, hankeTunnus, hankeName, signedInUser }:
const saveButtonDisabled = modifiedUsers.length === 0;

const columns: Column<HankeUser>[] = useMemo(() => {
return [{ accessor: NAME_KEY }, { accessor: EMAIL_KEY }, { accessor: ACCESS_RIGHT_LEVEL_KEY }];
return [
{ accessor: NAME_KEY },
{ accessor: EMAIL_KEY },
{ accessor: ACCESS_RIGHT_LEVEL_KEY },
{ accessor: USER_IDENTIFIED_KEY },
];
}, []);

const {
Expand Down Expand Up @@ -102,6 +110,9 @@ function AccessRightsView({ hankeUsers, hankeTunnus, hankeName, signedInUser }:
}, [hankeUsers]);

const updateUsersMutation = useMutation(updateHankeUsers);
const resendInvitationMutation = useMutation(resendInvitation);
// List of user ids for tracking which users have been sent the invitation link
const linksSentTo = useRef<string[]>([]);

const [usersSearchValue, setUsersSearchValue] = useState('');

Expand Down Expand Up @@ -177,10 +188,56 @@ function AccessRightsView({ hankeUsers, hankeTunnus, hankeName, signedInUser }:
option.value === 'KAIKKI_OIKEUDET' && signedInUser?.kayttooikeustaso !== 'KAIKKI_OIKEUDET'
}
disabled={isDisabled}
className={styles.accessRightSelect}
/>
);
}

function getInvitationResendButton(args: HankeUser) {
if (args.tunnistautunut) {
return (
<Flex alignItems="baseline">
<IconCheckCircleFill
color="var(--color-success)"
style={{ marginRight: 'var(--spacing-3-xs)', alignSelf: 'center' }}
aria-hidden
/>
<p>{t('hankeUsers:userIdentified')}</p>
</Flex>
);
}

const linkSent = linksSentTo.current.includes(args.id);
const buttonText = linkSent
? t('hankeUsers:buttons:invitationSent')
: t('hankeUsers:buttons:resendInvitation');
const isButtonLoading =
resendInvitationMutation.isLoading && resendInvitationMutation.variables === args.id;

function sendInvitation() {
resendInvitationMutation.mutate(args.id, {
onSuccess(data) {
linksSentTo.current.push(data);
},
});
}

return (
<div className={styles.invitationSendButtonContainer}>
<Button
variant="secondary"
className={styles.invitationSendButton}
iconLeft={<IconEnvelope aria-hidden />}
onClick={sendInvitation}
disabled={linkSent}
isLoading={isButtonLoading}
>
{buttonText}
</Button>
</div>
);
}

function updateUsers() {
const users: Pick<HankeUser, 'id' | 'kayttooikeustaso'>[] = modifiedUsers.map((user) => {
return {
Expand Down Expand Up @@ -218,6 +275,14 @@ function AccessRightsView({ hankeUsers, hankeTunnus, hankeName, signedInUser }:
},
];

if (signedInUser?.kayttooikeudet.includes('RESEND_INVITATION')) {
tableCols.push({
headerName: '',
key: USER_IDENTIFIED_KEY,
transform: getInvitationResendButton,
});
}

return (
<article className={styles.container}>
<header className={styles.header}>
Expand Down Expand Up @@ -309,7 +374,10 @@ function AccessRightsView({ hankeUsers, hankeTunnus, hankeName, signedInUser }:
{page.map((row) => {
return (
<UserCard key={row.original.id} user={row.original}>
{getAccessRightSelect(row.original)}
<Box marginBottom="var(--spacing-s)">{getAccessRightSelect(row.original)}</Box>
{signedInUser?.kayttooikeudet.includes('RESEND_INVITATION')
? getInvitationResendButton(row.original)
: null}
</UserCard>
);
})}
Expand Down Expand Up @@ -356,7 +424,7 @@ function AccessRightsView({ hankeUsers, hankeTunnus, hankeName, signedInUser }:
dismissible
type="error"
label={t('hankeUsers:notifications:rightsUpdatedErrorLabel')}
closeButtonLabelText={t('hankeUsers:notifications:rightsUpdatedErrorLabel')}
closeButtonLabelText={t('common:components:notification:closeButtonLabelText')}
onClose={() => updateUsersMutation.reset()}
>
<Trans i18nKey="hankeUsers:notifications:rightsUpdatedErrorText">
Expand All @@ -368,6 +436,42 @@ function AccessRightsView({ hankeUsers, hankeTunnus, hankeName, signedInUser }:
</Trans>
</Notification>
)}

{resendInvitationMutation.isSuccess && (
<Notification
position="top-right"
dismissible
autoClose
autoCloseDuration={4000}
type="success"
label={t('hankeUsers:notifications:invitationSentSuccessLabel')}
closeButtonLabelText={t('common:components:notification:closeButtonLabelText')}
onClose={() => resendInvitationMutation.reset()}
>
{t('hankeUsers:notifications:invitationSentSuccessText', {
email: hankeUsers.find((user) => user.id === resendInvitationMutation.data)
?.sahkoposti,
})}
</Notification>
)}
{resendInvitationMutation.isError && (
<Notification
position="top-right"
dismissible
type="error"
label={t('hankeUsers:notifications:invitationSentErrorLabel')}
closeButtonLabelText={t('common:components:notification:closeButtonLabelText')}
onClose={() => resendInvitationMutation.reset()}
>
<Trans i18nKey="hankeUsers:notifications:invitationSentErrorText">
<p>
Kutsulinkin lähettämisessä tapahtui virhe. Yritä myöhemmin uudelleen tai ota
yhteyttä Haitattoman tekniseen tukeen sähköpostiosoitteessa
<HDSLink href="mailto:[email protected]">[email protected]</HDSLink>.
</p>
</Trans>
</Notification>
)}
</Container>
</article>
);
Expand Down
1 change: 1 addition & 0 deletions src/domain/hanke/hankeUsers/hankeUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export enum Rights {
MODIFY_DELETE_PERMISSIONS = 'MODIFY_DELETE_PERMISSIONS',
EDIT_APPLICATIONS = 'EDIT_APPLICATIONS',
MODIFY_APPLICATION_PERMISSIONS = 'MODIFY_APPLICATION_PERMISSIONS',
RESEND_INVITATION = 'RESEND_INVITATION',
}

export type UserRights = Array<keyof typeof Rights>;
Expand Down
5 changes: 5 additions & 0 deletions src/domain/hanke/hankeUsers/hankeUsersApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,8 @@ export async function identifyUser(id: string) {
const { data } = await api.post<IdentificationResponse>('kayttajat', { tunniste: id });
return data;
}

export async function resendInvitation(kayttajaId: string) {
await api.post(`kayttajat/${kayttajaId}/kutsu`);
return kayttajaId;
}
Loading

0 comments on commit 9355206

Please sign in to comment.