${this.renderPanel(msg("Overview"), this.renderOverview(), [
tw`rounded-lg border p-4`,
@@ -673,20 +687,52 @@ export class ArchivedItemDetail extends BtrixElement {
}
private renderHeader() {
- return html`
-
`;
+ };
+ return pageHeader({
+ title: this.item ? renderName(this.item) : undefined,
+ secondary: when(this.item, badges, badgesSkeleton),
+ actions: this.isCrawler
+ ? this.item
+ ? this.renderMenu()
+ : html`
`
+ : undefined,
+ });
}
private renderMenu() {
@@ -831,6 +877,16 @@ export class ArchivedItemDetail extends BtrixElement {
}
private renderReplay() {
+ const dedupeDependent =
+ this.item && isCrawl(this.item) && this.item.requiresCrawls.length;
+
+ return html`
+ ${dedupeDependent ? dedupeReplayNotice() : nothing}
+
+ `;
+ }
+
+ private renderRWP() {
if (!this.item) return;
const replaySource = `/api/orgs/${this.item.oid}/${
this.item.type === "upload" ? "uploads" : "crawls"
@@ -1049,7 +1105,11 @@ export class ArchivedItemDetail extends BtrixElement {
}
private renderFiles() {
+ const dedupeDependent =
+ this.item && isCrawl(this.item) && this.item.requiresCrawls.length;
+
return html`
+ ${this.hasFiles && dedupeDependent ? dedupeFilesNotice() : nothing}
${this.hasFiles
? html`
diff --git a/frontend/src/pages/org/workflow-detail.ts b/frontend/src/pages/org/workflow-detail.ts
index aae3defed9..113cb8f906 100644
--- a/frontend/src/pages/org/workflow-detail.ts
+++ b/frontend/src/pages/org/workflow-detail.ts
@@ -20,6 +20,7 @@ import type { Alert } from "@/components/ui/alert";
import { parsePage, type PageChangeEvent } from "@/components/ui/pagination";
import { ClipboardController } from "@/controllers/clipboard";
import { CrawlStatus } from "@/features/archived-items/crawl-status";
+import { dedupeReplayNotice } from "@/features/archived-items/templates/dedupe-replay-notice";
import { ExclusionEditor } from "@/features/crawl-workflows/exclusion-editor";
import { ShareableNotice } from "@/features/crawl-workflows/templates/shareable-notice";
import {
@@ -29,7 +30,7 @@ import {
import type { BtrixChangeCrawlStateFilterEvent } from "@/features/crawls/crawl-state-filter";
import { pageError } from "@/layouts/pageError";
import { pageNav, type Breadcrumb } from "@/layouts/pageHeader";
-import { WorkflowTab } from "@/routes";
+import { CommonTab, OrgTab, WorkflowTab } from "@/routes";
import { deleteConfirmation, noData, notApplicable } from "@/strings/ui";
import type { APIPaginatedList, APIPaginationQuery } from "@/types/api";
import { type CrawlState } from "@/types/crawlState";
@@ -683,7 +684,12 @@ export class WorkflowDetail extends BtrixElement {
- ${this.tabLabels[tab]}
+
+
${this.tabLabels[tab]}
+ ${tab === WorkflowTab.LatestCrawl
+ ? this.renderDedupeBadge()
+ : nothing}
+
${this.renderPanelAction()}
@@ -1387,6 +1393,17 @@ export class WorkflowDetail extends BtrixElement {
`;
};
+ private readonly renderDedupeBadge = () => {
+ const latestCrawl = this.latestCrawlTask.value;
+
+ if (!latestCrawl) return;
+
+ return html``;
+ };
+
private readonly renderPausedNotice = (
{ truncate } = { truncate: false },
) => {
@@ -1715,6 +1732,16 @@ export class WorkflowDetail extends BtrixElement {
}
return html`
+ ${when(this.latestCrawlTask.value, (crawl) =>
+ crawl.requiresCrawls.length
+ ? dedupeReplayNotice({
+ href: this.workflow?.dedupeCollId
+ ? `${this.navigate.orgBasePath}/${OrgTab.Collections}/${CommonTab.View}/${this.workflow.dedupeCollId}`
+ : undefined,
+ })
+ : nothing,
+ )}
+
${guard([this.lastCrawlId], () =>
when(this.latestCrawlTask.value, this.renderReplay),
diff --git a/frontend/src/stories/components/Badge.stories.ts b/frontend/src/stories/components/Badge.stories.ts
index 3ba5efefbc..af9367e819 100644
--- a/frontend/src/stories/components/Badge.stories.ts
+++ b/frontend/src/stories/components/Badge.stories.ts
@@ -6,6 +6,8 @@ import { renderComponent, type RenderProps } from "./Badge";
import "@/features/crawls/crawler-channel-badge";
import "@/features/crawls/proxy-badge";
+import "@/features/collections/dedupe-badge";
+import "@/features/collections/dedupe-source-badge";
const meta = {
title: "Components/Badge",
@@ -32,6 +34,7 @@ const variants = [
"danger",
"neutral",
"primary",
+ "lime",
"cyan",
"blue",
"violet",
@@ -129,5 +132,10 @@ export const FeatureBadges: Story = {
+
+
`,
};
diff --git a/frontend/src/stories/components/DedupeBadge.stories.ts b/frontend/src/stories/components/DedupeBadge.stories.ts
new file mode 100644
index 0000000000..67cc75f8e7
--- /dev/null
+++ b/frontend/src/stories/components/DedupeBadge.stories.ts
@@ -0,0 +1,35 @@
+import type { Meta, StoryObj } from "@storybook/web-components";
+
+import { renderComponent, type RenderProps } from "./DedupeBadge";
+
+const meta = {
+ title: "Features/Dedupe Badge",
+ component: "btrix-dedupe-badge",
+ tags: ["autodocs"],
+ decorators: [],
+ render: renderComponent,
+ argTypes: {},
+ args: {},
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Dependents: Story = {
+ args: {
+ dependents: ["crawl1", "crawl2"],
+ },
+};
+
+export const Dependencies: Story = {
+ args: {
+ dependencies: ["crawl1"],
+ },
+};
+
+export const Both: Story = {
+ args: {
+ dependents: ["crawl1", "crawl2"],
+ dependencies: ["crawl1"],
+ },
+};
diff --git a/frontend/src/stories/components/DedupeBadge.ts b/frontend/src/stories/components/DedupeBadge.ts
new file mode 100644
index 0000000000..9eee109f16
--- /dev/null
+++ b/frontend/src/stories/components/DedupeBadge.ts
@@ -0,0 +1,14 @@
+import { html } from "lit";
+
+import type { DedupeBadge } from "@/features/collections/dedupe-badge";
+
+import "@/features/collections/dedupe-badge";
+
+export type RenderProps = DedupeBadge;
+
+export const renderComponent = (props: Partial) => {
+ return html``;
+};
diff --git a/frontend/src/types/crawlState.ts b/frontend/src/types/crawlState.ts
index f32e06c1ae..20bc18f0df 100644
--- a/frontend/src/types/crawlState.ts
+++ b/frontend/src/types/crawlState.ts
@@ -11,6 +11,7 @@ export const WAITING_NOT_PAUSED_STATES = [
"starting",
"waiting_capacity",
"waiting_org_limit",
+ "waiting_dedupe_index",
] as const;
// Match backend TYPE_PAUSED_STATES in models.py
diff --git a/frontend/src/types/crawler.ts b/frontend/src/types/crawler.ts
index f24ade1241..f78901a14a 100644
--- a/frontend/src/types/crawler.ts
+++ b/frontend/src/types/crawler.ts
@@ -206,6 +206,8 @@ export type Crawl = ArchivedItemBase &
browserWindows: number;
shouldPause: boolean | null;
resources?: (StorageFile & { numReplicas: number })[];
+ requiresCrawls: string[];
+ requiredByCrawls: string[];
};
export type Upload = ArchivedItemBase & {