Fix superadmin running crawls views (#846)

- Updates superadmin "Running Crawls" to show active crawls (starting, waiting, running, stopping) and sort by start by default
- Navigates to crawl workflow watch view on clicking crawl item
- Adds "Copy Crawl ID" to crawl actions for easy paste into "Jump to crawl"
- Navigates to crawl workflow watch when jumping to crawl
This commit is contained in:
sua yoo 2023-05-10 23:15:52 -07:00 committed by GitHub
parent d8b36c0ae2
commit 98d82184e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 101 additions and 34 deletions

View File

@ -27,6 +27,7 @@ import { RelativeDuration } from "./relative-duration";
import type { Crawl } from "../types/crawler"; import type { Crawl } from "../types/crawler";
import { srOnly, truncate, dropdown } from "../utils/css"; import { srOnly, truncate, dropdown } from "../utils/css";
import type { NavigateEvent } from "../utils/LiteElement"; import type { NavigateEvent } from "../utils/LiteElement";
import { isActive } from "../utils/crawler";
const mediumBreakpointCss = css`30rem`; const mediumBreakpointCss = css`30rem`;
const largeBreakpointCss = css`60rem`; const largeBreakpointCss = css`60rem`;
@ -219,12 +220,13 @@ export class CrawlListItem extends LitElement {
} }
renderRow() { renderRow() {
const hash = this.crawl && isActive(this.crawl.state) ? "#watch" : "";
return html`<a return html`<a
class="item row" class="item row"
role="button" role="button"
href=${`${this.baseUrl || `/orgs/${this.crawl?.oid}/artifacts/crawl`}/${ href=${`${this.baseUrl || `/orgs/${this.crawl?.oid}/artifacts/crawl`}/${
this.crawl?.id this.crawl?.id
}`} }${hash}`}
@click=${async (e: MouseEvent) => { @click=${async (e: MouseEvent) => {
e.preventDefault(); e.preventDefault();
await this.updateComplete; await this.updateComplete;

View File

@ -704,8 +704,8 @@ export class App extends LiteElement {
<form <form
@submit=${(e: any) => { @submit=${(e: any) => {
e.preventDefault(); e.preventDefault();
const id = new FormData(e.target).get("crawlId"); const id = new FormData(e.target).get("crawlId") as string;
this.navigate(`/crawls/crawl/${id}`); this.navigate(`/crawls/crawl/${id}#watch`);
e.target.closest("sl-dropdown").hide(); e.target.closest("sl-dropdown").hide();
}} }}
> >

View File

@ -4,19 +4,29 @@ import { msg, localized, str } from "@lit/localize";
import type { AuthState } from "../utils/AuthService"; import type { AuthState } from "../utils/AuthService";
import LiteElement, { html } from "../utils/LiteElement"; import LiteElement, { html } from "../utils/LiteElement";
import { needLogin } from "../utils/auth"; import { needLogin } from "../utils/auth";
import type { Crawl } from "../types/crawler";
import { ROUTES } from "../routes"; import { ROUTES } from "../routes";
import "./org/crawl-detail"; import "./org/workflow-detail";
import "./org/crawls-list"; import "./org/crawls-list";
@needLogin @needLogin
@localized() @localized()
export class Crawls extends LiteElement { export class Crawls extends LiteElement {
@property({ type: Object }) @property({ type: Object })
authState?: AuthState; authState!: AuthState;
@property({ type: String }) @property({ type: String })
crawlId?: string; crawlId?: string;
@state()
private crawl?: Crawl;
willUpdate(changedProperties: Map<string, any>) {
if (changedProperties.has("crawlId") && this.crawlId) {
this.fetchWorkflowId();
}
}
render() { render() {
return html` <div return html` <div
class="w-full max-w-screen-lg mx-auto px-3 py-4 box-border" class="w-full max-w-screen-lg mx-auto px-3 py-4 box-border"
@ -26,23 +36,44 @@ export class Crawls extends LiteElement {
} }
private renderDetail() { private renderDetail() {
if (!this.crawl) return;
return html` return html`
<btrix-crawl-detail <btrix-workflow-detail
.authState=${this.authState!} .authState=${this.authState!}
crawlId=${this.crawlId!} orgId=${this.crawl.oid}
crawlsBaseUrl=${ROUTES.crawls} workflowId=${this.crawl.cid}
crawlsAPIBaseUrl="/orgs/all/crawls" initialActivePanel="watch"
showOrgLink isCrawler
></btrix-crawl-detail> ></btrix-workflow-detail>
`; `;
} }
private renderList() { private renderList() {
return html`<btrix-crawls-list return html`<btrix-crawls-list
.authState=${this.authState!} .authState=${this.authState}
crawlsBaseUrl=${ROUTES.crawls} crawlsBaseUrl=${ROUTES.crawls}
crawlsAPIBaseUrl="/orgs/all/crawls" crawlsAPIBaseUrl="/orgs/all/crawls"
isCrawler
isAdminView
shouldFetch shouldFetch
></btrix-crawls-list>`; ></btrix-crawls-list>`;
} }
private async fetchWorkflowId() {
try {
this.crawl = await this.getCrawl();
} catch (e) {
console.error(e);
}
}
private async getCrawl(): Promise<Crawl> {
const data: Crawl = await this.apiFetch(
`/orgs/all/crawls/${this.crawlId}/replay.json`,
this.authState!
);
return data;
}
} }

View File

@ -18,7 +18,7 @@ import type { AuthState } from "../../utils/AuthService";
import LiteElement, { html } from "../../utils/LiteElement"; import LiteElement, { html } from "../../utils/LiteElement";
import type { Crawl, CrawlState, Workflow, WorkflowParams } from "./types"; import type { Crawl, CrawlState, Workflow, WorkflowParams } from "./types";
import type { APIPaginatedList, APIPaginationQuery } from "../../types/api"; import type { APIPaginatedList, APIPaginationQuery } from "../../types/api";
import { isActive } from "../../utils/crawler"; import { isActive, activeCrawlStates } from "../../utils/crawler";
type Crawls = APIPaginatedList & { type Crawls = APIPaginatedList & {
items: Crawl[]; items: Crawl[];
@ -78,6 +78,7 @@ export class CrawlsList extends LiteElement {
firstSeed: msg("Crawl Start URL"), firstSeed: msg("Crawl Start URL"),
cid: msg("Workflow ID"), cid: msg("Workflow ID"),
}; };
@property({ type: Object }) @property({ type: Object })
authState!: AuthState; authState!: AuthState;
@ -87,6 +88,11 @@ export class CrawlsList extends LiteElement {
@property({ type: Boolean }) @property({ type: Boolean })
isCrawler!: boolean; isCrawler!: boolean;
// TODO better handling of using same crawls-list
// component between superadmin view and regular view
@property({ type: Boolean })
isAdminView = false;
// e.g. `/org/${this.orgId}/crawls` // e.g. `/org/${this.orgId}/crawls`
@property({ type: String }) @property({ type: String })
crawlsBaseUrl!: string; crawlsBaseUrl!: string;
@ -103,7 +109,7 @@ export class CrawlsList extends LiteElement {
shouldFetch?: boolean; shouldFetch?: boolean;
@state() @state()
private lastFetched?: number; private crawlStates: CrawlState[] = finishedCrawlStates;
@state() @state()
private crawls?: Crawls; private crawls?: Crawls;
@ -166,6 +172,15 @@ export class CrawlsList extends LiteElement {
} }
protected willUpdate(changedProperties: Map<string, any>) { protected willUpdate(changedProperties: Map<string, any>) {
if (changedProperties.has("isAdminView") && this.isAdminView === true) {
// TODO better handling of using same crawls-list
// component between superadmin view and regular view
this.crawlStates = activeCrawlStates;
this.orderBy = {
field: "started",
direction: sortableFields["started"].defaultDirection!,
};
}
if ( if (
changedProperties.has("shouldFetch") || changedProperties.has("shouldFetch") ||
changedProperties.get("crawlsBaseUrl") || changedProperties.get("crawlsBaseUrl") ||
@ -199,7 +214,10 @@ export class CrawlsList extends LiteElement {
changedProperties.has("crawlsBaseUrl") || changedProperties.has("crawlsBaseUrl") ||
changedProperties.has("crawlsAPIBaseUrl") changedProperties.has("crawlsAPIBaseUrl")
) { ) {
this.fetchConfigSearchValues(); // TODO add back when API supports `orgs/all/crawlconfigs`
if (!this.isAdminView) {
this.fetchConfigSearchValues();
}
} }
} }
@ -223,7 +241,11 @@ export class CrawlsList extends LiteElement {
<main> <main>
<header class="contents"> <header class="contents">
<div class="flex w-full h-8 mb-4"> <div class="flex w-full h-8 mb-4">
<h1 class="text-xl font-semibold">${msg("Finished Crawls")}</h1> <h1 class="text-xl font-semibold">
${this.isAdminView
? msg("Running Crawls")
: msg("Finished Crawls")}
</h1>
</div> </div>
<div <div
class="sticky z-10 mb-3 top-2 p-4 bg-neutral-50 border rounded-lg" class="sticky z-10 mb-3 top-2 p-4 bg-neutral-50 border rounded-lg"
@ -266,7 +288,7 @@ export class CrawlsList extends LiteElement {
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-[minmax(0,100%)_fit-content(100%)_fit-content(100%)] gap-x-2 gap-y-2 items-center" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-[minmax(0,100%)_fit-content(100%)_fit-content(100%)] gap-x-2 gap-y-2 items-center"
> >
<div class="col-span-1 md:col-span-2 lg:col-span-1"> <div class="col-span-1 md:col-span-2 lg:col-span-1">
${this.renderSearch()} ${when(!this.isAdminView, () => this.renderSearch())}
</div> </div>
<div class="flex items-center"> <div class="flex items-center">
<div class="text-neutral-500 mx-2">${msg("View:")}</div> <div class="text-neutral-500 mx-2">${msg("View:")}</div>
@ -277,7 +299,9 @@ export class CrawlsList extends LiteElement {
pill pill
multiple multiple
max-tags-visible="1" max-tags-visible="1"
placeholder=${msg("Finished Crawls")} placeholder=${this.isAdminView
? msg("All Active Crawls")
: msg("Finished Crawls")}
@sl-change=${async (e: CustomEvent) => { @sl-change=${async (e: CustomEvent) => {
const value = (e.target as SlSelect).value as CrawlState[]; const value = (e.target as SlSelect).value as CrawlState[];
await this.updateComplete; await this.updateComplete;
@ -287,7 +311,7 @@ export class CrawlsList extends LiteElement {
}; };
}} }}
> >
${finishedCrawlStates.map(this.renderStatusMenuItem)} ${this.crawlStates.map(this.renderStatusMenuItem)}
</sl-select> </sl-select>
</div> </div>
@ -448,7 +472,7 @@ export class CrawlsList extends LiteElement {
if (!this.crawls) return; if (!this.crawls) return;
return html` return html`
<btrix-crawl-list> <btrix-crawl-list baseUrl=${this.isAdminView ? "/crawls/crawl" : ""}>
${this.crawls.items.map(this.renderCrawlItem)} ${this.crawls.items.map(this.renderCrawlItem)}
</btrix-crawl-list> </btrix-crawl-list>
@ -515,6 +539,10 @@ export class CrawlsList extends LiteElement {
<sl-icon name="copy-code" library="app" slot="prefix"></sl-icon> <sl-icon name="copy-code" library="app" slot="prefix"></sl-icon>
${msg("Copy Workflow ID")} ${msg("Copy Workflow ID")}
</sl-menu-item> </sl-menu-item>
<sl-menu-item @click=${() => CopyButton.copyToClipboard(crawl.id)}>
<sl-icon name="copy-code" library="app" slot="prefix"></sl-icon>
${msg("Copy Crawl ID")}
</sl-menu-item>
<sl-menu-item <sl-menu-item
@click=${() => CopyButton.copyToClipboard(crawl.tags.join(","))} @click=${() => CopyButton.copyToClipboard(crawl.tags.join(","))}
?disabled=${!crawl.tags.length} ?disabled=${!crawl.tags.length}
@ -639,7 +667,7 @@ export class CrawlsList extends LiteElement {
} }
private async getCrawls(queryParams?: APIPaginationQuery): Promise<Crawls> { private async getCrawls(queryParams?: APIPaginationQuery): Promise<Crawls> {
const state = this.filterBy.state || finishedCrawlStates; const state = this.filterBy.state || this.crawlStates;
const query = queryString.stringify( const query = queryString.stringify(
{ {
...this.filterBy, ...this.filterBy,
@ -666,7 +694,6 @@ export class CrawlsList extends LiteElement {
); );
this.getCrawlsController = null; this.getCrawlsController = null;
this.lastFetched = Date.now();
return data; return data;
} }

View File

@ -1,6 +1,7 @@
import type { HTMLTemplateResult, TemplateResult } from "lit"; import type { HTMLTemplateResult, TemplateResult } from "lit";
import { state, property } from "lit/decorators.js"; import { state, property } from "lit/decorators.js";
import { when } from "lit/directives/when.js"; import { when } from "lit/directives/when.js";
import { until } from "lit/directives/until.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { msg, localized, str } from "@lit/localize"; import { msg, localized, str } from "@lit/localize";
import queryString from "query-string"; import queryString from "query-string";
@ -424,16 +425,22 @@ export class WorkflowDetail extends LiteElement {
<btrix-tab-panel name="artifacts" <btrix-tab-panel name="artifacts"
>${this.renderArtifacts()}</btrix-tab-panel >${this.renderArtifacts()}</btrix-tab-panel
> >
<btrix-tab-panel name="watch" <btrix-tab-panel name="watch">
>${when(this.activePanel === "watch", () => ${until(
this.currentCrawlId this.getWorkflowPromise?.then(
? html` <div class="border rounded-lg py-2 mb-5 h-14"> () => html`
${this.renderCurrentCrawl()} ${when(this.activePanel === "watch", () =>
</div> this.currentCrawlId
${this.renderWatchCrawl()}` ? html` <div class="border rounded-lg py-2 mb-5 h-14">
: this.renderInactiveWatchCrawl() ${this.renderCurrentCrawl()}
)}</btrix-tab-panel </div>
> ${this.renderWatchCrawl()}`
: this.renderInactiveWatchCrawl()
)}
`
)
)}
</btrix-tab-panel>
<btrix-tab-panel name="settings"> <btrix-tab-panel name="settings">
${this.renderSettings()} ${this.renderSettings()}
</btrix-tab-panel> </btrix-tab-panel>
@ -496,7 +503,7 @@ export class WorkflowDetail extends LiteElement {
return html` return html`
<a <a
slot="nav" slot="nav"
href=${`/orgs/${this.orgId}/workflows/crawl/${this.workflow?.id}#${tabName}`} href=${`${window.location.pathname}#${tabName}`}
class="block font-medium rounded-sm mb-2 mr-2 p-2 transition-all ${className}" class="block font-medium rounded-sm mb-2 mr-2 p-2 transition-all ${className}"
aria-selected=${isActive} aria-selected=${isActive}
aria-disabled=${disabled} aria-disabled=${disabled}
@ -806,7 +813,7 @@ export class WorkflowDetail extends LiteElement {
${msg( ${msg(
html`Crawl is currently running. html`Crawl is currently running.
<a <a
href="${`/orgs/${this.orgId}/workflows/crawl/${this.workflow?.id}#watch`}" href="${`${window.location.pathname}#watch`}"
class="underline hover:no-underline" class="underline hover:no-underline"
>Watch Crawl Progress</a >Watch Crawl Progress</a
>` >`