From be1b790ff56abe3451e0ddb766d35928d45c046e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brei=20Soli=C3=B1o?= Date: Mon, 9 Oct 2023 13:38:16 +0200 Subject: [PATCH] Dynamic HTML output for monitoring (#2062) Co-authored-by: Klaus Zimmermann --- esmvalcore/experimental/recipe_output.py | 41 +++++- .../experimental/templates/RecipeOutput.j2 | 70 +++++++++- .../experimental/templates/TaskOutput.j2 | 49 ++++--- esmvalcore/experimental/templates/head.j2 | 3 + .../templates/recipe_output_page.j2 | 32 +++-- esmvalcore/experimental/templates/scripts.js | 122 ++++++++++++++++++ tests/unit/experimental/test_recipe_output.py | 52 ++++++++ 7 files changed, 339 insertions(+), 30 deletions(-) create mode 100644 esmvalcore/experimental/templates/scripts.js diff --git a/esmvalcore/experimental/recipe_output.py b/esmvalcore/experimental/recipe_output.py index 69f63765a2..9765d15c77 100644 --- a/esmvalcore/experimental/recipe_output.py +++ b/esmvalcore/experimental/recipe_output.py @@ -2,7 +2,7 @@ import base64 import logging import os.path -from collections.abc import Mapping +from collections.abc import Mapping, Sequence from pathlib import Path from typing import Optional, Tuple, Type @@ -123,6 +123,13 @@ class RecipeOutput(Mapping): The session used to run the recipe. """ + FILTER_ATTRS: list = [ + "realms", + "plot_type", # Used by several diagnostics + "plot_types", + "long_names", + ] + def __init__(self, task_output: dict, session=None, info=None): self._raw_task_output = task_output self._task_output = {} @@ -141,6 +148,7 @@ def __init__(self, task_output: dict, session=None, info=None): diagnostics[name].append(task) # Create diagnostic output + filters: dict = {} for name, tasks in diagnostics.items(): diagnostic_info = info.data['diagnostics'][name] self.diagnostics[name] = DiagnosticOutput( @@ -150,6 +158,36 @@ def __init__(self, task_output: dict, session=None, info=None): description=diagnostic_info.get('description'), ) + # Add data to filters + for task in tasks: + for file in task.files: + RecipeOutput._add_to_filters(filters, file.attributes) + + # Sort at the end because sets are unordered + self.filters = RecipeOutput._sort_filters(filters) + + @classmethod + def _add_to_filters(cls, filters, attributes): + """Add valid values to the HTML output filters.""" + for attr in RecipeOutput.FILTER_ATTRS: + if attr not in attributes: + continue + values = attributes[attr] + # `set()` to avoid duplicates + attr_list = filters.get(attr, set()) + if (isinstance(values, str) or not isinstance(values, Sequence)): + attr_list.add(values) + else: + attr_list.update(values) + filters[attr] = attr_list + + @classmethod + def _sort_filters(cls, filters): + """Sort the HTML output filters.""" + for _filter, _attrs in filters.items(): + filters[_filter] = sorted(_attrs) + return filters + def __repr__(self): """Return canonical string representation.""" string = '\n'.join(repr(item) for item in self._task_output.values()) @@ -218,6 +256,7 @@ def render(self, template=None): diagnostics=self.diagnostics.values(), session=self.session, info=self.info, + filters=self.filters, relpath=os.path.relpath, ) diff --git a/esmvalcore/experimental/templates/RecipeOutput.j2 b/esmvalcore/experimental/templates/RecipeOutput.j2 index c502c39a33..cde84362f9 100644 --- a/esmvalcore/experimental/templates/RecipeOutput.j2 +++ b/esmvalcore/experimental/templates/RecipeOutput.j2 @@ -1,12 +1,72 @@ + + + + +
{% for diagnostic in diagnostics %} -

{{ diagnostic.title }}

-

{{ diagnostic.description }}

+
+

{{ diagnostic.title }}

+

{{ diagnostic.description }}

- {% for task in diagnostic.task_output %} + {% set diagnostic_loop = loop %} + {% for task in diagnostic.task_output %} - {% include 'TaskOutput.j2' %} + {% include 'TaskOutput.j2' %} - {% endfor %} + {% endfor %} +
{% endfor %} +
diff --git a/esmvalcore/experimental/templates/TaskOutput.j2 b/esmvalcore/experimental/templates/TaskOutput.j2 index 48d259ea7c..43753418b2 100644 --- a/esmvalcore/experimental/templates/TaskOutput.j2 +++ b/esmvalcore/experimental/templates/TaskOutput.j2 @@ -2,11 +2,23 @@ {% for file in task.image_files %} -
+
+
- {{ file.caption }} + {{ file.caption }} -
+
{{ file.caption }}

@@ -16,21 +28,28 @@ provenance
+
{% endfor %} -

Data files

+{% if task.data_files|length > 0 %} +

Data files

- + + +{% endif %} diff --git a/esmvalcore/experimental/templates/head.j2 b/esmvalcore/experimental/templates/head.j2 index e6ec19d5ee..0306620f8a 100644 --- a/esmvalcore/experimental/templates/head.j2 +++ b/esmvalcore/experimental/templates/head.j2 @@ -2,6 +2,9 @@ {{ title }} + + +