Improve sorting workflows by lastUpdated (#826)

* Precompute config crawl stats

Includes a database migration to move preciously dynamically computed
crawl stats for workflows into the CrawlConfig model.

* Add lastRun sorting option and enable it by default

* Add modified as final sort key to order non-run workflows

* Remove currCrawl* fields and update frontend accordingly

* Add isCrawlRunning field to backend and use in frontend
This commit is contained in:
Tessa Walsh 2023-05-22 18:42:30 -04:00 committed by GitHub
parent 60fac2b677
commit bd8b306fbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 265 additions and 110 deletions

View File

@ -191,6 +191,10 @@ class CrawlConfig(CrawlConfigCore):
lastCrawlState: Optional[str]
lastCrawlSize: Optional[int]
lastRun: Optional[datetime]
isCrawlRunning: Optional[bool] = False
def get_raw_config(self):
"""serialize config for browsertrix-crawler"""
return self.config.dict(exclude_unset=True, exclude_none=True)
@ -198,13 +202,9 @@ class CrawlConfig(CrawlConfigCore):
# ============================================================================
class CrawlConfigOut(CrawlConfig):
"""Crawl Config Output, includes currCrawlId of running crawl"""
"""Crawl Config Output"""
currCrawlId: Optional[str]
currCrawlStartTime: Optional[datetime]
currCrawlState: Optional[str]
currCrawlSize: Optional[int] = 0
currCrawlStopping: Optional[bool] = False
lastCrawlStopping: Optional[bool] = False
profileName: Optional[str]
@ -281,6 +281,10 @@ class CrawlConfigOps:
[("oid", pymongo.ASCENDING), ("tags", pymongo.ASCENDING)]
)
await self.crawl_configs.create_index(
[("lastRun", pymongo.DESCENDING), ("modified", pymongo.DESCENDING)]
)
await self.config_revs.create_index([("cid", pymongo.HASHED)])
await self.config_revs.create_index(
@ -480,7 +484,7 @@ class CrawlConfigOps:
description: str = None,
tags: Optional[List[str]] = None,
schedule: Optional[bool] = None,
sort_by: str = None,
sort_by: str = "lastRun",
sort_direction: int = -1,
):
"""Get all crawl configs for an organization is a member of"""
@ -525,12 +529,31 @@ class CrawlConfigOps:
aggregate.extend([{"$match": {"firstSeed": first_seed}}])
if sort_by:
if sort_by not in ("created", "modified", "firstSeed", "lastCrawlTime"):
if sort_by not in (
"created",
"modified",
"firstSeed",
"lastCrawlTime",
"lastCrawlStartTime",
"lastRun",
):
raise HTTPException(status_code=400, detail="invalid_sort_by")
if sort_direction not in (1, -1):
raise HTTPException(status_code=400, detail="invalid_sort_direction")
aggregate.extend([{"$sort": {sort_by: sort_direction}}])
sort_query = {sort_by: sort_direction}
# Add modified as final sort key to give some order to workflows that
# haven't been run yet.
if sort_by in (
"firstSeed",
"lastCrawlTime",
"lastCrawlStartTime",
"lastRun",
):
sort_query = {sort_by: sort_direction, "modified": sort_direction}
aggregate.extend([{"$sort": sort_query}])
aggregate.extend(
[
@ -641,11 +664,9 @@ class CrawlConfigOps:
if not crawl:
return
crawlconfig.currCrawlId = crawl.id
crawlconfig.currCrawlStartTime = crawl.started
crawlconfig.currCrawlState = crawl.state
crawlconfig.currCrawlSize = crawl.stats.get("size", 0) if crawl.stats else 0
crawlconfig.currCrawlStopping = crawl.stopping
crawlconfig.lastCrawlState = crawl.state
crawlconfig.lastCrawlSize = crawl.stats.get("size", 0) if crawl.stats else 0
crawlconfig.lastCrawlStopping = crawl.stopping
async def get_crawl_config_out(self, cid: uuid.UUID, org: Organization):
"""Return CrawlConfigOut, including state of currently running crawl, if active
@ -890,10 +911,37 @@ async def inc_crawl_count(crawl_configs, cid: uuid.UUID):
)
# ============================================================================
async def set_config_current_crawl_info(
crawl_configs, cid: uuid.UUID, crawl_id: str, crawl_start: datetime
):
"""Set current crawl info in config when crawl begins"""
result = await crawl_configs.find_one_and_update(
{"_id": cid, "inactive": {"$ne": True}},
{
"$set": {
"lastCrawlId": crawl_id,
"lastCrawlStartTime": crawl_start,
"lastCrawlTime": None,
"lastRun": crawl_start,
"isCrawlRunning": True,
}
},
return_document=pymongo.ReturnDocument.AFTER,
)
if result:
return True
return False
# ============================================================================
# pylint: disable=too-many-locals
async def update_config_crawl_stats(crawl_configs, crawls, cid: uuid.UUID):
"""re-calculate and update crawl statistics for config"""
"""Re-calculate and update crawl statistics for config.
Should only be called when a crawl completes from operator or on migration
when no crawls are running.
"""
update_query = {
"crawlCount": 0,
"totalSize": 0,
@ -903,6 +951,8 @@ async def update_config_crawl_stats(crawl_configs, crawls, cid: uuid.UUID):
"lastCrawlTime": None,
"lastCrawlState": None,
"lastCrawlSize": None,
"lastCrawlStopping": False,
"isCrawlRunning": False,
}
match_query = {"cid": cid, "finished": {"$ne": None}}
@ -912,15 +962,21 @@ async def update_config_crawl_stats(crawl_configs, crawls, cid: uuid.UUID):
update_query["crawlCount"] = len(results)
last_crawl = results[0]
last_crawl_finished = last_crawl.get("finished")
update_query["lastCrawlId"] = str(last_crawl.get("_id"))
update_query["lastCrawlStartTime"] = last_crawl.get("started")
update_query["lastStartedBy"] = last_crawl.get("userid")
update_query["lastCrawlTime"] = last_crawl.get("finished")
update_query["lastCrawlTime"] = last_crawl_finished
update_query["lastCrawlState"] = last_crawl.get("state")
update_query["lastCrawlSize"] = sum(
file_.get("size", 0) for file_ in last_crawl.get("files", [])
)
if last_crawl_finished:
update_query["lastRun"] = last_crawl_finished
total_size = 0
for res in results:
files = res.get("files", [])

View File

@ -18,7 +18,13 @@ from pydantic import BaseModel, UUID4, conint, HttpUrl
from redis import asyncio as aioredis, exceptions
import pymongo
from .crawlconfigs import Seed, CrawlConfigCore, CrawlConfig, UpdateCrawlConfig
from .crawlconfigs import (
Seed,
CrawlConfigCore,
CrawlConfig,
UpdateCrawlConfig,
set_config_current_crawl_info,
)
from .db import BaseMongoModel
from .orgs import Organization, MAX_CRAWL_SCALE
from .pagination import DEFAULT_PAGE_SIZE, paginated_format
@ -536,7 +542,13 @@ class CrawlOps:
async def add_new_crawl(self, crawl_id: str, crawlconfig: CrawlConfig, user: User):
"""initialize new crawl"""
return await add_new_crawl(self.crawls, crawl_id, crawlconfig, user.id)
new_crawl = await add_new_crawl(self.crawls, crawl_id, crawlconfig, user.id)
return await set_config_current_crawl_info(
self.crawl_configs.crawl_configs,
crawlconfig.id,
new_crawl["id"],
new_crawl["started"],
)
async def update_crawl(self, crawl_id: str, org: Organization, update: UpdateCrawl):
"""Update existing crawl (tags and notes only for now)"""
@ -835,6 +847,8 @@ async def add_new_crawl(
crawls, crawl_id: str, crawlconfig: CrawlConfig, userid: UUID4, manual=True
):
"""initialize new crawl"""
started = ts_now()
crawl = Crawl(
id=crawl_id,
state="starting",
@ -849,13 +863,13 @@ async def add_new_crawl(
schedule=crawlconfig.schedule,
crawlTimeout=crawlconfig.crawlTimeout,
manual=manual,
started=ts_now(),
started=started,
tags=crawlconfig.tags,
)
try:
await crawls.insert_one(crawl.to_dict())
return True
result = await crawls.insert_one(crawl.to_dict())
return {"id": str(result.inserted_id), "started": started}
except pymongo.errors.DuplicateKeyError:
# print(f"Crawl Already Added: {crawl.id} - {crawl.state}")
return False

View File

@ -6,7 +6,11 @@ import uuid
from .k8sapi import K8sAPI
from .db import init_db
from .crawlconfigs import get_crawl_config, inc_crawl_count
from .crawlconfigs import (
get_crawl_config,
inc_crawl_count,
set_config_current_crawl_info,
)
from .crawls import add_new_crawl
from .utils import register_exit_handler
@ -43,9 +47,16 @@ class ScheduledJob(K8sAPI):
# db create
await inc_crawl_count(self.crawlconfigs, crawlconfig.id)
await add_new_crawl(
new_crawl = await add_new_crawl(
self.crawls, crawl_id, crawlconfig, uuid.UUID(userid), manual=False
)
# pylint: disable=duplicate-code
await set_config_current_crawl_info(
self.crawlconfigs.crawl_configs,
crawlconfig.id,
new_crawl["id"],
new_crawl["started"],
)
print("Crawl Created: " + crawl_id)

View File

@ -232,6 +232,7 @@ def test_workflow_total_size_and_last_crawl_stats(
assert workflow["lastStartedByName"]
assert workflow["lastCrawlTime"]
assert workflow["lastCrawlState"]
assert workflow["lastRun"]
assert workflow["lastCrawlSize"] > 0
if last_crawl_id == admin_crawl_id:
@ -254,4 +255,5 @@ def test_workflow_total_size_and_last_crawl_stats(
assert data["lastStartedByName"]
assert data["lastCrawlTime"]
assert data["lastCrawlState"]
assert data["lastRun"]
assert data["lastCrawlSize"] > 0

View File

@ -433,11 +433,12 @@ def test_sort_crawl_configs(
last_crawl_time = None
for config in items:
if not config.get("lastCrawlTime"):
config_last_time = config.get("lastCrawlTime")
if not config_last_time:
continue
if last_crawl_time:
assert config["lastCrawlTime"] <= last_crawl_time
last_crawl_time = config["lastCrawlTime"]
elif last_crawl_time and config_last_time:
assert config_last_time <= last_crawl_time
last_crawl_time = config_last_time
# Sort by lastCrawlTime, ascending
r = requests.get(
@ -449,11 +450,80 @@ def test_sort_crawl_configs(
last_crawl_time = None
for config in items:
if not config.get("lastCrawlTime"):
config_last_time = config.get("lastCrawlTime")
if not config_last_time:
continue
if last_crawl_time:
assert config["lastCrawlTime"] >= last_crawl_time
last_crawl_time = config["lastCrawlTime"]
elif last_crawl_time and config_last_time:
assert config_last_time >= last_crawl_time
last_crawl_time = config_last_time
# Sort by lastCrawlStartTime
r = requests.get(
f"{API_PREFIX}/orgs/{default_org_id}/crawlconfigs?sortBy=lastCrawlStartTime",
headers=crawler_auth_headers,
)
data = r.json()
items = data["items"]
last_crawl_time = None
for config in items:
config_last_time = config.get("lastCrawlStartTime")
if not config_last_time:
continue
elif last_crawl_time and config_last_time:
assert config_last_time <= last_crawl_time
last_crawl_time = config_last_time
# Sort by lastCrawlStartTime, ascending
r = requests.get(
f"{API_PREFIX}/orgs/{default_org_id}/crawlconfigs?sortBy=lastCrawlStartTime&sortDirection=1",
headers=crawler_auth_headers,
)
data = r.json()
items = data["items"]
last_crawl_time = None
for config in items:
config_last_time = config.get("lastCrawlStartTime")
if not config_last_time:
continue
elif last_crawl_time and config_last_time:
assert config_last_time >= last_crawl_time
last_crawl_time = config_last_time
# Sort by lastRun
r = requests.get(
f"{API_PREFIX}/orgs/{default_org_id}/crawlconfigs?sortBy=lastRun",
headers=crawler_auth_headers,
)
data = r.json()
items = data["items"]
last_updated_time = None
for config in items:
config_last_updated = config.get("lastRun")
if not config_last_updated:
continue
elif last_updated_time and config_last_updated:
assert config_last_updated <= last_updated_time
last_updated_time = config_last_updated
# Sort by lastRun, ascending
r = requests.get(
f"{API_PREFIX}/orgs/{default_org_id}/crawlconfigs?sortBy=lastRun&sortDirection=1",
headers=crawler_auth_headers,
)
data = r.json()
items = data["items"]
last_updated_time = None
for config in items:
config_last_updated = config.get("lastRun")
if not config_last_updated:
continue
elif last_updated_time and config_last_updated:
assert config_last_updated >= last_updated_time
last_updated_time = config_last_updated
# Invalid sort value
r = requests.get(

View File

@ -255,7 +255,7 @@ export class WorkflowListItem extends LitElement {
role="button"
href=${`/orgs/${this.workflow?.oid}/workflows/crawl/${
this.workflow?.id
}#${this.workflow?.currCrawlId ? "watch" : "artifacts"}`}
}#${this.workflow?.isCrawlRunning ? "watch" : "artifacts"}`}
@click=${async (e: MouseEvent) => {
e.preventDefault();
await this.updateComplete;
@ -294,20 +294,19 @@ export class WorkflowListItem extends LitElement {
(workflow) =>
html`
<btrix-crawl-status
state=${workflow.currCrawlState ||
workflow.lastCrawlState ||
state=${workflow.lastCrawlState ||
msg("No Crawls Yet")}
?stopping=${workflow.currCrawlStopping}
?stopping=${workflow.lastCrawlStopping}
></btrix-crawl-status>
`
)}
</div>
<div class="desc duration">
${this.safeRender((workflow) => {
if (workflow.currCrawlStartTime) {
if (workflow.lastCrawlStartTime) {
const diff =
new Date().valueOf() -
new Date(`${workflow.currCrawlStartTime}Z`).valueOf();
new Date(`${workflow.lastCrawlStartTime}Z`).valueOf();
if (diff < 1000) {
return "";
}
@ -333,7 +332,7 @@ export class WorkflowListItem extends LitElement {
<div class="col">
<div class="detail">
${this.safeRender((workflow) => {
if (workflow.totalSize && workflow.currCrawlSize) {
if (workflow.isCrawlRunning && workflow.totalSize && workflow.lastCrawlSize) {
return html`<sl-format-bytes
value=${workflow.totalSize}
display="narrow"
@ -341,15 +340,21 @@ export class WorkflowListItem extends LitElement {
<span class="currCrawlSize">
+
<sl-format-bytes
value=${workflow.currCrawlSize}
value=${workflow.lastCrawlSize}
display="narrow"
></sl-format-bytes>
</span>`;
}
if (workflow.currCrawlSize) {
if (workflow.totalSize && workflow.lastCrawlSize) {
return html`<sl-format-bytes
value=${workflow.totalSize}
display="narrow"
></sl-format-bytes>`;
}
if (workflow.isCrawlRunning && workflow.lastCrawlSize) {
return html`<span class="currCrawlSize">
<sl-format-bytes
value=${workflow.currCrawlSize}
value=${workflow.lastCrawlSize}
display="narrow"
></sl-format-bytes>
</span>`;

View File

@ -63,13 +63,13 @@ export class WorkflowDetail extends LiteElement {
private crawls?: APIPaginatedList; // Only inactive crawls
@state()
private currentCrawlId: Workflow["currCrawlId"] = null;
private lastCrawlId: Workflow["lastCrawlId"] = null;
@state()
private currentCrawlStartTime: Workflow["currCrawlStartTime"] = null;
private lastCrawlStartTime: Workflow["lastCrawlStartTime"] = null;
@state()
private currentCrawlStats?: Crawl["stats"];
private lastCrawlStats?: Crawl["stats"];
@state()
private activePanel: Tab = SECTIONS[0];
@ -150,8 +150,8 @@ export class WorkflowDetail extends LiteElement {
this.stopPoll();
}
if (
changedProperties.get("currentCrawlId") &&
!this.currentCrawlId &&
changedProperties.get("lastCrawlId") &&
!this.lastCrawlId &&
this.activePanel === "watch"
) {
this.handleCrawlRunEnd();
@ -244,9 +244,9 @@ export class WorkflowDetail extends LiteElement {
try {
this.getWorkflowPromise = this.getWorkflow();
this.workflow = await this.getWorkflowPromise;
this.currentCrawlId = this.workflow.currCrawlId;
this.currentCrawlStartTime = this.workflow.currCrawlStartTime;
if (this.currentCrawlId) {
this.lastCrawlId = this.workflow.lastCrawlId;
this.lastCrawlStartTime = this.workflow.lastCrawlStartTime;
if (this.lastCrawlId) {
this.fetchCurrentCrawlStats();
}
} catch (e: any) {
@ -417,7 +417,7 @@ export class WorkflowDetail extends LiteElement {
</btrix-observable>
${this.renderTab("artifacts")}
${this.renderTab("watch", { disabled: !this.currentCrawlId })}
${this.renderTab("watch", { disabled: !this.lastCrawlId })}
${this.renderTab("settings")}
<btrix-tab-panel name="artifacts"
@ -428,7 +428,7 @@ export class WorkflowDetail extends LiteElement {
this.getWorkflowPromise?.then(
() => html`
${when(this.activePanel === "watch", () =>
this.currentCrawlId
this.lastCrawlId
? html` <div class="border rounded-lg py-2 mb-5 h-14">
${this.renderCurrentCrawl()}
</div>
@ -455,7 +455,7 @@ export class WorkflowDetail extends LiteElement {
() =>
html`
<span class="text-neutral-500"
>(${this.crawls!.total.toLocaleString()}${this.currentCrawlId
>(${this.crawls!.total.toLocaleString()}${this.workflow?.isCrawlRunning
? html`<span class="text-success"> + 1</span>`
: ""})</span
>
@ -479,7 +479,7 @@ export class WorkflowDetail extends LiteElement {
return html` <h3>${this.tabLabels[this.activePanel]}</h3>
<sl-button
size="small"
?disabled=${this.workflow?.currCrawlState !== "running"}
?disabled=${this.workflow?.isCrawlRunning}
@click=${() => (this.openDialogName = "scale")}
>
<sl-icon name="plus-slash-minus" slot="prefix"></sl-icon>
@ -545,15 +545,15 @@ export class WorkflowDetail extends LiteElement {
return html`
${when(
this.currentCrawlId,
this.workflow?.isCrawlRunning,
() => html`
<sl-button-group class="mr-2">
<sl-button
size="small"
@click=${() => (this.openDialogName = "stop")}
?disabled=${!this.currentCrawlId ||
?disabled=${!this.lastCrawlId ||
this.isCancelingOrStoppingCrawl ||
this.workflow?.currCrawlStopping}
this.workflow?.lastCrawlStopping}
>
<sl-icon name="dash-circle" slot="prefix"></sl-icon>
<span>${msg("Stop")}</span>
@ -561,7 +561,7 @@ export class WorkflowDetail extends LiteElement {
<sl-button
size="small"
@click=${() => (this.openDialogName = "cancel")}
?disabled=${!this.currentCrawlId ||
?disabled=${!this.lastCrawlId ||
this.isCancelingOrStoppingCrawl}
>
<sl-icon
@ -592,13 +592,13 @@ export class WorkflowDetail extends LiteElement {
>
<sl-menu>
${when(
this.currentCrawlId,
this.workflow?.isCrawlRunning,
// HACK shoelace doesn't current have a way to override non-hover
// color without resetting the --sl-color-neutral-700 variable
() => html`
<sl-menu-item
@click=${() => (this.openDialogName = "stop")}
?disabled=${workflow.currCrawlStopping ||
?disabled=${workflow.lastCrawlStopping ||
this.isCancelingOrStoppingCrawl}
>
<sl-icon name="dash-circle" slot="prefix"></sl-icon>
@ -624,7 +624,7 @@ export class WorkflowDetail extends LiteElement {
`
)}
${when(
workflow.currCrawlState === "running",
workflow.isCrawlRunning,
() => html`
<sl-divider></sl-divider>
<sl-menu-item @click=${() => (this.openDialogName = "scale")}>
@ -660,7 +660,7 @@ export class WorkflowDetail extends LiteElement {
<sl-icon name="files" slot="prefix"></sl-icon>
${msg("Duplicate Workflow")}
</sl-menu-item>
${when(!this.currentCrawlId, () => {
${when(!this.lastCrawlId, () => {
const shouldDeactivate = workflow.crawlCount && !workflow.inactive;
return html`
<sl-divider></sl-divider>
@ -693,10 +693,9 @@ export class WorkflowDetail extends LiteElement {
msg("Status"),
() => html`
<btrix-crawl-status
state=${this.workflow!.currCrawlState ||
this.workflow!.lastCrawlState ||
state=${this.workflow!.lastCrawlState ||
msg("No Crawls Yet")}
?stopping=${this.workflow?.currCrawlStopping}
?stopping=${this.workflow?.lastCrawlStopping}
></btrix-crawl-status>
`
)}
@ -805,7 +804,7 @@ export class WorkflowDetail extends LiteElement {
</div>
${when(
this.currentCrawlId,
this.workflow?.isCrawlRunning,
() => html`<div class="mb-4">
<btrix-alert variant="success" class="text-sm">
${msg(
@ -887,28 +886,28 @@ export class WorkflowDetail extends LiteElement {
return html`
<dl class="px-3 md:px-0 md:flex justify-evenly">
${this.renderDetailItem(msg("Pages Crawled"), () =>
this.currentCrawlStats
this.lastCrawlStats
? msg(
str`${this.numberFormatter.format(
+(this.currentCrawlStats.done || 0)
+(this.lastCrawlStats.done || 0)
)} / ${this.numberFormatter.format(
+(this.currentCrawlStats.found || 0)
+(this.lastCrawlStats.found || 0)
)}`
)
: html`<sl-spinner></sl-spinner>`
)}
${this.renderDetailItem(msg("Run Duration"), () =>
this.currentCrawlStartTime
this.lastCrawlStartTime
? RelativeDuration.humanize(
new Date().valueOf() -
new Date(`${this.currentCrawlStartTime}Z`).valueOf()
new Date(`${this.lastCrawlStartTime}Z`).valueOf()
)
: skeleton
)}
${this.renderDetailItem(msg("Crawl Size"), () =>
this.workflow
? html`<sl-format-bytes
value=${this.workflow.currCrawlSize || 0}
value=${this.workflow.lastCrawlSize || 0}
display="narrow"
></sl-format-bytes>`
: skeleton
@ -923,12 +922,12 @@ export class WorkflowDetail extends LiteElement {
};
private renderWatchCrawl = () => {
if (!this.authState || !this.workflow?.currCrawlState) return "";
if (!this.authState || !(this.workflow?.lastCrawlState)) return "";
const isStarting = this.workflow.currCrawlState === "starting";
const isWaiting = this.workflow.currCrawlState === "waiting";
const isRunning = this.workflow.currCrawlState === "running";
const isStopping = this.workflow.currCrawlStopping;
const isStarting = this.workflow.lastCrawlState === "starting";
const isWaiting = this.workflow.lastCrawlState === "waiting";
const isRunning = this.workflow.lastCrawlState === "running";
const isStopping = this.workflow.lastCrawlStopping;
const authToken = this.authState.headers.Authorization.split(" ")[1];
return html`
@ -942,7 +941,7 @@ export class WorkflowDetail extends LiteElement {
)}
</p>
</div>`
: isActive(this.workflow.currCrawlState)
: isActive(this.workflow.lastCrawlState)
? html`
${isStopping
? html`
@ -962,7 +961,7 @@ export class WorkflowDetail extends LiteElement {
<btrix-screencast
authToken=${authToken}
orgId=${this.orgId}
crawlId=${this.currentCrawlId}
crawlId=${this.lastCrawlId}
scale=${this.workflow!.scale}
></btrix-screencast>
</div>
@ -1048,11 +1047,11 @@ export class WorkflowDetail extends LiteElement {
</header>
${when(
this.currentCrawlId,
this.lastCrawlId,
() => html`
<btrix-crawl-queue
orgId=${this.orgId}
crawlId=${this.currentCrawlId}
crawlId=${this.lastCrawlId}
.authState=${this.authState}
></btrix-crawl-queue>
`
@ -1069,10 +1068,10 @@ export class WorkflowDetail extends LiteElement {
${this.workflow && this.isDialogVisible
? html`<btrix-exclusion-editor
orgId=${this.orgId}
crawlId=${ifDefined(this.currentCrawlId)}
crawlId=${ifDefined(this.lastCrawlId)}
.config=${this.workflow.config}
.authState=${this.authState}
?isActiveCrawl=${isActive(this.workflow.currCrawlState!)}
?isActiveCrawl=${isActive(this.workflow.lastCrawlState!)}
@on-success=${this.handleExclusionChange}
></btrix-exclusion-editor>`
: ""}
@ -1159,12 +1158,12 @@ export class WorkflowDetail extends LiteElement {
}
private async scale(value: Crawl["scale"]) {
if (!this.currentCrawlId) return;
if (!this.lastCrawlId) return;
this.isSubmittingUpdate = true;
try {
const data = await this.apiFetch(
`/orgs/${this.orgId}/crawls/${this.currentCrawlId}/scale`,
`/orgs/${this.orgId}/crawls/${this.lastCrawlId}/scale`,
this.authState!,
{
method: "POST",
@ -1233,12 +1232,12 @@ export class WorkflowDetail extends LiteElement {
}
private async fetchCurrentCrawlStats() {
if (!this.currentCrawlId) return;
if (!this.lastCrawlId) return;
try {
// TODO see if API can pass stats in GET workflow
const { stats } = await this.getCrawl(this.currentCrawlId);
this.currentCrawlStats = stats;
const { stats } = await this.getCrawl(this.lastCrawlId);
this.lastCrawlStats = stats;
} catch (e) {
// TODO handle error
console.debug(e);
@ -1349,13 +1348,13 @@ export class WorkflowDetail extends LiteElement {
}
private async cancel() {
if (!this.currentCrawlId) return;
if (!this.lastCrawlId) return;
this.isCancelingOrStoppingCrawl = true;
try {
const data = await this.apiFetch(
`/orgs/${this.orgId}/crawls/${this.currentCrawlId}/cancel`,
`/orgs/${this.orgId}/crawls/${this.lastCrawlId}/cancel`,
this.authState!,
{
method: "POST",
@ -1378,13 +1377,13 @@ export class WorkflowDetail extends LiteElement {
}
private async stop() {
if (!this.currentCrawlId) return;
if (!this.lastCrawlId) return;
this.isCancelingOrStoppingCrawl = true;
try {
const data = await this.apiFetch(
`/orgs/${this.orgId}/crawls/${this.currentCrawlId}/stop`,
`/orgs/${this.orgId}/crawls/${this.lastCrawlId}/stop`,
this.authState!,
{
method: "POST",
@ -1415,9 +1414,9 @@ export class WorkflowDetail extends LiteElement {
method: "POST",
}
);
this.currentCrawlId = data.started;
this.lastCrawlId = data.started;
// remove 'Z' from timestamp to match API response
this.currentCrawlStartTime = new Date().toISOString().slice(0, -1);
this.lastCrawlStartTime = new Date().toISOString().slice(0, -1);
this.fetchWorkflow();
this.goToTab("watch");

View File

@ -355,20 +355,20 @@ export class WorkflowsList extends LiteElement {
private renderMenuItems(workflow: Workflow) {
return html`
${when(
workflow.currCrawlId,
workflow.isCrawlRunning,
// HACK shoelace doesn't current have a way to override non-hover
// color without resetting the --sl-color-neutral-700 variable
() => html`
<sl-menu-item
@click=${() => this.stop(workflow.currCrawlId)}
?disabled=${workflow.currCrawlStopping}
@click=${() => this.stop(workflow.lastCrawlId)}
?disabled=${workflow.lastCrawlStopping}
>
<sl-icon name="dash-circle" slot="prefix"></sl-icon>
${msg("Stop Crawl")}
</sl-menu-item>
<sl-menu-item
style="--sl-color-neutral-700: var(--danger)"
@click=${() => this.cancel(workflow.currCrawlId)}
@click=${() => this.cancel(workflow.lastCrawlId)}
>
<sl-icon name="x-octagon" slot="prefix"></sl-icon>
${msg("Cancel & Discard Crawl")}
@ -385,7 +385,9 @@ export class WorkflowsList extends LiteElement {
`
)}
${when(
workflow.currCrawlState === "running",
workflow.isCrawlRunning,
// HACK shoelace doesn't current have a way to override non-hover
// color without resetting the --sl-color-neutral-700 variable
() => html`
<sl-divider></sl-divider>
<sl-menu-item
@ -436,7 +438,7 @@ export class WorkflowsList extends LiteElement {
<sl-icon name="files" slot="prefix"></sl-icon>
${msg("Duplicate Workflow")}
</sl-menu-item>
${when(!workflow.currCrawlId, () => {
${when(workflow.isCrawlRunning, () => {
const shouldDeactivate = workflow.crawlCount && !workflow.inactive;
return html`
<sl-divider></sl-divider>
@ -482,7 +484,6 @@ export class WorkflowsList extends LiteElement {
return new Date(
Math.max(
...[
workflow.currCrawlStartTime,
workflow.lastCrawlTime,
workflow.lastCrawlStartTime,
workflow.modified,
@ -597,7 +598,7 @@ export class WorkflowsList extends LiteElement {
}
}
private async cancel(crawlId: Workflow["currCrawlId"]) {
private async cancel(crawlId: Workflow["lastCrawlId"]) {
if (!crawlId) return;
if (window.confirm(msg("Are you sure you want to cancel the crawl?"))) {
const data = await this.apiFetch(
@ -619,7 +620,7 @@ export class WorkflowsList extends LiteElement {
}
}
private async stop(crawlId: Workflow["currCrawlId"]) {
private async stop(crawlId: Workflow["lastCrawlId"]) {
if (!crawlId) return;
if (window.confirm(msg("Are you sure you want to stop the crawl?"))) {
const data = await this.apiFetch(

View File

@ -61,20 +61,17 @@ export type Workflow = CrawlConfig & {
modified: string; // Date string
crawlCount: number;
crawlAttemptCount: number;
lastCrawlId: string; // last finished crawl
lastCrawlStartTime: string;
lastCrawlTime: string; // when last crawl finished
lastCrawlId: string | null; // last finished or current crawl
lastCrawlStartTime: string | null;
lastCrawlTime: string | null; // when last crawl finished
lastCrawlState: CrawlState;
lastCrawlSize: number | null;
lastStartedByName: string | null;
currCrawlId: string | null;
currCrawlState: CrawlState | null;
currCrawlStartTime: string | null;
currCrawlSize: number | null;
currCrawlStopping: boolean | null;
lastCrawlStopping: boolean | null;
totalSize: string | null;
inactive: boolean;
firstSeed: string;
isCrawlRunning: boolean | null;
};
export type Profile = {