Merge pull request '(ui) Add review actions to filtered run lists' (#32) from issue-22-operational-review-actions into master
Reviewed-on: #32
This commit was merged in pull request #32.
This commit is contained in:
@@ -273,6 +273,11 @@
|
|||||||
}
|
}
|
||||||
button.secondary:hover,
|
button.secondary:hover,
|
||||||
.button-link.secondary:hover { background: #eef3f8; }
|
.button-link.secondary:hover { background: #eef3f8; }
|
||||||
|
button.compact,
|
||||||
|
.button-link.compact {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 5px 8px;
|
||||||
|
}
|
||||||
button:disabled {
|
button:disabled {
|
||||||
background: #d8dee6;
|
background: #d8dee6;
|
||||||
border-color: #d8dee6;
|
border-color: #d8dee6;
|
||||||
|
|||||||
@@ -95,7 +95,22 @@
|
|||||||
<span class="muted">none</span>
|
<span class="muted">none</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{% if run.reviewed_at %}reviewed{% elif run.status == "failed" or run.status == "warning" %}<span class="status warning">needed</span>{% else %}<span class="muted">none</span>{% endif %}</td>
|
<td>
|
||||||
|
{% if run.reviewed_at %}
|
||||||
|
reviewed
|
||||||
|
{% elif run.status == "failed" or run.status == "warning" %}
|
||||||
|
<div class="stack">
|
||||||
|
<span class="status warning">needed</span>
|
||||||
|
<form class="inline-form" method="post" action="{% url 'resolve_run_review' run.id %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="next" value="{{ request.get_full_path }}">
|
||||||
|
<button type="submit" class="secondary compact">Mark reviewed</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<span class="muted">none</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr><td colspan="9" class="muted">No runs matched the current filter.</td></tr>
|
<tr><td colspan="9" class="muted">No runs matched the current filter.</td></tr>
|
||||||
|
|||||||
@@ -234,6 +234,31 @@ class ViewTests(TestCase):
|
|||||||
self.assertContains(response, "needed")
|
self.assertContains(response, "needed")
|
||||||
self.assertNotContains(response, f"Run {success.id}")
|
self.assertNotContains(response, f"Run {success.id}")
|
||||||
|
|
||||||
|
def test_runs_list_can_mark_problem_run_reviewed(self) -> None:
|
||||||
|
self.client.force_login(self.staff_user)
|
||||||
|
host = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||||
|
run = BackupRun.objects.create(host=host, status=BackupRun.Status.FAILED, run_type=BackupRun.RunType.MANUAL)
|
||||||
|
list_url = f'{reverse("runs_list")}?status=failed&review=needed'
|
||||||
|
|
||||||
|
response = self.client.get(list_url)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, "Mark reviewed")
|
||||||
|
self.assertContains(response, 'value="/runs/?status=failed&review=needed"', html=False)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("resolve_run_review", args=[run.id]),
|
||||||
|
{"next": list_url},
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
run.refresh_from_db()
|
||||||
|
self.assertIsNotNone(run.reviewed_at)
|
||||||
|
self.assertEqual(run.reviewed_by, self.staff_user.username)
|
||||||
|
self.assertRedirects(response, list_url)
|
||||||
|
self.assertContains(response, f"Run {run.id} marked reviewed.")
|
||||||
|
self.assertNotContains(response, f"Run {run.id}</a>", html=False)
|
||||||
|
|
||||||
def test_snapshots_list_filters_by_host_and_kind(self) -> None:
|
def test_snapshots_list_filters_by_host_and_kind(self) -> None:
|
||||||
self.client.force_login(self.staff_user)
|
self.client.force_login(self.staff_user)
|
||||||
web = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
web = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||||
|
|||||||
@@ -661,13 +661,13 @@ def resolve_run_review(request, run_id: int):
|
|||||||
return redirect("run_detail", run_id=run.id)
|
return redirect("run_detail", run_id=run.id)
|
||||||
if run.reviewed_at:
|
if run.reviewed_at:
|
||||||
messages.info(request, f"Run {run.id} was already marked reviewed.")
|
messages.info(request, f"Run {run.id} was already marked reviewed.")
|
||||||
return redirect("run_detail", run_id=run.id)
|
return _redirect_after_run_review(request, run)
|
||||||
|
|
||||||
run.reviewed_at = timezone.now()
|
run.reviewed_at = timezone.now()
|
||||||
run.reviewed_by = request.user.get_username()
|
run.reviewed_by = request.user.get_username()
|
||||||
run.save(update_fields=["reviewed_at", "reviewed_by"])
|
run.save(update_fields=["reviewed_at", "reviewed_by"])
|
||||||
messages.success(request, f"Run {run.id} marked reviewed.")
|
messages.success(request, f"Run {run.id} marked reviewed.")
|
||||||
return redirect("run_detail", run_id=run.id)
|
return _redirect_after_run_review(request, run)
|
||||||
|
|
||||||
|
|
||||||
@staff_member_required
|
@staff_member_required
|
||||||
@@ -937,6 +937,13 @@ def _next_run_for_schedule(schedule: ScheduleConfig | None, host_config: HostCon
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _redirect_after_run_review(request, run: BackupRun):
|
||||||
|
next_url = request.POST.get("next", "").strip()
|
||||||
|
if next_url.startswith("/"):
|
||||||
|
return redirect(next_url)
|
||||||
|
return redirect("run_detail", run_id=run.id)
|
||||||
|
|
||||||
|
|
||||||
def _retention_warning_for_host(host_config: HostConfig, schedule: ScheduleConfig | None) -> dict[str, object]:
|
def _retention_warning_for_host(host_config: HostConfig, schedule: ScheduleConfig | None) -> dict[str, object]:
|
||||||
incomplete_count = host_config.snapshots.filter(
|
incomplete_count = host_config.snapshots.filter(
|
||||||
kind=SnapshotRecord.Kind.INCOMPLETE,
|
kind=SnapshotRecord.Kind.INCOMPLETE,
|
||||||
|
|||||||
Reference in New Issue
Block a user