Allow deleting completed access review campaigns

Completed campaigns were immutable because delete only accepted draft
or cancelled status. Extend deletion to completed records and surface
delete in the console for that status.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
This commit is contained in:
Cursor Agent
2026-07-29 15:35:22 +00:00
parent 255bea4738
commit 9c1cecfc1e
5 changed files with 8 additions and 6 deletions

View File

@@ -132,9 +132,9 @@ export default function AccessReviewCampaignsTab({ queryRef }: Props) {
deleteCampaignMutation,
);
// Only DRAFT and CANCELLED campaigns can be deleted (enforced by the backend).
// Only DRAFT, CANCELLED, and COMPLETED campaigns can be deleted (enforced by the backend).
const isDeletableStatus = (status: string) =>
status === "DRAFT" || status === "CANCELLED";
status === "DRAFT" || status === "CANCELLED" || status === "COMPLETED";
const handleDelete = (campaignId: string, campaignName: string) => {
confirm(

View File

@@ -217,7 +217,8 @@ export default function CampaignDetailPage({ queryRef }: Props) {
const isDraft = campaign.status === "DRAFT";
const isPendingActions = campaign.status === "PENDING_ACTIONS";
const isCancelled = campaign.status === "CANCELLED";
const canDelete = campaign.canDelete && (isDraft || isCancelled);
const isCompleted = campaign.status === "COMPLETED";
const canDelete = campaign.canDelete && (isDraft || isCancelled || isCompleted);
const campaignIdRef = useRef(campaign.id);

View File

@@ -206,7 +206,8 @@ func (s *Service) DeleteCampaign(
}
if campaign.Status != coredata.AccessReviewCampaignStatusDraft &&
campaign.Status != coredata.AccessReviewCampaignStatusCancelled {
campaign.Status != coredata.AccessReviewCampaignStatusCancelled &&
campaign.Status != coredata.AccessReviewCampaignStatusCompleted {
return NewCampaignNotDeletableError(campaign.ID)
}

View File

@@ -95,7 +95,7 @@ func NewCampaignNotDeletableError(campaignID gid.GID) error {
func (e *CampaignNotDeletableError) Error() string {
return fmt.Sprintf(
"access review campaign %q cannot be deleted unless it is draft or cancelled",
"access review campaign %q cannot be deleted unless it is draft, cancelled, or completed",
e.CampaignID,
)
}

View File

@@ -61,7 +61,7 @@ func TestCampaignClientErrors(t *testing.T) {
name: "not deletable",
err: accessreview.NewCampaignNotDeletableError(campaignID),
wantText: fmt.Sprintf(
"access review campaign %q cannot be deleted unless it is draft or cancelled",
"access review campaign %q cannot be deleted unless it is draft, cancelled, or completed",
campaignID,
),
sentinel: accessreview.ErrCampaignNotDeletable,