From 8886e1597c04483a2e74be6652720a422569b53d Mon Sep 17 00:00:00 2001 From: ThomasWhittington <46750921+ThomasWhittington@users.noreply.github.com> Date: Wed, 2 Oct 2024 15:48:35 +0100 Subject: [PATCH] Feat: Vertical navigation changes and bug fixes (#164) * feat: changed header logo to go to self-assessment (#153) Co-authored-by: Tom Whittington * Fix/bleeding links (#154) * fix: feedback link fixed * Fix: Fixed bleeding links --------- Co-authored-by: Tom Whittington * fix: added missing span from feedback banner * chore: Simplify hiding js-only elements * feature: setup to use plantechs head and cookies when ran with it (#156) * feature: setup to use plantechs head and cookies when ran with it * fix: added a title to the gtm iframe * chore: using index reference rather than .First --------- Co-authored-by: Tom Whittington * docs: added basic readme file (#160) * docs: added basic readme file * docs: added testing instruction to README.md --------- Co-authored-by: Tom Whittington * Fix: fixed contentful fail on first attempt (#159) * Using plantech contentful connection * chore: cleaned up contentful reshuffle --------- Co-authored-by: Tom Whittington * feat: vertical navigation changes (#161) * feat: made urls use new slug value instead of internal name * chore: cleanedup lint issues * chore: linter issue * fix: updated tests for slug change * fix: updated tests for slug change * feat: now uses csPage heading when no page available or when new useParentHero option is selected in contentful * feat: added unsupported element when item type isn't recognised * Removed tests for removed endpoints * Update src/Dfe.ContentSupport.Web/Views/Shared/_VerticalNavigation.cshtml Co-authored-by: jimwashbrook <131891854+jimwashbrook@users.noreply.github.com> * chore: fixed formatting in test * chore: removed redundant case * changes method back to not static and removed Privacy.cshtml --------- Co-authored-by: Tom Whittington Co-authored-by: jimwashbrook <131891854+jimwashbrook@users.noreply.github.com> * remove unnused variables from cypress config * "Bits and Bats". Required changes for plantech integration (#158) (#165) * feat: changed header logo to go to self-assessment (#153) * Fix/bleeding links (#154) * fix: feedback link fixed * Fix: Fixed bleeding links --------- * fix: added missing span from feedback banner * feature: setup to use plantechs head and cookies when ran with it (#156) * feature: setup to use plantechs head and cookies when ran with it * fix: added a title to the gtm iframe * chore: using index reference rather than .First --------- --------- Co-authored-by: jimwashbrook <131891854+jimwashbrook@users.noreply.github.com> Co-authored-by: Tom Whittington --------- Co-authored-by: Tom Whittington Co-authored-by: katie-gardner-AND Co-authored-by: jimwashbrook <131891854+jimwashbrook@users.noreply.github.com> Co-authored-by: jack.coggin Co-authored-by: Katie Gardner <114991656+katie-gardner@users.noreply.github.com> Co-authored-by: jack-coggin <119428483+jack-coggin@users.noreply.github.com> --- README.md | 21 +++- .../Configuration/CsContentfulOptions.cs | 9 -- .../Controllers/ContentController.cs | 28 +---- .../Dfe.ContentSupport.Web.csproj | 3 +- .../Extensions/HttpClientPolicyExtensions.cs | 17 +++ .../WebApplicationBuilderExtensions.cs | 34 ++++-- .../Http/HttpContentfulClient.cs | 33 ------ .../Http/IHttpContentfulClient.cs | 10 -- .../Http/StubHttpContentfulClient.cs | 22 ---- .../Models/ContentBase.cs | 8 +- src/Dfe.ContentSupport.Web/Models/Entry.cs | 1 + src/Dfe.ContentSupport.Web/Models/Heading.cs | 6 +- .../Models/Mapped/CsContentItem.cs | 3 + .../Models/Mapped/Custom/CustomAccordion.cs | 1 - .../Models/Mapped/Custom/CustomAttachment.cs | 5 +- .../Models/Mapped/Custom/CustomCard.cs | 1 - .../Models/Mapped/Standard/EmbeddedAsset.cs | 1 - src/Dfe.ContentSupport.Web/Models/Target.cs | 4 +- src/Dfe.ContentSupport.Web/Program.cs | 4 - .../Services/ContentService.cs | 5 +- .../Services/ContentfulService.cs | 31 +++-- .../Services/IContentfulService.cs | 14 +-- .../Services/ILayoutService.cs | 12 +- .../Services/LayoutService.cs | 111 ++++++++---------- .../Services/ModelMapper.cs | 12 +- .../Services/StubContentfulService.cs | 22 ++++ .../ViewModels/ContentSupportPage.cs | 6 - .../Views/Content/CsIndex.cshtml | 47 +------- .../Views/Content/Privacy.cshtml | 6 - .../Views/Shared/RichText/_Asset.cshtml | 4 +- .../Views/Shared/RichText/_H.cshtml | 3 +- .../Views/Shared/_BodyEnd.cshtml | 7 ++ .../Views/Shared/_Feedback.cshtml | 2 +- .../Views/Shared/_Print.cshtml | 2 +- .../Views/Shared/_VerticalNavigation.cshtml | 16 +++ .../cypress.config.js | 2 +- .../Controllers/ContentControllerTests.cs | 23 ---- .../Dfe.ContentSupport.Web.Tests.csproj | 6 + .../WebApplicationBuilderExtensionsTests.cs | 13 +- .../Http/HttpContentfulClientTests.cs | 19 --- .../Http/StubHttpContentfulClientTests.cs | 17 --- .../Services/ContentServiceTests.cs | 34 +++--- .../Services/ContentfulServiceTests.cs | 23 ++-- .../Services/LayoutServiceTests.cs | 79 +++++++++++-- .../Services/StubContentfulServiceTests.cs | 15 +++ .../StubData/ContentfulCollection.json | 2 +- 46 files changed, 348 insertions(+), 396 deletions(-) delete mode 100644 src/Dfe.ContentSupport.Web/Configuration/CsContentfulOptions.cs create mode 100644 src/Dfe.ContentSupport.Web/Extensions/HttpClientPolicyExtensions.cs delete mode 100644 src/Dfe.ContentSupport.Web/Http/HttpContentfulClient.cs delete mode 100644 src/Dfe.ContentSupport.Web/Http/IHttpContentfulClient.cs delete mode 100644 src/Dfe.ContentSupport.Web/Http/StubHttpContentfulClient.cs create mode 100644 src/Dfe.ContentSupport.Web/Services/StubContentfulService.cs delete mode 100644 src/Dfe.ContentSupport.Web/Views/Content/Privacy.cshtml create mode 100644 src/Dfe.ContentSupport.Web/Views/Shared/_VerticalNavigation.cshtml delete mode 100644 tests/Dfe.ContentSupport.Web.Tests/Http/HttpContentfulClientTests.cs delete mode 100644 tests/Dfe.ContentSupport.Web.Tests/Http/StubHttpContentfulClientTests.cs create mode 100644 tests/Dfe.ContentSupport.Web.Tests/Services/StubContentfulServiceTests.cs rename {src/Dfe.ContentSupport.Web/Http => tests/Dfe.ContentSupport.Web.Tests}/StubData/ContentfulCollection.json (99%) diff --git a/README.md b/README.md index 6ba1bbd..3b57945 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,19 @@ -# sts-knowledgebase -Using Specify part of knowledgebase -DfE help schools to describe the digital technology they want to buy?. +# sts-contentsupport + +Web application to surface additional support for STS services + +## Requirements + +- .Net 8.0 and any supported IDE for DEV running. + + +## Running locally + +- The startup project is [./src/Dfe.ContentSupport.Web](./src/Dfe.ContentSupport.Web) +- Add 'dotNet-user-secret' to .NET secrets found in keyvault s190d01-cands-kv +- Add yourself with some permissions in the keyvault + - s190d01-cands-kv/access_policies +- The secrets should be pulled from the keyvault by using them settings. You may need to add your public IP to the firewall on the keyvault + - s190d01-cands-kv/networking +- Run the application using the http profile +- Go to URL/content/SLUG to test \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Configuration/CsContentfulOptions.cs b/src/Dfe.ContentSupport.Web/Configuration/CsContentfulOptions.cs deleted file mode 100644 index 6441cf0..0000000 --- a/src/Dfe.ContentSupport.Web/Configuration/CsContentfulOptions.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Contentful.Core.Configuration; - -namespace Dfe.ContentSupport.Web.Configuration; - -public class CsContentfulOptions : ContentfulOptions -{ - public int IncludeDepth { get; set; } = 10; - public int RetryAttempts { get; set; } = 3; -} \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Controllers/ContentController.cs b/src/Dfe.ContentSupport.Web/Controllers/ContentController.cs index 2f449d8..169511c 100644 --- a/src/Dfe.ContentSupport.Web/Controllers/ContentController.cs +++ b/src/Dfe.ContentSupport.Web/Controllers/ContentController.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using Dfe.ContentSupport.Web.Models.Mapped; using Dfe.ContentSupport.Web.Services; using Dfe.ContentSupport.Web.ViewModels; using Microsoft.AspNetCore.Authorization; @@ -13,25 +12,7 @@ public class ContentController(IContentService contentService, ILayoutService la : Controller { public const string ErrorActionName = "error"; - - public async Task Home() - { - var defaultModel = new CsPage - { - Heading = new Models.Heading - { - Title = "Department for Education", - Subtitle = "Content and Support" - } - }; - - ViewBag.pages = await contentService.GetCsPages(); - - return View(defaultModel); - } - - - + [HttpGet("{slug}/{page?}")] public async Task Index(string slug, string page = "", bool isPreview = false, [FromQuery] List? tags = null) { @@ -67,12 +48,7 @@ public async Task Index(string slug, string page = "", bool isPre return RedirectToAction(ErrorActionName); } } - - - public IActionResult Privacy() - { - return View(); - } + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() diff --git a/src/Dfe.ContentSupport.Web/Dfe.ContentSupport.Web.csproj b/src/Dfe.ContentSupport.Web/Dfe.ContentSupport.Web.csproj index c927a0e..34b1ed2 100644 --- a/src/Dfe.ContentSupport.Web/Dfe.ContentSupport.Web.csproj +++ b/src/Dfe.ContentSupport.Web/Dfe.ContentSupport.Web.csproj @@ -10,10 +10,11 @@ - + + diff --git a/src/Dfe.ContentSupport.Web/Extensions/HttpClientPolicyExtensions.cs b/src/Dfe.ContentSupport.Web/Extensions/HttpClientPolicyExtensions.cs new file mode 100644 index 0000000..e7bd609 --- /dev/null +++ b/src/Dfe.ContentSupport.Web/Extensions/HttpClientPolicyExtensions.cs @@ -0,0 +1,17 @@ +using Polly; +using Polly.Extensions.Http; + +namespace Dfe.ContentSupport.Web.Extensions; + +public static class HttpClientPolicyExtensions +{ + public static void AddRetryPolicy(IHttpClientBuilder builder) => + builder + .SetHandlerLifetime(TimeSpan.FromMinutes(5)) + .AddPolicyHandler(GetRetryPolicy()); + + public static IAsyncPolicy GetRetryPolicy() => + HttpPolicyExtensions.HandleTransientHttpError() + .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound) + .WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); +} \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Extensions/WebApplicationBuilderExtensions.cs b/src/Dfe.ContentSupport.Web/Extensions/WebApplicationBuilderExtensions.cs index 71ea637..085bd9e 100644 --- a/src/Dfe.ContentSupport.Web/Extensions/WebApplicationBuilderExtensions.cs +++ b/src/Dfe.ContentSupport.Web/Extensions/WebApplicationBuilderExtensions.cs @@ -1,5 +1,6 @@ -using Dfe.ContentSupport.Web.Configuration; -using Dfe.ContentSupport.Web.Http; +using Contentful.Core; +using Contentful.Core.Configuration; +using Dfe.ContentSupport.Web.Configuration; using Dfe.ContentSupport.Web.Models.Mapped; using Dfe.ContentSupport.Web.Services; using Microsoft.Extensions.Options; @@ -10,19 +11,16 @@ public static class WebApplicationBuilderExtensions { public static void InitCsDependencyInjection(this WebApplicationBuilder app) { - app.Services.Configure(app.Configuration.GetSection("cs:contentful")) - .AddSingleton(sp => sp.GetRequiredService>().Value); - app.Services.Configure(app.Configuration.GetSection("tracking")) .AddSingleton(sp => sp.GetRequiredService>().Value); app.Services.Configure(app.Configuration.GetSection("cs:supportedAssetTypes")) .AddSingleton(sp => sp.GetRequiredService>().Value); - - app.Services - .AddTransient>, CsPagesCacheService>(); + + app.Services.SetupContentfulClient(app); + + app.Services.AddTransient>, CsPagesCacheService>(); app.Services.AddTransient(); - app.Services.AddTransient(); app.Services.AddTransient(); app.Services.AddTransient(); @@ -33,13 +31,27 @@ public static void InitCsDependencyInjection(this WebApplicationBuilder app) options.ConsentCookieValue = "false"; }); + + } + + public static void SetupContentfulClient(this IServiceCollection services, WebApplicationBuilder app) + { + app.Services.Configure(app.Configuration.GetSection("cs:contentful")) + .AddSingleton(sp => sp.GetRequiredService>().Value); + + services.AddScoped(); + if (app.Environment.EnvironmentName.Equals("e2e")) { - app.Services.AddTransient(); + services.AddScoped(); } else { - app.Services.AddTransient(); + services.AddScoped(); } + + + + HttpClientPolicyExtensions.AddRetryPolicy(services.AddHttpClient()); } } \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Http/HttpContentfulClient.cs b/src/Dfe.ContentSupport.Web/Http/HttpContentfulClient.cs deleted file mode 100644 index 32c446a..0000000 --- a/src/Dfe.ContentSupport.Web/Http/HttpContentfulClient.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Contentful.Core; -using Dfe.ContentSupport.Web.Configuration; -using Contentful.Core.Models; -using Contentful.Core.Search; - -namespace Dfe.ContentSupport.Web.Http; - -public class HttpContentfulClient(HttpClient httpClient, CsContentfulOptions options) - : ContentfulClient(httpClient, options), IHttpContentfulClient -{ - public async Task> Query(QueryBuilder queryBuilder, - CancellationToken cancellationToken = default) where T : class - { - queryBuilder = queryBuilder.Include(options.IncludeDepth); - - for (int attempt = 1; attempt <= options.RetryAttempts; attempt++) - { - try - { - return await GetEntries(queryBuilder, cancellationToken); - } - catch (Exception) - { - if (attempt == options.RetryAttempts) - { - throw; - } - } - } - - return default!; - } -} \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Http/IHttpContentfulClient.cs b/src/Dfe.ContentSupport.Web/Http/IHttpContentfulClient.cs deleted file mode 100644 index 60ad964..0000000 --- a/src/Dfe.ContentSupport.Web/Http/IHttpContentfulClient.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Contentful.Core.Models; -using Contentful.Core.Search; - -namespace Dfe.ContentSupport.Web.Http; - -public interface IHttpContentfulClient -{ - Task> Query(QueryBuilder queryBuilder, - CancellationToken cancellationToken = default) where T : class; -} \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Http/StubHttpContentfulClient.cs b/src/Dfe.ContentSupport.Web/Http/StubHttpContentfulClient.cs deleted file mode 100644 index aaf00ab..0000000 --- a/src/Dfe.ContentSupport.Web/Http/StubHttpContentfulClient.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Contentful.Core; -using Dfe.ContentSupport.Web.Configuration; -using Contentful.Core.Models; -using Contentful.Core.Search; -using Newtonsoft.Json; - -namespace Dfe.ContentSupport.Web.Http; - -public class StubHttpContentfulClient(HttpClient httpClient, CsContentfulOptions options) - : ContentfulClient(httpClient, options), IHttpContentfulClient -{ - public async Task> Query(QueryBuilder queryBuilder, - CancellationToken cancellationToken = default) where T : class - { - var json = await System.IO.File.ReadAllTextAsync("Http/StubData/ContentfulCollection.json", - cancellationToken); - var resp = JsonConvert.DeserializeObject(json); - return resp == null - ? new ContentfulCollection() - : new ContentfulCollection { Items = new[] { resp } }; - } -} \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Models/ContentBase.cs b/src/Dfe.ContentSupport.Web/Models/ContentBase.cs index 86ad154..02426ef 100644 --- a/src/Dfe.ContentSupport.Web/Models/ContentBase.cs +++ b/src/Dfe.ContentSupport.Web/Models/ContentBase.cs @@ -1,12 +1,12 @@ using System.Diagnostics.CodeAnalysis; + namespace Dfe.ContentSupport.Web.Models; [ExcludeFromCodeCoverage] public class ContentBase : Contentful.Core.Models.Entry { public string InternalName { get; set; } = null!; - - public string? Title { get; set; } = null; - - public string? Subtitle { get; set; } = null; + public string Slug { get; set; } = null!; + public string? Title { get; set; } + public string? Subtitle { get; set; } } \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Models/Entry.cs b/src/Dfe.ContentSupport.Web/Models/Entry.cs index 2eb2d01..b7a8f8c 100644 --- a/src/Dfe.ContentSupport.Web/Models/Entry.cs +++ b/src/Dfe.ContentSupport.Web/Models/Entry.cs @@ -7,4 +7,5 @@ public class Entry : ContentBase { public string JumpIdentifier { get; set; } = null!; public ContentItemBase RichText { get; set; } = null!; + public bool UseParentHero { get; set; } } \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Models/Heading.cs b/src/Dfe.ContentSupport.Web/Models/Heading.cs index 7e7555c..de5f0b6 100644 --- a/src/Dfe.ContentSupport.Web/Models/Heading.cs +++ b/src/Dfe.ContentSupport.Web/Models/Heading.cs @@ -3,8 +3,4 @@ namespace Dfe.ContentSupport.Web.Models; [ExcludeFromCodeCoverage] -public class Heading : ContentBase -{ - public string Title { get; init; } = null!; - public string Subtitle { get; init; } = null!; -} \ No newline at end of file +public class Heading : ContentBase; \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Models/Mapped/CsContentItem.cs b/src/Dfe.ContentSupport.Web/Models/Mapped/CsContentItem.cs index 7255ede..54e520e 100644 --- a/src/Dfe.ContentSupport.Web/Models/Mapped/CsContentItem.cs +++ b/src/Dfe.ContentSupport.Web/Models/Mapped/CsContentItem.cs @@ -6,6 +6,9 @@ namespace Dfe.ContentSupport.Web.Models.Mapped; public class CsContentItem { public string InternalName { get; set; } = null!; + public string Slug { get; set; } = null!; public string? Title { get; set; } = null; public string? Subtitle { get; set; } = null; + public bool UseParentHero { get; set; } + } \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Models/Mapped/Custom/CustomAccordion.cs b/src/Dfe.ContentSupport.Web/Models/Mapped/Custom/CustomAccordion.cs index 3846d31..ed61326 100644 --- a/src/Dfe.ContentSupport.Web/Models/Mapped/Custom/CustomAccordion.cs +++ b/src/Dfe.ContentSupport.Web/Models/Mapped/Custom/CustomAccordion.cs @@ -14,5 +14,4 @@ public CustomAccordion() public List Accordions { get; set; } = null!; public RichTextContentItem? Body { get; set; } public string SummaryLine { get; set; } = null!; - public string Title { get; set; } = null!; } \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Models/Mapped/Custom/CustomAttachment.cs b/src/Dfe.ContentSupport.Web/Models/Mapped/Custom/CustomAttachment.cs index c444284..9c96273 100644 --- a/src/Dfe.ContentSupport.Web/Models/Mapped/Custom/CustomAttachment.cs +++ b/src/Dfe.ContentSupport.Web/Models/Mapped/Custom/CustomAttachment.cs @@ -1,4 +1,4 @@ -using Dfe.ContentSupport.Web.Models.Mapped.Types; +using Dfe.ContentSupport.Web.Models.Mapped.Types; using System.Diagnostics.CodeAnalysis; namespace Dfe.ContentSupport.Web.Models.Mapped.Custom; @@ -13,7 +13,6 @@ public CustomAttachment() public string ContentType { get; set; } = null!; public long Size { get; set; } - public string Title { get; set; } = null!; public string Uri { get; set; } = null!; - public DateTime? UpdatedAt { get; set; } = null!; + public DateTime? UpdatedAt { get; set; } } \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Models/Mapped/Custom/CustomCard.cs b/src/Dfe.ContentSupport.Web/Models/Mapped/Custom/CustomCard.cs index efa1fd3..501c3ce 100644 --- a/src/Dfe.ContentSupport.Web/Models/Mapped/Custom/CustomCard.cs +++ b/src/Dfe.ContentSupport.Web/Models/Mapped/Custom/CustomCard.cs @@ -15,6 +15,5 @@ public CustomCard() public string ImageAlt { get; set; } = null!; public string ImageUri { get; set; } = null!; public string Meta { get; set; } = null!; - public string Title { get; set; } = null!; public string Uri { get; set; } = null!; } \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Models/Mapped/Standard/EmbeddedAsset.cs b/src/Dfe.ContentSupport.Web/Models/Mapped/Standard/EmbeddedAsset.cs index 606a047..68723a0 100644 --- a/src/Dfe.ContentSupport.Web/Models/Mapped/Standard/EmbeddedAsset.cs +++ b/src/Dfe.ContentSupport.Web/Models/Mapped/Standard/EmbeddedAsset.cs @@ -14,6 +14,5 @@ public EmbeddedAsset() public AssetContentType AssetContentType { get; set; } = AssetContentType.Unknown; public string Description { get; set; } = null!; - public string Title { get; set; } = null!; public string Uri { get; set; } = null!; } \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Models/Target.cs b/src/Dfe.ContentSupport.Web/Models/Target.cs index adc0772..2260c76 100644 --- a/src/Dfe.ContentSupport.Web/Models/Target.cs +++ b/src/Dfe.ContentSupport.Web/Models/Target.cs @@ -6,8 +6,7 @@ namespace Dfe.ContentSupport.Web.Models; [ExcludeFromCodeCoverage] public class Target : Entry { - public Fields Fields { get; set; } = null!; - public string Title { get; set; } = null!; + public new Fields Fields { get; set; } = null!; public Asset Asset { get; set; } = null!; public string SummaryLine { get; set; } = null!; public string Description { get; set; } = null!; @@ -16,4 +15,5 @@ public class Target : Entry public string Uri { get; set; } = null!; public Image Image { get; set; } = null!; public List Content { get; set; } = []; + public new string Title { get; set; } = null!; } \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Program.cs b/src/Dfe.ContentSupport.Web/Program.cs index b1b5f4c..0e70ecf 100644 --- a/src/Dfe.ContentSupport.Web/Program.cs +++ b/src/Dfe.ContentSupport.Web/Program.cs @@ -1,10 +1,8 @@ using System.Diagnostics.CodeAnalysis; using Azure.Identity; -using Contentful.AspNetCore; using Dfe.ContentSupport.Web.Extensions; using GovUk.Frontend.AspNetCore; - namespace Dfe.ContentSupport.Web; [ExcludeFromCodeCoverage] @@ -24,7 +22,6 @@ public static void Main(string[] args) builder.Services.AddHealthChecks(); builder.Services.AddGovUkFrontend(); - builder.Services.AddContentful(builder.Configuration); builder.InitCsDependencyInjection(); var app = builder.Build(); @@ -34,7 +31,6 @@ public static void Main(string[] args) app.UseHsts(); } - app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); diff --git a/src/Dfe.ContentSupport.Web/Services/ContentService.cs b/src/Dfe.ContentSupport.Web/Services/ContentService.cs index 9b3bc57..d049c38 100644 --- a/src/Dfe.ContentSupport.Web/Services/ContentService.cs +++ b/src/Dfe.ContentSupport.Web/Services/ContentService.cs @@ -1,5 +1,4 @@ using System.Xml.Linq; -using Contentful.Core.Search; using Dfe.ContentSupport.Web.Models.Mapped; using Dfe.ContentSupport.Web.ViewModels; @@ -59,9 +58,7 @@ public async Task> GetContentSupportPages( } - var builder = QueryBuilder.New.ContentTypeIs(nameof(ContentSupportPage)) - .FieldEquals($"fields.{field}", value); - var result = await contentfulService.ContentfulClient(isPreview).Query(builder); + var result = await contentfulService.GetContentSupportPages(field, value); var pages = modelMapper.MapToCsPages(result); if (!isPreview) diff --git a/src/Dfe.ContentSupport.Web/Services/ContentfulService.cs b/src/Dfe.ContentSupport.Web/Services/ContentfulService.cs index dce404e..c446905 100644 --- a/src/Dfe.ContentSupport.Web/Services/ContentfulService.cs +++ b/src/Dfe.ContentSupport.Web/Services/ContentfulService.cs @@ -1,15 +1,26 @@ -using Dfe.ContentSupport.Web.Configuration; -using Dfe.ContentSupport.Web.Http; +using Contentful.Core; +using Contentful.Core.Search; +using Dfe.ContentSupport.Web.ViewModels; -namespace Dfe.ContentSupport.Web.Services +namespace Dfe.ContentSupport.Web.Services; + +public class ContentfulService(IContentfulClient client) + : IContentfulService { - public class ContentfulService(CsContentfulOptions contentfulOptions, IHttpContentfulClient httpContentfulClient) : IContentfulService + private const int DefaultRequestDepth = 10; + + private readonly IContentfulClient _client = + client ?? throw new ArgumentNullException(nameof(client)); + + public async Task> GetContentSupportPages(string field, + string value, CancellationToken cancellationToken = default) { + var builder = QueryBuilder.New.ContentTypeIs(nameof(ContentSupportPage)) + .FieldEquals($"fields.{field}", value) + .Include(DefaultRequestDepth); + + var entries = await _client.GetEntries(builder, cancellationToken); - public IHttpContentfulClient ContentfulClient(bool isPreview = false) - { - contentfulOptions.UsePreviewApi = isPreview; - return httpContentfulClient; - } + return entries ?? Enumerable.Empty(); } -} +} \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Services/IContentfulService.cs b/src/Dfe.ContentSupport.Web/Services/IContentfulService.cs index 63ab5bf..b86813f 100644 --- a/src/Dfe.ContentSupport.Web/Services/IContentfulService.cs +++ b/src/Dfe.ContentSupport.Web/Services/IContentfulService.cs @@ -1,9 +1,9 @@ -using Dfe.ContentSupport.Web.Http; +using Dfe.ContentSupport.Web.ViewModels; -namespace Dfe.ContentSupport.Web.Services +namespace Dfe.ContentSupport.Web.Services; + +public interface IContentfulService { - public interface IContentfulService - { - IHttpContentfulClient ContentfulClient(bool isPreview = false); - } -} + Task> GetContentSupportPages(string field, string value, + CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Services/ILayoutService.cs b/src/Dfe.ContentSupport.Web/Services/ILayoutService.cs index d028752..52f166b 100644 --- a/src/Dfe.ContentSupport.Web/Services/ILayoutService.cs +++ b/src/Dfe.ContentSupport.Web/Services/ILayoutService.cs @@ -1,10 +1,8 @@ using Dfe.ContentSupport.Web.Models.Mapped; -namespace Dfe.ContentSupport.Web.Services -{ - public interface ILayoutService - { - CsPage GenerateLayout(CsPage page, HttpRequest request, string pageName); +namespace Dfe.ContentSupport.Web.Services; - } -} +public interface ILayoutService +{ + CsPage GenerateLayout(CsPage page, HttpRequest request, string pageSlug); +} \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Services/LayoutService.cs b/src/Dfe.ContentSupport.Web/Services/LayoutService.cs index 1f559c0..c25c275 100644 --- a/src/Dfe.ContentSupport.Web/Services/LayoutService.cs +++ b/src/Dfe.ContentSupport.Web/Services/LayoutService.cs @@ -1,85 +1,78 @@ using Dfe.ContentSupport.Web.Models; using Dfe.ContentSupport.Web.Models.Mapped; +namespace Dfe.ContentSupport.Web.Services; -namespace Dfe.ContentSupport.Web.Services +public class LayoutService : ILayoutService { - public class LayoutService : ILayoutService + public CsPage GenerateLayout(CsPage page, HttpRequest request, string pageSlug) { - public CsPage GenerateLayout(CsPage page, HttpRequest request, string pageName) - { - if (!page.ShowVerticalNavigation) return page; - - return new() - { - Heading = GetHeading(page, pageName), - MenuItems = GenerateVerticalNavigation(page, request, pageName), - Content = GetVisiblePageList(page, pageName), - UpdatedAt = page.UpdatedAt, - CreatedAt = page.CreatedAt, - HasCitation = page.HasCitation, - HasBackToTop = page.HasBackToTop, - IsSitemap = page.IsSitemap, - ShowVerticalNavigation = page.ShowVerticalNavigation, - Slug = page.Slug, - }; - } - + if (!page.ShowVerticalNavigation) return page; - public Heading GetHeading(CsPage page, string pageName) + return new CsPage { - var selectedPage = page.Content.Find(o => o.InternalName == pageName); + Heading = GetHeading(page, pageSlug), + MenuItems = GenerateVerticalNavigation(page, request, pageSlug), + Content = GetVisiblePageList(page, pageSlug), + UpdatedAt = page.UpdatedAt, + CreatedAt = page.CreatedAt, + HasCitation = page.HasCitation, + HasBackToTop = page.HasBackToTop, + IsSitemap = page.IsSitemap, + ShowVerticalNavigation = page.ShowVerticalNavigation, + Slug = page.Slug + }; + } - if (selectedPage != null) - return new() - { - Title = selectedPage.Title ?? "", - Subtitle = selectedPage.Subtitle ?? "" - }; + public Heading GetHeading(CsPage page, string pageSlug) + { + var selectedPage = page.Content.Find(o => o.Slug == pageSlug); - return new() + if (selectedPage is { UseParentHero: false }) + return new Heading { - Title = page.Content[0]?.Title ?? "", - Subtitle = page.Content[0]?.Subtitle ?? "" + Title = selectedPage.Title ?? string.Empty, + Subtitle = selectedPage.Subtitle ?? string.Empty }; - } + return page.Heading; + } - public List GenerateVerticalNavigation(CsPage page, HttpRequest request, string pageName) - { - var baseUrl = GetNavigationUrl(request); - var menuItems = page.Content.Select(o => new PageLink() - { - Title = o.Title ?? "", - Subtitle = o.Subtitle ?? "", - Url = $"{baseUrl}/{o.InternalName}", - IsActive = pageName == o.InternalName - }).ToList(); + public List GenerateVerticalNavigation(CsPage page, HttpRequest request, + string pageSlug) + { + var baseUrl = GetNavigationUrl(request); - if (string.IsNullOrEmpty(pageName) && menuItems.Count > 0) - menuItems[0].IsActive = true; + var menuItems = page.Content.Select(o => new PageLink + { + Title = o.Title ?? "", + Subtitle = o.Subtitle ?? "", + Url = $"{baseUrl}/{o.Slug}", + IsActive = pageSlug == o.Slug + }).ToList(); - return menuItems; - } + if (string.IsNullOrEmpty(pageSlug) && menuItems.Count > 0) + menuItems[0].IsActive = true; + return menuItems; + } - public List GetVisiblePageList(CsPage page, string pageName) - { - if (!string.IsNullOrEmpty(pageName)) - return page.Content.Where(o => o.InternalName == pageName).ToList(); + public List GetVisiblePageList(CsPage page, string pageSlug) + { + if (!string.IsNullOrEmpty(pageSlug)) + return page.Content.Where(o => o.Slug == pageSlug).ToList(); - return page.Content.GetRange(0, 1); - } + return page.Content.GetRange(0, 1); + } - public string GetNavigationUrl(HttpRequest request) - { - var splitUrl = request.Path.ToString().Split("/"); - return string.Join("/", splitUrl.Take(3)); - } + public string GetNavigationUrl(HttpRequest request) + { + var splitUrl = request.Path.ToString().Split("/"); + return string.Join("/", splitUrl.Take(3)); } -} +} \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Services/ModelMapper.cs b/src/Dfe.ContentSupport.Web/Services/ModelMapper.cs index b36ae15..3c7043f 100644 --- a/src/Dfe.ContentSupport.Web/Services/ModelMapper.cs +++ b/src/Dfe.ContentSupport.Web/Services/ModelMapper.cs @@ -53,7 +53,11 @@ public CsContentItem ConvertEntryToContentItem(Entry entry) { CsContentItem item = entry.RichText is not null ? MapRichTextContent(entry.RichText, entry)! - : new CsContentItem { InternalName = entry.InternalName, Title = entry.Title, Subtitle = entry.Subtitle }; + : new CsContentItem + { + InternalName = entry.InternalName, Slug = entry.Slug, Title = entry.Title, + Subtitle = entry.Subtitle, UseParentHero = entry.UseParentHero + }; return item; } @@ -64,8 +68,10 @@ public CsContentItem ConvertEntryToContentItem(Entry entry) new RichTextContentItem { InternalName = entry.InternalName, + Slug = entry.Slug, Title = entry.Title, Subtitle = entry.Subtitle, + UseParentHero = entry.UseParentHero, NodeType = ConvertToRichTextNodeType(richText.NodeType), Content = MapRichTextNodes(richText.Content), Tags = FlattenMetadata(entry.Metadata) @@ -76,7 +82,7 @@ public CsContentItem ConvertEntryToContentItem(Entry entry) public List MapRichTextNodes(List nodes) { return nodes.Select(node => MapContent(node) ?? new RichTextContentItem - { NodeType = RichTextNodeType.Unknown, InternalName = node.InternalName }).ToList(); + { NodeType = RichTextNodeType.Unknown, InternalName = node.InternalName }).ToList(); } @@ -85,7 +91,7 @@ public List MapRichTextNodes(List nodes) RichTextContentItem? item; var nodeType = ConvertToRichTextNodeType(contentItem.NodeType); var internalName = contentItem.InternalName; - + switch (nodeType) { diff --git a/src/Dfe.ContentSupport.Web/Services/StubContentfulService.cs b/src/Dfe.ContentSupport.Web/Services/StubContentfulService.cs new file mode 100644 index 0000000..30578ac --- /dev/null +++ b/src/Dfe.ContentSupport.Web/Services/StubContentfulService.cs @@ -0,0 +1,22 @@ +using Contentful.Core; +using Contentful.Core.Configuration; +using Contentful.Core.Models; +using Dfe.ContentSupport.Web.ViewModels; +using Newtonsoft.Json; + +namespace Dfe.ContentSupport.Web.Services; + +public class StubContentfulService(HttpClient httpClient, ContentfulOptions options) + : ContentfulClient(httpClient, options), IContentfulService +{ + public async Task> GetContentSupportPages(string field, + string value, CancellationToken cancellationToken = default) + { + var json = await System.IO.File.ReadAllTextAsync("StubData/ContentfulCollection.json", + cancellationToken); + var resp = JsonConvert.DeserializeObject(json); + return resp == null + ? new ContentfulCollection() + : new ContentfulCollection { Items = [resp] }; + } +} \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/ViewModels/ContentSupportPage.cs b/src/Dfe.ContentSupport.Web/ViewModels/ContentSupportPage.cs index 7668062..bffadc4 100644 --- a/src/Dfe.ContentSupport.Web/ViewModels/ContentSupportPage.cs +++ b/src/Dfe.ContentSupport.Web/ViewModels/ContentSupportPage.cs @@ -6,14 +6,8 @@ namespace Dfe.ContentSupport.Web.ViewModels; [ExcludeFromCodeCoverage] public class ContentSupportPage : ContentBase { - public string Slug { get; init; } = null!; - - public List BeforeTitleContent { get; init; } = []; - public Heading Heading { get; init; } = null!; public List Content { get; init; } = []; - - public bool DisplayBackButton { get; init; } public bool IsSitemap { get; init; } public bool HasCitation { get; init; } public bool HasBackToTop { get; init; } diff --git a/src/Dfe.ContentSupport.Web/Views/Content/CsIndex.cshtml b/src/Dfe.ContentSupport.Web/Views/Content/CsIndex.cshtml index 3087d3a..ef09188 100644 --- a/src/Dfe.ContentSupport.Web/Views/Content/CsIndex.cshtml +++ b/src/Dfe.ContentSupport.Web/Views/Content/CsIndex.cshtml @@ -8,20 +8,7 @@ @if (Model.MenuItems is not null) { -
- -
+ }
@@ -30,7 +17,7 @@ { } - + @if (Model.HasCitation) { @@ -45,10 +32,10 @@ { } - + @if (Model.HasFeedbackBanner) { - + }
@@ -76,28 +63,4 @@ addPrintButtonEventListener(); } - - -} \ No newline at end of file +} diff --git a/src/Dfe.ContentSupport.Web/Views/Content/Privacy.cshtml b/src/Dfe.ContentSupport.Web/Views/Content/Privacy.cshtml deleted file mode 100644 index 88d5bc4..0000000 --- a/src/Dfe.ContentSupport.Web/Views/Content/Privacy.cshtml +++ /dev/null @@ -1,6 +0,0 @@ -@{ - ViewData["Title"] = "Privacy Policy"; -} -

@ViewData["Title"]

- -

Use this page to detail your site's privacy policy.

\ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Views/Shared/RichText/_Asset.cshtml b/src/Dfe.ContentSupport.Web/Views/Shared/RichText/_Asset.cshtml index 847a098..c161f5b 100644 --- a/src/Dfe.ContentSupport.Web/Views/Shared/RichText/_Asset.cshtml +++ b/src/Dfe.ContentSupport.Web/Views/Shared/RichText/_Asset.cshtml @@ -12,8 +12,8 @@ Your browser does not support the video tag. break; - case AssetContentType.Unknown: default: - throw new ArgumentOutOfRangeException(nameof(Model.AssetContentType)); + + break; } \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Views/Shared/RichText/_H.cshtml b/src/Dfe.ContentSupport.Web/Views/Shared/RichText/_H.cshtml index 830e6bf..f59c2e9 100644 --- a/src/Dfe.ContentSupport.Web/Views/Shared/RichText/_H.cshtml +++ b/src/Dfe.ContentSupport.Web/Views/Shared/RichText/_H.cshtml @@ -29,6 +29,7 @@ break; default: - throw new ArgumentOutOfRangeException(nameof(Model.NodeType)); + + break; } } \ No newline at end of file diff --git a/src/Dfe.ContentSupport.Web/Views/Shared/_BodyEnd.cshtml b/src/Dfe.ContentSupport.Web/Views/Shared/_BodyEnd.cshtml index 49f78dd..2e5b4c6 100644 --- a/src/Dfe.ContentSupport.Web/Views/Shared/_BodyEnd.cshtml +++ b/src/Dfe.ContentSupport.Web/Views/Shared/_BodyEnd.cshtml @@ -1,4 +1,11 @@ diff --git a/src/Dfe.ContentSupport.Web/Views/Shared/_Feedback.cshtml b/src/Dfe.ContentSupport.Web/Views/Shared/_Feedback.cshtml index c6fbf40..b7fc3d0 100644 --- a/src/Dfe.ContentSupport.Web/Views/Shared/_Feedback.cshtml +++ b/src/Dfe.ContentSupport.Web/Views/Shared/_Feedback.cshtml @@ -17,7 +17,7 @@ @if (track) { -