Skip to content

Commit 83f7e6f

Browse files
authored
Merge branch 'master' into sharding_fix
2 parents 5a68825 + 660f645 commit 83f7e6f

File tree

17 files changed

+245
-60
lines changed

17 files changed

+245
-60
lines changed

.circleci/config.yml

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,8 @@ defaults: &defaults
130130
pip install -e ".[docs]"
131131
python setup.py build_sphinx
132132
cp -r ./build "$CIRCLE_ARTIFACTS"
133-
# Build egg
134133
- run:
135-
name: Build egg
134+
name: Build distribution
136135
command: |
137136
set -ex
138137
. ci/bin/activate
@@ -146,8 +145,7 @@ defaults: &defaults
146145
cat ../log
147146
exit 1
148147
fi # rst2html.py alwaysexits with 0, check log size
149-
python setup.py bdist_wheel --universal
150-
python setup.py bdist_egg
148+
python setup.py bdist_wheel
151149
python setup.py sdist
152150
mkdir -p "$CIRCLE_ARTIFACTS/dist"
153151
cp -r ./dist/* "$CIRCLE_ARTIFACTS/dist"
@@ -192,7 +190,7 @@ jobs:
192190
PYTHON_VERSION: "3_6"
193191
CIRCLE_ARTIFACTS: /tmp/circleci-artifacts/3_6
194192
CIRCLE_TEST_REPORTS: /tmp/circleci-test-results/3_6
195-
VERSION: 0.5.1
193+
VERSION: 0.6.0
196194
PANDOC_RELEASES_URL: https://github.com/jgm/pandoc/releases
197195
YARN_STATIC_DIR: notebooker/web/static/
198196
IMAGE_NAME: mangroup/notebooker
@@ -204,7 +202,7 @@ jobs:
204202
environment:
205203
CIRCLE_ARTIFACTS: /tmp/circleci-artifacts/3_7
206204
CIRCLE_TEST_REPORTS: /tmp/circleci-test-results/3_7
207-
VERSION: 0.5.1
205+
VERSION: 0.6.0
208206
PANDOC_RELEASES_URL: https://github.com/jgm/pandoc/releases
209207
YARN_STATIC_DIR: notebooker/web/static/
210208
IMAGE_NAME: mangroup/notebooker
@@ -216,7 +214,7 @@ jobs:
216214
environment:
217215
CIRCLE_ARTIFACTS: /tmp/circleci-artifacts/3_8
218216
CIRCLE_TEST_REPORTS: /tmp/circleci-test-results/3_8
219-
VERSION: 0.5.1
217+
VERSION: 0.6.0
220218
PANDOC_RELEASES_URL: https://github.com/jgm/pandoc/releases
221219
YARN_STATIC_DIR: notebooker/web/static/
222220
IMAGE_NAME: mangroup/notebooker

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
0.5.2 (2023-??)
1+
0.6.0 (2023-09-01)
22
------------------
3+
* Feature: Reports are now grouped by their containing folder on the main UI.
34
* Feature: Allow configuring error email addresses and email subject via UI.
45
* Bugfix: . and .. should now be allowed to be used when specifying the templates directory.
56
* Bugfix: corrected cron schedule incorrectly shifting back one day upon save.

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
author = "Man Group Quant Tech"
2424

2525
# The full version, including alpha/beta/rc tags
26-
release = "0.5.1"
26+
release = "0.6.0"
2727

2828

2929
# -- General configuration ---------------------------------------------------
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
@@ -389,10 +389,11 @@ def _get_result_count(self, base_filter):
389389
# pymongo<3.7 compat
390390
return self.library.count(base_filter)
391391

392-
def get_count_and_latest_time_per_report(self):
392+
def get_count_and_latest_time_per_report(self, subfolder: Optional[str]):
393+
base_filer = {} if not subfolder else {"report_name": {"$regex": subfolder + ".*"}}
393394
reports = list(
394395
self._get_raw_results(
395-
base_filter={},
396+
base_filter=base_filer,
396397
projection={"report_name": 1, "job_start_time": 1, "scheduler_job_id": 1, "_id": 0},
397398
limit=0,
398399
)

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/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.5.1"
1+
__version__ = "0.6.0"

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();

0 commit comments

Comments
 (0)