Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LINK-1982, LINK-1984 | Admin page permissions #386

Merged
merged 6 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ REACT_APP_ALLOWED_SUBSTITUTE_USER_DOMAINS=hel.fi

#feature flags
REACT_APP_SHOW_ADMIN=true
REACT_APP_SHOW_PLACE_PAGES=false
REACT_APP_LOCALIZED_IMAGE=true
REACT_APP_ENABLE_SWEDISH_TRANSLATIONS=false
REACT_APP_WEB_STORE_INTEGRATION_ENABLED=false
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ ARG REACT_APP_SWAGGER_URL

# Feature flags
ARG REACT_APP_SHOW_ADMIN
ARG REACT_APP_SHOW_PLACE_PAGES
ARG REACT_APP_LOCALIZED_IMAGE
ARG REACT_APP_ENABLE_SWEDISH_TRANSLATIONS
ARG REACT_APP_ENABLE_EXTERNAL_USER_EVENTS
Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ Use .env.development.local for development.
| REACT_APP_INTERNET_PLACE_ID | Id of the internet place. system:internet in development server, helsinki:internet in production |
| REACT_APP_REMOTE_PARTICIPATION_KEYWORD_ID | yso:p26626 |
| REACT_APP_LINKED_EVENTS_SYSTEM_DATA_SOURCE | helsinki |
| REACT_APP_SHOW_ADMIN | Flag to show admin, Default true. pages |
| REACT_APP_SHOW_ADMIN | Flag to show admin pages, Default true. pages |
| REACT_APP_SHOW_PLACE_PAGES | Flag to show place pages, Default is false.   |
| REACT_APP_LOCALIZED_IMAGE | Flag to disabled localized image alt texts, Default true. |
| REACT_APP_ENABLE_SWEDISH_TRANSLATIONS | Flag to enable swedish translations, Default is false. |
| REACT_APP_ENABLE_EXTERNAL_USER_EVENTS | Flag to enable events for users without an organization, Default true. |
Expand All @@ -104,6 +105,14 @@ Features enabled:
- Editing organizations.
- Editing places.

Note that also `REACT_APP_SHOW_PLACE_PAGES` has to be enabled to see place pages.

`REACT_APP_SHOW_PLACE_PAGES`:

Features enabled:

- Editing places.

`REACT_APP_LOCALIZED_IMAGE`:

Features enabled:
Expand Down
8 changes: 0 additions & 8 deletions e2e/tests/help.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,6 @@ test.describe('Help page', () => {
.soft(page.getByRole('heading', { name: 'Sisällöntuotannon ohjeet' }))
.toBeVisible();

await page
.getByLabel('Lisätietoa palvelusta')
.getByRole('link', { name: 'Ilmoittautumisen ohjeet' })
.click();
await expect
.soft(page.getByRole('heading', { name: 'Ilmoittautumisen ohjeet' }))
.toBeVisible();

await page
.getByLabel('Lisätietoa palvelusta')
.getByRole('link', { name: 'UKK' })
Expand Down
1 change: 1 addition & 0 deletions scripts/generate-sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const STATIC_URLS_BLACK_LIST = [
ROUTES.CREATE_KEYWORD_SET,
ROUTES.CREATE_ORGANIZATION,
ROUTES.CREATE_PLACE,
ROUTES.CREATE_PRICE_GROUP,
ROUTES.IMAGES,
ROUTES.KEYWORDS,
ROUTES.KEYWORD_SETS,
Expand Down
16 changes: 13 additions & 3 deletions src/domain/admin/layout/AdminPageLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import { useTranslation } from 'react-i18next';
import { matchPath, useLocation } from 'react-router';

import { ROUTES } from '../../../constants';
import { featureFlagUtils } from '../../../utils/featureFlags';
import LayoutWithSideNavigation from '../../app/layout/layoutWithSideNavigation/LayoutWithSideNavigation';
import useUser from '../../user/hooks/useUser';
import { areFinancialRoutesAllowed } from '../../user/permissions';

const AdminPageLayout: React.FC<PropsWithChildren> = ({ children }) => {
const { t } = useTranslation();
const { pathname } = useLocation();
const { user } = useUser();

const getIsActive = (localePath: string) => {
return !!matchPath({ path: localePath, end: false }, pathname);
Expand All @@ -18,9 +22,15 @@ const AdminPageLayout: React.FC<PropsWithChildren> = ({ children }) => {
{ label: t('keywordsPage.title'), to: ROUTES.KEYWORDS },
{ label: t('keywordSetsPage.title'), to: ROUTES.KEYWORD_SETS },
{ label: t('imagesPage.title'), to: ROUTES.IMAGES },
{ label: t('organizationsPage.title'), to: ROUTES.ORGANIZATIONS },
{ label: t('placesPage.title'), to: ROUTES.PLACES },
{ label: t('priceGroupsPage.title'), to: ROUTES.PRICE_GROUPS },
...(areFinancialRoutesAllowed(user)
? [{ label: t('organizationsPage.title'), to: ROUTES.ORGANIZATIONS }]
: []),
...(featureFlagUtils.isFeatureEnabled('SHOW_PLACE_PAGES')
? [{ label: t('placesPage.title'), to: ROUTES.PLACES }]
: []),
...(areFinancialRoutesAllowed(user)
? [{ label: t('priceGroupsPage.title'), to: ROUTES.PRICE_GROUPS }]
: []),
];

const levels = [
Expand Down
83 changes: 80 additions & 3 deletions src/domain/admin/layout/__tests__/AdminPageLayout.test.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,97 @@
import { MockedResponse } from '@apollo/client/testing';
import React from 'react';

import { ROUTES } from '../../../../constants';
import { configure, render, screen } from '../../../../utils/testUtils';
import { setFeatureFlags } from '../../../../test/featureFlags/featureFlags';
import { mockAuthenticatedLoginState } from '../../../../utils/mockLoginHooks';
import {
configure,
render,
screen,
userEvent,
waitFor,
} from '../../../../utils/testUtils';
import {
mockedFinancialAdminUserResponse,
mockedSuperuserResponse,
mockedUserResponse,
} from '../../../user/__mocks__/user';
import AdminPageLayout from '../AdminPageLayout';

configure({ defaultHidden: true });

beforeEach(() => {
mockAuthenticatedLoginState();

setFeatureFlags({
LOCALIZED_IMAGE: true,
SHOW_ADMIN: true,
SHOW_PLACE_PAGES: true,
SWEDISH_TRANSLATIONS: true,
WEB_STORE_INTEGRATION: true,
});
});

const defaultMocks = [mockedSuperuserResponse];

const route = `/fi${ROUTES.KEYWORDS}`;
const renderComponent = () =>
const renderComponent = (mocks: MockedResponse[] = defaultMocks) =>
render(<AdminPageLayout>Content</AdminPageLayout>, {
routes: [route],
mocks,
});

test('should render help page layout', async () => {
const shouldRenderDefaultTabs = () => {
screen.getByRole('link', { name: 'Avainsanat' });
screen.getByRole('link', { name: 'Avainsanaryhmät' });
screen.getByRole('link', { name: 'Kuvat' });
screen.getByRole('link', { name: 'Paikat' });
};

const shouldRenderFinancialAdminTabs = async () => {
await screen.findByRole('link', { name: 'Organisaatiot' });
screen.getByRole('link', { name: 'Asiakasryhmät' });
};

const shouldNotRenderFinancialAdminTabs = async () => {
expect(
screen.queryByRole('link', { name: 'Asiakasryhmät' })
).not.toBeInTheDocument();
};

test('should render admin page layout', async () => {
renderComponent();

screen.getByRole('button', { name: 'Hallinta' });
screen.getByRole('link', { name: 'Avainsanat' });
});

test('should render valid side navigation links for admin user', async () => {
renderComponent([mockedUserResponse]);
shouldRenderDefaultTabs();
shouldNotRenderFinancialAdminTabs();
});

test('should render valid side navigation links for financial admin user', async () => {
renderComponent([mockedFinancialAdminUserResponse]);
shouldRenderDefaultTabs();
await shouldRenderFinancialAdminTabs();
});

test('should render valid side navigation links for superuser', async () => {
renderComponent([mockedSuperuserResponse]);
shouldRenderDefaultTabs();
await shouldRenderFinancialAdminTabs();
});

test('should route to keywords page', async () => {
const user = userEvent.setup();
const { history } = renderComponent();

const keywordsLink = screen.getByRole('link', { name: 'Avainsanat' });
await user.click(keywordsLink);

await waitFor(() =>
expect(history.location.pathname).toBe('/fi/administration/keywords')
);
});
66 changes: 66 additions & 0 deletions src/domain/admin/layout/hooks/useAdminFormStyles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useMemo } from 'react';

import {
KeywordFieldsFragment,
KeywordSetFieldsFragment,
OrganizationFieldsFragment,
} from '../../../../generated/graphql';
import styles from '../form.module.scss';

type UseAdminFormStylesProps = {
isEditingAllowed: boolean;
instance?:
| KeywordFieldsFragment
| KeywordSetFieldsFragment
| OrganizationFieldsFragment;
};

type UseAdminFormStylesState = {
alignedInputStyle: string;
alignedInputStyleIfHasInstance: string;
inputRowBorderStyle: string;
inputRowBorderStyleIfHasInstance: string;
};

const useAdminFormStyles = ({
instance,
isEditingAllowed,
}: UseAdminFormStylesProps): UseAdminFormStylesState => {
const alignedInputStyle = useMemo(
() =>
/* istanbul ignore next */
isEditingAllowed
? styles.alignedInput
: styles.alignedInputWithFullBorder,
[isEditingAllowed]
);

const alignedInputStyleIfHasInstance = useMemo(
() =>
!isEditingAllowed || instance
? styles.alignedInputWithFullBorder
: styles.alignedInput,
[isEditingAllowed, instance]
);

const inputRowBorderStyle = useMemo(
() =>
/* istanbul ignore next */
isEditingAllowed ? '' : styles.borderInMobile,
[isEditingAllowed]
);

const inputRowBorderStyleIfHasInstance = useMemo(
() => (!isEditingAllowed || instance ? styles.borderInMobile : ''),
[isEditingAllowed, instance]
);

return {
alignedInputStyle,
alignedInputStyleIfHasInstance,
inputRowBorderStyle,
inputRowBorderStyleIfHasInstance,
};
};

export default useAdminFormStyles;
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ export type AdminType =
| 'external'
| 'financialAdmin'
| 'registrationAdmin'
| 'substituteUser';
| 'substituteUser'
| 'superUser';

type CommonProps = {
className?: string;
Expand Down Expand Up @@ -121,6 +122,10 @@ const AuthenticationNotification: React.FC<AuthenticationNotificationProps> = ({
return true;
}

if (requiredOrganizationType.includes('superUser') && user?.isSuperuser) {
return true;
}

return requiredOrganizationType.includes('any') && userOrganizations.length;
};

Expand Down
4 changes: 2 additions & 2 deletions src/domain/app/footer/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ import {
navigationGroupAdmin,
navigationGroupEvents,
navigationGroupHome,
navigationGroupInstructions,
navigationGroupRegistrations,
navigationGroupSupport,
navigationGroupTechnology,
NO_FOOTER_PATHS,
} from './constants';
import styles from './footer.module.scss';
import { getNavigationGroupInstructions } from './utils';

const Footer: React.FC = () => {
const { t } = useTranslation();
Expand Down Expand Up @@ -106,7 +106,7 @@ const Footer: React.FC = () => {
areAdminRoutesAllowed(user) &&
navigationGroupAdmin,
navigationGroupSupport,
navigationGroupInstructions,
getNavigationGroupInstructions(user),
navigationGroupTechnology,
]
.filter(skipFalsyType)
Expand Down
4 changes: 4 additions & 0 deletions src/domain/app/footer/__tests__/Footer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ test('should show navigation links and should route to correct page after clicki
setFeatureFlags({
LOCALIZED_IMAGE: true,
SHOW_ADMIN: true,
SHOW_PLACE_PAGES: true,
SWEDISH_TRANSLATIONS: true,
WEB_STORE_INTEGRATION: true,
});
Expand Down Expand Up @@ -110,6 +111,7 @@ test.each(registrationAndAdminTabTestCases)(
setFeatureFlags({
LOCALIZED_IMAGE: true,
SHOW_ADMIN: true,
SHOW_PLACE_PAGES: true,
SWEDISH_TRANSLATIONS: true,
WEB_STORE_INTEGRATION: true,
});
Expand Down Expand Up @@ -148,6 +150,7 @@ test('should not show admin links when those features are disabled', async () =>
setFeatureFlags({
LOCALIZED_IMAGE: true,
SHOW_ADMIN: false,
SHOW_PLACE_PAGES: true,
SWEDISH_TRANSLATIONS: true,
WEB_STORE_INTEGRATION: true,
});
Expand All @@ -163,6 +166,7 @@ test('should show and open utility links', async () => {
setFeatureFlags({
LOCALIZED_IMAGE: true,
SHOW_ADMIN: true,
SHOW_PLACE_PAGES: true,
SWEDISH_TRANSLATIONS: true,
WEB_STORE_INTEGRATION: true,
});
Expand Down
23 changes: 0 additions & 23 deletions src/domain/app/footer/__tests__/__snapshots__/Footer.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -242,29 +242,6 @@ exports[`matches snapshot 1`] = `
Instruktioner för innehållsproduktion
</span>
</a>
<a
class="Link-module_link__TeBQo link_hds-link__5Oxo- Link-module_linkM__30gsY link_hds-link--medium__xEU_F FooterLink-module_item__3Ig3B FooterLink-module_subItem__3unGg FooterLink-module_navigation__1pNfs"
href="/sv/help/instructions/registration"
>
<svg
aria-hidden="true"
aria-label="angle-right"
class="Icon-module_icon__1Jtzj icon_hds-icon__1YqNC Icon-module_s__2WGWe icon_hds-icon--size-s__2Lkik FooterLink-module_subItemIcon__2UHZi"
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
clip-rule="evenodd"
d="M13.5 12L8.5 7L10 5.5L16.5 12L10 18.5L8.5 17L13.5 12Z"
fill="currentColor"
fill-rule="evenodd"
/>
</svg>
<span>
Registreringsinstruktioner
</span>
</a>
<a
class="Link-module_link__TeBQo link_hds-link__5Oxo- Link-module_linkM__30gsY link_hds-link--medium__xEU_F FooterLink-module_item__3Ig3B FooterLink-module_subItem__3unGg FooterLink-module_navigation__1pNfs"
href="/sv/help/instructions/faq"
Expand Down
Loading
Loading