Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New components: asset download, details, breadcrumbs #521

Merged
merged 25 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
013a435
adr
Jun 27, 2024
66b714a
PoC for contentful asset download controller
mattb-hippo Sep 4, 2024
09c5d43
added some explanatory code comments
mattb-hippo Sep 4, 2024
d118a83
remove the download controller - not required
mattb-hippo Sep 24, 2024
de3388f
feat: created asset download component
mattb-hippo Sep 26, 2024
e1c83ee
Merge remote-tracking branch 'origin/main' into mattb/SWCD-2429-asset…
mattb-hippo Sep 26, 2024
9941c8f
added schema components for asset download
mattb-hippo Sep 26, 2024
e50bc94
Added description to asset download content type
mattb-hippo Sep 26, 2024
0bed214
fixed security callouts
mattb-hippo Sep 26, 2024
c2e006a
adding session handling to app startup
mattb-hippo Sep 30, 2024
6cfb220
created extension helpers for setting and getting objects on user ses…
mattb-hippo Sep 30, 2024
ac5dc35
add placeholder for page's breadcrumb trail to context model
mattb-hippo Sep 30, 2024
f620347
capture page visits in user session, and stub for calculating page br…
mattb-hippo Sep 30, 2024
efcd179
removed punctuation to better match design
mattb-hippo Sep 30, 2024
395f832
make the context models breadcrump variable a stack not a list
mattb-hippo Sep 30, 2024
0dbff12
Merge pull request #517 from DFE-Digital/mattb/SWCD-2429-asset-downlo…
mattb-hippo Sep 30, 2024
ce3a3c1
Merge branch 'next' into mattb/SWCD-2451-breadcrumbs
mattb-hippo Sep 30, 2024
ff65498
feat: implemented page breadcrumbs
mattb-hippo Oct 11, 2024
ebd6310
add handling and test case for calulating breadcumbs where user has v…
mattb-hippo Oct 11, 2024
0c58312
contentful schema schanges for breadcrumbs
mattb-hippo Oct 14, 2024
e28af26
Removed console logging
mattb-hippo Oct 14, 2024
60d68fc
Removed comment
mattb-hippo Oct 14, 2024
6349e79
Merge pull request #520 from DFE-Digital/mattb/SWCD-2451-breadcrumbs
mattb-hippo Oct 14, 2024
5b57357
Merge branch 'next' into doc-update-001
nealhippo Oct 15, 2024
63f5084
Merge pull request #475 from DFE-Digital/doc-update-001
mattb-hippo Oct 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ public class EntityResolverTests
[Test]
[TestCase("accordion", typeof(Accordion))]
[TestCase("accordionSection", typeof(AccordionSection))]
[TestCase("areaOfPractice", typeof(AreaOfPractice))]
[TestCase("areaOfPracticeList", typeof(AreaOfPracticeList))]
[TestCase("applicationFeature", typeof(ApplicationFeature))]
[TestCase("applicationFeatures", typeof(ApplicationFeatures))]
[TestCase("areaOfPractice", typeof(AreaOfPractice))]
[TestCase("areaOfPracticeList", typeof(AreaOfPracticeList))]
[TestCase("assetDownload", typeof(AssetDownload))]
[TestCase("audioResource", typeof(AudioResource))]
[TestCase("backToTop", typeof(BackToTop))]
[TestCase("columnLayout", typeof(ColumnLayout))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public partial class PartialsFactoryTests
new object[] { new AccordionSection(), "_AccordionSection" },
new object[] { new AreaOfPractice(), "_AreaOfPractice" },
new object[] { new AreaOfPracticeList(), "_AreaOfPracticeList" },
new object[] { new AssetDownload(), "_AssetDownload" },
new object[] { new AudioResource(), "_AudioResource" },
new object[] { new BackToTop(), "_BackToTop" },
new object[] { new ColumnLayout(), "_ColumnLayout" },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
using Childrens_Social_Care_CPD;
using Childrens_Social_Care_CPD.Contentful;
using Childrens_Social_Care_CPD.Contentful.Models;
using Childrens_Social_Care_CPD.Controllers;
using Childrens_Social_Care_CPD.Models;
using Contentful.Core.Models;
using Contentful.Core.Search;
using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.Azure;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Childrens_Social_Care_CPD_Tests.Controllers;

public class ContentControllerBreadcrumbTests
{
private ContentController _contentController;
private IRequestCookieCollection _cookies;
private HttpContext _httpContext;
private HttpRequest _httpRequest;
private ICpdContentfulClient _contentfulClient;

private void SetContent(List<KeyValuePair<string, Content>> content)
{
var contentCollections = new List<KeyValuePair<string, ContentfulCollection<Content>>>();
foreach (var contentDefinition in content)
{
var contentCollection = new ContentfulCollection<Content>();

contentCollection.Items = contentDefinition.Value == null
? new List<Content>()
: contentCollection.Items = new List<Content> { contentDefinition.Value };

contentCollections.Add(new KeyValuePair<string, ContentfulCollection<Content>>(contentDefinition.Key, contentCollection));
}

_contentfulClient
.GetEntries(Arg.Any<QueryBuilder<Content>>(), Arg.Any<CancellationToken>())
.Returns(x => {
var query = x.Arg<QueryBuilder<Content>>().Build();
foreach (var contentDefinition in content)
{
if (query.Contains("fields.id=" + contentDefinition.Key)) return contentCollections.First(x => x.Key == contentDefinition.Key).Value;
}
return new ContentfulCollection<Content>();
});

}

private void SetContent() {
var page = new Content()
{
Id = "page",
Title = "Content Page"
};

var content = new List<KeyValuePair<string, Content>>()
{
new KeyValuePair<string, Content>(page.Id, page),
};

SetContent(content);
}

[SetUp]
public void SetUp()
{
_cookies = Substitute.For<IRequestCookieCollection>();
_httpContext = Substitute.For<HttpContext>();
_httpRequest = Substitute.For<HttpRequest>();
var controllerContext = Substitute.For<ControllerContext>();

_httpRequest.Cookies.Returns(_cookies);
_httpContext.Request.Returns(_httpRequest);

controllerContext.HttpContext = _httpContext;

_contentfulClient = Substitute.For<ICpdContentfulClient>();

_contentController = new ContentController(_contentfulClient)
{
ControllerContext = controllerContext,
TempData = Substitute.For<ITempDataDictionary>()
};
}

[TearDown]
public void TearDown()
{
_contentfulClient = Substitute.For<ICpdContentfulClient>();
}

[Test]
public async Task Index_Sets_Breadcrumbs_In_ContextModel()
{
// arrange
var parentPage = new Content()
{
Id = "parent",
Title = "Parent Page"
};
var childPage = new Content(){
Id = "child",
Title = "Child Page",
ParentPages = new List<Content>(){parentPage}
};

var content = new List<KeyValuePair<string, Content>>(){
new KeyValuePair<string, Content>(parentPage.Id, parentPage),
new KeyValuePair<string, Content>(childPage.Id, childPage)
};

SetContent(content);

// act
await _contentController.Index("child");
var actual = _contentController.ViewData["ContextModel"] as ContextModel;
var breadcrumbTrail = actual?.BreadcrumbTrail;

// assert
actual.Should().NotBeNull();
breadcrumbTrail[0].Key.Should().Be("Child Page");
breadcrumbTrail[0].Value.Should().Be("child");
breadcrumbTrail[1].Key.Should().Be("Parent Page");
breadcrumbTrail[1].Value.Should().Be("parent");
}

[Test]
public async Task Index_Sets_Blank_Breadcrumbs_In_ContextModel_If_Page_Has_No_Parent()
{
// arrange
SetContent();

// act
await _contentController.Index("page");
var actual = _contentController.ViewData["ContextModel"] as ContextModel;
var breadcrumbTrail = actual?.BreadcrumbTrail;

// assert
actual.Should().NotBeNull();
breadcrumbTrail.Should().BeEmpty();
}

[Test]
public async Task Index_Sets_Breadcrumbs_Where_Page_Has_Multiple_Parents()
{
// arrange
var parentPage1 = new Content()
{
Id = "parent1",
Title = "First Parent Page"
};
var parentPage2 = new Content()
{
Id = "parent2",
Title = "Second Parent Page"
};
var childPage = new Content(){
Id = "child",
Title = "Child Page",
ParentPages = new List<Content>(){parentPage1, parentPage2}
};

var content = new List<KeyValuePair<string, Content>>(){
new KeyValuePair<string, Content>(parentPage1.Id, parentPage1),
new KeyValuePair<string, Content>(parentPage2.Id, parentPage2),
new KeyValuePair<string, Content>(childPage.Id, childPage)
};

SetContent(content);

// act
await _contentController.Index("child");
var actual = _contentController.ViewData["ContextModel"] as ContextModel;
var breadcrumbTrail = actual?.BreadcrumbTrail;

// assert
actual.Should().NotBeNull();
breadcrumbTrail[0].Key.Should().Be("Child Page");
breadcrumbTrail[0].Value.Should().Be("child");
breadcrumbTrail[1].Key.Should().Be("First Parent Page");
breadcrumbTrail[1].Value.Should().Be("parent1");
}
}
5 changes: 3 additions & 2 deletions Childrens-Social-Care-CPD/Contentful/EntityResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ public Type Resolve(string contentTypeId)
{
"accordion" => typeof(Accordion),
"accordionSection" => typeof(AccordionSection),
"areaOfPractice" => typeof(AreaOfPractice),
"areaOfPracticeList" => typeof(AreaOfPracticeList),
"applicationFeature" => typeof(ApplicationFeature),
"applicationFeatures" => typeof(ApplicationFeatures),
"areaOfPractice" => typeof(AreaOfPractice),
"areaOfPracticeList" => typeof(AreaOfPracticeList),
"assetDownload" => typeof(AssetDownload),
"audioResource" => typeof(AudioResource),
"backToTop" => typeof(BackToTop),
"columnLayout" => typeof(ColumnLayout),
Expand Down
9 changes: 9 additions & 0 deletions Childrens-Social-Care-CPD/Contentful/Models/AssetDownload.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Contentful.Core.Models;

namespace Childrens_Social_Care_CPD.Contentful.Models;

public class AssetDownload : IContent
{
public string LinkText { get; set; }
public Asset Asset { get; set; }
}
2 changes: 2 additions & 0 deletions Childrens-Social-Care-CPD/Contentful/Models/Content.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public class Content : IContent
public NavigationMenu Navigation { get; set; }
public RelatedContent RelatedContent { get; set; }
public int? EstimatedReadingTime { get; set; }
public List<Content> ParentPages { get; set; }
public string BreadcrumbText { get; set; }

[JsonProperty("$metadata")]
public ContentfulMetadata Metadata { get; set; }
Expand Down
1 change: 1 addition & 0 deletions Childrens-Social-Care-CPD/Contentful/PartialsFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public static string GetPartialFor(IContent item)
AccordionSection => "_AccordionSection",
AreaOfPractice => "_AreaOfPractice",
AreaOfPracticeList => "_AreaOfPracticeList",
AssetDownload => "_AssetDownload",
AudioResource => "_AudioResource",
BackToTop => "_BackToTop",
ColumnLayout => "_ColumnLayout",
Expand Down
61 changes: 60 additions & 1 deletion Childrens-Social-Care-CPD/Controllers/ContentController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using Childrens_Social_Care_CPD.Models;
using Contentful.Core.Search;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;

namespace Childrens_Social_Care_CPD.Controllers;

Expand All @@ -20,6 +22,57 @@ private async Task<Content> FetchPageContentAsync(string contentId, Cancellation
return result?.FirstOrDefault();
}

private async Task<List<KeyValuePair<string, string>>> BuildBreadcrumbTrail(
List<KeyValuePair<string, string>> trail,
Content page,
List<string> pagesVisited,
CancellationToken ct)
{
var trailItem = new KeyValuePair<string, string>(
page.BreadcrumbText.IsNullOrEmpty() ?
page.Title :
page.BreadcrumbText,
page.Id);

if (page.ParentPages == null || page.ParentPages.Count == 0) {
if (trail.Count > 0) trail.Add(trailItem);
return trail;
}

trail.Add(trailItem);

Content parentPage = new Content();

if (page.ParentPages?.Count == 1) {
parentPage = page.ParentPages[0];
}
else
{
var parentPageIds = page.ParentPages
.Select(parent => parent.Id)
.ToList();

var checkPages = pagesVisited.Reverse<string>();
bool parentFound = false;

foreach (var pageId in checkPages)
{
if (parentPageIds.Contains(pageId))
{
parentPage = page.ParentPages.First(p => p.Id == pageId);
parentFound = true;
break;
}
};

// if we don't find a parent page in the recently vistied pages, just use the first in the list
if (!parentFound) parentPage = page.ParentPages[0];
}

var parentObject = await FetchPageContentAsync(parentPage.Id, ct);
return await BuildBreadcrumbTrail(trail, parentObject, pagesVisited, ct);
}

[HttpGet]
[Route("/")]
/*
Expand Down Expand Up @@ -49,6 +102,11 @@ public async Task<IActionResult> Index(string pageName = "home", bool preference
return NotFound();
}

var pagesVisited = HttpContext.Session.Get<List<string>>("pagesVisited");
if (pagesVisited == null) pagesVisited = new List<string>();
pagesVisited.Add(pageName);
HttpContext.Session.Set("pagesVisited", pagesVisited);

var contextModel = new ContextModel(
Id: content.Id,
Title: content.Title,
Expand All @@ -57,7 +115,8 @@ public async Task<IActionResult> Index(string pageName = "home", bool preference
UseContainers: content.Navigation == null,
PreferenceSet: preferenceSet,
BackLink: content.BackLink,
FeedbackSubmitted: fs);
FeedbackSubmitted: fs,
BreadcrumbTrail: await BuildBreadcrumbTrail(new List<KeyValuePair<string, string>>(), content, pagesVisited, cancellationToken));

ViewData["ContextModel"] = contextModel;
ViewData["StateModel"] = new StateModel();
Expand Down
19 changes: 19 additions & 0 deletions Childrens-Social-Care-CPD/Extensions/SessionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Newtonsoft.Json;

#nullable enable

namespace Childrens_Social_Care_CPD;

public static class SessionExtensions
{
public static void Set<T>(this ISession session, string key, T value)
{
session.SetString(key, JsonConvert.SerializeObject(value));
}

public static T? Get<T>(this ISession session, string key)
{
var value = session.GetString(key);
return value == null ? default : JsonConvert.DeserializeObject<T>(value);
}
}
12 changes: 11 additions & 1 deletion Childrens-Social-Care-CPD/Models/ContextModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@

namespace Childrens_Social_Care_CPD.Models;

public record ContextModel(string Id, string Title, string PageName, string Category, bool UseContainers, bool PreferenceSet, bool HideConsent = false, ContentLink BackLink = null, bool FeedbackSubmitted = false)
public record ContextModel(
string Id,
string Title,
string PageName,
string Category,
bool UseContainers,
bool PreferenceSet,
bool HideConsent = false,
ContentLink BackLink = null,
bool FeedbackSubmitted = false,
List<KeyValuePair<string, string>> BreadcrumbTrail = null)
{
public Stack<string> ContentStack { get; } = new Stack<string>();
}
Loading
Loading