Skip to content

Commit

Permalink
Add ReportsController and page
Browse files Browse the repository at this point in the history
  • Loading branch information
jsrobertson committed Sep 30, 2024
1 parent 1d0b938 commit b199698
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 0 deletions.
3 changes: 3 additions & 0 deletions server/controllers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import assessControllers from './assess'
import DashboardController from './dashboardController'
import findControllers from './find'
import referControllers from './refer'
import ReportsController from './reportsController'
import sharedControllers from './shared'

export const controllers = (services: Services) => {
const dashboardController = new DashboardController()
const reportsController = new ReportsController(services.statisticsService)

return {
dashboardController,
reportsController,
...assessControllers(services),
...findControllers(services),
...referControllers(services),
Expand Down
87 changes: 87 additions & 0 deletions server/controllers/reportsController.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { type DeepMocked, createMock } from '@golevelup/ts-jest'
import type { NextFunction, Request, Response } from 'express'

import type { StatisticsService } from '../services'
import ReportsController from './reportsController'
import { reportContentFactory } from '../testutils/factories'
import { StatisticsReportUtils } from '../utils'

jest.mock('../utils/statisticsReportUtils')

const mockStatisticsReportUtils = StatisticsReportUtils as jest.Mocked<typeof StatisticsReportUtils>

describe('ReportsController', () => {
const username = 'USERNAME'
const mockTodaysDate = new Date('2024-02-14')

let request: DeepMocked<Request>
let response: DeepMocked<Response>
const next: DeepMocked<NextFunction> = createMock<NextFunction>({})

const statisticsService = createMock<StatisticsService>({})

const reportDataBlock = {
date: 'January 2024',
testId: 'referral-count',
title: 'Total referrals submitted',
value: '123',
}

let controller: ReportsController

beforeEach(() => {
mockStatisticsReportUtils.reportContentDataBlock.mockReturnValue(reportDataBlock)

controller = new ReportsController(statisticsService)

request = createMock<Request>({
user: { username },
})
response = createMock<Response>()

jest.useFakeTimers().setSystemTime(mockTodaysDate)
})

afterEach(() => {
jest.resetAllMocks()
jest.useRealTimers()
})

describe('show', () => {
it('should render the reports/show view with the correct data', async () => {
statisticsService.getReport.mockResolvedValue(reportContentFactory.build())

const requestHandler = controller.show()
await requestHandler(request, response, next)

const expectedQuery = {
endDate: '2024-01-31',
startDate: '2024-01-01',
}

const reportTypes = [
'REFERRAL_COUNT',
'PROGRAMME_COMPLETE_COUNT',
'NOT_ELIGIBLE_COUNT',
'WITHDRAWN_COUNT',
'DESELECTED_COUNT',
]

expect(statisticsService.getReport).toHaveBeenCalledTimes(reportTypes.length)
reportTypes.forEach(reportType => {
expect(statisticsService.getReport).toHaveBeenCalledWith(username, reportType, expectedQuery)
})

expect(StatisticsReportUtils.reportContentDataBlock).toHaveBeenCalledTimes(reportTypes.length)
expect(StatisticsReportUtils.reportContentDataBlock).toHaveBeenCalledWith(
expect.anything(),
new Date(expectedQuery.startDate),
)

expect(response.render).toHaveBeenCalledWith('reports/show', {
pageHeading: 'Accredited Programmes data',
reportDataBlocks: [reportDataBlock, reportDataBlock, reportDataBlock, reportDataBlock, reportDataBlock],
})
})
})
})
42 changes: 42 additions & 0 deletions server/controllers/reportsController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { Request, Response, TypedRequestHandler } from 'express'

import type { StatisticsService } from '../services'
import { DateUtils, StatisticsReportUtils, TypeUtils } from '../utils'

export default class ReportsController {
constructor(private readonly statisticsService: StatisticsService) {}

show(): TypedRequestHandler<Request, Response> {
return async (req: Request, res: Response) => {
TypeUtils.assertHasUser(req)

const now = new Date()
const startDateOfLastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1)
const endDateOfLastMonth = new Date(now.getFullYear(), now.getMonth(), 0)

const reportTypes = [
'REFERRAL_COUNT',
'PROGRAMME_COMPLETE_COUNT',
'NOT_ELIGIBLE_COUNT',
'WITHDRAWN_COUNT',
'DESELECTED_COUNT',
]

const reports = await Promise.all(
reportTypes.map(reportType =>
this.statisticsService.getReport(req.user.username, reportType, {
endDate: DateUtils.isoDateOnly(endDateOfLastMonth),
startDate: DateUtils.isoDateOnly(startDateOfLastMonth),
}),
),
)

res.render('reports/show', {
pageHeading: 'Accredited Programmes data',
reportDataBlocks: reports.map(report =>
StatisticsReportUtils.reportContentDataBlock(report, startDateOfLastMonth),
),
})
}
}
}
2 changes: 2 additions & 0 deletions server/paths/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import findPaths from './find'
import prisonApiPaths from './prisonApi'
import prisonRegisterApiPaths from './prisonRegisterApi'
import referPaths, { referPathBase } from './refer'
import reportsPaths from './reports'

export {
apiPaths,
Expand All @@ -16,4 +17,5 @@ export {
prisonRegisterApiPaths,
referPathBase,
referPaths,
reportsPaths,
}
7 changes: 7 additions & 0 deletions server/paths/reports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { path } from 'static-path'

const reportsPathBase = path('/reports')

export default {
show: reportsPathBase,
}
2 changes: 2 additions & 0 deletions server/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import assessRoutes from './assess'
import editorRoutes from './editor'
import findRoutes from './find'
import referRoutes from './refer'
import reportsRoutes from './reports'
import config from '../config'
import type { Controllers } from '../controllers'
import { RouteUtils } from '../utils'
Expand All @@ -17,6 +18,7 @@ export default function routes(controllers: Controllers): Router {

editorRoutes(controllers, router)
findRoutes(controllers, router)
reportsRoutes(controllers, router)
if (config.flags.referEnabled) {
assessRoutes(controllers, router)
referRoutes(controllers, router)
Expand Down
14 changes: 14 additions & 0 deletions server/routes/reports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Router } from 'express'

import type { Controllers } from '../controllers'
import { reportsPaths } from '../paths'
import { RouteUtils } from '../utils'

export default function routes(controllers: Controllers, router: Router): Router {
const { get } = RouteUtils.actions(router)
const { reportsController } = controllers

get(reportsPaths.show.pattern, reportsController.show())

return router
}
31 changes: 31 additions & 0 deletions server/views/reports/show.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{% from "govuk/components/back-link/macro.njk" import govukBackLink %}
{% from "./_reportDataBlock.njk" import reportDataBlock %}

{% extends "../partials/layout.njk" %}

{% block backLink %}
{{ govukBackLink({
text: "Back",
href: "/"
}) }}
{% endblock backLink %}

{% block content %}
<h1 class="govuk-heading-l">{{ pageHeading }}</h1>

<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
{% if reportDataBlocks | length %}
{% for items in reportDataBlocks | slice(2) %}
<div class="govuk-grid-row govuk-!-margin-bottom-6">
{% for item in items %}
<div class="govuk-grid-column-one-third">
{{ reportDataBlock(item) }}
</div>
{% endfor %}
</div>
{% endfor %}
{% endif %}
</div>
</div>
{% endblock content %}

0 comments on commit b199698

Please sign in to comment.