Skip to content

Commit

Permalink
#558 - feat: allow deletion of list
Browse files Browse the repository at this point in the history
  • Loading branch information
svenvandescheur committed Jan 2, 2025
1 parent b6c5ed0 commit df4d295
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# fmt: off

from django.test import tag

from openarchiefbeheer.utils.tests.e2e import browser_page
from openarchiefbeheer.utils.tests.gherkin import GherkinLikeTestCase

from ....constants import ListStatus


@tag("e2e")
@tag("gh-558")
class FeatureListDeleteTests(GherkinLikeTestCase):
async def test_scenario_delete_list(self):
async with browser_page() as page:
record_manger = await self.given.record_manager_exists()
await self.given.assignee_exists(user=record_manger)
await self.given.list_exists(name="Destruction list to delete", status=ListStatus.new)

await self.when.record_manager_logs_in(page)
await self.then.path_should_be(page, "/destruction-lists")

await self.when.user_clicks_button(page, "Destruction list to delete")
await self.when.user_clicks_button(page, "Lijst verwijderen")
await self.when.user_fills_form_field(page, "Type naam van de lijst ter bevestiging", "Destruction list to delete")
await self.when.user_clicks_button(page, "Lijst verwijderen", 1)

await self.then.path_should_be(page, "/destruction-lists")
await self.then.not_.list_should_exist(page, name="Destruction list to delete")
await self.then.not_.page_should_contain_text(page, "Destruction list to delete")

async def test_scenario_cannot_delete_if_status_not_new(self):
async with browser_page() as page:
record_manger = await self.given.record_manager_exists()
await self.given.assignee_exists(user=record_manger)
await self.given.list_exists(name="Destruction list to delete", status=ListStatus.ready_to_delete)

await self.when.record_manager_logs_in(page)
await self.then.path_should_be(page, "/destruction-lists")

await self.when.user_clicks_button(page, "Destruction list to delete")
await self.then.not_.page_should_contain_text(page, "Lijst verwijderen")
7 changes: 6 additions & 1 deletion backend/src/openarchiefbeheer/utils/tests/gherkin.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,12 @@ async def inverted_method(*args, **kwargs):
return InvertedThen(self)

async def list_should_exist(self, page, name):
return await DestructionList.objects.aget(name=name)
try:
return await DestructionList.objects.aget(name=name)
except DestructionList.DoesNotExist:
raise AssertionError(
f"Destruction list with name '{name}' does not exist."
)

async def list_should_have_assignee(self, page, destruction_list, assignee):
@sync_to_async()
Expand Down
18 changes: 16 additions & 2 deletions frontend/src/lib/api/destructionLists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,20 @@ export async function listDestructionLists(
return promise;
}

/**
* Delete a destruction list.
* @param uuid
* @returns
*/
export async function deleteDestructionList(uuid: string) {
const response = await request("DELETE", `/destruction-lists/${uuid}/`, {});
if (response.status === 204) {
return null;
}
const promise: Promise<unknown> = response.json();
return promise;
}

/**
* Update destruction list.
* @param uuid
Expand All @@ -150,7 +164,7 @@ export async function updateDestructionList(
}

/**
* Mark destruction list as ready to reveiw.
* Mark destruction list as ready to review.
* @param uuid
*/
export async function markDestructionListAsReadyToReview(uuid: string) {
Expand Down Expand Up @@ -191,7 +205,7 @@ export async function markDestructionListAsFinal(
}

/**
* Destroy destruction list
* Queue a background process that will delete the cases in the list from the case system.
* @param uuid
* @returns
*/
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/lib/auth/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ export const canChangeSettings: PermissionCheck = (user) => {
export const canStartDestructionList: PermissionCheck = (user) =>
user.role.canStartDestruction;

/**
* Returns whether `user` is allowed to delete `destructionList`.
* @param user
* @param destructionList
*/
export const canDeleteDestructionList: DestructionListPermissionCheck = (
user,
destructionList,
) => {
if (!user.role.canStartDestruction) {
return false;
}
return destructionList.status === "new";
};

/**
* Returns whether `user` is allowed to mark `destructionList` as ready to review.
* @param user
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { redirect } from "react-router-dom";
import { JsonValue, TypedAction } from "../../../hooks";
import {
abort,
deleteDestructionList,
destructionListQueueDestruction,
markDestructionListAsFinal,
markDestructionListAsReadyToReview,
Expand All @@ -16,6 +17,7 @@ import {
import { clearZaakSelection } from "../../../lib/zaakSelection/zaakSelection";

export type UpdateDestructionListAction<P = JsonValue> = TypedAction<
| "DELETE_LIST"
| "QUEUE_DESTRUCTION"
| "CANCEL_DESTROY"
| "MAKE_FINAL"
Expand All @@ -36,6 +38,8 @@ export async function destructionListUpdateAction({
const action = data as UpdateDestructionListAction<unknown>;

switch (action.type) {
case "DELETE_LIST":
return await destructionListDeleteAction({ request, params });
case "QUEUE_DESTRUCTION":
return await destructionListQueueDestructionAction({ request, params });
case "MAKE_FINAL":
Expand All @@ -56,6 +60,24 @@ export async function destructionListUpdateAction({
}
}

/**
* React Router action (user intents to DELETE THE DESTRUCTION LIST!).
*/
export async function destructionListDeleteAction({
request,
}: ActionFunctionArgs) {
const { payload } = await request.json();
try {
await deleteDestructionList(payload.uuid);
} catch (e: unknown) {
if (e instanceof Response) {
return await (e as Response).json();
}
throw e;
}
return redirect("/");
}

/**
* React Router action (user intents to mark the destruction list as final (assign to archivist)).
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { useSubmitAction } from "../../../../hooks";
import { ReviewItemResponse } from "../../../../lib/api/reviewResponse";
import {
DestructionListPermissionCheck,
canDeleteDestructionList,
canMarkAsReadyToReview,
canMarkListAsFinal,
canTriggerDestruction,
Expand All @@ -42,7 +43,7 @@ type MakeFinalFormType = {
comment: string;
};

type DestroyFormType = {
type DestructionListNameFormType = {
name: string;
};

Expand Down Expand Up @@ -86,6 +87,55 @@ export function useSecondaryNavigation(): ToolbarItem[] {
return toolbarItem;
};

const BUTTON_DELETE_LIST: ToolbarItem = {
children: (
<>
<Solid.DocumentMinusIcon />
Lijst verwijderen
</>
),
pad: "h",
variant: "danger",
onClick: () =>
formDialog<DestructionListNameFormType>(
"Lijst verwijderen",
`Dit verwijdert de lijst maar niet de zaken die erop staan. Weet u zeker dat u de lijst "${destructionList.name}" wilt verwijderen?`,
[
{
label: "Type naam van de lijst ter bevestiging",
name: "name",
placeholder: "Naam van de vernietigingslijst",
required: true,
},
],
`Lijst verwijderen`,
"Annuleren",
handleDeleteList,
undefined,
undefined,
{
buttonProps: {
variant: "danger",
},
validate: validateName,
validateOnChange: true,
role: "form",
},
),
};

/**
* Dispatches action to DELETE THE DESTRUCTION LIST!
*/
const handleDeleteList = async () => {
submitAction({
type: "DELETE_LIST",
payload: {
uuid: destructionList.uuid,
},
});
};

const BUTTON_READY_TO_REVIEW: ToolbarItem = {
children: (
<>
Expand All @@ -94,6 +144,7 @@ export function useSecondaryNavigation(): ToolbarItem[] {
</>
),
pad: "h",
variant: "primary",
onClick: () =>
confirm(
"Ter beoordeling indienen",
Expand Down Expand Up @@ -282,7 +333,7 @@ export function useSecondaryNavigation(): ToolbarItem[] {
variant: "danger",
pad: "h",
onClick: () =>
formDialog<DestroyFormType>(
formDialog<DestructionListNameFormType>(
"Zaken definitief vernietigen",
`U staat op het punt om ${destructionListItems.count} zaken definitief te vernietigen`,
[
Expand All @@ -295,21 +346,21 @@ export function useSecondaryNavigation(): ToolbarItem[] {
],
`${destructionListItems.count} zaken vernietigen`,
"Annuleren",
handleDestroy,
handleQueueDestruction,
undefined,
undefined,
{
buttonProps: {
variant: "danger",
},
validate: validateDestroy,
validate: validateName,
validateOnChange: true,
role: "form",
},
),
};

const validateDestroy = ({ name }: DestroyFormType) => {
const validateName = ({ name }: DestructionListNameFormType) => {
// Name can be undefined at a certain point and will crash the entire page
if (
(name as string | undefined)?.toLowerCase() ===
Expand All @@ -325,7 +376,7 @@ export function useSecondaryNavigation(): ToolbarItem[] {
/**
* Dispatches action to DESTROY ALL ZAKEN ON THE DESTRUCTION LIST!
*/
const handleDestroy = async () => {
const handleQueueDestruction = async () => {
submitAction({
type: "QUEUE_DESTRUCTION",
payload: {
Expand Down Expand Up @@ -390,7 +441,7 @@ export function useSecondaryNavigation(): ToolbarItem[] {
destructionList={destructionList}
/>,

// Status: "ready_to_delete", badge and spacer
// Status: "ready_to_delete": badge and spacer
getPermittedToolbarItem(
<ProcessingStatusBadge
key={destructionList.pk}
Expand All @@ -407,7 +458,8 @@ export function useSecondaryNavigation(): ToolbarItem[] {
// Right
//

// Status: "new", "Ter beoordeling indienen"
// Status: "new": "Lijst verwijderen" and "Ter beoordeling indienen"
getPermittedToolbarItem(BUTTON_DELETE_LIST, canDeleteDestructionList),
getPermittedToolbarItem(
BUTTON_READY_TO_REVIEW,
(user, destructionList) =>
Expand All @@ -420,18 +472,18 @@ export function useSecondaryNavigation(): ToolbarItem[] {
(user, destructionList) => destructionList.status !== "new",
),

// Status: "changes_requested", "Opnieuw indienen"
// Status: "changes_requested": "Opnieuw indienen"
getPermittedToolbarItem(
BUTTON_PROCESS_REVIEW,
(user, destructionList) =>
canMarkAsReadyToReview(user, destructionList) &&
destructionList.status === "changes_requested",
),

// Status: "internally_reviewed", "Markeren als definitief"
// Status: "internally_reviewed": "Markeren als definitief"
getPermittedToolbarItem(BUTTON_MAKE_FINAL, canMarkListAsFinal),

// Status: "ready_to_delete" "Vernietigen starten"/"Vernietigen herstarten"
// Status: "ready_to_delete": "Vernietigen starten"/"Vernietigen herstarten"
getPermittedToolbarItem(
BUTTON_DESTROY,
(user, destructionList) =>
Expand Down

0 comments on commit df4d295

Please sign in to comment.