From 597b6d801c7c74e0d2b293c0746dc3e146eff558 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Mon, 18 May 2026 21:37:16 -0400 Subject: [PATCH] fix: Allow deleting failed and canceled imports (#12379) * fix: Allow deleting failed and canceled imports The delete policy only permitted imports in the Completed state, so the overflow menu for Errored or Canceled imports rendered with no items. Co-Authored-By: Claude Opus 4.7 * test: Cover Errored and Canceled in imports.delete Co-Authored-By: Claude Opus 4.7 --------- Co-authored-by: Claude Opus 4.7 --- server/policies/import.ts | 8 +++++- server/routes/api/imports/imports.test.ts | 35 ++++++++++++----------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/server/policies/import.ts b/server/policies/import.ts index 05b51011a8..a017e3007f 100644 --- a/server/policies/import.ts +++ b/server/policies/import.ts @@ -3,6 +3,12 @@ import { User, Team, Import } from "@server/models"; import { allow, can } from "./cancan"; import { and, isTeamAdmin, isTeamMutable, or } from "./utils"; +const TerminalStates = [ + ImportState.Completed, + ImportState.Errored, + ImportState.Canceled, +]; + allow(User, ["createImport", "listImports"], Team, (actor, team) => and(isTeamAdmin(actor, team), isTeamMutable(actor)) ); @@ -14,7 +20,7 @@ allow(User, "read", Import, (actor, importModel) => allow(User, "delete", Import, (actor, importModel) => and( can(actor, "read", importModel), - importModel?.state === ImportState.Completed + !!importModel && TerminalStates.includes(importModel.state) ) ); diff --git a/server/routes/api/imports/imports.test.ts b/server/routes/api/imports/imports.test.ts index 5238cb038c..eb5f949ad7 100644 --- a/server/routes/api/imports/imports.test.ts +++ b/server/routes/api/imports/imports.test.ts @@ -177,24 +177,27 @@ describe("#imports.info", () => { }); describe("#imports.delete", () => { - it("should delete the import", async () => { - const admin = await buildAdmin(); - const importModel = await buildImport({ - state: ImportState.Completed, - createdById: admin.id, - teamId: admin.teamId, - }); + it.each([ImportState.Completed, ImportState.Errored, ImportState.Canceled])( + "should delete the import when in %s state", + async (state) => { + const admin = await buildAdmin(); + const importModel = await buildImport({ + state, + createdById: admin.id, + teamId: admin.teamId, + }); - const res = await server.post("/api/imports.delete", admin, { - body: { - id: importModel.id, - }, - }); - const body = await res.json(); + const res = await server.post("/api/imports.delete", admin, { + body: { + id: importModel.id, + }, + }); + const body = await res.json(); - expect(res.status).toEqual(200); - expect(body.success).toEqual(true); - }); + expect(res.status).toEqual(200); + expect(body.success).toEqual(true); + } + ); it("should throw error when import is not in deletable state", async () => { const admin = await buildAdmin();