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:
parent
1260aec976
commit
6a5e070ffc
@ -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);
|
||||||
|
|||||||
@ -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.total
|
${this.workflows
|
||||||
? this.renderWorkflowList()
|
? this.workflows.total
|
||||||
: this.renderEmptyState()
|
? this.renderWorkflowList()
|
||||||
: this.renderLoading(),
|
: this.renderEmptyState()
|
||||||
|
: 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")}
|
||||||
|
|||||||
9
frontend/src/strings/ui.ts
Normal file
9
frontend/src/strings/ui.ts
Normal 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>?
|
||||||
|
`);
|
||||||
Loading…
Reference in New Issue
Block a user