From c4ef5a9955bb362e818013a5ba3528f6ca084e77 Mon Sep 17 00:00:00 2001 From: Jez Barnsley Date: Thu, 14 May 2026 15:06:53 +0100 Subject: [PATCH 1/3] Stash for rebase --- .../repositories/aggregation/form-metadata-aggregation.js | 5 +++-- src/api/forms/repositories/aggregation/types.js | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/api/forms/repositories/aggregation/form-metadata-aggregation.js b/src/api/forms/repositories/aggregation/form-metadata-aggregation.js index d5a25bf8..9c239519 100644 --- a/src/api/forms/repositories/aggregation/form-metadata-aggregation.js +++ b/src/api/forms/repositories/aggregation/form-metadata-aggregation.js @@ -1,4 +1,4 @@ -import { FormStatus } from '@defra/forms-model' +import { FormExtendedStatus, FormStatus } from '@defra/forms-model' import { ObjectId } from 'mongodb' import { escapeRegExp } from '~/src/helpers/string-utils.js' @@ -341,7 +341,8 @@ export function processFilterResults(filterResults) { return { authors: processAuthorNames(filterResults.authors), organisations: filterResults.organisations.map((org) => org.name), - statuses: filterResults.status.at(0)?.statuses ?? [] + statuses: filterResults.status.at(0)?.statuses ?? [], + extendedStatuses: [FormExtendedStatus.Offline] } } diff --git a/src/api/forms/repositories/aggregation/types.js b/src/api/forms/repositories/aggregation/types.js index df384dfe..2df81039 100644 --- a/src/api/forms/repositories/aggregation/types.js +++ b/src/api/forms/repositories/aggregation/types.js @@ -16,6 +16,7 @@ * @property {{ name: string }[]} authors - Array of author names * @property {{ name: string }[]} organisations - Array of organisation names * @property {[{ statuses: FormStatus[] }]} status - Array containing status values + * @property {[{ extendedStatuses: FormExtendedStatus[] }]} extendedStatus - Array containing extended status values */ /** @@ -79,5 +80,5 @@ */ /** - * @import { SearchOptions, FormStatus } from '@defra/forms-model' + * @import { SearchOptions, FormStatus, FormExtendedStatus } from '@defra/forms-model' */ From 5f3eab72409e3f3edd4656c94dbab3b8ae515fba Mon Sep 17 00:00:00 2001 From: Jez Barnsley Date: Fri, 15 May 2026 09:31:38 +0100 Subject: [PATCH 2/3] Add 'offline' as filter option --- .../aggregation/form-metadata-aggregation.js | 34 +++++++++++++------ .../form-metadata-aggregation.test.js | 30 +++++++++++----- .../forms/repositories/aggregation/types.js | 5 ++- src/routes/forms.test.js | 4 +-- 4 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/api/forms/repositories/aggregation/form-metadata-aggregation.js b/src/api/forms/repositories/aggregation/form-metadata-aggregation.js index 9c239519..8671aeb7 100644 --- a/src/api/forms/repositories/aggregation/form-metadata-aggregation.js +++ b/src/api/forms/repositories/aggregation/form-metadata-aggregation.js @@ -1,4 +1,4 @@ -import { FormExtendedStatus, FormStatus } from '@defra/forms-model' +import { FormExtendedStatus } from '@defra/forms-model' import { ObjectId } from 'mongodb' import { escapeRegExp } from '~/src/helpers/string-utils.js' @@ -31,11 +31,15 @@ export function buildFilterConditions(options) { } if (status && status.length > 0) { - conditions.$or = status.map((s) => - s === FormStatus.Live - ? { live: { $exists: true } } - : { live: { $exists: false } } - ) + conditions.$or = status.map((s) => { + if (s === FormExtendedStatus.Live) { + return { live: { $exists: true } } + } else if (s === FormExtendedStatus.Offline) { + return { offline: true } + } else { + return { live: { $exists: false } } + } + }) } return conditions @@ -74,7 +78,16 @@ export function buildFiltersFacet() { _id: null, // Single group for all documents statuses: { $addToSet: { - $cond: [{ $ifNull: ['$live', false] }, 'live', 'draft'] // If live field exists, status is 'live', else 'draft' + $switch: { + // If offline === true, status is 'offline' + // If live field exists, status is 'live' + // Otherwise 'draft' + branches: [ + { case: { $eq: ['$offline', true] }, then: 'offline' }, + { case: { $ifNull: ['$live', false] }, then: 'live' } + ], + default: 'draft' + } } } } @@ -92,7 +105,7 @@ export function buildFiltersFacet() { * @param {string} title - The title to filter by. * @param {string} author - The author to filter by. * @param {string[]} organisations - The organisations to filter by. - * @param {FormStatus[]} status - The status values to filter by. + * @param {FormExtendedStatus[]} status - The status values to filter by. * @returns {{ pipeline: PipelineStage[], aggOptions: AggregateOptions }} */ export function buildAggregationPipeline( @@ -134,7 +147,7 @@ export function buildAggregationPipeline( * @param {string} title - The title to filter by. * @param {string} author - The author to filter by. * @param {string[]} organisations - The organisations to filter by. - * @param {FormStatus[]} status - The status values to filter by. + * @param {FormExtendedStatus[]} status - The status values to filter by. * @returns {{ pipeline: PipelineStage[], aggOptions: AggregateOptions }} */ export function buildAggregationPipelineWithVersions( @@ -341,8 +354,7 @@ export function processFilterResults(filterResults) { return { authors: processAuthorNames(filterResults.authors), organisations: filterResults.organisations.map((org) => org.name), - statuses: filterResults.status.at(0)?.statuses ?? [], - extendedStatuses: [FormExtendedStatus.Offline] + statuses: filterResults.status.at(0)?.statuses ?? [] } } diff --git a/src/api/forms/repositories/aggregation/form-metadata-aggregation.test.js b/src/api/forms/repositories/aggregation/form-metadata-aggregation.test.js index 9eb70b10..70d1288c 100644 --- a/src/api/forms/repositories/aggregation/form-metadata-aggregation.test.js +++ b/src/api/forms/repositories/aggregation/form-metadata-aggregation.test.js @@ -1,4 +1,4 @@ -import { FormStatus } from '@defra/forms-model' +import { FormExtendedStatus } from '@defra/forms-model' import { ObjectId } from 'mongodb' import { @@ -70,14 +70,18 @@ describe('Form metadata aggregation', () => { describe('with status filter', () => { it('should create status filter for live forms', () => { - const result = buildFilterConditions({ status: [FormStatus.Live] }) + const result = buildFilterConditions({ + status: [FormExtendedStatus.Live] + }) expect(result).toEqual({ $or: [{ live: { $exists: true } }] }) }) it('should create status filter for draft forms', () => { - const result = buildFilterConditions({ status: [FormStatus.Draft] }) + const result = buildFilterConditions({ + status: [FormExtendedStatus.Draft] + }) expect(result).toEqual({ $or: [{ live: { $exists: false } }] }) @@ -87,7 +91,7 @@ describe('Form metadata aggregation', () => { describe('with multiple status values', () => { it('should create combined status filter', () => { const result = buildFilterConditions({ - status: [FormStatus.Live, FormStatus.Draft] + status: [FormExtendedStatus.Live, FormExtendedStatus.Draft] }) expect(result).toEqual({ $or: [{ live: { $exists: true } }, { live: { $exists: false } }] @@ -101,7 +105,7 @@ describe('Form metadata aggregation', () => { title: 'Wildlife Permit Application', author: 'Henrique Silva', organisations: ['Natural England', 'Defra'], - status: [FormStatus.Live] + status: [FormExtendedStatus.Live] }) expect(result).toEqual({ @@ -141,7 +145,7 @@ describe('Form metadata aggregation', () => { 'Wildlife Permit Application', 'Henrique', ['Defra'], - [FormStatus.Live] + [FormExtendedStatus.Live] ) expect(pipeline[0]).toHaveProperty('$match') @@ -208,7 +212,7 @@ describe('Form metadata aggregation', () => { 'Wildlife Permit Application', 'Henrique', ['Defra'], - [FormStatus.Live] + [FormExtendedStatus.Live] ) expect(pipeline[0]).toHaveProperty('$match') @@ -455,7 +459,13 @@ describe('Form metadata aggregation', () => { _id: null, statuses: { $addToSet: { - $cond: [{ $ifNull: ['$live', false] }, 'live', 'draft'] + $switch: { + branches: [ + { case: { $eq: ['$offline', true] }, then: 'offline' }, + { case: { $ifNull: ['$live', false] }, then: 'live' } + ], + default: 'draft' + } } } } @@ -498,7 +508,9 @@ describe('Form metadata aggregation', () => { { name: 'Sarah Wilson (Natural England)' } ], organisations: [{ name: 'Defra' }, { name: 'Natural England' }], - status: [{ statuses: [FormStatus.Live, FormStatus.Draft] }] + status: [ + { statuses: [FormExtendedStatus.Live, FormExtendedStatus.Draft] } + ] } const result = processFilterResults(filterResults) diff --git a/src/api/forms/repositories/aggregation/types.js b/src/api/forms/repositories/aggregation/types.js index 2df81039..7ea93a71 100644 --- a/src/api/forms/repositories/aggregation/types.js +++ b/src/api/forms/repositories/aggregation/types.js @@ -3,7 +3,7 @@ * @property {{ $regex: RegExp }} [title] - Optional MongoDB regex query for title matching * @property {{ displayName: { $regex: RegExp } }} [createdBy] - Optional MongoDB regex query for author matching * @property {{ $in: string[] }} [organisation] - Optional MongoDB $in query for organisation matching - * @property {{ live: { $exists: boolean } }[]} [$or] - Optional MongoDB $or query for status matching + * @property {{ live?: { $exists: boolean }, offline?: boolean }[]} [$or] - Optional MongoDB $or query for status matching * @property {object} [$expr] - Optional MongoDB $expr for expression queries */ @@ -15,8 +15,7 @@ * @typedef {object} FilterAggregationResult * @property {{ name: string }[]} authors - Array of author names * @property {{ name: string }[]} organisations - Array of organisation names - * @property {[{ statuses: FormStatus[] }]} status - Array containing status values - * @property {[{ extendedStatuses: FormExtendedStatus[] }]} extendedStatus - Array containing extended status values + * @property {[{ statuses: FormExtendedStatus[] }]} status - Array containing status values */ /** diff --git a/src/routes/forms.test.js b/src/routes/forms.test.js index bb7eb6d3..ce37169c 100644 --- a/src/routes/forms.test.js +++ b/src/routes/forms.test.js @@ -1433,7 +1433,7 @@ describe('Forms route', () => { expect(response.headers['content-type']).toContain(jsonContentType) expect(response.result).toMatchObject({ error: 'Bad Request', - message: '"status" must be one of [draft, live]', + message: '"status" must be one of [draft, live, offline]', validation: { keys: ['status'], source: 'query' @@ -1453,7 +1453,7 @@ describe('Forms route', () => { expect(response.result).toMatchObject({ error: 'Bad Request', message: - '"organisations" must be one of [Animal and Plant Health Agency – APHA, Centre for Environment, Fisheries and Aquaculture Science – Cefas, Defra, Environment Agency, Forestry Commission, Marine Management Organisation – MMO, Natural England, Rural Payments Agency – RPA, Veterinary Medicines Directorate – VMD]. "status" must be one of [draft, live]', + '"organisations" must be one of [Animal and Plant Health Agency – APHA, Centre for Environment, Fisheries and Aquaculture Science – Cefas, Defra, Environment Agency, Forestry Commission, Marine Management Organisation – MMO, Natural England, Rural Payments Agency – RPA, Veterinary Medicines Directorate – VMD]. "status" must be one of [draft, live, offline]', validation: { keys: ['organisations', 'status'], source: 'query' From 6c3a2b2c75bcfb8cfd7ca5749140ea1a205eb7e0 Mon Sep 17 00:00:00 2001 From: Jez Barnsley Date: Fri, 15 May 2026 09:39:23 +0100 Subject: [PATCH 3/3] Renamed enum --- .../aggregation/form-metadata-aggregation.js | 10 +++++----- .../form-metadata-aggregation.test.js | 18 ++++++++---------- .../forms/repositories/aggregation/types.js | 4 ++-- .../form-metadata-repository.test.js | 6 +++--- src/api/forms/service/definition.test.js | 6 +++--- 5 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/api/forms/repositories/aggregation/form-metadata-aggregation.js b/src/api/forms/repositories/aggregation/form-metadata-aggregation.js index 8671aeb7..7b12ccb5 100644 --- a/src/api/forms/repositories/aggregation/form-metadata-aggregation.js +++ b/src/api/forms/repositories/aggregation/form-metadata-aggregation.js @@ -1,4 +1,4 @@ -import { FormExtendedStatus } from '@defra/forms-model' +import { FormFilterStatus } from '@defra/forms-model' import { ObjectId } from 'mongodb' import { escapeRegExp } from '~/src/helpers/string-utils.js' @@ -32,9 +32,9 @@ export function buildFilterConditions(options) { if (status && status.length > 0) { conditions.$or = status.map((s) => { - if (s === FormExtendedStatus.Live) { + if (s === FormFilterStatus.Live) { return { live: { $exists: true } } - } else if (s === FormExtendedStatus.Offline) { + } else if (s === FormFilterStatus.Offline) { return { offline: true } } else { return { live: { $exists: false } } @@ -105,7 +105,7 @@ export function buildFiltersFacet() { * @param {string} title - The title to filter by. * @param {string} author - The author to filter by. * @param {string[]} organisations - The organisations to filter by. - * @param {FormExtendedStatus[]} status - The status values to filter by. + * @param {FormFilterStatus[]} status - The status values to filter by. * @returns {{ pipeline: PipelineStage[], aggOptions: AggregateOptions }} */ export function buildAggregationPipeline( @@ -147,7 +147,7 @@ export function buildAggregationPipeline( * @param {string} title - The title to filter by. * @param {string} author - The author to filter by. * @param {string[]} organisations - The organisations to filter by. - * @param {FormExtendedStatus[]} status - The status values to filter by. + * @param {FormFilterStatus[]} status - The status values to filter by. * @returns {{ pipeline: PipelineStage[], aggOptions: AggregateOptions }} */ export function buildAggregationPipelineWithVersions( diff --git a/src/api/forms/repositories/aggregation/form-metadata-aggregation.test.js b/src/api/forms/repositories/aggregation/form-metadata-aggregation.test.js index 70d1288c..2637343e 100644 --- a/src/api/forms/repositories/aggregation/form-metadata-aggregation.test.js +++ b/src/api/forms/repositories/aggregation/form-metadata-aggregation.test.js @@ -1,4 +1,4 @@ -import { FormExtendedStatus } from '@defra/forms-model' +import { FormFilterStatus } from '@defra/forms-model' import { ObjectId } from 'mongodb' import { @@ -71,7 +71,7 @@ describe('Form metadata aggregation', () => { describe('with status filter', () => { it('should create status filter for live forms', () => { const result = buildFilterConditions({ - status: [FormExtendedStatus.Live] + status: [FormFilterStatus.Live] }) expect(result).toEqual({ $or: [{ live: { $exists: true } }] @@ -80,7 +80,7 @@ describe('Form metadata aggregation', () => { it('should create status filter for draft forms', () => { const result = buildFilterConditions({ - status: [FormExtendedStatus.Draft] + status: [FormFilterStatus.Draft] }) expect(result).toEqual({ $or: [{ live: { $exists: false } }] @@ -91,7 +91,7 @@ describe('Form metadata aggregation', () => { describe('with multiple status values', () => { it('should create combined status filter', () => { const result = buildFilterConditions({ - status: [FormExtendedStatus.Live, FormExtendedStatus.Draft] + status: [FormFilterStatus.Live, FormFilterStatus.Draft] }) expect(result).toEqual({ $or: [{ live: { $exists: true } }, { live: { $exists: false } }] @@ -105,7 +105,7 @@ describe('Form metadata aggregation', () => { title: 'Wildlife Permit Application', author: 'Henrique Silva', organisations: ['Natural England', 'Defra'], - status: [FormExtendedStatus.Live] + status: [FormFilterStatus.Live] }) expect(result).toEqual({ @@ -145,7 +145,7 @@ describe('Form metadata aggregation', () => { 'Wildlife Permit Application', 'Henrique', ['Defra'], - [FormExtendedStatus.Live] + [FormFilterStatus.Live] ) expect(pipeline[0]).toHaveProperty('$match') @@ -212,7 +212,7 @@ describe('Form metadata aggregation', () => { 'Wildlife Permit Application', 'Henrique', ['Defra'], - [FormExtendedStatus.Live] + [FormFilterStatus.Live] ) expect(pipeline[0]).toHaveProperty('$match') @@ -508,9 +508,7 @@ describe('Form metadata aggregation', () => { { name: 'Sarah Wilson (Natural England)' } ], organisations: [{ name: 'Defra' }, { name: 'Natural England' }], - status: [ - { statuses: [FormExtendedStatus.Live, FormExtendedStatus.Draft] } - ] + status: [{ statuses: [FormFilterStatus.Live, FormFilterStatus.Draft] }] } const result = processFilterResults(filterResults) diff --git a/src/api/forms/repositories/aggregation/types.js b/src/api/forms/repositories/aggregation/types.js index 7ea93a71..068ed630 100644 --- a/src/api/forms/repositories/aggregation/types.js +++ b/src/api/forms/repositories/aggregation/types.js @@ -15,7 +15,7 @@ * @typedef {object} FilterAggregationResult * @property {{ name: string }[]} authors - Array of author names * @property {{ name: string }[]} organisations - Array of organisation names - * @property {[{ statuses: FormExtendedStatus[] }]} status - Array containing status values + * @property {[{ statuses: FormFilterStatus[] }]} status - Array containing status values */ /** @@ -79,5 +79,5 @@ */ /** - * @import { SearchOptions, FormStatus, FormExtendedStatus } from '@defra/forms-model' + * @import { SearchOptions, FormStatus, FormFilterStatus } from '@defra/forms-model' */ diff --git a/src/api/forms/repositories/form-metadata-repository.test.js b/src/api/forms/repositories/form-metadata-repository.test.js index 5b7e5a18..5a56654a 100644 --- a/src/api/forms/repositories/form-metadata-repository.test.js +++ b/src/api/forms/repositories/form-metadata-repository.test.js @@ -1,4 +1,4 @@ -import { FormStatus } from '@defra/forms-model' +import { FormFilterStatus } from '@defra/forms-model' import Boom from '@hapi/boom' import { MongoServerError, ObjectId } from 'mongodb' @@ -249,7 +249,7 @@ describe('form-metadata-repository', () => { title: 'test', author: 'John', organisations: ['Defra'], - status: [FormStatus.Draft] + status: [FormFilterStatus.Draft] } const result = await list(options) @@ -328,7 +328,7 @@ describe('form-metadata-repository', () => { title: 'test form', author: 'Jane Doe', organisations: ['Defra', 'DWP'], - status: [FormStatus.Draft, FormStatus.Live] + status: [FormFilterStatus.Draft, FormFilterStatus.Live] } const result = await listWithVersions(options) diff --git a/src/api/forms/service/definition.test.js b/src/api/forms/service/definition.test.js index e63075b9..28c0c256 100644 --- a/src/api/forms/service/definition.test.js +++ b/src/api/forms/service/definition.test.js @@ -3,7 +3,7 @@ import { ComponentType, Engine, FormDefinitionRequestType, - FormStatus, + FormFilterStatus, formDefinitionSchema, formDefinitionV2Schema } from '@defra/forms-model' @@ -1009,7 +1009,7 @@ describe('Forms service', () => { perPage: 10, author: 'Henrique Chase', organisations: ['Defra'], - status: [FormStatus.Live] + status: [FormFilterStatus.Live] } jest.mocked(formMetadata.list).mockResolvedValue({ @@ -1035,7 +1035,7 @@ describe('Forms service', () => { perPage: 10, author: 'Henrique Chase', organisations: ['Defra', 'Natural England'], - status: [FormStatus.Live, FormStatus.Draft] + status: [FormFilterStatus.Live, FormFilterStatus.Draft] } jest.mocked(formMetadata.list).mockResolvedValue({