From a9c48338a88e0a3b497ada6a0003519af2f935df Mon Sep 17 00:00:00 2001 From: Faizan Date: Fri, 7 Nov 2025 19:32:25 +0500 Subject: [PATCH 01/17] [ADD] A new module that is dependency map view its a new view type to check the dependency map of records --- dependency_map_view | 1 + 1 file changed, 1 insertion(+) create mode 160000 dependency_map_view diff --git a/dependency_map_view b/dependency_map_view new file mode 160000 index 00000000000..57d0d25537c --- /dev/null +++ b/dependency_map_view @@ -0,0 +1 @@ +Subproject commit 57d0d25537cce356b65c4ccaed4ec0abcefb3901 From 116ab0e41076cd1fbc399f2ff49657f2d552e2ff Mon Sep 17 00:00:00 2001 From: Faizan Date: Fri, 7 Nov 2025 19:54:36 +0500 Subject: [PATCH 02/17] [ADD] dependency_map_view: new view type to visualize record dependencies --- dependency_map_view | 1 - dependency_map_view/.gitignore | 5 + dependency_map_view/README.md | 52 ++++ dependency_map_view/__init__.py | 23 ++ dependency_map_view/__manifest__.py | 52 ++++ dependency_map_view/hooks.py | 49 ++++ dependency_map_view/models/__init__.py | 20 ++ dependency_map_view/models/ir_actions.py | 98 +++++++ dependency_map_view/models/ir_ui_view.py | 107 +++++++ .../static/description/icon.png | Bin 0 -> 23769 bytes .../src/components/map_view/map_view.js | 277 ++++++++++++++++++ .../src/components/map_view/map_view.scss | 178 +++++++++++ .../src/components/map_view/map_view.xml | 75 +++++ .../static/src/libs/vis-network.min.js | 27 ++ dependency_map_view/views/default_view.xml | 11 + 15 files changed, 974 insertions(+), 1 deletion(-) delete mode 160000 dependency_map_view create mode 100644 dependency_map_view/.gitignore create mode 100644 dependency_map_view/README.md create mode 100644 dependency_map_view/__init__.py create mode 100644 dependency_map_view/__manifest__.py create mode 100644 dependency_map_view/hooks.py create mode 100644 dependency_map_view/models/__init__.py create mode 100644 dependency_map_view/models/ir_actions.py create mode 100644 dependency_map_view/models/ir_ui_view.py create mode 100644 dependency_map_view/static/description/icon.png create mode 100644 dependency_map_view/static/src/components/map_view/map_view.js create mode 100644 dependency_map_view/static/src/components/map_view/map_view.scss create mode 100644 dependency_map_view/static/src/components/map_view/map_view.xml create mode 100644 dependency_map_view/static/src/libs/vis-network.min.js create mode 100644 dependency_map_view/views/default_view.xml diff --git a/dependency_map_view b/dependency_map_view deleted file mode 160000 index 57d0d25537c..00000000000 --- a/dependency_map_view +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 57d0d25537cce356b65c4ccaed4ec0abcefb3901 diff --git a/dependency_map_view/.gitignore b/dependency_map_view/.gitignore new file mode 100644 index 00000000000..2fd09bdaa5a --- /dev/null +++ b/dependency_map_view/.gitignore @@ -0,0 +1,5 @@ +*.pyc +__pycache__/ +*.py[cod] +*$py.class +.DS_Store diff --git a/dependency_map_view/README.md b/dependency_map_view/README.md new file mode 100644 index 00000000000..4e569d7485c --- /dev/null +++ b/dependency_map_view/README.md @@ -0,0 +1,52 @@ +# Dependency & Impact Map View + +Interactive visual graph view for analyzing record dependencies and relationships in Odoo. + +## Features + +- **Visual Graph Representation**: Interactive network diagram showing record relationships +- **Multiple Relationship Types**: Support for Many2One, One2Many, and Many2Many fields +- **Color-Coded Relationships**: Different colors for different relationship types +- **Interactive Navigation**: Click on nodes to navigate to related records +- **Hierarchical Layout**: Automatic positioning with vis.js network library +- **Export Capabilities**: Export maps as PNG, JSON, or PDF +- **Real-time Analysis**: Dynamic relationship discovery and visualization +- **Auto-Integration**: Automatically available for all Odoo models + +## Installation + +1. Copy the module to your Odoo addons directory +2. Update the apps list in Odoo +3. Install the "Dependency & Impact Map View" module + +## Usage + +1. Go to any model's list view (e.g., Sales Orders, Customers, Products) +2. Click on the view switcher and select "Dependency Map" view +3. The system will automatically generate a visual map of record relationships +4. Click on nodes to navigate to related records +5. Use export options to save the map + +## Technical Details + +- **Framework**: Built with OWL (Odoo Web Library) +- **Visualization**: Uses vis.js network library +- **Compatibility**: Odoo 18.0+ +- **Auto-View Creation**: Automatically creates dependency_map views for any model +- **Performance**: Optimized for large datasets with progressive loading + +## Configuration + +No configuration required. The module automatically: +- Registers the new view type +- Creates default views for all models +- Integrates with existing Odoo interface + +## Author + +**Faizan Lodhi** +Website: https://axiomworld.net + +## License + +LGPL-3 \ No newline at end of file diff --git a/dependency_map_view/__init__.py b/dependency_map_view/__init__.py new file mode 100644 index 00000000000..f65029d5c5d --- /dev/null +++ b/dependency_map_view/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +""" +Dependency & Impact Map View Module Initialization + +This module provides an interactive visual graph view for analyzing record +dependencies and relationships in Odoo. It enables users to visualize how +records are connected through Many2One, One2Many, and Many2Many relationships. + +The module automatically integrates with all Odoo models that have relational +fields, providing a new 'dependency_map' view type that can be added to any +window action. + +Key Components: + - models: Contains model extensions for ir.ui.view and ir.actions.act_window + - hooks: Post-installation hook to add dependency_map view to existing actions + +""" + +# Import model extensions +from . import models + +# Import post-installation hook for automatic view integration +from .hooks import post_init_hook diff --git a/dependency_map_view/__manifest__.py b/dependency_map_view/__manifest__.py new file mode 100644 index 00000000000..b7b22538457 --- /dev/null +++ b/dependency_map_view/__manifest__.py @@ -0,0 +1,52 @@ +{ + 'name': 'Dependency & Impact Map View', + 'version': '18.0.1.0.0', + 'summary': 'Interactive visual graph view for analyzing record dependencies and relationships', + 'description': """ + Dependency & Impact Map View + ============================= + + Features: + --------- + * Visual graph representation of record relationships + * Interactive network diagram with vis.js + * Support for Many2One, One2Many, and Many2Many relationships + * Color-coded relationship types + * Hierarchical layout with automatic positioning + * Click-to-navigate to related records + * Export capabilities (PNG, JSON, PDF) + * Real-time relationship analysis + * Automatic view integration for all models + + Technical: + ---------- + * Built with OWL framework + * Responsive design + * Optimized for large datasets + * Background PDF generation + * Progress tracking in systray + """, + 'category': 'Productivity/Tools', + 'author': 'Faizan Lodhi', + 'website': 'https://axiomworld.net', + 'depends': ['base', 'web'], + 'data': [ + 'views/default_view.xml', + ], + 'assets': { + 'web.assets_backend': [ + 'dependency_map_view/static/src/libs/vis-network.min.js', + 'dependency_map_view/static/src/components/map_view/map_view.js', + 'dependency_map_view/static/src/components/map_view/map_view.xml', + 'dependency_map_view/static/src/components/map_view/map_view.scss', + ], + }, + 'images': ['static/description/icon.png'], + 'post_init_hook': 'post_init_hook', + 'installable': True, + 'application': False, + 'auto_install': False, + 'license': 'LGPL-3', + 'price': 0.00, + 'currency': 'USD', +} diff --git a/dependency_map_view/hooks.py b/dependency_map_view/hooks.py new file mode 100644 index 00000000000..2ae419bce17 --- /dev/null +++ b/dependency_map_view/hooks.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +""" +Post-Installation Hook for Dependency Map View + +This module contains the post-installation hook that automatically adds the +'dependency_map' view mode to all existing window actions in the system. + +The hook ensures that the dependency map view is available across all models +without requiring manual configuration for each action. +""" + + +def post_init_hook(env): + """ + Add dependency_map view mode to all existing window actions. + + This function is executed automatically after the module is installed. + It searches for all ir.actions.act_window records that have a view_mode + defined and appends 'dependency_map' to the view_mode if it's not already + present. + + This allows users to access the dependency map view for any model without + needing to manually update each action's view_mode configuration. + + Args: + env (odoo.api.Environment): The Odoo environment object providing + access to the database and models. + + Returns: + None + + Example: + After installation, an action with view_mode='tree,form' will be + updated to view_mode='tree,form,dependency_map' + + Note: + - Only actions with existing view_mode values are modified + - Duplicate 'dependency_map' entries are prevented by checking first + - This hook runs once during module installation + """ + # Search for all window actions that have a view_mode defined + actions = env['ir.actions.act_window'].search([('view_mode', '!=', False)]) + + # Iterate through each action and add dependency_map view if not present + for action in actions: + # Check if dependency_map is not already in the view_mode + if 'dependency_map' not in action.view_mode: + # Append dependency_map to the existing view modes + action.view_mode = action.view_mode + ',dependency_map' diff --git a/dependency_map_view/models/__init__.py b/dependency_map_view/models/__init__.py new file mode 100644 index 00000000000..69562ad000f --- /dev/null +++ b/dependency_map_view/models/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +""" +Models Package Initialization + +This package contains model extensions that add dependency map view +functionality to Odoo's core models. + +Modules: + - ir_ui_view: Extends ir.ui.view to add 'dependency_map' as a new view type + - ir_actions: Extends ir.actions.act_window to automatically add dependency + map view to actions for models with relational fields + + +""" + +# Import view model extension +from . import ir_ui_view + +# Import action model extension +from . import ir_actions diff --git a/dependency_map_view/models/ir_actions.py b/dependency_map_view/models/ir_actions.py new file mode 100644 index 00000000000..e5ce60944e3 --- /dev/null +++ b/dependency_map_view/models/ir_actions.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +""" +Window Action Extension for Dependency Map View + +This module extends the ir.actions.act_window model to automatically add +the dependency_map view mode to newly created actions when the target model +contains relational fields (Many2One, One2Many, or Many2Many). + +The extension intelligently detects whether a model would benefit from the +dependency map view by checking for the presence of relational fields, and +excludes system models (ir.* and mail.*) that typically don't need this view. + +""" + +from odoo import models, api + + +class IrActionsActWindow(models.Model): + """ + Extension of ir.actions.act_window to auto-add dependency map view. + + This class extends the standard Odoo window action model to automatically + include the 'dependency_map' view mode for models that have relational + fields, making the dependency visualization available without manual + configuration. + + Attributes: + _inherit (str): Inherits from 'ir.actions.act_window' model + """ + + _inherit = 'ir.actions.act_window' + + @api.model_create_multi + def create(self, vals_list): + """ + Override create method to auto-add dependency_map view mode. + + This method intercepts the creation of new window actions and + automatically appends 'dependency_map' to the view_mode if: + 1. The action has a view_mode defined + 2. The dependency_map is not already present + 3. The target model is not a system model (ir.* or mail.*) + 4. The target model has at least one relational field + + Args: + vals_list (list): List of dictionaries containing values for + creating new action records. Each dictionary represents + one action to be created. + + Returns: + recordset: The newly created ir.actions.act_window records + + Example: + When creating an action for 'res.partner' model: + Input: {'name': 'Partners', 'res_model': 'res.partner', + 'view_mode': 'tree,form'} + Result: view_mode becomes 'tree,form,dependency_map' + + Note: + - System models (ir.*, mail.*) are excluded to avoid cluttering + technical views + - Models without relational fields are skipped as they wouldn't + benefit from dependency visualization + - Errors during field checking are silently caught to prevent + action creation failures + """ + # Call parent create method to create the actions + actions = super().create(vals_list) + + # Process each newly created action + for action in actions: + # Check if action has view_mode and dependency_map is not already present + if action.view_mode and 'dependency_map' not in action.view_mode: + # Verify the action has a target model and it's not a system model + if action.res_model and not action.res_model.startswith(('ir.', 'mail.')): + # Check if model has relational fields that would benefit from dependency map + try: + # Get the model instance + model = self.env[action.res_model] + + # Retrieve all field definitions for the model + fields = model.fields_get() + + # Check if any field is a relational field type + has_relations = any( + f.get('type') in ['many2one', 'one2many', 'many2many'] + for f in fields.values() + ) + + # If model has relational fields, add dependency_map view + if has_relations: + action.view_mode = action.view_mode + ',dependency_map' + except Exception: + # Silently pass if there's any error checking the model + # This prevents breaking action creation due to model access issues + pass + + return actions diff --git a/dependency_map_view/models/ir_ui_view.py b/dependency_map_view/models/ir_ui_view.py new file mode 100644 index 00000000000..fedf974923d --- /dev/null +++ b/dependency_map_view/models/ir_ui_view.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +""" +UI View Extension for Dependency Map View Type + +This module extends the ir.ui.view model to add 'dependency_map' as a new +view type in Odoo. It enables the system to recognize and handle dependency +map views, and automatically creates default views when needed. + +The extension allows any model to have a dependency map view without requiring +manual view definition in XML. If a dependency_map view is requested but doesn't +exist, it will be automatically generated. + + +""" + +from odoo import fields, models, api + + +class View(models.Model): + """ + Extension of ir.ui.view to support dependency_map view type. + + This class extends Odoo's view model to register 'dependency_map' as a + valid view type alongside standard views (tree, form, kanban, etc.). + It also provides automatic view creation functionality when a dependency + map view is requested but doesn't exist. + + Attributes: + _inherit (str): Inherits from 'ir.ui.view' model + type (fields.Selection): Extended selection field to include dependency_map + """ + + _inherit = 'ir.ui.view' + + # Extend the type selection field to include 'dependency_map' as a valid view type + type = fields.Selection( + selection_add=[('dependency_map', 'Dependency Map')], + ondelete={'dependency_map': 'cascade'} # Delete views when view type is removed + ) + + @api.model + def default_view(self, model, view_type): + """ + Auto-create dependency_map view if it doesn't exist for a model. + + This method overrides the default_view method to provide automatic + view creation for dependency_map views. When a dependency_map view + is requested for a model but doesn't exist, this method creates a + minimal default view automatically. + + This eliminates the need to manually define dependency_map views in + XML for every model, as the view will be generated on-demand with + a standard structure. + + Args: + model (str): The technical name of the model (e.g., 'res.partner') + view_type (str): The type of view being requested (e.g., 'form', + 'tree', 'dependency_map') + + Returns: + int: The database ID of the view record (either existing or newly created) + + Example: + When requesting dependency_map view for 'sale.order': + - If view exists: Returns existing view ID + - If view doesn't exist: Creates new view with minimal arch and returns its ID + + Note: + - Only handles 'dependency_map' view type; other types are passed to parent + - Created views have a standardized naming convention: {model}.dependency.map.auto + - The arch is minimal: '' as the frontend handles rendering + - Views are created with 'primary' mode to be used as default + """ + # Check if the requested view type is dependency_map + if view_type == 'dependency_map': + # Search for existing dependency_map view for this model + view = self.search([ + ('model', '=', model), + ('type', '=', 'dependency_map') + ], limit=1) + + # If no view exists, create a default one automatically + if not view: + view = self.create({ + 'name': f'{model}.dependency.map.auto', # Auto-generated name + 'model': model, # Target model + 'type': 'dependency_map', # View type + 'arch': '', # Minimal XML architecture + 'mode': 'primary', # Set as primary view for this type + }) + + # Return the view ID (either found or created) + return view.id + + # For all other view types, use the standard parent method + return super().default_view(model, view_type) + + def _get_view_info(self): + # Get the original dictionary + view_info = super()._get_view_info() + + view_info['dependency_map'] = { + 'icon': 'fa fa-code-fork', + 'multi_record': True, + } + + return view_info diff --git a/dependency_map_view/static/description/icon.png b/dependency_map_view/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7108c3aa8a5a2d3c5cb0736778fb7611dc32f1bd GIT binary patch literal 23769 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 this.loadRecords()); + onWillUnmount(() => { + if (this.network) this.network.destroy(); + }); + } + + async loadRecords() { + this.state.loading = true; + const model = this.props.resModel || 'res.partner'; + const records = await this.orm.searchRead(model, [], ['id', 'display_name'], { limit: 50 }); + this.state.records = records; + this.state.loading = false; + } + + async selectRecord(record) { + this.state.selectedRecord = record; + await this.renderMap(record); + } + + async renderMap(record) { + if (!record) return; + + this.state.loading = true; + const model = this.props.resModel || 'res.partner'; + + const nodes = []; + const edges = []; + + nodes.push({ + id: record.id, + label: record.display_name, + shape: 'box', + color: { background: '#9b59b6', border: '#8e44ad' }, + font: { size: 14, color: '#ffffff', bold: true }, + margin: 10, + level: 0, + }); + + const fields = await this.orm.call(model, 'fields_get', [], { + attributes: ['type', 'relation', 'string'] + }); + + for (const [fieldName, fieldInfo] of Object.entries(fields)) { + if (!['many2one', 'one2many', 'many2many'].includes(fieldInfo.type)) continue; + if (!fieldInfo.relation) continue; + if (fieldInfo.relation.includes('mail.') || fieldInfo.relation.includes('ir.')) continue; + + const fullRecord = await this.orm.searchRead(model, [['id', '=', record.id]], [fieldName]); + + if (fullRecord.length > 0 && fullRecord[0][fieldName]) { + const relValue = fullRecord[0][fieldName]; + const isManyToOne = fieldInfo.type === 'many2one'; + + // Handle many2one fields (single relationship) + if (isManyToOne) { + let relId, relName; + + // Case 1: [id, name] tuple format + if (Array.isArray(relValue) && relValue.length === 2 && typeof relValue[0] === 'number') { + relId = relValue[0]; + relName = relValue[1]; + } + // Case 2: Just ID (number) + else if (typeof relValue === 'number') { + relId = relValue; + try { + const relRecords = await this.orm.searchRead(fieldInfo.relation, [['id', '=', relId]], ['display_name']); + relName = relRecords[0]?.display_name || `ID: ${relId}`; + } catch (e) { + relName = `ID: ${relId}`; + } + } + + if (relId) { + const relNodeId = `${fieldInfo.relation}_${relId}_${fieldName}`; + if (!nodes.find(n => n.id === relNodeId)) { + nodes.push({ + id: relNodeId, + label: relName, + shape: 'box', + color: { background: '#f39c12', border: '#e67e22' }, + font: { size: 12, color: '#ffffff' }, + margin: 8, + level: -1, + }); + } + edges.push({ + from: relNodeId, + to: record.id, + arrows: 'to', + label: fieldInfo.string, + color: { color: '#e67e22' }, + width: 2, + }); + } + } + // Handle one2many and many2many fields (multiple relationships) + else if (Array.isArray(relValue) && relValue.length > 0) { + const isManyToMany = fieldInfo.type === 'many2many'; + const nodeColor = isManyToMany + ? { background: '#27ae60', border: '#229954' } // Green for many2many + : { background: '#3498db', border: '#2980b9' }; // Blue for one2many + const edgeStyle = isManyToMany ? { dashes: [5, 5] } : {}; + + const validIds = relValue.filter(id => typeof id === 'number'); + for (const relId of validIds) { + try { + const relRecords = await this.orm.searchRead(fieldInfo.relation, [['id', '=', relId]], ['id', 'display_name']); + if (relRecords.length > 0) { + const relNodeId = `${fieldInfo.relation}_${relId}`; + if (!nodes.find(n => n.id === relNodeId)) { + nodes.push({ + id: relNodeId, + label: relRecords[0].display_name || `ID: ${relId}`, + shape: 'box', + color: nodeColor, + font: { size: 12, color: '#ffffff' }, + margin: 8, + level: 1, + }); + } + const edgeColor = isManyToMany ? '#229954' : '#2980b9'; + edges.push({ + from: record.id, + to: relNodeId, + arrows: 'to', + label: fieldInfo.string, + color: { color: edgeColor }, + width: 2, + }); + } + } catch (e) { + console.error('Error fetching relation:', fieldName, relId, e); + } + } + } + } + } + + this.drawNetwork(nodes, edges); + this.state.loading = false; + } + + drawNetwork(nodes, edges) { + if (this.network) this.network.destroy(); + + const container = this.containerRef.el; + const data = { nodes, edges }; + const options = { + layout: { + hierarchical: { + enabled: true, + direction: 'UD', + sortMethod: 'directed', + levelSeparation: 150, + nodeSpacing: 200, + treeSpacing: 250, + blockShifting: true, + edgeMinimization: true, + parentCentralization: true, + } + }, + physics: { enabled: false }, + nodes: { + shape: 'box', + font: { + size: 13, + face: 'Arial', + color: '#ffffff' + }, + borderWidth: 2, + borderWidthSelected: 3, + shadow: { + enabled: true, + color: 'rgba(0,0,0,0.2)', + size: 10, + x: 3, + y: 3 + }, + margin: 10, + widthConstraint: { + minimum: 120, + maximum: 250 + } + }, + edges: { + width: 2, + font: { + size: 11, + color: '#666666', + background: '#ffffff', + strokeWidth: 0 + }, + smooth: { + enabled: true, + type: 'cubicBezier', + roundness: 0.5 + }, + arrows: { + to: { + enabled: true, + scaleFactor: 0.8 + } + } + }, + interaction: { + hover: true, + zoomView: true, + dragView: true, + selectConnectedEdges: false + } + }; + + this.network = new vis.Network(container, data, options); + + this.network.on('click', (params) => { + if (params.nodes.length > 0) { + const nodeId = params.nodes[0]; + let model, id; + + if (typeof nodeId === 'number') { + model = this.props.resModel; + id = nodeId; + } else { + [model, id] = nodeId.split('_'); + id = parseInt(id); + } + + this.action.doAction({ + type: 'ir.actions.act_window', + res_model: model, + res_id: id, + views: [[false, 'form']], + target: 'new', + }); + } + }); + } +} + +export class DependencyMapController extends Component { + static template = "dependency_map_view.MapController"; + static components = { Layout, DependencyMapRenderer }; + static props = ["*"]; +} + +export const dependencyMapView = { + type: "dependency_map", + display_name: "Dependency Map", + icon: "fa fa-code-fork", + multiRecord: true, + Controller: DependencyMapController, +}; + +registry.category("views").add("dependency_map", dependencyMapView); diff --git a/dependency_map_view/static/src/components/map_view/map_view.scss b/dependency_map_view/static/src/components/map_view/map_view.scss new file mode 100644 index 00000000000..06349272a35 --- /dev/null +++ b/dependency_map_view/static/src/components/map_view/map_view.scss @@ -0,0 +1,178 @@ +.o_dependency_map_view { + height: 100%; + display: flex; + flex-direction: column; +} + +.o_dependency_map_container { + flex: 1; + display: flex; + background: #ecf0f1; + overflow: hidden; +} + +.o_map_sidebar { + width: 280px; + background: white; + border-right: 2px solid #bdc3c7; + display: flex; + flex-direction: column; + + .sidebar_header { + padding: 15px; + border-bottom: 2px solid #ecf0f1; + background: #f8f9fa; + + h5 { + margin: 0; + font-size: 15px; + font-weight: 600; + color: #2c3e50; + } + } + + .sidebar_content { + flex: 1; + overflow-y: auto; + + .record_item { + padding: 10px 15px; + cursor: pointer; + border-bottom: 1px solid #ecf0f1; + transition: all 0.2s; + display: flex; + align-items: center; + + &:hover { + background: #f8f9fa; + } + + &.active { + background: #3498db; + color: white; + font-weight: 500; + + i { + color: white; + } + } + + i { + color: #95a5a6; + font-size: 8px; + } + + span { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + } +} + +.o_map_content { + flex: 1; + position: relative; + background: #f5f7fa; + background-image: + linear-gradient(rgba(200, 200, 200, 0.1) 1px, transparent 1px), + linear-gradient(90deg, rgba(200, 200, 200, 0.1) 1px, transparent 1px); + background-size: 20px 20px; +} + +.o_map_toolbar { + position: absolute; + top: 15px; + left: 15px; + z-index: 10; + display: flex; + gap: 10px; + + .btn { + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + + &.active { + background: #3498db; + color: white; + border-color: #2980b9; + } + } +} + +.o_map_legend { + position: absolute; + top: 15px; + right: 15px; + background: white; + padding: 15px; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0,0,0,0.15); + z-index: 10; + min-width: 200px; + + h6 { + margin: 0 0 10px 0; + font-size: 13px; + font-weight: 600; + color: #2c3e50; + } + + .legend_item { + display: flex; + align-items: center; + margin: 8px 0; + font-size: 12px; + + .legend_dot { + width: 16px; + height: 16px; + border-radius: 50%; + margin-right: 8px; + border: 2px solid rgba(0,0,0,0.2); + } + } + + hr { + margin: 10px 0; + } + + small { + display: block; + line-height: 1.6; + } +} + +.o_map_canvas { + width: 100%; + height: 100%; + + // Override vis.js default styles for cleaner look + canvas { + outline: none !important; + } +} + +.o_map_empty { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + text-align: center; + color: #95a5a6; + + i { + color: #bdc3c7; + } + + h4 { + color: #7f8c8d; + margin-bottom: 10px; + } + + p { + color: #95a5a6; + font-size: 14px; + } +} diff --git a/dependency_map_view/static/src/components/map_view/map_view.xml b/dependency_map_view/static/src/components/map_view/map_view.xml new file mode 100644 index 00000000000..ccad6e4e69a --- /dev/null +++ b/dependency_map_view/static/src/components/map_view/map_view.xml @@ -0,0 +1,75 @@ + + + + + + + + + +
+
+ + +
+ +
+ +
+ +

Select a Record

+

Choose a record from the sidebar

+
+
+ + +
+
Relationship Types
+
+ + Selected Record +
+
+ + Many2One (Parent) +
+
+ + One2Many (Children) +
+
+ + Many2Many (Tags) +
+
+ + Lines:
+ Solid = One2Many
+ Dashed = Many2One/Many2Many +
+
+ +
+ +
+
+ + diff --git a/dependency_map_view/static/src/libs/vis-network.min.js b/dependency_map_view/static/src/libs/vis-network.min.js new file mode 100644 index 00000000000..bff4916d2f0 --- /dev/null +++ b/dependency_map_view/static/src/libs/vis-network.min.js @@ -0,0 +1,27 @@ +/** + * vis-network + * https://visjs.github.io/vis-network/ + * + * A dynamic, browser-based visualization library. + * + * @version 9.1.2 + * @date 2022-03-28T20:17:35.342Z + * + * @copyright (c) 2011-2017 Almende B.V, http://almende.com + * @copyright (c) 2017-2019 visjs contributors, https://github.com/visjs + * + * @license + * vis.js is dual licensed under both + * + * 1. The Apache 2.0 License + * http://www.apache.org/licenses/LICENSE-2.0 + * + * and + * + * 2. The MIT License + * http://opensource.org/licenses/MIT + * + * vis.js may be distributed under either license. + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).vis=t.vis||{})}(this,(function(t){"use strict";var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},i=function(t){return t&&t.Math==Math&&t},n=i("object"==typeof globalThis&&globalThis)||i("object"==typeof window&&window)||i("object"==typeof self&&self)||i("object"==typeof e&&e)||function(){return this}()||Function("return this")(),o=function(t){try{return!!t()}catch(t){return!0}},r=!o((function(){var t=function(){}.bind();return"function"!=typeof t||t.hasOwnProperty("prototype")})),s=r,a=Function.prototype,h=a.apply,l=a.call,d="object"==typeof Reflect&&Reflect.apply||(s?l.bind(h):function(){return l.apply(h,arguments)}),c=r,u=Function.prototype,f=u.bind,p=u.call,v=c&&f.bind(p,p),g=c?function(t){return t&&v(t)}:function(t){return t&&function(){return p.apply(t,arguments)}},y=function(t){return"function"==typeof t},m={},b=!o((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]})),w=r,k=Function.prototype.call,_=w?k.bind(k):function(){return k.apply(k,arguments)},x={},E={}.propertyIsEnumerable,O=Object.getOwnPropertyDescriptor,C=O&&!E.call({1:2},1);x.f=C?function(t){var e=O(this,t);return!!e&&e.enumerable}:E;var S,T,M=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}},P=g,D=P({}.toString),I=P("".slice),B=function(t){return I(D(t),8,-1)},z=g,N=o,F=B,A=n.Object,j=z("".split),R=N((function(){return!A("z").propertyIsEnumerable(0)}))?function(t){return"String"==F(t)?j(t,""):A(t)}:A,L=n.TypeError,H=function(t){if(null==t)throw L("Can't call method on "+t);return t},W=R,q=H,V=function(t){return W(q(t))},U=y,Y=function(t){return"object"==typeof t?null!==t:U(t)},X={},G=X,K=n,$=y,Z=function(t){return $(t)?t:void 0},Q=function(t,e){return arguments.length<2?Z(G[t])||Z(K[t]):G[t]&&G[t][e]||K[t]&&K[t][e]},J=g({}.isPrototypeOf),tt=Q("navigator","userAgent")||"",et=n,it=tt,nt=et.process,ot=et.Deno,rt=nt&&nt.versions||ot&&ot.version,st=rt&&rt.v8;st&&(T=(S=st.split("."))[0]>0&&S[0]<4?1:+(S[0]+S[1])),!T&&it&&(!(S=it.match(/Edge\/(\d+)/))||S[1]>=74)&&(S=it.match(/Chrome\/(\d+)/))&&(T=+S[1]);var at=T,ht=at,lt=o,dt=!!Object.getOwnPropertySymbols&&!lt((function(){var t=Symbol();return!String(t)||!(Object(t)instanceof Symbol)||!Symbol.sham&&ht&&ht<41})),ct=dt&&!Symbol.sham&&"symbol"==typeof Symbol.iterator,ut=Q,ft=y,pt=J,vt=ct,gt=n.Object,yt=vt?function(t){return"symbol"==typeof t}:function(t){var e=ut("Symbol");return ft(e)&&pt(e.prototype,gt(t))},mt=n.String,bt=function(t){try{return mt(t)}catch(t){return"Object"}},wt=y,kt=bt,_t=n.TypeError,xt=function(t){if(wt(t))return t;throw _t(kt(t)+" is not a function")},Et=xt,Ot=function(t,e){var i=t[e];return null==i?void 0:Et(i)},Ct=_,St=y,Tt=Y,Mt=n.TypeError,Pt={exports:{}},Dt=n,It=Object.defineProperty,Bt=function(t,e){try{It(Dt,t,{value:e,configurable:!0,writable:!0})}catch(i){Dt[t]=e}return e},zt="__core-js_shared__",Nt=n[zt]||Bt(zt,{}),Ft=Nt;(Pt.exports=function(t,e){return Ft[t]||(Ft[t]=void 0!==e?e:{})})("versions",[]).push({version:"3.21.1",mode:"pure",copyright:"© 2014-2022 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.21.1/LICENSE",source:"https://github.com/zloirock/core-js"});var At=H,jt=n.Object,Rt=function(t){return jt(At(t))},Lt=Rt,Ht=g({}.hasOwnProperty),Wt=Object.hasOwn||function(t,e){return Ht(Lt(t),e)},qt=g,Vt=0,Ut=Math.random(),Yt=qt(1..toString),Xt=function(t){return"Symbol("+(void 0===t?"":t)+")_"+Yt(++Vt+Ut,36)},Gt=n,Kt=Pt.exports,$t=Wt,Zt=Xt,Qt=dt,Jt=ct,te=Kt("wks"),ee=Gt.Symbol,ie=ee&&ee.for,ne=Jt?ee:ee&&ee.withoutSetter||Zt,oe=function(t){if(!$t(te,t)||!Qt&&"string"!=typeof te[t]){var e="Symbol."+t;Qt&&$t(ee,t)?te[t]=ee[t]:te[t]=Jt&&ie?ie(e):ne(e)}return te[t]},re=_,se=Y,ae=yt,he=Ot,le=function(t,e){var i,n;if("string"===e&&St(i=t.toString)&&!Tt(n=Ct(i,t)))return n;if(St(i=t.valueOf)&&!Tt(n=Ct(i,t)))return n;if("string"!==e&&St(i=t.toString)&&!Tt(n=Ct(i,t)))return n;throw Mt("Can't convert object to primitive value")},de=oe,ce=n.TypeError,ue=de("toPrimitive"),fe=function(t,e){if(!se(t)||ae(t))return t;var i,n=he(t,ue);if(n){if(void 0===e&&(e="default"),i=re(n,t,e),!se(i)||ae(i))return i;throw ce("Can't convert object to primitive value")}return void 0===e&&(e="number"),le(t,e)},pe=yt,ve=function(t){var e=fe(t,"string");return pe(e)?e:e+""},ge=Y,ye=n.document,me=ge(ye)&&ge(ye.createElement),be=function(t){return me?ye.createElement(t):{}},we=be,ke=!b&&!o((function(){return 7!=Object.defineProperty(we("div"),"a",{get:function(){return 7}}).a})),_e=b,xe=_,Ee=x,Oe=M,Ce=V,Se=ve,Te=Wt,Me=ke,Pe=Object.getOwnPropertyDescriptor;m.f=_e?Pe:function(t,e){if(t=Ce(t),e=Se(e),Me)try{return Pe(t,e)}catch(t){}if(Te(t,e))return Oe(!xe(Ee.f,t,e),t[e])};var De=o,Ie=y,Be=/#|\.prototype\./,ze=function(t,e){var i=Fe[Ne(t)];return i==je||i!=Ae&&(Ie(e)?De(e):!!e)},Ne=ze.normalize=function(t){return String(t).replace(Be,".").toLowerCase()},Fe=ze.data={},Ae=ze.NATIVE="N",je=ze.POLYFILL="P",Re=ze,Le=xt,He=r,We=g(g.bind),qe=function(t,e){return Le(t),void 0===e?t:He?We(t,e):function(){return t.apply(e,arguments)}},Ve={},Ue=b&&o((function(){return 42!=Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype})),Ye=n,Xe=Y,Ge=Ye.String,Ke=Ye.TypeError,$e=function(t){if(Xe(t))return t;throw Ke(Ge(t)+" is not an object")},Ze=b,Qe=ke,Je=Ue,ti=$e,ei=ve,ii=n.TypeError,ni=Object.defineProperty,oi=Object.getOwnPropertyDescriptor,ri="enumerable",si="configurable",ai="writable";Ve.f=Ze?Je?function(t,e,i){if(ti(t),e=ei(e),ti(i),"function"==typeof t&&"prototype"===e&&"value"in i&&ai in i&&!i.writable){var n=oi(t,e);n&&n.writable&&(t[e]=i.value,i={configurable:si in i?i.configurable:n.configurable,enumerable:ri in i?i.enumerable:n.enumerable,writable:!1})}return ni(t,e,i)}:ni:function(t,e,i){if(ti(t),e=ei(e),ti(i),Qe)try{return ni(t,e,i)}catch(t){}if("get"in i||"set"in i)throw ii("Accessors not supported");return"value"in i&&(t[e]=i.value),t};var hi=Ve,li=M,di=b?function(t,e,i){return hi.f(t,e,li(1,i))}:function(t,e,i){return t[e]=i,t},ci=n,ui=d,fi=g,pi=y,vi=m.f,gi=Re,yi=X,mi=qe,bi=di,wi=Wt,ki=function(t){var e=function(i,n,o){if(this instanceof e){switch(arguments.length){case 0:return new t;case 1:return new t(i);case 2:return new t(i,n)}return new t(i,n,o)}return ui(t,this,arguments)};return e.prototype=t.prototype,e},_i=function(t,e){var i,n,o,r,s,a,h,l,d=t.target,c=t.global,u=t.stat,f=t.proto,p=c?ci:u?ci[d]:(ci[d]||{}).prototype,v=c?yi:yi[d]||bi(yi,d,{})[d],g=v.prototype;for(o in e)i=!gi(c?o:d+(u?".":"#")+o,t.forced)&&p&&wi(p,o),s=v[o],i&&(a=t.noTargetGet?(l=vi(p,o))&&l.value:p[o]),r=i&&a?a:e[o],i&&typeof s==typeof r||(h=t.bind&&i?mi(r,ci):t.wrap&&i?ki(r):f&&pi(r)?fi(r):r,(t.sham||r&&r.sham||s&&s.sham)&&bi(h,"sham",!0),bi(v,o,h),f&&(wi(yi,n=d+"Prototype")||bi(yi,n,{}),bi(yi[n],o,r),t.real&&g&&!g[o]&&bi(g,o,r)))},xi=Math.ceil,Ei=Math.floor,Oi=function(t){var e=+t;return e!=e||0===e?0:(e>0?Ei:xi)(e)},Ci=Oi,Si=Math.max,Ti=Math.min,Mi=function(t,e){var i=Ci(t);return i<0?Si(i+e,0):Ti(i,e)},Pi=Oi,Di=Math.min,Ii=function(t){return t>0?Di(Pi(t),9007199254740991):0},Bi=function(t){return Ii(t.length)},zi=V,Ni=Mi,Fi=Bi,Ai=function(t){return function(e,i,n){var o,r=zi(e),s=Fi(r),a=Ni(n,s);if(t&&i!=i){for(;s>a;)if((o=r[a++])!=o)return!0}else for(;s>a;a++)if((t||a in r)&&r[a]===i)return t||a||0;return!t&&-1}},ji={includes:Ai(!0),indexOf:Ai(!1)},Ri={},Li=Wt,Hi=V,Wi=ji.indexOf,qi=Ri,Vi=g([].push),Ui=function(t,e){var i,n=Hi(t),o=0,r=[];for(i in n)!Li(qi,i)&&Li(n,i)&&Vi(r,i);for(;e.length>o;)Li(n,i=e[o++])&&(~Wi(r,i)||Vi(r,i));return r},Yi=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],Xi=Ui,Gi=Yi,Ki=Object.keys||function(t){return Xi(t,Gi)},$i={};$i.f=Object.getOwnPropertySymbols;var Zi=b,Qi=g,Ji=_,tn=o,en=Ki,nn=$i,on=x,rn=Rt,sn=R,an=Object.assign,hn=Object.defineProperty,ln=Qi([].concat),dn=!an||tn((function(){if(Zi&&1!==an({b:1},an(hn({},"a",{enumerable:!0,get:function(){hn(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var t={},e={},i=Symbol(),n="abcdefghijklmnopqrst";return t[i]=7,n.split("").forEach((function(t){e[t]=t})),7!=an({},t)[i]||en(an({},e)).join("")!=n}))?function(t,e){for(var i=rn(t),n=arguments.length,o=1,r=nn.f,s=on.f;n>o;)for(var a,h=sn(arguments[o++]),l=r?ln(en(h),r(h)):en(h),d=l.length,c=0;d>c;)a=l[c++],Zi&&!Ji(s,h,a)||(i[a]=h[a]);return i}:an,cn=dn;_i({target:"Object",stat:!0,forced:Object.assign!==cn},{assign:cn});var un=X.Object.assign,fn=g([].slice),pn=g,vn=xt,gn=Y,yn=Wt,mn=fn,bn=r,wn=n.Function,kn=pn([].concat),_n=pn([].join),xn={},En=function(t,e,i){if(!yn(xn,e)){for(var n=[],o=0;o=.1;)(p=+r[c++%s])>d&&(p=d),f=Math.sqrt(p*p/(1+l*l)),e+=f=a<0?-f:f,i+=l*f,!0===u?t.lineTo(e,i):t.moveTo(e,i),d-=p,u=!u}var Ln={circle:Nn,dashedLine:Rn,database:jn,diamond:function(t,e,i,n){t.beginPath(),t.lineTo(e,i+n),t.lineTo(e+n,i),t.lineTo(e,i-n),t.lineTo(e-n,i),t.closePath()},ellipse:An,ellipse_vis:An,hexagon:function(t,e,i,n){t.beginPath();var o=2*Math.PI/6;t.moveTo(e+n,i);for(var r=1;r<6;r++)t.lineTo(e+n*Math.cos(o*r),i+n*Math.sin(o*r));t.closePath()},roundRect:Fn,square:function(t,e,i,n){t.beginPath(),t.rect(e-n,i-n,2*n,2*n),t.closePath()},star:function(t,e,i,n){t.beginPath(),i+=.1*(n*=.82);for(var o=0;o<10;o++){var r=o%2==0?1.3*n:.5*n;t.lineTo(e+r*Math.sin(2*o*Math.PI/10),i-r*Math.cos(2*o*Math.PI/10))}t.closePath()},triangle:function(t,e,i,n){t.beginPath(),i+=.275*(n*=1.15);var o=2*n,r=o/2,s=Math.sqrt(3)/6*o,a=Math.sqrt(o*o-r*r);t.moveTo(e,i-(a-s)),t.lineTo(e+r,i+s),t.lineTo(e-r,i+s),t.lineTo(e,i-(a-s)),t.closePath()},triangleDown:function(t,e,i,n){t.beginPath(),i-=.275*(n*=1.15);var o=2*n,r=o/2,s=Math.sqrt(3)/6*o,a=Math.sqrt(o*o-r*r);t.moveTo(e,i+(a-s)),t.lineTo(e+r,i-s),t.lineTo(e-r,i-s),t.lineTo(e,i+(a-s)),t.closePath()}};var Hn={exports:{}};!function(t){function e(t){if(t)return function(t){for(var i in e.prototype)t[i]=e.prototype[i];return t}(t)}t.exports=e,e.prototype.on=e.prototype.addEventListener=function(t,e){return this._callbacks=this._callbacks||{},(this._callbacks["$"+t]=this._callbacks["$"+t]||[]).push(e),this},e.prototype.once=function(t,e){function i(){this.off(t,i),e.apply(this,arguments)}return i.fn=e,this.on(t,i),this},e.prototype.off=e.prototype.removeListener=e.prototype.removeAllListeners=e.prototype.removeEventListener=function(t,e){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var i,n=this._callbacks["$"+t];if(!n)return this;if(1==arguments.length)return delete this._callbacks["$"+t],this;for(var o=0;o=a?t?"":void 0:(n=ao(r,s))<55296||n>56319||s+1===a||(o=ao(r,s+1))<56320||o>57343?t?so(r,s):n:t?ho(r,s,s+2):o-56320+(n-55296<<10)+65536}},co={codeAt:lo(!1),charAt:lo(!0)},uo=y,fo=Nt,po=g(Function.toString);uo(fo.inspectSource)||(fo.inspectSource=function(t){return po(t)});var vo,go,yo,mo=fo.inspectSource,bo=y,wo=mo,ko=n.WeakMap,_o=bo(ko)&&/native code/.test(wo(ko)),xo=Pt.exports,Eo=Xt,Oo=xo("keys"),Co=function(t){return Oo[t]||(Oo[t]=Eo(t))},So=_o,To=n,Mo=g,Po=Y,Do=di,Io=Wt,Bo=Nt,zo=Co,No=Ri,Fo="Object already initialized",Ao=To.TypeError,jo=To.WeakMap;if(So||Bo.state){var Ro=Bo.state||(Bo.state=new jo),Lo=Mo(Ro.get),Ho=Mo(Ro.has),Wo=Mo(Ro.set);vo=function(t,e){if(Ho(Ro,t))throw new Ao(Fo);return e.facade=t,Wo(Ro,t,e),e},go=function(t){return Lo(Ro,t)||{}},yo=function(t){return Ho(Ro,t)}}else{var qo=zo("state");No[qo]=!0,vo=function(t,e){if(Io(t,qo))throw new Ao(Fo);return e.facade=t,Do(t,qo,e),e},go=function(t){return Io(t,qo)?t[qo]:{}},yo=function(t){return Io(t,qo)}}var Vo={set:vo,get:go,has:yo,enforce:function(t){return yo(t)?go(t):vo(t,{})},getterFor:function(t){return function(e){var i;if(!Po(e)||(i=go(e)).type!==t)throw Ao("Incompatible receiver, "+t+" required");return i}}},Uo=b,Yo=Wt,Xo=Function.prototype,Go=Uo&&Object.getOwnPropertyDescriptor,Ko=Yo(Xo,"name"),$o={EXISTS:Ko,PROPER:Ko&&"something"===function(){}.name,CONFIGURABLE:Ko&&(!Uo||Uo&&Go(Xo,"name").configurable)},Zo={},Qo=b,Jo=Ue,tr=Ve,er=$e,ir=V,nr=Ki;Zo.f=Qo&&!Jo?Object.defineProperties:function(t,e){er(t);for(var i,n=ir(e),o=nr(e),r=o.length,s=0;r>s;)tr.f(t,i=o[s++],n[i]);return t};var or,rr=Q("document","documentElement"),sr=$e,ar=Zo,hr=Yi,lr=Ri,dr=rr,cr=be,ur=Co("IE_PROTO"),fr=function(){},pr=function(t){return"