Skip to content

Commit

Permalink
SHM-3771: Enhance Bug Reporting with "Report Bug" Button in UI Error …
Browse files Browse the repository at this point in the history
…Messages
  • Loading branch information
tanuj-shardeum authored and chrypnotoad committed Mar 7, 2024
1 parent 1387cd6 commit 02dd60b
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 59 deletions.
96 changes: 69 additions & 27 deletions components/ToastContextProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import { createContext, ReactNode, useState } from 'react';
import { XMarkIcon, InformationCircleIcon } from '@heroicons/react/24/outline';
import { createContext, ReactNode, useState } from "react";
import {
XMarkIcon,
InformationCircleIcon,
BugAntIcon,
} from "@heroicons/react/24/outline";
import { useNodeLogs } from "../hooks/useNodeLogs";

export const ToastContext = createContext<{
open: boolean,
setOpen: (open: boolean) => void,
message: string,
setMessage: (message: string) => void,
severity: ToastSeverity,
setSeverity: (severity: ToastSeverity) => void,
showTemporarySuccessMessage: (message: string) => void,
showTemporaryErrorMessage: (message: string) => void,
showErrorMessage: (message: string) => void,
showErrorDetails: (errorDetails: string) => void,
}>
({
open: boolean;
setOpen: (open: boolean) => void;
message: string;
setMessage: (message: string) => void;
severity: ToastSeverity;
setSeverity: (severity: ToastSeverity) => void;
showTemporarySuccessMessage: (message: string) => void;
showTemporaryErrorMessage: (message: string) => void;
showErrorMessage: (message: string) => void;
showErrorDetails: (errorDetails: string) => void;
}>({
open: false,
setOpen: () => false,
message: "",
Expand All @@ -34,30 +38,39 @@ export const ToastContext = createContext<{
},
});

type ToastSeverity = "alert-success" | "alert-error" | "alert-warning" | "alert-info";
type ToastSeverity =
| "alert-success"
| "alert-error"
| "alert-warning"
| "alert-info";


export default function ToastContextProvider({children}: { children: ReactNode }) {
export default function ToastContextProvider({
children,
}: {
children: ReactNode;
}) {
const [open, setOpen] = useState(false);
const [message, setMessage] = useState("");
const [severity, setSeverity] = useState<ToastSeverity>("alert-success");
const [detailedMessage, setDetailMessage] = useState<string | null>(null);
const [isButtonPressed, setIsButtonPressed] = useState<boolean>(false);
const { downloadAllLogs } = useNodeLogs();

function handleClose() {
setOpen(false);
setDetailMessage(null); // Clear the detailedMessage
}

function showTemporarySuccessMessage(message: string) {
setSeverity('alert-success');
setSeverity("alert-success");
setMessage(message);
setOpen(true);
setTimeout(() => setOpen(false), 6000);
}

// todo: right now we can only display one message at a time. if need arises to queue multiple messages, we can do that
function showErrorMessage(message: string) {
setSeverity('alert-error');
setSeverity("alert-error");
setMessage(message);
setOpen(true);
}
Expand Down Expand Up @@ -119,14 +132,43 @@ export default function ToastContextProvider({children}: { children: ReactNode }
return (
<>
{open && (
<div className="toast toast-top toast-center">
<div className={`alert ${severity} rounded-lg max-w-[45rem] flex`}>
<span className="flex-grow max-w-[80vw] w-max wrap-anywhere" dangerouslySetInnerHTML={{__html: message}}/>
{detailedMessage && message !== detailedMessage && (
<button onClick={() => alert(detailedMessage)}>
<InformationCircleIcon className="h-5 w-5 inline ml-2" />
</button>
)}
<div className="alert toast toast-top toast-center mt-2 bg-transparent">
<div className={`${severity} rounded-lg max-w-[45rem] flex p-4`}>
<div>
<span
className="flex-grow max-w-[80vw] w-max wrap-anywhere"
dangerouslySetInnerHTML={{ __html: message }}
/>
{detailedMessage && message !== detailedMessage && (
<button onClick={() => alert(detailedMessage)}>
<InformationCircleIcon className="h-5 w-5 inline ml-2" />
</button>
)}
{severity === "alert-error" && (
<div>
<button
className="btn-link text-white hover:text-slate-400"
disabled={isButtonPressed}
onClick={() => {
setIsButtonPressed(true);
downloadAllLogs()
.then(() => {
setIsButtonPressed(false);
window.open(
"https://github.com/Shardeum/shardeum-bug-reporting/issues"
);
})
.catch(() => {
setIsButtonPressed(false);
});
}}
>
<BugAntIcon className="text-sm h-5 inline mr-2" />
Report Bug
</button>
</div>
)}
</div>
<button onClick={handleClose}>
<XMarkIcon className="h-5 w-5 inline ml-2" />
</button>
Expand Down
2 changes: 1 addition & 1 deletion hooks/fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const fetcher = <T>(input: RequestInfo | URL,
if (res.status === 403) {
authService.logout(apiBase);
} else if (res.status === 500) {
showToast('<span>Sorry, something went wrong. Please report this issue to our support team so we can investigate and resolve the problem. [<a href="https://github.com/Shardeum/shardeum-bug-reporting/issues" target="_blank" rel="noopener noreferrer" style="text-decoration: underline;">Report Issue</a>]</span>');
showToast('<span>Sorry, something went wrong. Please report this issue to our support team so we can investigate and resolve the problem.</span>');
return;
} else if (!res.ok) {
console.log(data.errorDetails);
Expand Down
80 changes: 49 additions & 31 deletions hooks/useNodeLogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const useNodeLogs = (): NodeLogsResponse => {
const downloadLog = (logName: string): void => {
fetch(`${apiBase}/api/node/logs/${logName}`, {
method: "GET",
credentials: 'include'
credentials: 'include',
})
.then((response) => response.blob())
.then((blob) => {
Expand All @@ -40,42 +40,60 @@ export const useNodeLogs = (): NodeLogsResponse => {
};

const downloadAllLogs = async (): Promise<void> => {
const zip = new JSZip();
let fileName = "allLogs.zip";
try {
let logsData = data;
if (!logsData || logsData.length == 0 || error) {
const response = await fetch(`${apiBase}/api/node/logs`, {
method: "GET",
credentials: 'include',
});
if (!response.ok) {
throw new Error(response.statusText);
}
logsData = await response.json();
}
const zip = new JSZip();
let fileName = "allLogs.zip";

if (data && data.length > 0) {
for (const logName of data) {
try {
const response = await fetch(`${apiBase}/api/node/logs/${logName}`, {
method: "GET",
credentials: 'include'
});
if (logsData && logsData.length > 0) {
for (const logName of logsData) {
try {
const response = await fetch(
`${apiBase}/api/node/logs/${logName}`,
{
method: "GET",
credentials: 'include',
}
);

if (!response.ok) {
// Handle unsuccessful fetch by adding a log with the "downloaderror" prefix
const errorLogName = `downloaderror_${logName}`;
const errorText = `Failed to download ${logName}: ${response.statusText}`;
zip.file(errorLogName, errorText);
} else {
const blob = await response.blob();
zip.file(logName, blob, { binary: true });
if (!response.ok) {
// Handle unsuccessful fetch by adding a log with the "downloaderror" prefix
const errorLogName = `downloaderror_${logName}`;
const errorText = `Failed to download ${logName}: ${response.statusText}`;
zip.file(errorLogName, errorText);
} else {
const blob = await response.blob();
zip.file(logName, blob, { binary: true });
}
} catch (error) {
console.error(`Error while fetching ${logName}:`, error);
}
} catch (error) {
console.error(`Error while fetching ${logName}:`, error);
}
}

// Generate the zip file
const zipBlob = await zip.generateAsync({ type: "blob" });
// Generate the zip file
const zipBlob = await zip.generateAsync({ type: "blob" });

// Trigger the download of the zip file
const zipUrl = window.URL.createObjectURL(zipBlob);
const zipLink = document.createElement("a");
zipLink.href = zipUrl;
zipLink.setAttribute("download", fileName);
document.body.appendChild(zipLink);
zipLink.click();
zipLink.parentNode?.removeChild(zipLink);
// Trigger the download of the zip file
const zipUrl = window.URL.createObjectURL(zipBlob);
const zipLink = document.createElement("a");
zipLink.href = zipUrl;
zipLink.setAttribute("download", fileName);
document.body.appendChild(zipLink);
zipLink.click();
zipLink.parentNode?.removeChild(zipLink);
}
} catch (error) {
console.error("Error while downloading all logs:", error);
}
};

Expand Down

0 comments on commit 02dd60b

Please sign in to comment.