diff --git a/g2p_superset_dashboard/__init__.py b/g2p_superset_dashboard/__init__.py index 0650744..377fcf0 100644 --- a/g2p_superset_dashboard/__init__.py +++ b/g2p_superset_dashboard/__init__.py @@ -1 +1,5 @@ from . import models + + +def uninstall_hook(env): + env["g2p.superset.dashboard.config"].search([]).unlink() diff --git a/g2p_superset_dashboard/__manifest__.py b/g2p_superset_dashboard/__manifest__.py index 3d348ef..3dae115 100644 --- a/g2p_superset_dashboard/__manifest__.py +++ b/g2p_superset_dashboard/__manifest__.py @@ -10,8 +10,9 @@ "depends": ["base", "web"], "external_dependencies": {}, "data": [ + "security/groups.xml", + "security/ir.model.access.csv", "views/superset_dashboard_config_views.xml", - "views/superset_dashboard_embedding_views.xml", ], "demo": [], "installable": True, @@ -24,4 +25,5 @@ "g2p_superset_dashboard/static/src/components/**/*.scss", ], }, + "uninstall_hook": "uninstall_hook", } diff --git a/g2p_superset_dashboard/models/g2p_superset_dashboard_config.py b/g2p_superset_dashboard/models/g2p_superset_dashboard_config.py index c39d5e6..a4349da 100644 --- a/g2p_superset_dashboard/models/g2p_superset_dashboard_config.py +++ b/g2p_superset_dashboard/models/g2p_superset_dashboard_config.py @@ -1,106 +1,112 @@ -import logging +import re -from odoo import fields, models - -_logger = logging.getLogger(__name__) +from odoo import api, fields, models +from odoo.exceptions import ValidationError class SupersetDashboardConfig(models.Model): _name = "g2p.superset.dashboard.config" _description = "Superset Dashboard Configuration" - name = fields.Char(string="Dashboard Name", required=True) - url = fields.Char(string="Dashboard URL", required=True) - access_user_ids = fields.Many2many("res.users", string="Access Rights") - - -# *** USE CODE BELOW TO EMBED SUPERSET DASHBOARD IN ODOO MENUITEMS. *** -# THIS MAY SLOW PERFORMANCE OF ODOO SINCE IT WILL BE CREATING MENUITEMS AND ACTIONS FOR EACH DASHBOARDS. - - -# Override create method to regenerate menus when new dashboards are created.s -# @api.model_create_multi -# def create(self, vals_list): -# records = super().create(vals_list) -# self._create_or_update_menus(records) -# return records - -# # Override write method to regenerate menus when dashboards are updated. -# def write(self, vals): -# res = super().write(vals) -# self._create_or_update_menus(self) -# return res - -# # Override unlink method to delete menus related to the dashboards being deleted. -# def unlink(self): -# dashboard_names = self.mapped("name") -# res = super().unlink() -# self._delete_menus(dashboard_names) -# return res - -# # Create or update the menus for the provided dashboards. -# def _create_or_update_menus(self, dashboards): -# try: -# for dashboard in dashboards: -# existing_menu = self.env["ir.ui.menu"].search([ -# ("parent_id", "=", self.env.ref( -# "g2p_superset_dashboard.menu_superset_dashboard_embedded").id), -# ("name", "=", dashboard.name) -# ]) -# existing_menu.unlink() - -# action = self.env["ir.actions.client"].create({ -# "name": dashboard.name, -# "tag": "g2p_superset_dashboard_embedded", -# "context": {"url": dashboard.url}, -# }) - -# self.env["ir.ui.menu"].create({ -# "name": dashboard.name, -# "parent_id": self.env.ref( -# "g2p_superset_dashboard.menu_superset_dashboard_embedded").id, -# "action": f"ir.actions.client,{action.id}", -# }) - -# _logger.info(f"Menu for dashboard '{dashboard.name}' created/updated successfully.") -# except Exception as e: -# _logger.error(f"Error while creating/updating menus: {e}") -# raise - -# # Delete the menus associated with the dashboards being deleted. -# # def _delete_menus(self, dashboard_names): -# # try: -# menus_to_delete = self.env["ir.ui.menu"].search([ -# ("parent_id", "=", self.env.ref( -# "g2p_superset_dashboard.menu_superset_dashboard_embedded").id), -# ("name", "in", dashboard_names) -# ]) -# # if menus_to_delete: -# menus_to_delete.unlink() -# _logger.info(f"Menus for dashboards {dashboard_names} deleted successfully.") -# else: -# _logger.info(f"No menus found for dashboards {dashboard_names} to delete.") -# except Exception as e: -# _logger.error(f"Error while deleting menus: {e}") -# raise - -# # Regenerates all menus for the dashboards. -# # @api.model -# # def create_menus(self): -# # try: -# # # Clear all existing dynamic menus -# existing_menus = self.env["ir.ui.menu"].search([ -# ("parent_id", "=", self.env.ref( -# "g2p_superset_dashboard.menu_superset_dashboard_embedded").id) -# ]) -# # existing_menus.unlink() - -# # Fetch all dashboards -# dashboards = self.search([]) - -# # Create or update menus for all dashboards -# self._create_or_update_menus(dashboards) - -# except Exception as e: -# _logger.error(f"Error while regenerating all menus: {e}") -# raise + name = fields.Char(string="Dashboard name", required=True) + url = fields.Char( + string="Dashboard URL", + required=True, + help="Enter the URL for your Superset dashboard. URL must start with 'http://' or 'https://'", + ) + group_name = fields.Char("Group name", required=True) + group = fields.Many2one("res.groups") + menu_name = fields.Char("Menu title", required=True) + menu = fields.Many2one("ir.ui.menu") + action = fields.Many2one("ir.actions.client") + + @api.constrains("url") + def _oncreate_check_url(self): + for record in self: + record.check_url(record.url) + + @api.onchange("url") + def _onchange_check_url(self): + for record in self: + if record.url: + record.check_url(record.url) + + def check_url(self, url): + url_pattern = re.compile( + r"^(https?:\/\/)(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+$", + re.IGNORECASE, + ) + if not url_pattern.match(url): + error_msg = "Invalid URL format. The URL must start with 'http://' or 'https://'." + raise ValidationError(error_msg) + + @api.model + def create(self, vals): + group_vals = { + "name": vals.get("group_name"), + "category_id": self.env.ref("g2p_superset_dashboard.g2p_superset_dashboard_access_module").id, + "implied_ids": [(4, self.env.ref("g2p_superset_dashboard.group_superset_user").id)], + } + group = self.env["res.groups"].create(group_vals) + vals["group"] = group.id + + action_vals = { + "name": f"{vals.get('menu_name')} Action", + "tag": "g2p.superset_dashboard_embedded", + } + action = self.env["ir.actions.client"].create(action_vals) + action_id = action.id + + menu_vals = { + "name": vals.get("menu_name"), + "parent_id": self.env.ref("g2p_superset_dashboard.menu_superset_dashboards").id, + "action": f"ir.actions.client,{action_id}", + "sequence": 1, + "groups_id": [(6, 0, [int(group.id)])], # Ensure IDs are integers + } + menu = self.env["ir.ui.menu"].create(menu_vals) + vals["menu"] = menu.id + + if action_id: + vals["action"] = action_id + + return super().create(vals) + + def unlink(self): + for record in self: + if not record.exists(): + continue + if record.menu and record.menu.exists(): + record.menu.write({"groups_id": [(5, 0, [])]}) + + if record.group and record.group.exists(): + if ( + record.group.category_id.id + == self.env.ref("g2p_superset_dashboard.g2p_superset_dashboard_access_module").id + ): + users = self.env["res.users"].search([("groups_id", "in", record.group.id)]) + if users: + users.write({"groups_id": [(3, record.group.id)]}) + record.group.unlink() + + if record.group and record.group.exists(): + record.group.unlink() + + if record.action and record.action.exists(): + record.action.unlink() + + if record.menu and record.menu.exists(): + record.menu.unlink() + return super().unlink() + + def write(self, vals): + if "menu_name" in vals: + menu = self.env["ir.ui.menu"].browse(self.menu.id) + if menu: + menu.write({"name": vals["menu_name"]}) + + if "group_name" in vals: + group = self.env["res.groups"].browse(self.group.id) + if group: + group.write({"name": vals["group_name"]}) + return super().write(vals) diff --git a/g2p_superset_dashboard/security/groups.xml b/g2p_superset_dashboard/security/groups.xml new file mode 100644 index 0000000..cffd816 --- /dev/null +++ b/g2p_superset_dashboard/security/groups.xml @@ -0,0 +1,27 @@ + + + + + Superset Dashboard + Dashboard access for users + 4 + + + + Superset User + + + + + + Superset Admin + + + + + + + + + + diff --git a/g2p_superset_dashboard/security/ir.model.access.csv b/g2p_superset_dashboard/security/ir.model.access.csv new file mode 100644 index 0000000..b1b4b0c --- /dev/null +++ b/g2p_superset_dashboard/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_superset_config,Superset Configuration,model_g2p_superset_dashboard_config,group_superset_admin,1,1,1,1 diff --git a/g2p_superset_dashboard/static/src/components/dashboard/g2p_superset_dashboard.css b/g2p_superset_dashboard/static/src/components/dashboard/g2p_superset_dashboard.css index d8bcec2..1ea4a3e 100644 --- a/g2p_superset_dashboard/static/src/components/dashboard/g2p_superset_dashboard.css +++ b/g2p_superset_dashboard/static/src/components/dashboard/g2p_superset_dashboard.css @@ -15,3 +15,17 @@ height: auto; animation: zoomInOut 3s infinite; } + +.fade-in-g2p { + animation: fadeInAnimation 0.5s ease-in-out forwards; + opacity: 0; +} + +@keyframes fadeInAnimation { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} diff --git a/g2p_superset_dashboard/static/src/components/dashboard/g2p_superset_dashboard.js b/g2p_superset_dashboard/static/src/components/dashboard/g2p_superset_dashboard.js index f4a295f..3d8ceb3 100644 --- a/g2p_superset_dashboard/static/src/components/dashboard/g2p_superset_dashboard.js +++ b/g2p_superset_dashboard/static/src/components/dashboard/g2p_superset_dashboard.js @@ -1,35 +1,46 @@ /** @odoo-module */ -import {Component, useState} from "@odoo/owl"; +import {Component, useState, useSubEnv} from "@odoo/owl"; +import {getDefaultConfig} from "@web/views/view"; import {registry} from "@web/core/registry"; export class G2PSupersetDashboardEmbedded extends Component { setup() { this.state = useState({ isLoading: true, - dashboards: [], dashboardUrl: "", }); + this.actionId = this.props.actionId; const orm = this.env.services.orm; + + useSubEnv({ + config: { + ...getDefaultConfig(), + ...this.env.config, + }, + }); + this.loadDashboards(orm); } async loadDashboards(orm) { - // Fetch dashboard configurations - const dashboardData = await orm.searchRead("g2p.superset.dashboard.config", [], ["name", "url"]); - this.state.dashboards = dashboardData; + const data = await orm.searchRead( + "g2p.superset.dashboard.config", + [["action.id", "=", this.actionId]], + ["url"], + {limit: 1} + ); - if (dashboardData.length > 0) { - this.state.dashboardUrl = dashboardData[0].url; + console.log(data[0].url); + + if (data && data.length > 0 && data[0].url) { + this.superSetUrl = data[0].url; + } else { + this.superSetUrl = false; } this.state.isLoading = false; } - - onDashboardSelect(event) { - const selectedUrl = event.target.value; - this.state.dashboardUrl = selectedUrl; - } } G2PSupersetDashboardEmbedded.template = "g2p_superset_dashboard.G2PSupersetDashboardEmbedded"; diff --git a/g2p_superset_dashboard/static/src/components/dashboard/superset_dashboard_embedding_templates.xml b/g2p_superset_dashboard/static/src/components/dashboard/superset_dashboard_embedding_templates.xml index eee6b20..234e4ae 100644 --- a/g2p_superset_dashboard/static/src/components/dashboard/superset_dashboard_embedding_templates.xml +++ b/g2p_superset_dashboard/static/src/components/dashboard/superset_dashboard_embedding_templates.xml @@ -1,62 +1,43 @@ - - - -
+ +
- - Loading Superset Dashboard -
-
- - - -
+ Loading Superset Dashboard +
+
+ +
-
- + -
-
-

+

+

No Dashboards Available

-

Try contacting Admin to configure new dashboards or give you access rights.

-
-
- - - - -
- -
-
-