Skip to content

Commit

Permalink
[Resolve Errors] [Review Warnings] Exceed error warning limit alert &…
Browse files Browse the repository at this point in the history
… counts read off the response body (#699)

closes #623
closes #624 
closes #630 
closes #704 

## Changes
- feat(Errors/Warnings): A field alert alert appears when the
error/warnings limit is exceeded (i.e. `is_truncated`)
- feat(Errors/Warnings): Single Field, Multi Field and Register counts
now come from the data response object
- enhancement(Download Validation Report): Added button loader
- enhancement(Field Level Alert - Download Validation Report Link): Now
scrolls to the Download Validation Report button

## How to Test
- Upload [1_000_000
errors](https://github.com/cfpb/sbl-test-data/blob/main/locust-sblars/100000_entries_findings.csv)
- Navigate to Errors and/or Warnings 
- Ensure the counts properly match the data response object
- The Field Level Alert appears when necessary
- Test the Download Validation report link 

## Screenshot
<img width="623" alt="Screenshot 2024-06-12 at 2 50 47 PM"
src="https://github.com/cfpb/sbl-frontend/assets/13324863/6860f393-dcec-4332-8aa6-f94481b8ddfd">
<img width="623" alt="Screenshot 2024-06-12 at 2 50 47 PM"
src="https://github.com/cfpb/sbl-frontend/assets/13324863/6860f393-dcec-4332-8aa6-f94481b8ddfd">
<img width="623" alt="Screenshot 2024-06-12 at 2 50 47 PM"
src="https://github.com/cfpb/sbl-frontend/assets/13324863/6860f393-dcec-4332-8aa6-f94481b8ddfd">

---------

Co-authored-by: shindigira <[email protected]>
  • Loading branch information
shindigira authored Jun 18, 2024
1 parent 52a8d70 commit 06787c2
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 63 deletions.
21 changes: 18 additions & 3 deletions src/api/requests/downloadValidationReport.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,46 @@
import { FILING_URL } from 'api/common';
import type { SblAuthProperties } from 'api/useSblAuth';
import type { AxiosProgressEvent } from 'axios';
import axios from 'axios';
import type { FilingPeriodType } from 'types/filingTypes';
import { Hundred } from 'utils/constants';

export interface DownloadValidationReportProperties {
auth: SblAuthProperties;
lei: string;
submissionId: number;
filingPeriod: FilingPeriodType;
afterDownloadCallback?: () => void;
}

export const downloadValidationReport = async ({
auth,
lei,
filingPeriod,
submissionId,
afterDownloadCallback,
}: DownloadValidationReportProperties): Promise<void> => {
try {
await axios({
baseURL: FILING_URL,
headers: {
Authorization: `Bearer ${auth.user?.access_token}`,
'Cache-Control': 'no-cache',
Pragma: 'no-cache',
Expires: '0',
},
url: `/v1/filing/institutions/${lei}/filings/${filingPeriod}/submissions/${submissionId}/report`,
method: 'GET',
responseType: 'blob',
onDownloadProgress: (progressEvent: AxiosProgressEvent): void => {
if (
typeof progressEvent.total === 'number' &&
typeof progressEvent.loaded === 'number'
) {
// Keep incase we decide to use a progress bar
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const percentCompleted = Math.round(
(progressEvent.loaded * Hundred) / progressEvent.total,
);
}
},
}).then(response => {
const url = window.URL.createObjectURL(
new Blob([response.data], { type: 'text/csv;charset=utf-8' }),
Expand All @@ -49,6 +62,8 @@ export const downloadValidationReport = async ({
// Part of code cleanup for post-mvp see: https://github.com/cfpb/sbl-frontend/issues/717
// eslint-disable-next-line no-console
console.log(error);
} finally {
if (afterDownloadCallback) afterDownloadCallback();
}
};

Expand Down
3 changes: 2 additions & 1 deletion src/api/requests/uploadCsvAxios.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { filingApiClient, request } from 'api/axiosService';
import type { SblAuthProperties } from 'api/useSblAuth';
import type { AxiosProgressEvent } from 'axios';
import type { FilingPeriodType, SubmissionResponse } from 'types/filingTypes';
import type { InstitutionDetailsApiType } from 'types/formTypes';
import { Hundred } from 'utils/constants';
Expand All @@ -24,7 +25,7 @@ const uploadCsvAxios = async (
'Content-Type': 'multipart/form-data',
},
options: {
onUploadProgress: progressEvent => {
onUploadProgress: (progressEvent: AxiosProgressEvent): void => {
if (
typeof progressEvent.total === 'number' &&
typeof progressEvent.loaded === 'number'
Expand Down
25 changes: 23 additions & 2 deletions src/pages/Filing/FilingApp/FieldEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { Link } from 'components/Link';
import { Heading, Pagination, Table } from 'design-system-react';
import { useState } from 'react';
import Markdown from 'react-markdown';
import type { Detail, Field } from 'types/filingTypes';
import type { Detail, Field, FilingPeriodType } from 'types/filingTypes';
import { ITEMS_PER_PAGE, One } from 'utils/constants';
import useIsOverflowing from 'utils/useIsOverflowing';
import FilingErrorsWarningsLimit from './FilingErrors/FilingErrorsWarningsLimit';

// NOTE: To be removed after table styling finalized
// const maxUidTestRows = [...Array.from({ length: Hundred }).keys()].map(
Expand All @@ -27,13 +28,24 @@ import useIsOverflowing from 'utils/useIsOverflowing';

interface FieldEntryProperties {
fieldObject: Detail;
lei: string;
submissionId: number;
filingPeriod: FilingPeriodType;
isWarning?: boolean;
}

function FieldEntry({ fieldObject }: FieldEntryProperties): JSX.Element {
function FieldEntry({
fieldObject,
lei,
submissionId,
filingPeriod,
isWarning,
}: FieldEntryProperties): JSX.Element {
const validationId = fieldObject.validation.id;
const validationLink = fieldObject.validation.fig_link;
const validationName = fieldObject.validation.name;
const validationDescription = fieldObject.validation.description;
const validationIsTruncated = fieldObject.validation.is_truncated;
// eslint-disable-next-line unicorn/no-array-reduce
const additionalColumnHeaders = fieldObject.records[0].fields.reduce(
(accumulator: Field['name'][], fieldsObject) => [
Expand Down Expand Up @@ -119,6 +131,11 @@ function FieldEntry({ fieldObject }: FieldEntryProperties): JSX.Element {
</Link>
<Heading type='4'>{validationName}</Heading>
<Markdown>{validationDescription}</Markdown>
{validationIsTruncated ? (
<FilingErrorsWarningsLimit
{...{ isWarning, lei, submissionId, filingPeriod }}
/>
) : null}
</div>
<div className='mb-[0.9375rem]'>
<Table
Expand Down Expand Up @@ -161,4 +178,8 @@ function FieldEntry({ fieldObject }: FieldEntryProperties): JSX.Element {
);
}

FieldEntry.defaultProps = {
isWarning: false,
};

export default FieldEntry;
17 changes: 15 additions & 2 deletions src/pages/Filing/FilingApp/FieldSummary.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import SectionIntro from 'components/SectionIntro';
import FieldEntry from 'pages/Filing/FilingApp/FieldEntry';
import type { ReactNode } from 'react';
import type { Detail } from 'types/filingTypes';
import type { Detail, FilingPeriodType } from 'types/filingTypes';

interface FieldProperties {
fieldArray: Detail[];
heading: string;
bottomMargin?: boolean;
children: ReactNode;
id: string;
lei: string;
submissionId: number;
filingPeriod: FilingPeriodType;
isWarning?: boolean;
}

function FieldSummary({
Expand All @@ -18,6 +22,10 @@ function FieldSummary({
children,
id,
className = '',
lei,
submissionId,
filingPeriod,
isWarning,
}: FieldProperties & JSX.IntrinsicElements['div']): JSX.Element {
return (
<div
Expand All @@ -28,14 +36,19 @@ function FieldSummary({
{children}
</SectionIntro>
{fieldArray.map(fieldObject => (
<FieldEntry key={fieldObject.validation.id} fieldObject={fieldObject} />
<FieldEntry
key={fieldObject.validation.id}
fieldObject={fieldObject}
{...{ isWarning, lei, submissionId, filingPeriod }}
/>
))}
</div>
);
}

FieldSummary.defaultProps = {
bottomMargin: false,
isWarning: false,
};

export default FieldSummary;
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export const getErrorsWarningsSummary = (
};
};

// Deprecated: Count should now come from the data response object
export const getRecordsAffected = (
validationDetails: Detail[],
): Set<number> => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import downloadValidationReport from 'api/requests/downloadValidationReport';
import useSblAuth from 'api/useSblAuth';
import { AlertFieldLevel, Button } from 'design-system-react';
import type { FilingPeriodType } from 'types/filingTypes';
import { Thousand } from 'utils/constants';

interface DownloadValidationReportButtonLimitProperties {
lei: string;
submissionId: number;
filingPeriod: FilingPeriodType;
}
const onHandleDownloadClick = (): void => {
const element = document.querySelector(
`#download-validation-report-button`,
) as HTMLElement | undefined;
if (element) {
element.scrollIntoView({
behavior: 'smooth',
block: 'end',
inline: 'nearest',
});
setTimeout(() => element.focus(), Thousand);
}
};

function DownloadValidationReportButton({
lei,
filingPeriod,
submissionId,
}: DownloadValidationReportButtonLimitProperties): JSX.Element {
const auth = useSblAuth();
const onHandleDownloadClick = async (): Promise<void> => {
await downloadValidationReport({ auth, lei, filingPeriod, submissionId });
};
function DownloadValidationReportButton(): JSX.Element {
return (
// eslint-disable-next-line @typescript-eslint/no-misused-promises
<Button onClick={onHandleDownloadClick} label='Download report' asLink />
);
}
Expand All @@ -30,32 +27,22 @@ interface FilingErrorsWarningsLimitProperties {

function FilingErrorsWarningsLimit({
isWarning,
lei,
filingPeriod,
submissionId,
}: DownloadValidationReportButtonLimitProperties &
FilingErrorsWarningsLimitProperties): JSX.Element {
}: FilingErrorsWarningsLimitProperties): JSX.Element {
return (
<div className='mt-[0.9375rem] max-w-[41.875rem]'>
<div className='my-[1.875rem] max-w-[41.875rem]'>
<AlertFieldLevel
message={
isWarning ? (
<>
The number of warnings for this validation exceeds the display
limit.{' '}
<DownloadValidationReportButton
{...{ lei, filingPeriod, submissionId }}
/>{' '}
to see the full list of warnings.
limit. <DownloadValidationReportButton /> to see the full list of
warnings.
</>
) : (
<>
The number of errors for this validation exceeds the display
limit.{' '}
<DownloadValidationReportButton
{...{ lei, filingPeriod, submissionId }}
/>{' '}
to see the full list of logic errors and warnings.
limit. <DownloadValidationReportButton /> to see the full list of
logic errors and warnings.
</>
)
}
Expand Down
33 changes: 22 additions & 11 deletions src/pages/Filing/FilingApp/FilingErrors/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ import FormWrapper from 'components/FormWrapper';
import { LoadingContent } from 'components/Loading';
import { Paragraph, TextIntroduction } from 'design-system-react';
import FieldSummary from 'pages/Filing/FilingApp/FieldSummary';
import {
getErrorsWarningsSummary,
getRecordsAffected,
} from 'pages/Filing/FilingApp/FilingErrors/FilingErrors.helpers';
import { getErrorsWarningsSummary } from 'pages/Filing/FilingApp/FilingErrors/FilingErrors.helpers';
import FilingErrorsAlerts from 'pages/Filing/FilingApp/FilingErrors/FilingErrorsAlerts';
import { FilingSteps } from 'pages/Filing/FilingApp/FilingSteps';
import InstitutionHeading from 'pages/Filing/FilingApp/InstitutionHeading';
Expand Down Expand Up @@ -66,11 +63,16 @@ function FilingErrors(): JSX.Element {
: syntaxErrorsSingle;

// Count rows with errors per type (not total errors)
const singleFieldRowErrorsCount = getRecordsAffected(
singleFieldErrorsUsed,
).size;
const multiFieldRowErrorsCount = getRecordsAffected(logicErrorsMulti).size;
const registerLevelRowErrorsCount = getRecordsAffected(registerErrors).size;
const singleFieldCategory = isStep2 ? 'logic_errors' : 'syntax_errors';
const singleFieldRowErrorsCount =
actualDataGetSubmissionLatest?.validation_results?.[singleFieldCategory]
.single_field_count ?? 0;
const multiFieldRowErrorsCount =
actualDataGetSubmissionLatest?.validation_results?.[singleFieldCategory]
.multi_field_count ?? 0;
const registerLevelRowErrorsCount =
actualDataGetSubmissionLatest?.validation_results?.[singleFieldCategory]
.register_count ?? 0;

if (isFetchingGetSubmissionLatest || isLoadingInstitution)
return <LoadingContent />;
Expand Down Expand Up @@ -173,25 +175,31 @@ function FilingErrors(): JSX.Element {
{!errorGetSubmissionLatest && (
<>
{/* SINGLE-FIELD ERRORS */}
{errorState ? (
{errorState && actualDataGetSubmissionLatest?.id ? (
<FieldSummary
id='single-field-errors'
heading={`Single-field errors: ${singleFieldRowErrorsCount.toLocaleString()} found`}
fieldArray={singleFieldErrorsUsed}
lei={lei}
filingPeriod={year}
submissionId={actualDataGetSubmissionLatest.id}
bottomMargin={Boolean(isStep2)}
>
Each single-field validation pertains to only one specific field
in each record. These validations check that the data held in an
individual field match the values that are expected.
</FieldSummary>
) : null}
{isStep2 && errorState ? (
{isStep2 && errorState && actualDataGetSubmissionLatest?.id ? (
<>
{/* MULTI-FIELD ERRORS */}
<FieldSummary
id='multi-field-errors'
heading={`Multi-field errors: ${multiFieldRowErrorsCount.toLocaleString()} found`}
fieldArray={logicErrorsMulti}
lei={lei}
filingPeriod={year}
submissionId={actualDataGetSubmissionLatest.id}
bottomMargin
>
Multi-field validations check that the values of certain
Expand All @@ -203,6 +211,9 @@ function FilingErrors(): JSX.Element {
id='register-level-errors'
heading={`Register-level errors: ${registerLevelRowErrorsCount.toLocaleString()} found`}
fieldArray={registerErrors}
lei={lei}
filingPeriod={year}
submissionId={actualDataGetSubmissionLatest.id}
>
This validation checks that the register does not contain
duplicate IDs.
Expand Down
20 changes: 18 additions & 2 deletions src/pages/Filing/FilingApp/FilingFieldLinks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import downloadValidationReport from 'api/requests/downloadValidationReport';
import useSblAuth from 'api/useSblAuth';
import { Link } from 'components/Link';
import { Button, Paragraph } from 'design-system-react';
import { useCallback, useState } from 'react';
import type { FilingPeriodType } from 'types/filingTypes';

interface FilingFieldLinksProperties {
Expand All @@ -19,19 +20,34 @@ function FilingFieldLinks({
className,
...others
}: FilingFieldLinksProperties & JSX.IntrinsicElements['div']): JSX.Element {
// download in-progress state
const [downloadInProgress, setDownloadInProgress] = useState<boolean>(false);
const auth = useSblAuth();

const afterDownloadCallback = useCallback(
() => setDownloadInProgress(false),
[],
);

const onHandleDownloadClick = async (): Promise<void> => {
await downloadValidationReport({ auth, lei, filingPeriod, submissionId });
setDownloadInProgress(true);
await downloadValidationReport({
auth,
lei,
filingPeriod,
submissionId,
afterDownloadCallback,
});
};
return (
<div id={id} className={`mt-[1.875rem] ${className}`} {...others}>
<div className='flex items-center gap-[0.9375rem]'>
<Button
label='Download report'
id='download-validation-report-button'
// eslint-disable-next-line @typescript-eslint/no-misused-promises
onClick={onHandleDownloadClick}
iconRight='download'
iconRight={downloadInProgress ? 'updating' : 'download'}
/>
<Paragraph>
<Link href={`/filing/${filingPeriod}/${lei}/upload`}>
Expand Down
Loading

0 comments on commit 06787c2

Please sign in to comment.