diff --git a/src/GovUk.Education.ExploreEducationStatistics.Common.Tests/Extensions/HttpResponseMessageTestExtensions.cs b/src/GovUk.Education.ExploreEducationStatistics.Common.Tests/Extensions/HttpResponseMessageTestExtensions.cs index c4ecfb9ad34..aa60be6979a 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Common.Tests/Extensions/HttpResponseMessageTestExtensions.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Common.Tests/Extensions/HttpResponseMessageTestExtensions.cs @@ -1,4 +1,5 @@ #nullable enable +using System; using System.Net.Http; using GovUk.Education.ExploreEducationStatistics.Common.Extensions; using Xunit; @@ -34,6 +35,13 @@ public static T AssertOk(this HttpResponseMessage message, T expectedBody) return message.AssertBodyEqualTo(expectedBody); } + public static T AssertCreated(this HttpResponseMessage message, T expectedBody, string expectedLocation) + { + Assert.Equal(Created, message.StatusCode); + Assert.Equal(new Uri(expectedLocation), message.Headers.Location); + return message.AssertBodyEqualTo(expectedBody); + } + public static void AssertNoContent(this HttpResponseMessage message) { Assert.Equal(NoContent, message.StatusCode); diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Controllers/PermalinkControllerTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Controllers/PermalinkControllerTests.cs index 3b30b6de777..6927e4c03ed 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Controllers/PermalinkControllerTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Controllers/PermalinkControllerTests.cs @@ -8,6 +8,7 @@ using GovUk.Education.ExploreEducationStatistics.Common.Model; using GovUk.Education.ExploreEducationStatistics.Common.Tests.Extensions; using GovUk.Education.ExploreEducationStatistics.Common.Tests.Fixtures; +using GovUk.Education.ExploreEducationStatistics.Common.Tests.Utils; using GovUk.Education.ExploreEducationStatistics.Common.Utils; using GovUk.Education.ExploreEducationStatistics.Data.Api.Requests; using GovUk.Education.ExploreEducationStatistics.Data.Api.Services.Interfaces; @@ -18,8 +19,6 @@ using Microsoft.Net.Http.Headers; using Moq; using Xunit; -using static GovUk.Education.ExploreEducationStatistics.Common.Tests.Utils.MockUtils; -using static Moq.MockBehavior; namespace GovUk.Education.ExploreEducationStatistics.Data.Api.Tests.Controllers; @@ -36,9 +35,12 @@ public PermalinkControllerTests(TestApplicationFactory testApp) public async Task CreatePermalink() { var createRequest = new PermalinkCreateRequest(); - var expectedResult = new PermalinkViewModel(); + var expectedResult = new PermalinkViewModel + { + Id = Guid.NewGuid() + }; - var permalinkService = new Mock(Strict); + var permalinkService = new Mock(MockBehavior.Strict); permalinkService .Setup(s => s.CreatePermalink(It.Is(r => r.IsDeepEqualTo(createRequest)), @@ -49,38 +51,12 @@ public async Task CreatePermalink() .CreateClient(); var response = await client.PostAsync( - requestUri: "/api/permalink-snapshot", - content: new JsonNetContent(createRequest)); - - VerifyAllMocks(permalinkService); - - response.AssertOk(expectedResult); - } - - [Fact] - public async Task CreatePermalink_WithReleaseId() - { - var releaseId = Guid.NewGuid(); - var createRequest = new PermalinkCreateRequest(); - var expectedResult = new PermalinkViewModel(); - - var permalinkService = new Mock(Strict); - - permalinkService - .Setup(s => s.CreatePermalink(releaseId, It.Is(r => r.IsDeepEqualTo(createRequest)), - It.IsAny())) - .ReturnsAsync(expectedResult); - - var client = SetupApp(permalinkService: permalinkService.Object) - .CreateClient(); - - var response = await client.PostAsync( - requestUri: $"/api/permalink-snapshot/release/{releaseId}", + requestUri: "/api/permalink", content: new JsonNetContent(createRequest)); - VerifyAllMocks(permalinkService); + MockUtils.VerifyAllMocks(permalinkService); - response.AssertOk(expectedResult); + response.AssertCreated(expectedResult, $"http://localhost/api/permalink/{expectedResult.Id}"); } [Fact] @@ -92,7 +68,7 @@ public async Task GetPermalink() Id = permalinkId }; - var permalinkService = new Mock(Strict); + var permalinkService = new Mock(MockBehavior.Strict); permalinkService .Setup(s => s.GetPermalink(permalinkId, It.IsAny())) @@ -102,14 +78,14 @@ public async Task GetPermalink() .CreateClient(); var response = await client.GetAsync( - uri: $"/api/permalink-snapshot/{permalinkId}", + uri: $"/api/permalink/{permalinkId}", headers: new Dictionary { { HeaderNames.Accept, "application/json" } } ); - VerifyAllMocks(permalinkService); + MockUtils.VerifyAllMocks(permalinkService); response.AssertOk(permalink); } @@ -119,7 +95,7 @@ public async Task GetPermalink_NotFound() { var permalinkId = Guid.NewGuid(); - var permalinkService = new Mock(Strict); + var permalinkService = new Mock(MockBehavior.Strict); permalinkService .Setup(s => s.GetPermalink(permalinkId, It.IsAny())) @@ -129,14 +105,14 @@ public async Task GetPermalink_NotFound() .CreateClient(); var response = await client.GetAsync( - uri: $"/api/permalink-snapshot/{permalinkId}", + uri: $"/api/permalink/{permalinkId}", headers: new Dictionary { { HeaderNames.Accept, "application/json" } } ); - VerifyAllMocks(permalinkService); + MockUtils.VerifyAllMocks(permalinkService); response.AssertNotFound(); } @@ -145,7 +121,7 @@ public async Task GetPermalink_NotFound() public async Task GetPermalink_Csv() { var permalinkId = Guid.NewGuid(); - var permalinkService = new Mock(Strict); + var permalinkService = new Mock(MockBehavior.Strict); permalinkService .Setup(s => s @@ -159,14 +135,14 @@ public async Task GetPermalink_Csv() .CreateClient(); var response = await client.GetAsync( - uri: $"/api/permalink-snapshot/{permalinkId}", + uri: $"/api/permalink/{permalinkId}", headers: new Dictionary { { HeaderNames.Accept, ContentTypes.Csv } } ); - VerifyAllMocks(permalinkService); + MockUtils.VerifyAllMocks(permalinkService); response.AssertOk("Test csv"); } @@ -176,7 +152,7 @@ public async Task GetPermalink_Csv_NotFound() { var permalinkId = Guid.NewGuid(); - var permalinkService = new Mock(Strict); + var permalinkService = new Mock(MockBehavior.Strict); permalinkService .Setup(s => s @@ -187,14 +163,14 @@ public async Task GetPermalink_Csv_NotFound() .CreateClient(); var response = await client.GetAsync( - uri: $"/api/permalink-snapshot/{permalinkId}", + uri: $"/api/permalink/{permalinkId}", headers: new Dictionary { { HeaderNames.Accept, ContentTypes.Csv } } ); - VerifyAllMocks(permalinkService); + MockUtils.VerifyAllMocks(permalinkService); response.AssertNotFound(); } @@ -204,7 +180,7 @@ public async Task GetPermalink_InvalidIdReturnsNotFound() { var client = SetupApp().CreateClient(); - var response = await client.GetAsync("/api/permalink-snapshot/not-a-guid"); + var response = await client.GetAsync("/api/permalink/not-a-guid"); response.AssertNotFound(); } @@ -212,7 +188,10 @@ public async Task GetPermalink_InvalidIdReturnsNotFound() private WebApplicationFactory SetupApp(IPermalinkService? permalinkService = null) { return _testApp.ConfigureServices( - services => { services.AddTransient(_ => permalinkService ?? Mock.Of(Strict)); } + services => + { + services.AddTransient(_ => permalinkService ?? Mock.Of(MockBehavior.Strict)); + } ); } } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/PermalinkServiceTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/PermalinkServiceTests.cs index 9b5a8757da1..ebfd385e7b6 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/PermalinkServiceTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Data.Api.Tests/Services/PermalinkServiceTests.cs @@ -188,18 +188,6 @@ public async Task CreatePermalink_WithoutReleaseId() var footnoteViewModels = FootnotesViewModelBuilder.BuildFootnotes(footnotes); - var request = new PermalinkCreateRequest - { - Configuration = new TableBuilderConfiguration - { - TableHeaders = new TableHeaders() - }, - Query = - { - SubjectId = subject.Id - } - }; - var tableResult = new TableBuilderResultViewModel { SubjectMeta = new SubjectResultMetaViewModel @@ -278,6 +266,19 @@ public async Task CreatePermalink_WithoutReleaseId() } }; + var request = new PermalinkCreateRequest + { + ReleaseId = null, + Configuration = new TableBuilderConfiguration + { + TableHeaders = new TableHeaders() + }, + Query = + { + SubjectId = subject.Id + } + }; + var publicBlobStorageService = new Mock(MockBehavior.Strict); Guid expectedPermalinkId; @@ -519,18 +520,6 @@ public async Task CreatePermalink_WithReleaseId() var footnoteViewModels = FootnotesViewModelBuilder.BuildFootnotes(footnotes); - var request = new PermalinkCreateRequest - { - Configuration = new TableBuilderConfiguration - { - TableHeaders = new TableHeaders() - }, - Query = - { - SubjectId = subject.Id - } - }; - var tableResult = new TableBuilderResultViewModel { SubjectMeta = new SubjectResultMetaViewModel @@ -609,6 +598,19 @@ public async Task CreatePermalink_WithReleaseId() } }; + var request = new PermalinkCreateRequest + { + ReleaseId = release.Id, + Configuration = new TableBuilderConfiguration + { + TableHeaders = new TableHeaders() + }, + Query = + { + SubjectId = subject.Id + } + }; + var publicBlobStorageService = new Mock(MockBehavior.Strict); Guid expectedPermalinkId; @@ -705,7 +707,7 @@ public async Task CreatePermalink_WithReleaseId() permalinkCsvMetaService: permalinkCsvMetaService.Object, tableBuilderService: tableBuilderService.Object); - var result = (await service.CreatePermalink(release.Id, request)).AssertRight(); + var result = (await service.CreatePermalink(request)).AssertRight(); MockUtils.VerifyAllMocks( publicBlobStorageService, diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Controllers/PermalinkController.cs b/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Controllers/PermalinkController.cs index ebc051c0c98..fecc5f0d32c 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Controllers/PermalinkController.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Controllers/PermalinkController.cs @@ -23,7 +23,7 @@ public PermalinkController(IPermalinkService permalinkService) _permalinkService = permalinkService; } - [HttpGet("permalink-snapshot/{permalinkId:guid}")] + [HttpGet("permalink/{permalinkId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [Produces("application/json", "text/csv")] @@ -58,27 +58,19 @@ public async Task GetPermalink(Guid permalinkId, await result.ExecuteResultAsync(ControllerContext); } - [HttpPost("permalink-snapshot")] - [ProducesResponseType(StatusCodes.Status200OK)] + [HttpPost("permalink")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> CreatePermalink( [FromBody] PermalinkCreateRequest request, CancellationToken cancellationToken = default) { return await _permalinkService .CreatePermalink(request, cancellationToken) - .HandleFailuresOrOk(); - } - - [HttpPost("permalink-snapshot/release/{releaseId:guid}")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> CreatePermalink( - Guid releaseId, - [FromBody] PermalinkCreateRequest request, - CancellationToken cancellationToken = default) - { - return await _permalinkService - .CreatePermalink(releaseId, request, cancellationToken) - .HandleFailuresOrOk(); + .HandleFailuresOr(permalink => CreatedAtAction(nameof(GetPermalink), new + { + permalinkId = permalink.Id + }, permalink)); } } } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Requests/PermalinkCreateRequest.cs b/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Requests/PermalinkCreateRequest.cs index 59b6647e305..f1505144466 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Requests/PermalinkCreateRequest.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Requests/PermalinkCreateRequest.cs @@ -1,4 +1,5 @@ #nullable enable +using System; using GovUk.Education.ExploreEducationStatistics.Common.Model.Data; using GovUk.Education.ExploreEducationStatistics.Common.Model.Data.Query; @@ -6,6 +7,8 @@ namespace GovUk.Education.ExploreEducationStatistics.Data.Api.Requests; public record PermalinkCreateRequest { + public Guid? ReleaseId { get; init; } + public TableBuilderConfiguration Configuration { get; init; } = new(); public ObservationQueryContext Query { get; init; } = new(); diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Services/Interfaces/IPermalinkService.cs b/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Services/Interfaces/IPermalinkService.cs index 25fc4af5518..944d29904cd 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Services/Interfaces/IPermalinkService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Services/Interfaces/IPermalinkService.cs @@ -15,10 +15,6 @@ public interface IPermalinkService Task> CreatePermalink(PermalinkCreateRequest request, CancellationToken cancellationToken = default); - Task> CreatePermalink(Guid releaseId, - PermalinkCreateRequest request, - CancellationToken cancellationToken = default); - Task> GetPermalink(Guid permalinkId, CancellationToken cancellationToken = default); diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Services/PermalinkService.cs b/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Services/PermalinkService.cs index 1cea86c1ad1..ae858f73275 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Services/PermalinkService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Data.Api/Services/PermalinkService.cs @@ -86,75 +86,69 @@ private async Task> Find(Guid permalinkId, public async Task> CreatePermalink(PermalinkCreateRequest request, CancellationToken cancellationToken = default) { - return await _subjectRepository.FindPublicationIdForSubject(request.Query.SubjectId) - .OrNotFound() - .OnSuccess(publicationId => _releaseRepository.GetLatestPublishedRelease(publicationId)) - .OnSuccess(release => CreatePermalink(release.Id, request, cancellationToken)); - } - - public async Task> CreatePermalink(Guid releaseId, - PermalinkCreateRequest request, - CancellationToken cancellationToken = default) - { - return await _tableBuilderService.Query(releaseId, request.Query, cancellationToken) - .OnSuccess(async tableResult => + return await (request.ReleaseId ?? await FindLatestPublishedReleaseId(request.Query.SubjectId)) + .OnSuccess(releaseId => { - var frontendTableTask = _frontendService.CreateTable( - tableResult, - request.Configuration, - cancellationToken - ); - - var csvMetaTask = _permalinkCsvMetaService.GetCsvMeta( - request.Query.SubjectId, - tableResult.SubjectMeta, - cancellationToken - ); - - await Task.WhenAll(frontendTableTask, csvMetaTask); - - var frontendTableResult = frontendTableTask.Result; - var csvMetaResult = csvMetaTask.Result; - - if (frontendTableResult.IsLeft) - { - return frontendTableResult.Left; - } - - if (csvMetaResult.IsLeft) - { - return csvMetaResult.Left; - } - - var table = frontendTableResult.Right; - var csvMeta = csvMetaResult.Right; - - var subjectMeta = tableResult.SubjectMeta; - - // To avoid the frontend processing and returning the footnotes unnecessarily, - // create a new view model with the footnotes added directly - var tableWithFootnotes = table with - { - Footnotes = subjectMeta.Footnotes - }; - - var permalink = new Permalink - { - ReleaseId = releaseId, - SubjectId = request.Query.SubjectId, - PublicationTitle = subjectMeta.PublicationName, - DataSetTitle = subjectMeta.SubjectName, - }; - _contentDbContext.Permalinks.Add(permalink); - - await UploadSnapshot(permalink: permalink, - observations: tableResult.Results.ToList(), - csvMeta: csvMeta, - table: tableWithFootnotes, - cancellationToken: cancellationToken); - - await _contentDbContext.SaveChangesAsync(cancellationToken); - return await BuildViewModel(permalink, tableWithFootnotes); + return _tableBuilderService.Query(releaseId, request.Query, cancellationToken) + .OnSuccess(async tableResult => + { + var frontendTableTask = _frontendService.CreateTable( + tableResult, + request.Configuration, + cancellationToken + ); + + var csvMetaTask = _permalinkCsvMetaService.GetCsvMeta( + request.Query.SubjectId, + tableResult.SubjectMeta, + cancellationToken + ); + + await Task.WhenAll(frontendTableTask, csvMetaTask); + + var frontendTableResult = frontendTableTask.Result; + var csvMetaResult = csvMetaTask.Result; + + if (frontendTableResult.IsLeft) + { + return frontendTableResult.Left; + } + + if (csvMetaResult.IsLeft) + { + return csvMetaResult.Left; + } + + var table = frontendTableResult.Right; + var csvMeta = csvMetaResult.Right; + + var subjectMeta = tableResult.SubjectMeta; + + // To avoid the frontend processing and returning the footnotes unnecessarily, + // create a new view model with the footnotes added directly + var tableWithFootnotes = table with + { + Footnotes = subjectMeta.Footnotes + }; + + var permalink = new Permalink + { + ReleaseId = releaseId, + SubjectId = request.Query.SubjectId, + PublicationTitle = subjectMeta.PublicationName, + DataSetTitle = subjectMeta.SubjectName, + }; + _contentDbContext.Permalinks.Add(permalink); + + await UploadSnapshot(permalink: permalink, + observations: tableResult.Results.ToList(), + csvMeta: csvMeta, + table: tableWithFootnotes, + cancellationToken: cancellationToken); + + await _contentDbContext.SaveChangesAsync(cancellationToken); + return await BuildViewModel(permalink, tableWithFootnotes); + }); }); } @@ -306,6 +300,14 @@ private async Task BuildViewModel(Permalink permalink, Perma }; } + private async Task> FindLatestPublishedReleaseId(Guid subjectId) + { + return await _subjectRepository.FindPublicationIdForSubject(subjectId) + .OrNotFound() + .OnSuccess(publicationId => _releaseRepository.GetLatestPublishedRelease(publicationId)) + .OnSuccess(release => release.Id); + } + private async Task GetPermalinkStatus(Guid subjectId) { // TODO EES-3339 This doesn't currently include a status to warn if the footnotes have been amended on a Release, diff --git a/src/explore-education-statistics-common/src/modules/table-tool/components/__tests__/TimePeriodDataTable.test.tsx b/src/explore-education-statistics-common/src/modules/table-tool/components/__tests__/TimePeriodDataTable.test.tsx index 74c7aa9fd63..08c98a6bc9d 100644 --- a/src/explore-education-statistics-common/src/modules/table-tool/components/__tests__/TimePeriodDataTable.test.tsx +++ b/src/explore-education-statistics-common/src/modules/table-tool/components/__tests__/TimePeriodDataTable.test.tsx @@ -10,7 +10,7 @@ import { testDataNoFiltersTableHeadersConfig, } from '@common/modules/table-tool/components/__tests__/__data__/timePeriodDataTable.data'; import TimePeriodDataTable from '@common/modules/table-tool/components/TimePeriodDataTable'; -import { UnmappedTableHeadersConfig } from '@common/services/permalinkSnapshotService'; +import { UnmappedTableHeadersConfig } from '@common/services/permalinkService'; import { ReleaseTableDataQuery, TableDataResponse, diff --git a/src/explore-education-statistics-common/src/modules/table-tool/components/__tests__/__data__/timePeriodDataTable.data.tsx b/src/explore-education-statistics-common/src/modules/table-tool/components/__tests__/__data__/timePeriodDataTable.data.tsx index f3974e3f5dd..7da98d53d22 100644 --- a/src/explore-education-statistics-common/src/modules/table-tool/components/__tests__/__data__/timePeriodDataTable.data.tsx +++ b/src/explore-education-statistics-common/src/modules/table-tool/components/__tests__/__data__/timePeriodDataTable.data.tsx @@ -1,4 +1,4 @@ -import { UnmappedTableHeadersConfig } from '@common/services/permalinkSnapshotService'; +import { UnmappedTableHeadersConfig } from '@common/services/permalinkService'; import { TableDataResponse } from '@common/services/tableBuilderService'; export const testData1Table: TableDataResponse = { diff --git a/src/explore-education-statistics-common/src/modules/table-tool/utils/__tests__/mapTableHeadersConfig.test.ts b/src/explore-education-statistics-common/src/modules/table-tool/utils/__tests__/mapTableHeadersConfig.test.ts index b482ae4f761..580e8de904f 100644 --- a/src/explore-education-statistics-common/src/modules/table-tool/utils/__tests__/mapTableHeadersConfig.test.ts +++ b/src/explore-education-statistics-common/src/modules/table-tool/utils/__tests__/mapTableHeadersConfig.test.ts @@ -1,4 +1,4 @@ -import { UnmappedTableHeadersConfig } from '@common/services/permalinkSnapshotService'; +import { UnmappedTableHeadersConfig } from '@common/services/permalinkService'; import { CategoryFilter, Indicator, diff --git a/src/explore-education-statistics-common/src/modules/table-tool/utils/mapTableHeadersConfig.ts b/src/explore-education-statistics-common/src/modules/table-tool/utils/mapTableHeadersConfig.ts index f7a611c3b9f..d70f92621d4 100644 --- a/src/explore-education-statistics-common/src/modules/table-tool/utils/mapTableHeadersConfig.ts +++ b/src/explore-education-statistics-common/src/modules/table-tool/utils/mapTableHeadersConfig.ts @@ -4,7 +4,7 @@ import { TableHeadersConfig } from '@common/modules/table-tool/types/tableHeader import { TableHeader, UnmappedTableHeadersConfig, -} from '@common/services/permalinkSnapshotService'; +} from '@common/services/permalinkService'; /** * This function remaps the config filters into diff --git a/src/explore-education-statistics-common/src/modules/table-tool/utils/mapUnmappedTableHeaders.ts b/src/explore-education-statistics-common/src/modules/table-tool/utils/mapUnmappedTableHeaders.ts index fe61f7734f0..5606ec54b9d 100644 --- a/src/explore-education-statistics-common/src/modules/table-tool/utils/mapUnmappedTableHeaders.ts +++ b/src/explore-education-statistics-common/src/modules/table-tool/utils/mapUnmappedTableHeaders.ts @@ -9,7 +9,7 @@ import { TableHeadersConfig } from '@common/modules/table-tool/types/tableHeader import { TableHeader, UnmappedTableHeadersConfig, -} from '@common/services/permalinkSnapshotService'; +} from '@common/services/permalinkService'; const mapToTableHeaders = (filters: Filter[]): TableHeader[] => { return filters.map(filter => { diff --git a/src/explore-education-statistics-common/src/services/permalinkSnapshotService.ts b/src/explore-education-statistics-common/src/services/permalinkService.ts similarity index 81% rename from src/explore-education-statistics-common/src/services/permalinkSnapshotService.ts rename to src/explore-education-statistics-common/src/services/permalinkService.ts index 5b151a3c28c..fd0884f0437 100644 --- a/src/explore-education-statistics-common/src/services/permalinkSnapshotService.ts +++ b/src/explore-education-statistics-common/src/services/permalinkService.ts @@ -40,25 +40,26 @@ export interface PermalinkSnapshot { } interface CreatePermalink { + releaseId?: string; query: TableDataQuery; configuration: { tableHeaders: UnmappedTableHeadersConfig; }; } -const permalinkSnapshotService = { - createPermalink(query: CreatePermalink): Promise { - return dataApi.post(`/permalink-snapshot`, query); +const permalinkService = { + createPermalink(permalink: CreatePermalink): Promise { + return dataApi.post('/permalink', permalink); }, async getPermalink(id: string): Promise { - return dataApi.get(`/permalink-snapshot/${id}`, { + return dataApi.get(`/permalink/${id}`, { headers: { Accept: 'application/json', }, }); }, async getPermalinkCsv(id: string): Promise { - return dataApi.get(`/permalink-snapshot/${id}`, { + return dataApi.get(`/permalink/${id}`, { headers: { Accept: 'text/csv', }, @@ -67,4 +68,4 @@ const permalinkSnapshotService = { }, }; -export default permalinkSnapshotService; +export default permalinkService; diff --git a/src/explore-education-statistics-common/src/services/types/blocks.ts b/src/explore-education-statistics-common/src/services/types/blocks.ts index b76a9a22802..20e7a39ca36 100644 --- a/src/explore-education-statistics-common/src/services/types/blocks.ts +++ b/src/explore-education-statistics-common/src/services/types/blocks.ts @@ -4,7 +4,7 @@ import { AxisType, } from '@common/modules/charts/types/chart'; import { DataSet } from '@common/modules/charts/types/dataSet'; -import { UnmappedTableHeadersConfig } from '@common/services/permalinkSnapshotService'; +import { UnmappedTableHeadersConfig } from '@common/services/permalinkService'; import { TableDataQuery } from '@common/services/tableBuilderService'; import { OmitStrict } from '@common/types'; diff --git a/src/explore-education-statistics-common/src/services/types/table.ts b/src/explore-education-statistics-common/src/services/types/table.ts index cd7717e9476..0e1901bbb2f 100644 --- a/src/explore-education-statistics-common/src/services/types/table.ts +++ b/src/explore-education-statistics-common/src/services/types/table.ts @@ -1,4 +1,4 @@ -import { UnmappedTableHeadersConfig } from '@common/services/permalinkSnapshotService'; +import { UnmappedTableHeadersConfig } from '@common/services/permalinkService'; import { TableDataResponse } from '@common/services/tableBuilderService'; export interface ConfiguredTable { diff --git a/src/explore-education-statistics-frontend/src/modules/api/permalink/createPermalinkTable.ts b/src/explore-education-statistics-frontend/src/modules/api/permalink/createPermalinkTable.ts index 043363cfd82..3b63532682f 100644 --- a/src/explore-education-statistics-frontend/src/modules/api/permalink/createPermalinkTable.ts +++ b/src/explore-education-statistics-frontend/src/modules/api/permalink/createPermalinkTable.ts @@ -7,7 +7,7 @@ import generateTableTitle from '@common/modules/table-tool/utils/generateTableTi import logger from '@common/services/logger'; import { ErrorBody } from '@frontend/modules/api/types/error'; import type { NextApiRequest, NextApiResponse } from 'next'; -import { UnmappedTableHeadersConfig } from '@common/services/permalinkSnapshotService'; +import { UnmappedTableHeadersConfig } from '@common/services/permalinkService'; import { TableDataResponse } from '@common/services/tableBuilderService'; interface SuccessBody { diff --git a/src/explore-education-statistics-frontend/src/modules/permalink/PermalinkPage.tsx b/src/explore-education-statistics-frontend/src/modules/permalink/PermalinkPage.tsx index fc66fd2c286..9f05016a9c2 100644 --- a/src/explore-education-statistics-frontend/src/modules/permalink/PermalinkPage.tsx +++ b/src/explore-education-statistics-frontend/src/modules/permalink/PermalinkPage.tsx @@ -1,9 +1,9 @@ import FormattedDate from '@common/components/FormattedDate'; import WarningMessage from '@common/components/WarningMessage'; import DownloadTable from '@common/modules/table-tool/components/DownloadTable'; -import permalinkSnapshotService, { +import permalinkService, { PermalinkSnapshot, -} from '@common/services/permalinkSnapshotService'; +} from '@common/services/permalinkService'; import ButtonLink from '@frontend/components/ButtonLink'; import Page from '@frontend/components/Page'; import PrintThisPage from '@frontend/components/PrintThisPage'; @@ -102,9 +102,7 @@ const PermalinkPage: NextPage = ({ data }) => { headingTag="h2" tableRef={tableRef} tableTitle={caption} - onCsvDownload={() => - permalinkSnapshotService.getPermalinkCsv(data.id) - } + onCsvDownload={() => permalinkService.getPermalinkCsv(data.id)} onSubmit={fileFormat => logEvent({ category: 'Permalink page', @@ -134,7 +132,7 @@ export const getServerSideProps: GetServerSideProps = withAxiosHandler( async ({ query }) => { const { permalink } = query as Dictionary; - const data = await permalinkSnapshotService.getPermalink(permalink); + const data = await permalinkService.getPermalink(permalink); return { props: { diff --git a/src/explore-education-statistics-frontend/src/modules/permalink/__data__/testPermalinkData.ts b/src/explore-education-statistics-frontend/src/modules/permalink/__data__/testPermalinkData.ts index e055a3da374..ffb90eef1b7 100644 --- a/src/explore-education-statistics-frontend/src/modules/permalink/__data__/testPermalinkData.ts +++ b/src/explore-education-statistics-frontend/src/modules/permalink/__data__/testPermalinkData.ts @@ -1,4 +1,4 @@ -import { PermalinkSnapshot } from '@common/services/permalinkSnapshotService'; +import { PermalinkSnapshot } from '@common/services/permalinkService'; const testPermalinkSnapshot: PermalinkSnapshot = { created: '2020-10-07T12:00:00.00Z', diff --git a/src/explore-education-statistics-frontend/src/modules/table-tool/components/TableToolFinalStep.tsx b/src/explore-education-statistics-frontend/src/modules/table-tool/components/TableToolFinalStep.tsx index f44ab5d0f34..64670d95a04 100644 --- a/src/explore-education-statistics-frontend/src/modules/table-tool/components/TableToolFinalStep.tsx +++ b/src/explore-education-statistics-frontend/src/modules/table-tool/components/TableToolFinalStep.tsx @@ -12,13 +12,13 @@ import { TableHeadersConfig } from '@common/modules/table-tool/types/tableHeader import publicationService from '@common/services/publicationService'; import Link from '@frontend/components/Link'; import tableBuilderService, { - TableDataQuery, + ReleaseTableDataQuery, } from '@common/services/tableBuilderService'; import { logEvent } from '@frontend/services/googleAnalyticsService'; import React, { memo, ReactNode, useRef } from 'react'; interface TableToolFinalStepProps { - query: TableDataQuery; + query: ReleaseTableDataQuery; table: FullTable; tableHeaders: TableHeadersConfig; selectedPublication: SelectedPublication; diff --git a/src/explore-education-statistics-frontend/src/modules/table-tool/components/TableToolShare.tsx b/src/explore-education-statistics-frontend/src/modules/table-tool/components/TableToolShare.tsx index f8244730ff6..d03c8c61703 100644 --- a/src/explore-education-statistics-frontend/src/modules/table-tool/components/TableToolShare.tsx +++ b/src/explore-education-statistics-frontend/src/modules/table-tool/components/TableToolShare.tsx @@ -4,10 +4,12 @@ import ButtonGroup from '@common/components/ButtonGroup'; import LoadingSpinner from '@common/components/LoadingSpinner'; import ScreenReaderMessage from '@common/components/ScreenReaderMessage'; import UrlContainer from '@common/components/UrlContainer'; +import ErrorMessage from '@common/components/ErrorMessage'; import { TableHeadersConfig } from '@common/modules/table-tool/types/tableHeaders'; import mapUnmappedTableHeaders from '@common/modules/table-tool/utils/mapUnmappedTableHeaders'; -import permalinkSnapshotService from '@common/services/permalinkSnapshotService'; -import { TableDataQuery } from '@common/services/tableBuilderService'; +import logger from '@common/services/logger'; +import permalinkService from '@common/services/permalinkService'; +import { ReleaseTableDataQuery } from '@common/services/tableBuilderService'; import ButtonLink from '@frontend/components/ButtonLink'; import React, { useEffect, useState } from 'react'; @@ -16,12 +18,13 @@ const linkInstructions = interface Props { tableHeaders?: TableHeadersConfig; - query: TableDataQuery; + query: ReleaseTableDataQuery; } const TableToolShare = ({ tableHeaders, query }: Props) => { const [permalinkUrl, setPermalinkUrl] = useState(''); const [permalinkLoading, setPermalinkLoading] = useState(false); + const [permalinkError, setPermalinkError] = useState(); const [screenReaderMessage, setScreenReaderMessage] = useState(''); useEffect(() => { @@ -32,19 +35,31 @@ const TableToolShare = ({ tableHeaders, query }: Props) => { if (!tableHeaders) { return; } + setPermalinkError(undefined); setPermalinkLoading(true); - const { id } = await permalinkSnapshotService.createPermalink({ - query, - configuration: { - tableHeaders: mapUnmappedTableHeaders(tableHeaders), - }, - }); - - setPermalinkUrl(`${process.env.PUBLIC_URL}data-tables/permalink/${id}`); - setPermalinkLoading(false); - - setScreenReaderMessage(`Shareable link generated. ${linkInstructions}`); + const { releaseId } = query; + try { + const { id } = await permalinkService.createPermalink({ + releaseId, + query, + configuration: { + tableHeaders: mapUnmappedTableHeaders(tableHeaders), + }, + }); + + setPermalinkUrl(`${process.env.PUBLIC_URL}data-tables/permalink/${id}`); + + setScreenReaderMessage(`Shareable link generated. ${linkInstructions}`); + } catch (err) { + logger.error(err); + const errorMessage = 'There was a problem generating the share link.'; + + setPermalinkError(errorMessage); + setScreenReaderMessage(`Error: ${errorMessage}`); + } finally { + setPermalinkLoading(false); + } }; const handleCopyClick = () => { @@ -64,9 +79,13 @@ const TableToolShare = ({ tableHeaders, query }: Props) => { size="sm" text="Generating shareable link" > - - Generate shareable link - + {permalinkError ? ( + {permalinkError} + ) : ( + + Generate shareable link + + )} ) : ( diff --git a/src/explore-education-statistics-frontend/src/modules/table-tool/components/__tests__/TableToolShare.test.tsx b/src/explore-education-statistics-frontend/src/modules/table-tool/components/__tests__/TableToolShare.test.tsx index 33759c4914f..63c6fa244d9 100644 --- a/src/explore-education-statistics-frontend/src/modules/table-tool/components/__tests__/TableToolShare.test.tsx +++ b/src/explore-education-statistics-frontend/src/modules/table-tool/components/__tests__/TableToolShare.test.tsx @@ -1,25 +1,32 @@ import TableToolShare from '@frontend/modules/table-tool/components/TableToolShare'; +import mapUnmappedTableHeaders from '@common/modules/table-tool/utils/mapUnmappedTableHeaders'; import { testTableHeaders, testQuery, } from '@frontend/modules/table-tool/components/__tests__/__data__/tableData'; -import _permalinkSnapshotService, { +import _permalinkService, { PermalinkSnapshot, -} from '@common/services/permalinkSnapshotService'; +} from '@common/services/permalinkService'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; +import { ReleaseTableDataQuery } from '@common/services/tableBuilderService'; -jest.mock('@common/services/permalinkSnapshotService'); +jest.mock('@common/services/permalinkService'); -const permalinkSnapshotService = _permalinkSnapshotService as jest.Mocked< - typeof _permalinkSnapshotService +const permalinkService = _permalinkService as jest.Mocked< + typeof _permalinkService >; +const tableQuery: ReleaseTableDataQuery = { + releaseId: 'release-1', + ...testQuery, +}; + describe('TableToolShare', () => { test('renders the generate button', () => { render( - , + , ); expect(screen.getByText('Save table')).toBeInTheDocument(); @@ -29,11 +36,11 @@ describe('TableToolShare', () => { }); test('shows the share link when the button is clicked', async () => { - permalinkSnapshotService.createPermalink.mockResolvedValue({ + permalinkService.createPermalink.mockResolvedValue({ id: 'permalink-id', } as PermalinkSnapshot); render( - , + , ); userEvent.click( @@ -42,6 +49,18 @@ describe('TableToolShare', () => { }), ); + await waitFor(() => { + expect(permalinkService.createPermalink).toHaveBeenCalledWith< + Parameters + >({ + releaseId: 'release-1', + query: tableQuery, + configuration: { + tableHeaders: mapUnmappedTableHeaders(testTableHeaders), + }, + }); + }); + await waitFor(() => { expect(screen.getByText('Generated share link')).toBeInTheDocument(); }); @@ -72,12 +91,12 @@ describe('TableToolShare', () => { }); test('copies the link to the clipboard when the copy button is clicked', async () => { - permalinkSnapshotService.createPermalink.mockResolvedValue({ + permalinkService.createPermalink.mockResolvedValue({ id: 'permalink-id', } as PermalinkSnapshot); render( - , + , ); userEvent.click(