Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MLPAB-1473: Add holding modal while reduced fee evidence documents are scanned #805

Merged
merged 36 commits into from
Nov 2, 2023

Conversation

acsauk
Copy link
Contributor

@acsauk acsauk commented Oct 27, 2023

Purpose

Adds a holding modal that initiates a SSE connection to monitor when files are scanned.

I also had to add some optimistic locking to dynamo PUTs as the virus scan results were sometimes arriving within milliseconds creating a race condition that was overwriting data. I've added 1 automatic retry to the lambda to try to mitigate this where we can but we'll need to see how this works to see if we need to rethink how we save the data (maybe to separate rows like the evidence-received event?) I've moved what was called Evidence on the Lpa into Documents on individual rows to account for this.

I also found the spinner component in the Home Office design system but it felt like overkill to install the whole design system for what is a few lines of CSS.

Fixes MLPAB-1473

@codecov
Copy link

codecov bot commented Oct 27, 2023

Codecov Report

Attention: 62 lines in your changes are missing coverage. Please review.

Files Coverage Δ
internal/app/app.go 86.66% <100.00%> (+0.36%) ⬆️
internal/app/donor_store.go 92.89% <100.00%> (+0.59%) ⬆️
internal/page/data.go 92.41% <ø> (-0.80%) ⬇️
internal/page/document.go 100.00% <100.00%> (ø)
internal/page/donor/payment_confirmation.go 100.00% <100.00%> (+8.16%) ⬆️
internal/page/donor/register.go 100.00% <100.00%> (ø)
internal/page/donor/upload_evidence.go 88.50% <100.00%> (+0.12%) ⬆️
internal/page/donor/upload_evidence_sse.go 100.00% <100.00%> (ø)
internal/page/paths.go 100.00% <ø> (ø)
internal/validation/validation.go 97.56% <ø> (ø)
... and 6 more

📢 Thoughts on this report? Let us know!.

@github-actions
Copy link

github-actions bot commented Oct 27, 2023

PR Environment Terraform Plan Summary

Plan: 1 to add, 4 to change, 1 to destroy

@acsauk acsauk temporarily deployed to dev_805mlpab147 October 27, 2023 12:54 — with GitHub Actions Inactive
@acsauk acsauk temporarily deployed to dev_805mlpab147 October 29, 2023 15:01 — with GitHub Actions Inactive
@acsauk acsauk marked this pull request as ready for review October 30, 2023 08:41
@acsauk acsauk requested a review from a team as a code owner October 30, 2023 08:41
@acsauk acsauk temporarily deployed to dev_805mlpab147 October 30, 2023 09:08 — with GitHub Actions Inactive
internal/page/donor/upload_evidence.go Outdated Show resolved Hide resolved
@@ -840,5 +844,9 @@
"lpaWithdrawn": "LPA withdrawn",
"youHaveWithdrawnLpaNumber": "You have withdrawn LPA number <span class=\"govuk-!-font-weight-bold\">{{.UID}}</span>.",
"opgWillNowContactAnyoneWhoHasAlreadyBeenContacted": "OPG will now contact anyone who has already been contacted in relation to your LPA and notify them of your decision to withdraw.",
"withdrawn": "Withdrawn"
"withdrawn": "Withdrawn",
"yourFilesAreUploading": "Your files are uploading",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

the wording on this page is now weird, "Your files are uploading" even though they are in a section called "Uploaded files"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed this - it will make more sense to have the modal appear when clicking upload files rather than continue.

@hawx
Copy link
Contributor

hawx commented Oct 30, 2023

I also think it would be nice if it were stored in separate rows, I think you'd be able to simplify the polling then instead of getting the Lpa each time

@acsauk acsauk temporarily deployed to dev_805mlpab147 October 31, 2023 22:25 — with GitHub Actions Inactive
@acsauk acsauk temporarily deployed to dev_805mlpab147 November 1, 2023 11:09 — with GitHub Actions Inactive
@acsauk acsauk temporarily deployed to dev_805mlpab147 November 1, 2023 12:50 — with GitHub Actions Inactive
@@ -55,6 +55,11 @@ type shareCodeSender interface {
SendCertificateProvider(context.Context, notify.Template, page.AppData, bool, *page.Lpa) error
}

//go:generate mockery --testonly --inpackage --name DocumentStore --structname mockDocumentStore
type DocumentStore interface {
UpdateScanResults(ctx context.Context, PK, SK string, virusDetected bool) error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keep pk, sk lowercase


if err := client.Put(ctx, lpa); err != nil {
return fmt.Errorf("failed to update LPA for '%s': %w", objectTagsAddedEventName, err)
err = documentStore.UpdateScanResults(ctx, lpaKey.PK, "#DOCUMENT#"+objectKey, hasVirus)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to push #DOCUMENT# into the store, so outside only has to deal with IDs and not how it is stored in dynamo. And the same with changing PK to be the Lpa ID (even though it is awkward)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much simpler - I was going around in circles with circular dependencies but makes more sense to hide the detail.

@@ -49,13 +50,16 @@ type DynamoClient interface {
Put(ctx context.Context, v interface{}) error
Create(ctx context.Context, v interface{}) error
DeleteKeys(ctx context.Context, keys []dynamo.Key) error
DeleteOne(ctx context.Context, key dynamo.Key) error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe I should have made it DeleteKeys(ctx context.Context, keys ...dynamo.Key)

@@ -49,13 +50,16 @@ type DynamoClient interface {
Put(ctx context.Context, v interface{}) error
Create(ctx context.Context, v interface{}) error
DeleteKeys(ctx context.Context, keys []dynamo.Key) error
DeleteOne(ctx context.Context, key dynamo.Key) error
Update(ctx context.Context, input *dynamodb.UpdateItemInput) error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about exposing the dynamodb type here, we avoid it in the other signatures. Would Update(ctx context.Context, pk, sk string, values map[string]types.AttributeValue, expression string) error be any nicer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That works for me - slightly less boilerplate code to be passing in.

}

func NewDocumentStore(dynamoClient DynamoClient, s3Client S3Client) DocumentStore {
return documentStore{dynamoClient: dynamoClient, s3Client: s3Client}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably makes sense to have this return &documentStore and change all the receivers to (s *documentStore)

if document.Sent.IsZero() && !document.Scanned {
document.Sent = s.now()
if err := s.documentStore.Put(ctx, document, nil); err != nil {
return err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't be returning errors from here. It should just fail, log, and retry next time

Copy link
Contributor

@hawx hawx Nov 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also would be nice to batch these changes to documents up (but don't worry if it ends up being too much)

var updatedAtTime time.Time
err = attributevalue.Unmarshal(updatedAt, &updatedAtTime)
// Tracking Value equality against data on write allows for optimistic locking
if currentVersion, exists := item["Version"]; exists {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we still need this if documents are split out?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's still a (admittedly small) chance that an LPA could be updated at the same time by a donor and an event coming in. Maybe we could: look at seeing if there's a way to restructure all event data persistence to rows to be safe or stick with optimistic locking OR remove and monitor once we go live 🤷

@@ -177,56 +177,101 @@ type Lpa struct {
HasSentPreviousApplicationLinkedEvent bool
}

type Evidence struct {
Documents []Document
type Document struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could all the document related stuff live in a new file

for i, evidence := range lpa.Evidence.Documents {
if evidence.Sent.IsZero() {
err := evidenceS3Client.PutObjectTagging(r.Context(), evidence.Key, []types.Tag{
documents, err := documentStore.GetAll(r.Context())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need all of this since it happens in payHelper.Pay?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does, but not for HalfFee - we wanted to be sure that the payment had been successful before tagging the docs as being ready for replication hence the replication.

Filename: file.Filename,
Key: key,
}
if err := documentStore.Put(r.Context(), document, file.Data); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be nice to provide a documentStore.Create(ctx, lpa.ID, key, file.Filename) method, then you won't have LPA# and #DOCUMENT# exposed here

var lpaKey dynamo.Key
if err := dynamodbClient.OneByUID(ctx, parts[0], &lpaKey); err != nil {
return fmt.Errorf("failed to resolve uid for '%s': %w", objectTagsAddedEventName, err)
lpa, err := getLpaByUID(ctx, dynamodbClient, parts[0], objectTagsAddedEventName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it worth introducing another dynamo call vs _, lpaID, _ := strings.Cut(lpaKey.PK, "#")

}

return []page.Document{}, err
if err := s.dynamoClient.AllByPartialSk(ctx, lpaKey(data.LpaID), documentKey(""), &ds); err != nil && !errors.Is(err, dynamo.NotFoundError{}) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't thought before, but AllByPartialSk doesn't return dynamo.NotFoundError, we only do that in the One... methods

return s.dynamoClient.Update(ctx, input)
}
toWrite := len(converted)
written, err := s.dynamoClient.BatchPut(ctx, converted)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I shouldn't have mentioned this, it was fine before. For batch stuff I think TransactWriteItem is better because it either works or fails

@acsauk acsauk merged commit eb7e9b4 into main Nov 2, 2023
24 checks passed
@acsauk acsauk deleted the MLPAB-1473 branch November 2, 2023 10:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants