diff --git a/queue_job_batch/models/queue_job.py b/queue_job_batch/models/queue_job.py
index 524da23ef7..58d2fcc6c9 100644
--- a/queue_job_batch/models/queue_job.py
+++ b/queue_job_batch/models/queue_job.py
@@ -25,7 +25,7 @@ def write(self, vals):
for record in self:
if record.state != "done" and record.job_batch_id:
batches |= record.job_batch_id
- for batch in batches:
+ for batch in batches.with_context(job_batch=None):
# We need to make it with delay in order to prevent two jobs
# to work with the same batch
batch.with_delay(identity_key=identity_exact).check_state()
diff --git a/queue_job_batch/models/queue_job_batch.py b/queue_job_batch/models/queue_job_batch.py
index a0d084040d..aea19cea12 100644
--- a/queue_job_batch/models/queue_job_batch.py
+++ b/queue_job_batch/models/queue_job_batch.py
@@ -2,9 +2,12 @@
# 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
+from odoo.addons.queue_job.exception import RetryableJobError
class QueueJobBatch(models.Model):
@@ -58,6 +61,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 +121,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,8 +143,21 @@ 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)
+ def _on_done(self):
+ if self.job_count != self.finished_job_count + self.failed_job_count:
+ raise RetryableJobError(
+ "%s: %d total jobs != %d finished + %d failed"
+ % (
+ self.name,
+ self.job_count,
+ self.finished_job_count,
+ self.failed_job_count,
+ )
+ )
+
@api.model
def _to_store_fnames(self):
return (
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..688b7d59f8
--- /dev/null
+++ b/queue_job_batch/tests/test_queue_job_batch.py
@@ -0,0 +1,50 @@
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from odoo.tests import TransactionCase
+
+from odoo.addons.queue_job.exception import RetryableJobError
+
+
+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,
+ )
+
+ def test_on_done(self):
+ self.jobs[0].set_started()
+ self.jobs[0].perform()
+ self.jobs[0].set_done()
+ self.jobs[0].store()
+ with self.assertRaises(RetryableJobError):
+ self.job_batch._on_done()
+ self.jobs[1].set_started()
+ self.jobs[1].perform()
+ self.jobs[1].set_done()
+ self.jobs[1].store()
+ self.job_batch._on_done()
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'}"
/>
+