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

fix(RV-426): Adds error framework #496

Merged
merged 1 commit into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading