Skip to content
2 changes: 2 additions & 0 deletions backend/btrixcloud/crawls.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,8 @@ async def list_crawls(
"qaRunCount",
"lastQAState",
"lastQAStarted",
"crawlExecSeconds",
"pageCount",
):
raise HTTPException(status_code=400, detail="invalid_sort_by")
if sort_direction not in (1, -1):
Expand Down
4 changes: 2 additions & 2 deletions frontend/docs/docs/user-guide/archived-items.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ The status of an archived item depends on its type. Uploads will always have the
| <span class="status-success">:bootstrap-check-circle-fill: Complete</span> | The crawl completed according to the workflow's settings. Workflows with [crawl limits](workflow-setup.md#crawl-limits) set may stop running before they capture every queued page, but the resulting archived item will still be marked as "Complete". |
| <span class="status-neutral">:bootstrap-dash-square-fill: Stopped</span> | The crawl workflow was _stopped_ gracefully by a user and data is saved. |
| <span class="status-neutral">:bootstrap-exclamation-square-fill: Stopped: Reason</span> | A workflow limit (listed as the reason) was reached and data is saved. |
| <span class="status-warning">:bootstrap-x-octagon-fill: Canceled</span> | The crawl workflow was _canceled_ by a user, no data is saved. |
| <span class="status-neutral">:bootstrap-exclamation-octagon: Canceled</span> | The crawl workflow was _canceled_ by a user, no data is saved. |
| <span class="status-danger">:bootstrap-diamond-triangle-fill: Failed</span> | A serious error occurred while crawling, no data is saved.|

Because <span class="status-warning">:bootstrap-x-octagon-fill: Canceled</span> and <span class="status-danger">:bootstrap-exclamation-diamond-fill: Failed</span> crawls do not contain data, they are omitted from the archived items list page and cannot be added to a collection.
Because <span class="status-neutral">:bootstrap-exclamation-octagon: Canceled</span> and <span class="status-danger">:bootstrap-exclamation-diamond-fill: Failed</span> crawls do not contain data, they are omitted from the archived items list page and cannot be added to a collection.

## Archived Item Details

Expand Down
19 changes: 14 additions & 5 deletions frontend/src/components/ui/badge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export class Badge extends TailwindElement {
@property({ type: String })
variant: BadgeVariant = "neutral";

@property({ type: String })
size?: "medium" | "large" = "medium";

@property({ type: Boolean })
outline = false;

Expand All @@ -47,15 +50,16 @@ export class Badge extends TailwindElement {
return html`
<span
class=${clsx(
tw`inline-flex min-h-4 items-center justify-center align-[1px] text-xs leading-none`,
tw`inline-flex min-h-4 items-center justify-center align-[1px] leading-none`,
this.size === "medium" && tw`text-xs`,
this.outline
? [
tw`ring-1`,
tw`mx-px ring-1`,
{
success: tw`bg-success-500 text-success-500 ring-success-500`,
success: tw`bg-success-50 text-success-700 ring-success-400`,
warning: tw`bg-warning-600 text-warning-600 ring-warning-600`,
danger: tw`bg-danger-500 text-danger-500 ring-danger-500`,
neutral: tw`bg-neutral-100 text-neutral-600 ring-neutral-600`,
neutral: tw`bg-neutral-100 text-neutral-600 ring-neutral-300`,
"high-contrast": tw`bg-neutral-600 text-neutral-0 ring-neutral-0`,
primary: tw`bg-white text-primary ring-primary`,
cyan: tw`bg-cyan-50 text-cyan-600 ring-cyan-600`,
Expand All @@ -72,7 +76,12 @@ export class Badge extends TailwindElement {
cyan: tw`bg-cyan-50 text-cyan-600`,
blue: tw`bg-blue-50 text-blue-600`,
}[this.variant],
this.pill ? tw`min-w-[1.125rem] rounded-full px-1` : tw`rounded px-2`,
this.pill
? [
tw`min-w-[1.125rem] rounded-full`,
this.size === "large" ? tw`px-1.5 py-0.5` : tw`px-1`,
]
: [tw`rounded`, this.size === "large" ? tw`px-2.5 py-1` : tw`px-2`],
)}
part="base"
>
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/events/btrix-cancel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type BtrixCancelEvent = CustomEvent<never>;

declare global {
interface GlobalEventHandlersEventMap {
"btrix-cancel": BtrixCancelEvent;
}
}
7 changes: 7 additions & 0 deletions frontend/src/events/btrix-confirm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type BtrixConfirmEvent = CustomEvent<never>;

declare global {
interface GlobalEventHandlersEventMap {
"btrix-confirm": BtrixConfirmEvent;
}
}
19 changes: 16 additions & 3 deletions frontend/src/features/archived-items/archived-item-state-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { CrawlStatus } from "./crawl-status";

import { BtrixElement } from "@/classes/BtrixElement";
import type { BtrixChangeEvent } from "@/events/btrix-change";
import { type CrawlState } from "@/types/crawlState";
import { CRAWL_STATES, type CrawlState } from "@/types/crawlState";
import { finishedCrawlStates } from "@/utils/crawler";
import { isNotEqual } from "@/utils/is-not-equal";
import { tw } from "@/utils/tailwind";
Expand All @@ -42,6 +42,9 @@ export class ArchivedItemStateFilter extends BtrixElement {
@property({ type: Array })
states?: CrawlState[];

@property({ type: Boolean })
showUnfinishedStates = false;

@state()
private searchString = "";

Expand All @@ -51,11 +54,19 @@ export class ArchivedItemStateFilter extends BtrixElement {
@queryAll("sl-checkbox")
private readonly checkboxes!: NodeListOf<SlCheckbox>;

private readonly fuse = new Fuse<CrawlState>(finishedCrawlStates);
private fuse = new Fuse<CrawlState>(finishedCrawlStates);

@state({ hasChanged: isNotEqual })
selected = new Map<CrawlState, boolean>();

connectedCallback(): void {
super.connectedCallback();

if (this.showUnfinishedStates) {
this.fuse = new Fuse(CRAWL_STATES);
}
}

protected willUpdate(changedProperties: PropertyValues<this>): void {
if (changedProperties.has("states")) {
if (this.states) {
Expand Down Expand Up @@ -85,7 +96,9 @@ export class ArchivedItemStateFilter extends BtrixElement {
render() {
const options = this.searchString
? this.fuse.search(this.searchString)
: finishedCrawlStates.map((state) => ({ item: state }));
: (this.showUnfinishedStates ? CRAWL_STATES : finishedCrawlStates).map(
(state) => ({ item: state }),
);
return html`
<btrix-filter-chip
?checked=${!!this.states?.length}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/features/archived-items/crawl-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,9 @@ export class CrawlStatus extends TailwindElement {
break;

case "canceled":
color = "var(--sl-color-orange-600)";
color = "var(--sl-color-neutral-600)";
icon = html`<sl-icon
name="x-octagon-fill"
name="x-octagon"
slot="prefix"
style="color: ${color}"
></sl-icon>`;
Expand Down
144 changes: 144 additions & 0 deletions frontend/src/features/archived-items/delete-item-dialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { localized, msg, str } from "@lit/localize";
import { Task } from "@lit/task";
import { html } from "lit";
import { customElement, property, query } from "lit/decorators.js";
import { when } from "lit/directives/when.js";

import { BtrixElement } from "@/classes/BtrixElement";
import type { Dialog } from "@/components/ui/dialog";
import type { ArchivedItem } from "@/types/crawler";
import { isCrawl, renderName } from "@/utils/crawler";
import { pluralOf } from "@/utils/pluralize";

/**
* Confirm deletion of an archived item, crawl associated
* with an archived item, or crawl run.
*
* @slot name
*/
@customElement("btrix-delete-item-dialog")
@localized()
export class DeleteItemDialog extends BtrixElement {
@property({ type: Object })
item?: ArchivedItem;

@property({ type: Boolean })
open = false;

@query("btrix-dialog")
readonly dialog?: Dialog | null;

private readonly collectionsTask = new Task(this, {
task: async ([open, crawl], { signal }) => {
if (!crawl?.collectionIds) return;

if (!open) {
return crawl.collectionIds.map((id) => ({ id }));
}

return (await this.getCrawl(crawl.id, signal)).collections;
},
args: () => [this.open, this.item] as const,
});

render() {
return html`<btrix-dialog
.label=${this.item
? isCrawl(this.item)
? msg("Delete Crawl?")
: msg("Delete Archived Item?")
: msg("Delete")}
.open=${this.open}
>
${this.renderContent()}
</btrix-dialog>`;
}

private renderContent() {
const item = this.item;

if (!item) return;

const crawl = isCrawl(item);
const item_name = html`<slot name="name"
><strong class="font-semibold">${renderName(item)}</strong></slot
>`;

return html`
<p>
${msg(html`Are you sure you want to delete ${item_name}?`)}
${msg("All associated files and logs will be deleted.")}
</p>

${this.renderCollections()}
<div slot="footer" class="flex justify-between">
<sl-button
size="small"
.autofocus=${true}
@click=${() => {
void this.dialog?.hide();
this.dispatchEvent(new CustomEvent("btrix-cancel"));
}}
>${msg("Cancel")}</sl-button
>
<sl-button
size="small"
variant="danger"
@click=${() => {
this.dispatchEvent(new CustomEvent("btrix-confirm"));
}}
>${crawl
? msg("Delete Crawl")
: msg("Delete Archived Item")}</sl-button
>
</div>
`;
}

private renderCollections() {
if (!this.item?.collectionIds.length) return;

const { collectionIds } = this.item;
const count = collectionIds.length;

const number_of_collections = this.localize.number(count);
const plural_of_collections = pluralOf("collections", count);

return html`
<p class="my-2">
${msg(
str`The archived item will be removed from ${number_of_collections} ${plural_of_collections}:`,
)}
</p>
${this.collectionsTask.render({
pending: () =>
html`<btrix-linked-collections-list
.collections=${collectionIds.map((id) => ({ id }))}
baseUrl="${this.navigate.orgBasePath}/collections/view"
>
</btrix-linked-collections-list>`,
complete: (res) =>
when(
res,
(collections) =>
html`<btrix-linked-collections-list
.collections=${collections}
baseUrl="${this.navigate.orgBasePath}/collections/view"
>
</btrix-linked-collections-list>`,
),
})}
`;
}

private async getCrawl(id: string, signal: AbortSignal) {
const data: ArchivedItem = await this.api.fetch<ArchivedItem>(
`/orgs/${this.orgId}/crawls/${id}/replay.json`,
{
signal,
},
);

return data;
}
}
2 changes: 1 addition & 1 deletion frontend/src/features/archived-items/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import("./archived-item-list");
import("./archived-item-state-filter");
import("./archived-item-tag-filter");
import("./crawl-list");
import("./crawl-log-table");
import("./crawl-logs");
import("./delete-item-dialog");
import("./item-metadata-editor");
import("./crawl-pending-exclusions");
import("./crawl-queue");
Expand Down
1 change: 1 addition & 0 deletions frontend/src/features/crawl-workflows/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import("./workflow-action-menu/workflow-action-menu");
import("./workflow-editor");
import("./workflow-list");
import("./workflow-schedule-filter");
import("./workflow-search");
import("./workflow-tag-filter");
import("./workflow-profile-filter");
import("./workflow-last-crawl-state-filter");
7 changes: 6 additions & 1 deletion frontend/src/features/crawl-workflows/workflow-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,12 @@ export class WorkflowListItem extends BtrixElement {
<sl-tooltip hoist placement="bottom">
<div>
<div class="detail truncate">
<span class="userName">${workflow.modifiedByName}</span>
${workflow.modifiedByName
? html`<btrix-user-chip
userId=${workflow.modifiedBy}
userName=${workflow.modifiedByName}
></btrix-user-chip>`
: notSpecified}
</div>
<div class="desc">${shortDate(workflow.modified)}</div>
</div>
Expand Down
26 changes: 26 additions & 0 deletions frontend/src/features/crawl-workflows/workflow-search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { localized, msg } from "@lit/localize";
import { customElement, property } from "lit/decorators.js";

import { SearchCombobox } from "@/components/ui/search-combobox";

export type SearchFields = "name" | "firstSeed";

@customElement("btrix-workflow-search")
@localized()
export class WorkflowSearch extends SearchCombobox<{ [x: string]: string }> {
static FieldLabels: Record<SearchFields, string> = {
name: msg("Name"),
firstSeed: msg("Crawl Start URL"),
};

@property({ type: Array })
searchOptions: { [x: string]: string }[] = [];

@property({ type: String })
selectedKey?: string;

readonly searchKeys = ["name", "firstSeed"];
readonly keyLabels = WorkflowSearch.FieldLabels;

placeholder = msg("Search by workflow name or crawl start URL");
}
Loading
Loading