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

Async handlers for s3PathHandler and fileWriter #46

Merged
merged 13 commits into from
Nov 26, 2024
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
73 changes: 73 additions & 0 deletions lib/components/ErrorModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import Modal from "react-bootstrap/Modal";
import Form from "react-bootstrap/Form";
import Accordion from "react-bootstrap/Accordion";
import Button from "react-bootstrap/Button";

interface ErrorModalContentsProps {
error: Error | null;
message?: string;
}

interface ErrorModalProps extends ErrorModalContentsProps {
show: boolean;
onHide: () => void;
title: string;
}

function ErrorModalContents(props: ErrorModalContentsProps) {
return (
<Form>
<Form.Group className="mb-3">
<Form.Label className="d-flex justify-content-center">
Error Occurred.
</Form.Label>
{props.message && (
<Form.Text className="d-flex justify-content-center">
<b className="onyx-text-pink">{props.message}</b>
</Form.Text>
)}
<Form.Text className="d-flex justify-content-center">
Please try again or contact CLIMB-TRE support if the problem persists.
</Form.Text>
</Form.Group>
<Accordion>
<Accordion.Item eventKey="0">
<Accordion.Header>View Error Message</Accordion.Header>
<Accordion.Body>
<small className="onyx-text-pink font-monospace">
{props.error
? `${props.error.name}: ${props.error.message}`
: "No error message."}
</small>
</Accordion.Body>
</Accordion.Item>
</Accordion>
</Form>
);
}

function ErrorModal(props: ErrorModalProps) {
return (
<Modal
className="onyx-modal"
centered
show={props.show}
onHide={props.onHide}
>
<Modal.Header closeButton>
<Modal.Title>{props.title}</Modal.Title>
</Modal.Header>
<Modal.Body>
<ErrorModalContents {...props} />
</Modal.Body>
<Modal.Footer>
<Button variant="dark" onClick={props.onHide}>
Close
</Button>
</Modal.Footer>
</Modal>
);
}

export default ErrorModal;
export { ErrorModalContents };
33 changes: 26 additions & 7 deletions lib/components/ExportModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import Button from "react-bootstrap/Button";
import InputGroup from "react-bootstrap/InputGroup";
import Form from "react-bootstrap/Form";
import ProgressBar from "react-bootstrap/ProgressBar";
import { DataProps, ExportHandlerProps } from "../interfaces";
import Stack from "react-bootstrap/Stack";
import Spinner from "react-bootstrap/Spinner";
import { ExportHandlerProps } from "../interfaces";
import { ExportStatus } from "../types";
import { ErrorModalContents } from "./ErrorModal";

interface ExportModalProps extends DataProps {
interface ExportModalProps {
show: boolean;
onHide: () => void;
defaultFileNamePrefix: string;
Expand Down Expand Up @@ -39,6 +42,7 @@ function isInvalidPrefix(prefix: string) {
function ExportModal(props: ExportModalProps) {
const [exportStatus, setExportStatus] = useState(ExportStatus.READY);
const [exportProgress, setExportProgress] = useState(0);
const [exportError, setExportError] = useState<Error | null>(null);
const [fileNamePrefix, setFileNamePrefix] = useState("");
const [fileNameIsInvalid, setFileNameIsInvalid] = useState(false);
const { statusToken, readyExport, cancelExport } = useExportStatusToken();
Expand All @@ -54,13 +58,14 @@ function ExportModal(props: ExportModalProps) {
} else setFileNameIsInvalid(false);

setExportProgress(0);
setExportError(null);
readyExport();
setExportStatus(ExportStatus.RUNNING);
props.handleExport({
fileName: prefix + props.fileExtension,
statusToken,
setExportProgress,
setExportStatus,
setExportProgress,
setExportError,
});
};

Expand Down Expand Up @@ -139,12 +144,24 @@ function ExportModal(props: ExportModalProps) {
<ProgressBar now={exportProgress} variant="danger" />
</Form.Group>
)}
{exportStatus === ExportStatus.ERROR && (
<ErrorModalContents error={exportError} />
)}
{exportStatus === ExportStatus.WRITING && (
<Form.Group className="mb-3">
<Form.Label className="d-flex justify-content-center">
<Stack direction="horizontal" gap={2}>
<Spinner />
<span>Writing File...</span>
</Stack>
</Form.Label>
</Form.Group>
)}
{exportStatus === ExportStatus.FINISHED && (
<Form.Group className="mb-3">
<Form.Label className="d-flex justify-content-center">
Export Finished.
</Form.Label>
<ProgressBar now={exportProgress} />
<Form.Text className="d-flex justify-content-center">
<span>
File Name:{" "}
Expand Down Expand Up @@ -172,15 +189,17 @@ function ExportModal(props: ExportModalProps) {
Cancel
</Button>
)}
{exportStatus === ExportStatus.CANCELLED && (
{(exportStatus === ExportStatus.CANCELLED ||
exportStatus === ExportStatus.ERROR) && (
<Button
variant="primary"
onClick={() => setExportStatus(ExportStatus.READY)}
>
Retry
</Button>
)}
{exportStatus === ExportStatus.FINISHED && (
{(exportStatus === ExportStatus.WRITING ||
exportStatus === ExportStatus.FINISHED) && (
<Button variant="primary" onClick={props.onHide}>
Done
</Button>
Expand Down
2 changes: 1 addition & 1 deletion lib/components/FilterPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ function FilterPanel(props: FilterPanelProps) {
return (
<Card className="h-50">
<Card.Header>
<span>Filters</span>
<span>Filter</span>
<Button
className="float-end"
size="sm"
Expand Down
2 changes: 1 addition & 1 deletion lib/components/QueryHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function QueryHandler({
<LoadingSpinner />
) : error ? (
<Alert variant="danger">Error: {error.message}</Alert>
) : data.messages ? (
) : data?.messages ? (
<ErrorMessages messages={data.messages} />
) : (
children
Expand Down
67 changes: 46 additions & 21 deletions lib/components/RecordModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useState, useMemo } from "react";
import { CustomCellRendererProps } from "@ag-grid-community/react";
import Badge from "react-bootstrap/Badge";
import Stack from "react-bootstrap/Stack";
Expand All @@ -12,10 +12,12 @@ import Row from "react-bootstrap/Row";
import Container from "react-bootstrap/Container";
import { useQuery } from "@tanstack/react-query";
import Table from "./Table";
import ErrorModal from "./ErrorModal";
import QueryHandler from "./QueryHandler";
import { ResultData, ResultType, ExportStatus } from "../types";
import { DataProps, ExportHandlerProps } from "../interfaces";
import ExportModal from "./ExportModal";
import { s3BucketsMessage } from "../utils/errorMessages";

interface RecordModalProps extends DataProps {
recordID: string;
Expand Down Expand Up @@ -48,10 +50,16 @@ function RecordDataField({

function RecordData(props: RecordModalProps) {
const [exportModalShow, setExportModalShow] = useState(false);
const [errorModalShow, setErrorModalShow] = useState(false);
const [s3ReportError, setS3ReportError] = useState<Error | null>(null);

const handleErrorModalShow = (error: Error) => {
setS3ReportError(error);
setErrorModalShow(true);
};

const DetailCellRenderer = (cellRendererProps: CustomCellRendererProps) => {
if (
props.s3PathHandler &&
typeof cellRendererProps.value === "string" &&
cellRendererProps.value.startsWith("s3://") &&
cellRendererProps.value.endsWith(".html")
Expand All @@ -62,7 +70,9 @@ function RecordData(props: RecordModalProps) {
size="sm"
variant="link"
onClick={() =>
props.s3PathHandler && props.s3PathHandler(cellRendererProps.value)
props
.s3PathHandler(cellRendererProps.value)
.catch((error: Error) => handleErrorModalShow(error))
}
>
{cellRendererProps.value}
Expand Down Expand Up @@ -96,23 +106,32 @@ function RecordData(props: RecordModalProps) {
.join(" ");
};

const detailFields = Object.entries(recordData.data).filter(
([key]) => props.projectFields.get(key)?.type !== "relation"
const detailFields = useMemo(
() =>
Object.entries(recordData?.data || {}).filter(
([key]) => props.projectFields.get(key)?.type !== "relation"
),
[recordData, props.projectFields]
);

const relationFields = Object.entries(recordData.data)
.filter(([key]) => props.projectFields.get(key)?.type === "relation")
.sort(([key1], [key2]) => (key1 < key2 ? -1 : 1));
const relationFields = useMemo(
() =>
Object.entries(recordData?.data || {})
.filter(([key]) => props.projectFields.get(key)?.type === "relation")
.sort(([key1], [key2]) => (key1 < key2 ? -1 : 1)),
[recordData, props.projectFields]
);

const handleJSONExport = (exportProps: ExportHandlerProps) => {
const fileWriter = props.fileWriter;

if (fileWriter) {
exportProps.setExportProgress(100);
const jsonData = JSON.stringify(recordData.data);
fileWriter(exportProps.fileName, jsonData);
exportProps.setExportStatus(ExportStatus.FINISHED);
}
const jsonData = JSON.stringify(recordData.data);
exportProps.setExportStatus(ExportStatus.WRITING);
props
.fileWriter(exportProps.fileName, jsonData)
.then(() => exportProps.setExportStatus(ExportStatus.FINISHED))
.catch((error: Error) => {
exportProps.setExportError(error);
exportProps.setExportStatus(ExportStatus.ERROR);
});
};

return (
Expand All @@ -125,6 +144,13 @@ function RecordData(props: RecordModalProps) {
id="record-data-tabs"
defaultActiveKey="record-data-details"
>
<ErrorModal
title="S3 Reports"
message={s3BucketsMessage}
error={s3ReportError}
show={errorModalShow}
onHide={() => setErrorModalShow(false)}
/>
<ExportModal
{...props}
defaultFileNamePrefix={props.recordID}
Expand All @@ -140,18 +166,18 @@ function RecordData(props: RecordModalProps) {
<hr />
<Container>
<RecordDataField
record={recordData.data}
record={recordData?.data}
field="published_date"
name="Date"
/>
<RecordDataField
record={recordData.data}
record={recordData?.data}
field="site"
name="Site"
/>
{recordData.data["platform"] && (
{recordData?.data?.platform && (
<RecordDataField
record={recordData.data}
record={recordData?.data}
field="platform"
name="Platform"
/>
Expand All @@ -171,7 +197,6 @@ function RecordData(props: RecordModalProps) {
<hr />
<Button
size="sm"
disabled={!props.fileWriter}
variant="dark"
onClick={() => setExportModalShow(true)}
>
Expand Down
Loading