diff --git a/application/single_app/config.py b/application/single_app/config.py index 9dabbc5a3..26c151c66 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -96,7 +96,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.260.001" +VERSION = "0.260.002" IS_DEVELOPMENT = is_development_env_enabled() SESSION_COOKIE_SAMESITE = os.getenv('SESSION_COOKIE_SAMESITE', 'Lax') diff --git a/application/single_app/functions_data_management.py b/application/single_app/functions_data_management.py index 5b6658aef..0c8565bbb 100644 --- a/application/single_app/functions_data_management.py +++ b/application/single_app/functions_data_management.py @@ -10538,6 +10538,22 @@ def get_data_management_backup_inventory(limit=100): ] +def _count_data_management_backups(extra_clauses=None, extra_parameters=None): + """Count backups with a VALUE aggregate; the Python client cannot serve GROUP BY here.""" + clauses = ["c.type = @type", "c.operation = @operation"] + parameters = [ + {"name": "@type", "value": DATA_MANAGEMENT_JOB_TYPE}, + {"name": "@operation", "value": DATA_MANAGEMENT_OPERATION_BACKUP}, + ] + clauses.extend(extra_clauses or []) + parameters.extend(extra_parameters or []) + rows = _query_data_management_history_items( + query=f"SELECT VALUE COUNT(1) FROM c WHERE {' AND '.join(clauses)}", + parameters=parameters, + ) + return _safe_int(rows[0], default=0, minimum=0) if rows else 0 + + def _get_data_management_backup_global_summary(): summary = { "full": 0, @@ -10549,39 +10565,42 @@ def _get_data_management_backup_global_summary(): "latest_full": None, "latest_partial": None, } - aggregate_query = ( - "SELECT c.backup_type, c.status, COUNT(1) AS count FROM c " - "WHERE c.type = @type AND c.operation = @operation " - "GROUP BY c.backup_type, c.status" - ) parameters = [ {"name": "@type", "value": DATA_MANAGEMENT_JOB_TYPE}, {"name": "@operation", "value": DATA_MANAGEMENT_OPERATION_BACKUP}, ] - aggregate_rows = _query_data_management_history_items( - query=aggregate_query, - parameters=parameters, + available_clause = "(c.status = @completed OR c.status = @completed_with_warnings)" + available_parameters = [ + {"name": "@completed", "value": DATA_MANAGEMENT_STATUS_COMPLETED}, + { + "name": "@completed_with_warnings", + "value": DATA_MANAGEMENT_STATUS_COMPLETED_WITH_WARNINGS, + }, + ] + + summary["total"] = _count_data_management_backups() + summary["available"] = _count_data_management_backups( + [available_clause], + available_parameters, ) - for row in aggregate_rows: - if not isinstance(row, dict): - continue - count = _safe_int(row.get("count"), default=0, minimum=0) - status = row.get("status") - backup_type = row.get("backup_type") - summary["total"] += count - if status in { - DATA_MANAGEMENT_STATUS_COMPLETED, - DATA_MANAGEMENT_STATUS_COMPLETED_WITH_WARNINGS, - }: - summary["available"] += count - if backup_type == DATA_MANAGEMENT_BACKUP_FULL: - summary["full"] += count - elif backup_type == DATA_MANAGEMENT_BACKUP_PARTIAL: - summary["partial"] += count - elif status == DATA_MANAGEMENT_STATUS_RUNNING: - summary["running"] += count - elif status == DATA_MANAGEMENT_STATUS_FAILED: - summary["failed"] += count + summary["running"] = _count_data_management_backups( + ["c.status = @running"], + [{"name": "@running", "value": DATA_MANAGEMENT_STATUS_RUNNING}], + ) + summary["failed"] = _count_data_management_backups( + ["c.status = @failed"], + [{"name": "@failed", "value": DATA_MANAGEMENT_STATUS_FAILED}], + ) + for backup_type, summary_field in ( + (DATA_MANAGEMENT_BACKUP_FULL, "full"), + (DATA_MANAGEMENT_BACKUP_PARTIAL, "partial"), + ): + summary[summary_field] = _count_data_management_backups( + [available_clause, "c.backup_type = @backup_type"], + available_parameters + [ + {"name": "@backup_type", "value": backup_type}, + ], + ) for backup_type, summary_field in ( (DATA_MANAGEMENT_BACKUP_FULL, "latest_full"), diff --git a/application/single_app/templates/admin_settings.html b/application/single_app/templates/admin_settings.html index 8b15e6607..7bcd8410f 100644 --- a/application/single_app/templates/admin_settings.html +++ b/application/single_app/templates/admin_settings.html @@ -6720,15 +6720,41 @@

Cosmos DB JSON E

Backup Inventory

Track completed full and partial backups created by Data Management jobs.

-
- +
+
+
+
What does Run Retention Cleanup do?
+

+ It permanently deletes backups whose age exceeds the retention period configured in Data Management settings, + and removes their stored artifacts from the backup container. Backups newer than the retention cutoff are left alone. +

+ +

+ Seeing “found no expired backups to delete” means every backup is still inside the retention window. That is expected, not an error. +

+
+