From 00c546e7cd3a7848e03bcaf18b2b03e317401ac1 Mon Sep 17 00:00:00 2001 From: Alex Luckett Date: Fri, 2 May 2025 15:48:26 +0100 Subject: [PATCH 1/7] Support for async view contexts --- docs/GETTING_STARTED.md | 2 +- src/server/forms/adding-value.json | 1247 +++++++++++++++++ src/server/plugins/engine/plugin.ts | 2 +- .../engine/services/localFormsService.js | 15 + src/server/plugins/nunjucks/context.js | 13 +- 5 files changed, 1272 insertions(+), 7 deletions(-) create mode 100644 src/server/forms/adding-value.json diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index ed6b21b6b..9c24a1453 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -117,7 +117,7 @@ await server.register({ /** * View context attributes made available to your pages. Returns an object containing an arbitrary set of key-value pairs. */ - viewContext: (request) => { + viewContext: async (request) => { "example": "hello world" // available to render on a nunjucks page as {{ example }} } } diff --git a/src/server/forms/adding-value.json b/src/server/forms/adding-value.json new file mode 100644 index 000000000..d8ede688a --- /dev/null +++ b/src/server/forms/adding-value.json @@ -0,0 +1,1247 @@ +{ + "engine": "V2", + "name": "Adding value", + "metadata": { + "referenceNumberPrefix": "AV" + }, + "pages": [ + { + "title": "Check if you can apply for a Farming Transformation Fund Adding Value Grant", + "path": "/start", + "components": [ + { + "name": "startInfoOne", + "title": "Html", + "type": "Html", + "content": "

Use this service to:

\n\n\n\n

You can apply if you:

\n\n" + }, + { + "name": "startInsetText", + "title": "Inset text", + "type": "InsetText", + "content": "If your project is eligible, you can submit your answers to the Rural Payments Agency (RPA) to request the full application form." + }, + { + "name": "startInfoTwo", + "title": "Html", + "type": "Html", + "content": "

Before you start

\n

To use the checker, you need:

\n\n

If you do not enter any information for more than 20 minutes, your application will time out and you will have to start again.

\n\n

Problems using the online service

\n

If you have any problems using the online service, contact the RPA.

\n\n

Telephone

\n

Telephone: 03000 200 301

\n

Telephone: 03000 200 301

\n\n

Monday to Friday, 9am to 5pm (except public holidays)

\n\n\n\n" + } + ] + }, + { + "title": "What is your business?", + "path": "/nature-of-business", + "components": [ + { + "name": "natureOfBusinessRadiosField", + "title": "What is your business?", + "type": "RadiosField", + "list": "natureOfBusinessList", + "hint": "Select one option", + "options": { + "customValidationMessages": { + "any.required": "Select the option that applies to your business" + } + } + } + ] + }, + { + "title": "You cannot apply for a grant from this scheme", + "path": "/cannot-apply-nature-of-business", + "components": [ + { + "name": "natureOfBusinessCannotApply", + "title": "Html", + "type": "Html", + "content": "

This grant is for businesses who:

\n\n\n\n

See other grants you may be eligible for.

\n" + } + ], + "condition": "natureOfBusinessCondition" + }, + { + "title": "What is the legal status of the business?", + "path": "/legal-status", + "components": [ + { + "name": "legalStatusRadiosField", + "title": "What is the legal status of the business?", + "type": "RadiosField", + "list": "legalStatusList", + "options": { + "customValidationMessages": { + "any.required": "Select the legal status of the business" + } + } + } + ] + }, + { + "title": "You cannot apply for a grant from this scheme", + "path": "/legal-status-cannot-apply", + "components": [ + { + "name": "legalStatusCannotApplyInfo", + "title": "Html", + "type": "Html", + "content": "

Your business does not have an eligible legal status.

\n" + }, + { + "type": "Html", + "name": "legalStatusWarning", + "title": "Html", + "content": "
! WarningOther types of business may be supported in future schemes.
" + } + ], + "condition": "legalStatusCondition" + }, + { + "title": "Is the planned project in England?", + "path": "/country", + "components": [ + { + "name": "countryYesNoField", + "title": "Is the planned project in England?", + "hint": "The site where the work will happen", + "type": "YesNoField", + "options": { + "customValidationMessages": { + "any.required": "Select yes if the project is in England" + } + } + } + ] + }, + { + "title": "You cannot apply for a grant from this scheme", + "path": "/cannot-apply-country", + "components": [ + { + "name": "countryCannotApplyInfo", + "title": "Html", + "type": "Html", + "content": "

This grant is only for projects in England.

\n
Scotland, Wales and Northern Ireland have other grants available.
" + } + ], + "condition": "countryCondition" + }, + { + "title": "Does the project have planning permission?", + "path": "/planning-permission", + "components": [ + { + "name": "planningPermissionRadiosField", + "title": "Does the project have planning permission?", + "type": "RadiosField", + "list": "planningPermissionList", + "options": { + "customValidationMessages": { + "any.required": "Select when the project will have planning permission" + } + } + } + ] + }, + { + "title": "You may be able to apply for a grant from this scheme", + "path": "/planning-permission-may-apply", + "components": [ + { + "name": "planningPermissionMayApplyInfo", + "title": "Html", + "type": "Html", + "content": "

You must have secured planning permission before you submit a full application. The application deadline is 31 May 2025

\n" + }, + { + "name": "planningPermissionMayApplyLink", + "title": "Html", + "type": "Html", + "content": "

See other grants you may be eligible for

" + } + ], + "condition": "planningPermissionShouldBeCondition" + }, + { + "title": "You cannot apply for a grant from this scheme", + "path": "/planning-permission-cannot-apply", + "components": [ + { + "name": "planningPermissionCannotApplyInfo", + "type": "Html", + "title": "Html", + "content": "

You must have secured planning permission before you submit a full application.

\n

See other grants you may be eligible for

" + } + ], + "condition": "planningPermissionWillNotCondition" + }, + { + "title": "Have you already started work on the project?", + "path": "/project-start", + "components": [ + { + "name": "projectStartRadiosField", + "title": "Have you already started work on the project?", + "type": "RadiosField", + "list": "projectStartList", + "options": { + "customValidationMessages": { + "any.required": "Select the option that applies to your project" + } + } + } + ] + }, + { + "title": "You cannot apply for a grant from this scheme", + "path": "/cannot-apply-project-start", + "components": [ + { + "name": "cannotApplyProjectStartInfo", + "type": "Html", + "title": "Html", + "content": "

You cannot apply for a grant if you have already started work on the project.

\n
Starting the project or committing to any costs (such as placing orders) before you receive a funding agreement invalidates your application.

See other grants you may be eligible for

" + } + ], + "condition": "projectStartCondition" + }, + { + "title": "Is the planned project on land the business owns?", + "path": "/tenancy", + "components": [ + { + "name": "tenancyYesNoField", + "title": "Is the planned project on land the business owns?", + "type": "YesNoField", + "options": { + "customValidationMessages": { + "any.required": "Select yes if the planned project is on land the business owns" + } + } + } + ] + }, + { + "title": "Do you have a tenancy agreement for 5 years after the final grant payment?", + "path": "/tenancy-length", + "components": [ + { + "name": "tenancyLengthYesNoField", + "title": "Do you have a tenancy agreement for 5 years after the final grant payment?", + "type": "YesNoField", + "options": { + "customValidationMessages": { + "any.required": "Select yes if the land has a tenancy agreement in place for 5 years after the final grant payment." + } + } + } + ], + "condition": "tenancyCondition" + }, + { + "title": "You may be able to apply for a grant from this scheme", + "path": "/may-apply-tenancy-length", + "components": [ + { + "name": "mayApplyTenancyLengthInfo", + "type": "Html", + "title": "Html", + "content": "

You will need to extend your tenancy agreement for 5 years after the final grant payment.

" + } + ], + "condition": "tenancyLengthCondition" + }, + { + "title": "Do you want to build a new smaller abattoir?", + "path": "/smaller-abattoir", + "components": [ + { + "name": "smallerAbattoirYesNoField", + "title": "Do you want to build a new smaller abattoir?", + "type": "YesNoField", + "hint": "A smaller abattoir is a red meat abattoir that processes up to 10,000 livestock units each year or a poultry abattoir that slaughters up to 500,000 birds each year.", + "options": { + "customValidationMessages": { + "any.required": "Select yes if you want to build a new smaller abattoir" + } + } + } + ] + }, + { + "title": "Will this abattoir provide services to other farmers?", + "path": "/other-farmers", + "components": [ + { + "name": "otherFarmersYesNoField", + "title": "Will this abattoir provide services to other farmers?", + "hint": "For example, farmers pay you to slaughter their livestock.", + "type": "YesNoField", + "options": { + "customValidationMessages": { + "any.required": "Select yes if this abattoir will provide services to other farmers" + } + } + } + ], + "condition": "smallerAbattoirConditionYes" + }, + { + "title": "You cannot apply for a grant from this scheme", + "path": "/cannot-apply-other-farmers", + "components": [ + { + "type": "Html", + "name": "cannotApplyOtherFarmersInfo", + "title": "Html", + "content": "

You must provide some abattoir services to other farmers if you are building a new smaller abattoir with this grant.

\n

See other grants you may be eligible for

" + } + ], + "condition": "otherFarmersConditionNo" + }, + { + "title": "Do you want to build new controlled atmosphere storage for top fruit?", + "path": "/fruit-storage", + "components": [ + { + "name": "fruitStorageYesNoField", + "title": "Do you want to build new controlled atmosphere storage for top fruit?", + "hint": "Fruit that grows on trees, for example apples, pears, quinces, medlars, plums, peaches, apricots and cherries", + "type": "YesNoField", + "options": { + "customValidationMessages": { + "any.required": "Select yes if you want to build new controlled atmosphere storage for top fruit" + } + } + } + ], + "condition": "smallerAbattoirConditionNo" + }, + { + "title": "What eligible items does your project need?", + "path": "/project-items", + "components": [ + { + "name": "projectItemsCheckboxesField", + "title": "What eligible items does your project need?", + "hint": "Storage facilities will only be funded as part of a bigger project and cannot be more than 50% of the total grant funding.\n\nSelect all the items your project needs", + "type": "CheckboxesField", + "list": "projectItemsList", + "options": { + "customValidationMessages": { + "any.required": "Select all the items your project needs" + } + } + } + ], + "condition": "otherFarmersYesOrFruitStorageCondition" + }, + { + "title": "You cannot apply for a grant from this scheme", + "path": "/cannot-apply-project-items", + "components": [ + { + "type": "Html", + "name": "cannotApplyProjectItemsInfo", + "title": "Html", + "content": "

This grant is for:

\n\n\n

See other grants you may be eligible for

" + } + ], + "condition": "projectItemsCondition" + }, + { + "title": "Does your project also need storage facilities?", + "path": "/storage", + "components": [ + { + "name": "storageRadiosField", + "title": "Does your project also need storage facilities?", + "hint": "For example, cold stores or controlled atmosphere storage", + "type": "RadiosField", + "list": "storageRadiosList", + "options": { + "customValidationMessages": { + "any.required": "Select yes if you will need storage facilities" + } + } + }, + { + "type": "Html", + "name": "storageWarning", + "title": "Html", + "content": "
! WarningStorage facilities cannot be more than 50% of the total grant funding.
" + } + ], + "condition": "otherFarmersYesOrFruitStorageCondition" + }, + { + "title": "What is the estimated cost of the items?", + "path": "/project-cost", + "components": [ + { + "name": "projectCostInfo", + "type": "Html", + "title": "Html", + "content": "

Do not include VAT

Enter cost of items, for example {{ 695000 | formatCurrency: 'en-GB', 'GBP', 0, 'decimal' }}

" + }, + { + "name": "projectCostNumberField", + "type": "NumberField", + "title": "Enter amount", + "options": { + "prefix": "£", + "required": true, + "classes": "govuk-!-width-one-third", + "customValidationMessages": { + "any.required": "Enter the estimated cost of the items", + "number.max": "Enter a whole number with a maximum of 7 digits", + "number.min": "Enter a whole number with a maximum of 7 digits" + } + }, + "schema": { + "min": 1, + "max": 9999999, + "precision": 0 + } + } + ] + }, + { + "title": "You cannot apply for a grant from this scheme", + "path": "/project-cost-cannot-apply", + "components": [ + { + "name": "projectCostCannotApplyInfo", + "title": "Html", + "type": "Html", + "content": "

The minimum grant you can apply for is £25,000 (40% of £62,500).

\n\n

See other grants you may be eligible for

\n" + } + ], + "condition": "projectCostCondition" + }, + { + "title": "Potential grant funding", + "path": "/potential-funding", + "components": [ + { + "name": "potentialFundingInfo", + "title": "Html", + "type": "Html", + "content": "

{% assign estimatedCost = 'projectCostNumberField' | answer %} {% assign eligibilityMultiplier = 0.4 %} {% assign eligibilityAmount = estimatedCost | times: eligibilityMultiplier %} {% if eligibilityAmount <= 300000 and eligibilityAmount >= 25000 %} You may be able to apply for grant funding of up to {{ eligibilityAmount | formatCurrency: 'en-GB', 'GBP', 0 }} (40% of {{ estimatedCost | formatCurrency: 'en-GB', 'GBP', 0 }}).{% elsif eligibilityAmount > 300000 %} You may be able to apply for grant funding of up to £300,000, based on the estimated cost of {{ estimatedCost | formatCurrency: 'en-GB', 'GBP', 0 }}.\n\n\n

The maximum grant you can apply for is £300,000.
{% endif %}

\n", + "options": {}, + "schema": {} + }, + { + "type": "Html", + "name": "potentialFundingWarning", + "title": "Html", + "content": "
! WarningThere's no guarantee the project will receive a grant.
", + "options": {}, + "schema": {} + } + ] + }, + { + "title": "{% assign estimatedCost = 'projectCostNumberField' | answer %} {% assign applicantPayMultiplier = 0.6 %} {% assign applicantPayAmount = estimatedCost | times: applicantPayMultiplier %} {% assign grantPayAmount = estimatedCost | minus: applicantPayAmount %} {% if grantPayAmount >= 300000 %} {% assign applicantPayAmount = estimatedCost | minus: 300000 %} {% endif %} Can you pay the remaining costs of {{ applicantPayAmount | formatCurrency: 'en-GB', 'GBP', 0 }}?", + "path": "/remaining-costs", + "components": [ + { + "type": "YesNoField", + "name": "remainingCostsYesNoField", + "title": " ", + "options": { + "customValidationMessages": { + "any.required": "Select yes if you can pay the remaining costs" + } + } + } + ] + }, + { + "title": "You cannot apply for a grant from this scheme", + "path": "/cannot-apply-remaining-costs", + "components": [ + { + "name": "cannotApplyRemainingCostsInfo", + "title": "Html", + "type": "Html", + "content": "

You cannot use public money (for example, grant funding from government or local authorities) towards the project costs.

You also cannot use money from a producer organisation under the Fresh Fruit and Vegetable Aid Scheme.

For example, you can use:

\n\n\n\n

See other grants you may be eligible for

\n" + } + ], + "condition": "remainingCostsCondition" + }, + { + "title": "What type of produce is being processed?", + "path": "/produce-processed", + "components": [ + { + "name": "produceProcessedRadiosField", + "title": "What type of produce is being processed?", + "type": "RadiosField", + "list": "produceProcessedList", + "hint": "Select one option", + "options": { + "customValidationMessages": { + "any.required": "Select what type of produce is being processed" + } + } + } + ], + "condition": "otherFarmersYesOrFruitStorageCondition" + }, + { + "title": "How will this project add value to the produce?", + "path": "/how-adding-value", + "components": [ + { + "name": "howAddingValueRadiosField", + "title": "How will this project add value to the produce?", + "type": "RadiosField", + "list": "howAddingValueList", + "hint": "Select the main option that applies", + "options": { + "customValidationMessages": { + "any.required": "Select how this project will add value to the produce" + } + } + } + ], + "condition": "otherFarmersYesOrFruitStorageCondition" + }, + { + "title": "Confirmation", + "path": "/confirmation" + } + ], + "lists": [ + { + "title": "What is your business?", + "name": "natureOfBusinessList", + "type": "string", + "items": [ + { + "text": "A grower or producer of agricultural or horticultural produce", + "description": "For example, arable or livestock farmer, fruit producer, salad grower", + "value": "natureOfBusiness-A1" + }, + { + "text": "A business processing agricultural or horticultural products that is at least 50% owned by agricultural or horticultural producers", + "description": "For example, a cheese production business owned by a group of farmers", + "value": "natureOfBusiness-A2" + }, + { + "text": "A woodland manager processing wild venison products", + "value": "natureOfBusiness-A3" + }, + { + "text": "None of the above", + "value": "natureOfBusiness-A4" + } + ] + }, + { + "title": "Who is applying for this grant?", + "name": "applyingList", + "type": "string", + "items": [ + { + "text": "Applicant", + "value": "applying-A1" + }, + { + "text": "Agent", + "value": "applying-A2" + } + ] + }, + { + "title": "What type of produce is being processed?", + "name": "produceProcessedList", + "type": "string", + "items": [ + { + "text": "Arable produce", + "description": "For example, crushing of oilseeds, rolling or flaking of grains as food ingredients", + "value": "produceProcessed-A1" + }, + { + "text": "Wild venison meat produce", + "description": "For example, processing and packing wild venison meat", + "value": "produceProcessed-A2" + }, + { + "text": "Dairy or meat produce", + "description": "For example, processing and bottling milk or slaughtering, cutting, processing and packing meat", + "value": "produceProcessed-A3" + }, + { + "text": "Fibre produce", + "description": "For example, processing animal hides and leather, processing fibres such as wool, flax and hemp", + "value": "produceProcessed-A4" + }, + { + "text": "Fodder produce", + "description": "For example, processing and repacking hay and straw for specialist markets or retail sale", + "value": "produceProcessed-A5" + }, + { + "text": "Horticultural produce", + "description": "For example, grading and packing of soft fruit, washing and packing vegetables, packing salad crops", + "value": "produceProcessed-A6" + }, + { + "text": "Non-edible produce", + "description": "For example, processing and packing ornamental flowers and bulbs after harvesting", + "value": "produceProcessed-A7" + } + ] + }, + { + "title": "How will this project add value to the produce?", + "name": "howAddingValueList", + "type": "string", + "items": [ + { + "text": "Introducing a new product to your farm", + "description": "For example, processing meat to burgers, milk to cheese, cereals to beer or spirits.", + "value": "howAddingValue-A1" + }, + { + "text": "Grading or sorting produce", + "description": "For example, washing and grading vegetables, egg grading, optical grading of top fruit.", + "value": "howAddingValue-A2" + }, + { + "text": "Packing produce", + "description": "For example, packing top fruit, bagging vegetables, bottling wine.", + "value": "howAddingValuee-A3" + }, + { + "text": "A new retail facility to sell direct to consumers", + "description": "", + "value": "howAddingValue-A4" + } + ] + }, + { + "title": "What impact will this project have?", + "name": "projectImpactList", + "type": "string", + "items": [ + { + "text": "Increasing range of added-value products", + "description": "", + "value": "projectImpact-A1" + }, + { + "text": "Increasing volume of added-value products", + "description": "", + "value": "projectImpact-A2" + }, + { + "text": "Allow selling direct to consumers", + "description": "For example, retail and internet sales.", + "value": "projectImpact-A3" + }, + { + "text": "Starting to make added-value products for the first time", + "description": "This only applies if you do not already make added-value products.", + "value": "projectImpact-A4" + } + ] + }, + { + "title": "What is the legal status of your business?", + "name": "legalStatusList", + "type": "string", + "items": [ + { + "text": "Sole trader", + "value": "legalStatus-A1" + }, + { + "text": "Partnership", + "value": "legalStatus-A2" + }, + { + "text": "Limited company", + "value": "legalStatus-A3" + }, + { + "text": "Charity", + "value": "legalStatus-A4" + }, + { + "text": "Trust", + "value": "legalStatus-A5" + }, + { + "text": "Limited liability partnership", + "value": "legalStatus-A6" + }, + { + "text": "Community interest company", + "value": "legalStatus-A7" + }, + { + "text": "Limited partnership", + "value": "legalStatus-A8" + }, + { + "text": "Industrial and provident society", + "value": "legalStatus-A9" + }, + { + "text": "Co-operative society (Co-Op)", + "value": "legalStatus-A10" + }, + { + "text": "Community benefit society (BenCom)", + "value": "legalStatus-A11" + }, + { + "text": "None of the above", + "value": "legalStatus-A12" + } + ] + }, + { + "title": "Does the project have planning permission?", + "name": "planningPermissionList", + "type": "string", + "items": [ + { + "text": "Not needed", + "value": "planningPermission-A1" + }, + { + "text": "Secured", + "value": "planningPermission-A2" + }, + { + "text": "Should be in place by the time I make my full application", + "value": "planningPermission-A3" + }, + { + "text": "Will not be in place by the time I make my full application", + "value": "planningPermission-A4" + } + ] + }, + { + "title": "Have you already started work on the project?", + "name": "projectStartList", + "type": "string", + "items": [ + { + "text": "Yes, preparatory work", + "description": "For example, quotes from suppliers, applying for planning permission", + "value": "projectStart-A1" + }, + { + "text": "Yes, we have begun project work", + "description": "For example, started construction work, signing contracts, placing orders", + "value": "projectStart-A2" + }, + { + "text": "No, we have not done any work on this project yet", + "value": "projectStart-A3" + } + ] + }, + { + "title": "What eligible items does your project need?", + "name": "projectItemsList", + "type": "string", + "items": [ + { + "text": "Constructing or improving buildings for processing", + "description": "For example, a new building for cheese making, extending an existing building to install a new meat-cutting and packing line", + "value": "projectItems-A1" + }, + { + "text": "Processing equipment or machinery", + "description": "For example, equipment and machinery for pasteurising and bottling milk, a meat cutting and packing line or vegetable washing and packing", + "value": "projectItems-A2" + }, + { + "text": "Retail facilities", + "description": "For example, shops or display cabinets", + "value": "projectItems-A3" + }, + { + "text": "None of the above", + "value": "projectItems-A4" + } + ] + }, + { + "title": "Does your project also need storage facilities?", + "name": "storageRadiosList", + "type": "string", + "items": [ + { + "text": "Yes, we will need storage facilities", + "value": "storageRadios-A1" + }, + { + "text": "No, we do not need storage facilities", + "value": "storageRadios-A2" + } + ] + }, + { + "title": "How much manual labour will the mechanisation be equal to?", + "name": "manualLabourAmountList", + "type": "string", + "items": [ + { + "text": "Up to 5% of workforce", + "value": "manualLabourAmount-A1" + }, + { + "text": "Between 5% and 10%", + "value": "manualLabourAmount-A2" + }, + { + "text": "More than 10%", + "value": "manualLabourAmount-A3" + } + ] + }, + { + "title": "Consent optional", + "name": "consent-optional-list", + "type": "string", + "items": [ + { + "text": "(Optional) I consent to being contacted by Defra or a third party about service improvements", + "value": "consentOptional-A1" + } + ] + } + ], + "sections": [ + { + "title": "Eligibility", + "name": "EligibilitySection", + "hideTitle": false + } + ], + "conditions": [ + { + "name": "natureOfBusinessCondition", + "displayName": "natureOfBusinessNone", + "value": { + "name": "natureOfBusinessNone", + "conditions": [ + { + "field": { + "name": "natureOfBusinessRadiosField", + "type": "RadiosField", + "display": "What is your business?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "natureOfBusiness-A4", + "display": "None of the above" + } + } + ] + } + }, + { + "name": "legalStatusCondition", + "displayName": "legalStatusNone", + "value": { + "name": "legalStatusNone", + "conditions": [ + { + "field": { + "name": "legalStatusRadiosField", + "type": "RadiosField", + "display": "What is the legal status of the business?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "legalStatus-A12", + "display": "None of the above" + } + } + ] + } + }, + { + "name": "countryCondition", + "displayName": "plannedProjectNo", + "value": { + "name": "plannedProjectNo", + "conditions": [ + { + "field": { + "name": "countryYesNoField", + "type": "YesNoField", + "display": "Is the planned project in England?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "false", + "display": "No" + } + } + ] + } + }, + { + "name": "planningPermissionShouldBeCondition", + "displayName": "planningPermissionShouldBe", + "value": { + "name": "planningPermissionShouldBe", + "conditions": [ + { + "field": { + "name": "planningPermissionRadiosField", + "type": "RadiosField", + "display": "Does the project have planning permission?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "planningPermission-A3", + "display": "Should be in place by the time I make my full application" + } + } + ] + } + }, + { + "name": "planningPermissionWillNotCondition", + "displayName": "planningPermissionWillNot", + "value": { + "name": "planningPermissionWillNot", + "conditions": [ + { + "field": { + "name": "planningPermissionRadiosField", + "type": "RadiosField", + "display": "Does the project have planning permission?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "planningPermission-A4", + "display": "Will not be in place by the time I make my full application" + } + } + ] + } + }, + { + "name": "projectStartCondition", + "displayName": "startProjectWork", + "value": { + "name": "startProjectWork", + "conditions": [ + { + "field": { + "name": "projectStartRadiosField", + "type": "RadiosField", + "display": "Have you already started work on the project?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "projectStart-A2", + "display": "Yes, we have begun project work" + } + } + ] + } + }, + { + "name": "tenancyCondition", + "displayName": "tenancyConditionNo", + "value": { + "name": "tenancyConditionNo", + "conditions": [ + { + "field": { + "name": "tenancyYesNoField", + "type": "YesNoField", + "display": "Is the planned project on land the business owns?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "false", + "display": "No" + } + } + ] + } + }, + { + "name": "tenancyLengthCondition", + "displayName": "tenancyLengthConditionNo", + "value": { + "name": "tenancyLengthConditionNo", + "conditions": [ + { + "field": { + "name": "tenancyLengthYesNoField", + "type": "YesNoField", + "display": "Do you have a tenancy agreement for 5 years after the final grant payment?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "false", + "display": "No" + } + } + ] + } + }, + { + "name": "smallerAbattoirConditionYes", + "displayName": "smallerAbattoirConditionYes", + "value": { + "name": "smallerAbattoirConditionYes", + "conditions": [ + { + "field": { + "name": "smallerAbattoirYesNoField", + "type": "YesNoField", + "display": "Do you want to build a new smaller abattoir?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "true", + "display": "Yes" + } + } + ] + } + }, + { + "name": "smallerAbattoirConditionNo", + "displayName": "smallerAbattoirConditionNo", + "value": { + "name": "smallerAbattoirConditionNo", + "conditions": [ + { + "field": { + "name": "smallerAbattoirYesNoField", + "type": "YesNoField", + "display": "Do you want to build a new smaller abattoir?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "false", + "display": "No" + } + } + ] + } + }, + { + "name": "otherFarmersConditionNo", + "displayName": "otherFarmersConditionNo", + "value": { + "name": "otherFarmersConditionNo", + "conditions": [ + { + "field": { + "name": "otherFarmersYesNoField", + "type": "YesNoField", + "display": "Will this abattoir provide services to other farmers?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "false", + "display": "No" + } + } + ] + } + }, + { + "name": "otherFarmersConditionYes", + "displayName": "otherFarmersConditionYes", + "value": { + "name": "otherFarmersConditionYes", + "conditions": [ + { + "field": { + "name": "otherFarmersYesNoField", + "type": "YesNoField", + "display": "Will this abattoir provide services to other farmers?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "true", + "display": "Yes" + } + } + ] + } + }, + { + "name": "fruitStorageCondition", + "displayName": "fruitStorageConditionNo", + "value": { + "name": "fruitStorageConditionNo", + "conditions": [ + { + "field": { + "name": "fruitStorageYesNoField", + "type": "YesNoField", + "display": "Do you want to build new controlled atmosphere storage for top fruit?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "false", + "display": "No" + } + } + ] + } + }, + { + "name": "otherFarmersYesOrFruitStorageCondition", + "displayName": "otherFarmersYesOrFruitStorage", + "value": { + "name": "otherFarmersYesOrFruitStorage", + "conditions": [ + { + "conditionName": "otherFarmersConditionYes", + "conditionDisplayName": "Yes" + }, + { + "coordinator": "or", + "conditionName": "fruitStorageCondition", + "conditionDisplayName": "No" + } + ] + } + }, + { + "name": "projectItemsCondition", + "displayName": "projectItemsConditionNone", + "value": { + "name": "projectItemsConditionNone", + "conditions": [ + { + "field": { + "name": "projectItemsCheckboxesField", + "type": "CheckboxesField", + "display": "What eligible items does your project need?" + }, + "operator": "contains", + "value": { + "type": "Value", + "value": "projectItems-A4", + "display": "None of the above" + } + } + ] + } + }, + { + "name": "projectCostCondition", + "displayName": "projectCostConditionNotReached", + "value": { + "name": "projectCostConditionNotReached", + "conditions": [ + { + "field": { + "name": "projectCostNumberField", + "type": "NumberField", + "display": "What is the estimated cost of the items?" + }, + "operator": "is less than", + "value": { + "type": "Value", + "value": "62500", + "display": "Enter amount" + } + } + ] + } + }, + { + "name": "remainingCostsCondition", + "displayName": "canYouPayTheRemainingCosts", + "value": { + "name": "canYouPayTheRemainingCosts", + "conditions": [ + { + "field": { + "name": "remainingCostsYesNoField", + "type": "YesNoField", + "display": "Can you pay the remaining costs of £x?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "false", + "display": "No" + } + } + ] + } + }, + { + "name": "mechanisationCondition", + "displayName": "mechanisationYes", + "value": { + "name": "mechanisationYes", + "conditions": [ + { + "field": { + "name": "mechanisationYesNoField", + "type": "YesNoField", + "display": "Will this project use any mechanisation instead of manual labour?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "true", + "display": "Yes" + } + } + ] + } + }, + { + "name": "agentDetailsSelected", + "displayName": "agentDetailsSelected", + "value": { + "name": "agentDetailsSelected", + "conditions": [ + { + "field": { + "name": "whoIsApplyingForThisGrantComponent", + "type": "RadiosField", + "display": "Who is applying for this grant?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "applying-A2", + "display": "Agent" + } + } + ] + } + } + ] +} diff --git a/src/server/plugins/engine/plugin.ts b/src/server/plugins/engine/plugin.ts index 8952b9be2..3bfa605a1 100644 --- a/src/server/plugins/engine/plugin.ts +++ b/src/server/plugins/engine/plugin.ts @@ -97,7 +97,7 @@ export interface PluginOptions { } viewContext: ( request: FormRequest | FormRequestPayload | null - ) => Record + ) => Promise> } export const plugin = { diff --git a/src/server/plugins/engine/services/localFormsService.js b/src/server/plugins/engine/services/localFormsService.js index bdbce67d1..d80d8189e 100644 --- a/src/server/plugins/engine/services/localFormsService.js +++ b/src/server/plugins/engine/services/localFormsService.js @@ -45,5 +45,20 @@ export const formsService = async () => { slug: 'register-as-a-unicorn-breeder-yaml' // if we needed to validate any JSON logic, make it available for convenience }) + // Add a Yaml form + await loader.addForm('src/server/forms/adding-value.json', { + ...metadata, + id: 'a0f1b8c2-4d3e-4f5b-9a7c-6d1e0f2b5c3d', + title: 'Adding value', + slug: 'adding-value' // if we needed to validate any JSON logic, make it available for convenience + }) + + await loader.addForm('src/server/forms/components.json', { + ...metadata, + id: '6a872d3b-13f9e-804ce3e-4830-5c45fb32', + title: 'Components', + slug: 'components' // if we needed to validate any JSON logic, make it available for convenience + }) + return loader.toFormsService() } diff --git a/src/server/plugins/nunjucks/context.js b/src/server/plugins/nunjucks/context.js index 1bd2780ee..663b334a4 100644 --- a/src/server/plugins/nunjucks/context.js +++ b/src/server/plugins/nunjucks/context.js @@ -20,8 +20,9 @@ let webpackManifest /** * @param {FormRequest | FormRequestPayload | null} request + * @returns {Promise} */ -export function context(request) { +export async function context(request) { const { params, response } = request ?? {} const { isPreview: isPreviewMode, state: formState } = checkFormStatus(params) @@ -43,7 +44,7 @@ export function context(request) { } if ('viewContext' in pluginStorage) { - consumerViewContext = pluginStorage.viewContext(request) + consumerViewContext = await pluginStorage.viewContext(request) } /** @type {ViewContext} */ @@ -71,8 +72,10 @@ export function context(request) { /** * Returns the context for the devtool. Consumers won't have access to this. + * @param {FormRequest | FormRequestPayload | null} _request + * @returns {Record} */ -export function devtoolContext() { +export function devtoolContext(_request) { const manifestPath = join(config.get('publicDir'), 'assets-manifest.json') if (!webpackManifest) { @@ -84,12 +87,12 @@ export function devtoolContext() { } } - return { + return Promise.resolve({ assetPath: '/assets', getDxtAssetPath: (asset = '') => { return `/${webpackManifest?.[asset] ?? asset}` } - } + }) } /** From 144fff77d2681cecd1d217994e1f24b6062616a5 Mon Sep 17 00:00:00 2001 From: Alex Luckett Date: Fri, 2 May 2025 16:31:08 +0100 Subject: [PATCH 2/7] Support async viewcontext (required for FCP) --- src/server/forms/adding-value.json | 1247 ----------------- .../engine/services/localFormsService.js | 10 +- src/server/plugins/nunjucks/context.js | 2 +- src/server/plugins/nunjucks/context.test.js | 28 +- 4 files changed, 16 insertions(+), 1271 deletions(-) delete mode 100644 src/server/forms/adding-value.json diff --git a/src/server/forms/adding-value.json b/src/server/forms/adding-value.json deleted file mode 100644 index d8ede688a..000000000 --- a/src/server/forms/adding-value.json +++ /dev/null @@ -1,1247 +0,0 @@ -{ - "engine": "V2", - "name": "Adding value", - "metadata": { - "referenceNumberPrefix": "AV" - }, - "pages": [ - { - "title": "Check if you can apply for a Farming Transformation Fund Adding Value Grant", - "path": "/start", - "components": [ - { - "name": "startInfoOne", - "title": "Html", - "type": "Html", - "content": "

Use this service to:

\n\n
    \n
  • check if you can apply for a grant for your project (takes about 5 minutes)
  • \n
  • check how well your project fits the funding priorities (takes about 15 minutes if you have all the project details)
  • \n
\n\n

You can apply if you:

\n\n
    \n
  • are a grower or producer of agricultural or horticultural produce
  • \n
  • are a business processing agricultural or horticultural products that is at least 50% owned by agricultural or horticultural producers
  • \n
  • will do the grant-funded work in England
  • \n
  • estimate the project costs are over £62,500
  • \n
" - }, - { - "name": "startInsetText", - "title": "Inset text", - "type": "InsetText", - "content": "If your project is eligible, you can submit your answers to the Rural Payments Agency (RPA) to request the full application form." - }, - { - "name": "startInfoTwo", - "title": "Html", - "type": "Html", - "content": "

Before you start

\n

To use the checker, you need:

\n
    \n
  • information about your business (for example, number of employees, turnover)
  • \n
  • information about the project (for example, the types of products being processed and how you will add value, the impact the project will have)
  • \n
  • a list of the items you'd like to buy for the project
  • \n
  • an estimate of the total cost of the items
  • \n
\n

If you do not enter any information for more than 20 minutes, your application will time out and you will have to start again.

\n\n

Problems using the online service

\n

If you have any problems using the online service, contact the RPA.

\n\n

Telephone

\n

Telephone: 03000 200 301

\n

Telephone: 03000 200 301

\n\n

Monday to Friday, 9am to 5pm (except public holidays)

\n\n\n\n" - } - ] - }, - { - "title": "What is your business?", - "path": "/nature-of-business", - "components": [ - { - "name": "natureOfBusinessRadiosField", - "title": "What is your business?", - "type": "RadiosField", - "list": "natureOfBusinessList", - "hint": "Select one option", - "options": { - "customValidationMessages": { - "any.required": "Select the option that applies to your business" - } - } - } - ] - }, - { - "title": "You cannot apply for a grant from this scheme", - "path": "/cannot-apply-nature-of-business", - "components": [ - { - "name": "natureOfBusinessCannotApply", - "title": "Html", - "type": "Html", - "content": "

This grant is for businesses who:

\n\n
    \n
  • are agricultural or horticultural growers or producers
  • \n
  • are a business processing agricultural or horticultural products that is at least 50% owned by agricultural or horticultural producers
  • \n
\n\n

See other grants you may be eligible for.

\n" - } - ], - "condition": "natureOfBusinessCondition" - }, - { - "title": "What is the legal status of the business?", - "path": "/legal-status", - "components": [ - { - "name": "legalStatusRadiosField", - "title": "What is the legal status of the business?", - "type": "RadiosField", - "list": "legalStatusList", - "options": { - "customValidationMessages": { - "any.required": "Select the legal status of the business" - } - } - } - ] - }, - { - "title": "You cannot apply for a grant from this scheme", - "path": "/legal-status-cannot-apply", - "components": [ - { - "name": "legalStatusCannotApplyInfo", - "title": "Html", - "type": "Html", - "content": "

Your business does not have an eligible legal status.

\n" - }, - { - "type": "Html", - "name": "legalStatusWarning", - "title": "Html", - "content": "
! WarningOther types of business may be supported in future schemes.
" - } - ], - "condition": "legalStatusCondition" - }, - { - "title": "Is the planned project in England?", - "path": "/country", - "components": [ - { - "name": "countryYesNoField", - "title": "Is the planned project in England?", - "hint": "The site where the work will happen", - "type": "YesNoField", - "options": { - "customValidationMessages": { - "any.required": "Select yes if the project is in England" - } - } - } - ] - }, - { - "title": "You cannot apply for a grant from this scheme", - "path": "/cannot-apply-country", - "components": [ - { - "name": "countryCannotApplyInfo", - "title": "Html", - "type": "Html", - "content": "

This grant is only for projects in England.

\n
Scotland, Wales and Northern Ireland have other grants available.
" - } - ], - "condition": "countryCondition" - }, - { - "title": "Does the project have planning permission?", - "path": "/planning-permission", - "components": [ - { - "name": "planningPermissionRadiosField", - "title": "Does the project have planning permission?", - "type": "RadiosField", - "list": "planningPermissionList", - "options": { - "customValidationMessages": { - "any.required": "Select when the project will have planning permission" - } - } - } - ] - }, - { - "title": "You may be able to apply for a grant from this scheme", - "path": "/planning-permission-may-apply", - "components": [ - { - "name": "planningPermissionMayApplyInfo", - "title": "Html", - "type": "Html", - "content": "

You must have secured planning permission before you submit a full application. The application deadline is 31 May 2025

\n" - }, - { - "name": "planningPermissionMayApplyLink", - "title": "Html", - "type": "Html", - "content": "

See other grants you may be eligible for

" - } - ], - "condition": "planningPermissionShouldBeCondition" - }, - { - "title": "You cannot apply for a grant from this scheme", - "path": "/planning-permission-cannot-apply", - "components": [ - { - "name": "planningPermissionCannotApplyInfo", - "type": "Html", - "title": "Html", - "content": "

You must have secured planning permission before you submit a full application.

\n

See other grants you may be eligible for

" - } - ], - "condition": "planningPermissionWillNotCondition" - }, - { - "title": "Have you already started work on the project?", - "path": "/project-start", - "components": [ - { - "name": "projectStartRadiosField", - "title": "Have you already started work on the project?", - "type": "RadiosField", - "list": "projectStartList", - "options": { - "customValidationMessages": { - "any.required": "Select the option that applies to your project" - } - } - } - ] - }, - { - "title": "You cannot apply for a grant from this scheme", - "path": "/cannot-apply-project-start", - "components": [ - { - "name": "cannotApplyProjectStartInfo", - "type": "Html", - "title": "Html", - "content": "

You cannot apply for a grant if you have already started work on the project.

\n
Starting the project or committing to any costs (such as placing orders) before you receive a funding agreement invalidates your application.

See other grants you may be eligible for

" - } - ], - "condition": "projectStartCondition" - }, - { - "title": "Is the planned project on land the business owns?", - "path": "/tenancy", - "components": [ - { - "name": "tenancyYesNoField", - "title": "Is the planned project on land the business owns?", - "type": "YesNoField", - "options": { - "customValidationMessages": { - "any.required": "Select yes if the planned project is on land the business owns" - } - } - } - ] - }, - { - "title": "Do you have a tenancy agreement for 5 years after the final grant payment?", - "path": "/tenancy-length", - "components": [ - { - "name": "tenancyLengthYesNoField", - "title": "Do you have a tenancy agreement for 5 years after the final grant payment?", - "type": "YesNoField", - "options": { - "customValidationMessages": { - "any.required": "Select yes if the land has a tenancy agreement in place for 5 years after the final grant payment." - } - } - } - ], - "condition": "tenancyCondition" - }, - { - "title": "You may be able to apply for a grant from this scheme", - "path": "/may-apply-tenancy-length", - "components": [ - { - "name": "mayApplyTenancyLengthInfo", - "type": "Html", - "title": "Html", - "content": "

You will need to extend your tenancy agreement for 5 years after the final grant payment.

" - } - ], - "condition": "tenancyLengthCondition" - }, - { - "title": "Do you want to build a new smaller abattoir?", - "path": "/smaller-abattoir", - "components": [ - { - "name": "smallerAbattoirYesNoField", - "title": "Do you want to build a new smaller abattoir?", - "type": "YesNoField", - "hint": "A smaller abattoir is a red meat abattoir that processes up to 10,000 livestock units each year or a poultry abattoir that slaughters up to 500,000 birds each year.", - "options": { - "customValidationMessages": { - "any.required": "Select yes if you want to build a new smaller abattoir" - } - } - } - ] - }, - { - "title": "Will this abattoir provide services to other farmers?", - "path": "/other-farmers", - "components": [ - { - "name": "otherFarmersYesNoField", - "title": "Will this abattoir provide services to other farmers?", - "hint": "For example, farmers pay you to slaughter their livestock.", - "type": "YesNoField", - "options": { - "customValidationMessages": { - "any.required": "Select yes if this abattoir will provide services to other farmers" - } - } - } - ], - "condition": "smallerAbattoirConditionYes" - }, - { - "title": "You cannot apply for a grant from this scheme", - "path": "/cannot-apply-other-farmers", - "components": [ - { - "type": "Html", - "name": "cannotApplyOtherFarmersInfo", - "title": "Html", - "content": "

You must provide some abattoir services to other farmers if you are building a new smaller abattoir with this grant.

\n

See other grants you may be eligible for

" - } - ], - "condition": "otherFarmersConditionNo" - }, - { - "title": "Do you want to build new controlled atmosphere storage for top fruit?", - "path": "/fruit-storage", - "components": [ - { - "name": "fruitStorageYesNoField", - "title": "Do you want to build new controlled atmosphere storage for top fruit?", - "hint": "Fruit that grows on trees, for example apples, pears, quinces, medlars, plums, peaches, apricots and cherries", - "type": "YesNoField", - "options": { - "customValidationMessages": { - "any.required": "Select yes if you want to build new controlled atmosphere storage for top fruit" - } - } - } - ], - "condition": "smallerAbattoirConditionNo" - }, - { - "title": "What eligible items does your project need?", - "path": "/project-items", - "components": [ - { - "name": "projectItemsCheckboxesField", - "title": "What eligible items does your project need?", - "hint": "Storage facilities will only be funded as part of a bigger project and cannot be more than 50% of the total grant funding.\n\nSelect all the items your project needs", - "type": "CheckboxesField", - "list": "projectItemsList", - "options": { - "customValidationMessages": { - "any.required": "Select all the items your project needs" - } - } - } - ], - "condition": "otherFarmersYesOrFruitStorageCondition" - }, - { - "title": "You cannot apply for a grant from this scheme", - "path": "/cannot-apply-project-items", - "components": [ - { - "type": "Html", - "name": "cannotApplyProjectItemsInfo", - "title": "Html", - "content": "

This grant is for:

\n\n
    \n
  • constructing or improving buildings for processing
  • \n
  • processing equipment or machinery
  • \n
  • retail facilities
  • \n
\n

See other grants you may be eligible for

" - } - ], - "condition": "projectItemsCondition" - }, - { - "title": "Does your project also need storage facilities?", - "path": "/storage", - "components": [ - { - "name": "storageRadiosField", - "title": "Does your project also need storage facilities?", - "hint": "For example, cold stores or controlled atmosphere storage", - "type": "RadiosField", - "list": "storageRadiosList", - "options": { - "customValidationMessages": { - "any.required": "Select yes if you will need storage facilities" - } - } - }, - { - "type": "Html", - "name": "storageWarning", - "title": "Html", - "content": "
! WarningStorage facilities cannot be more than 50% of the total grant funding.
" - } - ], - "condition": "otherFarmersYesOrFruitStorageCondition" - }, - { - "title": "What is the estimated cost of the items?", - "path": "/project-cost", - "components": [ - { - "name": "projectCostInfo", - "type": "Html", - "title": "Html", - "content": "

Do not include VAT

Enter cost of items, for example {{ 695000 | formatCurrency: 'en-GB', 'GBP', 0, 'decimal' }}

" - }, - { - "name": "projectCostNumberField", - "type": "NumberField", - "title": "Enter amount", - "options": { - "prefix": "£", - "required": true, - "classes": "govuk-!-width-one-third", - "customValidationMessages": { - "any.required": "Enter the estimated cost of the items", - "number.max": "Enter a whole number with a maximum of 7 digits", - "number.min": "Enter a whole number with a maximum of 7 digits" - } - }, - "schema": { - "min": 1, - "max": 9999999, - "precision": 0 - } - } - ] - }, - { - "title": "You cannot apply for a grant from this scheme", - "path": "/project-cost-cannot-apply", - "components": [ - { - "name": "projectCostCannotApplyInfo", - "title": "Html", - "type": "Html", - "content": "

The minimum grant you can apply for is £25,000 (40% of £62,500).

\n\n

See other grants you may be eligible for

\n" - } - ], - "condition": "projectCostCondition" - }, - { - "title": "Potential grant funding", - "path": "/potential-funding", - "components": [ - { - "name": "potentialFundingInfo", - "title": "Html", - "type": "Html", - "content": "

{% assign estimatedCost = 'projectCostNumberField' | answer %} {% assign eligibilityMultiplier = 0.4 %} {% assign eligibilityAmount = estimatedCost | times: eligibilityMultiplier %} {% if eligibilityAmount <= 300000 and eligibilityAmount >= 25000 %} You may be able to apply for grant funding of up to {{ eligibilityAmount | formatCurrency: 'en-GB', 'GBP', 0 }} (40% of {{ estimatedCost | formatCurrency: 'en-GB', 'GBP', 0 }}).{% elsif eligibilityAmount > 300000 %} You may be able to apply for grant funding of up to £300,000, based on the estimated cost of {{ estimatedCost | formatCurrency: 'en-GB', 'GBP', 0 }}.\n\n\n

The maximum grant you can apply for is £300,000.
{% endif %}

\n", - "options": {}, - "schema": {} - }, - { - "type": "Html", - "name": "potentialFundingWarning", - "title": "Html", - "content": "
! WarningThere's no guarantee the project will receive a grant.
", - "options": {}, - "schema": {} - } - ] - }, - { - "title": "{% assign estimatedCost = 'projectCostNumberField' | answer %} {% assign applicantPayMultiplier = 0.6 %} {% assign applicantPayAmount = estimatedCost | times: applicantPayMultiplier %} {% assign grantPayAmount = estimatedCost | minus: applicantPayAmount %} {% if grantPayAmount >= 300000 %} {% assign applicantPayAmount = estimatedCost | minus: 300000 %} {% endif %} Can you pay the remaining costs of {{ applicantPayAmount | formatCurrency: 'en-GB', 'GBP', 0 }}?", - "path": "/remaining-costs", - "components": [ - { - "type": "YesNoField", - "name": "remainingCostsYesNoField", - "title": " ", - "options": { - "customValidationMessages": { - "any.required": "Select yes if you can pay the remaining costs" - } - } - } - ] - }, - { - "title": "You cannot apply for a grant from this scheme", - "path": "/cannot-apply-remaining-costs", - "components": [ - { - "name": "cannotApplyRemainingCostsInfo", - "title": "Html", - "type": "Html", - "content": "

You cannot use public money (for example, grant funding from government or local authorities) towards the project costs.

You also cannot use money from a producer organisation under the Fresh Fruit and Vegetable Aid Scheme.

For example, you can use:

\n\n
    \n
  • loans
  • \n
  • overdrafts
  • \n
  • delinked payments
\n\n

See other grants you may be eligible for

\n" - } - ], - "condition": "remainingCostsCondition" - }, - { - "title": "What type of produce is being processed?", - "path": "/produce-processed", - "components": [ - { - "name": "produceProcessedRadiosField", - "title": "What type of produce is being processed?", - "type": "RadiosField", - "list": "produceProcessedList", - "hint": "Select one option", - "options": { - "customValidationMessages": { - "any.required": "Select what type of produce is being processed" - } - } - } - ], - "condition": "otherFarmersYesOrFruitStorageCondition" - }, - { - "title": "How will this project add value to the produce?", - "path": "/how-adding-value", - "components": [ - { - "name": "howAddingValueRadiosField", - "title": "How will this project add value to the produce?", - "type": "RadiosField", - "list": "howAddingValueList", - "hint": "Select the main option that applies", - "options": { - "customValidationMessages": { - "any.required": "Select how this project will add value to the produce" - } - } - } - ], - "condition": "otherFarmersYesOrFruitStorageCondition" - }, - { - "title": "Confirmation", - "path": "/confirmation" - } - ], - "lists": [ - { - "title": "What is your business?", - "name": "natureOfBusinessList", - "type": "string", - "items": [ - { - "text": "A grower or producer of agricultural or horticultural produce", - "description": "For example, arable or livestock farmer, fruit producer, salad grower", - "value": "natureOfBusiness-A1" - }, - { - "text": "A business processing agricultural or horticultural products that is at least 50% owned by agricultural or horticultural producers", - "description": "For example, a cheese production business owned by a group of farmers", - "value": "natureOfBusiness-A2" - }, - { - "text": "A woodland manager processing wild venison products", - "value": "natureOfBusiness-A3" - }, - { - "text": "None of the above", - "value": "natureOfBusiness-A4" - } - ] - }, - { - "title": "Who is applying for this grant?", - "name": "applyingList", - "type": "string", - "items": [ - { - "text": "Applicant", - "value": "applying-A1" - }, - { - "text": "Agent", - "value": "applying-A2" - } - ] - }, - { - "title": "What type of produce is being processed?", - "name": "produceProcessedList", - "type": "string", - "items": [ - { - "text": "Arable produce", - "description": "For example, crushing of oilseeds, rolling or flaking of grains as food ingredients", - "value": "produceProcessed-A1" - }, - { - "text": "Wild venison meat produce", - "description": "For example, processing and packing wild venison meat", - "value": "produceProcessed-A2" - }, - { - "text": "Dairy or meat produce", - "description": "For example, processing and bottling milk or slaughtering, cutting, processing and packing meat", - "value": "produceProcessed-A3" - }, - { - "text": "Fibre produce", - "description": "For example, processing animal hides and leather, processing fibres such as wool, flax and hemp", - "value": "produceProcessed-A4" - }, - { - "text": "Fodder produce", - "description": "For example, processing and repacking hay and straw for specialist markets or retail sale", - "value": "produceProcessed-A5" - }, - { - "text": "Horticultural produce", - "description": "For example, grading and packing of soft fruit, washing and packing vegetables, packing salad crops", - "value": "produceProcessed-A6" - }, - { - "text": "Non-edible produce", - "description": "For example, processing and packing ornamental flowers and bulbs after harvesting", - "value": "produceProcessed-A7" - } - ] - }, - { - "title": "How will this project add value to the produce?", - "name": "howAddingValueList", - "type": "string", - "items": [ - { - "text": "Introducing a new product to your farm", - "description": "For example, processing meat to burgers, milk to cheese, cereals to beer or spirits.", - "value": "howAddingValue-A1" - }, - { - "text": "Grading or sorting produce", - "description": "For example, washing and grading vegetables, egg grading, optical grading of top fruit.", - "value": "howAddingValue-A2" - }, - { - "text": "Packing produce", - "description": "For example, packing top fruit, bagging vegetables, bottling wine.", - "value": "howAddingValuee-A3" - }, - { - "text": "A new retail facility to sell direct to consumers", - "description": "", - "value": "howAddingValue-A4" - } - ] - }, - { - "title": "What impact will this project have?", - "name": "projectImpactList", - "type": "string", - "items": [ - { - "text": "Increasing range of added-value products", - "description": "", - "value": "projectImpact-A1" - }, - { - "text": "Increasing volume of added-value products", - "description": "", - "value": "projectImpact-A2" - }, - { - "text": "Allow selling direct to consumers", - "description": "For example, retail and internet sales.", - "value": "projectImpact-A3" - }, - { - "text": "Starting to make added-value products for the first time", - "description": "This only applies if you do not already make added-value products.", - "value": "projectImpact-A4" - } - ] - }, - { - "title": "What is the legal status of your business?", - "name": "legalStatusList", - "type": "string", - "items": [ - { - "text": "Sole trader", - "value": "legalStatus-A1" - }, - { - "text": "Partnership", - "value": "legalStatus-A2" - }, - { - "text": "Limited company", - "value": "legalStatus-A3" - }, - { - "text": "Charity", - "value": "legalStatus-A4" - }, - { - "text": "Trust", - "value": "legalStatus-A5" - }, - { - "text": "Limited liability partnership", - "value": "legalStatus-A6" - }, - { - "text": "Community interest company", - "value": "legalStatus-A7" - }, - { - "text": "Limited partnership", - "value": "legalStatus-A8" - }, - { - "text": "Industrial and provident society", - "value": "legalStatus-A9" - }, - { - "text": "Co-operative society (Co-Op)", - "value": "legalStatus-A10" - }, - { - "text": "Community benefit society (BenCom)", - "value": "legalStatus-A11" - }, - { - "text": "None of the above", - "value": "legalStatus-A12" - } - ] - }, - { - "title": "Does the project have planning permission?", - "name": "planningPermissionList", - "type": "string", - "items": [ - { - "text": "Not needed", - "value": "planningPermission-A1" - }, - { - "text": "Secured", - "value": "planningPermission-A2" - }, - { - "text": "Should be in place by the time I make my full application", - "value": "planningPermission-A3" - }, - { - "text": "Will not be in place by the time I make my full application", - "value": "planningPermission-A4" - } - ] - }, - { - "title": "Have you already started work on the project?", - "name": "projectStartList", - "type": "string", - "items": [ - { - "text": "Yes, preparatory work", - "description": "For example, quotes from suppliers, applying for planning permission", - "value": "projectStart-A1" - }, - { - "text": "Yes, we have begun project work", - "description": "For example, started construction work, signing contracts, placing orders", - "value": "projectStart-A2" - }, - { - "text": "No, we have not done any work on this project yet", - "value": "projectStart-A3" - } - ] - }, - { - "title": "What eligible items does your project need?", - "name": "projectItemsList", - "type": "string", - "items": [ - { - "text": "Constructing or improving buildings for processing", - "description": "For example, a new building for cheese making, extending an existing building to install a new meat-cutting and packing line", - "value": "projectItems-A1" - }, - { - "text": "Processing equipment or machinery", - "description": "For example, equipment and machinery for pasteurising and bottling milk, a meat cutting and packing line or vegetable washing and packing", - "value": "projectItems-A2" - }, - { - "text": "Retail facilities", - "description": "For example, shops or display cabinets", - "value": "projectItems-A3" - }, - { - "text": "None of the above", - "value": "projectItems-A4" - } - ] - }, - { - "title": "Does your project also need storage facilities?", - "name": "storageRadiosList", - "type": "string", - "items": [ - { - "text": "Yes, we will need storage facilities", - "value": "storageRadios-A1" - }, - { - "text": "No, we do not need storage facilities", - "value": "storageRadios-A2" - } - ] - }, - { - "title": "How much manual labour will the mechanisation be equal to?", - "name": "manualLabourAmountList", - "type": "string", - "items": [ - { - "text": "Up to 5% of workforce", - "value": "manualLabourAmount-A1" - }, - { - "text": "Between 5% and 10%", - "value": "manualLabourAmount-A2" - }, - { - "text": "More than 10%", - "value": "manualLabourAmount-A3" - } - ] - }, - { - "title": "Consent optional", - "name": "consent-optional-list", - "type": "string", - "items": [ - { - "text": "(Optional) I consent to being contacted by Defra or a third party about service improvements", - "value": "consentOptional-A1" - } - ] - } - ], - "sections": [ - { - "title": "Eligibility", - "name": "EligibilitySection", - "hideTitle": false - } - ], - "conditions": [ - { - "name": "natureOfBusinessCondition", - "displayName": "natureOfBusinessNone", - "value": { - "name": "natureOfBusinessNone", - "conditions": [ - { - "field": { - "name": "natureOfBusinessRadiosField", - "type": "RadiosField", - "display": "What is your business?" - }, - "operator": "is", - "value": { - "type": "Value", - "value": "natureOfBusiness-A4", - "display": "None of the above" - } - } - ] - } - }, - { - "name": "legalStatusCondition", - "displayName": "legalStatusNone", - "value": { - "name": "legalStatusNone", - "conditions": [ - { - "field": { - "name": "legalStatusRadiosField", - "type": "RadiosField", - "display": "What is the legal status of the business?" - }, - "operator": "is", - "value": { - "type": "Value", - "value": "legalStatus-A12", - "display": "None of the above" - } - } - ] - } - }, - { - "name": "countryCondition", - "displayName": "plannedProjectNo", - "value": { - "name": "plannedProjectNo", - "conditions": [ - { - "field": { - "name": "countryYesNoField", - "type": "YesNoField", - "display": "Is the planned project in England?" - }, - "operator": "is", - "value": { - "type": "Value", - "value": "false", - "display": "No" - } - } - ] - } - }, - { - "name": "planningPermissionShouldBeCondition", - "displayName": "planningPermissionShouldBe", - "value": { - "name": "planningPermissionShouldBe", - "conditions": [ - { - "field": { - "name": "planningPermissionRadiosField", - "type": "RadiosField", - "display": "Does the project have planning permission?" - }, - "operator": "is", - "value": { - "type": "Value", - "value": "planningPermission-A3", - "display": "Should be in place by the time I make my full application" - } - } - ] - } - }, - { - "name": "planningPermissionWillNotCondition", - "displayName": "planningPermissionWillNot", - "value": { - "name": "planningPermissionWillNot", - "conditions": [ - { - "field": { - "name": "planningPermissionRadiosField", - "type": "RadiosField", - "display": "Does the project have planning permission?" - }, - "operator": "is", - "value": { - "type": "Value", - "value": "planningPermission-A4", - "display": "Will not be in place by the time I make my full application" - } - } - ] - } - }, - { - "name": "projectStartCondition", - "displayName": "startProjectWork", - "value": { - "name": "startProjectWork", - "conditions": [ - { - "field": { - "name": "projectStartRadiosField", - "type": "RadiosField", - "display": "Have you already started work on the project?" - }, - "operator": "is", - "value": { - "type": "Value", - "value": "projectStart-A2", - "display": "Yes, we have begun project work" - } - } - ] - } - }, - { - "name": "tenancyCondition", - "displayName": "tenancyConditionNo", - "value": { - "name": "tenancyConditionNo", - "conditions": [ - { - "field": { - "name": "tenancyYesNoField", - "type": "YesNoField", - "display": "Is the planned project on land the business owns?" - }, - "operator": "is", - "value": { - "type": "Value", - "value": "false", - "display": "No" - } - } - ] - } - }, - { - "name": "tenancyLengthCondition", - "displayName": "tenancyLengthConditionNo", - "value": { - "name": "tenancyLengthConditionNo", - "conditions": [ - { - "field": { - "name": "tenancyLengthYesNoField", - "type": "YesNoField", - "display": "Do you have a tenancy agreement for 5 years after the final grant payment?" - }, - "operator": "is", - "value": { - "type": "Value", - "value": "false", - "display": "No" - } - } - ] - } - }, - { - "name": "smallerAbattoirConditionYes", - "displayName": "smallerAbattoirConditionYes", - "value": { - "name": "smallerAbattoirConditionYes", - "conditions": [ - { - "field": { - "name": "smallerAbattoirYesNoField", - "type": "YesNoField", - "display": "Do you want to build a new smaller abattoir?" - }, - "operator": "is", - "value": { - "type": "Value", - "value": "true", - "display": "Yes" - } - } - ] - } - }, - { - "name": "smallerAbattoirConditionNo", - "displayName": "smallerAbattoirConditionNo", - "value": { - "name": "smallerAbattoirConditionNo", - "conditions": [ - { - "field": { - "name": "smallerAbattoirYesNoField", - "type": "YesNoField", - "display": "Do you want to build a new smaller abattoir?" - }, - "operator": "is", - "value": { - "type": "Value", - "value": "false", - "display": "No" - } - } - ] - } - }, - { - "name": "otherFarmersConditionNo", - "displayName": "otherFarmersConditionNo", - "value": { - "name": "otherFarmersConditionNo", - "conditions": [ - { - "field": { - "name": "otherFarmersYesNoField", - "type": "YesNoField", - "display": "Will this abattoir provide services to other farmers?" - }, - "operator": "is", - "value": { - "type": "Value", - "value": "false", - "display": "No" - } - } - ] - } - }, - { - "name": "otherFarmersConditionYes", - "displayName": "otherFarmersConditionYes", - "value": { - "name": "otherFarmersConditionYes", - "conditions": [ - { - "field": { - "name": "otherFarmersYesNoField", - "type": "YesNoField", - "display": "Will this abattoir provide services to other farmers?" - }, - "operator": "is", - "value": { - "type": "Value", - "value": "true", - "display": "Yes" - } - } - ] - } - }, - { - "name": "fruitStorageCondition", - "displayName": "fruitStorageConditionNo", - "value": { - "name": "fruitStorageConditionNo", - "conditions": [ - { - "field": { - "name": "fruitStorageYesNoField", - "type": "YesNoField", - "display": "Do you want to build new controlled atmosphere storage for top fruit?" - }, - "operator": "is", - "value": { - "type": "Value", - "value": "false", - "display": "No" - } - } - ] - } - }, - { - "name": "otherFarmersYesOrFruitStorageCondition", - "displayName": "otherFarmersYesOrFruitStorage", - "value": { - "name": "otherFarmersYesOrFruitStorage", - "conditions": [ - { - "conditionName": "otherFarmersConditionYes", - "conditionDisplayName": "Yes" - }, - { - "coordinator": "or", - "conditionName": "fruitStorageCondition", - "conditionDisplayName": "No" - } - ] - } - }, - { - "name": "projectItemsCondition", - "displayName": "projectItemsConditionNone", - "value": { - "name": "projectItemsConditionNone", - "conditions": [ - { - "field": { - "name": "projectItemsCheckboxesField", - "type": "CheckboxesField", - "display": "What eligible items does your project need?" - }, - "operator": "contains", - "value": { - "type": "Value", - "value": "projectItems-A4", - "display": "None of the above" - } - } - ] - } - }, - { - "name": "projectCostCondition", - "displayName": "projectCostConditionNotReached", - "value": { - "name": "projectCostConditionNotReached", - "conditions": [ - { - "field": { - "name": "projectCostNumberField", - "type": "NumberField", - "display": "What is the estimated cost of the items?" - }, - "operator": "is less than", - "value": { - "type": "Value", - "value": "62500", - "display": "Enter amount" - } - } - ] - } - }, - { - "name": "remainingCostsCondition", - "displayName": "canYouPayTheRemainingCosts", - "value": { - "name": "canYouPayTheRemainingCosts", - "conditions": [ - { - "field": { - "name": "remainingCostsYesNoField", - "type": "YesNoField", - "display": "Can you pay the remaining costs of £x?" - }, - "operator": "is", - "value": { - "type": "Value", - "value": "false", - "display": "No" - } - } - ] - } - }, - { - "name": "mechanisationCondition", - "displayName": "mechanisationYes", - "value": { - "name": "mechanisationYes", - "conditions": [ - { - "field": { - "name": "mechanisationYesNoField", - "type": "YesNoField", - "display": "Will this project use any mechanisation instead of manual labour?" - }, - "operator": "is", - "value": { - "type": "Value", - "value": "true", - "display": "Yes" - } - } - ] - } - }, - { - "name": "agentDetailsSelected", - "displayName": "agentDetailsSelected", - "value": { - "name": "agentDetailsSelected", - "conditions": [ - { - "field": { - "name": "whoIsApplyingForThisGrantComponent", - "type": "RadiosField", - "display": "Who is applying for this grant?" - }, - "operator": "is", - "value": { - "type": "Value", - "value": "applying-A2", - "display": "Agent" - } - } - ] - } - } - ] -} diff --git a/src/server/plugins/engine/services/localFormsService.js b/src/server/plugins/engine/services/localFormsService.js index d80d8189e..cb6e1a834 100644 --- a/src/server/plugins/engine/services/localFormsService.js +++ b/src/server/plugins/engine/services/localFormsService.js @@ -45,19 +45,11 @@ export const formsService = async () => { slug: 'register-as-a-unicorn-breeder-yaml' // if we needed to validate any JSON logic, make it available for convenience }) - // Add a Yaml form - await loader.addForm('src/server/forms/adding-value.json', { - ...metadata, - id: 'a0f1b8c2-4d3e-4f5b-9a7c-6d1e0f2b5c3d', - title: 'Adding value', - slug: 'adding-value' // if we needed to validate any JSON logic, make it available for convenience - }) - await loader.addForm('src/server/forms/components.json', { ...metadata, id: '6a872d3b-13f9e-804ce3e-4830-5c45fb32', title: 'Components', - slug: 'components' // if we needed to validate any JSON logic, make it available for convenience + slug: 'components' }) return loader.toFormsService() diff --git a/src/server/plugins/nunjucks/context.js b/src/server/plugins/nunjucks/context.js index 663b334a4..142edec04 100644 --- a/src/server/plugins/nunjucks/context.js +++ b/src/server/plugins/nunjucks/context.js @@ -73,7 +73,7 @@ export async function context(request) { /** * Returns the context for the devtool. Consumers won't have access to this. * @param {FormRequest | FormRequestPayload | null} _request - * @returns {Record} + * @returns {Promise & { assetPath: string, getDxtAssetPath: (asset: string) => string }>} */ export function devtoolContext(_request) { const manifestPath = join(config.get('publicDir'), 'assets-manifest.json') diff --git a/src/server/plugins/nunjucks/context.test.js b/src/server/plugins/nunjucks/context.test.js index 91865cf4d..8efa048f1 100644 --- a/src/server/plugins/nunjucks/context.test.js +++ b/src/server/plugins/nunjucks/context.test.js @@ -9,15 +9,15 @@ describe('Nunjucks context', () => { beforeEach(() => jest.resetModules()) describe('Asset path', () => { - it("should include 'assetPath' for GOV.UK Frontend icons", () => { - const { assetPath } = devtoolContext() + it("should include 'assetPath' for GOV.UK Frontend icons", async () => { + const { assetPath } = await devtoolContext(null) expect(assetPath).toBe('/assets') }) }) describe('Asset helper', () => { - it("should locate 'assets-manifest.json' assets", () => { - const { getDxtAssetPath } = devtoolContext() + it("should locate 'assets-manifest.json' assets", async () => { + const { getDxtAssetPath } = await devtoolContext(null) expect(getDxtAssetPath('example.scss')).toBe( '/stylesheets/example.xxxxxxx.min.css' @@ -39,7 +39,7 @@ describe('Nunjucks context', () => { // Update config for missing manifest config.set('publicDir', tmpdir()) - const { getDxtAssetPath } = devtoolContext() + const { getDxtAssetPath } = await devtoolContext(null) // Uses original paths when missing expect(getDxtAssetPath('example.scss')).toBe('/example.scss') @@ -47,25 +47,25 @@ describe('Nunjucks context', () => { }) }) - it('should return path to unknown assets', () => { - const { getDxtAssetPath } = devtoolContext() + it('should return path to unknown assets', async () => { + const { getDxtAssetPath } = await devtoolContext(null) - expect(getDxtAssetPath()).toBe('/') + expect(getDxtAssetPath('')).toBe('/') expect(getDxtAssetPath('example.jpg')).toBe('/example.jpg') expect(getDxtAssetPath('example.gif')).toBe('/example.gif') }) }) describe('Config', () => { - it('should include environment, phase tag and service info', () => { - expect(() => context(null)).toThrow( + it('should include environment, phase tag and service info', async () => { + await expect(context(null)).rejects.toThrow( 'context called before plugin registered' ) }) }) describe('Crumb', () => { - it('should handle malformed requests with missing state', () => { + it('should handle malformed requests with missing state', async () => { // While state should always exist in a valid Hapi request (it holds cookies), // we've seen malformed requests in production where it's missing const malformedRequest = /** @type {FormRequest} */ ( @@ -92,14 +92,14 @@ describe('Nunjucks context', () => { }) ) - const { crumb } = context(malformedRequest) + const { crumb } = await context(malformedRequest) expect(crumb).toBeUndefined() expect( malformedRequest.server.plugins.crumb.generate ).not.toHaveBeenCalled() }) - it('should generate crumb when state exists', () => { + it('should generate crumb when state exists', async () => { const mockCrumb = 'generated-crumb-value' const validRequest = /** @type {FormRequest} */ ( /** @type {unknown} */ ({ @@ -125,7 +125,7 @@ describe('Nunjucks context', () => { }) ) - const { crumb } = context(validRequest) + const { crumb } = await context(validRequest) expect(crumb).toBe(mockCrumb) expect(validRequest.server.plugins.crumb.generate).toHaveBeenCalledWith( validRequest From 9b8194f47c9ad52dae4b2de125b40b6791ff89b0 Mon Sep 17 00:00:00 2001 From: Alex Luckett Date: Fri, 2 May 2025 16:34:50 +0100 Subject: [PATCH 3/7] Fix documentation inconsistency --- docs/GETTING_STARTED.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 9c24a1453..83e27c80c 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -103,8 +103,8 @@ await server.register({ * Options that DXT uses to render Nunjucks templates */ nunjucks: { - basePageLayout: 'your-base-layout.html', // the base page layout. Usually based off https://design-system.service.gov.uk/styles/page-template/ - viewPaths // list of directories DXT should use to render your views. Must contain basePageLayout. + baseLayoutPath: 'your-base-layout.html', // the base page layout. Usually based off https://design-system.service.gov.uk/styles/page-template/ + viewPaths // list of directories DXT should use to render your views. Must contain baseLayoutPath. }, /** * Services is what DXT uses to interact with external APIs From 51bd8c9b0ea60028ac331a5e579f011458e1e7f1 Mon Sep 17 00:00:00 2001 From: Alex Luckett Date: Tue, 6 May 2025 10:32:54 +0100 Subject: [PATCH 4/7] Fix types for view context --- src/server/plugins/engine/plugin.ts | 5 ++--- src/typings/hapi/index.d.ts | 5 +++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/server/plugins/engine/plugin.ts b/src/server/plugins/engine/plugin.ts index 3bfa605a1..3ef047c49 100644 --- a/src/server/plugins/engine/plugin.ts +++ b/src/server/plugins/engine/plugin.ts @@ -6,6 +6,7 @@ import { hasFormComponents, slugSchema } from '@defra/forms-model' import Boom from '@hapi/boom' import { type Plugin, + type PluginProperties, type ResponseObject, type ResponseToolkit, type RouteOptions, @@ -95,9 +96,7 @@ export interface PluginOptions { baseLayoutPath: string paths: string[] } - viewContext: ( - request: FormRequest | FormRequestPayload | null - ) => Promise> + viewContext: PluginProperties['forms-engine-plugin']['viewContext'] } export const plugin = { diff --git a/src/typings/hapi/index.d.ts b/src/typings/hapi/index.d.ts index 11f5c27f2..d6244e321 100644 --- a/src/typings/hapi/index.d.ts +++ b/src/typings/hapi/index.d.ts @@ -5,7 +5,6 @@ import { type ServerYar, type Yar } from '@hapi/yar' import { type Logger } from 'pino' import { type FormModel } from '~/src/server/plugins/engine/models/index.js' -import { type context } from '~/src/server/plugins/engine/nunjucks.js' import { type FormRequest, type FormRequestPayload @@ -22,7 +21,9 @@ declare module '@hapi/hapi' { 'forms-engine-plugin': { baseLayoutPath: string cacheService: CacheService - viewContext: context + viewContext?: ( + request: FormRequest | FormRequestPayload | null + ) => Record | Promise> } } From 98cc35eb5adb85bc8d8e76275e089673e62d1e02 Mon Sep 17 00:00:00 2001 From: Alex Luckett Date: Tue, 6 May 2025 10:39:28 +0100 Subject: [PATCH 5/7] update viewcontext example with promise --- docs/GETTING_STARTED.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 83e27c80c..194e0d448 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -117,8 +117,13 @@ await server.register({ /** * View context attributes made available to your pages. Returns an object containing an arbitrary set of key-value pairs. */ - viewContext: async (request) => { - "example": "hello world" // available to render on a nunjucks page as {{ example }} + viewContext: async (request) => { // async can be dropped if there's no async code within + const user = await userService.getUser(request.auth.credentials) + + return { + "greeting": "Hello" // available to render on a nunjucks page as {{ greeting }} + "username": user.username // available to render on a nunjucks page as {{ username }} + } } } }) From 7c762ff1c5575c62a5ab1ea6ca136305a529a7fe Mon Sep 17 00:00:00 2001 From: Alex Luckett Date: Tue, 6 May 2025 10:42:30 +0100 Subject: [PATCH 6/7] remove unnecessary return annotation --- src/server/plugins/nunjucks/context.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server/plugins/nunjucks/context.js b/src/server/plugins/nunjucks/context.js index e733a7ee8..7cc1899a5 100644 --- a/src/server/plugins/nunjucks/context.js +++ b/src/server/plugins/nunjucks/context.js @@ -20,7 +20,6 @@ let webpackManifest /** * @param {FormRequest | FormRequestPayload | null} request - * @returns {Promise} */ export async function context(request) { const { params, response } = request ?? {} From ffb8d26da4e879b0ab7e707bb155f59c54e6001e Mon Sep 17 00:00:00 2001 From: Alex Luckett Date: Tue, 6 May 2025 10:44:43 +0100 Subject: [PATCH 7/7] drop Promise.resolve as async is not neccessary any more --- src/server/plugins/nunjucks/context.js | 6 +++--- src/server/plugins/nunjucks/context.test.js | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/server/plugins/nunjucks/context.js b/src/server/plugins/nunjucks/context.js index 7cc1899a5..df1931364 100644 --- a/src/server/plugins/nunjucks/context.js +++ b/src/server/plugins/nunjucks/context.js @@ -72,7 +72,7 @@ export async function context(request) { /** * Returns the context for the devtool. Consumers won't have access to this. * @param {FormRequest | FormRequestPayload | null} _request - * @returns {Promise & { assetPath: string, getDxtAssetPath: (asset: string) => string }>} + * @returns {Record & { assetPath: string, getDxtAssetPath: (asset: string) => string }} */ export function devtoolContext(_request) { const manifestPath = join(config.get('publicDir'), 'assets-manifest.json') @@ -86,12 +86,12 @@ export function devtoolContext(_request) { } } - return Promise.resolve({ + return { assetPath: '/assets', getDxtAssetPath: (asset = '') => { return `/${webpackManifest?.[asset] ?? asset}` } - }) + } } /** diff --git a/src/server/plugins/nunjucks/context.test.js b/src/server/plugins/nunjucks/context.test.js index 8efa048f1..35fec85f8 100644 --- a/src/server/plugins/nunjucks/context.test.js +++ b/src/server/plugins/nunjucks/context.test.js @@ -9,15 +9,15 @@ describe('Nunjucks context', () => { beforeEach(() => jest.resetModules()) describe('Asset path', () => { - it("should include 'assetPath' for GOV.UK Frontend icons", async () => { - const { assetPath } = await devtoolContext(null) + it("should include 'assetPath' for GOV.UK Frontend icons", () => { + const { assetPath } = devtoolContext(null) expect(assetPath).toBe('/assets') }) }) describe('Asset helper', () => { - it("should locate 'assets-manifest.json' assets", async () => { - const { getDxtAssetPath } = await devtoolContext(null) + it("should locate 'assets-manifest.json' assets", () => { + const { getDxtAssetPath } = devtoolContext(null) expect(getDxtAssetPath('example.scss')).toBe( '/stylesheets/example.xxxxxxx.min.css' @@ -39,7 +39,7 @@ describe('Nunjucks context', () => { // Update config for missing manifest config.set('publicDir', tmpdir()) - const { getDxtAssetPath } = await devtoolContext(null) + const { getDxtAssetPath } = devtoolContext(null) // Uses original paths when missing expect(getDxtAssetPath('example.scss')).toBe('/example.scss') @@ -47,8 +47,8 @@ describe('Nunjucks context', () => { }) }) - it('should return path to unknown assets', async () => { - const { getDxtAssetPath } = await devtoolContext(null) + it('should return path to unknown assets', () => { + const { getDxtAssetPath } = devtoolContext(null) expect(getDxtAssetPath('')).toBe('/') expect(getDxtAssetPath('example.jpg')).toBe('/example.jpg')