Improve crawl elapsed time UX (#323)
Smoother elapsed crawl timer: - Crawls list: show seconds increment up to 2 minutes, then show minutes only - Crawls detail: show seconds increment up to one day
This commit is contained in:
parent
2bfbeab55f
commit
8708c24a74
@ -1,50 +1,89 @@
|
|||||||
import { LitElement } from "lit";
|
import { LitElement } from "lit";
|
||||||
import { property, state } from "lit/decorators.js";
|
import { property, state } from "lit/decorators.js";
|
||||||
|
import { msg, localized, str } from "@lit/localize";
|
||||||
import humanizeDuration from "pretty-ms";
|
import humanizeDuration from "pretty-ms";
|
||||||
|
|
||||||
|
export type HumanizeOptions = {
|
||||||
|
compact?: boolean;
|
||||||
|
verbose?: boolean;
|
||||||
|
unitCount?: number;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show time passed from date in human-friendly format
|
* Show time passed from date in human-friendly format
|
||||||
* Updates every 5 seconds
|
|
||||||
*
|
*
|
||||||
* Usage example:
|
* Usage example:
|
||||||
* ```ts
|
* ```ts
|
||||||
* <btrix-relative-duration value=${value}></btrix-relative-duration>
|
* <btrix-relative-duration value=${value}></btrix-relative-duration>
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
@localized()
|
||||||
export class RelativeDuration extends LitElement {
|
export class RelativeDuration extends LitElement {
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
value?: string; // `new Date` compatible date format
|
value?: string; // `new Date` compatible date format
|
||||||
|
|
||||||
@state()
|
@property({ type: Number })
|
||||||
private now = Date.now();
|
tickSeconds?: number; // Enables ticks every specified seconds
|
||||||
|
|
||||||
// For long polling:
|
@property({ type: Number })
|
||||||
|
endTime?: number = Date.now();
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
compact? = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
verbose? = false;
|
||||||
|
|
||||||
|
@property({ type: Number })
|
||||||
|
unitCount?: number;
|
||||||
|
|
||||||
|
@state()
|
||||||
private timerId?: number;
|
private timerId?: number;
|
||||||
|
|
||||||
static humanize(duration: number) {
|
static humanize(duration: number, options: HumanizeOptions = {}) {
|
||||||
return humanizeDuration(duration, {
|
return humanizeDuration(duration, {
|
||||||
secondsDecimalDigits: 0,
|
secondsDecimalDigits: 0,
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback(): void {
|
connectedCallback(): void {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
|
||||||
this.timerId = window.setInterval(() => this.updateValue(), 1000 * 5);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback(): void {
|
disconnectedCallback(): void {
|
||||||
window.clearInterval(this.timerId);
|
window.clearTimeout(this.timerId);
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected updated(changedProperties: Map<string | number | symbol, unknown>) {
|
||||||
|
if (changedProperties.has("tickSeconds")) {
|
||||||
|
window.clearTimeout(this.timerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changedProperties.has("endTime") && this.tickSeconds) {
|
||||||
|
this.tick(this.tickSeconds * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private tick(timeoutMs: number) {
|
||||||
|
window.clearTimeout(this.timerId);
|
||||||
|
|
||||||
|
this.timerId = window.setTimeout(() => {
|
||||||
|
this.endTime = Date.now();
|
||||||
|
}, timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this.value) return "";
|
if (!this.value) return "";
|
||||||
|
|
||||||
return RelativeDuration.humanize(this.now - new Date(this.value).valueOf());
|
return RelativeDuration.humanize(
|
||||||
}
|
(this.endTime || Date.now()) - new Date(this.value).valueOf(),
|
||||||
|
{
|
||||||
private updateValue() {
|
compact: this.compact,
|
||||||
this.now = Date.now();
|
verbose: this.verbose,
|
||||||
|
unitCount: this.unitCount,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -446,6 +446,8 @@ export class CrawlDetail extends LiteElement {
|
|||||||
<span class="text-purple-600">
|
<span class="text-purple-600">
|
||||||
<btrix-relative-duration
|
<btrix-relative-duration
|
||||||
value=${`${this.crawl.started}Z`}
|
value=${`${this.crawl.started}Z`}
|
||||||
|
unitCount="3"
|
||||||
|
tickSeconds="1"
|
||||||
></btrix-relative-duration>
|
></btrix-relative-duration>
|
||||||
</span>
|
</span>
|
||||||
`}
|
`}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { state, property } from "lit/decorators.js";
|
import { state, property } from "lit/decorators.js";
|
||||||
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { msg, localized, str } from "@lit/localize";
|
import { msg, localized, str } from "@lit/localize";
|
||||||
import debounce from "lodash/fp/debounce";
|
import debounce from "lodash/fp/debounce";
|
||||||
import flow from "lodash/fp/flow";
|
import flow from "lodash/fp/flow";
|
||||||
@ -137,7 +138,9 @@ export class CrawlsList extends LiteElement {
|
|||||||
? this.renderCrawlList()
|
? this.renderCrawlList()
|
||||||
: html`
|
: html`
|
||||||
<div class="border-t border-b py-5">
|
<div class="border-t border-b py-5">
|
||||||
<p class="text-center text-0-500">${msg("No crawls yet.")}</p>
|
<p class="text-center text-neutral-500">
|
||||||
|
${msg("No crawls yet.")}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
</section>
|
</section>
|
||||||
@ -177,7 +180,9 @@ export class CrawlsList extends LiteElement {
|
|||||||
</sl-input>
|
</sl-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-12 md:col-span-1 flex items-center justify-end">
|
<div class="col-span-12 md:col-span-1 flex items-center justify-end">
|
||||||
<div class="whitespace-nowrap text-0-500 mr-2">${msg("Sort By")}</div>
|
<div class="whitespace-nowrap text-neutral-500 mr-2">
|
||||||
|
${msg("Sort By")}
|
||||||
|
</div>
|
||||||
<sl-dropdown
|
<sl-dropdown
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
distance="4"
|
distance="4"
|
||||||
@ -252,7 +257,6 @@ export class CrawlsList extends LiteElement {
|
|||||||
<a
|
<a
|
||||||
href=${`${this.crawlsBaseUrl}/crawl/${crawl.id}`}
|
href=${`${this.crawlsBaseUrl}/crawl/${crawl.id}`}
|
||||||
class="grid grid-cols-12 gap-4 p-4 leading-none hover:bg-zinc-50 hover:text-primary transition-colors"
|
class="grid grid-cols-12 gap-4 p-4 leading-none hover:bg-zinc-50 hover:text-primary transition-colors"
|
||||||
title=${crawl.configName}
|
|
||||||
@click=${this.navLink}
|
@click=${this.navLink}
|
||||||
>
|
>
|
||||||
<div class="col-span-11 md:col-span-5">
|
<div class="col-span-11 md:col-span-5">
|
||||||
@ -376,7 +380,7 @@ export class CrawlsList extends LiteElement {
|
|||||||
: crawl.state === "complete"
|
: crawl.state === "complete"
|
||||||
? "text-emerald-500"
|
? "text-emerald-500"
|
||||||
: isActive(crawl)
|
: isActive(crawl)
|
||||||
? "text-purple-500"
|
? "text-purple-500 motion-safe:animate-pulse"
|
||||||
: "text-zinc-300"}"
|
: "text-zinc-300"}"
|
||||||
style="font-size: 10px; vertical-align: 2px"
|
style="font-size: 10px; vertical-align: 2px"
|
||||||
>
|
>
|
||||||
@ -391,16 +395,20 @@ export class CrawlsList extends LiteElement {
|
|||||||
>
|
>
|
||||||
${crawl.state.replace(/_/g, " ")}
|
${crawl.state.replace(/_/g, " ")}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-0-500 text-sm whitespace-nowrap truncate">
|
<div class="text-neutral-500 text-sm whitespace-nowrap truncate">
|
||||||
${crawl.finished
|
${crawl.finished
|
||||||
? html`
|
? html`
|
||||||
<sl-relative-time
|
<sl-relative-time
|
||||||
date=${`${crawl.finished}Z` /** Z for UTC */}
|
date=${`${crawl.finished}Z` /** Z for UTC */}
|
||||||
></sl-relative-time>
|
></sl-relative-time>
|
||||||
`
|
`
|
||||||
: html`<btrix-relative-duration
|
: ""}
|
||||||
value=${`${crawl.started}Z`}
|
${!crawl.finished
|
||||||
></btrix-relative-duration>`}
|
? html`
|
||||||
|
${crawl.state === "canceled" ? msg("Unknown") : ""}
|
||||||
|
${isActive(crawl) ? this.renderActiveDuration(crawl) : ""}
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -414,17 +422,20 @@ export class CrawlsList extends LiteElement {
|
|||||||
lang=${/* TODO localize: */ "en"}
|
lang=${/* TODO localize: */ "en"}
|
||||||
></sl-format-bytes>
|
></sl-format-bytes>
|
||||||
</span>
|
</span>
|
||||||
<span class="text-0-500">
|
<span class="text-neutral-500">
|
||||||
(${crawl.fileCount === 1
|
(${crawl.fileCount === 1
|
||||||
? msg(str`${crawl.fileCount} file`)
|
? msg(str`${crawl.fileCount} file`)
|
||||||
: msg(str`${crawl.fileCount} files`)})
|
: msg(str`${crawl.fileCount} files`)})
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-0-500 text-sm whitespace-nowrap truncate">
|
<div
|
||||||
|
class="text-neutral-500 text-sm whitespace-nowrap truncate"
|
||||||
|
>
|
||||||
${msg(
|
${msg(
|
||||||
str`in ${RelativeDuration.humanize(
|
str`in ${RelativeDuration.humanize(
|
||||||
new Date(`${crawl.finished}Z`).valueOf() -
|
new Date(`${crawl.finished}Z`).valueOf() -
|
||||||
new Date(`${crawl.started}Z`).valueOf()
|
new Date(`${crawl.started}Z`).valueOf(),
|
||||||
|
{ compact: true }
|
||||||
)}`
|
)}`
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -438,7 +449,9 @@ export class CrawlsList extends LiteElement {
|
|||||||
<span class="text-0-400">/</span>
|
<span class="text-0-400">/</span>
|
||||||
${this.numberFormatter.format(+crawl.stats.found)}
|
${this.numberFormatter.format(+crawl.stats.found)}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-0-500 text-sm whitespace-nowrap truncate">
|
<div
|
||||||
|
class="text-neutral-500 text-sm whitespace-nowrap truncate"
|
||||||
|
>
|
||||||
${msg("pages crawled")}
|
${msg("pages crawled")}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
@ -453,7 +466,9 @@ export class CrawlsList extends LiteElement {
|
|||||||
>${msg("Manual Start")}</span
|
>${msg("Manual Start")}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-1 text-0-500 text-sm whitespace-nowrap truncate">
|
<div
|
||||||
|
class="ml-1 text-neutral-500 text-sm whitespace-nowrap truncate"
|
||||||
|
>
|
||||||
${msg(str`by ${crawl.userName || crawl.userid}`)}
|
${msg(str`by ${crawl.userName || crawl.userid}`)}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
@ -470,6 +485,37 @@ export class CrawlsList extends LiteElement {
|
|||||||
</li>`;
|
</li>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private renderActiveDuration(crawl: Crawl) {
|
||||||
|
const endTime = this.lastFetched || Date.now();
|
||||||
|
const duration = endTime - new Date(`${crawl.started}Z`).valueOf();
|
||||||
|
let unitCount: number;
|
||||||
|
let tickSeconds: number | undefined = undefined;
|
||||||
|
|
||||||
|
// Show second unit if showing seconds or greater than 1 hr
|
||||||
|
const showSeconds = duration < 60 * 2 * 1000;
|
||||||
|
if (showSeconds || duration > 60 * 60 * 1000) {
|
||||||
|
unitCount = 2;
|
||||||
|
} else {
|
||||||
|
unitCount = 1;
|
||||||
|
}
|
||||||
|
// Tick if seconds are showing
|
||||||
|
if (showSeconds) {
|
||||||
|
tickSeconds = 1;
|
||||||
|
} else {
|
||||||
|
tickSeconds = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<btrix-relative-duration
|
||||||
|
class="text-purple-500"
|
||||||
|
value=${`${crawl.started}Z`}
|
||||||
|
endTime=${this.lastFetched || Date.now()}
|
||||||
|
unitCount=${unitCount}
|
||||||
|
tickSeconds=${ifDefined(tickSeconds)}
|
||||||
|
></btrix-relative-duration>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
private onSearchInput = debounce(200)((e: any) => {
|
private onSearchInput = debounce(200)((e: any) => {
|
||||||
this.filterBy = e.target.value;
|
this.filterBy = e.target.value;
|
||||||
}) as any;
|
}) as any;
|
||||||
|
@ -5,7 +5,8 @@ type CrawlState =
|
|||||||
| "failed"
|
| "failed"
|
||||||
| "partial_complete"
|
| "partial_complete"
|
||||||
| "timed_out"
|
| "timed_out"
|
||||||
| "stopping";
|
| "stopping"
|
||||||
|
| "canceled";
|
||||||
|
|
||||||
export type Crawl = {
|
export type Crawl = {
|
||||||
id: string;
|
id: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user