Skip to content

Commit

Permalink
Merge branch 'develop' into feat/GAP-2420
Browse files Browse the repository at this point in the history
  • Loading branch information
jgunnCO authored Mar 27, 2024
2 parents aef2454 + 1aa4bba commit 630a35f
Show file tree
Hide file tree
Showing 229 changed files with 9,753 additions and 2,423 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/admin-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ on:
pull_request:
branches:
- develop
- feat/**
- feature/**
paths:
- "packages/admin/**"
- "packages/gap-web-ui/**"
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/applicant-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ on:
pull_request:
branches:
- develop
- feat/**
- feature/**
paths:
- "packages/applicant/**"
- "packages/gap-web-ui/**"
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/gap-web-ui-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ on:
pull_request:
branches:
- develop
- feat/**
- feature/**
paths:
- "packages/gap-web-ui/**"
- ".github/workflows/gap-web-ui-ci.yml"
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
18.17.0
18.19.1
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ARG NODE_VERSION=18.17.0
FROM --platform=linux/amd64 node:${NODE_VERSION}-alpine as build
ARG IMAGE_NAME=18.19-alpine
FROM --platform=linux/amd64 node:${IMAGE_NAME} as build

ARG APP_NAME

Expand All @@ -20,7 +20,7 @@ RUN yarn workspace gap-web-ui build

RUN yarn workspace ${APP_NAME} build

FROM --platform=linux/amd64 node:${NODE_VERSION}-alpine
FROM --platform=linux/amd64 node:${IMAGE_NAME}

ARG APP_NAME

Expand Down
8 changes: 7 additions & 1 deletion packages/admin/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@ CYPRESS_WIREMOCK_BASE_URL=http://localhost:8888/__admin
CYPRESS_DATABASE_URL=postgres://postgres:postgres@localhost:5432
CYPRESS_USER_SERVICE_DB_NAME=gapuserlocaldb
VALIDATE_USER_ROLES_IN_MIDDLEWARE=true
SUPER_ADMIN_DASHBOARD_URL=http://localhost:3001/apply/admin/super-admin-dashboard
SUPER_ADMIN_DASHBOARD_URL=http://localhost:3001/apply/admin/super-admin-dashboard
ENCRYPTION_KEY_NAME=encryption-key-name
ENCRYPTION_KEY_NAMESPACE=encryption-key-namespace
ENCRYPTION_WRAPPING_KEY=encryption-wrapping-key
ENCRYPTION_STAGE=encryption-stage
ENCRYPTION_ORIGIN=eu-west-2
ENCRYPTION_GENERATOR_KEY=encryption-generator-key
Binary file modified packages/admin/public/assets/images/favicon.ico
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified packages/admin/public/assets/images/govuk-apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 1 addition & 7 deletions packages/admin/public/assets/images/govuk-mask-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified packages/admin/public/assets/images/govuk-opengraph-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified packages/admin/public/favicon.ico
Binary file not shown.
23 changes: 23 additions & 0 deletions packages/admin/src/components/layout/Header.test.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import Header from './Header';
import { useAdminAuth } from '../../pages/_app.page';

jest.mock('../../pages/_app.page');

const mockUseAdminAuth = jest.mocked(useAdminAuth);

describe('Testing Header component', () => {
it('Renders a sign out button', () => {
mockUseAdminAuth.mockReturnValue({ isSuperAdmin: false });
render(<Header />);
screen.getByRole('link', { name: 'Sign out' });
});

it('should render Beta block', () => {
mockUseAdminAuth.mockReturnValue({ isSuperAdmin: false });
render(<Header />);
screen.getByText(/beta/i);
expect(
Expand All @@ -21,3 +28,19 @@ describe('Testing Header component', () => {
);
});
});

describe('Testing SuperAdmin Dashboard link', () => {
it('It should render SuperAdmin Dashboard link', () => {
mockUseAdminAuth.mockReturnValue({ isSuperAdmin: true });
render(<Header />);
expect(screen.getByText('Superadmin Dashboard')).toBeVisible();
expect(
screen.getByRole('link', { name: 'Superadmin Dashboard' })
).toBeInTheDocument();
});
it('It should NOT render SuperAdmin Dashboard link', () => {
mockUseAdminAuth.mockReturnValue({ isSuperAdmin: false });
render(<Header />);
expect(screen.queryByText('Superdmin Dashboard')).toBeNull();
});
});
27 changes: 20 additions & 7 deletions packages/admin/src/components/layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import Image from 'next/image';
import { isIE } from 'react-device-detect';
import getConfig from 'next/config';
import CustomLink from '../custom-link/CustomLink';
import { useAdminAuth } from '../../pages/_app.page';

const Header = () => {
const feedbackContent = `https://docs.google.com/forms/d/e/1FAIpQLSd2V0IqOMpb2_yQnz_Ges0WCYFnDOTxZpF299gePV1j8kMdLA/viewform`;
const { publicRuntimeConfig } = getConfig();
const { isSuperAdmin } = useAdminAuth();
return (
<>
<header className="govuk-header" role="banner" data-module="govuk-header">
Expand Down Expand Up @@ -61,16 +63,27 @@ const Header = () => {
</a>
</div>
<div className="govuk-header__content">
<a
href={`${publicRuntimeConfig.SUB_PATH}/dashboard`}
className="govuk-header__link govuk-header__link--service-name"
>
Manage a grant
</a>
<div className="govuk-header__content">
<a
href={`${publicRuntimeConfig.SUB_PATH}/dashboard`}
className="govuk-header__link govuk-header__link--service-name"
>
Manage a grant
</a>
</div>
{isSuperAdmin && (
<div className="super-admin-link govuk-!-padding-top-2">
<a
href={`${publicRuntimeConfig.SUB_PATH}/super-admin-dashboard`}
className="govuk-header__link govuk-!-margin-left-9 govuk-!-font-weight-bold"
>
Superadmin Dashboard
</a>
</div>
)}
</div>
</div>
</header>

<nav>
<div className="govuk-width-container">
<div className="govuk-phase-banner">
Expand Down
5 changes: 4 additions & 1 deletion packages/admin/src/components/pagination/Pagination.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const Pagination = ({
itemsPerPage = 10,
totalItems = 0,
itemType = 'items',
itemCountMargin = false,
}) => {
const router = useRouter();

Expand Down Expand Up @@ -110,7 +111,9 @@ const Pagination = ({
</>
)}
<p
className="moj-pagination__results"
className={`moj-pagination__results ${
itemCountMargin ? 'govuk-!-margin-top-9' : ''
}`}
data-cy="cyPaginationShowingGrants"
style={{
paddingTop: '0.6rem',
Expand Down
1 change: 1 addition & 0 deletions packages/admin/src/enums/ExportStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ enum ExportStatusEnum {
ERROR = 'ERROR',
REQUESTED = 'REQUESTED',
EXPIRED = 'EXPIRED',
FAILED = 'FAILED',
}

export default ExportStatusEnum;
19 changes: 14 additions & 5 deletions packages/admin/src/middleware.page.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// eslint-disable-next-line @next/next/no-server-import-in-page
import { NextRequest, NextResponse } from 'next/server';
import { getLoginUrl } from './utils/general';
import { NextRequest, NextResponse, URLPattern } from 'next/server';
import { isAdminSessionValid } from './services/UserService';
import { csrfMiddleware } from './utils/csrfMiddleware';
import { getLoginUrl } from './utils/general';

// It will apply the middleware to all those paths
// (if new folders at page root are created, they need to be included here)
Expand Down Expand Up @@ -54,9 +54,18 @@ export async function middleware(req: NextRequest) {
});

res.headers.set('Cache-Control', 'no-store');

return res;
} else {
return NextResponse.redirect(getLoginUrl());
}
let url = getLoginUrl();
console.log('Middleware redirect URL: ' + url);
if (submissionDownloadPattern.test({ pathname: req.nextUrl.pathname })) {
url = url + req.nextUrl.pathname;
console.log('Getting submission export download redirect URL: ' + url);
}
console.log('Final redirect URL from admin middleware: ' + url);
return NextResponse.redirect(url);
}

const submissionDownloadPattern = new URLPattern({
pathname: '/scheme/:schemeId([0-9]+)/:exportBatchUuid([0-9a-f-]+)',
});
16 changes: 10 additions & 6 deletions packages/admin/src/middleware.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import '@testing-library/jest-dom';
import { middleware } from './middleware.page';
// eslint-disable-next-line @next/next/no-server-import-in-page
import { ResponseCookie } from 'next/dist/compiled/@edge-runtime/cookies';
// eslint-disable-next-line @next/next/no-server-import-in-page
import { NextRequest, NextResponse } from 'next/server';
import { middleware } from './middleware.page';
import { isAdminSessionValid } from './services/UserService';
import { getLoginUrl } from './utils/general';
import {
RequestCookie,
ResponseCookie,
} from 'next/dist/compiled/@edge-runtime/cookies';

jest.mock('./utils/csrfMiddleware');
jest.mock('./utils/general');
jest.mock('./services/UserService', () => ({
isAdminSessionValid: jest.fn(),
}));

jest.mock('next/server', () => ({
...jest.requireActual('next/server'),
URLPattern: jest.fn().mockImplementation(() => ({
test: jest.fn(),
})),
}));

describe('middleware', () => {
const req = new NextRequest('http://localhost:3000/apply/test/destination');

Expand Down
29 changes: 23 additions & 6 deletions packages/admin/src/pages/_app.page.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@ import App from 'next/app';
import getConfig from 'next/config';
import Script from 'next/script';
import nookies from 'nookies';
import { useEffect } from 'react';
import { createContext, useContext, useEffect } from 'react';
import TagManager from 'react-gtm-module';
import '../../../../node_modules/gap-web-ui/dist/cjs/index.css';
import Layout from '../components/layout/Layout';
import '../lib/ie11_nodelist_polyfill';
import '../styles/globals.scss';
import { getUserRoles } from '../services/UserService';

const MyApp = ({ Component, pageProps, cookies }) => {
const USER_TOKEN_NAME = process.env.JWT_COOKIE_NAME;
export const AuthContext = createContext({
isSuperAdmin: false,
});
export const useAdminAuth = () => useContext(AuthContext);

const MyApp = ({ Component, pageProps, cookies, isSuperAdmin }) => {
const { publicRuntimeConfig } = getConfig();

const showCookieBanner = !cookies.design_system_cookies_policy;
Expand Down Expand Up @@ -40,20 +47,30 @@ const MyApp = ({ Component, pageProps, cookies }) => {
src={`${publicRuntimeConfig.SUB_PATH}/javascript/govuk.js`}
strategy="beforeInteractive"
/>
<Layout showCookieBanner={showCookieBanner}>
<Component {...pageProps} />
</Layout>
<AuthContext.Provider value={{ isSuperAdmin }}>
<Layout showCookieBanner={showCookieBanner}>
<Component {...pageProps} />
</Layout>
</AuthContext.Provider>
</>
);
};

MyApp.getInitialProps = async (appContext) => {
const appProps = await App.getInitialProps(appContext);
const { req } = appContext.ctx;
const userServiceToken = req.cookies[USER_TOKEN_NAME];
const cookies =
typeof window === 'undefined'
? appContext.ctx.req.cookies
: nookies.get({});
return { ...appProps, cookies };

try {
const isSuperAdmin = (await getUserRoles(userServiceToken)).isSuperAdmin;
return { ...appProps, cookies, isSuperAdmin };
} catch (e) {
return { ...appProps, cookies, isSuperAdmin: false };
}
};

export default MyApp;
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import {
downloadSummary,
getApplicationFormSummary,
} from '../../../../services/ApplicationService';
import { getSessionIdFromCookies } from '../../../../utils/session';
import { APIGlobalHandler } from '../../../../utils/apiErrorHandler';

async function handler(req: NextApiRequest, res: NextApiResponse) {
const sessionCookie = getSessionIdFromCookies(req);
const applicationId = (req.query.applicationId || '').toString();

const { data } = await downloadSummary(applicationId, sessionCookie);

const application = await getApplicationFormSummary(
applicationId,
sessionCookie,
false,
false
);
const applicationName = application.applicationName
.substring(0, 100)
.replaceAll(new RegExp('[^a-zA-Z0-9\\-\\.()]', 'g'), '_');
const filename = `${applicationName}_questions.odt`;

res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', `attachment; filename=${filename}`);
res.send(Buffer.from(data));
}

export default (req: NextApiRequest, res: NextApiResponse) =>
APIGlobalHandler(req, res, handler);
Loading

0 comments on commit 630a35f

Please sign in to comment.