diff --git a/designer/server/src/lib/metrics.js b/designer/server/src/lib/metrics.js index ab82b3c88..08e239bbf 100644 --- a/designer/server/src/lib/metrics.js +++ b/designer/server/src/lib/metrics.js @@ -1,5 +1,6 @@ import config from '~/src/config.js' -import { getJson } from '~/src/lib/fetch.js' +import { getJson, postJson } from '~/src/lib/fetch.js' +import { getHeaders } from '~/src/lib/utils.js' const metricsEndpoint = new URL('/report/', config.auditUrl) @@ -18,6 +19,17 @@ export async function getMetrics() { return body } +/** + * Regenerate the full set of metrics afresh (clears the 'mertics' DB and repopulates) + * @param {string} token + */ +export async function regenerateMetrics(token) { + const requestUrl = new URL('regenerate', metricsEndpoint) + await postJson(requestUrl, { + ...getHeaders(token) + }) +} + /** * @import { FormOverviewMetric, FormTotalsMetric } from '@defra/forms-model' */ diff --git a/designer/server/src/lib/metrics.test.js b/designer/server/src/lib/metrics.test.js index a096ffecb..6e25a6263 100644 --- a/designer/server/src/lib/metrics.test.js +++ b/designer/server/src/lib/metrics.test.js @@ -1,9 +1,10 @@ import config from '~/src/config.js' import { createMockResponse, - mockedGetJson + mockedGetJson, + mockedPostJson } from '~/src/lib/__stubs__/editor.js' -import { getMetrics } from '~/src/lib/metrics.js' +import { getMetrics, regenerateMetrics } from '~/src/lib/metrics.js' jest.mock('~/src/lib/fetch.js') @@ -23,4 +24,18 @@ describe('metrics.js', () => { expect(mockedGetJson).toHaveBeenCalledWith(expectedUrl) }) }) + + describe('regenerateMetrics', () => { + it('should call endpoint', async () => { + mockedPostJson.mockResolvedValueOnce({ + response: createMockResponse(), + body: {} + }) + const expectedUrl = new URL('/report/regenerate', auditEndpoint) + await regenerateMetrics('token') + expect(mockedPostJson).toHaveBeenCalledWith(expectedUrl, { + headers: { Authorization: 'Bearer token' } + }) + }) + }) }) diff --git a/designer/server/src/models/admin/metrics-helper.js b/designer/server/src/models/admin/metrics-helper.js index 78e214a03..070d78f67 100644 --- a/designer/server/src/models/admin/metrics-helper.js +++ b/designer/server/src/models/admin/metrics-helper.js @@ -17,11 +17,12 @@ const formStructureMetricNames = questionTypes: 'Question types per form' }) -const straplineWording = { - [FormMetricName.NewFormsCreated]: { noun: 'form', verb: 'created' }, - [FormMetricName.FormsPublished]: { noun: 'form', verb: 'published' }, - [FormMetricName.Submissions]: { noun: 'submission', verb: '' } -} +const straplineWording = + /** @type {Record} */ ({ + [FormMetricName.NewFormsCreated]: { noun: 'form', verb: 'created' }, + [FormMetricName.FormsPublished]: { noun: 'form', verb: 'published' }, + [FormMetricName.Submissions]: { noun: 'submission', verb: '' } + }) /** * @typedef PeriodName * @property {string} ariaPeriodName - period name within aria label diff --git a/designer/server/src/routes/admin/__snapshots__/form-metrics.test.js.snap b/designer/server/src/routes/admin/__snapshots__/form-metrics.test.js.snap index ecf2e9000..a8ac41141 100644 --- a/designer/server/src/routes/admin/__snapshots__/form-metrics.test.js.snap +++ b/designer/server/src/routes/admin/__snapshots__/form-metrics.test.js.snap @@ -1,6 +1,284 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Form metrics routes GET should render form 1`] = ` +exports[`Form metrics routes form-metrics should render regenerate form 1`] = ` +" + + + + Admin tools - Defra Form Designer metrics - Forms designer + + + + + + + + + + + + + + + + + + Skip to main content + + + + + + +
+ + + + + +
+
+
+ +
+

+ Defra Form Designer metrics +

+
+
+
+
+
+ + +
+
+
+ +
+

Regenerating metrics

+

This will clear out the 'metrics' collection in the 'forms-audit-api' database, and regenerate a full history of metrics.

+ + +
+ + +
+
+ +
+ + +
+ + + + + + + + + + + +" +`; + +exports[`Form metrics routes form-metrics should render report form 1`] = ` " diff --git a/designer/server/src/routes/admin/form-metrics.js b/designer/server/src/routes/admin/form-metrics.js index a6a767a91..999f0e314 100644 --- a/designer/server/src/routes/admin/form-metrics.js +++ b/designer/server/src/routes/admin/form-metrics.js @@ -1,13 +1,15 @@ import { Scopes } from '@defra/forms-model' +import { StatusCodes } from 'http-status-codes' import { buildAdminNavigation } from '~/src/common/nunjucks/context/build-navigation.js' -import { getMetrics } from '~/src/lib/metrics.js' +import { getMetrics, regenerateMetrics } from '~/src/lib/metrics.js' import { metricsComponentUsageViewModel, metricsFormActivityViewModel } from '~/src/models/admin/metrics.js' -export const ROUTE_FULL_PATH = '/admin/form-metrics/{tab?}' +const ROUTE_FULL_PATH = '/admin/form-metrics/{tab?}' +const ROUTE_ADMIN_INDEX = '/admin/index' const ADMIN_TOOLS = 'Admin tools' const METRICS_TITLE = 'Defra Form Designer metrics' @@ -40,7 +42,7 @@ export default [ pageHeading: { text: METRICS_TITLE }, backLink: { text: 'Back to admin tools', - href: '/admin/index' + href: ROUTE_ADMIN_INDEX }, navigation, model @@ -52,6 +54,53 @@ export default [ access: { entity: 'user', scope: [`+${Scopes.FormsReport}`] } } } + }), + + /** + * @satisfies {ServerRoute} + */ + ({ + method: 'GET', + path: '/admin/form-metrics-regenerate', + handler(_request, h) { + const navigation = buildAdminNavigation(ADMIN_TOOLS) + + return h.view('admin/form-metrics-regenerate', { + pageTitle: `${ADMIN_TOOLS} - ${METRICS_TITLE}`, + pageHeading: { text: METRICS_TITLE }, + backLink: { + text: 'Back to admin tools', + href: ROUTE_ADMIN_INDEX + }, + navigation + }) + }, + options: { + auth: { + mode: 'required', + access: { entity: 'user', scope: [`+${Scopes.RegenerateMetrics}`] } + } + } + }), + + /** + * @satisfies {ServerRoute} + */ + ({ + method: 'POST', + path: '/admin/form-metrics-regenerate', + async handler(request, h) { + const { auth } = request + const { token } = auth.credentials + await regenerateMetrics(token) + return h.redirect(ROUTE_ADMIN_INDEX).code(StatusCodes.SEE_OTHER) + }, + options: { + auth: { + mode: 'required', + access: { entity: 'user', scope: [`+${Scopes.RegenerateMetrics}`] } + } + } }) ] diff --git a/designer/server/src/routes/admin/form-metrics.test.js b/designer/server/src/routes/admin/form-metrics.test.js index e254e7549..ca7502141 100644 --- a/designer/server/src/routes/admin/form-metrics.test.js +++ b/designer/server/src/routes/admin/form-metrics.test.js @@ -24,8 +24,8 @@ describe('Form metrics routes', () => { jest.clearAllMocks() }) - describe('GET', () => { - test('should render form', async () => { + describe('form-metrics', () => { + test('should render report form', async () => { const mockMetrics = { overview: [], totals: /** @type {FormTotalsMetric} */ ({ @@ -68,6 +68,45 @@ describe('Form metrics routes', () => { expect(response.headers['content-type']).toContain('text/html') expect(response.result).toMatchSnapshot() }) + + test('should render regenerate form', async () => { + const options = { + method: 'get', + url: '/admin/form-metrics-regenerate', + auth + } + + const { response, container } = await renderResponse(server, options) + + const $mastheadHeading = container.getByRole('heading', { level: 1 }) + + expect($mastheadHeading).toHaveTextContent('Defra Form Designer metrics') + expect($mastheadHeading).toHaveClass('govuk-heading-xl') + + const $headings2 = container.getAllByRole('heading', { level: 2 }) + + expect($headings2[0]).toHaveTextContent('Regenerating metrics') + expect($headings2[0]).toHaveClass('govuk-heading-l') + + expect(response.statusCode).toEqual(StatusCodes.OK) + expect(response.headers['content-type']).toContain('text/html') + expect(response.result).toMatchSnapshot() + }) + + test('should post and redirect', async () => { + const options = { + method: 'post', + url: '/admin/form-metrics-regenerate', + auth + } + + const { + response: { statusCode, headers } + } = await renderResponse(server, options) + + expect(statusCode).toBe(StatusCodes.SEE_OTHER) + expect(headers.location).toBe('/admin/index') + }) }) }) diff --git a/designer/server/src/views/admin/form-metrics-regenerate.njk b/designer/server/src/views/admin/form-metrics-regenerate.njk new file mode 100644 index 000000000..f7fe1dca0 --- /dev/null +++ b/designer/server/src/views/admin/form-metrics-regenerate.njk @@ -0,0 +1,31 @@ +{% extends "layouts/page.njk" %} + +{% from "page-body/macro.njk" import appPageBody %} +{% from "govuk/components/button/macro.njk" import govukButton %} + +{% set mainClasses = "govuk-main-wrapper--masthead" %} + +{% block content %} + {% call appPageBody({ + heading: pageHeading, + caption: caption, + useNewMasthead: true, + classes: "govuk-grid-column-full", + backLink: backLink + }) %} + +
+

Regenerating metrics

+

This will clear out the 'metrics' collection in the 'forms-audit-api' database, and regenerate a full history of metrics.

+ + {{ govukButton({ + text: 'Regenerate metrics' + }) }} +
+ + {% endcall %} + {% endblock %} + + {% block mainEnd %} + {{ super() }} + {% endblock %} diff --git a/designer/server/test/fixtures/auth.js b/designer/server/test/fixtures/auth.js index b370bb482..57e1c584e 100644 --- a/designer/server/test/fixtures/auth.js +++ b/designer/server/test/fixtures/auth.js @@ -115,7 +115,8 @@ export const authSuperAdmin = { Scopes.ResetSaveAndExit, Scopes.DeadLetterQueues, Scopes.FormsInspect, - Scopes.FormsReport + Scopes.FormsReport, + Scopes.RegenerateMetrics ] }) } diff --git a/model/src/form/form-metrics/enums.ts b/model/src/form/form-metrics/enums.ts index 1c9b4553a..5696f728f 100644 --- a/model/src/form/form-metrics/enums.ts +++ b/model/src/form/form-metrics/enums.ts @@ -7,5 +7,7 @@ export enum FormMetricType { export enum FormMetricName { NewFormsCreated = 'NewFormsCreated', FormsPublished = 'FormsPublished', - Submissions = 'Submissions' + Submissions = 'Submissions', + FormsInDraft = 'FormsInDraft', + TimeToPublish = 'TimeToPublish' } diff --git a/model/src/manage/roles.test.ts b/model/src/manage/roles.test.ts index 4d590f64d..acfabaaaa 100644 --- a/model/src/manage/roles.test.ts +++ b/model/src/manage/roles.test.ts @@ -16,7 +16,8 @@ describe('Scopes', () => { Scopes.ResetSaveAndExit, Scopes.DeadLetterQueues, Scopes.FormsInspect, - Scopes.FormsReport + Scopes.FormsReport, + Scopes.RegenerateMetrics ]) }) diff --git a/model/src/manage/roles.ts b/model/src/manage/roles.ts index 3b56dc21a..0ecc3a572 100644 --- a/model/src/manage/roles.ts +++ b/model/src/manage/roles.ts @@ -19,7 +19,8 @@ export enum Scopes { ResetSaveAndExit = 'reset-save-and-exit', DeadLetterQueues = 'dead-letter-queues', FormsInspect = 'forms-inspect', - FormsReport = 'forms-report' + FormsReport = 'forms-report', + RegenerateMetrics = 'regenerate-metrics' } export const RoleScopes = {