Skip to content

Commit

Permalink
fix(RV-426): Adds error framework (#496)
Browse files Browse the repository at this point in the history
  • Loading branch information
knguyenrise8 authored Jan 8, 2025
1 parent 04bdad6 commit 5c80f8c
Show file tree
Hide file tree
Showing 17 changed files with 447 additions and 43 deletions.
15 changes: 13 additions & 2 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"prop-types": "^15.8.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^5.0.0",
"react-image-label": "^1.3.4",
"react-router-dom": "^6.26.0",
"zustand": "^5.0.1"
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import "./App.scss";
import { useFiles } from "./contexts/FilesContext.tsx";
import { useQuery } from "@tanstack/react-query";
import { TemplateAPI } from "./types/templates.ts";
import { useError } from "./contexts/ErrorContext.tsx";

function App() {
const { pathname } = useLocation();
Expand Down Expand Up @@ -40,6 +41,7 @@ function App() {
};
getTemplates();
}, [templateQuery.data]);
const { setError } = useError();

useEffect(() => {
if (templates.length > 0) {
Expand All @@ -51,7 +53,8 @@ function App() {

useEffect(() => {
setFiles([]);
},[]);
setError(null);
}, []);

const navLinks = [
{ text: "Annotate and Extract", url: "/" },
Expand Down
39 changes: 20 additions & 19 deletions frontend/src/components/ExtractUploadFile.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { ChangeEvent, useEffect, useId, useState } from "react";
import { Button, Icon, Select } from "@trussworks/react-uswds";
import { Button, Select } from "@trussworks/react-uswds";
import { useFiles } from "../contexts/FilesContext";
import { useNavigate } from "react-router-dom";
import image from "../assets/green_check.svg";
Expand All @@ -11,6 +11,8 @@ import "./ExtractUploadFile.scss";
import { FileInput } from "./FileInput/file-input";
import { TemplateAPI } from "../types/templates.ts";
import { useQuery } from "@tanstack/react-query";
import ErrorBanner from "../error/ErrorBanner.tsx";
import { ERRORS, useError } from "../contexts/ErrorContext.tsx";

pdfjsLib.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjsLib.version}/build/pdf.worker.min.mjs`;

Expand Down Expand Up @@ -51,7 +53,7 @@ export const ExtractUploadFile: React.FC<ExtractUploadFileProps> = ({
} = useFiles();
const navigate = useNavigate();
const [templates, setTemplates] = useState<Template[]>([]);
const [hasError, setHasError] = useState<boolean>(false);
const { error, setError } = useError();
const [extractedImages, setExtractedImages] = useState<ExtractedFiles[]>([]);
const _templates =
useQuery({
Expand Down Expand Up @@ -181,7 +183,7 @@ export const ExtractUploadFile: React.FC<ExtractUploadFileProps> = ({
tempSelectedTemplates.push(template);
}

const error = tempSelectedTemplates.some((template) => {
const localErr = tempSelectedTemplates.some((template) => {
const tempplate = templates.find(
(tpl) => tpl.name === template.templateName,
);
Expand All @@ -190,17 +192,25 @@ export const ExtractUploadFile: React.FC<ExtractUploadFileProps> = ({
);
return tempplate?.pages.length !== extractedImage?.images.length;
});
setHasError(error);
if (localErr) {
setError(ERRORS.MISMATCH_ERROR);
} else {
setError(null);
}
}

if (selectedTemplates.length === 0) {
const checkTemplate = templates.find((tpl) => tpl.name === templateName);
const extractedImage = extractedImages.find(
(img) => img.file === fileName,
);
const error =
const localErr =
checkTemplate?.pages.length !== extractedImage?.images.length;
setHasError(error);
if (localErr) {
setError(ERRORS.MISMATCH_ERROR);
} else {
setError(null);
}
}
};

Expand All @@ -209,7 +219,6 @@ export const ExtractUploadFile: React.FC<ExtractUploadFileProps> = ({
clearTemplates();
clearFiles();
};

return (
<div className="display-flex flex-column flex-align-start flex-justify-start height-full width-full padding-2 bg-primary-lighter">
<div className="extract-upload-header">
Expand All @@ -221,18 +230,10 @@ export const ExtractUploadFile: React.FC<ExtractUploadFileProps> = ({
</p>
<p className="helper-text">Select one or more files</p>
</div>
{hasError && (
<div className="error-container">
<div className="error-header">
<Icon.Error className="error-icon" />
<h2 className="error-title">Mismatch error:</h2>
</div>
<p className="error-message">
The uploaded file has a different number of pages than the template.
Please upload a file with the correct pages to proceed or choose a
different template.
</p>
</div>
{error?.title && (
<ErrorBanner
title={error.title}
message={error.message} />
)}
<div
data-testid="dashed-container"
Expand Down
13 changes: 11 additions & 2 deletions frontend/src/components/ReviewBulk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { SortableTable } from "./SortableTable/SortableTable";

import "./ReviewBulk.scss";
import { Button, Icon } from "@trussworks/react-uswds";
import ErrorBanner from "../error/ErrorBanner";
import { useError } from "../contexts/ErrorContext";

interface ReviewBulkProps {
resultsTable: ReviewBulk[];
Expand Down Expand Up @@ -36,6 +38,7 @@ const ReviewBulk = ({
onDownload,
}: ReviewBulkProps) => {
const navigate = useNavigate();
const { error } = useError();

const onClick = (index: number) => {
setIsReviewing(true);
Expand Down Expand Up @@ -77,8 +80,13 @@ const ReviewBulk = ({
};

const handleCSVDownload = () => {
onDownload();
navigate("/");
try {
onDownload();
navigate("/");
} catch (error) {
console.error("Error downloading CSV", error);
}

}

return (
Expand Down Expand Up @@ -118,6 +126,7 @@ const ReviewBulk = ({
Download CSV
</Button>
</div>
{error?.title && <div><ErrorBanner title={error.title} message={error.message} /></div>}
<div className="table-div">
<SortableTable
columns={["fileName", "pageCount", "errors", "confidence"]}
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/components/ReviewTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { useNavigate } from "react-router-dom";

import "./ReviewTable.scss";
import Toolbar from "./Toolbar";
import ErrorBanner from "../error/ErrorBanner";
import { useError } from "../contexts/ErrorContext";

interface ReviewTableProps {
isSingle: boolean;
Expand Down Expand Up @@ -62,6 +64,7 @@ const ReviewTable = ({
onDownload,
}: ReviewTableProps) => {
const navigate = useNavigate();
const { error } = useError();
const handleImageChange = (index: number) => {
setIndex(index);
};
Expand Down Expand Up @@ -136,6 +139,7 @@ const ReviewTable = ({
)}
</div>
</div>
{error?.title && <ErrorBanner title={error.title} message={error.message} />}
<div className="table-container">
<SortableTable
columns={["name", "value", "confidence"]}
Expand Down
70 changes: 70 additions & 0 deletions frontend/src/contexts/ErrorContext.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { render, screen, fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom";
import { ErrorProvider, useError } from "./ErrorContext";

function TestComponent() {
const { error, setError, resetError } = useError();

return (
<div>
<p data-testid="errorMessage">
{error ? error.message : "No error"}
</p>
<button
data-testid="triggerError"
onClick={() => setError(new Error("Test error"))}
>
Trigger Error
</button>
<button data-testid="resetError" onClick={resetError}>
Reset Error
</button>
</div>
);
}

describe("ErrorContext", () => {
it("should default to null error", () => {
render(
<ErrorProvider>
<TestComponent />
</ErrorProvider>
);

const messageEl = screen.getByTestId("errorMessage");
expect(messageEl).toHaveTextContent("No error");
});

it("should set and display the error message", () => {
render(
<ErrorProvider>
<TestComponent />
</ErrorProvider>
);

const triggerButton = screen.getByTestId("triggerError");
fireEvent.click(triggerButton);

const messageEl = screen.getByTestId("errorMessage");
expect(messageEl).toHaveTextContent("Test error");
});

it("should reset the error message", () => {
render(
<ErrorProvider>
<TestComponent />
</ErrorProvider>
);

// Trigger the error first
const triggerButton = screen.getByTestId("triggerError");
fireEvent.click(triggerButton);

// Reset the error
const resetButton = screen.getByTestId("resetError");
fireEvent.click(resetButton);

const messageEl = screen.getByTestId("errorMessage");
expect(messageEl).toHaveTextContent("No error");
});
});
68 changes: 68 additions & 0 deletions frontend/src/contexts/ErrorContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { createContext, useContext, useState, ReactNode } from "react";


type Error = typeof ERRORS[keyof typeof ERRORS] | null;

interface ErrorContextProps {
error: Error | null;
setError: (error: Error | null) => void;
resetError: () => void;
}

const ErrorContext = createContext<ErrorContextProps>({
error: null,
setError: () => {
/* no-op */
},
resetError: () => {
/* no-op */
},
});

interface ErrorProviderProps {
children: ReactNode;
}

/** ErrorProvider component that wraps your entire application */
export function ErrorProvider({ children }: ErrorProviderProps) {
const [error, setErrorState] = useState<Error | null>(null);

const setError = (newError: Error | null) => {
setErrorState(newError);
};

const resetError = () => {
setErrorState(null);
};
return (
<ErrorContext.Provider
value={{
error,
setError,
resetError,
}}
>
{children}
</ErrorContext.Provider>
);
}

export function useError() {
return useContext(ErrorContext);
};

export const ERRORS = {
NO_ERROR: null,
GENERIC_ERROR: {
title: "An error occurred",
message: "Please try again later",
},
MISMATCH_ERROR: {
title: 'Mismatch Error',
message: 'The uploaded file has a different number of pages than template. Please upload a file with correct pages pages to proceed or choose different template.'
},
CSV_ERROR: {
title: 'Error downloading file',
message: 'There was an issue with downloading your file. Please try again.'
}
}
Loading

0 comments on commit 5c80f8c

Please sign in to comment.