diff --git a/containers/backupchecks/src/backend/app/cove_importer.py b/containers/backupchecks/src/backend/app/cove_importer.py index 974dc94..ccc3107 100644 --- a/containers/backupchecks/src/backend/app/cove_importer.py +++ b/containers/backupchecks/src/backend/app/cove_importer.py @@ -342,7 +342,7 @@ def _process_account(account: dict) -> bool: Returns True if a new JobRun was created, False otherwise. """ - from .models import CoveAccount, JobRun + from .models import CoveAccount, Job, JobRun flat = _flatten_settings(account) @@ -393,7 +393,20 @@ def _process_account(account: dict) -> bool: db.session.flush() # ensure cove_acc.id is set - # If not linked to a job yet, nothing more to do (shows up in Cove Accounts page) + # Backwards-compat fallback: resolve link from jobs.cove_account_id. + # This keeps legacy/manual job-detail linking working even if cove_accounts.job_id + # was not set yet. + if not cove_acc.job_id: + fallback_job = ( + Job.query + .filter(Job.cove_account_id == account_id, Job.archived.is_(False)) + .order_by(Job.id.asc()) + .first() + ) + if fallback_job: + cove_acc.job_id = fallback_job.id + + # If still not linked to a job, nothing more to do (shows up in Cove Accounts page) if not cove_acc.job_id: db.session.commit() return False @@ -409,7 +422,6 @@ def _process_account(account: dict) -> bool: run_ts = 0 # Fetch the linked job - from .models import Job job = Job.query.get(cove_acc.job_id) if not job: db.session.commit() diff --git a/containers/backupchecks/src/backend/app/main/routes_jobs.py b/containers/backupchecks/src/backend/app/main/routes_jobs.py index 16257cd..c1e6df4 100644 --- a/containers/backupchecks/src/backend/app/main/routes_jobs.py +++ b/containers/backupchecks/src/backend/app/main/routes_jobs.py @@ -192,7 +192,10 @@ def unarchive_job(job_id: int): @roles_required("admin", "operator") def job_set_cove_account(job_id: int): """Save or clear the Cove Account ID for this job.""" + from ..models import CoveAccount + job = Job.query.get_or_404(job_id) + old_account_id = job.cove_account_id account_id_raw = (request.form.get("cove_account_id") or "").strip() if account_id_raw: try: @@ -203,6 +206,18 @@ def job_set_cove_account(job_id: int): else: job.cove_account_id = None + # Keep staging-table link in sync when possible. + # Importer primarily links from cove_accounts.job_id. + if old_account_id and old_account_id != job.cove_account_id: + old_cove_acc = CoveAccount.query.filter_by(account_id=old_account_id, job_id=job.id).first() + if old_cove_acc: + old_cove_acc.job_id = None + + if job.cove_account_id: + cove_acc = CoveAccount.query.filter_by(account_id=job.cove_account_id).first() + if cove_acc: + cove_acc.job_id = job.id + db.session.commit() try: log_admin_event( diff --git a/docs/changelog-claude.md b/docs/changelog-claude.md index c2f661d..c88bab4 100644 --- a/docs/changelog-claude.md +++ b/docs/changelog-claude.md @@ -9,6 +9,10 @@ This file documents all changes made to this project via Claude Code. - Fixed transaction scope in `app/cove_importer.py` for datasource object persistence. - `run_object_links` / related upserts now use the same SQLAlchemy session transaction as `JobRun` creation instead of a separate engine connection. - Prevents FK/visibility issues where a new uncommitted `JobRun` was not visible to a second connection, causing run creation to roll back and resulting in no Cove runs appearing. +- Cove link compatibility between two link paths: + - `app/cove_importer.py` now falls back to `jobs.cove_account_id` when `cove_accounts.job_id` is not set yet. + - `main/routes_jobs.py` (`POST /jobs//set-cove-account`) now synchronizes `cove_accounts.job_id` when the staged Cove account already exists. + - Fixes scenario where Cove import showed many skipped accounts and zero new runs because links were saved only on `jobs.cove_account_id`. ## [2026-02-27]