fix: Allow deleting workflows without any crawls (#2285)

- Uses crawl count to determine whether workflow can be deleted instead
of last crawl ID
- Display delete confirmation dialog when trying to delete a workflow
This commit is contained in:
sua yoo 2025-01-08 16:02:53 -08:00 committed by GitHub
parent 1260aec976
commit 6a5e070ffc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 118 additions and 18 deletions

View File

@ -18,6 +18,7 @@ import type { CrawlLog } from "@/features/archived-items/crawl-logs";
import { CrawlStatus } from "@/features/archived-items/crawl-status"; import { CrawlStatus } from "@/features/archived-items/crawl-status";
import { ExclusionEditor } from "@/features/crawl-workflows/exclusion-editor"; import { ExclusionEditor } from "@/features/crawl-workflows/exclusion-editor";
import { pageNav, type Breadcrumb } from "@/layouts/pageHeader"; import { pageNav, type Breadcrumb } from "@/layouts/pageHeader";
import { deleteConfirmation } from "@/strings/ui";
import type { APIPaginatedList } from "@/types/api"; import type { APIPaginatedList } from "@/types/api";
import { type CrawlState } from "@/types/crawlState"; import { type CrawlState } from "@/types/crawlState";
import { isApiError } from "@/utils/api"; import { isApiError } from "@/utils/api";
@ -54,7 +55,13 @@ export class WorkflowDetail extends BtrixElement {
isCrawler!: boolean; isCrawler!: boolean;
@property({ type: String }) @property({ type: String })
openDialogName?: "scale" | "exclusions" | "cancel" | "stop" | "delete"; openDialogName?:
| "scale"
| "exclusions"
| "cancel"
| "stop"
| "delete"
| "deleteCrawl";
@property({ type: String }) @property({ type: String })
initialActivePanel?: Tab; initialActivePanel?: Tab;
@ -255,14 +262,14 @@ export class WorkflowDetail extends BtrixElement {
render() { render() {
if (this.isEditing && this.isCrawler) { if (this.isEditing && this.isCrawler) {
return html` return html`
<div class="grid grid-cols-1 gap-7"> <div class="grid grid-cols-1 gap-7 pb-7">
${when(this.workflow, this.renderEditor)} ${when(this.workflow, this.renderEditor)}
</div> </div>
`; `;
} }
return html` return html`
<div class="grid grid-cols-1 gap-7"> <div class="grid grid-cols-1 gap-7 pb-7">
<div class="col-span-1">${this.renderBreadcrumbs()}</div> <div class="col-span-1">${this.renderBreadcrumbs()}</div>
<div> <div>
@ -355,7 +362,7 @@ export class WorkflowDetail extends BtrixElement {
</btrix-dialog> </btrix-dialog>
<btrix-dialog <btrix-dialog
.label=${msg("Delete Crawl?")} .label=${msg("Delete Crawl?")}
.open=${this.openDialogName === "delete"} .open=${this.openDialogName === "deleteCrawl"}
@sl-request-close=${() => (this.openDialogName = undefined)} @sl-request-close=${() => (this.openDialogName = undefined)}
@sl-show=${this.showDialog} @sl-show=${this.showDialog}
@sl-after-hide=${() => (this.isDialogVisible = false)} @sl-after-hide=${() => (this.isDialogVisible = false)}
@ -392,6 +399,32 @@ export class WorkflowDetail extends BtrixElement {
> >
${this.isDialogVisible ? this.renderEditScale() : ""} ${this.isDialogVisible ? this.renderEditScale() : ""}
</btrix-dialog> </btrix-dialog>
<btrix-dialog
.label=${msg("Delete Workflow?")}
.open=${this.openDialogName === "delete"}
@sl-request-close=${() => (this.openDialogName = undefined)}
@sl-show=${this.showDialog}
@sl-after-hide=${() => (this.isDialogVisible = false)}
>
${deleteConfirmation(this.renderName())}
<div slot="footer" class="flex justify-between">
<sl-button
size="small"
.autofocus=${true}
@click=${() => (this.openDialogName = undefined)}
>${msg("Cancel")}</sl-button
>
<sl-button
size="small"
variant="danger"
@click=${async () => {
void this.delete();
this.openDialogName = undefined;
}}
>${msg("Delete Workflow")}</sl-button
>
</div>
</btrix-dialog>
`; `;
} }
@ -736,12 +769,12 @@ export class WorkflowDetail extends BtrixElement {
${msg("Duplicate Workflow")} ${msg("Duplicate Workflow")}
</sl-menu-item> </sl-menu-item>
${when( ${when(
!this.lastCrawlId, !workflow.crawlCount,
() => html` () => html`
<sl-divider></sl-divider> <sl-divider></sl-divider>
<sl-menu-item <sl-menu-item
style="--sl-color-neutral-700: var(--danger)" style="--sl-color-neutral-700: var(--danger)"
@click=${() => void this.delete()} @click=${() => (this.openDialogName = "delete")}
> >
<sl-icon name="trash3" slot="prefix"></sl-icon> <sl-icon name="trash3" slot="prefix"></sl-icon>
${msg("Delete Workflow")} ${msg("Delete Workflow")}
@ -842,7 +875,7 @@ export class WorkflowDetail extends BtrixElement {
private renderCrawls() { private renderCrawls() {
return html` return html`
<section> <section class="h-56 min-h-max">
<div <div
class="mb-3 flex items-center justify-end rounded-lg border bg-neutral-50 p-4" class="mb-3 flex items-center justify-end rounded-lg border bg-neutral-50 p-4"
> >
@ -1643,7 +1676,7 @@ export class WorkflowDetail extends BtrixElement {
private readonly confirmDeleteCrawl = (crawl: Crawl) => { private readonly confirmDeleteCrawl = (crawl: Crawl) => {
this.crawlToDelete = crawl; this.crawlToDelete = crawl;
this.openDialogName = "delete"; this.openDialogName = "deleteCrawl";
}; };
private async deleteCrawl(crawl: Crawl) { private async deleteCrawl(crawl: Crawl) {
@ -1666,6 +1699,9 @@ export class WorkflowDetail extends BtrixElement {
id: "archived-item-delete-status", id: "archived-item-delete-status",
}); });
void this.fetchCrawls(); void this.fetchCrawls();
// Update crawl count
void this.fetchWorkflow();
} catch (e) { } catch (e) {
if (this.crawlToDelete) { if (this.crawlToDelete) {
this.confirmDeleteCrawl(this.crawlToDelete); this.confirmDeleteCrawl(this.crawlToDelete);

View File

@ -1,7 +1,11 @@
import { localized, msg, str } from "@lit/localize"; import { localized, msg, str } from "@lit/localize";
import type { SlCheckbox, SlSelectEvent } from "@shoelace-style/shoelace"; import type {
SlCheckbox,
SlDialog,
SlSelectEvent,
} from "@shoelace-style/shoelace";
import { html, type PropertyValues } from "lit"; import { html, type PropertyValues } from "lit";
import { customElement, state } from "lit/decorators.js"; import { customElement, query, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { when } from "lit/directives/when.js"; import { when } from "lit/directives/when.js";
import queryString from "query-string"; import queryString from "query-string";
@ -21,6 +25,7 @@ import { type SelectEvent } from "@/components/ui/search-combobox";
import type { SelectJobTypeEvent } from "@/features/crawl-workflows/new-workflow-dialog"; import type { SelectJobTypeEvent } from "@/features/crawl-workflows/new-workflow-dialog";
import { pageHeader } from "@/layouts/pageHeader"; import { pageHeader } from "@/layouts/pageHeader";
import scopeTypeLabels from "@/strings/crawl-workflows/scopeType"; import scopeTypeLabels from "@/strings/crawl-workflows/scopeType";
import { deleteConfirmation } from "@/strings/ui";
import type { APIPaginatedList, APIPaginationQuery } from "@/types/api"; import type { APIPaginatedList, APIPaginationQuery } from "@/types/api";
import { NewWorkflowOnlyScopeType } from "@/types/workflow"; import { NewWorkflowOnlyScopeType } from "@/types/workflow";
import { isApiError } from "@/utils/api"; import { isApiError } from "@/utils/api";
@ -91,6 +96,9 @@ export class WorkflowsList extends BtrixElement {
@state() @state()
private fetchErrorStatusCode?: number; private fetchErrorStatusCode?: number;
@state()
private workflowToDelete?: ListWorkflow;
@state() @state()
private orderBy: { private orderBy: {
field: SortField; field: SortField;
@ -106,6 +114,9 @@ export class WorkflowsList extends BtrixElement {
@state() @state()
private filterByCurrentUser = false; private filterByCurrentUser = false;
@query("#deleteDialog")
private readonly deleteDialog?: SlDialog | null;
// For fuzzy search: // For fuzzy search:
private readonly searchKeys = ["name", "firstSeed"]; private readonly searchKeys = ["name", "firstSeed"];
@ -311,12 +322,52 @@ export class WorkflowsList extends BtrixElement {
</btrix-alert> </btrix-alert>
</div> </div>
`, `,
() => () => html`
this.workflows <div class="pb-10">
${this.workflows
? this.workflows.total ? this.workflows.total
? this.renderWorkflowList() ? this.renderWorkflowList()
: this.renderEmptyState() : this.renderEmptyState()
: this.renderLoading(), : this.renderLoading()}
</div>
`,
)}
${this.renderDialogs()}
`;
}
private renderDialogs() {
return html`
${when(
this.workflowToDelete,
(workflow) => html`
<btrix-dialog id="deleteDialog" .label=${msg("Delete Workflow?")}>
${deleteConfirmation(this.renderName(workflow))}
<div slot="footer" class="flex justify-between">
<sl-button
size="small"
.autofocus=${true}
@click=${() => void this.deleteDialog?.hide()}
>${msg("Cancel")}</sl-button
>
<sl-button
size="small"
variant="danger"
@click=${async () => {
void this.deleteDialog?.hide();
try {
await this.delete(workflow);
this.workflowToDelete = undefined;
} catch {
void this.deleteDialog?.show();
}
}}
>${msg("Delete Workflow")}</sl-button
>
</div>
</btrix-dialog>
`,
)} )}
`; `;
} }
@ -593,12 +644,16 @@ export class WorkflowsList extends BtrixElement {
${msg("Duplicate Workflow")} ${msg("Duplicate Workflow")}
</sl-menu-item> </sl-menu-item>
${when( ${when(
!workflow.lastCrawlId, !workflow.crawlCount,
() => html` () => html`
<sl-divider></sl-divider> <sl-divider></sl-divider>
<sl-menu-item <sl-menu-item
style="--sl-color-neutral-700: var(--danger)" style="--sl-color-neutral-700: var(--danger)"
@click=${() => void this.delete(workflow)} @click=${async () => {
this.workflowToDelete = workflow;
await this.updateComplete;
void this.deleteDialog?.show();
}}
> >
<sl-icon name="trash3" slot="prefix"></sl-icon> <sl-icon name="trash3" slot="prefix"></sl-icon>
${msg("Delete Workflow")} ${msg("Delete Workflow")}

View File

@ -0,0 +1,9 @@
import { msg } from "@lit/localize";
import { html, type TemplateResult } from "lit";
// TODO Refactor all generic confirmation messages to use utility
export const deleteConfirmation = (name: string | TemplateResult) =>
msg(html`
Are you sure you want to delete
<strong class="font-semibold">${name}</strong>?
`);