Skip to content

Commit

Permalink
Merge pull request #760 from DFE-Digital/development
Browse files Browse the repository at this point in the history
Release v2.5.0
  • Loading branch information
jimwashbrook committed Aug 29, 2024
2 parents f5a4743 + cfe5857 commit ca045c9
Show file tree
Hide file tree
Showing 45 changed files with 3,070 additions and 189 deletions.
11 changes: 9 additions & 2 deletions .github/workflows/deploy-azure-function.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ jobs:
az_client_id: ${{ secrets.AZ_CLIENT_ID }}
az_client_secret: ${{ secrets.AZ_CLIENT_SECRET }}

- name: Set storage account name var
shell: bash
run: echo "STORAGE_ACCOUNT=$(echo ${{ env.AZURE_STORAGEACCOUNT_NAME }} | sed -e "s/-//g")" >> $GITHUB_ENV


- name: Get workflow IP address
id: whats-my-ip
uses: ./.github/actions/whats-my-ip-address
Expand All @@ -51,8 +56,9 @@ jobs:
# The Azure Storage Account's name contains no hyphens, unlike almost all of our other resources
# Therefore remove hyphens from the string before using it as the account name
run: |
az storage account update --name $STORAGE_ACCOUNT --resource-group ${{ env.AZURE_RESOURCEGROUP_NAME }} --public-network-access Enabled &> /dev/null
az storage account network-rule add --resource-group ${{ env.AZURE_RESOURCEGROUP_NAME }} \
--account-name $(echo ${{ env.AZURE_STORAGEACCOUNT_NAME }} | sed -e "s/-//g") \
--account-name $STORAGE_ACCOUNT \
--ip-address ${{ steps.whats-my-ip.outputs.ip }} &> /dev/null
- name: Deploy Azure Function App
Expand All @@ -66,5 +72,6 @@ jobs:
if: always()
run: |
az storage account network-rule remove --resource-group ${{ env.AZURE_RESOURCEGROUP_NAME }} \
--account-name $(echo ${{ env.AZURE_STORAGEACCOUNT_NAME }} | sed -e "s/-//g") \
--account-name $STORAGE_ACCOUNT \
--ip-address ${{ steps.whats-my-ip.outputs.ip }} &> /dev/null
az storage account update --name $STORAGE_ACCOUNT --resource-group ${{ env.AZURE_RESOURCEGROUP_NAME }} --public-network-access Disabled &> /dev/null
2 changes: 1 addition & 1 deletion contentandsupport
Submodule contentandsupport updated 75 files
+84 −0 .github/workflows/code-pr-check.yml
+4 −4 .github/workflows/matrix-deploy.yml
+41 −0 src/Dfe.ContentSupport.Web.Node/styles/scss/vertical-navigation.scss
+1 −0 src/Dfe.ContentSupport.Web/Configuration/CsContentfulOptions.cs
+39 −9 src/Dfe.ContentSupport.Web/Controllers/ContentController.cs
+9 −2 src/Dfe.ContentSupport.Web/Controllers/SitemapController.cs
+7 −19 src/Dfe.ContentSupport.Web/Dfe.ContentSupport.Web.csproj
+9 −0 src/Dfe.ContentSupport.Web/Extensions/DateTimeExtensions.cs
+1 −0 src/Dfe.ContentSupport.Web/Extensions/WebApplicationBuilderExtensions.cs
+17 −1 src/Dfe.ContentSupport.Web/Http/HttpContentfulClient.cs
+5 −2 src/Dfe.ContentSupport.Web/Models/ContentBase.cs
+3 −3 src/Dfe.ContentSupport.Web/Models/ContentItemBase.cs
+2 −0 src/Dfe.ContentSupport.Web/Models/Mapped/CsContentItem.cs
+9 −0 src/Dfe.ContentSupport.Web/Models/Mapped/CsPage.cs
+1 −1 src/Dfe.ContentSupport.Web/Models/Mapped/Custom/CustomAccordion.cs
+2 −1 src/Dfe.ContentSupport.Web/Models/Mapped/Custom/CustomAttachment.cs
+10 −0 src/Dfe.ContentSupport.Web/Models/Mapped/PageLink.cs
+1 −0 src/Dfe.ContentSupport.Web/Models/Mapped/RichTextContentItem.cs
+2 −0 src/Dfe.ContentSupport.Web/Models/Sys.cs
+0 −2 src/Dfe.ContentSupport.Web/Models/Target.cs
+4 −5 src/Dfe.ContentSupport.Web/Program.cs
+10 −0 src/Dfe.ContentSupport.Web/Services/ILayoutService.cs
+85 −0 src/Dfe.ContentSupport.Web/Services/LayoutService.cs
+39 −15 src/Dfe.ContentSupport.Web/Services/ModelMapper.cs
+6 −4 src/Dfe.ContentSupport.Web/ViewModels/ContentSupportPage.cs
+96 −3 src/Dfe.ContentSupport.Web/Views/Content/CsIndex.cshtml
+2 −2 src/Dfe.ContentSupport.Web/Views/Content/Home.cshtml
+1 −1 src/Dfe.ContentSupport.Web/Views/Content/Privacy.cshtml
+1 −1 src/Dfe.ContentSupport.Web/Views/Shared/Error.cshtml
+1 −1 src/Dfe.ContentSupport.Web/Views/Shared/RichText/Custom/_Accordion.cshtml
+55 −40 src/Dfe.ContentSupport.Web/Views/Shared/RichText/Custom/_Attachment.cshtml
+2 −2 src/Dfe.ContentSupport.Web/Views/Shared/RichText/_H.cshtml
+11 −0 src/Dfe.ContentSupport.Web/Views/Shared/RichText/_H2.cshtml
+11 −0 src/Dfe.ContentSupport.Web/Views/Shared/RichText/_H3.cshtml
+11 −0 src/Dfe.ContentSupport.Web/Views/Shared/RichText/_H4.cshtml
+11 −0 src/Dfe.ContentSupport.Web/Views/Shared/RichText/_H5.cshtml
+11 −0 src/Dfe.ContentSupport.Web/Views/Shared/RichText/_H6.cshtml
+1 −1 src/Dfe.ContentSupport.Web/Views/Shared/RichText/_HyperLink.cshtml
+1 −1 src/Dfe.ContentSupport.Web/Views/Shared/RichText/_Paragraph.cshtml
+23 −18 src/Dfe.ContentSupport.Web/Views/Shared/RichText/_RichText.cshtml
+1 −1 src/Dfe.ContentSupport.Web/Views/Shared/RichText/_UnorderedList.cshtml
+7 −0 src/Dfe.ContentSupport.Web/Views/Shared/_BackToTop.cshtml
+2 −1 src/Dfe.ContentSupport.Web/Views/Shared/_BetaHeader.cshtml
+15 −0 src/Dfe.ContentSupport.Web/Views/Shared/_Citation.cshtml
+9 −5 src/Dfe.ContentSupport.Web/Views/Shared/_CookieConsent.cshtml
+3 −5 src/Dfe.ContentSupport.Web/Views/Shared/_CsHeader.cshtml
+1 −3 src/Dfe.ContentSupport.Web/Views/Shared/_CsLayout.cshtml
+59 −0 src/Dfe.ContentSupport.Web/Views/Shared/_Feedback.cshtml
+2 −2 src/Dfe.ContentSupport.Web/Views/Shared/_Footer.cshtml
+1 −1 src/Dfe.ContentSupport.Web/Views/Shared/_Hero.cshtml
+3 −0 src/Dfe.ContentSupport.Web/Views/Shared/_Print.cshtml
+1 −1 src/Dfe.ContentSupport.Web/Views/Shared/_UnsupportedElement.cshtml
+2 −0 src/Dfe.ContentSupport.Web/Views/Sitemap/Index.cshtml
+0 −20 src/Dfe.ContentSupport.Web/appsettings.json
+ src/Dfe.ContentSupport.Web/wwwroot/assets/icon-print.png
+1 −1 src/Dfe.ContentSupport.Web/wwwroot/assets/images/generic-file-icon.svg
+1 −1 src/Dfe.ContentSupport.Web/wwwroot/assets/images/html-file-icon.svg
+1 −1 src/Dfe.ContentSupport.Web/wwwroot/assets/images/pdf-file-icon.svg
+1 −1 src/Dfe.ContentSupport.Web/wwwroot/assets/images/spreadsheet-file-icon.svg
+380 −4 src/Dfe.ContentSupport.Web/wwwroot/css/cands-site.css
+30 −0 tests/Dfe.ContentSupport.Web.E2ETests/cypress/e2e/pages/feedback.cy.js
+25 −0 tests/Dfe.ContentSupport.Web.E2ETests/cypress/e2e/pages/print-button.cy.js
+2 −1 tests/Dfe.ContentSupport.Web.E2ETests/cypress/e2e/pages/rich-text.cy.js
+46 −3 tests/Dfe.ContentSupport.Web.Tests/Controllers/ContentControllerTests.cs
+4 −4 tests/Dfe.ContentSupport.Web.Tests/Controllers/SitemapControllerTests.cs
+9 −8 tests/Dfe.ContentSupport.Web.Tests/Dfe.ContentSupport.Web.Tests.csproj
+17 −0 tests/Dfe.ContentSupport.Web.Tests/Extensions/DateTimeExtensionsTests.cs
+22 −9 tests/Dfe.ContentSupport.Web.Tests/Models/Mapped/Custom/CustomAccordionTests.cs
+9 −4 tests/Dfe.ContentSupport.Web.Tests/Models/Mapped/Custom/CustomAttachmentTests.cs
+6 −6 tests/Dfe.ContentSupport.Web.Tests/Models/Mapped/Custom/CustomCardTests.cs
+10 −8 tests/Dfe.ContentSupport.Web.Tests/Models/Mapped/Custom/CustomGridContainerTests.cs
+3 −2 tests/Dfe.ContentSupport.Web.Tests/Models/Mapped/Standard/EmbeddedEntryTests.cs
+9 −5 tests/Dfe.ContentSupport.Web.Tests/Services/ContentServiceTests.cs
+231 −0 tests/Dfe.ContentSupport.Web.Tests/Services/LayoutServiceTests.cs
+17 −6 tests/Dfe.ContentSupport.Web.Tests/Services/ModelMapperTests.cs
66 changes: 66 additions & 0 deletions docs/adr/0037-content-validation-process.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# 0037 - Content Validation Process

* **Status**: proposed

## Context and Problem Statement

Certain fields in Contentful are mandatory, and we currently do not save any content items which are invalid, due to missing such fields.
This poses a problem with the following content creation process:
1. A question is created and details filled in
2. A new answer is added to that question
3. The answer is incomplete, so invalid, so doesn't save
4. Therefore no question -> answer relationship is saved
5. The answer is filled in
6. It then gets saved to the db on its own
7. the question -> answer relationship was never re-attempted, so still does not exist
8. The question then doesn't show this answer in production

This can be resolved by republishing the question, but it is frustrating for content designers to have to repeatedly republish content.
It also leaves production missing content until someone notices, so we need a way to fix this issue.

## Decision Drivers

- Ease of development
- Reliability of the solution
- How testable the solution is
- Minimising risk of error for the function
- Minimising risk of error in the web app

## Considered Options

### Overnight/periodic job to run a content validator and retrieve missing items:

- Pros
- Minimal regression risk to web app as it can stay exactly as is
- Can handle any error that has occured with the function, not just this, including it temporarily going down entirely
- Cons
- In the time between an issue arising and the periodic job running, the app could be broken due to missing relationships, so this may not be adequate
- May be resource intensive if run frequently, as it does full validation
- Requires developing a solution to fix the issues found by the validator and setting up periodic jobs

### Making all database fields (except primary keys) nullable:

- Pros
- Robust approach to the problem, it's highly likely to ensure the correct relationships save to the database every time
- Minimal risk of errors within the Azure function
- Cons
- High risk of hitting an error page in the web app when partial content exists, due to it expecting content that isn't there
- Would require extensive testing with partially complete content items to be sure of the web app not breaking
- Widespread changes within the code base, risk of regression

### Having default values for simple fields, and nullable content references
Not every field can have a default, for instance a question section requires an interstitial page, which cannot be populated with a default value,
but we could make every field that can have a default, have a default and the others nullable.

- Pros
- Lower risk than making all fields nullable, as it requires fewer changes to the web app
- Also a robust approach that is highly likely to work
- Relatively low risk of errors within the Azure function
- Cons
- May still be quite a few code changes involved in making all content references nullable, with the same risks as the option above (but less widespread)
- Needs careful testing of all types which are made nullable
- The defaults will render incomplete content "valid" when it isn't. This risk is small because Contentful doesn't allow publishing content with missing mandatory fields. So only UsePreview environments have a risk of showing blank content.

## Decision Outcome

Option 3. It's lowest risk and involvement whilst effectively resolving the problem. The suggested implementation will be to modify the way the Azure function maps a json object into database entities and add defaults at this stage, to minimise the changes required.
11 changes: 6 additions & 5 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ General information about architectural decision records is available at <https:
## List

| Title | Status |
| ----------------------------------------------------------------------------------------------------------- | -------- |
|-------------------------------------------------------------------------------------------------------------|----------|
| [0001 - Web Framework](./0001-web-framework.md) | Accepted |
| [0002 - Application Framework](./0002-application-framework.md) | Accepted |
| [0003 - Build/Test/Deploy Environment](./0002-application-framework.md) | Accepted |
Expand Down Expand Up @@ -40,10 +40,11 @@ General information about architectural decision records is available at <https:
| [0027 - Accesibility Testing](./0027-accesibility-testing.md) | Accepted |
| [0028 - Command Line Arguments Parsing](./0028-command-line-arguments.md) | Accepted |
| [0029 - Terraform input variables](./0029-terraform-variable-for-admin-password.md) | Accepted |
| [0030 - Storing CMS Data In Database][./0030-storing-cms-in-db.md] | Accepted |
| [0031 - CMS DB Structure][./0031-cms-db-structure.md] | Accepted |
| [0032 - Classes/Code Structure for DB Entities][./0032-classes-for-db-entities.md] | Accepted |
| [0030 - Storing CMS Data In Database](./0030-storing-cms-in-db.md) | Accepted |
| [0031 - CMS DB Structure](./0031-cms-db-structure.md) | Accepted |
| [0032 - Classes/Code Structure for DB Entities](./0032-classes-for-db-entities.md) | Accepted |
| [0033 - Test data generation](./0033-test-data-generation.md) | Accepted |
| [0034 - Entity mapping](./0034-entity-mapping.md) | Accepted |
| [0035 - Caching](./0035-caching.md) | Accepted |
| [0036 - DB Unit Tests](./0036-db-unit-tests.md) | Proposed |
| [0036 - DB Unit Tests](./0036-db-unit-tests.md) | Proposed |
| [0037 - Content Validation Process](./0037-content-validation-process.md) | Proposed |
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,20 @@ public async Task<List<SectionStatusDto>> GetSectionSubmissionStatuses(string ca
return await db.ToListAsync(db.GetSectionStatuses(categoryId, establishmentId));
}

public async Task<SectionStatusNew> GetSectionSubmissionStatusAsync(int establishmentId,
public async Task<SectionStatus> GetSectionSubmissionStatusAsync(int establishmentId,
ISectionComponent section,
bool completed,
CancellationToken cancellationToken)
{
var sectionStatus = db.GetSubmissions.Where(submission => submission.EstablishmentId == establishmentId &&
submission.SectionId == section.Sys.Id && !submission.Deleted);
var sectionStatus = db.GetSubmissions.Where(submission => submission.EstablishmentId == establishmentId
&& submission.SectionId == section.Sys.Id
&& !submission.Deleted);

var groupedAndLatest = GetLatestSubmissionStatus(sectionStatus, completed);

var result = await db.FirstOrDefaultAsync(groupedAndLatest, cancellationToken);

return result ?? new SectionStatusNew()
return result ?? new SectionStatus()
{
SectionId = section.Sys.Id,
Completed = false,
Expand All @@ -46,8 +47,8 @@ public async Task<SectionStatusNew> GetSectionSubmissionStatusAsync(int establis
/// <param name="submissionStatuses"></param>
/// <param name="completed"></param>
/// <returns></returns>
private static IQueryable<SectionStatusNew> GetLatestSubmissionStatus(IQueryable<Submission> submissionStatuses, bool completed)
=> submissionStatuses.Select(submission => new SectionStatusNew()
private static IQueryable<SectionStatus> GetLatestSubmissionStatus(IQueryable<Submission> submissionStatuses, bool completed)
=> submissionStatuses.Select(submission => new SectionStatus()
{
DateCreated = submission.DateCreated,
Completed = submission.Completed,
Expand All @@ -56,8 +57,5 @@ private static IQueryable<SectionStatusNew> GetLatestSubmissionStatus(IQueryable
Status = submission.Completed ? Status.Completed : submission.Responses.Count != 0 ? Status.InProgress : Status.NotStarted,
})
.GroupBy(submission => submission.SectionId)
.Select(grouping => grouping
.OrderByDescending(status => completed && status.Completed)
.ThenByDescending(status => status.DateCreated)
.First());
.Select(grouping => grouping.OrderByDescending(status => completed && status.Completed).ThenByDescending(status => status.DateCreated).First());
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public ISectionComponent Section
}
public IUser User { get; init; }
public Question? NextQuestion { get; set; }
public SectionStatusNew? SectionStatus { get; private set; }
public SectionStatus? SectionStatus { get; private set; }
public SubmissionStatus Status { get; set; }

public SubmissionStatusProcessor(IGetSectionQuery getSectionQuery,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
CREATE OR ALTER PROCEDURE GetCurrentSubmissionId
@sectionId NVARCHAR(50),
@establishmentId INT,
@submissionId INT OUTPUT
AS

SELECT @submissionId = (
SELECT TOP 1 Id
FROM [dbo].[submission]
WHERE completed = 0
AND deleted = 0
AND sectionId = @sectionId
AND establishmentId = @establishmentId
ORDER BY dateCreated DESC
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
ALTER PROCEDURE SelectOrInsertSubmissionId
@sectionId NVARCHAR(50),
@sectionName NVARCHAR(50),
@establishmentId INT,
@submissionId INT OUTPUT
AS

BEGIN TRY
BEGIN TRAN
EXEC GetCurrentSubmissionId
@sectionid=@sectionId,
@establishmentId=@establishmentId,
@submissionId=@submissionId OUTPUT

IF @submissionId IS NULL
BEGIN
INSERT INTO [dbo].[submission]
(establishmentId, completed, sectionId, sectionName)
OUTPUT INSERTED.ID
VALUES
(@establishmentId, 0, @sectionId, @sectionName)

SELECT @submissionId = SCOPE_IDENTITY()
END
COMMIT TRAN
END TRY
BEGIN CATCH
ROLLBACK TRAN
END CATCH
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
CREATE OR ALTER PROCEDURE DeleteCurrentSubmission
@sectionId NVARCHAR(50),
@sectionName NVARCHAR(50),
@establishmentId INT
AS
BEGIN TRY
DECLARE @submissionId INT

EXEC GetCurrentSubmissionId
@sectionid=@sectionId,
@establishmentId=@establishmentId,
@submissionId=@submissionId OUTPUT

BEGIN TRAN
UPDATE S
SET deleted = 1
FROM [dbo].[submission] S
WHERE S.id = @submissionId
COMMIT TRAN

END TRY
BEGIN CATCH
ROLLBACK TRAN
END CATCH
GO
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public interface IGetSubmissionStatusesQuery
{
Task<List<SectionStatusDto>> GetSectionSubmissionStatuses(string categoryId);

Task<SectionStatusNew> GetSectionSubmissionStatusAsync(int establishmentId,
Task<SectionStatus> GetSectionSubmissionStatusAsync(int establishmentId,
ISectionComponent section,
bool completed,
CancellationToken cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public interface ISubmissionStatusProcessor
public SubmissionStatus Status { get; set; }
public Question? NextQuestion { get; set; }
public ISectionComponent Section { get; }
public SectionStatusNew? SectionStatus { get; }
public SectionStatus? SectionStatus { get; }

Task GetJourneyStatusForSection(string sectionSlug, CancellationToken cancellationToken);
Task GetJourneyStatusForSectionRecommendation(string sectionSlug, CancellationToken cancellationToken);
Expand Down
15 changes: 1 addition & 14 deletions src/Dfe.PlanTech.Domain/Submissions/Models/SectionStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,7 @@

namespace Dfe.PlanTech.Domain.Submissions.Models;

public class SectionStatusDto
{
public string SectionId { get; set; } = null!;

public bool Completed { get; set; }

public string? LastMaturity { get; set; }

public DateTime DateCreated { get; set; }

public DateTime DateUpdated { get; set; }
}

public record SectionStatusNew
public record SectionStatus
{
public string SectionId { get; set; } = null!;

Expand Down
14 changes: 14 additions & 0 deletions src/Dfe.PlanTech.Domain/Submissions/Models/SectionStatusDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Dfe.PlanTech.Domain.Submissions.Models;

public class SectionStatusDto
{
public string SectionId { get; set; } = null!;

public bool Completed { get; set; }

public string? LastMaturity { get; set; }

public DateTime DateCreated { get; set; }

public DateTime DateUpdated { get; set; }
}
Loading

0 comments on commit ca045c9

Please sign in to comment.