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 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/views.py b/backend/audit/views.py index 505b10e54e..6a017b280c 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 FORM_SECTIONS @@ -137,11 +138,17 @@ 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. + # These come back as tuples: + # [(col1, row1, field1, link1, help-text1), (col2, row2, ...), ...] logger.warn(f"{form_section} Excel upload failed validation: {e}") - raise BadRequest(e) + return JsonResponse({"errors": list(e), "type": "error_row"}, status=400) except MultiValueDictKeyError: - logger.warn("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}") + 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 bfb5ef7935..deee9bebe5 100644 --- a/backend/report_submission/templates/report_submission/upload-page.html +++ b/backend/report_submission/templates/report_submission/upload-page.html @@ -1,19 +1,20 @@ {% extends "base.html" %} {% load static %} {% block content %} -
    -
    - -
    -
    +
    +
    + +
    +
    -
    +
    {% comment %} {% include "../sidenav.html" %} {% endcomment %}
    Upload completed worksheet

    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 %} - - + + {% 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 9e1f65c303..ed6621f89b 100644 --- a/backend/static/js/upload-page.js +++ b/backend/static/js/upload-page.js @@ -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( @@ -36,12 +36,52 @@ 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 uploaded the correct template, or contact an administrator.'; + console.error(`Unexpected error.\n`, error); + } +} + +function get_error_table(data) { + var rows_html = ''; + var row_array = []; + 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 += ` + + ${row_array[0]}${row_array[1]} + ${row_array[2]} + ${row_array[3]['text']}. + `; + // Link + } + const validationTable = `

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

    + + + + + + + + + + ${rows_html} + +
    CellFieldHelp Text
    `; + return validationTable; } // On file upload, send it off for verification. @@ -61,37 +101,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((response) => { - if (response.status == 200) { + .then((res) => { + if (res.status == 200) { info_box.innerHTML = 'File successfully validated! Your work has been saved.'; - setContinueButtonDisabled(false); + setFormDisabled(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.'; + res.json().then((data) => { + 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}`; @@ -108,9 +153,7 @@ function attachEventHandlers() { function init() { attachEventHandlers(); - already_submitted - ? setContinueButtonDisabled(false) - : setContinueButtonDisabled(true); + already_submitted ? setFormDisabled(false) : setFormDisabled(true); } init();