diff --git a/frontend/src/features/crawl-workflows/workflow-list.ts b/frontend/src/features/crawl-workflows/workflow-list.ts index 7528d705..e141e218 100644 --- a/frontend/src/features/crawl-workflows/workflow-list.ts +++ b/frontend/src/features/crawl-workflows/workflow-list.ts @@ -244,7 +244,7 @@ export class WorkflowListItem extends BtrixElement { } e.preventDefault(); await this.updateComplete; - const href = `/orgs/${this.orgSlugState}/workflows/${this.workflow?.id}/${WorkflowTab.LatestCrawl}`; + const href = `/orgs/${this.orgSlugState}/workflows/${this.workflow?.id}/${this.workflow?.lastCrawlState === "failed" ? WorkflowTab.Logs : WorkflowTab.LatestCrawl}`; this.navigate.to(href); }} > diff --git a/frontend/src/pages/org/workflow-detail.ts b/frontend/src/pages/org/workflow-detail.ts index 1b6bd43a..c9cb9805 100644 --- a/frontend/src/pages/org/workflow-detail.ts +++ b/frontend/src/pages/org/workflow-detail.ts @@ -28,12 +28,13 @@ import { pageNav, type Breadcrumb } from "@/layouts/pageHeader"; import { WorkflowTab } from "@/routes"; import { deleteConfirmation, noData, notApplicable } from "@/strings/ui"; import type { APIPaginatedList, APIPaginationQuery } from "@/types/api"; -import { FAILED_STATES, type CrawlState } from "@/types/crawlState"; +import { type CrawlState } from "@/types/crawlState"; import { isApiError } from "@/utils/api"; import { DEFAULT_MAX_SCALE, inactiveCrawlStates, isActive, + isSkipped, isSuccessfullyFinished, } from "@/utils/crawler"; import { humanizeSchedule } from "@/utils/cron"; @@ -328,10 +329,12 @@ export class WorkflowDetail extends BtrixElement { return this.workflow?.isCrawlRunning && !this.isPaused; } - // Workflow is for a crawl that has failed or canceled - private get isUnsuccessfullyFinished() { - return (FAILED_STATES as readonly string[]).includes( - this.workflow?.lastCrawlState || "", + private get isSkippedOrCanceled() { + if (!this.workflow?.lastCrawlState) return null; + + return ( + this.workflow.lastCrawlState === "canceled" || + isSkipped({ state: this.workflow.lastCrawlState }) ); } @@ -678,6 +681,10 @@ export class WorkflowDetail extends BtrixElement { const logTotals = this.logTotalsTask.value; const authToken = this.authState?.headers.Authorization.split(" ")[1]; const disableDownload = this.isRunning; + const disableReplay = !latestCrawl.fileSize; + const disableLogs = !(logTotals?.errors || logTotals?.behaviors); + const replayHref = `/api/orgs/${this.orgId}/all-crawls/${latestCrawlId}/download?auth_bearer=${authToken}`; + const replayFilename = `browsertrix-${latestCrawlId}.wacz`; return html` ${msg("Download")} @@ -715,7 +722,7 @@ export class WorkflowDetail extends BtrixElement { slot="trigger" size="small" caret - ?disabled=${disableDownload} + ?disabled=${disableReplay && disableLogs} > ${msg("Download options")} ${msg("Item")} @@ -741,7 +748,7 @@ export class WorkflowDetail extends BtrixElement { { - if (!this.lastCrawlId || this.isUnsuccessfullyFinished) { + if (!this.lastCrawlId || this.isSkippedOrCanceled) { return this.renderInactiveCrawlMessage(); } @@ -1722,6 +1729,10 @@ export class WorkflowDetail extends BtrixElement { `; } + if (!isSuccessfullyFinished({ state: workflow.lastCrawlState })) { + return notApplicable; + } + return html`
${latestCrawl.reviewStatus || !this.isCrawler ? html` { + if (!workflow.lastCrawlId) return; + + if (workflow.lastCrawlState === "failed") { + return html`
+ + ${msg("View Error Logs")} + + +
`; + } + + return html`
+ + ${msg("View Crawl Details")} + + +
`; + }; + return html`
html`
${this.renderRunNowButton()}
`, )} - ${when( - this.lastCrawlId, - (id) => - html`
- - ${msg("View Crawl Details")} - - -
`, - )} + ${when(this.workflow, actionButton)}
`; } diff --git a/frontend/src/utils/crawler.ts b/frontend/src/utils/crawler.ts index 7d90fa3a..986acad5 100644 --- a/frontend/src/utils/crawler.ts +++ b/frontend/src/utils/crawler.ts @@ -33,11 +33,15 @@ export function isActive({ state }: Partial) { return (activeCrawlStates as readonly (typeof state)[]).includes(state); } -export function isSuccessfullyFinished({ state }: { state: string }) { +export function isSuccessfullyFinished({ state }: { state: string | null }) { return state && (SUCCESSFUL_STATES as readonly string[]).includes(state); } -export function isNotFailed({ state }: { state: string }) { +export function isSkipped({ state }: { state: string | null }) { + return state?.startsWith("skipped"); +} + +export function isNotFailed({ state }: { state: string | null }) { return ( state && !(FAILED_STATES as readonly string[]).some((str) => str === state) );