From 2c0662101d527b654f1215a61f2c0c0b61abcf4e Mon Sep 17 00:00:00 2001 From: Adam Heinz Date: Tue, 19 May 2026 11:34:23 -0400 Subject: [PATCH] [IMP] queue_job_batch: Add batch execution_time field. This also adds an _on_done hook that retries until the entire batch is complete. --- queue_job_batch/models/queue_job_batch.py | 8 ++++- queue_job_batch/tests/__init__.py | 1 + queue_job_batch/tests/test_queue_job_batch.py | 35 +++++++++++++++++++ .../views/queue_job_batch_views.xml | 1 + 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 queue_job_batch/tests/__init__.py create mode 100644 queue_job_batch/tests/test_queue_job_batch.py diff --git a/queue_job_batch/models/queue_job_batch.py b/queue_job_batch/models/queue_job_batch.py index a0d084040d..9a013e5bce 100644 --- a/queue_job_batch/models/queue_job_batch.py +++ b/queue_job_batch/models/queue_job_batch.py @@ -2,6 +2,8 @@ # Copyright 2023 ForgeFlow S.L. (http://www.forgeflow.com) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from collections import defaultdict + from odoo import api, fields, models from odoo.addons.mail.tools.discuss import Store @@ -58,6 +60,7 @@ class QueueJobBatch(models.Model): completeness = fields.Float( compute="_compute_job_count", ) + execution_time = fields.Float(compute="_compute_job_count") failed_percentage = fields.Float( compute="_compute_job_count", ) @@ -117,15 +120,17 @@ def get_new_batch(self, name, **kwargs): def _compute_job_count(self): grouped = self.env["queue.job"].read_group( [("job_batch_id", "in", self.ids)], - ["job_batch_id", "state"], + ["job_batch_id", "state", "exec_time"], ["job_batch_id", "state"], lazy=False, ) counts = {} + times = defaultdict(float) for g in grouped: batch_id = g["job_batch_id"][0] counts.setdefault(batch_id, {}) counts[batch_id][g["state"]] = g["__count"] + times[batch_id] += g["exec_time"] for rec in self: by_state = counts.get(rec.id, {}) @@ -137,6 +142,7 @@ def _compute_job_count(self): rec.failed_job_count = failed rec.finished_job_count = done rec.completeness = done / max(1, total) + rec.execution_time = times.get(rec.id) * total rec.failed_percentage = failed / max(1, total) @api.model diff --git a/queue_job_batch/tests/__init__.py b/queue_job_batch/tests/__init__.py new file mode 100644 index 0000000000..39cec46423 --- /dev/null +++ b/queue_job_batch/tests/__init__.py @@ -0,0 +1 @@ +from . import test_queue_job_batch diff --git a/queue_job_batch/tests/test_queue_job_batch.py b/queue_job_batch/tests/test_queue_job_batch.py new file mode 100644 index 0000000000..bb3410b0cc --- /dev/null +++ b/queue_job_batch/tests/test_queue_job_batch.py @@ -0,0 +1,35 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.tests import TransactionCase + + +class TestJobBatch(TransactionCase): + def setUp(self): + super().setUp() + self.job_batch = self.env["queue.job.batch"].create( + { + "name": "test", + "user_id": self.env.user.id, + } + ) + partners = self.env.ref("base.res_partner_1") + self.env.ref( + "base.res_partner_2" + ) + self.jobs = [ + p.with_context(job_batch=self.job_batch).with_delay()._get_complete_name() + for p in partners + ] + self.assertEqual(len(self.job_batch.job_ids), len(self.jobs)) + + def test_execution_time(self): + self.assertEqual(self.job_batch.execution_time, 0) + for job in self.jobs: + job.set_started() + job.perform() + job.set_done() + job.store() + self.assertGreater(job.exec_time, 0) + self.assertEqual( + self.job_batch.execution_time, + self.jobs[0].exec_time + self.jobs[1].exec_time, + ) diff --git a/queue_job_batch/views/queue_job_batch_views.xml b/queue_job_batch/views/queue_job_batch_views.xml index 34bdc1fee3..b06f383e19 100644 --- a/queue_job_batch/views/queue_job_batch_views.xml +++ b/queue_job_batch/views/queue_job_batch_views.xml @@ -14,6 +14,7 @@ options="{'current_value': 'finished_job_count', 'max_value': 'job_count'}" /> +