Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic HTML output for monitoring #2062

Merged
merged 57 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
b429a30
Added tabs for diagnostics
bsolino May 24, 2023
218f0ee
Hide data files by default and button to toggle
bsolino May 24, 2023
7e32462
Added filters to RecipeOutput, to allow filtering on javascript
bsolino May 31, 2023
62067e0
Added javascript and divs for filtering
bsolino May 31, 2023
8b0fac0
Merge branch 'main' into html_monitoring
bsolino May 31, 2023
c0f34ec
Autoformatting
bsolino May 31, 2023
883127e
Merge branch 'html_monitoring' of github.com:ESMValGroup/ESMValCore i…
bsolino May 31, 2023
4ea2413
Refactored renaming of built-in function
bsolino May 31, 2023
6c0a495
Added support for non lists, corrected codacy issue
bsolino Jun 13, 2023
8f97523
Added support for non lists attributes
bsolino Jun 13, 2023
3186fef
Merge branch 'main' into html_monitoring
bsolino Jun 14, 2023
8bc3f45
Solved bug
bsolino Jun 14, 2023
51320c9
Added slightly different plot type key from monitor_base.py
bsolino Jun 14, 2023
c63cc3a
Moved style, inline figures, hide empty diagnostics
bsolino Jun 15, 2023
1ba401d
Moved scripts to own file, refactored
bsolino Jun 15, 2023
fe4c3f4
Modified comment
bsolino Jun 16, 2023
f8eba2d
Renamed div, title formatting for filters
bsolino Jun 16, 2023
657a950
Moved show data files button to same line as title
bsolino Jun 20, 2023
32bc619
Imports to jQuery, Bootstrap 5; removed custom CSS
bsolino Aug 21, 2023
5042049
Corrected body tag, added scripts at the end
bsolino Aug 21, 2023
f24993d
Ported Toggle data files to Bootstrap
bsolino Aug 21, 2023
83c70e6
Ported Tabs to Bootstrap, started porting Filter
bsolino Aug 21, 2023
126df63
Merge branch 'main' into html_monitoring
bsolino Aug 21, 2023
7036257
Automatically corrected linting issues
bsolino Aug 21, 2023
b28a034
Final attribute list, refactored for unit testing
bsolino Aug 24, 2023
c329715
Added unit testing
bsolino Aug 24, 2023
505f6bd
Changed checkboxes to Bootstrap
bsolino Aug 24, 2023
215766b
Added filter name to filter class
bsolino Aug 24, 2023
39d9ab2
Corrected creation of RecipeOutput
bsolino Aug 24, 2023
48fa2e5
Merge branch 'main' into html_monitoring
bsolino Aug 24, 2023
d37345e
Finished unit test for _sort_filters
bsolino Aug 24, 2023
21bc34d
Navigation tabs now stay at the top
bsolino Aug 25, 2023
b2dfeb2
Removed commented out styles
bsolino Aug 31, 2023
c2ae14e
Merge branch 'main' into html_monitoring
bsolino Sep 5, 2023
936cf12
Renaming for clarity
bsolino Sep 7, 2023
1ca1000
Updated button to bootstrap, refactored names and classes
bsolino Sep 7, 2023
84f37fe
Ported scripts from plain Javascript to jQuery
bsolino Sep 7, 2023
2280f26
Changed style of Hide checkbox to switch
bsolino Sep 7, 2023
09401f4
Moved filters to dropdown menu
bsolino Sep 8, 2023
0d9f8b6
Added space between text and button
bsolino Sep 8, 2023
b7a7154
Improved visualization of filters
bsolino Sep 8, 2023
272b063
Don't print data files when it's empty
bsolino Sep 8, 2023
292e3ef
Added Bootstrap classes to figures, set up as flex items
bsolino Sep 8, 2023
7ddf0ec
Improvements to filter menu visuals
bsolino Sep 11, 2023
dc963a8
Solved bug where figures where not properly hidden
bsolino Sep 11, 2023
f8e435c
Added ESMValTool logo to output html file
bsolino Sep 14, 2023
8d8f659
Merge branch 'main' into html_monitoring
bsolino Sep 14, 2023
e6a2e18
Refactored filters so they are stored once they're ready
bsolino Sep 15, 2023
5f965ae
Removed unused mockers
bsolino Sep 15, 2023
ab5e4a8
Refactored assert for legibility
bsolino Sep 15, 2023
aeba3e9
Corrected mypy warning
bsolino Sep 15, 2023
a00640d
Removed TODO regarding pre-existing code
bsolino Sep 15, 2023
fd04f48
Merge branch 'main' into html_monitoring
bsolino Sep 19, 2023
7c3428d
Merge branch 'main' into html_monitoring
Oct 9, 2023
2f7c277
Address Codacy comments
Oct 9, 2023
ee76223
Address more Codacy comments
Oct 9, 2023
0b9eb1e
Merge branch 'main' into html_monitoring
Oct 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion esmvalcore/experimental/recipe_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -123,12 +123,20 @@ 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 = {}
self.diagnostics = {}
self.info = info
self.session = session
self.filters: dict = {}

# Group create task output and group by diagnostic
diagnostics: dict = {}
Expand All @@ -142,6 +150,7 @@ def __init__(self, task_output: dict, session=None, info=None):

# Create diagnostic output
for name, tasks in diagnostics.items():
# TODO? This could fail if info is None
diagnostic_info = info.data['diagnostics'][name]
self.diagnostics[name] = DiagnosticOutput(
name=name,
Expand All @@ -150,6 +159,33 @@ 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:
self._add_to_filters(file.attributes)

# Sort at the end because sets are unordered
self._sort_filters()

def _add_to_filters(self, attributes):
"""Adds 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 = self.filters.get(attr, set())
if (isinstance(values, str) or not isinstance(values, Sequence)):
attr_list.add(values)
else:
attr_list.update(values)
self.filters[attr] = attr_list

def _sort_filters(self):
"""Sorts the HTML output filters."""
for _filter, _attrs in self.filters.items():
self.filters[_filter] = sorted(_attrs)

def __repr__(self):
"""Return canonical string representation."""
string = '\n'.join(repr(item) for item in self._task_output.values())
Expand Down Expand Up @@ -218,6 +254,7 @@ def render(self, template=None):
diagnostics=self.diagnostics.values(),
session=self.session,
info=self.info,
filters=self.filters,
relpath=os.path.relpath,
)

Expand Down
56 changes: 51 additions & 5 deletions esmvalcore/experimental/templates/RecipeOutput.j2
Original file line number Diff line number Diff line change
@@ -1,12 +1,58 @@
<!-- Filter -->
<div class="container div_filter">
<h3>Filter</h3>
<div class="row row-cols-auto">
{% for filter_type, types in filters.items() %}
{% set filter_loop = loop %}
<div class="col col-lg-3 filter_{{ filter_type }}">
<h4>{{ filter_type|replace('_', ' ')|title }}</h4>
{% for t in types %}
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="cb_{{ filter_loop.index }}_{{ loop.index }}" rel="f_{{ filter_type|replace(' ', '_') }}_{{ t|replace(' ', '_') }}">
<label class="form-check-label" for="cb_{{ filter_loop.index }}_{{ loop.index }}">
{{ t|replace('_', ' ')|title }}
</label>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
<button class="btn btn-primary disabled" onclick="resetFilter()">Clear all Filters</button>
</div>

<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="cb_hideEmptyDiagnostics" rel="" checked>
<label class="form-check-label" for="cb_hideEmptyDiagnostics">
Hide empty diagnostics
</label>
</div>

<!-- Tab links -->
<ul class="nav nav-tabs" id="tabDiagnostics" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active diagnostics-tab" id="tabAll" data-bs-toggle="tab" data-bs-target="#" type="button" role="tab" aria-controls="" aria-selected="true">All</button>
</li>
{% for diagnostic in diagnostics %}
<li class="nav-item" role="presentation">
<button class="nav-link diagnostics-tab" id="tab_{{ loop.index }}" data-bs-toggle="tab" data-bs-target="#tabPane_{{ loop.index }}" type="button" role="tab" aria-controls="tabPane_{{ loop.index }}" aria-selected="true">{{ diagnostic.title }}</button>
</li>
{% endfor %}
</ul>

<div class="tab-content" id="tabContentDiagnostics">
{% for diagnostic in diagnostics %}

<h2>{{ diagnostic.title }}</h2>
<p>{{ diagnostic.description }}</p>
<div id="tabPane_{{ loop.index }}" class="tab-pane show active diagnostics-tab-pane" role="tabpanel" aria-labelledby="tab_{{ loop.index }}">
<h2>{{ diagnostic.title }}</h2>
<p>{{ diagnostic.description }}</p>

{% for task in diagnostic.task_output %}
{% set diagnostic_loop = loop %}
{% for task in diagnostic.task_output %}

{% include 'TaskOutput.j2' %}
{% include 'TaskOutput.j2' %}

{% endfor %}
{% endfor %}
</div>

{% endfor %}
</div>
41 changes: 29 additions & 12 deletions esmvalcore/experimental/templates/TaskOutput.j2
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

{% for file in task.image_files %}

<div class="div_figure
{% for filter in filters.keys() %}
{% if filter in file.attributes %}
{% set attribute = file.attributes[filter] %}
{% if attribute is string or not attribute is iterable %}
f_{{ filter|replace(' ', '_') }}_{{ attribute | replace(' ', '_') }}
{% else %}
{% for attr in attribute %} f_{{ filter|replace(' ', '_') }}_{{ attr | replace(' ', '_') }} {% endfor %}
{% endif %}
{% endif %}
{% endfor %}
">
<figure>
<a href='{{ relpath(file.path, session.session_dir) }}'>
<img src='{{ relpath(file.path, session.session_dir) }}' alt='{{ file.caption }}'/>
Expand All @@ -16,21 +28,26 @@
<a href='{{ relpath(file.provenance_xml_file, session.session_dir) }}'>provenance</a>
</figcaption>
</figure>
</div>

{% endfor %}

<h4>Data files</h4>
<h4>Data files<button class="btn btn-primary" data-bs-toggle="collapse" data-bs-target="#df_{{ diagnostic_loop.index }}_{{ loop.index }}" aria-expanded="false" aria-controls="df_{{ diagnostic_loop.index }}_{{ loop.index }}">Show/Hide</button></h4>

<ul>
{% for file in task.data_files %}
<div id="df_{{ diagnostic_loop.index }}_{{ loop.index }}" class="collapse">
<div class="card card-body">
<ul>
{% for file in task.data_files %}

<li>
{{ file.caption }} |
<a href='{{ relpath(file.path, session.session_dir) }}'>download</a> |
<a href='{{ relpath(file.citation_file, session.session_dir) }}'>references</a> |
<a href='{{ relpath(file.data_citation_file, session.session_dir) }}'>extra data citation</a> |
<a href='{{ relpath(file.provenance_xml_file, session.session_dir) }}'>provenance</a>
</li>
<li>
{{ file.caption }} |
<a href='{{ relpath(file.path, session.session_dir) }}'>download</a> |
<a href='{{ relpath(file.citation_file, session.session_dir) }}'>references</a> |
<a href='{{ relpath(file.data_citation_file, session.session_dir) }}'>extra data citation</a> |
<a href='{{ relpath(file.provenance_xml_file, session.session_dir) }}'>provenance</a>
</li>

{% endfor %}
</ul>
{% endfor %}
</ul>
</div>
</div>
45 changes: 45 additions & 0 deletions esmvalcore/experimental/templates/head.j2
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm" crossorigin="anonymous"></script>
<style>
html {
font-size: medium;
Expand Down Expand Up @@ -70,4 +73,46 @@
margin-right: 1em;
}
</style>
{#
.div_figure {
display: inline-block;
}

/* Style the tab */
.tab {
overflow: hidden;
border: 1px solid #ccc;
background-color: #f1f1f1;
}

/* Style the buttons that are used to open the tab content */
.tab button {
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
}

/* Change background color of buttons on hover */
.tab button:hover {
background-color: #ddd;
}

/* Create an active/current tablink class */
.tab button.activeTablink {
background-color: #ccc;
}

/* Style the tab content */
.tabbox {
border: 1px solid #ccc;
}
.tabcontent {
display: none;
padding: 6px 12px;
border-top: none;
</style> #}
</head>
22 changes: 13 additions & 9 deletions esmvalcore/experimental/templates/recipe_output_page.j2
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,20 @@

{% include 'RecipeOutput.j2' %}

</body>
<h2>Files</h2>

<p>
<a href='{{ session.relative_main_log }}'>{{ session.main_log.name }}</a> |
<a href='{{ session.relative_main_log_debug }}'>{{ session.main_log_debug.name }}</a> |
<a href='{{ session.relative_run_dir / info.filename }}'>{{ info.filename }}</a> |
<a href='{{ session.relative_plot_dir }}'>figures</a> |
<a href='{{ session.relative_work_dir }}'>data</a>
</p>

<h2>Files</h2>
<script>
{% include 'scripts.js' %}
</script>

<p>
<a href='{{ session.relative_main_log }}'>{{ session.main_log.name }}</a> |
<a href='{{ session.relative_main_log_debug }}'>{{ session.main_log_debug.name }}</a> |
<a href='{{ session.relative_run_dir / info.filename }}'>{{ info.filename }}</a> |
<a href='{{ session.relative_plot_dir }}'>figures</a> |
<a href='{{ session.relative_work_dir }}'>data</a>
</p>
</body>

</html>
Loading