feat: Show running crawl when editing workflow (#2481)
Part of https://github.com/webrecorder/browsertrix/issues/2366 ## Changes - Displays latest running crawl status when editing workflow - Disables "Run Now" button if crawl is currently running Currently, clicking "Run Now" will result in a preventable server error if the crawl is already running. The change in this PR is in preparation for being able to update a currently running crawl and doesn't require any backend changes. ## Manual testing 1. Log in as crawler 2. Go to edit crawl workflow 3. Open same workflow in another tab 4. Run the workflow 5. Go back to edit tab. Verify "Starting" status is shown next to "Save" button and "Run Crawl" button is disabled ## Screenshots | Page | Image/video | | ---- | ----------- | | Edit Workflow | <img width="354" alt="Screenshot 2025-03-11 at 1 34 07 PM" src="https://github.com/user-attachments/assets/02f7fb4a-219d-43a4-bb1f-1f2b40ac1480" /> | <!-- ## Follow-ups --> --------- Co-authored-by: emma <hi@emma.cafe>
This commit is contained in:
parent
89a6e84377
commit
d2601a037e
@ -1,4 +1,5 @@
|
||||
import("./exclusion-editor");
|
||||
import("./live-workflow-status");
|
||||
import("./new-workflow-dialog");
|
||||
import("./queue-exclusion-form");
|
||||
import("./queue-exclusion-table");
|
||||
|
124
frontend/src/features/crawl-workflows/live-workflow-status.ts
Normal file
124
frontend/src/features/crawl-workflows/live-workflow-status.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import { localized } from "@lit/localize";
|
||||
import { Task } from "@lit/task";
|
||||
import { html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { guard } from "lit/directives/guard.js";
|
||||
|
||||
import { BtrixElement } from "@/classes/BtrixElement";
|
||||
import type { Workflow } from "@/types/crawler";
|
||||
|
||||
export type CrawlStatusChangedEventDetail = {
|
||||
isCrawlRunning: Workflow["isCrawlRunning"];
|
||||
state: Workflow["lastCrawlState"];
|
||||
};
|
||||
|
||||
const POLL_INTERVAL_SECONDS = 5;
|
||||
|
||||
/**
|
||||
* Current workflow status, displayed "live" by polling
|
||||
*
|
||||
* @fires btrix-crawl-status-changed
|
||||
*/
|
||||
@customElement("btrix-live-workflow-status")
|
||||
@localized()
|
||||
export class LiveWorkflowStatus extends BtrixElement {
|
||||
@property({ type: String })
|
||||
workflowId = "";
|
||||
|
||||
private readonly workflowTask = new Task(this, {
|
||||
task: async ([workflowId], { signal }) => {
|
||||
if (!workflowId) throw new Error("required `workflowId` missing");
|
||||
|
||||
try {
|
||||
const workflow = await this.getWorkflow(workflowId, signal);
|
||||
|
||||
if (this.workflowTask.value) {
|
||||
if (
|
||||
this.workflowTask.value.lastCrawlState !== workflow.lastCrawlState
|
||||
) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent<CrawlStatusChangedEventDetail>(
|
||||
"btrix-crawl-status-changed",
|
||||
{
|
||||
detail: {
|
||||
isCrawlRunning: workflow.isCrawlRunning,
|
||||
state: workflow.lastCrawlState,
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// dispatch status event on first run
|
||||
this.dispatchEvent(
|
||||
new CustomEvent<CrawlStatusChangedEventDetail>(
|
||||
"btrix-crawl-status-changed",
|
||||
{
|
||||
detail: {
|
||||
isCrawlRunning: workflow.isCrawlRunning,
|
||||
state: workflow.lastCrawlState,
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return workflow;
|
||||
} catch (e) {
|
||||
if ((e as Error).name === "AbortError") {
|
||||
console.debug("Fetch archived items aborted to throttle");
|
||||
} else {
|
||||
console.debug(e);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
args: () => [this.workflowId] as const,
|
||||
});
|
||||
|
||||
private readonly pollTask = new Task(this, {
|
||||
task: async ([workflow]) => {
|
||||
if (!workflow) return;
|
||||
|
||||
return window.setTimeout(() => {
|
||||
void this.workflowTask.run();
|
||||
}, POLL_INTERVAL_SECONDS * 1000);
|
||||
},
|
||||
args: () => [this.workflowTask.value] as const,
|
||||
});
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
|
||||
if (this.pollTask.value) {
|
||||
window.clearTimeout(this.pollTask.value);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const workflow = this.workflowTask.value;
|
||||
const lastCrawlState = workflow?.lastCrawlState;
|
||||
|
||||
if (!workflow?.isCrawlRunning || !lastCrawlState) return;
|
||||
|
||||
return guard([lastCrawlState], () => {
|
||||
return html`
|
||||
<btrix-crawl-status
|
||||
class="block"
|
||||
state=${lastCrawlState}
|
||||
></btrix-crawl-status>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
private async getWorkflow(
|
||||
workflowId: string,
|
||||
signal: AbortSignal,
|
||||
): Promise<Workflow> {
|
||||
const data: Workflow = await this.api.fetch(
|
||||
`/orgs/${this.orgId}/crawlconfigs/${workflowId}`,
|
||||
{ signal },
|
||||
);
|
||||
return data;
|
||||
}
|
||||
}
|
@ -49,6 +49,7 @@ import {
|
||||
} from "@/controllers/observable";
|
||||
import { type SelectBrowserProfileChangeEvent } from "@/features/browser-profiles/select-browser-profile";
|
||||
import type { CollectionsChangeEvent } from "@/features/collections/collections-add";
|
||||
import type { CrawlStatusChangedEventDetail } from "@/features/crawl-workflows/live-workflow-status";
|
||||
import type {
|
||||
ExclusionChangeEvent,
|
||||
QueueExclusionTable,
|
||||
@ -225,6 +226,9 @@ export class WorkflowEditor extends BtrixElement {
|
||||
@state()
|
||||
private serverError?: TemplateResult | string;
|
||||
|
||||
@state()
|
||||
private isCrawlRunning: boolean | null = null;
|
||||
|
||||
// For observing panel sections position in viewport
|
||||
private readonly observable = new ObservableController(this, {
|
||||
// Add some padding to account for stickied elements
|
||||
@ -580,6 +584,7 @@ export class WorkflowEditor extends BtrixElement {
|
||||
`
|
||||
: nothing}
|
||||
${when(this.serverError, (error) => this.renderErrorAlert(error))}
|
||||
${when(this.configId, this.renderCrawlStatus)}
|
||||
|
||||
<sl-tooltip content=${msg("Save without running")}>
|
||||
<sl-button
|
||||
@ -592,14 +597,21 @@ export class WorkflowEditor extends BtrixElement {
|
||||
${msg("Save")}
|
||||
</sl-button>
|
||||
</sl-tooltip>
|
||||
<sl-tooltip content=${msg("Save and run with new settings")}>
|
||||
<sl-tooltip
|
||||
content=${this.isCrawlRunning
|
||||
? msg("Crawl is already running")
|
||||
: msg("Save and run with new settings")}
|
||||
?disabled=${this.isCrawlRunning === null}
|
||||
>
|
||||
<sl-button
|
||||
size="small"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
?disabled=${isArchivingDisabled(this.org, true) ||
|
||||
this.isSubmitting}
|
||||
?loading=${this.isSubmitting}
|
||||
this.isSubmitting ||
|
||||
this.isCrawlRunning ||
|
||||
this.isCrawlRunning === null}
|
||||
?loading=${this.isSubmitting || this.isCrawlRunning === null}
|
||||
>
|
||||
${msg(html`Run Crawl`)}
|
||||
</sl-button>
|
||||
@ -608,6 +620,22 @@ export class WorkflowEditor extends BtrixElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private readonly renderCrawlStatus = (workflowId: string) => {
|
||||
if (!workflowId) return;
|
||||
|
||||
return html`
|
||||
<btrix-live-workflow-status
|
||||
class="mx-2"
|
||||
workflowId=${workflowId}
|
||||
@btrix-crawl-status-changed=${(
|
||||
e: CustomEvent<CrawlStatusChangedEventDetail>,
|
||||
) => {
|
||||
this.isCrawlRunning = e.detail.isCrawlRunning;
|
||||
}}
|
||||
></btrix-live-workflow-status>
|
||||
`;
|
||||
};
|
||||
|
||||
private renderSectionHeading(content: TemplateResult | string) {
|
||||
return html`
|
||||
<btrix-section-heading class="col-span-5">
|
||||
|
Loading…
Reference in New Issue
Block a user