Skip to content

Commit 83bafe2

Browse files
authored
[LIMS-1842] Alert LCs 24 hours before session (#13)
* Use paginate function from lims-utils * Alert LCs of upcoming sessions * Add email template * Fix link URL * Fix dependencies * Make contact email optional * Typing fixes * Update email contents * Fix test error with pytest 8.4.2
1 parent 3a67995 commit 83bafe2

File tree

19 files changed

+393
-83
lines changed

19 files changed

+393
-83
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ WORKDIR /project
1919

2020
# make the wheel outside of the venv so 'build' does not dirty requirements.txt
2121
RUN pip install --upgrade pip build && \
22-
export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) && \
22+
#export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) && \
2323
python -m build && \
2424
touch requirements.txt
2525

MANIFEST.in

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ include src/scaup/assets/cut-here.png
22
include src/scaup/assets/diamond-logo.jpg
33
include src/scaup/assets/this-side-up.png
44
include src/scaup/assets/DejaVuSans-Bold.ttf
5-
include src/scaup/assets/DejaVuSans.ttf
5+
include src/scaup/assets/DejaVuSans.ttf
6+
include src/scaup/assets/logo-light.png

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@
99
"backend_url": "https://sample-shipping-staging.diamond.ac.uk",
1010
"frontend_url": "https://sample-shipping-staging.diamond.ac.uk"
1111
},
12+
"alerts": {
13+
"contact_email": "pato@diamond.ac.uk",
14+
"smtp_port": 26,
15+
"smtp_server": "smtp.ac.uk",
16+
"local_contacts": {
17+
"Dr John Doe": "john@diamond.ac.uk"
18+
}
19+
},
1220
"db": { "pool": 3, "overflow": 6 },
1321
"ispyb_api": "http://127.0.0.1:8060/api",
1422
"frontend_url": "http://localtest.diamond.ac.uk:9000"

pyproject.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ classifiers = [
1414
description = "Scaup's backend"
1515

1616
dependencies = [
17+
"APScheduler~=3.11.0",
1718
"SQLAlchemy~=2.0.36",
1819
"fastapi~=0.116.1",
1920
"psycopg[binary,pool]~=3.2.9",
2021
"pydantic~=2.11.7",
21-
"uvicorn~=0.35.0",
22+
"uvicorn~=0.35.0",
2223
"types-requests",
2324
"lims_utils~=0.4.2",
2425
"requests",
@@ -38,7 +39,7 @@ dev = [
3839
"mypy",
3940
"pipdeptree",
4041
"pre-commit",
41-
"pytest~=8.4.1",
42+
"pytest~=8.4.2",
4243
"pytest-cov",
4344
"ruff",
4445
"freezegun~=1.5.5",
@@ -77,7 +78,8 @@ addopts = """
7778
# https://iscinumpy.gitlab.io/post/bound-version-constraints/#watch-for-warnings
7879
filterwarnings = "error"
7980
markers = [
80-
"noregister: do not register HTTP mock responses"
81+
"noregister: do not register HTTP mock responses",
82+
"no_sample_response: do not register sample creation HTTP mock responses"
8183
]
8284

8385
[tool.coverage.run]

src/scaup/assets/logo-light.png

25.1 KB
Loading

src/scaup/assets/paths.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
THIS_SIDE_UP = os.path.join(ASSETS_PATH, "this-side-up.png")
88
DEJAVU_SANS = os.path.join(ASSETS_PATH, "DejaVuSans.ttf")
99
DEJAVU_SANS_BOLD = os.path.join(ASSETS_PATH, "DejaVuSans-Bold.ttf")
10+
COMPANY_LOGO_LIGHT = os.path.join(ASSETS_PATH, "logo-light.png")

src/scaup/crud/pdf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ def generate_report(shipment_id: int, token: str):
429429
.join(Sample.container)
430430
.filter(Sample.shipmentId == shipment_id)
431431
.options(contains_eager(Sample.container))
432-
.order_by(Sample.subLocation.desc(), Sample.container, Sample.location)
432+
.order_by(Sample.subLocation.desc(), Sample.containerId, Sample.location)
433433
)
434434

435435
# TODO: rethink this once we're using user-provided templates

src/scaup/crud/pre_sessions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def get_pre_session_info(shipment_id: int, user: GenericUser, token: HTTPAuthori
2828

2929
validated_model = PreSessionOut(
3030
details=None if pre_session_info is None else pre_session_info.details,
31-
isLocked=check_session_locked(shipment_id, user, token),
31+
isLocked=check_session_locked(shipment_id, user, token=token),
3232
)
3333

3434
return validated_model

src/scaup/main.py

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import os
2+
from contextlib import asynccontextmanager
23

34
from fastapi import FastAPI, HTTPException, Request, Response
45
from fastapi.middleware.cors import CORSMiddleware
56
from lims_utils.database import get_session
67
from lims_utils.logging import app_logger, log_exception_handler, register_loggers
7-
from sqlalchemy import create_engine
8-
from sqlalchemy.orm import sessionmaker
98

109
from . import __version__
1110
from .routes import (
@@ -16,9 +15,23 @@
1615
shipments,
1716
top_level_containers,
1817
)
18+
from .utils.alerts import session_alerts_scheduler
1919
from .utils.config import Config
20+
from .utils.database import inner_session
2021

21-
app = FastAPI(version=__version__, title="Scaup API")
22+
23+
@asynccontextmanager
24+
async def lifespan(app: FastAPI):
25+
register_loggers()
26+
if Config.alerts.contact_email:
27+
session_alerts_scheduler.start()
28+
yield
29+
session_alerts_scheduler.shutdown()
30+
else:
31+
yield
32+
33+
34+
app = FastAPI(version=__version__, title="Scaup API", lifespan=lifespan)
2235

2336
api = FastAPI()
2437

@@ -32,24 +45,6 @@
3245
)
3346

3447

35-
inner_engine = create_engine(
36-
url=os.environ.get(
37-
"SQL_DATABASE_URL",
38-
"postgresql+psycopg://sample_handling:sample_root@127.0.0.1:5432/sample_handling",
39-
),
40-
pool_pre_ping=True,
41-
pool_recycle=3600,
42-
pool_size=Config.db.pool,
43-
max_overflow=Config.db.overflow,
44-
)
45-
46-
47-
inner_session = sessionmaker(autocommit=False, autoflush=False, bind=inner_engine)
48-
49-
50-
register_loggers()
51-
52-
5348
@app.middleware("http")
5449
async def get_session_as_middleware(request, call_next):
5550
with get_session(inner_session):

src/scaup/models/alerts.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# ruff: noqa: E501
2+
3+
from string import Template
4+
5+
EMAIL_HEADER = """
6+
<html>
7+
<body>
8+
<div class="wrapper" style="padding: 0.5%; text-align:center; border-radius: 5px; border: 1px solid #efefef">
9+
<div class="header" style="background: #001d55; text-align: center; padding-top: 15px; padding-bottom: 15px; border-top-left-radius: 5px; border-top-right-radius: 5px;">
10+
<img src="cid:logo-light.png" height="45"/>
11+
</div>
12+
<div>
13+
"""
14+
15+
EMAIL_FOOTER = """
16+
<p style="border-top: 1px solid #001d55; background-color: #1040A1; padding: 10px; color: white;">© 2025, Diamond Light Source</p></div>
17+
"""
18+
19+
SAMPLE_COLLECTION_LINK = Template("""
20+
<li><a href="$frontend_url/proposals/$proposal/sessions/$session/shipments/$shipment">$shipment_name</a></li>
21+
""")
22+
23+
ALERT_BODY = Template("""
24+
<p>Dear $local_contact,</p>
25+
26+
<p>The user(s) have submitted important information regarding the grids and pre-session data collection parameters for session $proposal-$session in SCAUP.</p>
27+
28+
<p>To view this information, either head to the <a href="$frontend_url/proposals/$proposal/sessions/$session">session samples dashboard</a>, select a sample collection and click “Print contents as table” under the Actions section, or view one of the session sample collections directly:</p>
29+
30+
<ul style="text-align: left;list-style: square;">
31+
$sample_collection_links
32+
</ul>
33+
34+
<p>Before starting an EPU session, please ensure that grids are added to the Cassette positions in the Sample Collection Summary page. This step allows results from the auto-processing pipeline in PATo to be correctly linked to sample conditions in SCAUP.</p>
35+
36+
<p>Note: If you are not the designated Local Contact for this session, kindly forward this email to the appropriate person.</p>
37+
38+
<p>Many thanks,</p>
39+
<p>SCAUP team</p>
40+
""")

0 commit comments

Comments
 (0)