Skip to content

Commit

Permalink
Merge pull request #6452 from cylc/8.3.x-sync
Browse files Browse the repository at this point in the history
🤖 Merge 8.3.x-sync into master
  • Loading branch information
oliver-sanders authored Oct 25, 2024
2 parents 21adf59 + 9f93995 commit cbfddb4
Show file tree
Hide file tree
Showing 10 changed files with 362 additions and 56 deletions.
1 change: 1 addition & 0 deletions changes.d/6388.fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix task state filtering in Tui.
9 changes: 7 additions & 2 deletions cylc/flow/tui/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
meanElapsedTime
}
}
familyProxies(exids: ["*/root"], states: $taskStates) {
familyProxies(exids: ["*/root"]) {
id
name
cyclePoint
Expand All @@ -78,13 +78,18 @@
name
}
}
cyclePoints: familyProxies(ids: ["*/root"], states: $taskStates) {
cyclePoints: familyProxies(ids: ["*/root"]) {
id
name
cyclePoint
state
isHeld
isQueued
isRunahead
firstParent {
id
name
}
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion cylc/flow/tui/updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,10 @@ async def _run_update(self, data):
)
)

return compute_tree(data)
# are any task state filters active?
task_filters_active = not all(self.filters['tasks'].values())

return compute_tree(data, prune_families=task_filters_active)

async def _update_workflow(self, w_id, client, data):
if not client:
Expand Down
122 changes: 109 additions & 13 deletions cylc/flow/tui/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from itertools import zip_longest
import re
from time import time
from typing import Tuple
from typing import Any, Dict, Optional, Set, Tuple

import urwid

Expand All @@ -44,6 +44,10 @@
ME = getuser()


Node = Dict[str, Any]
NodeStore = Dict[str, Dict[str, Node]]


@contextmanager
def suppress_logging():
"""Suppress Cylc logging.
Expand Down Expand Up @@ -139,12 +143,21 @@ def idpop(id_):
return tokens.id


def compute_tree(data):
"""Digest GraphQL data to produce a tree."""
root_node = add_node('root', 'root', {}, data={})
def compute_tree(data: dict, prune_families: bool = False) -> Node:
"""Digest GraphQL data to produce a tree.
Args:
data:
The workflow data as returned from the GraphQL query.
prune_families:
If True any empty families will be removed from the tree.
Turn this on if task state filters are active.
"""
root_node: Node = add_node('root', 'root', create_node_store(), data={})

for flow in data['workflows']:
nodes = {}
nodes: NodeStore = create_node_store() # nodes for this workflow
flow_node = add_node(
'workflow', flow['id'], nodes, data=flow)
root_node['children'].append(flow_node)
Expand Down Expand Up @@ -199,9 +212,13 @@ def compute_tree(data):
job_node['children'] = [job_info_node]
task_node['children'].append(job_node)

# trim empty families / cycles (cycles are just "root" families)
if prune_families:
_prune_empty_families(nodes)

# sort
for (type_, _), node in nodes.items():
if type_ != 'task':
for type_ in ('workflow', 'cycle', 'family', 'job'):
for node in nodes[type_].values():
# NOTE: jobs are sorted by submit-num in the GraphQL query
node['children'].sort(
key=lambda x: NaturalSort(x['id_'])
Expand All @@ -225,6 +242,62 @@ def compute_tree(data):
return root_node


def _prune_empty_families(nodes: NodeStore) -> None:
"""Prune empty families from the tree.
Note, cycles are "root" families.
We can end up with empty families when filtering by task state. We filter
tasks by state in the GraphQL query (so we don't need to perform this
filtering client-side), however, we cannot filter families by state because
the family state is an aggregate representing a collection of tasks (and or
families).
Args:
nodes: Dictionary containing all nodes present in the tree.
"""
# go through all families and cycles
stack: Set[str] = {*nodes['family'], *nodes['cycle']}
while stack:
family_id = stack.pop()

if family_id in nodes['family']:
# this is a family
family = nodes['family'][family_id]
if len(family['children']) > 0:
continue

# this family is empty -> find its parent (family/cycle)
_first_parent = family['data']['firstParent']
if _first_parent['name'] == 'root':
parent_type = 'cycle'
parent_id = idpop(_first_parent['id'])
else:
parent_type = 'family'
parent_id = _first_parent['id']

elif family_id in nodes['cycle']:
# this is a cycle
family = nodes['cycle'][family_id]
if len(family['children']) > 0:
continue

# this cycle is empty -> find its parent (workflow)
parent_type = 'workflow'
parent_id = idpop(family_id)

else:
# this node has already been pruned
continue

# remove the node from its parent
nodes[parent_type][parent_id]['children'].remove(family)
if parent_type in {'family', 'cycle'}:
# recurse up the family tree
stack.add(parent_id)


class NaturalSort:
"""An object to use as a sort key for sorting strings as a human would.
Expand Down Expand Up @@ -309,16 +382,39 @@ def __lt__(self, other):
return False


def dummy_flow(data):
def dummy_flow(data) -> Node:
return add_node(
'workflow',
data['id'],
{},
create_node_store(),
data
)


def add_node(type_, id_, nodes, data=None):
def create_node_store() -> NodeStore:
"""Returns a "node store" dictionary for use with add_nodes."""
return { # node_type: {node_id: node}
# the root of the tree
'root': {},
# spring nodes load the workflow when visited
'#spring': {},
# workflow//cycle/<task/family>/job
'workflow': {},
'cycle': {},
'family': {},
'task': {},
'job': {},
# the node under a job that contains metadata (platform, job_id, etc)
'job_info': {},
}


def add_node(
type_: str,
id_: str,
nodes: NodeStore,
data: Optional[dict] = None,
) -> Node:
"""Create a node add it to the store and return it.
Arguments:
Expand All @@ -336,14 +432,14 @@ def add_node(type_, id_, nodes, data=None):
dict - The requested node.
"""
if (type_, id_) not in nodes:
nodes[(type_, id_)] = {
if id_ not in nodes[type_]:
nodes[type_][id_] = {
'children': [],
'id_': id_,
'data': data or {},
'type_': type_
}
return nodes[(type_, id_)]
return nodes[type_][id_]


def get_job_icon(status):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<pre><span style="color:#ff0000;background:#e5e5e5;font-weight:bold">C</span><span style="color:#ffff00;background:#e5e5e5;font-weight:bold">y</span><span style="color:#00ff00;background:#e5e5e5;font-weight:bold">l</span><span style="color:#5c5cff;background:#e5e5e5;font-weight:bold">c</span><span style="color:#000000;background:#e5e5e5;font-weight:bold"> Tui</span><span style="color:#7f7f7f;background:#e5e5e5"> tasks filtered (</span><span style="color:#7f7f7f;background:#e5e5e5;font-weight:bold">F</span><span style="color:#7f7f7f;background:#e5e5e5"> - edit, </span><span style="color:#7f7f7f;background:#e5e5e5;font-weight:bold">R</span><span style="color:#7f7f7f;background:#e5e5e5"> - reset) workflows filtered (</span><span style="color:#7f7f7f;background:#e5e5e5;font-weight:bold">W</span><span style="color:#7f7f7f;background:#e5e5e5"> - edit,</span><span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#7f7f7f;background:#e5e5e5;font-weight:bold">E</span><span style="color:#7f7f7f;background:#e5e5e5"> - reset)</span><span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#000000;background:#e5e5e5"></span><span style="color:#e5e5e5;background:#000000">-</span><span style="color:#000000;background:#e5e5e5"></span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">~cylc </span>
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">-</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5;font-weight:bold">test_task_states</span><span style="color:#000000;background:#e5e5e5"> - </span><span style="color:#cdcd00;background:#e5e5e5">paused</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#00cdcd;background:#e5e5e5">1■</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#5c5cff;background:#e5e5e5">1■</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#00cd00;background:#e5e5e5">1■</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#ff0000;background:#e5e5e5">1■</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#ff00ff;background:#e5e5e5">1■</span><span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">-</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">̿⊗ 1 </span>
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">-</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">̿● X </span>
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">̿● a </span>
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">-</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">̿⊗ Y </span>
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">̿⊗ b </span>
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">-</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">̿⊘ 2 </span>
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">-</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">̿⊙ X </span>
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">̿⊙ a </span>
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">-</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">̿⊘ Y </span>
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">-</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">̿⊘ Y1 </span>
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">̿⊘ c </span>
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">̿⊙ b </span>
<span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#ffffff;background:#0000ee">quit: </span><span style="color:#00ffff;background:#0000ee">q</span><span style="color:#ffffff;background:#0000ee"> help: </span><span style="color:#00ffff;background:#0000ee">h</span><span style="color:#ffffff;background:#0000ee"> context: </span><span style="color:#00ffff;background:#0000ee">enter</span><span style="color:#ffffff;background:#0000ee"> tree: </span><span style="color:#00ffff;background:#0000ee">- ← + → </span><span style="color:#ffffff;background:#0000ee"> navigation: </span><span style="color:#00ffff;background:#0000ee">↑ ↓ ↥ ↧ Home End </span><span style="color:#ffffff;background:#0000ee"> </span>
<span style="color:#ffffff;background:#0000ee">filter tasks: </span><span style="color:#00ffff;background:#0000ee">T f s r R </span><span style="color:#ffffff;background:#0000ee"> filter workflows: </span><span style="color:#00ffff;background:#0000ee">W E p </span><span style="color:#ffffff;background:#0000ee"> </span>
</pre>
Loading

0 comments on commit cbfddb4

Please sign in to comment.