Skip to content

Commit 9a5860b

Browse files
authored
Add UploadEvidence/EvidenceFileLink view components (#2623)
Introduces `UploadEvidence` and `EvidenceFileLink` view components to consolidate/unify upload evidence behaviour. Implemented initially in Add Person/Edit Details journeys for review, and then will roll out to other journeys once merged. Extracts out: * `EvidenceUploadModel` - the model fields involved in uploading an evidence file * `UploadedEvidenceFile` - a file that has already been uploaded and is being displayed * `EvidenceUploadManager` - contains the validation logic, uploads the file and clears down already uploaded files ### Limitations * `UploadEvidence` does not use `ModelExpression` to resolve model fields to bind to, so the form keys are simply the base (i.e. unprefixed) property names of `EvidenceUploadModel`, so the hosting `PageModel` should not contain any field names that clash with the following to avoid model binding issues: * `UploadEvidence` * `EvidenceFile` * `UploadedEvidenceFile` (including fields of `UploadedEvidenceFile` i.e. `UploadedEvidenceFile.FileId`, `UploadedEvidenceFile.FileName`, `UploadedEvidenceFile.FileSizeDescription`, `UploadedEvidenceFile.PreviewUrl`) * For the same reason, any `PageModel` cannot contain more than one `EvidenceUploadModel` as the default model binder will not be able to distinguish between them to model-bind to (will arbitrarily bind all instances to the last mentioned values in the form collection) I think these are reasonable limitations and unlikely to cause any issues with the page structure as it currently stands. ### Checklist - [x] Attach to Trello card - [x] Rebased master - [x] Cleaned commit history - [x] Tested by running locally
1 parent dfe7af0 commit 9a5860b

40 files changed

+763
-901
lines changed

TeachingRecordSystem/src/TeachingRecordSystem.Core/Services/Files/BlobStorageFileService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ namespace TeachingRecordSystem.Core.Services.Files;
88
public class BlobStorageFileService : IFileService
99
{
1010
private const string UploadsContainerName = "uploads";
11+
1112
private readonly BlobServiceClient _blobServiceClient;
1213
private readonly IHostEnvironment _hostEnvironment;
1314
private BlobContainerClient? _blobContainerClient;

TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Extensions.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using TeachingRecordSystem.SupportUi.Infrastructure.ModelBinding;
1919
using TeachingRecordSystem.SupportUi.Infrastructure.Security;
2020
using TeachingRecordSystem.SupportUi.Pages;
21+
using TeachingRecordSystem.SupportUi.Pages.Shared.Evidence;
2122
using TeachingRecordSystem.SupportUi.Services.AzureActiveDirectory;
2223
using TeachingRecordSystem.SupportUi.TagHelpers;
2324
using TeachingRecordSystem.WebCommon.Filters;
@@ -90,7 +91,9 @@ public static IServiceCollection AddSupportUiServices(this IServiceCollection se
9091
.AddTransient<RequireOpenAlertFilter>()
9192
.AddTransient<RedirectWithPersonIdFilter>()
9293
.AddSingleton<ITagHelperInitializer<FormTagHelper>, FormTagHelperInitializer>()
93-
.AddSingleton<ITagHelperInitializer<TextInputTagHelper>, TextInputTagHelperInitializer>();
94+
.AddSingleton<ITagHelperInitializer<TextInputTagHelper>, TextInputTagHelperInitializer>()
95+
.AddTransient<EvidenceUploadManager>()
96+
.AddScoped<SupportUiFormContext>();
9497

9598
if (environment.IsProduction())
9699
{

TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/AddPerson/AddPersonState.cs

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Text.Json.Serialization;
2+
using TeachingRecordSystem.SupportUi.Pages.Shared.Evidence;
23

34
namespace TeachingRecordSystem.SupportUi.Pages.Persons.AddPerson;
45

@@ -18,15 +19,17 @@ public class AddPersonState : IRegisterJourney
1819
public AddPersonFieldState<NationalInsuranceNumber> NationalInsuranceNumber { get; set; } = new("", null);
1920
public Gender? Gender { get; set; }
2021

21-
public AddPersonReasonOption? CreateReason { get; set; }
22-
public string? CreateReasonDetail { get; set; }
23-
public bool? UploadEvidence { get; set; }
24-
public Guid? EvidenceFileId { get; set; }
25-
public string? EvidenceFileName { get; set; }
26-
public string? EvidenceFileSizeDescription { get; set; }
22+
public AddPersonReasonOption? Reason { get; set; }
23+
public string? ReasonDetail { get; set; }
24+
public EvidenceUploadModel Evidence { get; set; } = new();
2725

2826
public bool Initialized { get; set; }
2927

28+
[JsonIgnore]
29+
public bool IsComplete =>
30+
IsPersonalDetailsComplete &&
31+
IsCreateReasonComplete;
32+
3033
[JsonIgnore]
3134
public bool IsPersonalDetailsComplete =>
3235
FirstName is not null &&
@@ -35,15 +38,9 @@ LastName is not null &&
3538

3639
[JsonIgnore]
3740
public bool IsCreateReasonComplete =>
38-
CreateReason.HasValue &&
39-
(CreateReason.Value is not AddPersonReasonOption.AnotherReason || CreateReasonDetail is not null) &&
40-
UploadEvidence.HasValue &&
41-
(UploadEvidence.Value is not true || EvidenceFileId.HasValue);
42-
43-
[JsonIgnore]
44-
public bool IsComplete =>
45-
IsPersonalDetailsComplete &&
46-
IsCreateReasonComplete;
41+
Reason.HasValue &&
42+
(Reason is not AddPersonReasonOption.AnotherReason || ReasonDetail is not null) &&
43+
Evidence.IsComplete;
4744

4845
public void EnsureInitialized()
4946
{

TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/AddPerson/CheckAnswers.cshtml

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@
6161
<govuk-summary-list>
6262
<govuk-summary-list-row>
6363
<govuk-summary-list-row-key>Reason for creating record</govuk-summary-list-row-key>
64-
<govuk-summary-list-row-value>@Model.CreateReason?.GetDisplayName()</govuk-summary-list-row-value>
64+
<govuk-summary-list-row-value>@Model.Reason?.GetDisplayName()</govuk-summary-list-row-value>
6565
<govuk-summary-list-row-actions>
66-
<govuk-summary-list-row-action data-testid="change-create-reason-link" href=@Model.CreateReasonLink visually-hidden-text="reason">Change</govuk-summary-list-row-action>
66+
<govuk-summary-list-row-action data-testid="change-create-reason-link" href=@Model.ChangeReasonLink visually-hidden-text="reason">Change</govuk-summary-list-row-action>
6767
</govuk-summary-list-row-actions>
6868
</govuk-summary-list-row>
6969
<govuk-summary-list-row>
@@ -79,23 +79,16 @@
7979
}
8080
</govuk-summary-list-row-value>
8181
<govuk-summary-list-row-actions>
82-
<govuk-summary-list-row-action href=@Model.CreateReasonLink visually-hidden-text="reason details">Change</govuk-summary-list-row-action>
82+
<govuk-summary-list-row-action href=@Model.ChangeReasonLink visually-hidden-text="reason details">Change</govuk-summary-list-row-action>
8383
</govuk-summary-list-row-actions>
8484
</govuk-summary-list-row>
8585
<govuk-summary-list-row>
8686
<govuk-summary-list-row-key>Evidence</govuk-summary-list-row-key>
8787
<govuk-summary-list-row-value>
88-
@if (Model.EvidenceFileUrl is not null)
89-
{
90-
<a href="@Model.EvidenceFileUrl" class="govuk-link" rel="noreferrer noopener" target="_blank">@($"{Model.EvidenceFileName} (opens in new tab)")</a>
91-
}
92-
else
93-
{
94-
<span use-empty-fallback></span>
95-
}
88+
<vc:evidence-file-link evidence-file="@Model.EvidenceFile" />
9689
</govuk-summary-list-row-value>
9790
<govuk-summary-list-row-actions>
98-
<govuk-summary-list-row-action href=@Model.CreateReasonLink visually-hidden-text="evidence">Change</govuk-summary-list-row-action>
91+
<govuk-summary-list-row-action href=@Model.ChangeReasonLink visually-hidden-text="evidence">Change</govuk-summary-list-row-action>
9992
</govuk-summary-list-row-actions>
10093
</govuk-summary-list-row>
10194
</govuk-summary-list>

TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/AddPerson/CheckAnswers.cshtml.cs

Lines changed: 29 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
using TeachingRecordSystem.Core.DataStore.Postgres;
44
using TeachingRecordSystem.Core.DataStore.Postgres.Models;
55
using TeachingRecordSystem.Core.Events.Legacy;
6-
using TeachingRecordSystem.Core.Services.Files;
76
using TeachingRecordSystem.Core.Services.TrnGeneration;
8-
using TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail.EditDetails;
7+
using TeachingRecordSystem.SupportUi.Pages.Shared.Evidence;
98

109
namespace TeachingRecordSystem.SupportUi.Pages.Persons.AddPerson;
1110

@@ -14,9 +13,9 @@ public class CheckAnswersModel(
1413
TrsLinkGenerator linkGenerator,
1514
TrsDbContext dbContext,
1615
IClock clock,
17-
IFileService fileService,
18-
ITrnGenerator trnGenerator)
19-
: CommonJourneyPage(dbContext, linkGenerator, fileService)
16+
ITrnGenerator trnGenerator,
17+
EvidenceUploadManager evidenceController)
18+
: CommonJourneyPage(dbContext, linkGenerator, evidenceController)
2019
{
2120
public string? FirstName { get; set; }
2221
public string? MiddleName { get; set; }
@@ -25,24 +24,40 @@ public class CheckAnswersModel(
2524
public EmailAddress? EmailAddress { get; set; }
2625
public NationalInsuranceNumber? NationalInsuranceNumber { get; set; }
2726
public Gender? Gender { get; set; }
28-
public EditDetailsNameChangeReasonOption? NameChangeReason { get; set; }
29-
public AddPersonReasonOption? CreateReason { get; set; }
27+
public AddPersonReasonOption? Reason { get; set; }
3028
public string? ReasonDetail { get; set; }
31-
public Guid? EvidenceFileId { get; set; }
32-
public string? EvidenceFileName { get; set; }
33-
public string? EvidenceFileSizeDescription { get; set; }
34-
public string? EvidenceFileUrl { get; set; }
29+
public UploadedEvidenceFile? EvidenceFile { get; set; }
3530

3631
public string Name => StringHelper.JoinNonEmpty(' ', FirstName, MiddleName, LastName);
3732

3833
public string? ChangePersonalDetailsLink =>
3934
GetPageLink(AddPersonJourneyPage.PersonalDetails, true);
4035

41-
public string? CreateReasonLink =>
36+
public string? ChangeReasonLink =>
4237
GetPageLink(AddPersonJourneyPage.Reason, true);
4338

4439
public string BackLink => GetPageLink(AddPersonJourneyPage.Reason);
4540

41+
public override void OnPageHandlerExecuting(PageHandlerExecutingContext context)
42+
{
43+
if (!JourneyInstance!.State.IsComplete && NextIncompletePage < AddPersonJourneyPage.CheckAnswers)
44+
{
45+
context.Result = Redirect(GetPageLink(NextIncompletePage));
46+
return;
47+
}
48+
49+
FirstName = JourneyInstance!.State.FirstName;
50+
MiddleName = JourneyInstance.State.MiddleName;
51+
LastName = JourneyInstance.State.LastName;
52+
DateOfBirth = JourneyInstance.State.DateOfBirth;
53+
EmailAddress = JourneyInstance.State.EmailAddress.Parsed;
54+
NationalInsuranceNumber = JourneyInstance.State.NationalInsuranceNumber.Parsed;
55+
Gender = JourneyInstance.State.Gender;
56+
Reason = JourneyInstance.State.Reason;
57+
ReasonDetail = JourneyInstance.State.ReasonDetail;
58+
EvidenceFile = JourneyInstance.State.Evidence.UploadedEvidenceFile;
59+
}
60+
4661
public void OnGet()
4762
{
4863
}
@@ -71,15 +86,9 @@ public async Task<IActionResult> OnPostAsync()
7186
RaisedBy = User.GetUserId(),
7287
PersonId = person.PersonId,
7388
PersonAttributes = personAttributes,
74-
CreateReason = CreateReason?.GetDisplayName(),
89+
CreateReason = Reason?.GetDisplayName(),
7590
CreateReasonDetail = ReasonDetail,
76-
EvidenceFile = EvidenceFileId is Guid detailsFileId
77-
? new EventModels.File()
78-
{
79-
FileId = detailsFileId,
80-
Name = EvidenceFileName!
81-
}
82-
: null,
91+
EvidenceFile = EvidenceFile?.ToEventModel(),
8392
TrnRequestMetadata = null
8493
};
8594

@@ -95,28 +104,4 @@ public async Task<IActionResult> OnPostAsync()
95104

96105
return Redirect(LinkGenerator.PersonDetail(person.PersonId));
97106
}
98-
99-
protected override async Task OnPageHandlerExecutingAsync(PageHandlerExecutingContext context)
100-
{
101-
if (!JourneyInstance!.State.IsComplete && NextIncompletePage < AddPersonJourneyPage.CheckAnswers)
102-
{
103-
context.Result = Redirect(GetPageLink(NextIncompletePage));
104-
return;
105-
}
106-
107-
FirstName = JourneyInstance!.State.FirstName;
108-
MiddleName = JourneyInstance.State.MiddleName;
109-
LastName = JourneyInstance.State.LastName;
110-
DateOfBirth = JourneyInstance.State.DateOfBirth;
111-
EmailAddress = JourneyInstance.State.EmailAddress.Parsed;
112-
NationalInsuranceNumber = JourneyInstance.State.NationalInsuranceNumber.Parsed;
113-
Gender = JourneyInstance.State.Gender;
114-
CreateReason = JourneyInstance.State.CreateReason;
115-
ReasonDetail = JourneyInstance.State.CreateReasonDetail;
116-
EvidenceFileId = JourneyInstance.State.EvidenceFileId;
117-
EvidenceFileName = JourneyInstance.State.EvidenceFileName;
118-
EvidenceFileUrl = JourneyInstance.State.EvidenceFileId is not null ?
119-
await FileService.GetFileUrlAsync(JourneyInstance.State.EvidenceFileId.Value, FileUploadDefaults.FileUrlExpiry) :
120-
null;
121-
}
122107
}

TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/AddPerson/CommonJourneyPage.cs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,27 @@
22
using Microsoft.AspNetCore.Mvc.Filters;
33
using Microsoft.AspNetCore.Mvc.RazorPages;
44
using TeachingRecordSystem.Core.DataStore.Postgres;
5-
using TeachingRecordSystem.Core.Services.Files;
5+
using TeachingRecordSystem.SupportUi.Pages.Shared.Evidence;
66

77
namespace TeachingRecordSystem.SupportUi.Pages.Persons.AddPerson;
88

99
public abstract class CommonJourneyPage(
1010
TrsDbContext dbContext,
1111
TrsLinkGenerator linkGenerator,
12-
IFileService fileService) : PageModel
12+
EvidenceUploadManager evidenceController) : PageModel
1313
{
1414
public JourneyInstance<AddPersonState>? JourneyInstance { get; set; }
1515

1616
protected TrsDbContext DbContext { get; } = dbContext;
1717
protected TrsLinkGenerator LinkGenerator { get; } = linkGenerator;
18-
protected IFileService FileService { get; } = fileService;
18+
protected EvidenceUploadManager EvidenceController { get; } = evidenceController;
1919

2020
[FromQuery]
2121
public bool FromCheckAnswers { get; set; }
2222

2323
public async Task<IActionResult> OnPostCancelAsync()
2424
{
25-
if (JourneyInstance!.State.EvidenceFileId.HasValue)
26-
{
27-
await FileService.DeleteFileAsync(JourneyInstance!.State.EvidenceFileId.Value);
28-
}
29-
25+
await EvidenceController.DeleteUploadedFileAsync(JourneyInstance!.State.Evidence.UploadedEvidenceFile);
3026
await JourneyInstance!.DeleteAsync();
3127
return Redirect(LinkGenerator.PersonCreate());
3228
}
@@ -66,9 +62,9 @@ public override async Task OnPageHandlerExecutionAsync(PageHandlerExecutingConte
6662
}
6763
}
6864

69-
protected virtual Task OnPageHandlerExecutingAsync(PageHandlerExecutingContext context)
65+
public virtual Task OnPageHandlerExecutingAsync(PageHandlerExecutingContext context)
7066
=> Task.CompletedTask;
7167

72-
protected virtual Task OnPageHandlerExecutedAsync(PageHandlerExecutedContext context)
68+
public virtual Task OnPageHandlerExecutedAsync(PageHandlerExecutedContext context)
7369
=> Task.CompletedTask;
7470
}

TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/AddPerson/PersonalDetails.cshtml.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
using Microsoft.AspNetCore.Mvc;
33
using TeachingRecordSystem.Core.DataStore.Postgres;
44
using TeachingRecordSystem.Core.DataStore.Postgres.Models;
5-
using TeachingRecordSystem.Core.Services.Files;
5+
using TeachingRecordSystem.SupportUi.Pages.Shared.Evidence;
66

77
namespace TeachingRecordSystem.SupportUi.Pages.Persons.AddPerson;
88

@@ -11,8 +11,8 @@ public class PersonalDetailsModel(
1111
TrsDbContext dbContext,
1212
IClock clock,
1313
TrsLinkGenerator linkGenerator,
14-
IFileService fileService)
15-
: CommonJourneyPage(dbContext, linkGenerator, fileService)
14+
EvidenceUploadManager evidenceController)
15+
: CommonJourneyPage(dbContext, linkGenerator, evidenceController)
1616
{
1717
[BindProperty]
1818
[Display(Name = "First name")]

TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/AddPerson/Reason.cshtml

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<div class="govuk-grid-column-full">
1414
<form action=@LinkGenerator.PersonCreateCreateReason(Model.JourneyInstance!.InstanceId, Model.FromCheckAnswers) method="post" data-testid="submit-form">
1515
<span class="govuk-caption-l" data-testid="create-reason-caption">Create a record</span>
16-
<govuk-radios for="CreateReason" data-testid="create-reason-options">
16+
<govuk-radios for="Reason" data-testid="create-reason-options">
1717
<govuk-radios-fieldset>
1818
<govuk-radios-fieldset-legend class="govuk-fieldset__legend--l" is-page-heading="true" data-testid="create-reason-options-legend"/>
1919
<govuk-radios-item value="@AddPersonReasonOption.MandatoryQualification">
@@ -29,42 +29,13 @@
2929
@AddPersonReasonOption.AnotherReason.GetDisplayName()
3030

3131
<govuk-radios-item-conditional data-testid="create-reason-detail">
32-
<govuk-character-count for="CreateReasonDetail" max-length="FileUploadDefaults.DetailMaxCharacterCount" rows="FileUploadDefaults.DetailTextAreaMinimumRows" />
32+
<govuk-character-count for="ReasonDetail" max-length="FileUploadDefaults.DetailMaxCharacterCount" rows="FileUploadDefaults.DetailTextAreaMinimumRows" />
3333
</govuk-radios-item-conditional>
3434
</govuk-radios-item>
3535
</govuk-radios-fieldset>
3636
</govuk-radios>
3737

38-
<govuk-radios for="UploadEvidence" data-testid="upload-evidence-options">
39-
<govuk-radios-fieldset>
40-
<govuk-radios-fieldset-legend class="govuk-fieldset__legend--m" data-testid="upload-evidence-options-legend" />
41-
<govuk-radios-item value="@true">
42-
Yes
43-
<govuk-radios-item-conditional>
44-
@if (Model.EvidenceFileId is not null)
45-
{
46-
<span class="govuk-caption-m">Currently uploaded file</span>
47-
<p class="govuk-body">
48-
<a href="@Model.EvidenceFileUrl" class="govuk-link" rel="noreferrer noopener" target="_blank" data-testid="uploaded-evidence-file-link">@($"{Model.EvidenceFileName} ({Model.EvidenceFileSizeDescription})")</a>
49-
</p>
50-
<input type="hidden" asp-for="EvidenceFileId" />
51-
<input type="hidden" asp-for="EvidenceFileName" />
52-
<input type="hidden" asp-for="EvidenceFileSizeDescription" />
53-
<input type="hidden" asp-for="EvidenceFileUrl" />
54-
}
55-
<govuk-file-upload for="EvidenceFile"
56-
label-class="govuk-label--m"
57-
input-accept=".bmp, .csv, .doc, .docx, .eml, .jpeg, .jpg, .mbox, .msg, .ods, .odt, .pdf, .png, .tif, .txt, .xls, .xlsx">
58-
<govuk-file-upload-label>Upload a file</govuk-file-upload-label>
59-
<govuk-file-upload-hint>Must be smaller than 50MB</govuk-file-upload-hint>
60-
</govuk-file-upload>
61-
</govuk-radios-item-conditional>
62-
</govuk-radios-item>
63-
<govuk-radios-item value="@false">
64-
No
65-
</govuk-radios-item>
66-
</govuk-radios-fieldset>
67-
</govuk-radios>
38+
<vc:upload-evidence evidence="@Model.Evidence" expression="Evidence" />
6839

6940
<div class="govuk-button-group">
7041
<govuk-button type="submit" data-testid="continue-button">Continue</govuk-button>

0 commit comments

Comments
 (0)