Issue-delete from the UI (in the list-view)

See #50
This commit is contained in:
Klaas van Schelven
2025-07-04 21:25:57 +02:00
parent 72479fe982
commit 308034aadd
4 changed files with 83 additions and 3 deletions

View File

@@ -353,6 +353,10 @@ class IssueStateManager(object):
kind=TurningPointKind.UNMUTED, metadata=json.dumps(unmute_metadata))
triggering_event.never_evict = True # .save() will be called by the caller of this function
@staticmethod
def delete(issue):
issue.delete_deferred()
@staticmethod
def get_unmute_thresholds(issue):
unmute_vbcs = [
@@ -471,6 +475,11 @@ class IssueQuerysetStateManager(object):
for issue in issue_qs:
IssueStateManager.unmute(issue, triggering_event)
@staticmethod
def delete(issue_qs):
for issue in issue_qs:
issue.delete_deferred()
class TurningPointKind(models.IntegerChoices):
# The language of the kinds reflects a historic view of the system, e.g. "first seen" as opposed to "new issue"; an

View File

@@ -7,6 +7,23 @@
{% block content %}
<!-- Delete Confirmation Modal -->
<div id="deleteModal" class="hidden fixed inset-0 bg-slate-600 bg-opacity-50 overflow-y-auto h-full w-full z-50 flex items-center justify-center">
<div class="relative p-6 border border-slate-300 w-96 shadow-lg rounded-md bg-white">
<div class="text-center m-4">
<h3 class="text-2xl font-semibold text-slate-800 mt-3 mb-4">Delete Issues</h3>
<div class="mt-4 mb-6">
<p class="text-slate-700">
Deleting an Issue is a permanent action and cannot be undone. It's typically better to resolve or mute an issue instead of deleting it, as this allows you to keep track of past issues and their resolutions.
</p>
</div>
<div class="flex items-center justify-center space-x-4 mb-4">
<button id="cancelDelete" class="text-cyan-500 font-bold">Cancel</button>
<button id="confirmDelete" type="submit" class="font-bold py-2 px-4 rounded bg-red-500 text-white border-2 border-red-600 hover:bg-red-600 active:ring">Delete</button>
</div>
</div>
</div>
</div>
<div class="m-4">
@@ -35,7 +52,7 @@
<div>
<form action="." method="post">
<form action="." method="post" id="issueForm">
{% csrf_token %}
<table class="w-full">
@@ -122,8 +139,18 @@
<button disabled class="font-bold text-slate-300 border-slate-300 pl-4 pr-4 pb-2 pt-2 border-r-2 border-b-2 border-t-2 rounded-e-md" name="action" value="unmute">Unmute</button>
{% endif %}
<div class="dropdown">
<button disabled {# disabled b/c clicking the dropdown does nothing - we have hover for that #} class="font-bold text-slate-500 fill-slate-500 border-slate-300 ml-2 pl-4 pr-4 pb-2 pt-2 border-2 hover:bg-slate-200 active:ring rounded-md">...</button>
<div class="dropdown-content-right flex-col">
<button type="button" onclick="showDeleteConfirmation()" class="block self-stretch font-bold text-red-500 border-slate-300 pl-4 pr-4 pb-2 pt-2 border-l-2 border-r-2 border-b-2 bg-white hover:bg-red-50 active:ring text-left whitespace-nowrap">Delete</button>
</div>
</div>
{% endspaceless %}
{# NOTE: "reopen" is not available in the UI as per the notes in issue_detail #}
{# only for resolved/muted items <button class="font-bold text-slate-500 border-slate-300 pl-4 pr-4 pb-2 pt-2 ml-1 border-2 hover:bg-slate-200 active:ring rounded-md">Reopen</button> #}
@@ -281,5 +308,36 @@
{% endblock %}
{% block extra_js %}
<script>
const deleteButton = document.getElementById('');
const confirmationBox = document.getElementById('deleteModal');
const confirmDelete = document.getElementById('confirmDelete');
const cancelDelete = document.getElementById('cancelDelete');
const form = document.getElementById('issueForm');
let actionInput = null;
function showDeleteConfirmation() {
confirmationBox.style.display = 'flex';
}
cancelDelete.addEventListener('click', () => {
confirmationBox.style.display = 'none';
});
confirmDelete.addEventListener('click', () => {
// Add hidden input only for this submission
if (!actionInput) {
actionInput = document.createElement('input');
actionInput.type = 'hidden';
actionInput.name = 'action';
actionInput.value = 'delete';
form.appendChild(actionInput);
}
form.submit();
});
</script>
<script src="{% static 'js/issue_list.js' %}"></script>
{% endblock %}

View File

@@ -128,6 +128,10 @@ def _is_valid_action(action, issue):
"""We take the 'strict' approach of complaining even when the action is simply a no-op, because you're already in
the desired state."""
if action == "delete":
# any type of issue can be deleted
return True
if issue.is_resolved:
# any action is illegal on resolved issues (as per our current UI)
return False
@@ -153,6 +157,10 @@ def _is_valid_action(action, issue):
def _q_for_invalid_for_action(action):
"""returns a Q obj of issues for which the action is not valid."""
if action == "delete":
# delete is always valid, so we don't want any issues to be returned, https://stackoverflow.com/a/39001190
return Q(pk__in=[])
illegal_conditions = Q(is_resolved=True) # any action is illegal on resolved issues (as per our current UI)
if action.startswith("resolved_release:"):
@@ -169,7 +177,10 @@ def _q_for_invalid_for_action(action):
def _make_history(issue_or_qs, action, user):
if action == "resolve":
if action == "delete":
return # we're about to delete the issue, so no history is needed (nor possible)
elif action == "resolve":
kind = TurningPointKind.RESOLVED
elif action.startswith("resolved"):
kind = TurningPointKind.RESOLVED
@@ -252,6 +263,8 @@ def _apply_action(manager, issue_or_qs, action, user):
}]))
elif action == "unmute":
manager.unmute(issue_or_qs)
elif action == "delete":
manager.delete(issue_or_qs)
def issue_list(request, project_pk, state_filter="open"):

File diff suppressed because one or more lines are too long