diff --git a/automation_oca/__manifest__.py b/automation_oca/__manifest__.py
index 80e8202..b994d5a 100644
--- a/automation_oca/__manifest__.py
+++ b/automation_oca/__manifest__.py
@@ -24,6 +24,7 @@
"assets": {
"web.assets_backend": [
"automation_oca/static/src/**/*.js",
+ "automation_oca/static/src/**/*.xml",
"automation_oca/static/src/**/*.scss",
],
},
diff --git a/automation_oca/models/automation_configuration_activity.py b/automation_oca/models/automation_configuration_activity.py
index f4f924e..1a21071 100644
--- a/automation_oca/models/automation_configuration_activity.py
+++ b/automation_oca/models/automation_configuration_activity.py
@@ -1,10 +1,14 @@
# Copyright 2024 Dixmit
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+from collections import defaultdict
+
+import babel.dates
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models
from odoo.osv import expression
+from odoo.tools import get_lang
from odoo.tools.safe_eval import safe_eval
@@ -77,6 +81,83 @@ class AutomationConfigurationActivity(models.Model):
parent_position = fields.Integer(
compute="_compute_parent_position", recursive=True, store=True
)
+ graph_data = fields.Json(compute="_compute_graph_data")
+ graph_done = fields.Integer(compute="_compute_total_graph_data")
+ graph_error = fields.Integer(compute="_compute_total_graph_data")
+
+ @api.depends()
+ def _compute_graph_data(self):
+ total = self.env["automation.record.activity"].read_group(
+ [
+ ("configuration_activity_id", "in", self.ids),
+ ("processed_on", ">=", fields.Date.today() + relativedelta(days=-14)),
+ ],
+ ["configuration_activity_id"],
+ ["configuration_activity_id", "processed_on:day"],
+ lazy=False,
+ )
+ done = self.env["automation.record.activity"].read_group(
+ [
+ ("configuration_activity_id", "in", self.ids),
+ ("processed_on", ">=", fields.Date.today() + relativedelta(days=-14)),
+ ("state", "=", "done"),
+ ],
+ ["configuration_activity_id"],
+ ["configuration_activity_id", "processed_on:day"],
+ lazy=False,
+ )
+ now = fields.Datetime.now()
+ date_map = {
+ babel.dates.format_datetime(
+ now + relativedelta(days=i - 14),
+ format="dd MMM yyy",
+ tzinfo=self._context.get("tz", None),
+ locale=get_lang(self.env).code,
+ ): 0
+ for i in range(0, 15)
+ }
+ result = defaultdict(
+ lambda: {"done": date_map.copy(), "error": date_map.copy()}
+ )
+ for line in total:
+ result[line["configuration_activity_id"][0]]["error"][
+ line["processed_on:day"]
+ ] += line["__count"]
+ for line in done:
+ result[line["configuration_activity_id"][0]]["done"][
+ line["processed_on:day"]
+ ] += line["__count"]
+ result[line["configuration_activity_id"][0]]["error"][
+ line["processed_on:day"]
+ ] -= line["__count"]
+ for record in self:
+ graph_info = dict(result[record.id])
+ record.graph_data = {
+ "error": [
+ {"x": key[:-5], "y": value, "name": key}
+ for (key, value) in graph_info["error"].items()
+ ],
+ "done": [
+ {"x": key[:-5], "y": value, "name": key}
+ for (key, value) in graph_info["done"].items()
+ ],
+ }
+
+ @api.depends()
+ def _compute_total_graph_data(self):
+ for record in self:
+ record.graph_done = self.env["automation.record.activity"].search_count(
+ [
+ ("configuration_activity_id", "in", self.ids),
+ ("state", "=", "done"),
+ ]
+ )
+ record.graph_error = self.env["automation.record.activity"].search_count(
+ [
+ ("configuration_activity_id", "in", self.ids),
+ ("state", "in", ["expired", "rejected", "error", "cancel"]),
+ ]
+ )
@api.depends("trigger_interval", "trigger_interval_type")
def _compute_trigger_interval_hours(self):
diff --git a/automation_oca/static/src/fields/automation_activity.esm.js b/automation_oca/static/src/fields/automation_activity/automation_activity.esm.js
similarity index 93%
rename from automation_oca/static/src/fields/automation_activity.esm.js
rename to automation_oca/static/src/fields/automation_activity/automation_activity.esm.js
index 8b396f4..1d8149d 100644
--- a/automation_oca/static/src/fields/automation_activity.esm.js
+++ b/automation_oca/static/src/fields/automation_activity/automation_activity.esm.js
@@ -1,7 +1,7 @@
/** @odoo-module **/
import {useOpenX2ManyRecord, useX2ManyCrud} from "@web/views/fields/relational_utils";
-import {AutomationKanbanRenderer} from "../views/automation_kanban/automation_kanban_renderer.esm";
+import {AutomationKanbanRenderer} from "../../views/automation_kanban/automation_kanban_renderer.esm";
import {X2ManyField} from "@web/views/fields/x2many/x2many_field";
import {registry} from "@web/core/registry";
diff --git a/automation_oca/static/src/fields/automation_graph/automation_graph.esm.js b/automation_oca/static/src/fields/automation_graph/automation_graph.esm.js
new file mode 100644
index 0000000..b22906b
--- /dev/null
+++ b/automation_oca/static/src/fields/automation_graph/automation_graph.esm.js
@@ -0,0 +1,104 @@
+/** @odoo-module **/
+/* global Chart*/
+
+import {loadJS} from "@web/core/assets";
+import {registry} from "@web/core/registry";
+import {standardFieldProps} from "@web/views/fields/standard_field_props";
+
+const {Component, onWillStart, useEffect, useRef} = owl;
+
+export class AutomationGraph extends Component {
+ setup() {
+ this.chart = null;
+ this.canvasRef = useRef("canvas");
+ onWillStart(() => loadJS("/web/static/lib/Chart/Chart.js"));
+ useEffect(() => {
+ this.renderChart();
+ return () => {
+ if (this.chart) {
+ this.chart.destroy();
+ }
+ };
+ });
+ }
+ _getChartConfig() {
+ return {
+ type: "line",
+ data: {
+ labels: this.props.value.done.map(function (pt) {
+ return pt.x;
+ }),
+ datasets: [
+ {
+ backgroundColor: "#4CAF5080",
+ borderColor: "#4CAF50",
+ data: this.props.value.done,
+ fill: "start",
+ label: this.env._t("Done"),
+ borderWidth: 2,
+ },
+ {
+ backgroundColor: "#F4433680",
+ borderColor: "#F44336",
+ data: this.props.value.error,
+ fill: "start",
+ label: this.env._t("Error"),
+ borderWidth: 2,
+ },
+ ],
+ },
+ options: {
+ legend: {display: false},
+
+ layout: {
+ padding: {left: 10, right: 10, top: 10, bottom: 10},
+ },
+ scales: {
+ yAxes: [
+ {
+ type: "linear",
+ display: false,
+ ticks: {
+ beginAtZero: true,
+ },
+ },
+ ],
+ xAxes: [
+ {
+ ticks: {
+ maxRotation: 0,
+ },
+ },
+ ],
+ },
+ maintainAspectRatio: false,
+ elements: {
+ line: {
+ tension: 0.000001,
+ },
+ },
+ tooltips: {
+ intersect: false,
+ position: "nearest",
+ caretSize: 0,
+ borderWidth: 2,
+ },
+ },
+ };
+ }
+ renderChart() {
+ if (this.chart) {
+ this.chart.destroy();
+ }
+ var config = this._getChartConfig();
+ this.chart = new Chart(this.canvasRef.el, config);
+ Chart.animationService.advance();
+ }
+}
+
+AutomationGraph.template = "automation_oca.AutomationGraph";
+AutomationGraph.props = {
+ ...standardFieldProps,
+};
+
+registry.category("fields").add("automation_graph", AutomationGraph);
diff --git a/automation_oca/static/src/fields/automation_graph/automation_graph.xml b/automation_oca/static/src/fields/automation_graph/automation_graph.xml
new file mode 100644
index 0000000..03212b5
--- /dev/null
+++ b/automation_oca/static/src/fields/automation_graph/automation_graph.xml
@@ -0,0 +1,10 @@
+
+