Skip to content

gui

gui

Domo Magic ETL Canvas GUI Elements

Typed classes for the three element types stored in the dataflow gui dict
  • CanvasTile — a positioned action node on the canvas
  • CanvasComment — a sticky-note annotation
  • CanvasSection — a named group box
Factory

canvas_element_from_dict(obj) → CanvasTile | CanvasComment | CanvasSection | CanvasElement

Manager

CanvasElements.from_dict(gui) — parses the full gui dict, owns the element list CanvasElements.to_dict() — round-trips back to API format

CanvasComment dataclass

CanvasComment(
    id: str,
    element_type: str,
    x: int = 0,
    y: int = 0,
    width: int = TILE_W,
    height: int = TILE_H,
    parent_id: str | None = None,
    raw: dict = None,
    text_blocks: list[dict] = list(),
    background_color: str = "var(--colorBackground1)",
    version: str = "1.0",
)

Bases: CanvasElement

Sticky-note annotation on the canvas.

plain_text property

plain_text: str

Extract flat text from the first paragraph block.

create classmethod

create(
    text: str,
    x: int,
    y: int,
    *,
    width: int = 320,
    height: int = 80,
    background_color: str = "var(--colorBackground1)",
    parent_id: str | None = None
) -> CanvasComment

Create a new comment with plain text content.

Source code in src/crew_dcs/classes/DomoDataflow/gui.py
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
@classmethod
def create(
    cls,
    text: str,
    x: int,
    y: int,
    *,
    width: int = 320,
    height: int = 80,
    background_color: str = "var(--colorBackground1)",
    parent_id: str | None = None,
) -> CanvasComment:
    """Create a new comment with plain text content."""
    return cls(
        id=str(uuid.uuid4()),
        x=x,
        y=y,
        width=width,
        height=height,
        text_blocks=[{"type": "paragraph", "children": [{"text": text}]}],
        background_color=background_color,
        parent_id=parent_id,
        raw=None,
    )

CanvasElement dataclass

CanvasElement(
    id: str,
    element_type: str,
    x: int = 0,
    y: int = 0,
    width: int = TILE_W,
    height: int = TILE_H,
    parent_id: str | None = None,
    raw: dict = None,
)

Base class for all canvas elements.

Subclasses implement from_dict() and to_dict(). Unknown element types fall back to this class (raw preserved).

label property

label: str

Human-readable label for this element. Subclasses override.

from_dict classmethod

from_dict(obj: dict) -> CanvasElement

Create a typed element from a raw dict.

When called on CanvasElement directly, dispatches to the registered subclass for the element's 'type' field (polymorphic factory). When called on a concrete subclass, creates that subclass directly.

Source code in src/crew_dcs/classes/DomoDataflow/gui.py
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
@classmethod
def from_dict(cls, obj: dict) -> CanvasElement:
    """Create a typed element from a raw dict.

    When called on CanvasElement directly, dispatches to the registered
    subclass for the element's 'type' field (polymorphic factory).
    When called on a concrete subclass, creates that subclass directly.
    """
    if cls is CanvasElement:
        concrete_cls = _ELEMENT_REGISTRY.get(obj.get("type", ""))
        if concrete_cls is not None:
            return concrete_cls.from_dict(obj)
    return cls(
        id=obj.get("id", ""),
        element_type=obj.get("type", "Unknown"),
        x=obj.get("x", 0),
        y=obj.get("y", 0),
        width=obj.get("width", TILE_W),
        height=obj.get("height", TILE_H),
        parent_id=obj.get("parentId"),
        raw=obj,
    )

to_dict

to_dict() -> dict

Serialize back to API format. Subclasses extend this.

Source code in src/crew_dcs/classes/DomoDataflow/gui.py
80
81
82
83
84
85
86
87
88
89
90
91
92
def to_dict(self) -> dict:
    """Serialize back to API format. Subclasses extend this."""
    return (
        dict(self.raw)
        if self.raw
        else {
            "id": self.id,
            "type": self.element_type,
            "x": self.x,
            "y": self.y,
            "parentId": self.parent_id,
        }
    )

CanvasElements dataclass

CanvasElements(
    elements: list[CanvasElement] = list(),
    canvas_settings: dict = dict(),
    disabled_action_ids: list = list(),
)

Manager for the full canvas element collection.

Owns the ordered list of all elements (Tiles, Comments, Sections) and provides spatial helpers used for safe element placement.

Example

canvas = CanvasElements.from_dict(defn.raw.get("gui")) for tile in canvas.tiles: ... print(tile.id, tile.x, tile.y) x, y = canvas.next_free_position() comment = CanvasComment.create("My note", x, y) canvas.add(comment) new_def["gui"] = canvas.to_dict()

abs_pos

abs_pos(el: CanvasElement) -> tuple[int, int]

Return absolute (x, y) for an element.

Tiles/comments/sections inside another section store their x, y RELATIVE to the parent section's top-left corner. Resolve here so callers always work in absolute canvas coordinates.

Source code in src/crew_dcs/classes/DomoDataflow/gui.py
441
442
443
444
445
446
447
448
449
450
451
452
def abs_pos(self, el: CanvasElement) -> tuple[int, int]:
    """Return absolute (x, y) for an element.

    Tiles/comments/sections inside another section store their x, y
    RELATIVE to the parent section's top-left corner.  Resolve here so
    callers always work in absolute canvas coordinates.
    """
    if el.parent_id:
        sec = self.get(el.parent_id)
        if sec:
            return el.x + sec.x, el.y + sec.y
    return el.x, el.y

add

add(element: CanvasElement) -> None

Append an element to the canvas.

Source code in src/crew_dcs/classes/DomoDataflow/gui.py
429
430
431
def add(self, element: CanvasElement) -> None:
    """Append an element to the canvas."""
    self.elements.append(element)

add_comment

add_comment(
    text: str,
    x: int,
    y: int,
    *,
    width: int = 320,
    height: int = 80,
    background_color: str = "var(--colorBackground1)",
    parent_id: str | None = None
) -> CanvasComment

Create a new canvas comment and add it to the canvas.

Parameters:

Name Type Description Default
text str

Comment text content.

required
x int

Horizontal position.

required
y int

Vertical position.

required
width int

Comment width in pixels.

320
height int

Comment height in pixels.

80
background_color str

CSS color or Domo variable for the comment background.

'var(--colorBackground1)'
parent_id str | None

Optional parent section ID for relative positioning.

None

Returns:

Type Description
CanvasComment

The newly created CanvasComment (already added to elements).

Example::

canvas.add_comment("This stream handles X", 32, 144, width=600, parent_id=sec.id)
Source code in src/crew_dcs/classes/DomoDataflow/gui.py
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
def add_comment(
    self,
    text: str,
    x: int,
    y: int,
    *,
    width: int = 320,
    height: int = 80,
    background_color: str = "var(--colorBackground1)",
    parent_id: str | None = None,
) -> CanvasComment:
    """Create a new canvas comment and add it to the canvas.

    Args:
        text: Comment text content.
        x: Horizontal position.
        y: Vertical position.
        width: Comment width in pixels.
        height: Comment height in pixels.
        background_color: CSS color or Domo variable for the comment background.
        parent_id: Optional parent section ID for relative positioning.

    Returns:
        The newly created CanvasComment (already added to elements).

    Example::

        canvas.add_comment("This stream handles X", 32, 144, width=600, parent_id=sec.id)
    """
    comment = CanvasComment.create(
        text=text,
        x=x,
        y=y,
        width=width,
        height=height,
        background_color=background_color,
        parent_id=parent_id,
    )
    self.add(comment)
    return comment

add_section

add_section(
    name: str,
    x: int,
    y: int,
    *,
    width: int = 480,
    height: int = 288,
    background_color: str = "var(--colorChartBlue6)"
) -> CanvasSection

Create a new canvas section and add it to the canvas.

Parameters:

Name Type Description Default
name str

Section label text.

required
x int

Horizontal position.

required
y int

Vertical position.

required
width int

Section width in pixels.

480
height int

Section height in pixels.

288
background_color str

CSS color or Domo variable for the section background.

'var(--colorChartBlue6)'

Returns:

Type Description
CanvasSection

The newly created CanvasSection (already added to elements).

Example::

sec = canvas.add_section("Stream A", x=32, y=160, width=800, height=192,
                         background_color="var(--colorChartRed6)")
Source code in src/crew_dcs/classes/DomoDataflow/gui.py
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
def add_section(
    self,
    name: str,
    x: int,
    y: int,
    *,
    width: int = 480,
    height: int = 288,
    background_color: str = "var(--colorChartBlue6)",
) -> CanvasSection:
    """Create a new canvas section and add it to the canvas.

    Args:
        name: Section label text.
        x: Horizontal position.
        y: Vertical position.
        width: Section width in pixels.
        height: Section height in pixels.
        background_color: CSS color or Domo variable for the section background.

    Returns:
        The newly created CanvasSection (already added to elements).

    Example::

        sec = canvas.add_section("Stream A", x=32, y=160, width=800, height=192,
                                 background_color="var(--colorChartRed6)")
    """
    section = CanvasSection.create(
        name=name,
        x=x,
        y=y,
        width=width,
        height=height,
        background_color=background_color,
    )
    self.add(section)
    return section

from_dict classmethod

from_dict(gui: dict | None) -> CanvasElements

Parse the full gui dict from the Domo API response.

Parameters:

Name Type Description Default
gui dict | None

The 'gui' key from the dataflow definition response. May be None for dataflows with no canvas data.

required
Source code in src/crew_dcs/classes/DomoDataflow/gui.py
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
@classmethod
def from_dict(cls, gui: dict | None) -> CanvasElements:
    """Parse the full gui dict from the Domo API response.

    Args:
        gui: The 'gui' key from the dataflow definition response.
             May be None for dataflows with no canvas data.
    """
    if not gui:
        return cls()

    default_canvas = gui.get("canvases", {}).get("default", {})
    raw_elements = default_canvas.get("elements") or []

    return cls(
        elements=[CanvasElement.from_dict(el) for el in raw_elements],
        canvas_settings=default_canvas.get("canvasSettings") or {},
        disabled_action_ids=default_canvas.get("disabledActions") or [],
    )

keep_only_tiles

keep_only_tiles(tile_ids: set[str] | list[str]) -> None

Strip canvas down to only the specified tile IDs.

Removes all sections, comments, and tiles whose IDs are not in tile_ids. Useful before a full canvas rebuild to ensure a clean slate.

Parameters:

Name Type Description Default
tile_ids set[str] | list[str]

Set or list of action IDs to retain as tiles.

required

Example::

canvas.keep_only_tiles({"LoadFromVault-abc", "SQL-def", "PublishToVault-ghi"})
Source code in src/crew_dcs/classes/DomoDataflow/gui.py
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
def keep_only_tiles(self, tile_ids: set[str] | list[str]) -> None:
    """Strip canvas down to only the specified tile IDs.

    Removes all sections, comments, and tiles whose IDs are not in
    *tile_ids*.  Useful before a full canvas rebuild to ensure a clean
    slate.

    Args:
        tile_ids: Set or list of action IDs to retain as tiles.

    Example::

        canvas.keep_only_tiles({"LoadFromVault-abc", "SQL-def", "PublishToVault-ghi"})
    """
    ids = set(tile_ids)
    self.elements = [
        e for e in self.elements if isinstance(e, CanvasTile) and e.id in ids
    ]

layout

layout() -> list[dict]

Return all elements as normalised dicts with ABSOLUTE coordinates.

Compatible with the original canvas_layout() output format so the SVG renderer and runbook scripts don't need changes.

Source code in src/crew_dcs/classes/DomoDataflow/gui.py
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
def layout(self) -> list[dict]:
    """Return all elements as normalised dicts with ABSOLUTE coordinates.

    Compatible with the original canvas_layout() output format so
    the SVG renderer and runbook scripts don't need changes.
    """
    result = []
    for el in self.elements:
        abs_x, abs_y = self.abs_pos(el)
        result.append(
            {
                "id": el.id,
                "type": el.element_type,
                "x": abs_x,
                "y": abs_y,
                "w": el.width,
                "h": el.height,
                "label": el.label,
                "parentId": el.parent_id,
            }
        )
    return result

next_free_position

next_free_position(
    width: int = 224, height: int = 96, padding: int = 32
) -> tuple[int, int]

Return (x, y) guaranteed not to overlap any existing element.

Uses absolute positions so section-relative child elements are correctly accounted for.

Source code in src/crew_dcs/classes/DomoDataflow/gui.py
477
478
479
480
481
482
483
484
485
486
487
488
def next_free_position(
    self, width: int = 224, height: int = 96, padding: int = 32
) -> tuple[int, int]:
    """Return (x, y) guaranteed not to overlap any existing element.

    Uses absolute positions so section-relative child elements are
    correctly accounted for.
    """
    if not self.elements:
        return (32, 32)
    max_bottom = max(self.abs_pos(el)[1] + el.height for el in self.elements)
    return (32, max_bottom + padding)

place_tile

place_tile(
    action_id: str,
    x: int,
    y: int,
    *,
    section: CanvasSection | None = None,
    actions: list | None = None
) -> CanvasTile

Create a canvas tile for an action and add it to the canvas.

If section is provided, x/y are treated as relative to the section's top-left corner and the tile's parent_id is set automatically.

If actions (a list of DomoDataflow_Action_Base) is provided, the corresponding action's raw["gui"]["x"] and raw["gui"]["y"] are synced to the tile's absolute position so the Domo API payload stays consistent.

Parameters:

Name Type Description Default
action_id str

The action ID to create a tile for.

required
x int

Horizontal position (relative to section if provided).

required
y int

Vertical position (relative to section if provided).

required
section CanvasSection | None

Optional parent section for relative positioning.

None
actions list | None

Optional action list for GUI coordinate sync.

None

Returns:

Type Description
CanvasTile

The newly created CanvasTile (already added to elements).

Example::

sec = canvas.add_section("Stream A", x=32, y=160, width=800, height=192)
canvas.place_tile("LoadFromVault-abc", 32, 64, section=sec, actions=defn.Actions.actions)
Source code in src/crew_dcs/classes/DomoDataflow/gui.py
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
def place_tile(
    self,
    action_id: str,
    x: int,
    y: int,
    *,
    section: CanvasSection | None = None,
    actions: list | None = None,
) -> CanvasTile:
    """Create a canvas tile for an action and add it to the canvas.

    If *section* is provided, x/y are treated as **relative** to the
    section's top-left corner and the tile's ``parent_id`` is set
    automatically.

    If *actions* (a list of DomoDataflow_Action_Base) is provided,
    the corresponding action's ``raw["gui"]["x"]`` and
    ``raw["gui"]["y"]`` are synced to the tile's **absolute** position
    so the Domo API payload stays consistent.

    Args:
        action_id: The action ID to create a tile for.
        x: Horizontal position (relative to section if provided).
        y: Vertical position (relative to section if provided).
        section: Optional parent section for relative positioning.
        actions: Optional action list for GUI coordinate sync.

    Returns:
        The newly created CanvasTile (already added to elements).

    Example::

        sec = canvas.add_section("Stream A", x=32, y=160, width=800, height=192)
        canvas.place_tile("LoadFromVault-abc", 32, 64, section=sec, actions=defn.Actions.actions)
    """
    parent_id = section.id if section else None
    tile = CanvasTile.from_action(
        action_id=action_id, x=x, y=y, parent_id=parent_id
    )
    self.add(tile)

    # Sync action GUI coords to absolute position
    if actions is not None:
        abs_x, abs_y = self.abs_pos(tile)
        for action in actions:
            if action.id == action_id and action.raw and action.raw.get("gui"):
                action.raw["gui"]["x"] = abs_x
                action.raw["gui"]["y"] = abs_y
                break

    return tile

remove

remove(element_id: str) -> bool

Remove element by id. Returns True if found and removed.

Source code in src/crew_dcs/classes/DomoDataflow/gui.py
433
434
435
436
437
def remove(self, element_id: str) -> bool:
    """Remove element by id. Returns True if found and removed."""
    before = len(self.elements)
    self.elements = [e for e in self.elements if e.id != element_id]
    return len(self.elements) < before

remove_elements_where

remove_elements_where(predicate) -> list[CanvasElement]

Remove all canvas elements matching a predicate.

Parameters:

Name Type Description Default
predicate

Callable accepting a CanvasElement and returning True for elements to remove.

required

Returns:

Type Description
list[CanvasElement]

List of removed elements.

Example::

removed = canvas.remove_elements_where(
    lambda e: isinstance(e, CanvasComment) and "Claude" in e.plain_text
)
Source code in src/crew_dcs/classes/DomoDataflow/gui.py
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
def remove_elements_where(self, predicate) -> list[CanvasElement]:
    """Remove all canvas elements matching a predicate.

    Args:
        predicate: Callable accepting a CanvasElement and returning
            True for elements to remove.

    Returns:
        List of removed elements.

    Example::

        removed = canvas.remove_elements_where(
            lambda e: isinstance(e, CanvasComment) and "Claude" in e.plain_text
        )
    """
    keep, removed = [], []
    for el in self.elements:
        (removed if predicate(el) else keep).append(el)
    self.elements = keep
    return removed

to_dict

to_dict() -> dict

Serialize back to the API gui dict format.

Source code in src/crew_dcs/classes/DomoDataflow/gui.py
396
397
398
399
400
401
402
403
404
405
406
407
408
def to_dict(self) -> dict:
    """Serialize back to the API gui dict format."""
    return {
        "version": "1.0",
        "canvases": {
            "default": {
                "canvasSettings": self.canvas_settings,
                "elements": [el.to_dict() for el in self.elements],
                "disabledActions": self.disabled_action_ids,
            }
        },
        "useGraphUI": True,
    }

CanvasSection dataclass

CanvasSection(
    id: str,
    element_type: str,
    x: int = 0,
    y: int = 0,
    width: int = TILE_W,
    height: int = TILE_H,
    parent_id: str | None = None,
    raw: dict = None,
    name: str = "",
    background_color: str = "var(--colorChartBlue6)",
)

Bases: CanvasElement

Named group box that tiles can be placed inside.

create classmethod

create(
    name: str,
    x: int,
    y: int,
    *,
    width: int = 480,
    height: int = 288,
    background_color: str = "var(--colorChartBlue6)"
) -> CanvasSection

Create a new section.

Source code in src/crew_dcs/classes/DomoDataflow/gui.py
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
@classmethod
def create(
    cls,
    name: str,
    x: int,
    y: int,
    *,
    width: int = 480,
    height: int = 288,
    background_color: str = "var(--colorChartBlue6)",
) -> CanvasSection:
    """Create a new section."""
    return cls(
        id=str(uuid.uuid4()),
        x=x,
        y=y,
        width=width,
        height=height,
        name=name,
        background_color=background_color,
        raw=None,
    )

CanvasTile dataclass

CanvasTile(
    id: str,
    element_type: str,
    x: int = 0,
    y: int = 0,
    width: int = TILE_W,
    height: int = TILE_H,
    parent_id: str | None = None,
    raw: dict = None,
    color: str | None = None,
    color_source: str | None = None,
)

Bases: CanvasElement

Positioned action node on the Magic ETL canvas.

The id matches the action id in the definition actions list: "{ActionType}-{uuid}"

action_type property

action_type: str

Extract action type from id prefix (e.g. 'LoadFromVault').

from_action classmethod

from_action(
    action_id: str,
    x: int,
    y: int,
    *,
    color: str | None = None,
    color_source: str | None = None,
    parent_id: str | None = None
) -> CanvasTile

Create a new canvas tile from an action id + position.

Source code in src/crew_dcs/classes/DomoDataflow/gui.py
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
@classmethod
def from_action(
    cls,
    action_id: str,
    x: int,
    y: int,
    *,
    color: str | None = None,
    color_source: str | None = None,
    parent_id: str | None = None,
) -> CanvasTile:
    """Create a new canvas tile from an action id + position."""
    return cls(
        id=action_id,
        x=x,
        y=y,
        color=color,
        color_source=color_source,
        parent_id=parent_id,
        raw=None,
    )

canvas_element_from_dict

canvas_element_from_dict(obj: dict) -> CanvasElement

Dispatch a raw element dict to the appropriate typed class.

Source code in src/crew_dcs/classes/DomoDataflow/gui.py
343
344
345
346
def canvas_element_from_dict(obj: dict) -> CanvasElement:
    """Dispatch a raw element dict to the appropriate typed class."""
    cls = _ELEMENT_REGISTRY.get(obj.get("type", ""), CanvasElement)
    return cls.from_dict(obj)