Skip to content

[18.0][MIG] web_diagram#3494

Draft
mathben wants to merge 38 commits intoOCA:18.0from
TechnoLibre:18.0_mig_web_diagram
Draft

[18.0][MIG] web_diagram#3494
mathben wants to merge 38 commits intoOCA:18.0from
TechnoLibre:18.0_mig_web_diagram

Conversation

@mathben
Copy link
Copy Markdown

@mathben mathben commented Mar 27, 2026

Migrate from #3493
Overview : #3489

Improve user experience and fix bug from version Odoo 13
https://github.com/odoo/odoo/tree/13.0/addons/web_diagram

mathben added 30 commits March 27, 2026 00:15
- apply OCA script migration with OpenUpgrade
In Odoo 14, the test bundle was renamed from web.qunit_suite to
web.qunit_suite_tests. The old xpath also referenced kanban_tests.js
which no longer exists at the expected path, causing an install
error. Use expr="." position="inside" for a stable injection.

Generated by Claude Code 2.1.84 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
Odoo 14 validates the type field of ir.ui.view against a selection
list. Without extending the model to add ("diagram", "Diagram"),
installing any module with a diagram view raises:
"Wrong value for ir.ui.view.type: 'diagram'".
Follows the same pattern as OCA web_timeline.

Generated by Claude Code 2.1.84 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
Odoo 14's view postprocessor validates all <field> elements against
the main diagram model via NameManager. Since <node> and <arrow>
each declare their own 'object' model, their child fields must be
validated against that model instead. Without this, installing any
module with a diagram view raises a field-not-found error on the
connector's source/destination fields.

Generated by Claude Code 2.1.84 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
BasicView._processNode traverses all <field> elements in the arch
and looks them up in fv.viewFields (the main model's fields). For
diagram views, <node> and <arrow> fields belong to their own object
models, causing field to be undefined and crashing _getFieldWidgetClass
at field.type. Stop traversal into those tags and guard field lookups
in init against undefined.

Generated by Claude Code 2.1.84 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
ir.ui.view.graph_get() was dropped from Odoo core when the diagram
view was removed in v14. The diagram controller still calls it to
compute node positions and transitions. Port the Odoo 13 implementation
into the module's own ir.ui.view override so the method is available.

Generated by Claude Code 2.1.84 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
odoo.tools.graph was dropped in Odoo 14 along with the diagram view.
Port the Odoo 13 implementation into the module under tools/graph.py
and update the import to use the local copy.

Generated by Claude Code 2.1.84 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
Odoo 15 dropped the `qweb` manifest key and the XML template
inheritance pattern for assets. Assets must now be declared under
the `assets` key in __manifest__.py using named bundles
(web.assets_backend, web.assets_qweb, web.qunit_suite_tests).
Remove the now-obsolete views/web_diagram_templates.xml.

Generated by Claude Code 2.1.84 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
In Odoo 15, NameManager.__init__ no longer accepts a validate flag,
postprocess() was removed in favour of a stack loop inside
_postprocess_view(), and check_view_fields() was renamed to check().
Replace the manual NameManager/postprocess pattern with a wrapper
element passed to _postprocess_view() against the node/arrow object
model, which handles the full processing pipeline correctly.

Generated by Claude Code 2.1.84 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
The legacy web framework (BasicView, AbstractModel, AbstractController,
AbstractRenderer) was removed in Odoo 16. Rewrite all JS files as OWL
components and ES6 classes. Replace web.assets_qweb (dropped in v16)
by including the XML template directly in web.assets_backend. Replace
the deprecated name_get() call with display_name. Bump version to
16.0.1.0.0.

Generated by Claude Code 2.1.84 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
Provides developer and user reference for the diagram view module:
architecture overview, arch attribute reference, Odoo 16 migration
notes, view switcher limitation with recommended workaround, and
JSON-RPC endpoint signature.

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
- tools.ustr() was removed in Odoo 17; replaced with str()
- web.qunit_suite_tests renamed to web.assets_unit_tests in Odoo 18
- Legacy JS tests (odoo.define + testUtils.createView) removed in
  Odoo 17; rewritten with @odoo/hoot describe/test/expect
- graph.py operator spacing, comment formatting and long line wrapping
  updated to pass OCA ruff/pre-commit checks

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
- Move lxml import to module top (PEP 8, isort)
- Rename PascalCase/uppercase locals to snake_case in graph_get()
- Rename builtin-shadowing param id -> rec_id in graph_get()
- Remove stale version references from docstrings
- Remove unused variable cnt in graph.find_starts()
- Tighten DiagramController.props (replace wildcard ["*"])
- Delete dead view_registry.js (not in manifest)
- Add readme/SECURITY.rst documenting access control policy

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
jQuery was removed from Odoo 18's standard asset bundles, causing a
"jQuery is not defined" crash that broke the entire /web frontend
whenever web_diagram was installed.

Replace the jQuery mousewheel plugin with a native wheel event listener
(addEventListener + event.preventDefault + {passive: false}) and delete
the jquery.mousewheel.js library file.

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
The explicit props definition added in cf2901a used { type: [...] }
syntax which OWL 3 (Odoo 18) rejects with "Invalid object: 'type' is
not valid", crashing the entire web.assets_backend bundle and breaking
all pages under /web. Revert to the ["*"] wildcard which works across
all OWL versions.

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
In Odoo 18, the view registry validates the view definition object and
rejects the 'type' key — the view type is now the registry key itself
('diagram' in registry.category("views").add("diagram", ...)).
Including 'type' caused "Invalid object: 'type' is not valid" which
crashed the entire web.assets_backend bundle.

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
OWL 3 rejects { type: X } prop specs that have no other options
(optional, validate, etc.) with "Invalid object: 'type' is not valid".
The correct OWL 3 syntax is to pass the constructor directly.

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
In Odoo 18, the view registry validates every entry against a schema
that requires a 'type' field whose value must exist in session.view_info.
session.view_info is built from ir.ui.view._get_view_info(), which only
lists standard view types. Without extending _get_view_info(), "diagram"
is absent from session.view_info, causing the JS validation to throw
"Invalid object: 'type' is not valid" in debug/assets mode.

Fix: override _get_view_info() in the diagram module to add the
"diagram" entry, and restore type: "diagram" in the JS view definition
(required by the registry validation schema).

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
In Odoo 18, the rpc service was removed from the service registry.
The rpc function is now a standalone export from
@web/core/network/rpc and must be imported directly instead of
obtained via useService("rpc").

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
…Odoo 18

When switching to the diagram view via the control panel view switcher,
Odoo's switchView() passes no resId prop — it falls back to action.res_id
which is stale (false) for actions opened from a list. The form view's
currentState.resId is never forwarded to the new controller.

Read router.current.resId as a fallback at setup() time. This is safe
because router.pushState() is debounced (setTimeout), so the router
state still holds the form view's resId when setup() runs.

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
…ller

When a page is loaded/reloaded from a URL with resId (e.g. a bookmarked
form record), loadState() creates a lazy breadcrumb controller for the
multi-record view. This controller gets action.jsId assigned but never
receives a 'view' property.

switchView() then iterates the controllerStack checking:
  ct.action.jsId === X && !ct.view.multiRecord
The lazy controller matches on jsId but ct.view is undefined → TypeError.

Patch actionService.start() to catch this specific TypeError and fall
back to doAction() with stackPosition:"replaceCurrentAction", which uses
a different findIndex that only checks jsId (no ct.view access). This
also cleans the lazy controller out of the stack as a side effect.

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
Integrate the Layout component from @web/search/layout so that the
diagram view shows the standard Odoo 18 control panel, giving users
breadcrumb navigation and the form/diagram view-switcher buttons.

The "New Node" button is placed in the control-panel-top-left slot
so it appears alongside the standard controls.

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
The slot name was wrong (control-panel-top-left does not exist in the
ControlPanel template). The correct slot is control-panel-create-button,
which is the standard slot for primary action buttons.

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
Odoo 18 webclient_layout.scss sizes the view via:
  .o_content > .o_view_controller { position: absolute; inset: 0; }
Without the o_view_controller class the diagram container had no height,
causing the Raphael canvas to render as 0px and nodes to be invisible.

Add o_view_controller to the diagram wrapper div and update the SCSS to
use flex layout instead of the now-redundant height:100%.

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
Pager:
- Load the ordered ID list (matching the action domain) on startup.
- Show a Pager in the control-panel-navigation-additional slot when there
  are multiple records.
- onPagerUpdate reloads the diagram for the new record and calls
  router.pushState to keep the URL in sync.
- addNode now uses _currentResId (updated by pager) instead of the stale
  props.resId, so new nodes are always attached to the displayed record.

URL fix:
- Patch actionService.switchView to inject the current resId into props
  before delegating to the original implementation, whenever the target
  view is non-multiRecord.  Without this the action service falls back to
  action.res_id (often false for list-opened actions), dropping the record
  ID from the URL and causing form-view to open a blank new record on
  switch-back.

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
Browsers render SVG elements at a default intrinsic height of 150px when
height="100%" cannot resolve against an explicit parent height.  Even
though .o_diagram gets its height from flex layout (flex:1 1 auto), CSS
percentage resolution for grandchildren is unreliable without a definite
parent height.

Make .o_diagram a positioning context (position:relative) and set the
SVG to position:absolute with inset:0, so it fills the container
independently of how the flex algorithm sizes the parent.

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
…nates

Applying position:absolute directly to the Raphael SVG element breaks its
internal coordinate system and makes nodes invisible.  The root cause is
that height="100%" on the SVG cannot resolve against a flex-grown parent.

Instead, wrap .o_diagram in a new .o_diagram_canvas div that is the flex
item (flex:1 1 auto; position:relative).  .o_diagram itself is then
position:absolute with inset:0, giving it a definite pixel height that
the SVG's height="100%" resolves against correctly.

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
mathben added 3 commits March 27, 2026 03:08
… + 0px SVG

Root cause: new Raphael(div, "100%", "100%") in an off-screen div with no
explicit dimensions resolves "100%" to 0px.  Raphael always sets
overflow:hidden on the SVG (inline style), so all content drawn at y>0 is
clipped by the 0px viewport and never rendered visibly.

Fix: read container.offsetWidth/offsetHeight before creating the Raphael
paper, passing real pixel values so the coordinate system and overflow
clipping match the visible area.  Fall back to 800x600 if the container
has not yet been laid out (rare, but avoids a blank diagram on first mount).

Also change .o_diagram overflow from hidden to auto so that if the diagram
is larger than the viewport the user can scroll, consistent with the
original Odoo 13 behaviour.

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
Odoo 18 reuses the controller instance when navigating within the same
action, so onWillStart does not re-run. Add onWillUpdateProps to reload
fresh data on every re-activation. Also wire up Pager's updateTotal prop
so clicking the total triggers a manual refresh (replaces oe_pager_refresh).

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
The off-screen div approach created a fixed-pixel SVG that did not fill
the container, making nodes invisible when the container had different
dimensions. Rendering Raphael directly in .o_diagram (which has an
explicit height via position:absolute+inset:0 inside .o_diagram_canvas)
lets height="100%" on the SVG resolve to the full available space and
overflow:hidden clip at the correct boundary.

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
mathben added 5 commits March 27, 2026 16:05
height="100%" on the SVG never resolves inside Odoo 18's Layout because
no CSS ancestor provides an explicit height. Use getBoundingClientRect()
to measure how much viewport space remains below the container and pass
that as an explicit pixel height to Raphael. Remove the o_diagram_canvas
wrapper and the position:absolute CSS chain that were added as workarounds
but caused nodes to be invisible.

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
Apply a Bootstrap 5 / Odoo 18 inspired colour palette and card look:
- Nodes: rounded corners (r=8), light border (#CED4DA), near-black text,
  soft blue-grey fill (#EEF0F4) instead of medium grey
- Selected state: Bootstrap primary blue (#0D6EFD), thicker stroke
- Edges: softer blue-grey (#8C96A5), explicit fill:none on path
- Typography: Helvetica Neue / Arial stack on all labels
- Close button / connector: muted grey tones

Two new style keys consumed by graph.js: node_border_radius, font_family.

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
When a start node has no outgoing transitions (isolated root with
flow_start=True but no children), make_acyclic returns [] and
tree_list[start] is empty. A previous start whose own tree has only
one edge trivially passes the same-tree subset check (empty [1:] slice),
so roots becomes non-empty and the code tries tree_list[start][0][1]
on the empty list — IndexError.

Guard the roots block with an additional check that tree_list[self.start]
is non-empty; isolated roots keep their position from tree_order() and
simply skip the multi-root alignment step.

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
Raphael sets overflow:hidden on its SVG element, clipping any node
placed beyond the initial paper height. When many nodes are present,
the layout algorithm places them at large y-coordinates (140px × rank)
that exceed the viewport-based height (~600px), making all but the
first few nodes invisible. Derive the required height from the actual
node coordinates before creating the Raphael paper so all nodes fit.

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
The graph algorithm was designed for single-root DAGs. With multiple
flow_start nodes (e.g. 149 partner roots), process() only ran
make_acyclic from start_nodes[0], so find_starts() re-discovered all
other roots and added them to start_nodes again as duplicates. Each
root was then laid out twice, scrambling y-coordinates and hiding all
but the first few visible nodes.

Three targeted fixes:
- process(): traverse ALL provided start nodes before find_starts so
  every root's subtree is already in partial_order — find_starts only
  picks up truly disconnected remainder nodes.
- find_starts(): guard against adding a node already in start_nodes.
- init_order(): use `is None` instead of falsy check so nodes
  correctly placed at y=0 are not re-initialised on a second pass.

Generated by Claude Code 2.1.85 model claude-sonnet-4-6

Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
@mathben mathben force-pushed the 18.0_mig_web_diagram branch from 2f4c4c1 to cbc7677 Compare March 27, 2026 20:06
@mathben mathben marked this pull request as draft April 2, 2026 06:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants