Skip to content
Snippets Groups Projects
Commit f2ba190a authored by Christophe's avatar Christophe
Browse files

fix: error report link

parent aab2ca18
No related branches found
No related tags found
1 merge request!14fix(frontend): improve error legibility and reporting
import { type AxiosResponse, isAxiosError } from 'axios';
import { type FC, type ReactNode, useEffect, useState } from 'react';
import LoadingSpinner from '../LoadingSpinner';
import { ISSUES_URL } from '../../lib/links';
type ResponseContentsProps = {
response: AxiosResponse<unknown>;
};
const ResponseContents: FC<ResponseContentsProps> = ({ response }) => {
const [message, setMessage] = useState<ReactNode>();
useEffect(() => {
(async () => {
let text: string | null = null;
if (typeof response.data === 'string') {
text = response.data;
} else if (response.data instanceof Blob) {
try {
text = await response.data.text();
} catch (e) {
if (e instanceof Error) {
setMessage(`(Failed to process server error blob text: "${e.toString()}")`);
} else {
setMessage('(Failed to process server error blob text)');
}
}
}
if (text != null) {
if (response.headers['content-type'] === 'application/json') {
try {
setMessage(<pre>{JSON.stringify(JSON.parse(text), null, 2)}</pre>);
} catch {
setMessage(text);
}
} else {
setMessage(text);
}
} else {
setMessage('(Failed to read server error message)');
}
})();
}, [response]);
return message ?? <LoadingSpinner />;
};
type BasicErrorDisplayProps = {
error: unknown;
};
const BasicErrorDisplay = ({ error }: BasicErrorDisplayProps) =>
isAxiosError(error) ? (
<>
{error.code !== undefined && (
<>
<p>The server did not respond as expected:</p>
{error.response && (
<pre className="max-w-[80ch] overflow-x-scroll rounded-md bg-amber-200 p-2">
<ResponseContents response={error.response} />
</pre>
)}
{!error.response && <p>No server response.</p>}
<p className="mt-3">
If you believe this is a problem with the template, please report it under{' '}
<a href={ISSUES_URL}>the issues page and include the template name.</a>
</p>
</>
)}
{error.code === undefined && <p>Could not reach the server.</p>}
</>
) : (
<p>An unknown error occured.</p>
);
export default BasicErrorDisplay;
import { type FC, type PropsWithChildren } from 'react';
import Center from 'components/Center';
import BasicErrorDisplay from './BasicErrorDisplay';
type ErrorBoxProps = {
className?: string;
error: unknown;
};
const ErrorBox: FC<PropsWithChildren<ErrorBoxProps>> = ({ children, className, error }) => (
<Center className={className}>
<div className="rounded-md border border-red-500 p-2">
{children}
<BasicErrorDisplay error={error} />
</div>
</Center>
);
export default ErrorBox;
type TemplateDto = {
id: string;
repoFile: string;
title: string;
summary: string;
language: string;
tags: string[];
picture: string;
gitLink: string;
gitCheckout: string;
score: number;
};
export default TemplateDto;
......@@ -14,7 +14,7 @@ import { type AxiosRequestConfig } from 'axios';
import Form from 'components/template/Form';
import Button from 'components/Button';
import LoadingSpinner from 'components/LoadingSpinner';
import ErrorBox from 'components/ErrorBox';
import TemplateGenerationError from 'components/TemplateGenerationError';
const hasDefaultValue = (field: CutterField) => {
// TODO: type assertion because of api spec/generator issue
......@@ -148,9 +148,9 @@ const TemplateForm: FC<TemplateFormProps> = ({ template }) => {
)}
</div>
{fields.isError && (
<ErrorBox error={fields.error}>
<TemplateGenerationError error={fields.error}>
<p>Failed to load template fields:</p>
</ErrorBox>
</TemplateGenerationError>
)}
{(fields.isLoading || generate.isLoading) && (
<div className="mb-4 flex w-full justify-center">
......@@ -158,9 +158,13 @@ const TemplateForm: FC<TemplateFormProps> = ({ template }) => {
</div>
)}
{generate.isError && (
<ErrorBox error={generate.error} className="mt-2">
<p id="something-went-wrong">Failed to generate the project:</p>{' '}
</ErrorBox>
<TemplateGenerationError
error={generate.error}
template={template}
className="mt-2"
>
<p id="something-went-wrong">Failed to generate the project:</p>
</TemplateGenerationError>
)}
</form>
<dialog id="missing-fields" ref={missingFieldsModal} className="modal p-3">
......
import { type FC, type PropsWithChildren, useEffect, useState } from 'react';
import Center from 'components/Center';
import { type AxiosResponse, isAxiosError } from 'axios';
import LoadingSpinner from './LoadingSpinner';
import { ISSUES_URL } from '../lib/links';
import { type Template as TemplateDto } from '../lib/client';
type ErrorReport =
| {
json: string;
}
| {
content: string;
}
| {
parseError: string;
};
const analyzeError = async (response: AxiosResponse<unknown>): Promise<ErrorReport> => {
let text: string | null = null;
if (typeof response.data === 'string') {
text = response.data;
} else if (response.data instanceof Blob) {
try {
text = await response.data.text();
} catch (e) {
if (e instanceof Error) {
return {
parseError: `Failed to process server error blob text: "${e.toString()}")`,
};
} else {
return {
parseError: 'Failed to process server error blob text',
};
}
}
}
if (text != null) {
if (response.headers['content-type'] === 'application/json') {
try {
return { json: text };
} catch {
return { content: text };
}
} else {
return { content: text };
}
} else {
return { parseError: 'Failed to read server error message' };
}
};
type ResponseDisplayErrorProps = {
report: ErrorReport;
};
const ResponseDisplayError: FC<ResponseDisplayErrorProps> = ({ report }) => {
if ('json' in report) {
return <pre>{JSON.stringify(JSON.parse(report.json), null, 2)}</pre>;
}
if ('content' in report) {
return report.content;
}
return report.parseError;
};
type ResponseDisplayProps = {
response: AxiosResponse<unknown>;
template?: TemplateDto;
};
const ResponseDisplay: FC<ResponseDisplayProps> = ({ response, template }) => {
const [report, setReport] = useState<ErrorReport>();
useEffect(() => {
analyzeError(response).then(setReport);
}, [response]);
if (report === undefined) {
return <LoadingSpinner />;
}
return (
<>
<pre className="max-w-[80ch] overflow-x-scroll rounded-md bg-amber-200 p-2">
<ResponseDisplayError report={report} />
</pre>
{template && (
<p className="mt-3">
If you believe this is a problem with the template, please report it under{' '}
{ISSUES_URL.startsWith('https://github.com') ? (
<a
href={`${ISSUES_URL}/new?${new URLSearchParams({
title: `[${template.title.replace(' ', '+')}]`,
body: ('json' in report
? report.json
: 'content' in report
? report.content
: report.parseError
).replace(' ', '+'),
})}`}
>
the issues page
</a>
) : (
<a href={ISSUES_URL}>the issues page and include the template name.</a>
)}
</p>
)}
</>
);
};
type ErrorDisplayInnerProps = {
error: unknown;
template?: TemplateDto;
};
const ErrorDisplayInner: FC<ErrorDisplayInnerProps> = ({ template, error }) => {
if (!isAxiosError(error)) {
return <p>An unknown error occured.</p>;
}
if (error.code === undefined) {
return <p>Could not reach the server.</p>;
}
return (
<>
<p>The server did not respond as expected:</p>
{error.response ? (
<ResponseDisplay response={error.response} template={template} />
) : (
<p>No server response.</p>
)}
</>
);
};
type ErrorBoxProps = {
className?: string;
error: unknown;
template?: TemplateDto;
};
const TemplateGenerationError: FC<PropsWithChildren<ErrorBoxProps>> = ({
children,
className,
error,
template,
}) => (
<Center className={className}>
<div className="rounded-md border border-red-500 p-2">
{children}
<ErrorDisplayInner error={error} template={template} />
</div>
</Center>
);
export default TemplateGenerationError;
......@@ -4,7 +4,7 @@ import SelectInput from './SelectInput';
import TextInput from './TextInput';
import { type CutterField } from 'lib/client';
import CheckboxInput from 'components/template/CheckboxInput';
import ErrorBox from 'components/ErrorBox';
import TemplateGenerationError from 'components/TemplateGenerationError';
import ClickableLinks from 'components/ClickableLinks';
type FormFieldProps = { field: CutterField; flagged: boolean };
......@@ -19,7 +19,7 @@ const Formfield: FC<FormFieldProps> = ({ field, flagged }) => (
) : field.type === 'checkbox' ? (
<CheckboxInput field={field} className="mt-1" />
) : (
<ErrorBox error={`Unknown field type ${field.type}`} />
<TemplateGenerationError error={`Unknown field type ${field.type}`} />
))}
</div>
);
......
......@@ -6,7 +6,8 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"type-check": "tsc --noEmit"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.4.2",
......
......@@ -4,7 +4,7 @@ import Template from 'components/templates/Template';
import { useQuery } from '@tanstack/react-query';
import { useTemplateApi } from 'lib/useApi';
import LoadingSpinner from '../components/LoadingSpinner';
import ErrorBox from 'components/ErrorBox';
import TemplateGenerationError from 'components/TemplateGenerationError';
import { useMemo, useState } from 'react';
import type Ordering from 'lib/Ordering';
import OrderingSelector from 'components/OrderingSelector';
......@@ -90,9 +90,9 @@ const Templates: NextPage = () => {
)}
{templates.isError && (
<ErrorBox error={templates.error}>
<TemplateGenerationError error={templates.error}>
<p>An error occurred while loading the templates:</p>
</ErrorBox>
</TemplateGenerationError>
)}
</Layout>
);
......
......@@ -12,7 +12,7 @@ import Badge from 'components/Badge';
import TemplateForm from 'components/TemplateForm';
import Rating from 'components/Rating';
import { Code2 } from 'lucide-react';
import ErrorBox from 'components/ErrorBox';
import TemplateGenerationError from 'components/TemplateGenerationError';
import resolveImage from 'lib/resolveImage';
import Center from 'components/Center';
......@@ -54,9 +54,9 @@ const Template: NextPage = () => {
if (template.isError) {
return (
<Layout>
<ErrorBox error={template.error}>
<TemplateGenerationError error={template.error}>
<p>An error occurred while loading the template:</p>{' '}
</ErrorBox>
</TemplateGenerationError>
</Layout>
);
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment