Skip to content

Commit

Permalink
Production update 21.9.2023 (#373)
Browse files Browse the repository at this point in the history
Production update 21.9.2023
  • Loading branch information
markohaarni authored Sep 21, 2023
2 parents 6c7ac93 + 780dd03 commit 345378d
Show file tree
Hide file tree
Showing 49 changed files with 1,525 additions and 154 deletions.
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ REACT_APP_SENTRY_DSN="https://[email protected]
REACT_APP_DISABLE_SENTRY=0
REACT_APP_FEATURE_PUBLIC_HANKKEET=1
REACT_APP_FEATURE_HANKE=1
REACT_APP_FEATURE_ACCESS_RIGHTS=0
REACT_APP_FEATURE_ACCESS_RIGHTS=1
REACT_APP_WARNING_NOTIFICATION_LABEL=""
REACT_APP_WARNING_NOTIFICATION_LABEL_SV=""
REACT_APP_WARNING_NOTIFICATION_LABEL_EN=""
Expand Down
29 changes: 18 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
FROM registry.redhat.io/rhel8/nginx-116
FROM node:16-alpine as staticbuilder
COPY . /builder/
WORKDIR /builder
RUN yarn && yarn cache clean --force
RUN REACT_APP_DISABLE_SENTRY=0 yarn build

FROM nginx:stable
EXPOSE 8000
COPY ./build /usr/share/nginx/html
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
COPY --from=staticbuilder ./builder/build /usr/share/nginx/html
COPY --from=staticbuilder ./builder/nginx/nginx.conf /etc/nginx/nginx.conf
WORKDIR /usr/share/nginx/html
COPY .env .

USER root

RUN chgrp -R 0 /usr/share/nginx/html && \
chmod -R g=u /usr/share/nginx/html
# Create the environment file so that the user that will run the backend has
# permission to overwrite the config based on environment variables.
RUN touch env-config.js
RUN chmod a+rw env-config.js

# Copy default environment config and setup script
# Copy package.json so env.sh can read it
COPY ./scripts/env.sh /opt/env.sh
COPY .env /opt/.env
COPY package.json /opt/package.json
COPY --from=staticbuilder ./builder/scripts/env.sh /opt/env.sh
COPY --from=staticbuilder ./builder/.env /opt/.env
COPY --from=staticbuilder ./builder/package.json /opt/package.json
RUN chmod +x /opt/env.sh

ENV REACT_APP_DISABLE_SENTRY=0

CMD ["/bin/bash", "-c", "/opt/env.sh /opt /usr/share/nginx/html && nginx -g \"daemon off;\""]
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@
"i18next-browser-languagedetector": "^7.0.1",
"immer": "8.0.2",
"jest-fetch-mock": "^3.0.3",
"jest-localstorage-mock": "^2.4.6",
"local-web-server": "^4.2.1",
"lodash": "^4.17.21",
"msw": "^0.49.0",
Expand Down
5 changes: 1 addition & 4 deletions scripts/env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@

ENVDIR=${1:-.}
TARGETDIR=${2:-.}
# Recreate config file
rm -f $TARGETDIR/env-config.js
touch $TARGETDIR/env-config.js

REACT_APP_VERSION=$(grep -m1 version $ENVDIR/package.json | awk -F: '{ print $2 }' | sed 's/[", ]//g')
REACT_APP_APPLICATION_NAME=$(grep -m1 name $ENVDIR/package.json | awk -F: '{ print $2 }' | sed 's/[", ]//g')

# Add assignment
echo "window._env_ = {" >> $TARGETDIR/env-config.js
echo "window._env_ = {" > $TARGETDIR/env-config.js

# Read each line in .env file
# Each line represents key=value pairs
Expand Down
3 changes: 3 additions & 0 deletions src/common/components/textInput/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type PropTypes = {
helperText?: string;
shouldUnregister?: boolean;
className?: string;
autoComplete?: string;
};

const TextInput: React.FC<React.PropsWithChildren<PropTypes>> = ({
Expand All @@ -30,6 +31,7 @@ const TextInput: React.FC<React.PropsWithChildren<PropTypes>> = ({
helperText,
shouldUnregister,
className,
autoComplete,
}) => {
const { t } = useTranslation();
const { control } = useFormContext();
Expand Down Expand Up @@ -57,6 +59,7 @@ const TextInput: React.FC<React.PropsWithChildren<PropTypes>> = ({
onBlur={onBlur}
onChange={onChange}
ref={ref}
autoComplete={autoComplete}
{...tooltip}
/>
)}
Expand Down
1 change: 1 addition & 0 deletions src/common/hooks/useLinkPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const useLinkPath = (route: ROUTES): ((routeParams: RouteParams) => string) => {
[ROUTES.HANKE]: hankeTunnusReturnFunc,
[ROUTES.NEW_HANKE]: defaultReturnFunc,
[ROUTES.EDIT_HANKE]: hankeTunnusReturnFunc,
[ROUTES.ACCESS_RIGHTS]: hankeTunnusReturnFunc,
[ROUTES.PUBLIC_HANKKEET]: defaultReturnFunc,
[ROUTES.PUBLIC_HANKKEET_MAP]: defaultReturnFunc,
[ROUTES.PUBLIC_HANKKEET_LIST]: defaultReturnFunc,
Expand Down
7 changes: 6 additions & 1 deletion src/common/routes/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import Login from '../../domain/auth/components/Login';
import OidcCallback from '../../domain/auth/components/OidcCallback';
import { LOGIN_CALLBACK_PATH, LOGIN_PATH } from '../../domain/auth/constants';
import LocaleRoutes from './LocaleRoutes';
import { REDIRECT_PATH_KEY } from './constants';

const AppRoutes: React.FC<React.PropsWithChildren<unknown>> = () => {
const currentLocale = useLocale();
const redirectPath = sessionStorage.getItem(REDIRECT_PATH_KEY);

return (
<Routes>
Expand All @@ -17,7 +19,10 @@ const AppRoutes: React.FC<React.PropsWithChildren<unknown>> = () => {
{Object.values(LANGUAGES).map((locale) => (
<Route path={`/${locale}/*`} element={<LocaleRoutes />} key={locale} />
))}
<Route path="*" element={<Navigate to={`/${currentLocale}`} />} />
<Route
path="*"
element={<Navigate to={redirectPath ? redirectPath : `/${currentLocale}`} />}
/>
</Routes>
);
};
Expand Down
5 changes: 5 additions & 0 deletions src/common/routes/LocaleRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import ApplicationPage from '../../pages/ApplicationPage';
import EditJohtoselvitysPage from '../../pages/EditJohtoselvitysPage';
import NotFoundPage from '../../pages/staticPages/404Page';
import ManualPage from '../../pages/staticPages/ManualPage';
import AccessRightsPage from '../../pages/AccessRightsPage';

const LocaleRoutes = () => {
const { t } = useTranslation();
Expand All @@ -46,6 +47,10 @@ const LocaleRoutes = () => {
element={<PrivateRoute element={<HankePortfolioPage />} />}
/>
<Route path={t('routes:HANKE:path')} element={<PrivateRoute element={<HankePage />} />} />
<Route
path={t('routes:ACCESS_RIGHTS:path')}
element={<PrivateRoute element={<AccessRightsPage />} />}
/>
<Route path={t('routes:HAITATON_INFO:path')} element={<InfoPage />} />
<Route
path={t('routes:JOHTOSELVITYSHAKEMUS:path')}
Expand Down
21 changes: 17 additions & 4 deletions src/common/routes/PrivateRoute.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
import React from 'react';
import { Navigate } from 'react-router-dom';
import React, { useEffect } from 'react';
import { Navigate, useLocation } from 'react-router-dom';
import useUser from '../../domain/auth/useUser';
import { REDIRECT_PATH_KEY } from './constants';

type Props = {
element: JSX.Element;
};

const PrivateRoute: React.FC<React.PropsWithChildren<Props>> = ({ element }) => {
const { data: user } = useUser();
const isAuthenticated = Boolean(user?.profile);
const location = useLocation();

if (!user?.profile) {
return <Navigate to="/" />;
useEffect(() => {
if (isAuthenticated) {
sessionStorage.removeItem(REDIRECT_PATH_KEY);
}
}, [isAuthenticated]);

if (!isAuthenticated) {
// If user tries to access private route when not signed in,
// save URL path to session storage and navigate to login,
// so that user can be redirected to that route after login
sessionStorage.setItem(REDIRECT_PATH_KEY, location.pathname);
return <Navigate to="/login" />;
}

return element;
Expand Down
79 changes: 79 additions & 0 deletions src/common/routes/Routes.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React from 'react';
import { render, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { I18nextProvider } from 'react-i18next';
import { QueryClient, QueryClientProvider } from 'react-query';
import { InitialEntry } from '@remix-run/router';
import { User } from 'oidc-client';
import { Provider } from 'react-redux';
import AppRoutes from './AppRoutes';
import { FeatureFlagsProvider } from '../components/featureFlags/FeatureFlagsContext';
import { GlobalNotificationProvider } from '../components/globalNotification/GlobalNotificationContext';
import authService from '../../domain/auth/authService';
import { store } from '../redux/store';
import i18n from '../../locales/i18n';
import { REDIRECT_PATH_KEY } from './constants';

afterEach(() => {
sessionStorage.clear();
});

const path = '/fi/johtoselvityshakemus';

const mockUser: Partial<User> = {
id_token: 'fffff-aaaaaa-11111',
access_token: '.BnutWVN1x7RSAP5bU2a-tXdVPuof_9pBNd_Ozw',
profile: {
iss: '',
sub: '',
aud: '',
exp: 0,
iat: 0,
},
};

function getWrapper(routerInitialEntries?: InitialEntry[]) {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retryDelay: 0,
},
},
});

return render(
<MemoryRouter initialEntries={routerInitialEntries}>
<Provider store={store}>
<QueryClientProvider client={queryClient}>
<I18nextProvider i18n={i18n}>
<FeatureFlagsProvider>
<GlobalNotificationProvider>
<AppRoutes />
</GlobalNotificationProvider>
</FeatureFlagsProvider>
</I18nextProvider>
</QueryClientProvider>
</Provider>
</MemoryRouter>,
);
}

test('Should save path to session storage and navigate to login if trying to navigate to private route', async () => {
const login = jest.spyOn(authService, 'login').mockResolvedValue();

getWrapper([path]);

await waitFor(() => expect(login).toHaveBeenCalled());
expect(window.sessionStorage.getItem(REDIRECT_PATH_KEY)).toBe(path);
});

test('Should redirect to path stored in session storage after login if one exists', async () => {
sessionStorage.setItem(REDIRECT_PATH_KEY, path);
jest.spyOn(authService.userManager, 'getUser').mockResolvedValue(mockUser as User);

getWrapper();

await waitFor(() =>
expect(window.document.title).toBe('Haitaton - Luo uusi johtoselvityshakemus'),
);
});
1 change: 1 addition & 0 deletions src/common/routes/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const REDIRECT_PATH_KEY = 'redirect-path';
1 change: 1 addition & 0 deletions src/common/types/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export enum ROUTES {
PUBLIC_HANKKEET_LIST = 'PUBLIC_HANKKEET_LIST',
NEW_HANKE = 'NEW_HANKE',
EDIT_HANKE = 'EDIT_HANKE',
ACCESS_RIGHTS = 'ACCESS_RIGHTS',
FULL_PAGE_MAP = 'FULL_PAGE_MAP',
HAKEMUS = 'HAKEMUS',
JOHTOSELVITYSHAKEMUS = 'JOHTOSELVITYSHAKEMUS',
Expand Down
3 changes: 0 additions & 3 deletions src/domain/auth/authService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ describe('authService', () => {

afterEach(() => {
localStorage.clear();
// eslint-disable-next-line
// @ts-ignore
localStorage.setItem.mockClear();
jest.restoreAllMocks();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ function InformationViewContentContainer({

function InformationViewMainContent({
children,
className,
}: {
children: React.ReactNode | React.ReactNode[];
className?: string;
}) {
return <div className={clsx(styles.mainContent, styles.paddingLeft)}>{children}</div>;
return <div className={clsx(styles.mainContent, styles.paddingLeft, className)}>{children}</div>;
}

function InformationViewSidebar({ children }: { children: React.ReactNode | React.ReactNode[] }) {
Expand Down
72 changes: 72 additions & 0 deletions src/domain/hanke/accessRights/AccessRightsView.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
@import 'src/assets/styles/layout.scss';

.container {
background-color: var(--color-white);
height: 100%;
}

.header {
background-color: var(--color-summer-light);
padding-top: var(--spacing-2-xl);
padding-bottom: var(--spacing-xl);
}

.mainContent {
padding-top: var(--spacing-m);
padding-bottom: var(--spacing-m);
}

.hankeLink {
display: flex;
align-items: center;
gap: var(--spacing-2-xs);
margin-bottom: var(--spacing-m);

&:hover {
text-decoration: underline;
}
}

.infoAccordion {
margin-bottom: var(--spacing-xl);

.infoList {
padding-left: var(--spacing-m);

& > li {
margin-bottom: var(--spacing-2-xs);

&:last-child {
margin-bottom: var(--spacing-l);
}
}
}
}

.searchInput {
max-width: 328px;
margin-bottom: var(--spacing-m);
}

.table > div {
overflow-x: initial;

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

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

.pagination {
margin-top: var(--spacing-xs);
margin-bottom: var(--spacing-s);

* {
box-sizing: content-box;
}
}
Loading

0 comments on commit 345378d

Please sign in to comment.