diff --git a/backend/app/services/campaign_service.py b/backend/app/services/campaign_service.py index cfb4b02..e1b27c7 100644 --- a/backend/app/services/campaign_service.py +++ b/backend/app/services/campaign_service.py @@ -181,6 +181,7 @@ def generate_campaign_from_threat_actor( tool_used=template.tool_suggested, created_by=user.id, state=TestState.draft, + created_at=datetime.utcnow(), ) db.add(test) db.flush() # Get test.id diff --git a/backend/app/services/metrics_query_service.py b/backend/app/services/metrics_query_service.py index 932e433..cb7f711 100644 --- a/backend/app/services/metrics_query_service.py +++ b/backend/app/services/metrics_query_service.py @@ -232,10 +232,11 @@ def get_validation_rate(db: Session) -> list[ValidationRate]: def get_recent_tests(db: Session, *, limit: int = 10) -> list[RecentTestItem]: """Return the most recently created tests.""" + from sqlalchemy import nullslast tests = ( db.query(Test) .options(joinedload(Test.technique)) - .order_by(Test.created_at.desc()) + .order_by(nullslast(Test.created_at.desc())) .limit(limit) .all() ) diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index 521fc21..a90bc08 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -87,25 +87,29 @@ export default function DashboardPage() { queryFn: getCoverageByTactic, }); - // V2 queries - const { data: pipeline, isLoading: pipelineLoading } = useQuery({ + // V2 queries — retry:2 so transient failures don't leave widgets blank + const { data: pipeline, isLoading: pipelineLoading, isError: pipelineError } = useQuery({ queryKey: ["metrics", "test-pipeline"], queryFn: getTestPipeline, + retry: 2, }); - const { data: teamActivity, isLoading: teamLoading } = useQuery({ + const { data: teamActivity, isLoading: teamLoading, isError: teamError } = useQuery({ queryKey: ["metrics", "team-activity"], queryFn: getTeamActivity, + retry: 2, }); - const { data: validationRates, isLoading: validationLoading } = useQuery({ + const { data: validationRates, isLoading: validationLoading, isError: validationError } = useQuery({ queryKey: ["metrics", "validation-rate"], queryFn: getValidationRate, + retry: 2, }); - const { data: recentTests, isLoading: recentLoading } = useQuery({ + const { data: recentTests, isLoading: recentLoading, isError: recentError } = useQuery({ queryKey: ["metrics", "recent-tests"], queryFn: getRecentTests, + retry: 2, }); const { data: coverageEvolution, isLoading: evolutionLoading } = useQuery({ @@ -287,6 +291,8 @@ export default function DashboardPage() {
Could not load pipeline data.
) : pipeline ? (Could not load team activity.
) : teamActivity ? (Could not load validation data.
) : validationRates ? (Could not load recent tests — refresh the page.
) : recentTests && recentTests.length > 0 ? (