Skip to content

Commit

Permalink
feat: Add ability do distinguish the explicit block on the graph
Browse files Browse the repository at this point in the history
  • Loading branch information
haidaraM committed Sep 26, 2021
1 parent 9a0fdec commit c6fdd30
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 83 deletions.
11 changes: 7 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
- feat: Curved edge label based on the path [\#84](https://github.com/haidaraM/ansible-playbook-grapher/pull/84)
- feat: Add option to automatically view the generated
file [\#88](https://github.com/haidaraM/ansible-playbook-grapher/pull/88)
- feat: Add support for block [\#86](https://github.com/haidaraM/ansible-playbook-grapher/pull/86)
- fix:
- front: Refactor the JS part and fix issue when selecting/unselecting nodes
- front: Do not unhighlight the current selected node when hovering on parent node
- cli(typo): rename `--ouput-file-name` to `--output-file-name`
- fix: Use the correct tooltip for edges
- front: Refactor the JS part and fix issue when selecting/unselecting nodes
- front: Do not unhighlight the current selected node when hovering on parent node
- cli(typo): rename `--ouput-file-name` to `--output-file-name`
- Use the correct tooltip for edges
- style: Do not use bold style by default and apply color on nodes border
- Merge when condition with `and`
- test:
- Add Ansible 2.10.7 in the test matrix
- Make test verbose by default with `-vv` in the args
Expand Down
5 changes: 5 additions & 0 deletions ansibleplaybookgrapher/data/highlight-hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ function clickOnElement(event) {
$("#svg").ready(function () {
let plays = $("g[id^=play_]");
let roles = $("g[id^=role_]");
let blocks = $("g[id^=block_]");

// Set hover and click events on the plays
plays.hover(hoverMouseEnter, hoverMouseLeave);
Expand All @@ -109,4 +110,8 @@ $("#svg").ready(function () {
roles.hover(hoverMouseEnter, hoverMouseLeave);
roles.click(clickOnElement);

// Set hover and click events on the blocks
blocks.hover(hoverMouseEnter, hoverMouseLeave);
blocks.click(clickOnElement);

});
38 changes: 29 additions & 9 deletions ansibleplaybookgrapher/graph.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from abc import ABC
from collections import defaultdict
from typing import Dict, List

from ansibleplaybookgrapher.utils import generate_id


class Node(ABC):
class Node:
"""
A node in the graph. Everything of the final graph is a node: playbook, plays, edges, tasks and roles.
"""
Expand All @@ -29,13 +28,14 @@ class CompositeNode(Node):
A node that composed of multiple of nodes.
"""

def __init__(self, node_name: str, node_id: str):
def __init__(self, node_name: str, node_id: str, supported_compositions: List[str] = None):
"""
:param node_name:
:param node_id:
"""
super().__init__(node_name, node_id)
self._supported_compositions = supported_compositions or []
# The dict will contain the different types of composition.
self._compositions = defaultdict(list) # type: Dict[str, List]

Expand All @@ -54,6 +54,9 @@ def add_node(self, target_composition: str, node: Node):
:param node: The node to add in the given composition
:return:
"""
if target_composition not in self._supported_compositions:
raise Exception(
f"The target composition '{target_composition}' is unknown. Supported are: {self._supported_compositions}")
self._compositions[target_composition].append(node)

def links_structure(self) -> Dict[Node, List[Node]]:
Expand Down Expand Up @@ -84,7 +87,7 @@ class PlaybookNode(CompositeNode):
"""

def __init__(self, node_name: str, plays: List['PlayNode'] = None, node_id: str = None):
super().__init__(node_name, node_id or generate_id("playbook_"))
super().__init__(node_name, node_id or generate_id("playbook_"), ["plays"])
self._compositions['plays'] = plays or []

@property
Expand Down Expand Up @@ -122,7 +125,7 @@ def __init__(self, node_name: str, hosts: List[str] = None, node_id: str = None)
:param node_id:
:param hosts: List of hosts attached to the play
"""
super().__init__(node_name, node_id or generate_id("play_"))
super().__init__(node_name, node_id or generate_id("play_"), ["pre_tasks", "roles", "tasks", "post_tasks"])
self.hosts = hosts or []

@property
Expand All @@ -142,6 +145,23 @@ def tasks(self) -> List['EdgeNode']:
return self._compositions["tasks"]


class BlockNode(CompositeNode):
"""
A block node: https://docs.ansible.com/ansible/latest/user_guide/playbooks_blocks.html
"""

def __init__(self, node_name: str, node_id: str = None):
super().__init__(node_name, node_id or generate_id("block_"), ["tasks"])

@property
def tasks(self) -> List['EdgeNode']:
"""
The tasks attached to this block
:return:
"""
return self._compositions['tasks']


class EdgeNode(CompositeNode):
"""
An edge between two nodes. It's a special case of composite node with only one composition with one element
Expand All @@ -155,9 +175,9 @@ def __init__(self, source: Node, destination: Node, node_name: str = "", node_id
:param destination: The edge destination node
:param node_id: The edge id
"""
super().__init__(node_name, node_id or generate_id("edge_"))
super().__init__(node_name, node_id or generate_id("edge_"), ["destination"])
self.source = source
self.add_node("nodes", destination)
self.add_node("destination", destination)

def add_node(self, target_composition: str, node: Node):
"""
Expand All @@ -177,7 +197,7 @@ def destination(self) -> Node:
Return the destination of the edge
:return:
"""
return self._compositions["nodes"][0]
return self._compositions["destination"][0]


class TaskNode(Node):
Expand All @@ -195,7 +215,7 @@ class RoleNode(CompositeNode):
"""

def __init__(self, node_name: str, node_id: str = None):
super().__init__(node_name, node_id or generate_id("role_"))
super().__init__(node_name, node_id or generate_id("role_"), ["tasks"])

@property
def tasks(self):
Expand Down
29 changes: 18 additions & 11 deletions ansibleplaybookgrapher/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
from ansible.template import Templar
from ansible.utils.display import Display

from ansibleplaybookgrapher.graph import EdgeNode, TaskNode, PlaybookNode, RoleNode, PlayNode, CompositeNode
from ansibleplaybookgrapher.utils import clean_name, handle_include_path, has_role_parent, generate_id
from ansibleplaybookgrapher.graph import EdgeNode, TaskNode, PlaybookNode, RoleNode, PlayNode, CompositeNode, BlockNode
from ansibleplaybookgrapher.utils import clean_name, handle_include_path, has_role_parent, generate_id, \
convert_when_to_str


class BaseParser(ABC):
Expand Down Expand Up @@ -73,12 +74,8 @@ def _add_task(self, task: Task, task_vars: Dict, node_type: str, parent_node: Co

self.display.vv(f"Adding {node_type} '{task.get_name()}' to the graph")

edge_label = ""
if len(task.when) > 0:
when = "".join(map(str, task.when))
edge_label += " [when: " + when + "]"

task_name = clean_name(f"[{node_type}] " + self.template(task.get_name(), task_vars))
edge_label = convert_when_to_str(task.when)

edge_node = EdgeNode(parent_node, TaskNode(task_name, generate_id(f"{node_type}_")), edge_label)
parent_node.add_node(target_composition=f"{node_type}s", node=edge_node)
Expand Down Expand Up @@ -214,6 +211,13 @@ def _include_tasks_in_blocks(self, current_play: Play, parent_nodes: List[Compos
:return:
"""

if not block._implicit and block._role is None:
# Here we have an explicit block. Ansible internally converts all normal tasks to Block
block_node = BlockNode(str(block.name))
parent_nodes[-1].add_node(f"{node_type}s",
EdgeNode(parent_nodes[-1], block_node, convert_when_to_str(block.when)))
parent_nodes.append(block_node)

# loop through the tasks
for task_or_block in block.block:
if isinstance(task_or_block, Block):
Expand All @@ -233,7 +237,7 @@ def _include_tasks_in_blocks(self, current_play: Play, parent_nodes: List[Compos

role_node = RoleNode(task_or_block.args['name'])
# TODO: add support for conditional (when) for include_role in the edge label
parent_nodes[-1].add_node("roles", EdgeNode(parent_nodes[-1], role_node))
parent_nodes[-1].add_node(f"{node_type}s", EdgeNode(parent_nodes[-1], role_node))

if self.include_role_tasks:
# If we have an include_role and we want to include role tasks, the parent node now becomes
Expand Down Expand Up @@ -274,9 +278,12 @@ def _include_tasks_in_blocks(self, current_play: Play, parent_nodes: List[Compos
self._include_tasks_in_blocks(current_play=current_play, parent_nodes=parent_nodes, block=b,
play_vars=task_vars, node_type=node_type)
else:
if len(parent_nodes) > 1 and not has_role_parent(task_or_block):
# We add a new parent node only if we found an include_role. If an include_role is not found, and we
# have a task that is not from an include_role, we remove the last RoleNode we have added.
if len(parent_nodes) > 1 and not has_role_parent(task_or_block) and task_or_block._parent._implicit:
# We add a new parent node if:
# - We found an include_role
# - We found an explicit Block
# If an include_role is not found and we have a task that is not from an include_role and not from
# an explicit block => we remove the last CompositeNode we have added.
parent_nodes.pop()

# check if this task comes from a role, and we don't want to include tasks of the role
Expand Down
Loading

0 comments on commit c6fdd30

Please sign in to comment.