Skip to content

Commit

Permalink
HAI-1945 Don't show edit hanke link buttons in hanke list if user doe…
Browse files Browse the repository at this point in the history
…s not have edit rights (#379)

Edit hanke link button (pen icon) is hidden in hanke list if user
does not have edit rights for that hanke.
  • Loading branch information
markohaarni authored Oct 6, 2023
1 parent 8e9a64d commit 3496cc8
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 45 deletions.
4 changes: 2 additions & 2 deletions src/domain/hanke/accessRights/AccessRightsViewContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import ErrorLoadingText from '../../../common/components/errorLoadingText/ErrorL
import { useHankeUsers } from '../hankeUsers/hooks/useHankeUsers';
import useHanke from '../hooks/useHanke';
import AccessRightsView from './AccessRightsView';
import useSignedInUserRights from '../hankeUsers/hooks/useUserRights';
import useUserRightsForHanke from '../hankeUsers/hooks/useUserRightsForHanke';

type Props = {
hankeTunnus: string;
Expand All @@ -17,7 +17,7 @@ function AccessRightsViewContainer({ hankeTunnus }: Props) {
const { t } = useTranslation();
const { data: hankeUsers, isLoading, isError, error } = useHankeUsers(hankeTunnus);
const { data: hankeData } = useHanke(hankeTunnus);
const { data: signedInUser } = useSignedInUserRights(hankeTunnus);
const { data: signedInUser } = useUserRightsForHanke(hankeTunnus);

if (isLoading) {
return (
Expand Down
43 changes: 43 additions & 0 deletions src/domain/hanke/hankeUsers/UserRightsCheck.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import { rest } from 'msw';
import { render, screen, waitFor } from '../../../testUtils/render';
import { server } from '../../mocks/test-server';
import { SignedInUser } from './hankeUser';
import UserRightsCheck from './UserRightsCheck';

test('Should render children if user has required right', async () => {
render(
<UserRightsCheck requiredRight="EDIT" hankeTunnus="HAI22-2">
<p>Children</p>
</UserRightsCheck>,
);

await waitFor(() => {
expect(screen.getByText('Children')).toBeInTheDocument();
});
});

test('Should not render children if user does not have required right', async () => {
server.use(
rest.get('/api/hankkeet/:hankeTunnus/whoami', async (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json<SignedInUser>({
hankeKayttajaId: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
kayttooikeustaso: 'KATSELUOIKEUS',
kayttooikeudet: ['VIEW'],
}),
);
}),
);

render(
<UserRightsCheck requiredRight="EDIT" hankeTunnus="HAI22-2">
<p>Children</p>
</UserRightsCheck>,
);

await waitFor(() => {
expect(screen.queryByText('Children')).not.toBeInTheDocument();
});
});
29 changes: 29 additions & 0 deletions src/domain/hanke/hankeUsers/UserRightsCheck.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import useUserRightsForHanke from './hooks/useUserRightsForHanke';
import { Rights } from './hankeUser';

/**
* Check that user has required rights.
* If they have, render children.
*/
function UserRightsCheck({
requiredRight,
hankeTunnus,
children,
}: {
/** User right that is required to render children */
requiredRight: keyof typeof Rights;
/** hankeTunnus of the hanke that the right is required for */
hankeTunnus: string;
children: React.ReactElement;
}) {
const { data: signedInUser } = useUserRightsForHanke(hankeTunnus);

if (signedInUser?.kayttooikeudet.includes(requiredRight)) {
return children;
}

return null;
}

export default UserRightsCheck;
24 changes: 13 additions & 11 deletions src/domain/hanke/hankeUsers/hankeUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,19 @@ export type HankeUser = {
tunnistautunut: boolean;
};

export type UserRights = Array<
| 'VIEW'
| 'MODIFY_VIEW_PERMISSIONS'
| 'EDIT'
| 'MODIFY_EDIT_PERMISSIONS'
| 'DELETE'
| 'MODIFY_DELETE_PERMISSIONS'
| 'EDIT_APPLICATIONS'
| 'MODIFY_APPLICATION_PERMISSIONS'
| 'RESEND_INVITATION'
>;
export enum Rights {
VIEW = 'VIEW',
MODIFY_VIEW_PERMISSIONS = 'MODIFY_VIEW_PERMISSIONS',
EDIT = 'EDIT',
MODIFY_EDIT_PERMISSIONS = 'MODIFY_EDIT_PERMISSIONS',
DELETE = 'DELETE',
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>;

export type SignedInUser = {
hankeKayttajaId: string;
Expand Down
4 changes: 2 additions & 2 deletions src/domain/hanke/hankeUsers/hankeUsersApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export async function updateHankeUsers({
return data;
}

// Get user id and rights of the signed in user
export async function getSignedInUser(hankeTunnus?: string): Promise<SignedInUser> {
// Get user id and rights of the signed in user for a hanke
export async function getSignedInUserForHanke(hankeTunnus?: string): Promise<SignedInUser> {
const { data } = await api.get<SignedInUser>(`hankkeet/${hankeTunnus}/whoami`);
return data;
}
Expand Down
9 changes: 0 additions & 9 deletions src/domain/hanke/hankeUsers/hooks/useUserRights.ts

This file was deleted.

16 changes: 16 additions & 0 deletions src/domain/hanke/hankeUsers/hooks/useUserRightsForHanke.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useQuery } from 'react-query';
import { SignedInUser } from '../hankeUser';
import { getSignedInUserForHanke } from '../hankeUsersApi';
import { useFeatureFlags } from '../../../../common/components/featureFlags/FeatureFlagsContext';

export default function useSignedInUserRightsForHanke(hankeTunnus?: string) {
const features = useFeatureFlags();

return useQuery<SignedInUser>(
['signedInUser', hankeTunnus],
() => getSignedInUserForHanke(hankeTunnus),
{
enabled: Boolean(hankeTunnus) && features.accessRights,
},
);
}
4 changes: 2 additions & 2 deletions src/domain/hanke/hankeView/HankeViewContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import { ROUTES } from '../../../common/types/route';
import HankeDelete from '../edit/components/HankeDelete';
import useHanke from '../hooks/useHanke';
import HankeView from './HankeView';
import useSignedInUserRights from '../hankeUsers/hooks/useUserRights';
import useUserRightsForHanke from '../hankeUsers/hooks/useUserRightsForHanke';

type Props = {
hankeTunnus?: string;
};

const HankeViewContainer: React.FC<Props> = ({ hankeTunnus }) => {
const { data: hankeData } = useHanke(hankeTunnus);
const { data: signedInUser } = useSignedInUserRights(hankeTunnus);
const { data: signedInUser } = useUserRightsForHanke(hankeTunnus);
const getEditHankePath = useLinkPath(ROUTES.EDIT_HANKE);
const getEditRightsPath = useLinkPath(ROUTES.ACCESS_RIGHTS);
const navigate = useNavigate();
Expand Down
24 changes: 16 additions & 8 deletions src/domain/hanke/portfolio/HankePortfolio.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const endDateLabel = 'Ajanjakson loppu';

afterEach(cleanup);

jest.setTimeout(20000);
jest.setTimeout(30000);

describe.only('HankePortfolio', () => {
test('Changing search text filters correct number of projects', async () => {
Expand All @@ -30,7 +30,7 @@ describe.only('HankePortfolio', () => {
});
expect(screen.getByTestId('numberOfFilteredRows')).toHaveTextContent('0');
expect(
screen.queryByText('Valitsemillasi hakuehdoilla ei löytynyt yhtään hanketta')
screen.queryByText('Valitsemillasi hakuehdoilla ei löytynyt yhtään hanketta'),
).toBeInTheDocument();

await user.click(screen.getByRole('button', { name: /tyhjennä hakuehdot/i }));
Expand All @@ -50,7 +50,7 @@ describe.only('HankePortfolio', () => {
changeFilterDate(startDateLabel, renderedComponent, '11.10.2022');
expect(renderedComponent.getByTestId('numberOfFilteredRows')).toHaveTextContent('0');
expect(
screen.queryByText('Valitsemillasi hakuehdoilla ei löytynyt yhtään hanketta')
screen.queryByText('Valitsemillasi hakuehdoilla ei löytynyt yhtään hanketta'),
).toBeInTheDocument();
changeFilterDate(startDateLabel, renderedComponent, null);
});
Expand All @@ -61,7 +61,7 @@ describe.only('HankePortfolio', () => {
changeFilterDate(endDateLabel, renderedComponent, '01.10.2022');
expect(renderedComponent.getByTestId('numberOfFilteredRows')).toHaveTextContent('0');
expect(
screen.queryByText('Valitsemillasi hakuehdoilla ei löytynyt yhtään hanketta')
screen.queryByText('Valitsemillasi hakuehdoilla ei löytynyt yhtään hanketta'),
).toBeInTheDocument();
changeFilterDate(endDateLabel, renderedComponent, '05.10.2022');
expect(renderedComponent.getByTestId('numberOfFilteredRows')).toHaveTextContent('2');
Expand All @@ -75,22 +75,22 @@ describe.only('HankePortfolio', () => {
const renderedComponent = render(<HankePortfolioComponent hankkeet={hankeList} />);
expect(renderedComponent.getByTestId('numberOfFilteredRows')).toHaveTextContent('2');
await renderedComponent.user.click(
renderedComponent.getByRole('button', { name: 'Työn tyyppi' })
renderedComponent.getByRole('button', { name: 'Työn tyyppi' }),
);
await renderedComponent.user.click(renderedComponent.getByText('Sähkö'));
renderedComponent.getByText('Hankevaiheet').click();
expect(renderedComponent.getByTestId('numberOfFilteredRows')).toHaveTextContent('0');
expect(
screen.queryByText('Valitsemillasi hakuehdoilla ei löytynyt yhtään hanketta')
screen.queryByText('Valitsemillasi hakuehdoilla ei löytynyt yhtään hanketta'),
).toBeInTheDocument();
await renderedComponent.user.click(
renderedComponent.getByRole('button', { name: 'Työn tyyppi' })
renderedComponent.getByRole('button', { name: 'Työn tyyppi' }),
);
await renderedComponent.user.click(renderedComponent.getByText('Viemäri'));
renderedComponent.getByText('Hankevaiheet').click();
expect(renderedComponent.getByTestId('numberOfFilteredRows')).toHaveTextContent('1');
await renderedComponent.user.click(
renderedComponent.getByRole('button', { name: 'Työn tyyppi' })
renderedComponent.getByRole('button', { name: 'Työn tyyppi' }),
);
await renderedComponent.user.click(renderedComponent.getByText('Sadevesi'));
renderedComponent.getByText('Hankevaiheet').click();
Expand All @@ -102,4 +102,12 @@ describe.only('HankePortfolio', () => {

expect(screen.queryByText('Hankesalkussasi ei ole hankkeita')).toBeInTheDocument();
});

test('Should render edit hanke links for hankkeet that user has edit rights', async () => {
render(<HankePortfolioComponent hankkeet={hankeList} />);

await waitFor(() => {
expect(screen.queryAllByTestId('hankeEditLink')).toHaveLength(1);
});
});
});
25 changes: 14 additions & 11 deletions src/domain/hanke/portfolio/HankePortfolioComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { SKIP_TO_ELEMENT_ID } from '../../../common/constants/constants';
import useHankeViewPath from '../hooks/useHankeViewPath';
import { useNavigateToApplicationList } from '../hooks/useNavigateToApplicationList';
import FeatureFlags from '../../../common/components/featureFlags/FeatureFlags';
import UserRightsCheck from '../hankeUsers/UserRightsCheck';

type CustomAccordionProps = {
hanke: HankeData;
Expand Down Expand Up @@ -114,17 +115,19 @@ const CustomAccordion: React.FC<React.PropsWithChildren<CustomAccordionProps>> =
<IconEye aria-hidden />
</Link>
<FeatureFlags flags={['hanke']}>
<Link
to={getEditHankePath({ hankeTunnus: hanke.hankeTunnus })}
aria-label={
// eslint-disable-next-line
t(`routes:${ROUTES.EDIT_HANKE}.meta.title`) +
` ${hanke.nimi} - ${hanke.hankeTunnus} `
}
data-testid="hankeEditLink"
>
<IconPen aria-hidden />
</Link>
<UserRightsCheck requiredRight="EDIT" hankeTunnus={hanke.hankeTunnus}>
<Link
to={getEditHankePath({ hankeTunnus: hanke.hankeTunnus })}
aria-label={
// eslint-disable-next-line
t(`routes:${ROUTES.EDIT_HANKE}.meta.title`) +
` ${hanke.nimi} - ${hanke.hankeTunnus} `
}
data-testid="hankeEditLink"
>
<IconPen aria-hidden />
</Link>
</UserRightsCheck>
</FeatureFlags>
</div>
<button type="button" className={styles.iconWrapper}>
Expand Down
13 changes: 13 additions & 0 deletions src/domain/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,19 @@ export const handlers = [
}),

rest.get('/api/hankkeet/:hankeTunnus/whoami', async (req, res, ctx) => {
const { hankeTunnus } = req.params;

if (hankeTunnus === 'SMTGEN2_1') {
return res(
ctx.status(200),
ctx.json<SignedInUser>({
hankeKayttajaId: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
kayttooikeustaso: 'KATSELUOIKEUS',
kayttooikeudet: ['VIEW'],
}),
);
}

return res(
ctx.status(200),
ctx.json<SignedInUser>({
Expand Down

0 comments on commit 3496cc8

Please sign in to comment.