diff --git a/lib/cylc/gui/gscan.py b/lib/cylc/gui/gscan.py
index 6b12b0b5545..41aac468ae5 100644
--- a/lib/cylc/gui/gscan.py
+++ b/lib/cylc/gui/gscan.py
@@ -31,11 +31,13 @@
from cylc.cfgspec.globalcfg import GLOBAL_CFG
from cylc.cfgspec.gcylc import gcfg
+from cylc.exceptions import PortFileError
import cylc.flags
from cylc.gui.legend import ThemeLegendWindow
from cylc.gui.dot_maker import DotMaker
from cylc.gui.util import get_icon, setup_icons, set_exception_hook_dialog
from cylc.network.port_scan import scan_all
+from cylc.network.suite_state import StateSummaryClient
from cylc.owner import USER
from cylc.version import CYLC_VERSION
from cylc.task_state import TASK_STATUSES_ORDERED, TASK_STATUS_RUNAHEAD
@@ -646,6 +648,29 @@ def _on_query_tooltip(self, widget, x, y, kbd_ctx, tooltip):
if column.get_title() != "Status":
tooltip.set_text(None)
return False
+
+ # If hovering over a status indicator set tooltip to show most recent
+ # tasks.
+ dot_offset, dot_width = tuple(column.cell_get_position(
+ column.get_cell_renderers()[1]))
+ cell_index = (cell_x - dot_offset) // dot_width
+ if cell_index >= 0:
+ # NOTE: TreeViewColumn.get_cell_renderers() does not allways return
+ # cell renderers for the correct row.
+ info = re.findall(r'\D+\d+', model.get(iter_, 6)[0])
+ if cell_index >= len(info):
+ return False
+ state = info[cell_index].strip().split(' ')[0]
+ point_string = model.get(iter_, 5)[0]
+ tasks = self.updater.get_last_n_tasks(
+ suite, host, state, point_string, 5) # Last 5 tasks.
+ tooltip.set_markup('{state}\n{tasks}'.format(
+ state=state,
+ tasks='\n'.join(tasks))
+ )
+ return True
+
+ # Set the tooltip to a generic status for this suite.
state_texts = []
status_column_info = 6
state_text = model.get_value(iter_, status_column_info)
@@ -673,7 +698,7 @@ def _set_cell_pixbuf_state(self, column, cell, model, iter_, index_tuple):
is_stopped = model.get_value(iter_, 2)
info = re.findall(r'\D+\d+', state_info)
if index < len(info):
- state = info[index].rsplit(" ", 1)[0]
+ state = info[index].rsplit(" ", 1)[0].strip()
icon = self.dots.get_icon(state.strip(), is_stopped=is_stopped)
cell.set_property("visible", True)
else:
@@ -918,10 +943,14 @@ class ScanAppUpdater(BaseScanUpdater):
"""Update the scan app."""
+ TIME_STRINGS = ['submitted_time_string', 'started_time_string',
+ 'finished_time_string']
+
def __init__(self, hosts, suite_treemodel, suite_treeview, owner=None,
poll_interval=None):
self.suite_treemodel = suite_treemodel
self.suite_treeview = suite_treeview
+ self.active_tasks = {}
super(ScanAppUpdater, self).__init__(hosts, owner=owner,
poll_interval=poll_interval)
@@ -955,6 +984,46 @@ def clear_stopped_suites(self):
self.stopped_hosts_suites_info.clear()
gobject.idle_add(self.update)
+ def get_last_n_tasks(self, suite, host, task_state, point_string, n):
+ """Returns a list of the last 'n' tasks with the provided state for
+ the provided suite."""
+ # TODO: safety check
+
+ # Get task state summary information.
+ if suite + host not in self.active_tasks:
+ return ['Could not get info, try refreshing the scanner.']
+ tasks = self.active_tasks[suite + host]
+ if tasks is False:
+ return ['Cannot connect to suite.']
+
+ # If point_string specified, remove entries at other point_strings.
+ to_remove = []
+ if point_string:
+ for task in tasks:
+ _, task_point_string = task.rsplit('.', 1)
+ if task_point_string != point_string:
+ to_remove.append(task)
+ for task in to_remove:
+ del tasks[task]
+
+ # Get list of tasks that match the provided state.
+ ret = []
+ for task in tasks:
+ if tasks[task]['state'] == task_state:
+ time_strings = ['1970-01-01T00:00:00Z']
+ for time_string in self.TIME_STRINGS:
+ if time_string in tasks[task] and tasks[task][time_string]:
+ time_strings.append(tasks[task][time_string])
+ ret.append((max(time_strings), task))
+
+ # return only the n youngest items
+ ret.sort(reverse=True)
+ if len(ret) - n == 1:
+ n += 1
+ suffix = [] if len(ret) <= n else [
+ 'And %d more' % (len(ret) - n,)]
+ return [task[1] for task in ret[0:n]] + suffix
+
def update(self):
"""Update the Applet."""
row_ids = self._get_user_expanded_row_ids()
@@ -969,7 +1038,15 @@ def update(self):
if (suite, host) not in suite_host_tuples:
suite_host_tuples.append((suite, host))
suite_host_tuples.sort()
+ self.active_tasks = {}
for suite, host in suite_host_tuples:
+
+ try:
+ self.active_tasks[suite + host] = StateSummaryClient(
+ suite, host=host).get_suite_state_summary()[1]
+ except PortFileError:
+ self.active_tasks[suite + host] = False
+
if suite in info.get(host, {}):
suite_info = info[host][suite]
is_stopped = False