Poll crawls list & add additional details (#116)

This commit is contained in:
sua yoo 2022-01-29 14:37:16 -08:00 committed by GitHub
parent 9499ebfbba
commit 7777a22829
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 138 additions and 33 deletions

View File

@ -14,8 +14,10 @@ import LiteElement, { html } from "../../utils/LiteElement";
type Crawl = { type Crawl = {
id: string; id: string;
user: string; user: string;
username?: string;
aid: string; aid: string;
cid: string; cid: string;
configName?: string;
schedule: string; schedule: string;
manual: boolean; manual: boolean;
started: string; // UTC ISO date started: string; // UTC ISO date
@ -24,6 +26,8 @@ type Crawl = {
scale: number; scale: number;
stats: { done: number; found: number } | null; stats: { done: number; found: number } | null;
files?: { filename: string; hash: string; size: number }[]; files?: { filename: string; hash: string; size: number }[];
fileCount?: number;
fileSize?: number;
completions?: number; completions?: number;
}; };
@ -31,12 +35,16 @@ type CrawlSearchResult = {
item: Crawl; item: Crawl;
}; };
const POLL_INTERVAL_SECONDS = 10;
const MIN_SEARCH_LENGTH = 2; const MIN_SEARCH_LENGTH = 2;
const sortableFieldLabels = { const sortableFieldLabels = {
started_desc: msg("Newest"), started_desc: msg("Newest"),
started_asc: msg("Oldest"), started_asc: msg("Oldest"),
state: msg("Status"), state: msg("Status"),
configName: msg("Crawl Template Name"),
cid: msg("Crawl Template ID"), cid: msg("Crawl Template ID"),
fileSize_asc: msg("Smallest Files"),
fileSize_desc: msg("Largest Files"),
}; };
function isRunning(crawl: Crawl) { function isRunning(crawl: Crawl) {
@ -83,7 +91,16 @@ export class CrawlsList extends LiteElement {
private filterBy: string = ""; private filterBy: string = "";
// For fuzzy search: // For fuzzy search:
private fuse = new Fuse([], { keys: ["cid"], shouldSort: false }); private fuse = new Fuse([], {
keys: ["cid", "configName"],
shouldSort: false,
});
// For long polling:
private timerId?: number;
// TODO localize
private numberFormatter = new Intl.NumberFormat();
private sortCrawls(crawls: CrawlSearchResult[]): CrawlSearchResult[] { private sortCrawls(crawls: CrawlSearchResult[]): CrawlSearchResult[] {
return orderBy(({ item }) => item[this.orderBy.field])( return orderBy(({ item }) => item[this.orderBy.field])(
@ -92,11 +109,20 @@ export class CrawlsList extends LiteElement {
} }
protected updated(changedProperties: Map<string, any>) { protected updated(changedProperties: Map<string, any>) {
if (this.shouldFetch && changedProperties.has("shouldFetch")) { if (changedProperties.has("shouldFetch")) {
this.fetchCrawls(); if (this.shouldFetch) {
this.fetchCrawls();
} else {
this.stopPollTimer();
}
} }
} }
disconnectedCallback(): void {
super.disconnectedCallback();
this.stopPollTimer();
}
render() { render() {
if (!this.crawls) { if (!this.crawls) {
return html`<div return html`<div
@ -137,7 +163,7 @@ export class CrawlsList extends LiteElement {
<sl-input <sl-input
class="w-full" class="w-full"
slot="trigger" slot="trigger"
placeholder=${msg("Search by Crawl Template ID")} placeholder=${msg("Search by Crawl Template name or ID")}
pill pill
clearable clearable
@sl-input=${this.onSearchInput} @sl-input=${this.onSearchInput}
@ -146,7 +172,7 @@ export class CrawlsList extends LiteElement {
</sl-input> </sl-input>
</div> </div>
<div class="col-span-2 md:col-span-1 flex items-center justify-end"> <div class="col-span-2 md:col-span-1 flex items-center justify-end">
<div class="whitespace-nowrap text-sm text-0-600 mr-2"> <div class="whitespace-nowrap text-sm text-0-500 mr-2">
${msg("Sort by")} ${msg("Sort by")}
</div> </div>
<sl-dropdown <sl-dropdown
@ -215,22 +241,40 @@ export class CrawlsList extends LiteElement {
private renderCrawlItem = ({ item: crawl }: CrawlSearchResult) => { private renderCrawlItem = ({ item: crawl }: CrawlSearchResult) => {
return html`<li return html`<li
class="grid grid-cols-12 gap-4 md:gap-6 p-4 leading-none border-t first:border-t-0" class="grid grid-cols-12 gap-2 items-center md:gap-6 p-4 leading-none border-t first:border-t-0"
> >
<div class="col-span-12 md:col-span-4"> <div class="col-span-12 md:col-span-5">
<div class="font-medium whitespace-nowrap truncate mb-1"> <div class="font-medium mb-1">
${crawl.id}
</div>
<div class="text-0-500 text-sm whitespace-nowrap truncate">
<a <a
class="hover:underline" class="hover:text-0-600 transition-colors"
href=${`/archives/${this.archiveId}/crawl-templates/${crawl.cid}`} href=${`/archives/${this.archiveId}/crawl-templates/${crawl.cid}`}
@click=${this.navLink} @click=${this.navLink}
>${crawl.cid}</a >${crawl.configName || crawl.cid}</a
> >
</div> </div>
<div class="text-0-700 text-sm whitespace-nowrap truncate">
<sl-format-date
date=${`${crawl.started}Z` /** Z for UTC */}
month="2-digit"
day="2-digit"
year="2-digit"
hour="numeric"
minute="numeric"
></sl-format-date>
${crawl.manual
? html` <span
class="bg-fuchsia-50 text-fuchsia-700 text-xs rounded px-1 leading-4"
>${msg("Manual Start")}</span
>`
: html`
<span
class="bg-teal-50 text-teal-700 text-xs rounded px-1 leading-4"
>${msg("Scheduled Run")}</span
>
`}
</div>
</div> </div>
<div class="col-span-6 md:col-span-3 flex items-start"> <div class="col-span-6 md:col-span-2 flex items-start">
<div class="mr-2"> <div class="mr-2">
<!-- TODO switch case in lit template? needed for tailwindcss purging --> <!-- TODO switch case in lit template? needed for tailwindcss purging -->
<span <span
@ -259,7 +303,6 @@ export class CrawlsList extends LiteElement {
? html` ? html`
<sl-relative-time <sl-relative-time
date=${`${crawl.finished}Z` /** Z for UTC */} date=${`${crawl.finished}Z` /** Z for UTC */}
sync
></sl-relative-time> ></sl-relative-time>
` `
: humanizeDuration( : humanizeDuration(
@ -271,24 +314,60 @@ export class CrawlsList extends LiteElement {
</div> </div>
</div> </div>
</div> </div>
<div class="col-span-6 md:col-span-4"> <div class="col-span-6 md:col-span-2">
<div class="whitespace-nowrap truncate mb-1"> ${crawl.finished
${crawl.manual ? html`
? msg(html`Manual start by <span>${crawl.user}</span>`) <div class="whitespace-nowrap truncate text-sm">
: msg(html`Scheduled run`)} <span class="font-mono text-0-800 tracking-tighter">
</div> <sl-format-bytes
value=${crawl.fileSize || 0}
<div class="text-0-500 text-sm whitespace-nowrap truncate"> lang=${/* TODO localize: */ "en"}
<sl-format-date ></sl-format-bytes>
class="inline-block align-middle text-0-600" </span>
date=${`${crawl.started}Z` /** Z for UTC */} <span class="text-0-500">
month="2-digit" (${crawl.fileCount === 1
day="2-digit" ? msg(str`${crawl.fileCount} file`)
year="2-digit" : msg(str`${crawl.fileCount} files`)})
hour="numeric" </span>
minute="numeric" </div>
></sl-format-date> <div class="text-0-500 text-sm whitespace-nowrap truncate">
</div> ${msg(
str`in ${humanizeDuration(
new Date(`${crawl.finished}Z`).valueOf() -
new Date(`${crawl.started}Z`).valueOf(),
{
secondsDecimalDigits: 0,
}
)}`
)}
</div>
`
: crawl.stats
? html`
<div
class="whitespace-nowrap truncate text-sm text-purple-600 font-mono tracking-tighter"
>
${this.numberFormatter.format(crawl.stats.done)}
<span class="text-0-400">/</span>
${this.numberFormatter.format(crawl.stats.found)}
</div>
<div class="text-0-500 text-sm whitespace-nowrap truncate">
${msg("pages crawled")}
</div>
`
: ""}
</div>
<div class="col-span-6 md:col-span-2">
${crawl.manual
? html`
<div class="text-0-500 text-sm whitespace-nowrap truncate">
${msg("Started by")}
</div>
<div class="text-0-500 text-sm whitespace-nowrap truncate">
${crawl.username || crawl.user}
</div>
`
: ""}
</div> </div>
<div class="col-span-12 md:col-span-1 flex justify-end"> <div class="col-span-12 md:col-span-1 flex justify-end">
<sl-dropdown> <sl-dropdown>
@ -337,6 +416,18 @@ export class CrawlsList extends LiteElement {
> >
${msg("Copy Crawl Template ID")} ${msg("Copy Crawl Template ID")}
</li> </li>
<li
class="p-2 hover:bg-zinc-100 cursor-pointer"
role="menuitem"
@click=${(e: any) => {
e.stopPropagation();
this.navTo(
`/archives/${this.archiveId}/crawl-templates/${crawl.cid}`
);
}}
>
${msg("View Crawl Template")}
</li>
</ul> </ul>
</sl-dropdown> </sl-dropdown>
</div> </div>
@ -351,12 +442,19 @@ export class CrawlsList extends LiteElement {
* Fetch crawls and update internal state * Fetch crawls and update internal state
*/ */
private async fetchCrawls(): Promise<void> { private async fetchCrawls(): Promise<void> {
if (!this.shouldFetch) return;
try { try {
const { crawls } = await this.getCrawls(); const { crawls } = await this.getCrawls();
this.crawls = crawls; this.crawls = crawls;
// Update search/filter collection // Update search/filter collection
this.fuse.setCollection(this.crawls as any); this.fuse.setCollection(this.crawls as any);
// Start timer for next poll
this.timerId = window.setTimeout(() => {
this.fetchCrawls();
}, 1000 * POLL_INTERVAL_SECONDS);
} catch (e) { } catch (e) {
this.notify({ this.notify({
message: msg("Sorry, couldn't retrieve crawls at this time."), message: msg("Sorry, couldn't retrieve crawls at this time."),
@ -366,6 +464,10 @@ export class CrawlsList extends LiteElement {
} }
} }
private stopPollTimer() {
window.clearTimeout(this.timerId);
}
private async getCrawls(): Promise<{ crawls: Crawl[] }> { private async getCrawls(): Promise<{ crawls: Crawl[] }> {
// Mock to use in dev: // Mock to use in dev:
// return import("../../__mocks__/api/archives/[id]/crawls").then( // return import("../../__mocks__/api/archives/[id]/crawls").then(

View File

@ -20,6 +20,9 @@ import(
import( import(
/* webpackChunkName: "shoelace" */ "@shoelace-style/shoelace/dist/components/form/form" /* webpackChunkName: "shoelace" */ "@shoelace-style/shoelace/dist/components/form/form"
); );
import(
/* webpackChunkName: "shoelace" */ "@shoelace-style/shoelace/dist/components/format-bytes/format-bytes"
);
import( import(
/* webpackChunkName: "shoelace" */ "@shoelace-style/shoelace/dist/components/format-date/format-date" /* webpackChunkName: "shoelace" */ "@shoelace-style/shoelace/dist/components/format-date/format-date"
); );