Skip to content
This repository has been archived by the owner on Sep 12, 2024. It is now read-only.

superset dashboard Odoo menu and group access #16

Open
wants to merge 2 commits into
base: 17.0-1.2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions g2p_superset_dashboard/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
from . import models


def uninstall_hook(env):
env["g2p.superset.dashboard.config"].search([]).unlink()
5 changes: 4 additions & 1 deletion g2p_superset_dashboard/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
"depends": ["base", "web"],
"external_dependencies": {},
"data": [
"views/superset_dashboard_config_views.xml",
"security/groups.xml",
"security/ir.model.access.csv",
"views/superset_dashboard_embedding_views.xml",
"views/superset_dashboard_config_views.xml",
],
"demo": [],
"installable": True,
Expand All @@ -24,4 +26,5 @@
"g2p_superset_dashboard/static/src/components/**/*.scss",
],
},
"uninstall_hook": "uninstall_hook",
}
204 changes: 105 additions & 99 deletions g2p_superset_dashboard/models/g2p_superset_dashboard_config.py
Original file line number Diff line number Diff line change
@@ -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)
27 changes: 27 additions & 0 deletions g2p_superset_dashboard/security/groups.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>

<record model="ir.module.category" id="g2p_superset_dashboard_access_module">
<field name="name">Superset Dashboard</field>
<field name="description">Dashboard access for users</field>
<field name="sequence">4</field>
</record>

<record id="group_superset_user" model="res.groups">
<field name="name">Superset User</field>
<field name="category_id" ref="g2p_superset_dashboard_access_module" />
</record>


<record id="group_superset_admin" model="res.groups">
<field name="name">Superset Admin</field>
<field name="category_id" ref="g2p_superset_dashboard_access_module" />
<field name="implied_ids" eval="[(4, ref('group_superset_user'))]" />
</record>






</odoo>
2 changes: 2 additions & 0 deletions g2p_superset_dashboard/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -1,36 +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}
);

console.log(data[0].url);

if (dashboardData.length > 0) {
this.state.dashboardUrl = dashboardData[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";
registry.category("actions").add("g2p.superset_dashboard_embedded", G2PSupersetDashboardEmbedded);
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve" id="G2PSupersetDashboardEmbedded">
<t t-name="g2p_superset_dashboard.G2PSupersetDashboardEmbedded">
<!-- Loading State -->
<t t-name="g2p_superset_dashboard.G2PSupersetDashboardEmbedded" class="fade-in-g2p">

<t t-if="state.isLoading">
<div
style="display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100vh;"
Expand All @@ -16,8 +16,7 @@
</div>
</t>

<!-- No Dashboards Available -->
<t t-elif="!state.isLoading and !state.dashboards.length">
<t t-elif="!state.isLoading and !superSetUrl">
<div
style="display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100vh;"
>
Expand All @@ -38,25 +37,13 @@
</div>
</t>

<!-- Dashboard Selection and Embedding -->
<t t-else="">
<div>
<select
t-on-change="onDashboardSelect"
style="font-size: 24px; font-weight: bold; font-family: Roboto; text-align: center; text-align-last: center;"
>
<t t-foreach="state.dashboards" t-as="dashboard" t-key="dashboard.id">
<option
t-att-value="dashboard.url"
t-esc="dashboard.name"
style="font-size: 16px; font-weight: bold; font-family: Roboto;"
/>
</t>
</select>
</div>
<t t-elif="!state.isLoading and superSetUrl">
<div>
<iframe t-att-src="state.dashboardUrl" style="width:100%; height:100vh;" />
<iframe t-att-src="superSetUrl" style="width:100%; height:100vh;" />
</div>
</t>



</t>
</templates>
Loading
Loading