Improve format of crawl template config error from server (#281)

* better display of api errors, such as fields missing or invalid urls, addresses #280
This commit is contained in:
sua yoo 2022-06-29 17:57:03 -07:00 committed by GitHub
parent 301b05ff4e
commit 9606d59c3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 68 additions and 14 deletions

View File

@ -8,6 +8,7 @@ import LiteElement, { html } from "../../utils/LiteElement";
import { ScheduleInterval, humanizeNextDate } from "../../utils/cron"; import { ScheduleInterval, humanizeNextDate } from "../../utils/cron";
import type { CrawlConfig, Profile } from "./types"; import type { CrawlConfig, Profile } from "./types";
import { getUTCSchedule } from "../../utils/cron"; import { getUTCSchedule } from "../../utils/cron";
import { TemplateResult } from "lit";
type NewCrawlTemplate = { type NewCrawlTemplate = {
id?: string; id?: string;
@ -89,7 +90,7 @@ export class CrawlTemplatesNew extends LiteElement {
private browserProfileId?: string | null; private browserProfileId?: string | null;
@state() @state()
private serverError?: string; private serverError?: TemplateResult | string;
private get formattededNextCrawlDate() { private get formattededNextCrawlDate() {
const utcSchedule = this.getUTCSchedule(); const utcSchedule = this.getUTCSchedule();
@ -536,7 +537,13 @@ export class CrawlTemplatesNew extends LiteElement {
} }
} catch (e: any) { } catch (e: any) {
if (e?.isApiError) { if (e?.isApiError) {
this.serverError = e?.message; const isConfigError = ({ loc }: any) =>
loc.some((v: string) => v === "config");
if (e.details && e.details.some(isConfigError)) {
this.serverError = this.formatConfigServerError(e.details);
} else {
this.serverError = e.message;
}
} else { } else {
this.serverError = msg("Something unexpected went wrong"); this.serverError = msg("Something unexpected went wrong");
} }
@ -545,6 +552,34 @@ export class CrawlTemplatesNew extends LiteElement {
this.isSubmitting = false; this.isSubmitting = false;
} }
/**
* Format `config` related API error returned from server
*/
private formatConfigServerError(details: any): TemplateResult {
const detailsWithoutDictError = details.filter(
({ type }: any) => type !== "type_error.dict"
);
const renderDetail = ({ loc, msg: detailMsg }: any) => html`
<li>
${loc.some((v: string) => v === "seeds") &&
typeof loc[loc.length - 1] === "number"
? msg(str`Seed URL ${loc[loc.length - 1] + 1}: `)
: `${loc[loc.length - 1]}: `}
${detailMsg}
</li>
`;
return html`
${msg(
"Couldn't save crawl template. Please fix the following crawl configuration issues:"
)}
<ul class="list-disc w-fit mx-auto">
${detailsWithoutDictError.map(renderDetail)}
</ul>
`;
}
private getUTCSchedule(): string { private getUTCSchedule(): string {
if (!this.scheduleInterval) { if (!this.scheduleInterval) {
return ""; return "";

View File

@ -1,5 +1,6 @@
import { LitElement, html } from "lit"; import { LitElement, html } from "lit";
import type { TemplateResult } from "lit"; import type { TemplateResult } from "lit";
import { msg } from "@lit/localize";
import type { Auth } from "../utils/AuthService"; import type { Auth } from "../utils/AuthService";
import { APIError } from "./api"; import { APIError } from "./api";
@ -121,28 +122,29 @@ export default class LiteElement extends LitElement {
this.dispatchEvent(new CustomEvent("need-login")); this.dispatchEvent(new CustomEvent("need-login"));
} }
let errorMessage: string; let detail;
let errorMessage: string = msg("Unknown API error");
try { try {
const detail = (await resp.json()).detail; detail = (await resp.json()).detail;
if (typeof detail === "string") { if (typeof detail === "string") {
errorMessage = detail; errorMessage = detail;
} else { } else if (Array.isArray(detail) && detail.length) {
// TODO return client error details const fieldDetail = detail[0];
const fieldDetail = detail[detail.length - 1];
const { loc, msg } = fieldDetail; const { loc, msg } = fieldDetail;
errorMessage = `${loc[loc.length - 1]} ${msg}`; const fieldName = loc
.filter((v: any) => v !== "body" && typeof v === "string")
.join(" ");
errorMessage = `${fieldName} ${msg}`;
} }
} catch { } catch {}
errorMessage = "Unknown API error";
}
// TODO client error details
throw new APIError({ throw new APIError({
message: errorMessage, message: errorMessage,
status: resp.status, status: resp.status,
details: detail,
}); });
} }

View File

@ -1,13 +1,30 @@
type StatusCode = number;
type Detail = {
loc: any[];
msg: string;
type: string;
};
export class APIError extends Error { export class APIError extends Error {
statusCode: number; statusCode: StatusCode;
details: Detail[] | null;
get isApiError() { get isApiError() {
return true; return true;
} }
constructor({ message, status }: { message: string; status: number }) { constructor({
message,
status,
details,
}: {
message: string;
status: StatusCode;
details?: Detail[];
}) {
super(message); super(message);
this.statusCode = status; this.statusCode = status;
this.details = details || null;
} }
} }