Skip to content

Commit 962598d

Browse files
grouping report results into folders (#150)
grouping report results into folders
1 parent 93d9a17 commit 962598d

File tree

13 files changed

+236
-49
lines changed

13 files changed

+236
-49
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Feature: Allow configuring error email addresses and email subject via UI.
44
* Bugfix: . and .. should now be allowed to be used when specifying the templates directory.
55
* Bugfix: corrected cron schedule incorrectly shifting back one day upon save.
6+
* Feature: Grouping reports into nested folders
67

78
0.5.1 (2023-02-22)
89
------------------
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# ---
2+
# jupyter:
3+
# jupytext:
4+
# text_representation:
5+
# extension: .py
6+
# format_name: light
7+
# format_version: '1.5'
8+
# jupytext_version: 1.3.0
9+
# kernelspec:
10+
# display_name: py3-local-pegasus
11+
# language: python
12+
# name: env
13+
# ---
14+
15+
# + tags=["parameters"]
16+
the_range = 10
17+
# -
18+
19+
# + [markdown] slideshow={"slide_type": "slide"}
20+
# # This is slide number one
21+
22+
# + [markdown] slideshow={"slide_type": "subslide"}
23+
# ## This is slide two
24+
#
25+
# - I have some
26+
# - things to talk
27+
# - about.
28+
29+
# + [markdown] slideshow={"slide_type": "slide"}
30+
# # Okay onto the good stuff!
31+
# -
32+
33+
list(range(the_range))

notebooker/serialization/mongo.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -371,10 +371,11 @@ def _get_result_count(self, base_filter):
371371
# pymongo<3.7 compat
372372
return self.library.count(base_filter)
373373

374-
def get_count_and_latest_time_per_report(self):
374+
def get_count_and_latest_time_per_report(self, subfolder: Optional[str]):
375+
base_filer = {} if not subfolder else {"report_name": {"$regex": subfolder + ".*"}}
375376
reports = list(
376377
self._get_raw_results(
377-
base_filter={},
378+
base_filter=base_filer,
378379
projection={"report_name": 1, "job_start_time": 1, "scheduler_job_id": 1, "_id": 0},
379380
limit=0,
380381
)

notebooker/utils/results.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ def get_all_available_results_json(
139139
return json_output
140140

141141

142-
def get_count_and_latest_time_per_report(serializer: MongoResultSerializer):
143-
reports = serializer.get_count_and_latest_time_per_report()
142+
def get_count_and_latest_time_per_report(serializer: MongoResultSerializer, subfolder: Optional[str] = None):
143+
reports = serializer.get_count_and_latest_time_per_report(subfolder)
144144
output = {}
145145
for report_name, metadata in sorted(reports.items(), key=lambda x: x[1]["latest_run"], reverse=True):
146146
metadata["report_name"] = report_name

notebooker/web/routes/index.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from typing import Optional
12
import traceback
23

34
import inflection
@@ -10,7 +11,8 @@
1011

1112

1213
@index_bp.route("/", methods=["GET"])
13-
def index():
14+
@index_bp.route("/folder/<path:subfolder>", methods=["GET"])
15+
def index(subfolder: Optional[str] = None):
1416
"""
1517
The index page which shows cards of each report which has at least one result in the database.
1618
"""

notebooker/web/routes/templates.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Optional
2+
13
from flask import jsonify, Blueprint
24

35
from notebooker.utils.results import get_count_and_latest_time_per_report
@@ -8,14 +10,15 @@
810
templates_bp = Blueprint("templates_bp", __name__)
911

1012

11-
@templates_bp.route("/core/get_all_templates_with_results")
12-
def all_available_templates_with_results():
13+
@templates_bp.route("/core/get_all_templates_with_results/folder/")
14+
@templates_bp.route("/core/get_all_templates_with_results/folder/<path:subfolder>")
15+
def all_available_templates_with_results(subfolder: Optional[str] = None):
1316
"""
1417
Core function for the index.html view which shows the templates which have results available.
1518
1619
:returns: A JSON containing a list of template names with a count of how many results are in each.
1720
"""
18-
return jsonify(get_count_and_latest_time_per_report(get_serializer()))
21+
return jsonify(get_count_and_latest_time_per_report(get_serializer(), subfolder))
1922

2023

2124
@templates_bp.route("/core/all_possible_templates")

notebooker/web/static/notebooker/index.js

Lines changed: 117 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,124 @@
1+
2+
function getCurrentFolder(){
3+
if (window.location.pathname.startsWith("/folder/")){
4+
currentFolder = window.location.pathname.substring("/folder/".length) + "/"
5+
} else {
6+
currentFolder = ""
7+
}
8+
return currentFolder
9+
}
10+
11+
function updateNavigationPanel(currentFolder){
12+
let $folderNavigationPanel = $('#folderNavigationPanel');
13+
$folderNavigationPanel.empty()
14+
15+
if (currentFolder != "") {
16+
$folderNavigationPanel.append('<a href="/">Start</a>')
17+
parts = currentFolder.split("/")
18+
folderSoFar = ""
19+
for(let idx in parts){
20+
part = parts[idx]
21+
if(part.length > 0){
22+
folderSoFar = folderSoFar + "/" + part
23+
if(idx == parts.length - 2){
24+
$folderNavigationPanel.append('&nbsp;&gt;&nbsp;<span>' + part + "</span>")
25+
}else{
26+
$folderNavigationPanel.append('&nbsp;&gt;&nbsp;<a href="/folder' + folderSoFar + '">' + part + " </a>")
27+
}
28+
29+
}
30+
}
31+
}
32+
}
33+
34+
35+
function entryAfterLevel(path, level){
36+
while(level > 0) {
37+
path = path.substring(path.indexOf('/') + 1)
38+
level = level - 1
39+
}
40+
return path
41+
}
42+
43+
function updateContents(currentFolder, entries){
44+
let $cardContainer = $('#cardContainer');
45+
$cardContainer.empty();
46+
subfoldersInfo = {}
47+
reportParts = []
48+
for (let report in entries) {
49+
report_data = entries[report]
50+
report_path = report_data.report_name
51+
if(!report_path.startsWith(currentFolder)){
52+
continue;
53+
}
54+
remainingPath = report_path.substring(currentFolder.length)
55+
if(remainingPath.includes('/')) {
56+
// it is a folder
57+
subfolderPathName = remainingPath.substring(0, remainingPath.indexOf('/'))
58+
subfolderPath = currentFolder + subfolderPathName
59+
if (subfolderPath in subfoldersInfo) {
60+
info.reportCount = info.reportCount + 1
61+
} else{
62+
info = {}
63+
info.pathName = subfolderPathName
64+
info.path = subfolderPath
65+
info.reportCount = 1
66+
subfoldersInfo[subfolderPath] = info
67+
}
68+
}else{
69+
level = currentFolder.split('/').length - 1
70+
displayName = entryAfterLevel(report, level)
71+
let stats = entries[report];
72+
reportParts.push('<a class="ui card" href="/result_listing/' + stats.report_name + '">' +
73+
' <div class="content">' +
74+
' <h1>' + displayName + '</h1>\n' +
75+
' <div class="meta">\n' +
76+
' <span class="date">Last ran ' + stats.time_diff + ' ago</span>\n' +
77+
' </div>' +
78+
' <span>\n' +
79+
stats.count +
80+
' Runs\n' +
81+
' </span>' +
82+
'<br/>' +
83+
' <span>\n' +
84+
stats.scheduler_runs +
85+
' Scheduler Runs\n' +
86+
' </span>' +
87+
' </div>' +
88+
' <div class="extra content">' +
89+
' <span>Original report name: ' + stats.report_name + '</span>\n' +
90+
' </div>' +
91+
'</a>');
92+
}
93+
94+
95+
}
96+
// first add all folders
97+
for (let subfolder in subfoldersInfo) {
98+
info = subfoldersInfo[subfolder]
99+
$cardContainer.append(
100+
'<a class="ui card folder" href="/folder/' + info.path + '">' +
101+
' <div class="content">' +
102+
' <h1><i class="fa-solid fa-folder fa-xs"></i> ' + info.pathName + '</h1>\n' +
103+
' <span>Reports: ' + info.reportCount + '</span>' +
104+
' </div>' +
105+
'</a>');
106+
}
107+
// only then add individual items
108+
for (let idx in reportParts) {
109+
$cardContainer.append(reportParts[idx])
110+
}
111+
}
112+
113+
1114
load_data = () => {
2115
$.ajax({
3-
url: `/core/get_all_templates_with_results`,
116+
url: `/core/get_all_templates_with_results/folder/`.concat(getCurrentFolder()),
4117
dataType: 'json',
5118
success: (result) => {
6-
let $cardContainer = $('#cardContainer');
7-
$cardContainer.empty();
8-
for (let report in result) {
9-
let stats = result[report];
10-
$cardContainer.append(
11-
'<a class="ui card" href="/result_listing/' + stats.report_name + '">' +
12-
' <div class="content">' +
13-
' <h1>' + report + '</h1>\n' +
14-
' <div class="meta">\n' +
15-
' <span class="date">Last ran ' + stats.time_diff + ' ago</span>\n' +
16-
' </div>' +
17-
' <span>\n' +
18-
stats.count +
19-
' Runs\n' +
20-
' </span>' +
21-
'<br/>' +
22-
' <span>\n' +
23-
stats.scheduler_runs +
24-
' Scheduler Runs\n' +
25-
' </span>' +
26-
' </div>' +
27-
' <div class="extra content">' +
28-
' <span>Original report name: ' + stats.report_name + '</span>\n' +
29-
' </div>' +
30-
'</a>');
31-
}
119+
currentFolder = getCurrentFolder()
120+
updateNavigationPanel(currentFolder)
121+
updateContents(currentFolder, result)
32122
},
33123
error: (jqXHR, textStatus, errorThrown) => {
34124
$('#failedLoad').fadeIn();

notebooker/web/static/notebooker/one_click_notebooks.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ code {
4646
color: #222222;
4747
}
4848

49+
.ui.card.folder {
50+
background-color: #EEEEEE;
51+
}
52+
4953
.ui.table {
5054
width: 99.5%; /* Because a border with overflow-y:auto causes a tiny scrollbar */
5155
}
@@ -92,6 +96,14 @@ td button i {
9296
max-width: 280px; /* Enough to show full job id */
9397
}
9498

99+
.folderImg {
100+
margin-right: 10px;
101+
}
102+
103+
#folderNavigationPanel {
104+
margin-bottom: 1em;
105+
}
106+
95107
.resultHeader {
96108
margin-left: 1em !important;
97109
}

notebooker/web/templates/header.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<script src="{{ url_for('static', filename='node_modules/bootstrap-table/dist/themes/semantic/bootstrap-table-semantic.js') }}"></script>
1212
<script type="application/javascript" src="{{ url_for('static', filename='notebooker/iframe_loading.js') }}"></script>
1313
<script type="application/javascript" src="{{ url_for('static', filename='notebooker/header.js') }}"></script>
14+
<script src="https://kit.fontawesome.com/58031af4b9.js" crossorigin="anonymous"></script>
1415
<link rel="stylesheet" href="{{ url_for('static', filename='notebooker/one_click_notebooks.css') }}">
1516
</head>
1617
<body>

notebooker/web/templates/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ <h2>Couldn't load any reports!</h2>
1616
</span>
1717
<div class="ui centered grid" id="cardGrid">
1818
<div class="fifteen wide centered column">
19+
<div id="folderNavigationPanel"></div>
1920
<div class="ui cards" id="cardContainer">
2021
<div class="ui loading card">
2122
<div class="content">

0 commit comments

Comments
 (0)