diff --git a/src/domain/application/applicationView/ApplicationView.tsx b/src/domain/application/applicationView/ApplicationView.tsx index 542c89730..817c21c5d 100644 --- a/src/domain/application/applicationView/ApplicationView.tsx +++ b/src/domain/application/applicationView/ApplicationView.tsx @@ -38,7 +38,7 @@ import { ApplicationCancel } from '../components/ApplicationCancel'; import AttachmentSummary from '../components/AttachmentSummary'; import useAttachments from '../hooks/useAttachments'; import FeatureFlags from '../../../common/components/featureFlags/FeatureFlags'; -import UserRightsCheck from '../../hanke/hankeUsers/UserRightsCheck'; +import { CheckRightsByHanke } from '../../hanke/hankeUsers/UserRightsCheck'; type Props = { application: Application; @@ -120,7 +120,7 @@ function ApplicationView({ application, hanke, onEditApplication }: Props) { {isPending ? ( - + - + ) : null} {hanke ? ( - + } /> - + ) : null} diff --git a/src/domain/hanke/accessRights/AccessRightsViewContainer.tsx b/src/domain/hanke/accessRights/AccessRightsViewContainer.tsx index cf5c08827..7e21afbab 100644 --- a/src/domain/hanke/accessRights/AccessRightsViewContainer.tsx +++ b/src/domain/hanke/accessRights/AccessRightsViewContainer.tsx @@ -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 useUserRightsForHanke from '../hankeUsers/hooks/useUserRightsForHanke'; +import { usePermissionsForHanke } from '../hankeUsers/hooks/useUserRightsForHanke'; type Props = { hankeTunnus: string; @@ -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 } = useUserRightsForHanke(hankeTunnus); + const { data: signedInUser } = usePermissionsForHanke(hankeTunnus); if (isLoading) { return ( diff --git a/src/domain/hanke/hankeUsers/UserRightsCheck.test.tsx b/src/domain/hanke/hankeUsers/UserRightsCheck.test.tsx index 06be23cfb..3be64c2f6 100644 --- a/src/domain/hanke/hankeUsers/UserRightsCheck.test.tsx +++ b/src/domain/hanke/hankeUsers/UserRightsCheck.test.tsx @@ -2,60 +2,108 @@ 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( - -

Children

-
, - ); - - await waitFor(() => { - expect(screen.getByText('Children')).toBeInTheDocument(); +import { AccessRightLevel, SignedInUser } from './hankeUser'; +import { CheckRightsByHanke, CheckRightsByUser } from './UserRightsCheck'; +import { signedInUser } from '../../mocks/signedInUser'; + +describe('CheckRightsByHanke', () => { + test('Should render children if user has required right', async () => { + render( + +

Children

+
, + ); + + 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({ - hankeKayttajaId: '3fa85f64-5717-4562-b3fc-2c963f66afa6', - kayttooikeustaso: 'KATSELUOIKEUS', - kayttooikeudet: ['VIEW'], - }), - ); - }), - ); - - render( - -

Children

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

Children

+
, + ); + + await waitFor(() => { + expect(screen.queryByText('Children')).not.toBeInTheDocument(); + }); + }); + + test('Should render children when access right feature is not enabled', async () => { + const OLD_ENV = { ...window._env_ }; + window._env_ = { ...OLD_ENV, REACT_APP_FEATURE_ACCESS_RIGHTS: 0 }; + + render( + +

Children

+
, + ); + + await waitFor(() => { + expect(screen.getByText('Children')).toBeInTheDocument(); + }); + jest.resetModules(); + window._env_ = OLD_ENV; }); }); -test('Should render children when access right feature is not enabled', async () => { - const OLD_ENV = window._env_; - window._env_.REACT_APP_FEATURE_ACCESS_RIGHTS = 0; +describe('CheckRightsByUser', () => { + const ALL_RIGHTS_USER = signedInUser(AccessRightLevel.KAIKKI_OIKEUDET); + const VIEW_RIGHT_USER = signedInUser(AccessRightLevel.KATSELUOIKEUS); + + test('Should render children on required right', async () => { + render( + +

Children

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

Children

-
, - ); + test('Should not render children if not enough rights', async () => { + render( + +

Children

+
, + ); - await waitFor(() => { - expect(screen.getByText('Children')).toBeInTheDocument(); + await waitFor(() => { + expect(screen.queryByText('Children')).not.toBeInTheDocument(); + }); }); - jest.resetModules(); - window._env_ = OLD_ENV; + test('Should render children if feature not enabled regardless of permission', async () => { + const OLD_ENV = { ...window._env_ }; + window._env_ = { ...OLD_ENV, REACT_APP_FEATURE_ACCESS_RIGHTS: 0 }; + + render( + +

Children

+
, + ); + + await waitFor(() => { + expect(screen.getByText('Children')).toBeInTheDocument(); + }); + jest.resetModules(); + window._env_ = OLD_ENV; + }); }); diff --git a/src/domain/hanke/hankeUsers/UserRightsCheck.tsx b/src/domain/hanke/hankeUsers/UserRightsCheck.tsx index a6189086f..0189ab925 100644 --- a/src/domain/hanke/hankeUsers/UserRightsCheck.tsx +++ b/src/domain/hanke/hankeUsers/UserRightsCheck.tsx @@ -1,13 +1,13 @@ import React from 'react'; -import useUserRightsForHanke from './hooks/useUserRightsForHanke'; -import { Rights } from './hankeUser'; +import { Rights, SignedInUser } from './hankeUser'; import { useFeatureFlags } from '../../../common/components/featureFlags/FeatureFlagsContext'; +import { usePermissionsForHanke } from './hooks/useUserRightsForHanke'; /** * Check that user has required rights. * If they have, render children. */ -function UserRightsCheck({ +export function CheckRightsByHanke({ requiredRight, hankeTunnus, children, @@ -18,7 +18,7 @@ function UserRightsCheck({ hankeTunnus?: string; children: React.ReactElement | null; }) { - const { data: signedInUser } = useUserRightsForHanke(hankeTunnus); + const { data: signedInUser } = usePermissionsForHanke(hankeTunnus); const features = useFeatureFlags(); if (!features.accessRights) { @@ -32,4 +32,20 @@ function UserRightsCheck({ return null; } -export default UserRightsCheck; +export function CheckRightsByUser({ + requiredRight, + signedInUser: userPermission, + children, +}: { + requiredRight: keyof typeof Rights; + signedInUser: SignedInUser; + children: React.ReactElement | null; +}) { + const features = useFeatureFlags(); + + if (!features.accessRights) { + return children; + } + + return userPermission?.kayttooikeudet?.includes(requiredRight) ? children : null; +} diff --git a/src/domain/hanke/hankeUsers/hankeUser.ts b/src/domain/hanke/hankeUsers/hankeUser.ts index fd18b105f..b831cffdd 100644 --- a/src/domain/hanke/hankeUsers/hankeUser.ts +++ b/src/domain/hanke/hankeUsers/hankeUser.ts @@ -34,6 +34,10 @@ export type SignedInUser = { kayttooikeudet: UserRights; }; +export type SignedInUserByHanke = { + [hankeTunnus: string]: SignedInUser; +}; + export type IdentificationResponse = { kayttajaId: string; hankeTunnus: string; diff --git a/src/domain/hanke/hankeUsers/hankeUsersApi.ts b/src/domain/hanke/hankeUsers/hankeUsersApi.ts index 5ae8bd20c..6d176e769 100644 --- a/src/domain/hanke/hankeUsers/hankeUsersApi.ts +++ b/src/domain/hanke/hankeUsers/hankeUsersApi.ts @@ -1,5 +1,5 @@ import api from '../../api/api'; -import { HankeUser, IdentificationResponse, SignedInUser } from './hankeUser'; +import { HankeUser, IdentificationResponse, SignedInUser, SignedInUserByHanke } from './hankeUser'; export async function getHankeUsers(hankeTunnus: string) { const { data } = await api.get<{ kayttajat: HankeUser[] }>(`hankkeet/${hankeTunnus}/kayttajat`); @@ -23,6 +23,11 @@ export async function getSignedInUserForHanke(hankeTunnus?: string): Promise { + const { data } = await api.get('hankkeet/my-permissions'); + return data; +} + export async function identifyUser(id: string) { const { data } = await api.post('kayttajat', { tunniste: id }); return data; diff --git a/src/domain/hanke/hankeUsers/hooks/useUserRightsForHanke.ts b/src/domain/hanke/hankeUsers/hooks/useUserRightsForHanke.ts index a883f8407..4f5e82331 100644 --- a/src/domain/hanke/hankeUsers/hooks/useUserRightsForHanke.ts +++ b/src/domain/hanke/hankeUsers/hooks/useUserRightsForHanke.ts @@ -1,9 +1,9 @@ import { useQuery } from 'react-query'; -import { SignedInUser } from '../hankeUser'; -import { getSignedInUserForHanke } from '../hankeUsersApi'; +import { SignedInUser, SignedInUserByHanke } from '../hankeUser'; +import { getSignedInUserForHanke, getSignedInUserByHanke } from '../hankeUsersApi'; import { useFeatureFlags } from '../../../../common/components/featureFlags/FeatureFlagsContext'; -export default function useSignedInUserRightsForHanke(hankeTunnus?: string) { +export function usePermissionsForHanke(hankeTunnus?: string) { const features = useFeatureFlags(); return useQuery( @@ -14,3 +14,11 @@ export default function useSignedInUserRightsForHanke(hankeTunnus?: string) { }, ); } + +export function usePermissionsByHanke() { + const features = useFeatureFlags(); + + return useQuery(['signedInUserByHanke'], () => getSignedInUserByHanke(), { + enabled: features.accessRights, + }); +} diff --git a/src/domain/hanke/hankeView/HankeView.tsx b/src/domain/hanke/hankeView/HankeView.tsx index 044a992f7..54c5ba347 100644 --- a/src/domain/hanke/hankeView/HankeView.tsx +++ b/src/domain/hanke/hankeView/HankeView.tsx @@ -52,7 +52,7 @@ import { import FeatureFlags from '../../../common/components/featureFlags/FeatureFlags'; import { useFeatureFlags } from '../../../common/components/featureFlags/FeatureFlagsContext'; import { SignedInUser } from '../hankeUsers/hankeUser'; -import UserRightsCheck from '../hankeUsers/UserRightsCheck'; +import { CheckRightsByHanke } from '../hankeUsers/UserRightsCheck'; type AreaProps = { area: HankeAlue; @@ -217,7 +217,7 @@ const HankeView: React.FC = ({ - + - - + + {isHankePublic ? ( ) : null} - + - + - + {!isLoading && isCancelPossible && ( - + - + )} diff --git a/src/domain/hanke/hankeView/HankeViewContainer.tsx b/src/domain/hanke/hankeView/HankeViewContainer.tsx index 1f1346ec5..19c6a97d8 100644 --- a/src/domain/hanke/hankeView/HankeViewContainer.tsx +++ b/src/domain/hanke/hankeView/HankeViewContainer.tsx @@ -5,7 +5,7 @@ import { ROUTES } from '../../../common/types/route'; import HankeDelete from '../edit/components/HankeDelete'; import useHanke from '../hooks/useHanke'; import HankeView from './HankeView'; -import useUserRightsForHanke from '../hankeUsers/hooks/useUserRightsForHanke'; +import { usePermissionsForHanke } from '../hankeUsers/hooks/useUserRightsForHanke'; type Props = { hankeTunnus?: string; @@ -13,7 +13,7 @@ type Props = { const HankeViewContainer: React.FC = ({ hankeTunnus }) => { const { data: hankeData } = useHanke(hankeTunnus); - const { data: signedInUser } = useUserRightsForHanke(hankeTunnus); + const { data: signedInUser } = usePermissionsForHanke(hankeTunnus); const getEditHankePath = useLinkPath(ROUTES.EDIT_HANKE); const getEditRightsPath = useLinkPath(ROUTES.ACCESS_RIGHTS); const navigate = useNavigate(); diff --git a/src/domain/hanke/portfolio/HankePortfolio.test.tsx b/src/domain/hanke/portfolio/HankePortfolio.test.tsx index 9afaab508..2ff1c798a 100644 --- a/src/domain/hanke/portfolio/HankePortfolio.test.tsx +++ b/src/domain/hanke/portfolio/HankePortfolio.test.tsx @@ -6,6 +6,8 @@ import HankePortfolioComponent from './HankePortfolioComponent'; import { render, screen, waitFor } from '../../../testUtils/render'; import hankeList from '../../mocks/hankeList'; import { changeFilterDate } from '../../../testUtils/helperFunctions'; +import { USER_VIEW, signedInUserByHanke } from '../../mocks/signedInUser'; +import { SignedInUserByHanke } from '../hankeUsers/hankeUser'; const startDateLabel = 'Ajanjakson alku'; const endDateLabel = 'Ajanjakson loppu'; @@ -16,7 +18,9 @@ jest.setTimeout(30000); describe.only('HankePortfolio', () => { test('Changing search text filters correct number of projects', async () => { - const { user } = render(); + const { user } = render( + , + ); await user.type(screen.getByLabelText('Haku'), 'Mannerheimintie autottomaksi'); await waitFor(() => { @@ -41,7 +45,9 @@ describe.only('HankePortfolio', () => { }); test('Changing filter startDates filters correct number of projects', async () => { - const renderedComponent = render(); + const renderedComponent = render( + , + ); expect(renderedComponent.getByTestId('numberOfFilteredRows')).toHaveTextContent('2'); changeFilterDate(startDateLabel, renderedComponent, '02.10.2022'); expect(renderedComponent.getByTestId('numberOfFilteredRows')).toHaveTextContent('2'); @@ -56,7 +62,9 @@ describe.only('HankePortfolio', () => { }); test('Changing filter endDates filters correct number of projects', async () => { - const renderedComponent = render(); + const renderedComponent = render( + , + ); expect(renderedComponent.getByTestId('numberOfFilteredRows')).toHaveTextContent('2'); changeFilterDate(endDateLabel, renderedComponent, '01.10.2022'); expect(renderedComponent.getByTestId('numberOfFilteredRows')).toHaveTextContent('0'); @@ -72,7 +80,9 @@ describe.only('HankePortfolio', () => { }); test('Changing Hanke type filters correct number of projects', async () => { - const renderedComponent = render(); + const renderedComponent = render( + , + ); expect(renderedComponent.getByTestId('numberOfFilteredRows')).toHaveTextContent('2'); await renderedComponent.user.click( renderedComponent.getByRole('button', { name: 'Tyƶn tyyppi' }), @@ -98,13 +108,19 @@ describe.only('HankePortfolio', () => { }); test('Having no projects renders correct text', () => { - render(); + render(); expect(screen.queryByText('Hankesalkussasi ei ole hankkeita')).toBeInTheDocument(); }); test('Should render edit hanke links for hankkeet that user has edit rights', async () => { - render(); + const hankeTunnusList = hankeList.map((hanke) => hanke.hankeTunnus); + const signedUserData: SignedInUserByHanke = { + ...signedInUserByHanke(hankeTunnusList), + [hankeTunnusList[0]]: USER_VIEW, + }; + + render(); await waitFor(() => { expect(screen.queryAllByTestId('hankeEditLink')).toHaveLength(1); @@ -112,7 +128,7 @@ describe.only('HankePortfolio', () => { }); test('Should show draft state notification for hankkeet that are in draft state', async () => { - render(); + render(); expect( screen.getAllByText( diff --git a/src/domain/hanke/portfolio/HankePortfolioComponent.tsx b/src/domain/hanke/portfolio/HankePortfolioComponent.tsx index c16c344d7..61c99679f 100644 --- a/src/domain/hanke/portfolio/HankePortfolioComponent.tsx +++ b/src/domain/hanke/portfolio/HankePortfolioComponent.tsx @@ -39,13 +39,18 @@ 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'; +import { CheckRightsByUser } from '../hankeUsers/UserRightsCheck'; +import { SignedInUser, SignedInUserByHanke } from '../hankeUsers/hankeUser'; type CustomAccordionProps = { hanke: HankeData; + permission: SignedInUser; }; -const CustomAccordion: React.FC> = ({ hanke }) => { +const CustomAccordion: React.FC> = ({ + hanke, + permission, +}) => { const getEditHankePath = useLinkPath(ROUTES.EDIT_HANKE); const hankeViewPath = useHankeViewPath(hanke?.hankeTunnus); const navigateToApplications = useNavigateToApplicationList(hanke?.hankeTunnus); @@ -115,7 +120,7 @@ const CustomAccordion: React.FC> = - + > = > - +