Add job detail template with execution history and metrics display

- Created a new job_detail.html template extending base.html
- Implemented a macro for rendering nested data structures
- Added sections for job identification, schedule & timing, status, configuration, execution history, and results
- Included a timeline for execution history with visual indicators for job status
- Displayed job metrics including total executions, success rate, and average duration
- Handled cases for displaying results or indicating absence of results based on job status
This commit is contained in:
2026-05-25 19:07:56 +12:00
parent 5bf6b98ccc
commit 82c7712613
11 changed files with 1432 additions and 79 deletions
+30
View File
@@ -0,0 +1,30 @@
"""Fake recon-config data so dashboard metrics can run without a DB."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timedelta
@dataclass
class FakeReconConfig:
reference: str
name: str
status: str # "draft" | "published" | "archived"
frequency: str # "Ad Hoc" | "Intra Day" | "Daily" | "Weekly" | "Monthly" | "Quarterly"
business_process: str
start_datetime: datetime | None = None
def get_fake_configs(now: datetime) -> list[FakeReconConfig]:
base = now - timedelta(days=120)
return [
FakeReconConfig("fx-settlement", "FX Settlement Recon", "published", "Daily", "Treasury", base),
FakeReconConfig("cash-vs-ledger", "Cash vs Ledger", "published", "Daily", "Finance", base),
FakeReconConfig("intraday-liquidity", "Intraday Liquidity Sweep", "published", "Intra Day", "Treasury", base),
FakeReconConfig("eod-positions", "EOD Position Recon", "published", "Daily", "Trading", base),
FakeReconConfig("weekly-customers", "Customer Master Recon", "published", "Weekly", "Onboarding", base),
FakeReconConfig("monthly-reg", "Regulatory Reporting Recon", "published", "Monthly", "Compliance", base),
FakeReconConfig("ad-hoc-aml", "AML Spot Check", "published", "Ad Hoc", "FinCrime", None),
FakeReconConfig("crypto-wallet", "Crypto Wallet Recon (POC)", "draft", "Daily", "Digital", None),
FakeReconConfig("legacy-loans", "Legacy Loans Recon", "archived", "Monthly", "Lending", None),
]
+178
View File
@@ -0,0 +1,178 @@
"""In-memory fake of recon-job data so the dashboard/results views can run
without a database or the production `app.core.config` / `app.core.refdata`
modules. Replace with real DB-backed code when wiring up to the real repo.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import date, datetime, time, timedelta
from typing import Optional
@dataclass
class FakeReconJob:
id: int
name: str
as_at_date: date
status: str = "completed"
status_reason: str = ""
recon_config_reference: str = "demo-config"
username: str = "demo.user"
due_datetime: Optional[datetime] = None
start_datetime: Optional[datetime] = None
finish_datetime: Optional[datetime] = None
results: Optional[dict] = None
def _at(day: date, hour: int, minute: int = 0) -> datetime:
return datetime.combine(day, time(hour, minute))
def get_fake_jobs(now: datetime) -> list[FakeReconJob]:
today = now.date()
yesterday = today - timedelta(days=1)
sample_result_a = {
"Job": "FX Settlement Recon",
"As-at": yesterday.isoformat(),
"Matched": 1284,
"Unmatched": 12,
"Status": "Matched",
"Flag": "None",
}
sample_result_b = {
"Job": "Cash vs Ledger",
"As-at": yesterday.isoformat(),
"Matched": 980,
"Unmatched": 47,
"Status": "Unmatched",
"Flag": "Threshold Breach",
}
sample_result_c = {
"Job": "Intraday Liquidity Sweep",
"As-at": today.isoformat(),
"Matched": 311,
"Unmatched": 0,
"Status": "Matched",
"Flag": "None",
}
jobs: list[FakeReconJob] = [
# Yesterday — completed runs and one failure
FakeReconJob(
id=101, name="FX Settlement Recon", as_at_date=yesterday,
recon_config_reference="fx-settlement",
due_datetime=_at(yesterday, 6, 0),
start_datetime=_at(yesterday, 6, 2),
finish_datetime=_at(yesterday, 6, 47),
status="completed", results=sample_result_a,
),
FakeReconJob(
id=102, name="Cash vs Ledger", as_at_date=yesterday,
recon_config_reference="cash-vs-ledger",
due_datetime=_at(yesterday, 9, 30),
start_datetime=_at(yesterday, 9, 33),
finish_datetime=_at(yesterday, 10, 12),
status="completed", results=sample_result_b,
),
FakeReconJob(
id=103, name="Intraday Liquidity Sweep", as_at_date=yesterday,
recon_config_reference="intraday-liquidity",
due_datetime=_at(yesterday, 13, 0),
start_datetime=_at(yesterday, 13, 4),
finish_datetime=_at(yesterday, 13, 35),
status="completed",
results={"Job": "Intraday Liquidity Sweep", "As-at": yesterday.isoformat(),
"Matched": 290, "Unmatched": 4, "Status": "Matched", "Flag": "None"},
),
FakeReconJob(
id=104, name="EOD Position Recon", as_at_date=yesterday,
recon_config_reference="eod-positions",
due_datetime=_at(yesterday, 18, 0),
start_datetime=_at(yesterday, 18, 5),
finish_datetime=_at(yesterday, 19, 50),
status="completed",
),
FakeReconJob(
id=105, name="Regulatory Reporting Recon", as_at_date=yesterday,
recon_config_reference="monthly-reg",
due_datetime=_at(yesterday, 22, 0),
start_datetime=_at(yesterday, 22, 3),
finish_datetime=_at(yesterday, 22, 9),
status="failed", status_reason="Source feed unavailable",
),
# Today — mix of completed, running, scheduled, and a failure
FakeReconJob(
id=201, name="FX Settlement Recon", as_at_date=today,
recon_config_reference="fx-settlement",
due_datetime=_at(today, 6, 0),
start_datetime=_at(today, 6, 3),
finish_datetime=_at(today, 6, 51),
status="completed", results=sample_result_c,
),
FakeReconJob(
id=202, name="Cash vs Ledger", as_at_date=today,
recon_config_reference="cash-vs-ledger",
due_datetime=_at(today, 9, 30),
start_datetime=max(now - timedelta(minutes=18), _at(today, 0, 0)),
finish_datetime=None,
status="running",
),
FakeReconJob(
id=203, name="Intraday Liquidity Sweep", as_at_date=today,
recon_config_reference="intraday-liquidity",
due_datetime=_at(today, 13, 0) if now < _at(today, 13, 0) else now + timedelta(hours=1),
status="created",
),
FakeReconJob(
id=204, name="EOD Position Recon", as_at_date=today,
recon_config_reference="eod-positions",
due_datetime=_at(today, 18, 0) if now < _at(today, 18, 0) else now + timedelta(hours=2),
status="created",
),
FakeReconJob(
id=205, name="AML Spot Check", as_at_date=today,
recon_config_reference="ad-hoc-aml",
due_datetime=_at(today, 8, 0),
start_datetime=_at(today, 8, 2),
finish_datetime=_at(today, 8, 5),
status="failed", status_reason="Schema mismatch",
),
]
# Add a 7-day history (excluding the two days above) so totals look real.
history_template = [
("FX Settlement Recon", "fx-settlement", 6, 45, "completed"),
("Cash vs Ledger", "cash-vs-ledger", 9, 42, "completed"),
("Intraday Liquidity Sweep","intraday-liquidity", 13, 30, "completed"),
("EOD Position Recon", "eod-positions", 18, 105, "completed"),
]
next_id = 300
for d in range(2, 8):
day = today - timedelta(days=d)
for name, ref, hour, duration_min, status in history_template:
start = _at(day, hour, 5)
jobs.append(FakeReconJob(
id=next_id, name=name, as_at_date=day,
recon_config_reference=ref,
due_datetime=_at(day, hour, 0),
start_datetime=start,
finish_datetime=start + timedelta(minutes=duration_min),
status=status,
results={"Matched": 800 + next_id % 500, "Unmatched": (next_id * 3) % 60,
"Status": "Matched" if (next_id % 5) else "Unmatched", "Flag": "None"},
))
next_id += 1
# Sprinkle in occasional failures
if d in (3, 5):
jobs.append(FakeReconJob(
id=next_id, name="Customer Master Recon", as_at_date=day,
recon_config_reference="weekly-customers",
due_datetime=_at(day, 4, 0),
start_datetime=_at(day, 4, 1),
finish_datetime=_at(day, 4, 7),
status="failed", status_reason="Upstream timeout",
))
next_id += 1
return jobs