Skip to content

Commit

Permalink
Showing 21 changed files with 229 additions and 210 deletions.
Original file line number Diff line number Diff line change
@@ -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<T>(this HttpResponseMessage message, T expectedBody)
return message.AssertBodyEqualTo(expectedBody);
}

public static T AssertCreated<T>(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);
Original file line number Diff line number Diff line change
@@ -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<TestStartup> testApp)
public async Task CreatePermalink()
{
var createRequest = new PermalinkCreateRequest();
var expectedResult = new PermalinkViewModel();
var expectedResult = new PermalinkViewModel
{
Id = Guid.NewGuid()
};

var permalinkService = new Mock<IPermalinkService>(Strict);
var permalinkService = new Mock<IPermalinkService>(MockBehavior.Strict);

permalinkService
.Setup(s => s.CreatePermalink(It.Is<PermalinkCreateRequest>(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<IPermalinkService>(Strict);

permalinkService
.Setup(s => s.CreatePermalink(releaseId, It.Is<PermalinkCreateRequest>(r => r.IsDeepEqualTo(createRequest)),
It.IsAny<CancellationToken>()))
.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<IPermalinkService>(Strict);
var permalinkService = new Mock<IPermalinkService>(MockBehavior.Strict);

permalinkService
.Setup(s => s.GetPermalink(permalinkId, It.IsAny<CancellationToken>()))
@@ -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<string, string>
{
{ 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<IPermalinkService>(Strict);
var permalinkService = new Mock<IPermalinkService>(MockBehavior.Strict);

permalinkService
.Setup(s => s.GetPermalink(permalinkId, It.IsAny<CancellationToken>()))
@@ -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<string, string>
{
{ 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<IPermalinkService>(Strict);
var permalinkService = new Mock<IPermalinkService>(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<string, string>
{
{ 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<IPermalinkService>(Strict);
var permalinkService = new Mock<IPermalinkService>(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<string, string>
{
{ HeaderNames.Accept, ContentTypes.Csv }
}
);

VerifyAllMocks(permalinkService);
MockUtils.VerifyAllMocks(permalinkService);

response.AssertNotFound();
}
@@ -204,15 +180,18 @@ 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();
}

private WebApplicationFactory<TestStartup> SetupApp(IPermalinkService? permalinkService = null)
{
return _testApp.ConfigureServices(
services => { services.AddTransient(_ => permalinkService ?? Mock.Of<IPermalinkService>(Strict)); }
services =>
{
services.AddTransient(_ => permalinkService ?? Mock.Of<IPermalinkService>(MockBehavior.Strict));
}
);
}
}
Original file line number Diff line number Diff line change
@@ -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<IPublicBlobStorageService>(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<IPublicBlobStorageService>(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,
Original file line number Diff line number Diff line change
@@ -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<ActionResult<PermalinkViewModel>> 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<ActionResult<PermalinkViewModel>> 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));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
#nullable enable
using System;
using GovUk.Education.ExploreEducationStatistics.Common.Model.Data;
using GovUk.Education.ExploreEducationStatistics.Common.Model.Data.Query;

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();
Original file line number Diff line number Diff line change
@@ -15,10 +15,6 @@ public interface IPermalinkService
Task<Either<ActionResult, PermalinkViewModel>> CreatePermalink(PermalinkCreateRequest request,
CancellationToken cancellationToken = default);

Task<Either<ActionResult, PermalinkViewModel>> CreatePermalink(Guid releaseId,
PermalinkCreateRequest request,
CancellationToken cancellationToken = default);

Task<Either<ActionResult, PermalinkViewModel>> GetPermalink(Guid permalinkId,
CancellationToken cancellationToken = default);

Original file line number Diff line number Diff line change
@@ -86,75 +86,69 @@ private async Task<Either<ActionResult, Permalink>> Find(Guid permalinkId,
public async Task<Either<ActionResult, PermalinkViewModel>> 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<Either<ActionResult, PermalinkViewModel>> CreatePermalink(Guid releaseId,
PermalinkCreateRequest request,
CancellationToken cancellationToken = default)
{
return await _tableBuilderService.Query(releaseId, request.Query, cancellationToken)
.OnSuccess<ActionResult, TableBuilderResultViewModel, PermalinkViewModel>(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<ActionResult, TableBuilderResultViewModel, PermalinkViewModel>(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<PermalinkViewModel> BuildViewModel(Permalink permalink, Perma
};
}

private async Task<Either<ActionResult, Guid>> FindLatestPublishedReleaseId(Guid subjectId)
{
return await _subjectRepository.FindPublicationIdForSubject(subjectId)
.OrNotFound()
.OnSuccess(publicationId => _releaseRepository.GetLatestPublishedRelease(publicationId))
.OnSuccess(release => release.Id);
}

private async Task<PermalinkStatus> GetPermalinkStatus(Guid subjectId)
{
// TODO EES-3339 This doesn't currently include a status to warn if the footnotes have been amended on a Release,
Original file line number Diff line number Diff line change
@@ -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,
Original file line number Diff line number Diff line change
@@ -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 = {
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { UnmappedTableHeadersConfig } from '@common/services/permalinkSnapshotService';
import { UnmappedTableHeadersConfig } from '@common/services/permalinkService';
import {
CategoryFilter,
Indicator,
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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<TableHeader>(filter => {
Original file line number Diff line number Diff line change
@@ -40,25 +40,26 @@ export interface PermalinkSnapshot {
}

interface CreatePermalink {
releaseId?: string;
query: TableDataQuery;
configuration: {
tableHeaders: UnmappedTableHeadersConfig;
};
}

const permalinkSnapshotService = {
createPermalink(query: CreatePermalink): Promise<PermalinkSnapshot> {
return dataApi.post(`/permalink-snapshot`, query);
const permalinkService = {
createPermalink(permalink: CreatePermalink): Promise<PermalinkSnapshot> {
return dataApi.post('/permalink', permalink);
},
async getPermalink(id: string): Promise<PermalinkSnapshot> {
return dataApi.get(`/permalink-snapshot/${id}`, {
return dataApi.get(`/permalink/${id}`, {
headers: {
Accept: 'application/json',
},
});
},
async getPermalinkCsv(id: string): Promise<Blob> {
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;
Original file line number Diff line number Diff line change
@@ -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';

Original file line number Diff line number Diff line change
@@ -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 {
Original file line number Diff line number Diff line change
@@ -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 {
Original file line number Diff line number Diff line change
@@ -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<Props> = ({ 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<Props> = withAxiosHandler(
async ({ query }) => {
const { permalink } = query as Dictionary<string>;

const data = await permalinkSnapshotService.getPermalink(permalink);
const data = await permalinkService.getPermalink(permalink);

return {
props: {
Original file line number Diff line number Diff line change
@@ -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',
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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<boolean>(false);
const [permalinkError, setPermalinkError] = useState<string>();
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"
>
<ButtonText onClick={handlePermalinkClick}>
Generate shareable link
</ButtonText>
{permalinkError ? (
<ErrorMessage>{permalinkError}</ErrorMessage>
) : (
<ButtonText onClick={handlePermalinkClick}>
Generate shareable link
</ButtonText>
)}
</LoadingSpinner>
</>
) : (
Original file line number Diff line number Diff line change
@@ -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(
<TableToolShare query={testQuery} tableHeaders={testTableHeaders} />,
<TableToolShare query={tableQuery} tableHeaders={testTableHeaders} />,
);

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(
<TableToolShare query={testQuery} tableHeaders={testTableHeaders} />,
<TableToolShare query={tableQuery} tableHeaders={testTableHeaders} />,
);

userEvent.click(
@@ -42,6 +49,18 @@ describe('TableToolShare', () => {
}),
);

await waitFor(() => {
expect(permalinkService.createPermalink).toHaveBeenCalledWith<
Parameters<typeof permalinkService.createPermalink>
>({
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(
<TableToolShare query={testQuery} tableHeaders={testTableHeaders} />,
<TableToolShare query={tableQuery} tableHeaders={testTableHeaders} />,
);

userEvent.click(

0 comments on commit c38a080

Please sign in to comment.