diff --git a/frontend/src/features/crawl-workflows/workflow-action-menu/workflow-action-menu.ts b/frontend/src/features/crawl-workflows/workflow-action-menu/workflow-action-menu.ts index 87093a59de..f242a433bc 100644 --- a/frontend/src/features/crawl-workflows/workflow-action-menu/workflow-action-menu.ts +++ b/frontend/src/features/crawl-workflows/workflow-action-menu/workflow-action-menu.ts @@ -10,6 +10,7 @@ import { BtrixElement } from "@/classes/BtrixElement"; import { ClipboardController } from "@/controllers/clipboard"; import { WorkflowTab } from "@/routes"; import type { Crawl, ListWorkflow, Workflow } from "@/types/crawler"; +import { downloadLink } from "@/utils/crawl-workflows/downloadLink"; import { isNotFailed, isSuccessfullyFinished } from "@/utils/crawler"; import { isArchivingDisabled } from "@/utils/orgs"; @@ -209,13 +210,14 @@ export class WorkflowActionMenu extends BtrixElement { private renderLatestCrawlMenu(latestCrawl: Crawl) { const authToken = this.authState?.headers.Authorization.split(" ")[1]; const logTotals = this.logTotals; + const download = downloadLink(latestCrawl, this.authState); return html` ${msg("Download Item")} diff --git a/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts b/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts index cdcd911e63..64386ce657 100644 --- a/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts +++ b/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts @@ -21,6 +21,8 @@ import type { } from "@/types/crawler"; import type { QARun } from "@/types/qa"; import { isApiError } from "@/utils/api"; +import { downloadLink } from "@/utils/crawl-workflows/downloadLink"; +import { hasFiles } from "@/utils/crawl-workflows/hasFiles"; import { isActive, isNotFailed, @@ -145,13 +147,6 @@ export class ArchivedItemDetail extends BtrixElement { private timerId?: number; - private get hasFiles(): boolean | null { - if (!this.item) return null; - if (!this.item.resources) return false; - - return this.item.resources.length > 0; - } - private get formattedFinishedDate() { if (!this.item) return; @@ -326,17 +321,7 @@ export class ArchivedItemDetail extends BtrixElement { case "files": sectionContent = this.renderPanel( html` ${this.renderTitle(this.tabLabels.files)} - - - - ${msg("Download Files")} - - `, + ${this.renderDownloadFilesButton()}`, this.renderFiles(), ); break; @@ -622,7 +607,7 @@ export class ArchivedItemDetail extends BtrixElement { private renderMenu() { if (!this.item) return; - const authToken = this.authState?.headers.Authorization.split(" ")[1]; + const download = downloadLink(this.item, this.authState); return html` @@ -657,8 +642,8 @@ export class ArchivedItemDetail extends BtrixElement { `, )} ${msg("Download Item")} @@ -763,7 +748,7 @@ export class ArchivedItemDetail extends BtrixElement { const config = JSON.stringify({ headers }); - const canReplay = this.hasFiles; + const canReplay = hasFiles(this.item); return html` @@ -977,10 +962,10 @@ export class ArchivedItemDetail extends BtrixElement { private renderFiles() { return html` - ${this.hasFiles + ${hasFiles(this.item) ? html`
    - ${this.item!.resources!.map( + ${this.item.resources.map( (file) => html`
  • + + + ${singleFile ? msg("Download File") : msg("Download Files")} + + `; + } + private renderLogs() { if (!this.itemId) return; diff --git a/frontend/src/pages/org/workflow-detail.ts b/frontend/src/pages/org/workflow-detail.ts index 7fcb741095..2e3a623a97 100644 --- a/frontend/src/pages/org/workflow-detail.ts +++ b/frontend/src/pages/org/workflow-detail.ts @@ -37,6 +37,7 @@ import type { APIPaginatedList, APIPaginationQuery } from "@/types/api"; import { type CrawlState } from "@/types/crawlState"; import { type StorageSeedFile } from "@/types/workflow"; import { isApiError } from "@/utils/api"; +import { downloadLink } from "@/utils/crawl-workflows/downloadLink"; import { settingsForDuplicate } from "@/utils/crawl-workflows/settingsForDuplicate"; import { DEFAULT_MAX_SCALE, @@ -718,8 +719,7 @@ export class WorkflowDetail extends BtrixElement { const authToken = this.authState?.headers.Authorization.split(" ")[1]; const disableDownload = this.isRunning; const disableReplay = !latestCrawl.fileSize; - const replayHref = `/api/orgs/${this.orgId}/all-crawls/${latestCrawlId}/download?auth_bearer=${authToken}`; - const replayFilename = `browsertrix-${latestCrawlId}.wacz`; + const download = downloadLink(latestCrawl, this.authState); return html` - - - - ${msg("Download")} - - + + ${msg("Download")} + ${msg("Item")} diff --git a/frontend/src/types/utils.ts b/frontend/src/types/utils.ts index 3d15b69317..4d1a70abab 100644 --- a/frontend/src/types/utils.ts +++ b/frontend/src/types/utils.ts @@ -22,6 +22,9 @@ export type Range = Exclude< Enumerate >; +/** Array with at least one item */ +export type NonEmptyArray = [T, ...T[]]; + export enum SortDirection { Descending = -1, Ascending = 1, diff --git a/frontend/src/utils/crawl-workflows/downloadLink.ts b/frontend/src/utils/crawl-workflows/downloadLink.ts new file mode 100644 index 0000000000..e1c96a61fb --- /dev/null +++ b/frontend/src/utils/crawl-workflows/downloadLink.ts @@ -0,0 +1,24 @@ +import type { Auth } from "@/types/auth"; +import type { ArchivedItem } from "@/types/crawler"; +import { hasFiles } from "@/utils/crawl-workflows/hasFiles"; + +/** + * Get link to download archived item + */ +export function downloadLink( + item?: ArchivedItem, + authState?: Auth | null, +): { path: string; name: string } { + if (!hasFiles(item)) return { path: "", name: "" }; + + if (item.resources.length > 1) { + return { + path: `/api/orgs/${item.oid}/all-crawls/${item.id}/download?auth_bearer=${authState?.headers.Authorization.split(" ")[1]}`, + name: `${item.id}.wacz`, + }; + } + + const { path, name } = item.resources[0]; + + return { path, name }; +} diff --git a/frontend/src/utils/crawl-workflows/hasFiles.ts b/frontend/src/utils/crawl-workflows/hasFiles.ts new file mode 100644 index 0000000000..cc9793482c --- /dev/null +++ b/frontend/src/utils/crawl-workflows/hasFiles.ts @@ -0,0 +1,14 @@ +import type { ArchivedItem } from "@/types/crawler"; +import type { NonEmptyArray } from "@/types/utils"; + +/** + * Check whether archived item has at least one WACZ file + */ +export function hasFiles(item?: ArchivedItem): item is ArchivedItem & { + resources: NonEmptyArray[number]>; +} { + if (!item) return false; + if (!item.resources) return false; + + return Boolean(item.resources[0]); +}