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:
		
							parent
							
								
									301b05ff4e
								
							
						
					
					
						commit
						9606d59c3d
					
				@ -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 "";
 | 
				
			||||||
 | 
				
			|||||||
@ -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")
 | 
				
			||||||
      } catch {
 | 
					            .join(" ");
 | 
				
			||||||
        errorMessage = "Unknown API error";
 | 
					          errorMessage = `${fieldName} ${msg}`;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					      } catch {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // TODO client error details
 | 
					 | 
				
			||||||
      throw new APIError({
 | 
					      throw new APIError({
 | 
				
			||||||
        message: errorMessage,
 | 
					        message: errorMessage,
 | 
				
			||||||
        status: resp.status,
 | 
					        status: resp.status,
 | 
				
			||||||
 | 
					        details: detail,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user