fix(ui+backend): sidebar active state + technique status after test deletion

- Sidebar: add `end` prop to child NavLinks so "All Tests" (/tests) is
  only highlighted when exactly on /tests, not on /tests/validated.
- Backend: recalculate technique status_global for all affected techniques
  when tests are deleted via delete_campaign(delete_tests=True), preventing
  stale coverage metrics on the dashboard.
This commit is contained in:
kitos
2026-05-28 17:55:04 +02:00
parent 7594a09b20
commit 366fc2170c
2 changed files with 12 additions and 0 deletions
@@ -25,6 +25,7 @@ from app.services.campaign_service import (
TACTIC_TO_PHASE, TACTIC_TO_PHASE,
) )
from app.services.campaign_scheduler_service import calculate_next_run from app.services.campaign_scheduler_service import calculate_next_run
from app.services.status_service import recalculate_technique_status
# ── Serialization helpers ──────────────────────────────────────────────── # ── Serialization helpers ────────────────────────────────────────────────
@@ -465,11 +466,21 @@ def delete_campaign(
# Optionally delete the associated tests # Optionally delete the associated tests
if delete_tests: if delete_tests:
affected_technique_ids: set = set()
for test_id in test_ids: for test_id in test_ids:
test = db.query(Test).filter(Test.id == test_id).first() test = db.query(Test).filter(Test.id == test_id).first()
if test: if test:
if test.technique_id:
affected_technique_ids.add(test.technique_id)
db.delete(test) db.delete(test)
db.flush() db.flush()
# Recalculate status_global for every affected technique so the
# coverage metrics stay consistent after test deletion.
for tech_id in affected_technique_ids:
technique = db.query(Technique).filter(Technique.id == tech_id).first()
if technique:
recalculate_technique_status(db, technique)
db.flush()
# Null-out parent_campaign_id on child campaigns to avoid FK violation # Null-out parent_campaign_id on child campaigns to avoid FK violation
db.query(Campaign).filter(Campaign.parent_campaign_id == campaign.id).update( db.query(Campaign).filter(Campaign.parent_campaign_id == campaign.id).update(
+1
View File
@@ -82,6 +82,7 @@ function SidebarLink({ item }: { item: NavItem }) {
<NavLink <NavLink
key={child.to + child.label} key={child.to + child.label}
to={child.to} to={child.to}
end
className={({ isActive }) => className={({ isActive }) =>
`flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors ${ `flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors ${
isActive isActive