Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
0.5.2 (2023-??)
------------------
* Feature: Allow configuring error email addresses via UI.
* Feature: Allow configuring error email addresses and email subject via UI.
* Bugfix: . and .. should now be allowed to be used when specifying the templates directory.
* Bugfix: corrected cron schedule incorrectly shifting back one day upon save.


0.5.1 (2023-02-22)
------------------

Expand Down
6 changes: 2 additions & 4 deletions notebooker/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class NotebookResultBase(object):
stdout = attr.ib(default=attr.Factory(list))
scheduler_job_id = attr.ib(default=None)
mailfrom = attr.ib(default=None)
email_subject = attr.ib(default=None)
is_slideshow = attr.ib(default=False)

def saveable_output(self):
Expand Down Expand Up @@ -123,10 +124,7 @@ class NotebookResultError(NotebookResultBase):
scheduler_job_id = attr.ib(default=None)
mailfrom = attr.ib(default=None)
is_slideshow = attr.ib(default=False)

@property
def email_subject(self):
return ""
email_subject = attr.ib(default=None)

@property
def raw_html(self):
Expand Down
5 changes: 5 additions & 0 deletions notebooker/execute_notebook.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ def run_report(
mailfrom=mailfrom,
hide_code=hide_code,
is_slideshow=is_slideshow,
email_subject=email_subject,
)
logger.error(
"Report run failed. Saving error result to mongo library %s@%s...",
Expand Down Expand Up @@ -472,6 +473,7 @@ def run_report_in_subprocess(
scheduler_job_id=None,
run_synchronously=False,
mailfrom=None,
email_subject=None,
n_retries=3,
is_slideshow=False,
) -> str:
Expand All @@ -489,6 +491,7 @@ def run_report_in_subprocess(
:param scheduler_job_id: `Optional[str]` if the job was triggered from the scheduler, this is the scheduler's job id
:param run_synchronously: `bool` If True, then we will join the stderr monitoring thread until the job has completed
:param mailfrom: `str` if passed, then this string will be used in the from field
:param email_subject: `str` if passed, then this string will be used in the email subject
:param n_retries: The number of retries to attempt.
:param is_slideshow: Whether the notebook is a reveal.js slideshow or not.
:return: The unique job_id.
Expand All @@ -511,6 +514,7 @@ def run_report_in_subprocess(
hide_code=hide_code,
scheduler_job_id=scheduler_job_id,
is_slideshow=is_slideshow,
email_subject=email_subject,
)

command = (
Expand Down Expand Up @@ -553,6 +557,7 @@ def run_report_in_subprocess(
+ (["--is-slideshow"] if is_slideshow else [])
+ ([f"--scheduler-job-id={scheduler_job_id}"] if scheduler_job_id is not None else [])
+ ([f"--mailfrom={mailfrom}"] if mailfrom is not None else [])
+ ([f"--email-subject={email_subject}"] if email_subject else [])
)
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

Expand Down
15 changes: 7 additions & 8 deletions notebooker/serialization/mongo.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,7 @@ def update_check_status(self, job_id: str, status: JobStatus, **extra):
existing["status"] = status.value
for k, v in extra.items():
if k == "error_info" and v:
self.result_data_store.put(
v,
filename=_error_info_filename(job_id),
encoding="utf-8",
)
self.result_data_store.put(v, filename=_error_info_filename(job_id), encoding="utf-8")
else:
existing[k] = v
self._save_raw_to_db(existing)
Expand All @@ -192,6 +188,7 @@ def save_check_stub(
hide_code: bool = False,
scheduler_job_id: Optional[str] = None,
is_slideshow: bool = False,
email_subject: Optional[str] = None,
) -> None:
"""Call this when we are just starting a check. Saves a "pending" job into storage."""
job_start_time = job_start_time or datetime.datetime.now()
Expand All @@ -204,6 +201,7 @@ def save_check_stub(
report_name=report_name,
mailto=mailto,
error_mailto=error_mailto,
email_subject=email_subject,
generate_pdf_output=generate_pdf_output,
overrides=overrides or {},
hide_code=hide_code,
Expand All @@ -226,9 +224,7 @@ def save_check_result(self, notebook_result: Union[NotebookResultComplete, Noteb
filename=filename_func(notebook_result.job_id),
encoding="utf-8",
)
for json_attribute, filename_func in [
("raw_ipynb_json", _raw_json_filename),
]:
for json_attribute, filename_func in [("raw_ipynb_json", _raw_json_filename)]:
if getattr(notebook_result, json_attribute, None):
self.result_data_store.put(
json.dumps(getattr(notebook_result, json_attribute)),
Expand Down Expand Up @@ -306,6 +302,7 @@ def _convert_result(
stdout=result.get("stdout", []),
scheduler_job_id=result.get("scheduler_job_id", None),
is_slideshow=result.get("is_slideshow", False),
email_subject=result.get("email_subject", None),
)
elif cls == NotebookResultPending:
return NotebookResultPending(
Expand All @@ -319,6 +316,7 @@ def _convert_result(
report_title=result.get("report_title", result["report_name"]),
mailto=result.get("mailto", ""),
error_mailto=result.get("error_mailto", ""),
email_subject=result.get("email_subject", ""),
hide_code=result.get("hide_code", False),
stdout=result.get("stdout", []),
scheduler_job_id=result.get("scheduler_job_id", None),
Expand All @@ -343,6 +341,7 @@ def _convert_result(
report_title=result.get("report_title", result["report_name"]),
mailto=result.get("mailto", ""),
error_mailto=result.get("error_mailto", ""),
email_subject=result.get("email_subject", ""),
hide_code=result.get("hide_code", False),
stdout=result.get("stdout", []),
scheduler_job_id=result.get("scheduler_job_id", False),
Expand Down
4 changes: 3 additions & 1 deletion notebooker/utils/notebook_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ def _send_email(from_email: str, to_email: str, result: Union[NotebookResultComp
report_title = (
result.report_title.decode("utf-8") if isinstance(result.report_title, bytes) else result.report_title
)
subject = result.email_subject or f"Notebooker: {report_title} report completed with status: {result.status.value}"
subject = f"Notebooker: {report_title} report completed with status: {result.status.value}"
if isinstance(result, NotebookResultComplete):
subject = result.email_subject or subject
body = result.email_html or result.raw_html
attachments = []
tmp_dir = None
Expand Down
6 changes: 6 additions & 0 deletions notebooker/web/routes/report_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ class RunReportParams(NamedTuple):
hide_code: bool
scheduler_job_id: Optional[str]
is_slideshow: bool
email_subject: Optional[str]


def validate_run_params(report_name, params, issues: List[str]) -> RunReportParams:
Expand All @@ -142,6 +143,7 @@ def validate_run_params(report_name, params, issues: List[str]) -> RunReportPara
generate_pdf_output = params.get("generate_pdf") in ("on", "True", True)
hide_code = params.get("hide_code") in ("on", "True", True)
is_slideshow = params.get("is_slideshow") in ("on", "True", True)
email_subject = validate_title(params.get("email_subject") or "", issues)

out = RunReportParams(
report_title=report_title,
Expand All @@ -152,6 +154,7 @@ def validate_run_params(report_name, params, issues: List[str]) -> RunReportPara
hide_code=hide_code,
scheduler_job_id=params.get("scheduler_job_id"),
is_slideshow=is_slideshow,
email_subject=email_subject,
)
logger.info(f"Validated params: {out}")
return out
Expand All @@ -174,6 +177,7 @@ def _handle_run_report(
f"hide_code={params.hide_code} "
f"scheduler_job_id={params.scheduler_job_id} "
f"mailfrom={params.mailfrom} "
f"email_subject={params.email_subject} "
f"is_slideshow={params.is_slideshow} "
)
try:
Expand All @@ -190,6 +194,7 @@ def _handle_run_report(
hide_code=params.hide_code,
scheduler_job_id=params.scheduler_job_id,
mailfrom=params.mailfrom,
email_subject=params.email_subject,
is_slideshow=params.is_slideshow,
)
return (
Expand Down Expand Up @@ -255,6 +260,7 @@ def _rerun_report(job_id, prepare_only=False, run_synchronously=False):
scheduler_job_id=None, # the scheduler will never call rerun
run_synchronously=run_synchronously,
is_slideshow=result.is_slideshow,
email_subject=result.email_subject,
)
return new_job_id

Expand Down
2 changes: 2 additions & 0 deletions notebooker/web/routes/scheduling.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def update_schedule(report_name):
"mailto": params.mailto,
"error_mailto": params.error_mailto,
"mailfrom": params.mailfrom,
"email_subject": params.email_subject,
"generate_pdf": params.generate_pdf_output,
"hide_code": params.hide_code,
"is_slideshow": params.is_slideshow,
Expand Down Expand Up @@ -109,6 +110,7 @@ def create_schedule(report_name):
"mailto": params.mailto,
"error_mailto": params.error_mailto,
"mailfrom": params.mailfrom,
"email_subject": params.email_subject,
"generate_pdf": params.generate_pdf_output,
"hide_code": params.hide_code,
"scheduler_job_id": job_id,
Expand Down
4 changes: 4 additions & 0 deletions notebooker/web/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def run_report(
mailfrom: Optional[str] = None,
is_slideshow: bool = False,
error_mailto: Optional[str] = None,
email_subject: Optional[str] = None,
):
"""
This is the entrypoint of the scheduler; APScheduler has to
Expand All @@ -44,6 +45,7 @@ def run_report(
mailfrom=mailfrom,
n_retries=0,
is_slideshow=is_slideshow,
email_subject=email_subject,
)
else:
# Fall back to using API. This will not work in readonly mode.
Expand All @@ -64,6 +66,8 @@ def run_report(
# natural.
if mailfrom:
payload["mailfrom"] = mailfrom
if email_subject:
payload["email_subject"] = email_subject
logger.info(f"Running report at {url}, payload = {payload}")
result = requests.post(url, params=urllib.parse.urlencode(payload))
logger.info(result.content)
Expand Down
3 changes: 3 additions & 0 deletions notebooker/web/static/notebooker/scheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ function modifySchedulerModal(row) {
mailto: row.params.mailto,
error_mailto: row.params.error_mailto,
mailfrom: row.params.mailfrom,
email_subject: row.params.email_subject,
cronSchedule: row.cron_schedule,
is_slideshow: row.params.is_slideshow,
}
Expand Down Expand Up @@ -169,6 +170,7 @@ function handleAddButtonClick() {
mailto: "",
error_mailto: "",
mailfrom: "",
email_subject: "",
cronSchedule: "",
is_slideshow: "",
}
Expand Down Expand Up @@ -272,6 +274,7 @@ $(document).ready(() => {
mailto: formObj.mailto,
error_mailto: formObj.error_mailto,
mailfrom: formObj.mailfrom,
email_subject: formObj.email_subject,
generate_pdf: formObj.generate_pdf,
hide_code: formObj.hide_code,
is_slideshow: formObj.is_slideshow,
Expand Down
3 changes: 3 additions & 0 deletions notebooker/web/templates/results.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ <h4>Mailing results to:</h4> {{ result.mailto }}
{% if result.error_mailto %}
<h4>Mailing errors to:</h4> {{ result.error_mailto }}
{% endif %}
{% if result.email_subject %}
<h4>Email subject:</h4> {{ result.email_subject }}
{% endif %}
{% if result.overrides %}
<h4>Parameters:</h4>
<div class="ui list">
Expand Down
7 changes: 7 additions & 0 deletions notebooker/web/templates/run_report.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ <h3>Email from:</h3>
id="titleInput"
width="100%"/>
</div>
<div class="field">
<h3>Email subject:</h3>
<input name="email_subject"
placeholder="If specified overrides default email subject (optional)"
id="titleInput"
width="100%"/>
</div>
<div class="field">
<div class="ui checkbox">
<input name="generate_pdf" id="generate_pdf" type="checkbox">
Expand Down
8 changes: 8 additions & 0 deletions notebooker/web/templates/scheduler.html
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@
<input type="text" name="mailfrom" placeholder="{{ default_mailfrom }}">
</label>
</div>
</div>
<div class="field" id="email_subject">
<label>
Mail subject (optional)
<input type="text" name="email_subject" placeholder="">
</label>
</div>
<div class="fields">
<div class="four wide field" id="mailto">
<div class="ui toggle checkbox">
<label>Generate PDF?</label>
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/web/test_run_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def test_run_report_json_parameters(flask_app, setup_workspace):
hide_code = True
is_slideshow = True
scheduler_job_id = "abc/123"
email_subject = "Subject"
payload = {
"overrides": json.dumps(overrides),
"report_title": report_title,
Expand All @@ -28,6 +29,7 @@ def test_run_report_json_parameters(flask_app, setup_workspace):
"scheduler_job_id": scheduler_job_id,
"is_slideshow": is_slideshow,
"mailfrom": mailfrom,
"email_subject": email_subject,
}
with mock.patch("notebooker.web.routes.report_execution.run_report_in_subprocess") as rr:
rr.return_value = "fake_job_id"
Expand All @@ -47,6 +49,7 @@ def test_run_report_json_parameters(flask_app, setup_workspace):
scheduler_job_id=scheduler_job_id,
mailfrom=mailfrom,
is_slideshow=is_slideshow,
email_subject=email_subject,
)


Expand Down
2 changes: 2 additions & 0 deletions tests/integration/web/test_scheduling.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def test_create_schedule(flask_app, setup_workspace, report_name):
"is_slideshow": True,
"cron_schedule": "* * * * *",
"mailfrom": "test@example.com",
"email_subject": "Subject",
},
)
assert rv.status_code == 201
Expand All @@ -42,6 +43,7 @@ def test_create_schedule(flask_app, setup_workspace, report_name):
"is_slideshow": True,
"scheduler_job_id": f"{report_name}_test2",
"mailfrom": "test@example.com",
"email_subject": "Subject",
},
"trigger": {
"fields": {
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/test_run_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def test_validate_run_params():
("scheduler_job_id", "plot_random_asdas"),
("mailfrom", "test@example.com"),
("is_slideshow", "on"),
("email_subject", "Subject of the email"),
]
),
ImmutableMultiDict([]),
Expand All @@ -64,6 +65,7 @@ def test_validate_run_params():
scheduler_job_id="plot_random_asdas",
mailfrom="test@example.com",
is_slideshow=True,
email_subject="Subject of the email",
)
actual_output = validate_run_params("lovely_report_name", input_params, issues)
assert issues == []
Expand Down