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 type { CrawlConfig, Profile } from "./types";
import { getUTCSchedule } from "../../utils/cron";
import { TemplateResult } from "lit";
type NewCrawlTemplate = {
id?: string;
@ -89,7 +90,7 @@ export class CrawlTemplatesNew extends LiteElement {
private browserProfileId?: string | null;
@state()
private serverError?: string;
private serverError?: TemplateResult | string;
private get formattededNextCrawlDate() {
const utcSchedule = this.getUTCSchedule();
@ -536,7 +537,13 @@ export class CrawlTemplatesNew extends LiteElement {
}
} catch (e: any) {
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 {
this.serverError = msg("Something unexpected went wrong");
}
@ -545,6 +552,34 @@ export class CrawlTemplatesNew extends LiteElement {
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 {
if (!this.scheduleInterval) {
return "";

View File

@ -1,5 +1,6 @@
import { LitElement, html } from "lit";
import type { TemplateResult } from "lit";
import { msg } from "@lit/localize";
import type { Auth } from "../utils/AuthService";
import { APIError } from "./api";
@ -121,28 +122,29 @@ export default class LiteElement extends LitElement {
this.dispatchEvent(new CustomEvent("need-login"));
}
let errorMessage: string;
let detail;
let errorMessage: string = msg("Unknown API error");
try {
const detail = (await resp.json()).detail;
detail = (await resp.json()).detail;
if (typeof detail === "string") {
errorMessage = detail;
} else {
// TODO return client error details
const fieldDetail = detail[detail.length - 1];
} else if (Array.isArray(detail) && detail.length) {
const fieldDetail = detail[0];
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 {
errorMessage = "Unknown API error";
}
} catch {}
// TODO client error details
throw new APIError({
message: errorMessage,
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 {
statusCode: number;
statusCode: StatusCode;
details: Detail[] | null;
get isApiError() {
return true;
}
constructor({ message, status }: { message: string; status: number }) {
constructor({
message,
status,
details,
}: {
message: string;
status: StatusCode;
details?: Detail[];
}) {
super(message);
this.statusCode = status;
this.details = details || null;
}
}