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

feat: Add sort dropdown and Tags to Resource Search #315

Merged
merged 9 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -7,6 +7,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using NSubstitute;
using NSubstitute.Core;
using NUnit.Framework;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -58,6 +59,24 @@ public async Task Search_Returns_Strategy_Model()
actual.Model.Should().Be(model);
}

[Test]
public async Task Search_Returns_Strategy_Model_When_Order_Value_Returned()
{
// arrange
var model = new ResourcesListViewModel(null, null, null, null);
_searchStrategy.SearchAsync(Arg.Any<ResourcesQuery>(), Arg.Any<CancellationToken>()).Returns(model);
ResourcesQuery query = new ResourcesQuery()
{
Order = "1"
};

// act
var actual = await _resourcesController.Search(query: query) as ViewResult;

// assert
actual.Model.Should().Be(model);
}

[Test]
public async Task Disabling_Resources_Feature_Returns_NotFoundResult()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ private void SetPageContent(Content content)
private void SetSearchResults(ResponseType content)
{
_resourcesRepository
.FindByTagsAsync(Arg.Any<IEnumerable<string>>(), Arg.Any<int>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
.FindByTagsAsync(Arg.Any<IEnumerable<string>>(), Arg.Any<int>(), Arg.Any<int>(), Arg.Any<ResourceSortOrder>(), Arg.Any<CancellationToken>())
.Returns(content);
}

Expand Down Expand Up @@ -147,10 +147,10 @@ public async Task Invalid_Tags_Are_Not_Queried_For(string value)
Page = 2,
Tags = tags
};
await _resourcesRepository.FindByTagsAsync(Arg.Do<IEnumerable<string>>(value => passedTags = value), Arg.Any<int>(), Arg.Any<int>(), Arg.Any<CancellationToken>());
await _resourcesRepository.FindByTagsAsync(Arg.Do<IEnumerable<string>>(value => passedTags = value), Arg.Any<int>(), Arg.Any<int>(), Arg.Any<ResourceSortOrder>(), Arg.Any<CancellationToken>());

// act
var actual = await _sut.SearchAsync(query);
await _sut.SearchAsync(query);

//assert
passedTags.Should().NotContain(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ private void SetPageContent(Content content)
private void SetSearchResults(ResponseType content)
{
_resourcesRepository
.FindByTagsAsync(Arg.Any<IEnumerable<string>>(), Arg.Any<int>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
.FindByTagsAsync(Arg.Any<IEnumerable<string>>(), Arg.Any<int>(), Arg.Any<int>(), Arg.Any<ResourceSortOrder>(), Arg.Any<CancellationToken>())
.Returns(content);
}

Expand Down Expand Up @@ -141,10 +141,10 @@ public async Task Invalid_Tags_Are_Not_Queried_For(string value)
Page = 2,
Tags = tags
};
await _resourcesRepository.FindByTagsAsync(Arg.Do<IEnumerable<string>>(value => passedTags = value), Arg.Any<int>(), Arg.Any<int>(), Arg.Any<CancellationToken>());
await _resourcesRepository.FindByTagsAsync(Arg.Do<IEnumerable<string>>(value => passedTags = value), Arg.Any<int>(), Arg.Any<int>(), Arg.Any<ResourceSortOrder>(), Arg.Any<CancellationToken>());

// act
var actual = await _sut.SearchAsync(query);
await _sut.SearchAsync(query);

//assert
passedTags.Should().NotContain(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using System;
using Contentful.Core.Models.Management;
using System.Linq;
using Childrens_Social_Care_CPD.Controllers;

namespace Childrens_Social_Care_CPD_Tests.DataAccess;

Expand Down Expand Up @@ -106,7 +107,7 @@ public async Task FindByTagsAsync_Returns_Results()
var sut = new ResourcesRepository(_applicationConfiguration, _contentfulClient, _gqlClient);

// act
var result = await sut.FindByTagsAsync(Array.Empty<string>(), 0, 1, _cancellationTokenSource.Token);
var result = await sut.FindByTagsAsync(Array.Empty<string>(), 0, 1, ResourceSortOrder.UpdatedNewest, _cancellationTokenSource.Token);

// assert
result.Should().Be(results);
Expand All @@ -125,7 +126,7 @@ public async Task FindByTagsAsync_Limits_Results()
var sut = new ResourcesRepository(_applicationConfiguration, _contentfulClient, _gqlClient);

// act
await sut.FindByTagsAsync(Array.Empty<string>(), 0, 1, _cancellationTokenSource.Token);
await sut.FindByTagsAsync(Array.Empty<string>(), 0, 1, ResourceSortOrder.UpdatedNewest, _cancellationTokenSource.Token);

// assert
dynamic variables = request.Variables;
Expand All @@ -145,7 +146,7 @@ public async Task FindByTagsAsync_Skips_Results()
var sut = new ResourcesRepository(_applicationConfiguration, _contentfulClient, _gqlClient);

// act
await sut.FindByTagsAsync(Array.Empty<string>(), 5, 1, _cancellationTokenSource.Token);
await sut.FindByTagsAsync(Array.Empty<string>(), 5, 1, ResourceSortOrder.UpdatedNewest, _cancellationTokenSource.Token);

// assert
dynamic variables = request.Variables;
Expand All @@ -164,7 +165,7 @@ public async Task FindByTagsAsync_Preview_Flag_Is_False_By_Default()
var sut = new ResourcesRepository(_applicationConfiguration, _contentfulClient, _gqlClient);

// act
await sut.FindByTagsAsync(Array.Empty<string>(), 5, 1, _cancellationTokenSource.Token);
await sut.FindByTagsAsync(Array.Empty<string>(), 5, 1, ResourceSortOrder.UpdatedNewest, _cancellationTokenSource.Token);

// assert
dynamic variables = request.Variables;
Expand All @@ -185,7 +186,7 @@ public async Task FindByTagsAsync_Sets_Preview_Flag()
var sut = new ResourcesRepository(_applicationConfiguration, _contentfulClient, _gqlClient);

// act
await sut.FindByTagsAsync(Array.Empty<string>(), 5, 1, _cancellationTokenSource.Token);
await sut.FindByTagsAsync(Array.Empty<string>(), 5, 1, ResourceSortOrder.UpdatedNewest, _cancellationTokenSource.Token);

// assert
dynamic variables = request.Variables;
Expand Down
2 changes: 1 addition & 1 deletion Childrens-Social-Care-CPD/Contentful/Models/Resource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ public class Resource : IContent
public string Id { get; set; }
public string Title { get; set; }
public string From { get; set; }
public List<string> Type { get; set; }
public string Summary { get; set; }
public string Label { get; set; }
public string SearchSummary { get; set; }
public List<IContent> Items { get; set; }

Expand Down
16 changes: 15 additions & 1 deletion Childrens-Social-Care-CPD/Controllers/ResourcesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,24 @@
using Childrens_Social_Care_CPD.Core.Resources;
using Childrens_Social_Care_CPD.Models;
using Microsoft.AspNetCore.Mvc;
using Childrens_Social_Care_CPD.Extensions;

namespace Childrens_Social_Care_CPD.Controllers;

public enum ResourceSortOrder
{
UpdatedNewest = 0,
UpdatedOldest = 1
}

public class ResourcesQuery
{
public string[] Tags { get; set; }
public int Page { get; set; } = 1;
public string Order { get; set; }
public ResourceSortOrder SortOrder { get; set; }

public ResourcesQuery()
public ResourcesQuery()
{
Tags = Array.Empty<string>();
}
Expand All @@ -37,6 +46,11 @@ public async Task<IActionResult> Search([FromQuery] ResourcesQuery query, bool p
return NotFound();
}

if (query is not null && !string.IsNullOrEmpty(query.Order))
{
query.SortOrder = query.Order.ToEnum<ResourceSortOrder>();
}

var contextModel = new ContextModel(string.Empty, "Resources", "Resources", "Resources", true, preferencesSet);
ViewData["ContextModel"] = contextModel;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Childrens_Social_Care_CPD.Controllers;
using Childrens_Social_Care_CPD.DataAccess;
using Childrens_Social_Care_CPD.Extensions;
using Childrens_Social_Care_CPD.GraphQL.Queries;
using Childrens_Social_Care_CPD.Models;

Expand Down Expand Up @@ -62,15 +63,23 @@ public async Task<ResourcesListViewModel> SearchAsync(ResourcesQuery query, Canc
var skip = (page - 1) * PAGE_SIZE;

var pageContentTask = _resourcesRepository.FetchRootPageAsync(cancellationToken);
var searchResults = await _resourcesRepository.FindByTagsAsync(GetQueryTags(query.Tags, tagIds), skip, PAGE_SIZE, cancellationToken);
var searchResults = await _resourcesRepository.FindByTagsAsync(GetQueryTags(query.Tags, tagIds), skip, PAGE_SIZE, query.SortOrder, cancellationToken);
var pageContent = await pageContentTask;
(var totalResults, var totalPages, var currentPage) = CalculatePageStats(searchResults, page);

int startRecord = 0;
if (totalResults > 0)
{
startRecord = ((currentPage * PAGE_SIZE) - PAGE_SIZE) + 1;
}

return new ResourcesListViewModel(
pageContent,
searchResults?.ResourceCollection,
tagInfos,
query.Tags,
(int)query.SortOrder,
startRecord,
currentPage,
totalPages,
totalResults,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Childrens_Social_Care_CPD.Controllers;
using Childrens_Social_Care_CPD.DataAccess;
using Childrens_Social_Care_CPD.Extensions;
using Childrens_Social_Care_CPD.GraphQL.Queries;
using Childrens_Social_Care_CPD.Models;

Expand Down Expand Up @@ -71,15 +72,23 @@ public async Task<ResourcesListViewModel> SearchAsync(ResourcesQuery query, Canc
var page = Math.Max(query.Page, 1);
var skip = (page - 1) * PAGE_SIZE;
var pageContentTask = _resourcesRepository.FetchRootPageAsync(cancellationToken);
var searchResults = await _resourcesRepository.FindByTagsAsync(GetQueryTags(queryTags), skip, PAGE_SIZE, cancellationToken);
var searchResults = await _resourcesRepository.FindByTagsAsync(GetQueryTags(queryTags), skip, PAGE_SIZE, query.SortOrder, cancellationToken);
var pageContent = await pageContentTask;
(var totalResults, var totalPages, var currentPage) = CalculatePageStats(searchResults, page);

int startRecord = 0;
if (totalResults > 0)
{
startRecord = ((currentPage * PAGE_SIZE) - PAGE_SIZE) + 1;
}

return new ResourcesListViewModel(
pageContent,
searchResults?.ResourceCollection,
_tagInfos,
queryTags.Select(x => x.ToString()),
(int)query.SortOrder,
startRecord,
currentPage,
totalPages,
totalResults,
Expand Down
9 changes: 6 additions & 3 deletions Childrens-Social-Care-CPD/DataAccess/ResourcesRepository.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Childrens_Social_Care_CPD.Configuration;
using Childrens_Social_Care_CPD.Contentful;
using Childrens_Social_Care_CPD.Contentful.Models;
using Childrens_Social_Care_CPD.Controllers;
using Childrens_Social_Care_CPD.Core.Resources;
using Childrens_Social_Care_CPD.GraphQL.Queries;
using Contentful.Core.Search;
Expand All @@ -12,7 +13,7 @@ namespace Childrens_Social_Care_CPD.DataAccess;
public interface IResourcesRepository
{
Task<Content> FetchRootPageAsync(CancellationToken cancellationToken = default);
Task<SearchResourcesByTags.ResponseType> FindByTagsAsync(IEnumerable<string> tags, int skip, int take, CancellationToken cancellationToken = default);
Task<SearchResourcesByTags.ResponseType> FindByTagsAsync(IEnumerable<string> tags, int skip, int take, ResourceSortOrder resourceSortOrder, CancellationToken cancellationToken = default);
Task<IEnumerable<TagInfo>> GetSearchTagsAsync();
}

Expand Down Expand Up @@ -42,10 +43,12 @@ public Task<Content> FetchRootPageAsync(CancellationToken cancellationToken = de
.ContinueWith(x => x.Result.FirstOrDefault());
}

public Task<SearchResourcesByTags.ResponseType> FindByTagsAsync(IEnumerable<string> tags, int skip, int take, CancellationToken cancellationToken = default)
public Task<SearchResourcesByTags.ResponseType> FindByTagsAsync(IEnumerable<string> tags, int skip, int take, ResourceSortOrder resourceSortOrder, CancellationToken cancellationToken = default)
{
string order = (resourceSortOrder == ResourceSortOrder.UpdatedNewest) ? "sys_publishedAt_ASC" : "sys_publishedAt_DESC";

return _gqlClient
.SendQueryAsync<SearchResourcesByTags.ResponseType>(SearchResourcesByTags.Query(tags, take, skip, _isPreview), cancellationToken)
.SendQueryAsync<SearchResourcesByTags.ResponseType>(SearchResourcesByTags.Query(tags, take, skip, order, _isPreview), cancellationToken)
.ContinueWith(x => x.Result.Data);
}

Expand Down
10 changes: 10 additions & 0 deletions Childrens-Social-Care-CPD/Extensions/EnumExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Childrens_Social_Care_CPD.Extensions
{
public static class EnumExtension
{
public static T ToEnum<T>(this string enumString)
{
return (T)Enum.Parse(typeof(T), enumString);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,26 @@ namespace Childrens_Social_Care_CPD.GraphQL.Queries;

public class SearchResourcesByTags
{
public static GraphQLRequest Query(IEnumerable<string> tags, int limit, int skip, bool preview = false)
public static GraphQLRequest Query(IEnumerable<string> tags, int limit, int skip, string order = "sys_publishedAt_ASC", bool preview = false)
{
return new GraphQLRequest
{
Query = @"
query SearchResourcesByTags($searchTags: [String!], $limit: Int, $skip: Int, $preview: Boolean) {
query SearchResourcesByTags($searchTags: [String!], $limit: Int, $skip: Int, $order: [ResourceOrder], $preview: Boolean) {
resourceCollection(where: {
contentfulMetadata: {
tags_exists: true
tags: {
id_contains_some: $searchTags
}
}
}, limit: $limit, skip: $skip, preview: $preview) {
}, limit: $limit, skip: $skip, order: $order, preview: $preview) {
total
items {
title
from
searchSummary
type
label
sys {
publishedAt
firstPublishedAt
Expand All @@ -45,6 +45,7 @@ query SearchResourcesByTags($searchTags: [String!], $limit: Int, $skip: Int, $pr
searchTags = tags,
limit,
skip,
order,
preview,
}
};
Expand All @@ -68,7 +69,7 @@ public class SearchResult
public string Title { get; set; }
public string From { get; set; }
public string SearchSummary { get; set; }
public ICollection<string> Type { get; set; }
public string Label { get; set; }
public PublishedInfo Sys { get; set; }
public LinkedFromContentCollection LinkedFrom { get; set; }
}
Expand Down
11 changes: 11 additions & 0 deletions Childrens-Social-Care-CPD/Models/ResourcesListViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
using Childrens_Social_Care_CPD.Contentful.Models;
using Childrens_Social_Care_CPD.Controllers;
using Childrens_Social_Care_CPD.Core.Resources;
using Childrens_Social_Care_CPD.GraphQL.Queries;

namespace Childrens_Social_Care_CPD.Models;

public static class ResourceSort
{
public const string Updatednewest = "Updated (newest)";
public const string Updatedoldest = "Updated (oldest)";
public const string MostViewed = "Most viewed";
}


public record ResourcesListViewModel(
Content Content,
SearchResourcesByTags.ResourceCollection Results,
IEnumerable<TagInfo> TagInfos,
IEnumerable<string> SelectedTags,
int SortOrder = 0,
int StartRecord = 0,
int CurrentPage = 0,
int TotalPages = 0,
int TotalResults = 0,
Expand Down
8 changes: 4 additions & 4 deletions Childrens-Social-Care-CPD/TagHelpers/GdsFilterHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Html;
using Childrens_Social_Care_CPD.Models;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Razor.TagHelpers;
Expand All @@ -8,7 +9,6 @@ namespace Childrens_Social_Care_CPD.TagHelpers;

[HtmlTargetElement(TagName)]
[OutputElementHint("div")]
[RestrictChildren(GdsFilterCategoryTagHelper.TagName)]
public class GdsFilterTagHelper : TagHelper
{
internal const string TagName = "gds-filter";
Expand Down Expand Up @@ -40,10 +40,10 @@ private static IHtmlContent RenderBody(IHtmlContent content)
innerDiv.Attributes.Add("id", "accordion-default");
innerDiv.Attributes.Add("data-module", "govuk-accordion");
innerDiv.InnerHtml.AppendHtml(content);

var form = new TagBuilder("form");
form.Attributes.Add("id", "filter-form");
form.Attributes.Add("method", "get");

form.InnerHtml.AppendHtml(innerDiv);
form.InnerHtml.AppendHtml(button);

Expand Down
Loading
Loading