From ea05072756cec10a9699a3d80cce6ca8a1fb6245 Mon Sep 17 00:00:00 2001 From: James Person Date: Thu, 15 Jun 2023 09:13:06 -0400 Subject: [PATCH 01/10] upload page - useful errors --- backend/audit/views.py | 9 +++- .../report_submission/upload-page.html | 42 ++++++++-------- backend/static/js/upload-page.js | 49 +++++++++++++++---- 3 files changed, 68 insertions(+), 32 deletions(-) diff --git a/backend/audit/views.py b/backend/audit/views.py index 6f4a4269ac..caf6a2cbfa 100644 --- a/backend/audit/views.py +++ b/backend/audit/views.py @@ -9,6 +9,7 @@ from django.urls import reverse from django.utils.datastructures import MultiValueDictKeyError from django.utils.decorators import method_decorator +from django.http import JsonResponse from .fixtures.excel import ( FEDERAL_AWARDS_EXPENDED, @@ -137,11 +138,15 @@ def post(self, request, *args, **kwargs): logger.warn(f"no SingleAuditChecklist found with report ID {report_id}") raise PermissionDenied() except ValidationError as e: + # The good error, where bad rows/columns are sent back in the request. logger.warn(f"{form_section} Excel upload failed validation: {e}") - raise BadRequest(e) + return JsonResponse({"errors": list(e)}, status=400) except MultiValueDictKeyError: - logger.warn("no file found in request") + logger.warn(f"no file found in request") raise BadRequest() + except KeyError as e: + logger.warn(f"Field error. Field: {e}") + raise BadRequest(e) class ReadyForCertificationView(SingleAuditChecklistAccessRequiredMixin, generic.View): diff --git a/backend/report_submission/templates/report_submission/upload-page.html b/backend/report_submission/templates/report_submission/upload-page.html index bfb5ef7935..2c02f46626 100644 --- a/backend/report_submission/templates/report_submission/upload-page.html +++ b/backend/report_submission/templates/report_submission/upload-page.html @@ -1,24 +1,24 @@ {% extends "base.html" %} {% load static %} {% block content %} -
-
- -
-
+
+
+ +
+
{% comment %} {% include "../sidenav.html" %} {% endcomment %}
+ id="{{ view_id }}" + id_test="upload-page__form" + method="post"> {% csrf_token %}
Upload completed worksheet

Save your completed worksheet as an XLSX file and upload it below.

+ class="usa-file-input" + id_test="upload-page__file-input" + type="file" + name="file-input-{{ view_id }}-xlsx" + aria-describedby="file-input-{{ view_id }}-xlsx" + accept=".xlsx"/>
{% if already_submitted %}

A file has already been uploaded for this section. A successful reupload will overwrite your previous submission.

{% endif %} - +
diff --git a/backend/static/js/upload-page.js b/backend/static/js/upload-page.js index 4a8fe84bc5..00ecf114e4 100644 --- a/backend/static/js/upload-page.js +++ b/backend/static/js/upload-page.js @@ -36,12 +36,45 @@ function handleUploadErrors(error) { JSON.parse(errorMessage) ); info_box.innerHTML = - 'Error when uploading file. See the console for more information.'; + 'Error when uploading file. See the console for more information, or contact an administrator.'; }); } else if (error.name === 'AbortError') { console.error(`Timeout - Response took longer than expected.\n`, error); - info_box.innerHTML = `Timeout - Response took longer than expected.`; - } else console.error(`Unexpected error.\n`, error); + info_box.innerHTML = `Timeout - Response took longer than expected. Try again later.`; + } else { + info_box.innerHTML = + 'Error when uploading file. Ensure you have upload the correct template, or contact an administrator.'; + console.error(`Unexpected error.\n`, error); + } +} + +function getValidationErrorsTable(res){ + var error_rows = "" + for (let i = 0; i < res.errors.length; i++) { + row_array = res.errors[i].replaceAll(/[\(\)']/g, ` `).split(',') + error_rows += ` + + ${row_array[0]}

+ ${row_array[1]}

+ ${row_array[2]}

+ `; + } + const validationTable = + `

Error on validation. Check the following cells for errors, and re-upload. + Common errors include incorrect data types or missing information.

+ + + + + + + + + + ${error_rows} + +
RowColumnField
` + return validationTable } // On file upload, send it off for verification. @@ -78,16 +111,14 @@ function attachFileUploadHandler() { method: 'POST', body: data, }) - .then((response) => { - if (response.status == 200) { + .then((res) => res.json()) + .then((res) => { + if (res.status == 200) { info_box.innerHTML = 'File successfully validated! Your work has been saved.'; setContinueButtonDisabled(false); } else { - // TODO: Handle helpful validation errors here - console.error('Error when validating excel file.\n', response); - info_box.innerHTML = - 'Error on validation. See the console for more information.'; + info_box.innerHTML = getValidationErrorsTable(res) } }) .catch((error) => { From fc2352e3ef412127503af052b0f72691bcccbdfd Mon Sep 17 00:00:00 2001 From: James Person Date: Thu, 15 Jun 2023 09:29:57 -0400 Subject: [PATCH 02/10] Linting! Yay! --- .../report_submission/upload-page.html | 27 ++++++++++--------- backend/static/js/upload-page.js | 22 +++++++-------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/backend/report_submission/templates/report_submission/upload-page.html b/backend/report_submission/templates/report_submission/upload-page.html index 2c02f46626..0361de363b 100644 --- a/backend/report_submission/templates/report_submission/upload-page.html +++ b/backend/report_submission/templates/report_submission/upload-page.html @@ -5,8 +5,9 @@
@@ -16,9 +17,9 @@
{% comment %} {% include "../sidenav.html" %} {% endcomment %}
+ id="{{ view_id }}" + id_test="upload-page__form" + method="post"> {% csrf_token %}
Upload completed worksheet

Save your completed worksheet as an XLSX file and upload it below.

+ class="usa-file-input" + id_test="upload-page__file-input" + type="file" + name="file-input-{{ view_id }}-xlsx" + aria-describedby="file-input-{{ view_id }}-xlsx" + accept=".xlsx"/>
{% if already_submitted %} -

A file has already been uploaded for this section. A successful reupload will overwrite your previous submission.

+

+ A file has already been uploaded for this section. A successful reupload will overwrite your previous submission. +

{% endif %} diff --git a/backend/static/js/upload-page.js b/backend/static/js/upload-page.js index 00ecf114e4..85f8371e25 100644 --- a/backend/static/js/upload-page.js +++ b/backend/static/js/upload-page.js @@ -43,24 +43,24 @@ function handleUploadErrors(error) { info_box.innerHTML = `Timeout - Response took longer than expected. Try again later.`; } else { info_box.innerHTML = - 'Error when uploading file. Ensure you have upload the correct template, or contact an administrator.'; + 'Error when uploading file. Ensure you have upload the correct template, or contact an administrator.'; console.error(`Unexpected error.\n`, error); } } -function getValidationErrorsTable(res){ - var error_rows = "" +function getValidationErrorsTable(res) { + var rows_html = ''; + var row_array = []; for (let i = 0; i < res.errors.length; i++) { - row_array = res.errors[i].replaceAll(/[\(\)']/g, ` `).split(',') - error_rows += ` + row_array = res.errors[i].replaceAll(/[()']/g, ` `).split(','); + rows_html += ` ${row_array[0]}

${row_array[1]}

${row_array[2]}

`; } - const validationTable = - `

Error on validation. Check the following cells for errors, and re-upload. + const validationTable = `

Error on validation. Check the following cells for errors, and re-upload. Common errors include incorrect data types or missing information.

@@ -71,10 +71,10 @@ function getValidationErrorsTable(res){ - ${error_rows} + ${rows_html} -
` - return validationTable + `; + return validationTable; } // On file upload, send it off for verification. @@ -118,7 +118,7 @@ function attachFileUploadHandler() { 'File successfully validated! Your work has been saved.'; setContinueButtonDisabled(false); } else { - info_box.innerHTML = getValidationErrorsTable(res) + info_box.innerHTML = getValidationErrorsTable(res); } }) .catch((error) => { From d7294a0e0fe176961961db53ce92989071a8bd85 Mon Sep 17 00:00:00 2001 From: James Person Date: Thu, 15 Jun 2023 09:50:02 -0400 Subject: [PATCH 03/10] Linting. Always two tries! --- backend/audit/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/audit/views.py b/backend/audit/views.py index caf6a2cbfa..1ef0bbddf9 100644 --- a/backend/audit/views.py +++ b/backend/audit/views.py @@ -142,7 +142,7 @@ def post(self, request, *args, **kwargs): logger.warn(f"{form_section} Excel upload failed validation: {e}") return JsonResponse({"errors": list(e)}, status=400) except MultiValueDictKeyError: - logger.warn(f"no file found in request") + logger.warn("No file found in request") raise BadRequest() except KeyError as e: logger.warn(f"Field error. Field: {e}") From 6ad23a4e9fa6da4afa29b8f76ebf327a5ae37cf6 Mon Sep 17 00:00:00 2001 From: James Person Date: Wed, 21 Jun 2023 11:32:05 -0400 Subject: [PATCH 04/10] Table errors, minor routing changes --- .../templates/audit/submission-progress.html | 49 ++++-------- backend/audit/validators.py | 10 +-- backend/audit/views.py | 9 ++- backend/cypress/e2e/check-ueid.cy.js | 4 +- backend/cypress/e2e/display-submissions.cy.js | 2 +- .../report_submission/upload-page.html | 6 +- backend/report_submission/views.py | 2 +- backend/static/js/upload-page.js | 76 ++++++++++--------- 8 files changed, 73 insertions(+), 85 deletions(-) diff --git a/backend/audit/templates/audit/submission-progress.html b/backend/audit/templates/audit/submission-progress.html index 70db8d8e63..a2ea92db9e 100644 --- a/backend/audit/templates/audit/submission-progress.html +++ b/backend/audit/templates/audit/submission-progress.html @@ -129,51 +129,37 @@

{{ auditee_name | default_if_none:''

Sample description text, lorem ipsum, various questionably accurate latin phrases.

- +
  • -
    +
    -
    - {% if federal_awards_workbook.completed == True %} - Upload the Findings Text workbook - {% else %} - - Upload the Findings Text workbook - {% endif %} +
    + Upload the Audit Findings workbook

    Sample description text, lorem ipsum, various questionably accurate latin phrases.

  • - +
  • -
    +
    -
    - {% if federal_awards_workbook.completed == True %} - Upload the Audit Findings workbook - {% else %} - - Upload the Audit Findings workbook - {% endif %} +
    + Upload the Findings Text workbook

    Sample description text, lorem ipsum, various questionably accurate latin phrases.

  • @@ -189,15 +175,8 @@

    {{ auditee_name | default_if_none:''

    - {% if federal_awards_workbook.completed == True %} - Upload the Corrective Action Plan (CAP) workbook - {% else %} - - Upload the Corrective Action Plan (CAP) workbook - {% endif %} + Upload the Corrective Action Plan (CAP) workbook

    Sample description text, lorem ipsum, various questionably accurate latin phrases.

    diff --git a/backend/audit/validators.py b/backend/audit/validators.py index 195ba76319..757b63e3ab 100644 --- a/backend/audit/validators.py +++ b/backend/audit/validators.py @@ -44,7 +44,7 @@ XLSX_TEMPLATE_DEFINITION_DIR = settings.XLSX_TEMPLATE_JSON_DIR -ErrorDetails = list[tuple[str, str, str, str]] +ErrorDetails = list[str] def validate_uei(value): @@ -286,23 +286,23 @@ def _get_error_details(xlsx_definition_template, named_ranges_row_indices): for open_range in xlsx_definition_template["sheets"][0]["open_ranges"]: if open_range["range_name"] == named_range: error_details.append( - ( + [ open_range["title_cell"][0], xlsx_definition_template["title_row"] + row_index + 1, open_range["title"], open_range["help"], - ) + ] ) break for single_cell in xlsx_definition_template["sheets"][0]["single_cells"]: if single_cell["range_name"] == named_range: error_details.append( - ( + [ single_cell["range_cell"][0], single_cell["range_cell"][1], single_cell["title"], single_cell["help"], - ) + ] ) break return error_details diff --git a/backend/audit/views.py b/backend/audit/views.py index b4441ed1b6..15e61b0cd3 100644 --- a/backend/audit/views.py +++ b/backend/audit/views.py @@ -133,20 +133,23 @@ def post(self, request, *args, **kwargs): logger.warn(f"no form section found with name {form_section}") raise BadRequest() - return redirect("/") + return JsonResponse({"message": "File upload successful"}, status=200) except SingleAuditChecklist.DoesNotExist: logger.warn(f"no SingleAuditChecklist found with report ID {report_id}") raise PermissionDenied() except ValidationError as e: # The good error, where bad rows/columns are sent back in the request. + # These come back as: + # [col1, row1, field1, link1, help-text1, col2, row2, ...] + # Instead of as a nested list or in tuple form. logger.warn(f"{form_section} Excel upload failed validation: {e}") - return JsonResponse({"errors": list(e)}, status=400) + return JsonResponse({"errors": list(e), "type": "error_row"}, status=400) except MultiValueDictKeyError: logger.warn("No file found in request") raise BadRequest() except KeyError as e: logger.warn(f"Field error. Field: {e}") - raise BadRequest(e) + return JsonResponse({"errors": str(e), "type": "error_field"}, status=400) class ReadyForCertificationView(SingleAuditChecklistAccessRequiredMixin, generic.View): diff --git a/backend/cypress/e2e/check-ueid.cy.js b/backend/cypress/e2e/check-ueid.cy.js index e5fe535b35..795e8f1b9a 100644 --- a/backend/cypress/e2e/check-ueid.cy.js +++ b/backend/cypress/e2e/check-ueid.cy.js @@ -358,7 +358,7 @@ describe('Create New Audit', () => { interception.response.body.validueid, 'Failure API Response' ); - console.log('Response:' + interception.response.body.validueid); + // console.log('Response:' + interception.response.body.validueid); }); }); @@ -377,7 +377,7 @@ describe('Create New Audit', () => { interception.response.body.validueid, 'Succcessful API Response' ); - console.log('Response:' + interception.response.body.validueid); + // console.log('Response:' + interception.response.body.validueid); }); cy.url().should('include', '/report_submission/accessandsubmission'); }); diff --git a/backend/cypress/e2e/display-submissions.cy.js b/backend/cypress/e2e/display-submissions.cy.js index 1f6b614279..e7cafc156c 100644 --- a/backend/cypress/e2e/display-submissions.cy.js +++ b/backend/cypress/e2e/display-submissions.cy.js @@ -19,7 +19,7 @@ describe('Display my audit submissions', () => { ).as('hasNoData'); cy.visit('/submissions/'); cy.wait('@hasNoData').then((interception) => { - console.log(interception); + // console.log(interception); cy.get('.usa-table-container') .should('have.attr', 'class') .and('contain', 'display-none'); diff --git a/backend/report_submission/templates/report_submission/upload-page.html b/backend/report_submission/templates/report_submission/upload-page.html index 0361de363b..deee9bebe5 100644 --- a/backend/report_submission/templates/report_submission/upload-page.html +++ b/backend/report_submission/templates/report_submission/upload-page.html @@ -14,7 +14,7 @@
    -
    +
    {% comment %} {% include "../sidenav.html" %} {% endcomment %} Upload completed worksheet A file has already been uploaded for this section. A successful reupload will overwrite your previous submission.

    {% endif %} - - + +
    {% include "audit-metadata.html" %} diff --git a/backend/report_submission/views.py b/backend/report_submission/views.py index 2aa30978c9..31f27dba4d 100644 --- a/backend/report_submission/views.py +++ b/backend/report_submission/views.py @@ -182,7 +182,7 @@ def post(self, request, *args, **kwargs): SingleAuditChecklist.objects.filter(pk=sac.id).update(**update) return redirect( - "/report_submission/federal-awards/{}".format(report_id) + "/audit/submission-progress/{report_id}".format(report_id=report_id) ) except SingleAuditChecklist.DoesNotExist: raise PermissionDenied("You do not have access to this audit.") diff --git a/backend/static/js/upload-page.js b/backend/static/js/upload-page.js index 85f8371e25..89a3f655a7 100644 --- a/backend/static/js/upload-page.js +++ b/backend/static/js/upload-page.js @@ -3,10 +3,10 @@ */ // Matches current URL against the correct upload endpoint. Comes from /audit/fixtures/excel.py const UPLOAD_URLS = { - 'federal-awards': 'FederalAwardsExpended', - 'audit-findings': 'FindingsUniformGuidance', - 'audit-findings-text': 'FindingsText', - CAP: 'CorrectiveActionPlan', + 'federal-awards': 'federal-awards-expended', + 'audit-findings': 'findings-uniform-guidance', + 'audit-findings-text': 'findings-text', + CAP: 'corrective-action-plan', }; /* @@ -22,13 +22,13 @@ const already_submitted = document.getElementById(`already-submitted`); Function definitions */ // Disable/enable "Continue" button -function setContinueButtonDisabled(disabled) { +function setFormDisabled(shouldDisable) { const submitButton = document.getElementById('continue'); - submitButton.disabled = disabled; + submitButton.disabled = shouldDisable; } // Print helpful error info to page & console on unsuccessful upload -function handleUploadErrors(error) { +function handleErrorOnUpload(error) { if (typeof error.text === 'function') { error.text().then((errorMessage) => { console.error( @@ -43,31 +43,32 @@ function handleUploadErrors(error) { info_box.innerHTML = `Timeout - Response took longer than expected. Try again later.`; } else { info_box.innerHTML = - 'Error when uploading file. Ensure you have upload the correct template, or contact an administrator.'; + 'Error when uploading file. Ensure you have uploaded the correct template, or contact an administrator.'; console.error(`Unexpected error.\n`, error); } } -function getValidationErrorsTable(res) { +function get_error_table(res) { var rows_html = ''; var row_array = []; - for (let i = 0; i < res.errors.length; i++) { - row_array = res.errors[i].replaceAll(/[()']/g, ` `).split(','); + for (let i = 0; i < res.errors.length; i += 5) { + row_array = res.errors.slice(i, i + 5); rows_html += ` - ${row_array[0]}

    - ${row_array[1]}

    - ${row_array[2]}

    + ${row_array[0]}${row_array[1]} + ${row_array[2]} + ${row_array[4]}. `; + // Link } const validationTable = `

    Error on validation. Check the following cells for errors, and re-upload. Common errors include incorrect data types or missing information.

    - - + + @@ -94,35 +95,42 @@ function attachFileUploadHandler() { info_box.hidden = false; info_box.innerHTML = 'Validating your file...'; - const currentURL = new URL(window.location.href); + const current_url = new URL(window.location.href); const report_submission_url = - UPLOAD_URLS[currentURL.pathname.split('/')[2]]; + UPLOAD_URLS[current_url.pathname.split('/')[2]]; if (!report_submission_url) throw 'No upload URL available.'; if (!e.target.files[0]) throw 'No file selected.'; if (e.target.files[0].name.split('.').pop() !== 'xlsx') throw 'File type not accepted.'; - var data = new FormData(); - data.append('FILES', e.target.files[0]); - data.append('filename', e.target.files[0].name); - data.append('sac_id', sac_id); + var body = new FormData(); + body.append('FILES', e.target.files[0]); + body.append('filename', e.target.files[0].name); + body.append('sac_id', sac_id); fetch(`/audit/excel/${report_submission_url}/${sac_id}`, { method: 'POST', - body: data, + body: body, }) - .then((res) => res.json()) .then((res) => { - if (res.status == 200) { - info_box.innerHTML = - 'File successfully validated! Your work has been saved.'; - setContinueButtonDisabled(false); - } else { - info_box.innerHTML = getValidationErrorsTable(res); - } + res.json().then((data) => { + if (res.ok) { + info_box.innerHTML = + 'File successfully validated! Your work has been saved.'; + setFormDisabled(false); + } else { + if (data.type === 'error_row') { + info_box.innerHTML = get_error_table(data); + } else if (data.type === 'error_field') { + info_box.innerHTML = `Field Error: ${res.errors}`; + } else { + throw new Error('Returned error type is missing!'); + } + } + }); }) .catch((error) => { - handleUploadErrors(error); + handleErrorOnUpload(error); }); } catch (error) { info_box.innerHTML = `Error when sending excel file.\n ${error}`; @@ -139,9 +147,7 @@ function attachEventHandlers() { function init() { attachEventHandlers(); - already_submitted - ? setContinueButtonDisabled(false) - : setContinueButtonDisabled(true); + already_submitted ? setFormDisabled(false) : setFormDisabled(true); } init(); From 16f7bc9190c7441192639929b7c77bc3c372677a Mon Sep 17 00:00:00 2001 From: James Person Date: Wed, 21 Jun 2023 11:45:35 -0400 Subject: [PATCH 05/10] Django tests - 200 reponse on valid file upload --- backend/audit/test_views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/audit/test_views.py b/backend/audit/test_views.py index 9f33b71da7..1b72952d43 100644 --- a/backend/audit/test_views.py +++ b/backend/audit/test_views.py @@ -364,7 +364,7 @@ def test_valid_file_upload_for_federal_awards(self, mock_scan_file): data={"FILES": excel_file}, ) - self.assertEqual(response.status_code, 302) + self.assertEqual(response.status_code, 200) updated_sac = SingleAuditChecklist.objects.get(pk=sac.id) @@ -489,7 +489,7 @@ def test_valid_file_upload_for_corrective_action_plan(self, mock_scan_file): print(response.content) - self.assertEqual(response.status_code, 302) + self.assertEqual(response.status_code, 200) updated_sac = SingleAuditChecklist.objects.get(pk=sac.id) @@ -553,7 +553,7 @@ def test_valid_file_upload_for_findings_uniform_guidance(self, mock_scan_file): data={"FILES": excel_file}, ) - self.assertEqual(response.status_code, 302) + self.assertEqual(response.status_code, 200) updated_sac = SingleAuditChecklist.objects.get(pk=sac.id) @@ -631,7 +631,7 @@ def test_valid_file_upload_for_findings_text(self, mock_scan_file): data={"FILES": excel_file}, ) - self.assertEqual(response.status_code, 302) + self.assertEqual(response.status_code, 200) updated_sac = SingleAuditChecklist.objects.get(pk=sac.id) From f24e53aa188c7e4577d7d55e246dfc5445fe5a7f Mon Sep 17 00:00:00 2001 From: James Person Date: Wed, 21 Jun 2023 12:08:36 -0400 Subject: [PATCH 06/10] Upload response is a redirect (302). JS handles. --- backend/audit/test_views.py | 8 ++++---- backend/audit/views.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/audit/test_views.py b/backend/audit/test_views.py index 1b72952d43..9f33b71da7 100644 --- a/backend/audit/test_views.py +++ b/backend/audit/test_views.py @@ -364,7 +364,7 @@ def test_valid_file_upload_for_federal_awards(self, mock_scan_file): data={"FILES": excel_file}, ) - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, 302) updated_sac = SingleAuditChecklist.objects.get(pk=sac.id) @@ -489,7 +489,7 @@ def test_valid_file_upload_for_corrective_action_plan(self, mock_scan_file): print(response.content) - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, 302) updated_sac = SingleAuditChecklist.objects.get(pk=sac.id) @@ -553,7 +553,7 @@ def test_valid_file_upload_for_findings_uniform_guidance(self, mock_scan_file): data={"FILES": excel_file}, ) - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, 302) updated_sac = SingleAuditChecklist.objects.get(pk=sac.id) @@ -631,7 +631,7 @@ def test_valid_file_upload_for_findings_text(self, mock_scan_file): data={"FILES": excel_file}, ) - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, 302) updated_sac = SingleAuditChecklist.objects.get(pk=sac.id) diff --git a/backend/audit/views.py b/backend/audit/views.py index 15e61b0cd3..ad6caa3f0e 100644 --- a/backend/audit/views.py +++ b/backend/audit/views.py @@ -133,7 +133,7 @@ def post(self, request, *args, **kwargs): logger.warn(f"no form section found with name {form_section}") raise BadRequest() - return JsonResponse({"message": "File upload successful"}, status=200) + return redirect("/") except SingleAuditChecklist.DoesNotExist: logger.warn(f"no SingleAuditChecklist found with report ID {report_id}") raise PermissionDenied() From b7a3af9d85cdb2798be4e2191afd13777f5be120 Mon Sep 17 00:00:00 2001 From: James Person Date: Wed, 21 Jun 2023 12:43:27 -0400 Subject: [PATCH 07/10] JS handles 302 redirect correctly. --- backend/static/js/upload-page.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/static/js/upload-page.js b/backend/static/js/upload-page.js index 89a3f655a7..7efb48d80d 100644 --- a/backend/static/js/upload-page.js +++ b/backend/static/js/upload-page.js @@ -113,12 +113,12 @@ function attachFileUploadHandler() { body: body, }) .then((res) => { - res.json().then((data) => { - if (res.ok) { - info_box.innerHTML = - 'File successfully validated! Your work has been saved.'; - setFormDisabled(false); - } else { + if (res.status == 200) { + info_box.innerHTML = + 'File successfully validated! Your work has been saved.'; + setFormDisabled(false); + } else { + res.json().then((data) => { if (data.type === 'error_row') { info_box.innerHTML = get_error_table(data); } else if (data.type === 'error_field') { @@ -126,8 +126,8 @@ function attachFileUploadHandler() { } else { throw new Error('Returned error type is missing!'); } - } - }); + }); + } }) .catch((error) => { handleErrorOnUpload(error); From c0d4012198b2300e41ab8b60b650c675634336af Mon Sep 17 00:00:00 2001 From: James Person Date: Wed, 21 Jun 2023 13:23:09 -0400 Subject: [PATCH 08/10] Errors return to tuples - converted away in JS --- backend/audit/validators.py | 10 +++++----- backend/audit/views.py | 5 ++--- backend/static/js/upload-page.js | 14 +++++++++----- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/backend/audit/validators.py b/backend/audit/validators.py index 757b63e3ab..195ba76319 100644 --- a/backend/audit/validators.py +++ b/backend/audit/validators.py @@ -44,7 +44,7 @@ XLSX_TEMPLATE_DEFINITION_DIR = settings.XLSX_TEMPLATE_JSON_DIR -ErrorDetails = list[str] +ErrorDetails = list[tuple[str, str, str, str]] def validate_uei(value): @@ -286,23 +286,23 @@ def _get_error_details(xlsx_definition_template, named_ranges_row_indices): for open_range in xlsx_definition_template["sheets"][0]["open_ranges"]: if open_range["range_name"] == named_range: error_details.append( - [ + ( open_range["title_cell"][0], xlsx_definition_template["title_row"] + row_index + 1, open_range["title"], open_range["help"], - ] + ) ) break for single_cell in xlsx_definition_template["sheets"][0]["single_cells"]: if single_cell["range_name"] == named_range: error_details.append( - [ + ( single_cell["range_cell"][0], single_cell["range_cell"][1], single_cell["title"], single_cell["help"], - ] + ) ) break return error_details diff --git a/backend/audit/views.py b/backend/audit/views.py index ad6caa3f0e..6a017b280c 100644 --- a/backend/audit/views.py +++ b/backend/audit/views.py @@ -139,9 +139,8 @@ def post(self, request, *args, **kwargs): raise PermissionDenied() except ValidationError as e: # The good error, where bad rows/columns are sent back in the request. - # These come back as: - # [col1, row1, field1, link1, help-text1, col2, row2, ...] - # Instead of as a nested list or in tuple form. + # These come back as tuples: + # [(col1, row1, field1, link1, help-text1), (col2, row2, ...), ...] logger.warn(f"{form_section} Excel upload failed validation: {e}") return JsonResponse({"errors": list(e), "type": "error_row"}, status=400) except MultiValueDictKeyError: diff --git a/backend/static/js/upload-page.js b/backend/static/js/upload-page.js index 7efb48d80d..4de3a8c46a 100644 --- a/backend/static/js/upload-page.js +++ b/backend/static/js/upload-page.js @@ -48,18 +48,22 @@ function handleErrorOnUpload(error) { } } -function get_error_table(res) { +function get_error_table(data) { var rows_html = ''; var row_array = []; - for (let i = 0; i < res.errors.length; i += 5) { - row_array = res.errors.slice(i, i + 5); + for (let i = 0; i < data.errors.length; i++) { + // Convert given string-tuples into arrays: + // "(col, row...)" -> [col, row, ...] + row_array = data.errors[i]; + row_array = JSON.parse(row_array.replaceAll("(","[").replaceAll(")","]").replaceAll(`'`, `"`)) + rows_html += ` - + `; - // Link + // Link } const validationTable = `

    Error on validation. Check the following cells for errors, and re-upload. Common errors include incorrect data types or missing information.

    From 1602eb6549e1e3b6e4a1941466553bb8c29a2f1e Mon Sep 17 00:00:00 2001 From: James Person Date: Wed, 21 Jun 2023 13:29:23 -0400 Subject: [PATCH 09/10] Linting, as one does. --- backend/static/js/upload-page.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/backend/static/js/upload-page.js b/backend/static/js/upload-page.js index 4de3a8c46a..ed6621f89b 100644 --- a/backend/static/js/upload-page.js +++ b/backend/static/js/upload-page.js @@ -55,13 +55,15 @@ function get_error_table(data) { // Convert given string-tuples into arrays: // "(col, row...)" -> [col, row, ...] row_array = data.errors[i]; - row_array = JSON.parse(row_array.replaceAll("(","[").replaceAll(")","]").replaceAll(`'`, `"`)) - + row_array = JSON.parse( + row_array.replaceAll('(', '[').replaceAll(')', ']').replaceAll(`'`, `"`) + ); + rows_html += ` - + `; // Link } From bee894de84d905e9d66fadae89b2e3c600c0eba4 Mon Sep 17 00:00:00 2001 From: Tadhg O'Higgins <2626258+tadhg-ohiggins@users.noreply.github.com> Date: Wed, 21 Jun 2023 10:40:34 -0700 Subject: [PATCH 10/10] Add cursory comment. --- backend/audit/fixtures/excel.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/audit/fixtures/excel.py b/backend/audit/fixtures/excel.py index dee47cdc70..22086be21c 100644 --- a/backend/audit/fixtures/excel.py +++ b/backend/audit/fixtures/excel.py @@ -1,3 +1,6 @@ +""" +Fixtures for use with Excel spreadsheet handling. +""" from collections import namedtuple from django.conf import settings
    RowColumnCell FieldHelp Text
    ${row_array[0]}${row_array[1]} ${row_array[2]}${row_array[4]}.${row_array[3]["text"]}.
    ${row_array[0]}${row_array[1]} ${row_array[2]}${row_array[3]["text"]}.${row_array[3]['text']}.