From c614926726b51b09f43646ab980ac92eb68de86b Mon Sep 17 00:00:00 2001 From: Mark Youngman Date: Fri, 13 Dec 2024 14:29:24 +0000 Subject: [PATCH] EES-5738 Data Catalogue page Geog lvl filtering --- .../FunctionsIntegrationTest.cs | 1 - .../DataSetFilesControllerCachingTests.cs | 2 + .../Controllers/DataSetFilesController.cs | 2 + .../DataSetFileListRequest.cs | 12 ++++ .../DataSetFileService.cs | 62 ++++++++++--------- .../Interfaces/IDataSetFileService.cs | 2 + .../data-catalogue/DataCataloguePage.tsx | 5 ++ .../data-catalogue/components/Filters.tsx | 36 +++++++++++ .../utils/createDataSetFileListRequest.ts | 3 + .../utils/dataSetFileFilters.ts | 1 + .../src/services/dataSetFileService.ts | 2 + 11 files changed, 97 insertions(+), 31 deletions(-) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Common.Tests/FunctionsIntegrationTest.cs b/src/GovUk.Education.ExploreEducationStatistics.Common.Tests/FunctionsIntegrationTest.cs index 0a7cda58edf..9516e767bbc 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Common.Tests/FunctionsIntegrationTest.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Common.Tests/FunctionsIntegrationTest.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using System.Threading.Tasks; using Azure.Data.Tables; using GovUk.Education.ExploreEducationStatistics.Common.Extensions; diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Api.Tests/Controllers/DataSetFilesControllerCachingTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Api.Tests/Controllers/DataSetFilesControllerCachingTests.cs index 651ba30ac9d..efa82dcb7ee 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Api.Tests/Controllers/DataSetFilesControllerCachingTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Api.Tests/Controllers/DataSetFilesControllerCachingTests.cs @@ -31,6 +31,7 @@ public class ListDataSetsTests : DataSetFilesControllerCachingTests ThemeId: Guid.NewGuid(), PublicationId: Guid.NewGuid(), ReleaseId: Guid.NewGuid(), + GeographicLevel: GeographicLevel.Country.GetEnumValue(), LatestOnly: true, DataSetType: DataSetType.Api, SearchTerm: "term", @@ -116,6 +117,7 @@ public async Task NoCachedEntryExists_CreatesCache() _query.ThemeId, _query.PublicationId, _query.ReleaseId, + _query.GeographicLevelEnum, _query.LatestOnly, _query.DataSetType, _query.SearchTerm, diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Api/Controllers/DataSetFilesController.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Api/Controllers/DataSetFilesController.cs index 015a79b28da..db48b8fc809 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Api/Controllers/DataSetFilesController.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Api/Controllers/DataSetFilesController.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using GovUk.Education.ExploreEducationStatistics.Common.Cache; using GovUk.Education.ExploreEducationStatistics.Common.Extensions; +using GovUk.Education.ExploreEducationStatistics.Common.Model.Data; using GovUk.Education.ExploreEducationStatistics.Common.ViewModels; using GovUk.Education.ExploreEducationStatistics.Content.Api.Cache; using GovUk.Education.ExploreEducationStatistics.Content.Requests; @@ -39,6 +40,7 @@ public async Task + GeographicLevel == null + ? null + : EnumUtil.GetFromEnumValue(GeographicLevel); + public class Validator : AbstractValidator { public Validator() @@ -23,6 +32,9 @@ public Validator() .MinimumLength(3); RuleFor(request => request.ReleaseId).NotEmpty() .When(request => request.Sort == DataSetsListRequestSortBy.Natural); + RuleFor(request => request.GeographicLevel) + .AllowedValue(EnumUtil.GetEnumValues()) + .When(request => request.GeographicLevel != null); RuleFor(request => request.SearchTerm).NotEmpty() .When(request => request.Sort == DataSetsListRequestSortBy.Relevance); RuleFor(request => request.Page) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Services/DataSetFileService.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Services/DataSetFileService.cs index ab71aad330e..4a0775ccddd 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Services/DataSetFileService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Services/DataSetFileService.cs @@ -26,35 +26,25 @@ using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; +using GovUk.Education.ExploreEducationStatistics.Common.Model.Data; using static GovUk.Education.ExploreEducationStatistics.Common.Model.SortDirection; using static GovUk.Education.ExploreEducationStatistics.Content.Requests.DataSetsListRequestSortBy; using ReleaseVersion = GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseVersion; namespace GovUk.Education.ExploreEducationStatistics.Content.Services; -public class DataSetFileService : IDataSetFileService +public class DataSetFileService( + ContentDbContext contentDbContext, + IReleaseVersionRepository releaseVersionRepository, + IPublicBlobStorageService publicBlobStorageService, + IFootnoteRepository footnoteRepository) + : IDataSetFileService { - private readonly ContentDbContext _contentDbContext; - private readonly IReleaseVersionRepository _releaseVersionRepository; - private readonly IPublicBlobStorageService _publicBlobStorageService; - private readonly IFootnoteRepository _footnoteRepository; - - public DataSetFileService( - ContentDbContext contentDbContext, - IReleaseVersionRepository releaseVersionRepository, - IPublicBlobStorageService publicBlobStorageService, - IFootnoteRepository footnoteRepository) - { - _contentDbContext = contentDbContext; - _releaseVersionRepository = releaseVersionRepository; - _publicBlobStorageService = publicBlobStorageService; - _footnoteRepository = footnoteRepository; - } - public async Task>> ListDataSetFiles( Guid? themeId, Guid? publicationId, Guid? releaseVersionId, + GeographicLevel? geographicLevel, bool? latestOnly, DataSetType? dataSetType, string? searchTerm, @@ -73,18 +63,19 @@ public async Task rf.Id, searchTerm); + .JoinFreeText(contentDbContext.ReleaseFilesFreeTextTable, rf => rf.Id, searchTerm); var results = await query .OrderBy(sort.Value, sortDirection.Value) @@ -144,10 +135,10 @@ private static Expression, DataSetFileSumm public async Task>> ListSitemapItems( CancellationToken cancellationToken = default) { - var latestReleaseVersions = _contentDbContext.ReleaseVersions + var latestReleaseVersions = contentDbContext.ReleaseVersions .LatestReleaseVersions(publishedOnly: true); - var latestReleaseFiles = _contentDbContext.ReleaseFiles + var latestReleaseFiles = contentDbContext.ReleaseFiles .AsNoTracking() .OfFileType(FileType.Data) .HavingNoDataReplacementInProgress() @@ -176,7 +167,7 @@ private static async Task> ChangeSummaryHtmlTo public async Task> GetDataSetFile(Guid dataSetFileId) { - var releaseFile = await _contentDbContext.ReleaseFiles + var releaseFile = await contentDbContext.ReleaseFiles .Include(rf => rf.ReleaseVersion.Publication.Theme) .Include(rf => rf.ReleaseVersion.Publication.SupersededBy) .Include(rf => rf.File) @@ -188,7 +179,7 @@ public async Task> GetDataSetFile(Gui .FirstOrDefaultAsync(); if (releaseFile == null - || !await _releaseVersionRepository.IsLatestPublishedReleaseVersion( + || !await releaseVersionRepository.IsLatestPublishedReleaseVersion( releaseFile.ReleaseVersionId)) { return new NotFoundResult(); @@ -198,7 +189,7 @@ public async Task> GetDataSetFile(Gui var variables = GetVariables(releaseFile.File.DataSetFileMeta!); - var footnotes = await _footnoteRepository.GetFootnotes( + var footnotes = await footnoteRepository.GetFootnotes( releaseFile.ReleaseVersionId, releaseFile.File.SubjectId); @@ -249,7 +240,7 @@ public async Task> GetDataSetFile(Gui public async Task DownloadDataSetFile( Guid dataSetFileId) { - var releaseFile = await _contentDbContext.ReleaseFiles + var releaseFile = await contentDbContext.ReleaseFiles .Include(rf => rf.File) .Where(rf => rf.File.DataSetFileId == dataSetFileId @@ -259,13 +250,13 @@ public async Task DownloadDataSetFile( .FirstOrDefaultAsync(); if (releaseFile == null - || !await _releaseVersionRepository.IsLatestPublishedReleaseVersion( + || !await releaseVersionRepository.IsLatestPublishedReleaseVersion( releaseFile.ReleaseVersionId)) { return new NotFoundResult(); } - var stream = await _publicBlobStorageService.StreamBlob( + var stream = await publicBlobStorageService.StreamBlob( containerName: BlobContainers.PublicReleaseFiles, path: releaseFile.PublicPath()); @@ -306,7 +297,7 @@ private static DataSetFileMetaViewModel BuildDataSetFileMetaViewModel( private async Task GetDataCsvPreview(ReleaseFile releaseFile) { - var datafileStreamProvider = () => _publicBlobStorageService.StreamBlob( + var datafileStreamProvider = () => publicBlobStorageService.StreamBlob( containerName: BlobContainers.PublicReleaseFiles, path: releaseFile.PublicPath()); @@ -491,6 +482,17 @@ internal static IQueryable HavingReleaseVersionId( return releaseVersionId.HasValue ? query.Where(rf => rf.ReleaseVersionId == releaseVersionId.Value) : query; } + internal static IQueryable HavingGeographicLevel( // @MarkFix write tests + this IQueryable query, + GeographicLevel? geographicLevel) + { + return geographicLevel.HasValue + ? query.Where(rf => rf.File.DataSetFileGeographicLevels!.Any( // @MarkFix null allowing + gl => gl.GeographicLevel == geographicLevel + && rf.FileId == gl.DataSetFileVersionId)) + : query; + } + internal static IQueryable OfDataSetType( this IQueryable query, DataSetType dataSetType) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Services/Interfaces/IDataSetFileService.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Services/Interfaces/IDataSetFileService.cs index 1b5cb2a3372..0a16d4cb669 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Services/Interfaces/IDataSetFileService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Services/Interfaces/IDataSetFileService.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using GovUk.Education.ExploreEducationStatistics.Common.Model; +using GovUk.Education.ExploreEducationStatistics.Common.Model.Data; using GovUk.Education.ExploreEducationStatistics.Common.ViewModels; using GovUk.Education.ExploreEducationStatistics.Content.Requests; using GovUk.Education.ExploreEducationStatistics.Content.ViewModels; @@ -17,6 +18,7 @@ Task>> Guid? themeId, Guid? publicationId, Guid? releaseVersionId, + GeographicLevel? geographicLevel, bool? latestOnly, DataSetType? dataSetType, string? searchTerm, diff --git a/src/explore-education-statistics-frontend/src/modules/data-catalogue/DataCataloguePage.tsx b/src/explore-education-statistics-frontend/src/modules/data-catalogue/DataCataloguePage.tsx index e551420f4d2..c14872aa2e0 100644 --- a/src/explore-education-statistics-frontend/src/modules/data-catalogue/DataCataloguePage.tsx +++ b/src/explore-education-statistics-frontend/src/modules/data-catalogue/DataCataloguePage.tsx @@ -52,6 +52,7 @@ import omit from 'lodash/omit'; import Head from 'next/head'; import { useRouter } from 'next/router'; import { ParsedUrlQuery } from 'querystring'; +import { GeographicLevelCode } from '@common/utils/locationLevelsMap'; const defaultPageTitle = 'Data catalogue'; @@ -61,6 +62,7 @@ export interface DataCataloguePageQuery { page?: number; publicationId?: string; releaseId?: string; + geographicLevel?: GeographicLevelCode; searchTerm?: string; sortBy?: DataSetFileSortOption; sortDirection?: SortDirection; @@ -85,6 +87,7 @@ const DataCataloguePage: NextPage = ({ showTypeFilter }) => { sortBy, publicationId, releaseId, + geographicLevel, searchTerm, themeId, } = getParamsFromQuery(router.query); @@ -131,6 +134,7 @@ const DataCataloguePage: NextPage = ({ showTypeFilter }) => { searchTerm, selectedTheme?.title, selectedPublication?.title, + geographicLevel, ]).join(', '); const updateQueryParams = async (nextQuery: DataCataloguePageQuery) => { @@ -327,6 +331,7 @@ const DataCataloguePage: NextPage = ({ showTypeFilter }) => { publicationId={publicationId} publications={publications} releaseId={releaseId} + geographicLevel={geographicLevel} releases={releases} showResetFiltersButton={!isMobileMedia && isFiltered} showTypeFilter={showTypeFilter} diff --git a/src/explore-education-statistics-frontend/src/modules/data-catalogue/components/Filters.tsx b/src/explore-education-statistics-frontend/src/modules/data-catalogue/components/Filters.tsx index 1dc33f5d1ed..c564cbc8fd5 100644 --- a/src/explore-education-statistics-frontend/src/modules/data-catalogue/components/Filters.tsx +++ b/src/explore-education-statistics-frontend/src/modules/data-catalogue/components/Filters.tsx @@ -13,6 +13,10 @@ import styles from '@frontend/modules/data-catalogue/components/Filters.module.s import { DataSetFileFilter } from '@frontend/modules/data-catalogue/utils/dataSetFileFilters'; import React from 'react'; import classNames from 'classnames'; +import locationLevelsMap, { + GeographicLevelCode, +} from '@common/utils/locationLevelsMap'; +import typedKeys from '@common/utils/object/typedKeys'; const formId = 'filters-form'; @@ -23,6 +27,7 @@ interface Props { publications?: PublicationTreeSummary[]; releaseId?: string; releases?: ReleaseSummary[]; + geographicLevel?: GeographicLevelCode; showResetFiltersButton?: boolean; showTypeFilter?: boolean; themeId?: string; @@ -44,6 +49,7 @@ export default function Filters({ publicationId, releaseId, releases = [], + geographicLevel, showResetFiltersButton, showTypeFilter, themeId, @@ -140,6 +146,36 @@ export default function Filters({ /> + + + Filter by Geographic level + + } + name="geographicLevel" + options={[ + { label: 'All', value: 'all' }, + ...typedKeys(locationLevelsMap).map(key => { + return { + label: locationLevelsMap[key].label, + value: locationLevelsMap[key].code, + }; + }), + ]} + value={geographicLevel} + order={[]} + onChange={e => { + onChange({ + filterType: 'geographicLevel', + nextValue: e.target.value, + }); + }} + /> + + {showResetFiltersButton && (