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

fix(197631): 🐛 Fix content ordering on page #541

Merged
merged 12 commits into from
Feb 29, 2024
39 changes: 35 additions & 4 deletions src/Dfe.PlanTech.Application/Content/Queries/GetPageFromDbQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,55 @@ public GetPageFromDbQuery(ICmsDbContext db, ILogger<GetPageFromDbQuery> logger,

private async Task<PageDbEntity?> RetrievePageFromDatabase(string slug, CancellationToken cancellationToken)
{
var page = await _db.GetPageBySlug(slug, cancellationToken);
var page = await GetPageFromDb(slug, cancellationToken);

if (!IsValidPage(page, slug))
{
_logger.LogError("Retrieved page {slug} is invalid", slug);
return null;
}

await LoadPageChildrenFromDatabase(page, cancellationToken);

_logger.LogTrace("Successfully retrieved {page} from DB", slug);

return page;
}

private async Task<PageDbEntity?> GetPageFromDb(string slug, CancellationToken cancellationToken)
{
try
{
var page = await _db.GetPageBySlug(slug, cancellationToken);

if (page == null)
{
return null;
}

page.OrderContents();

return page;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error fetching {page} from database", slug);
throw;
}
}

private async Task LoadPageChildrenFromDatabase(PageDbEntity? page, CancellationToken cancellationToken)
{
foreach (var query in _getPageChildrenQueries)
try
{
foreach (var query in _getPageChildrenQueries)
{
await query.TryLoadChildren(page!, cancellationToken);
}
}
catch (Exception ex)
{
await query.TryLoadChildren(page!, cancellationToken);
_logger.LogError(ex, "Error loading children from database for {page}", page!.Id);
throw;
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/Dfe.PlanTech.Application/Content/Queries/GetPageQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ public GetPageQuery(GetPageFromContentfulQuery getPageFromContentfulQuery, GetPa
/// <returns>Page matching slug</returns>
public async Task<Page?> GetPageBySlug(string slug, CancellationToken cancellationToken = default)
{
var page = await _getPageFromDbQuery.GetPageBySlug(slug, cancellationToken) ?? await _getPageFromContentfulQuery.GetPageBySlug(slug, cancellationToken);
var page = await _getPageFromDbQuery.GetPageBySlug(slug, cancellationToken) ??
await _getPageFromContentfulQuery.GetPageBySlug(slug, cancellationToken);

return page;
}
Expand Down
22 changes: 19 additions & 3 deletions src/Dfe.PlanTech.AzureFunctions/Mappings/PageMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ public PageMapper(CmsDbContext db, ILogger<PageMapper> logger, JsonSerializerOpt
_db = db;
}

/// <summary>
/// Create joins for content, and before title content, and map the title ID to the correct expected name.
/// </summary>
/// <param name="values"></param>
/// <returns></returns>
/// <exception cref="KeyNotFoundException"></exception>
public override Dictionary<string, object?> PerformAdditionalMapping(Dictionary<string, object?> values)
{
var id = values["id"]?.ToString() ?? throw new KeyNotFoundException("Not found id");
Expand All @@ -30,18 +36,27 @@ public PageMapper(CmsDbContext db, ILogger<PageMapper> logger, JsonSerializerOpt

private void UpdateContentIds(Dictionary<string, object?> values, string pageId, string currentKey)
{
bool isBeforeTitleContent = currentKey == BeforeTitleContentKey;

if (values.TryGetValue(currentKey, out object? contents) && contents is object[] inners)
{
foreach (var inner in inners)
for (var index = 0; index < inners.Length; index++)
{
CreatePageContentEntity(inner, pageId, currentKey == BeforeTitleContentKey);
CreatePageContentEntity(inners[index], index, pageId, isBeforeTitleContent);
}

values.Remove(currentKey);
}
}

private void CreatePageContentEntity(object inner, string pageId, bool isBeforeTitleContent)
/// <summary>
/// Creates the necessary <see cref="PageContentDbEntity"/> for the relationship
/// </summary>
/// <param name="inner">The child content ID.</param>
/// <param name="order">Order of the content for the page</param>
/// <param name="pageId"></param>
/// <param name="isBeforeTitleContent"></param>
private void CreatePageContentEntity(object inner, int order, string pageId, bool isBeforeTitleContent)
{
if (inner is not string contentId)
{
Expand All @@ -52,6 +67,7 @@ private void CreatePageContentEntity(object inner, string pageId, bool isBeforeT
var pageContent = new PageContentDbEntity()
{
PageId = pageId,
Order = order
};

if (isBeforeTitleContent)
Expand Down
11 changes: 11 additions & 0 deletions src/Dfe.PlanTech.Domain/Content/Models/ContentComponentDbEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,16 @@ public abstract class ContentComponentDbEntity : IContentComponentDbEntity

public List<PageDbEntity> BeforeTitleContentPages { get; set; } = [];

/// <summary>
/// Joins for <see cref="BeforeTitleContentPages"/>
/// </summary>
public List<PageContentDbEntity> BeforeTitleContentPagesJoins { get; set; } = [];

public List<PageDbEntity> ContentPages { get; set; } = [];

/// <summary>
/// Joins for <see cref="ContentPages"/>
/// </summary>
public List<PageContentDbEntity> ContentPagesJoins { get; set; } = [];

}
4 changes: 2 additions & 2 deletions src/Dfe.PlanTech.Domain/Content/Models/Page.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ public class Page : ContentComponent, IPageContent

public string? SectionTitle { get; set; }

public List<ContentComponent> BeforeTitleContent { get; init; } = new();
public List<ContentComponent> BeforeTitleContent { get; init; } = [];

public Title? Title { get; init; }

public string? OrganisationName { get; set; }

public List<ContentComponent> Content { get; init; } = new();
public List<ContentComponent> Content { get; init; } = [];
}
5 changes: 5 additions & 0 deletions src/Dfe.PlanTech.Domain/Content/Models/PageContentDbEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ public class PageContentDbEntity

public string? BeforeContentComponentId { get; set; }
public ContentComponentDbEntity? BeforeContentComponent { get; set; }

/// <summary>
/// What order the component should be in in its respective section (e.g. before/after)
/// </summary>
public int Order { get; set; }
}
27 changes: 25 additions & 2 deletions src/Dfe.PlanTech.Domain/Content/Models/PageDbEntity.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.ComponentModel.DataAnnotations.Schema;
using Dfe.PlanTech.Domain.Content.Interfaces;
using Dfe.PlanTech.Domain.Questionnaire.Models;

Expand All @@ -19,16 +20,38 @@ public class PageDbEntity : ContentComponentDbEntity, IPage<ContentComponentDbEn

public bool RequiresAuthorisation { get; set; } = true;

public List<ContentComponentDbEntity> BeforeTitleContent { get; set; } = new();
public List<ContentComponentDbEntity> BeforeTitleContent { get; set; } = [];

public TitleDbEntity? Title { get; set; }

public string? TitleId { get; set; }

public List<ContentComponentDbEntity> Content { get; set; } = new();
public List<ContentComponentDbEntity> Content { get; set; } = [];

public RecommendationPageDbEntity? RecommendationPage { get; set; }

public SectionDbEntity? Section { get; set; }

/// <summary>
/// Combined joins for <see cref="Content"/> and <see cref="BeforeTitleContent"/>
/// </summary>
public List<PageContentDbEntity> AllPageContents { get; set; } = [];

public void OrderContents()
{
BeforeTitleContent = OrderContents(BeforeTitleContent, pageContent => pageContent.BeforeContentComponentId).ToList();
Content = OrderContents(Content, pageContent => pageContent.ContentComponentId).ToList();
}

private IEnumerable<ContentComponentDbEntity> OrderContents(List<ContentComponentDbEntity> contents, Func<PageContentDbEntity, string?> idSelector)
=> contents.Join(AllPageContents,
content => content.Id,
idSelector,
(content, pageContent) => new
{
content,
order = pageContent.Order
})
.OrderBy(content => content.order)
.Select(content => content.content);
}
33 changes: 20 additions & 13 deletions src/Dfe.PlanTech.Infrastructure.Data/CmsDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity<PageDbEntity>(entity =>
{
entity.HasMany(page => page.BeforeTitleContent)
.WithMany(c => c.BeforeTitleContentPages)
.UsingEntity<PageContentDbEntity>(
left => left.HasOne(pageContent => pageContent.BeforeContentComponent).WithMany().HasForeignKey("BeforeContentComponentId").OnDelete(DeleteBehavior.Restrict),
right => right.HasOne(pageContent => pageContent.Page).WithMany().HasForeignKey("PageId").OnDelete(DeleteBehavior.Restrict)
);
.WithMany(c => c.BeforeTitleContentPages)
.UsingEntity<PageContentDbEntity>(
left => left.HasOne(pageContent => pageContent.BeforeContentComponent).WithMany().HasForeignKey("BeforeContentComponentId").OnDelete(DeleteBehavior.Restrict),
right => right.HasOne(pageContent => pageContent.Page).WithMany().HasForeignKey("PageId").OnDelete(DeleteBehavior.Restrict)
);

entity.HasMany(page => page.Content)
.WithMany(c => c.ContentPages)
Expand All @@ -156,6 +156,15 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
entity.ToTable("Pages", Schema);
});

modelBuilder.Entity<PageContentDbEntity>(entity =>
{
entity.HasOne(pc => pc.BeforeContentComponent).WithMany(c => c.BeforeTitleContentPagesJoins);

entity.HasOne(pc => pc.ContentComponent).WithMany(c => c.ContentPagesJoins);

entity.HasOne(pc => pc.Page).WithMany(p => p.AllPageContents);
});

modelBuilder.Entity<QuestionDbEntity>().ToTable("Questions", Schema);

modelBuilder.Entity<RecommendationPageDbEntity>(entity =>
Expand Down Expand Up @@ -218,14 +227,12 @@ private Expression<Func<ContentComponentDbEntity, bool>> ShouldShowEntity()

public Task<PageDbEntity?> GetPageBySlug(string slug, CancellationToken cancellationToken = default)
=> Pages.Include(page => page.BeforeTitleContent)
.Include(page => page.Content)
.Include(page => page.Title)
.AsSplitQuery()
.FirstOrDefaultAsync(page => page.Slug == slug, cancellationToken);
.Include(page => page.Content)
.Include(page => page.Title)
.AsSplitQuery()
.FirstOrDefaultAsync(page => page.Slug == slug, cancellationToken);

public Task<List<T>> ToListAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
=> queryable.ToListAsync(cancellationToken: cancellationToken);
public Task<List<T>> ToListAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default) => queryable.ToListAsync(cancellationToken: cancellationToken);

public Task<T?> FirstOrDefaultAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
=> queryable.FirstOrDefaultAsync(cancellationToken);
public Task<T?> FirstOrDefaultAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default) => queryable.FirstOrDefaultAsync(cancellationToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ public class GetPageFromContentfulQueryTests
{
private const string TEST_PAGE_SLUG = "test-page-slug";
private const string SECTION_SLUG = "SectionSlugTest";
private const string SECTION_TITLE = "SectionTitleTest";
private const string LANDING_PAGE_SLUG = "LandingPage";
private const string BUTTON_REF_SLUG = "ButtonReferences";
private const string CATEGORY_ID = "category-one";

private readonly IContentRepository _repoSubstitute = Substitute.For<IContentRepository>();
private readonly ILogger<GetPageFromContentfulQuery> _logger = Substitute.For<ILogger<GetPageFromContentfulQuery>>();
Expand Down Expand Up @@ -70,7 +67,7 @@ private void SetupRepository()
}
}

return Array.Empty<Page>();
return [];
});
}

Expand Down
Loading
Loading