Skip to content

Commit 6405618

Browse files
ui details
1 parent e76137f commit 6405618

16 files changed

+107
-97
lines changed

core/pioreactor/calibrations/protocols/od_reference_standard.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def reference_standard_flow(ctx: SessionContext) -> CalibrationStep:
167167

168168
od_readings = _record_reference_standard_for_session(ctx, ir_led_intensity)
169169
recorded_ods = [0.0, 1000 * STANDARD_OD]
170-
timestamp = current_utc_datetime().strftime("%Y-%m-%d")
170+
timestamp = current_utc_datetime().strftime("%Y-%m-%d_%H-%M-%S")
171171

172172
calibration_links: list[dict[str, str | None]] = []
173173
if isinstance(od_readings, dict):

core/pioreactor/calibrations/protocols/pump_duration_based.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ def pump_duration_flow(ctx: SessionContext) -> CalibrationStep:
283283
ctx.step = "tracer_volume"
284284
return steps.action(
285285
"Tracer run",
286-
f"Running the pump for {tracer_duration:.2f} seconds, then measure the volume expelled.",
286+
f"Running the pump for {tracer_duration:.2f} seconds. Please measure the volume expelled.",
287287
)
288288

289289
if ctx.step == "tracer_volume":
@@ -327,8 +327,8 @@ def pump_duration_flow(ctx: SessionContext) -> CalibrationStep:
327327
ctx.step = "test_volume"
328328
duration = float(durations[test_index])
329329
step = steps.action(
330-
"Test run",
331-
f"Running the pump for {duration:.2f} seconds, then measure the volume expelled.",
330+
"Dispense run",
331+
f"Running the pump for {duration:.2f} seconds. Please measure the volume expelled.",
332332
)
333333
if results:
334334
step.metadata = {
@@ -394,7 +394,7 @@ def pump_duration_flow(ctx: SessionContext) -> CalibrationStep:
394394
"Too much uncertainty in slope - you probably want to rerun this calibration..."
395395
)
396396
step = steps.form(
397-
"Record test volume",
397+
"Record dispensed volume",
398398
"Enter the amount of water expelled (mL or g).",
399399
[fields.float("volume_ml", label="Volume expelled", minimum=0.0001)],
400400
)

core/pioreactor/calibrations/session_flow.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,8 +331,8 @@ def result(self, result: dict[str, Any]) -> CalibrationStep:
331331
return CalibrationStep(
332332
step_id="complete",
333333
step_type="result",
334-
title="Calibration complete",
335-
body="Calibration results are ready.",
334+
title="Calibration complete!",
335+
body="Calibration(s) is set to Active and ready to be used.",
336336
metadata={"result": result},
337337
)
338338

core/pioreactor/web/api.py

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from __future__ import annotations
33

44
import configparser
5+
import json
56
import os
67
import re
78
import sqlite3
@@ -82,6 +83,27 @@ def format_utc_timestamp_for_lookback_hours(lookback_hours: float) -> str:
8283
return cutoff.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
8384

8485

86+
def _extract_unit_api_error(response: Response | None) -> str | None:
87+
if response is None:
88+
return None
89+
body = response.content
90+
if not body:
91+
return None
92+
content_type = response.headers.get("Content-Type", "")
93+
if "application/json" not in content_type:
94+
return None
95+
try:
96+
payload = json.loads(body)
97+
except Exception:
98+
return None
99+
if isinstance(payload, dict):
100+
for key in ("error", "message", "description"):
101+
value = payload.get(key)
102+
if isinstance(value, str) and value.strip():
103+
return value.strip()
104+
return None
105+
106+
85107
def broadcast_get_across_cluster(endpoint: str, timeout: float = 5.0, return_raw=False) -> Result:
86108
assert endpoint.startswith("/unit_api")
87109
return tasks.multicast_get(
@@ -1526,6 +1548,7 @@ def start_calibration_session(pioreactor_unit: str) -> ResponseReturnValue:
15261548
if body is None:
15271549
abort_with(400, description="Missing JSON payload.")
15281550

1551+
response: Response | None = None
15291552
try:
15301553
response = post_into(
15311554
resolve_to_address(pioreactor_unit),
@@ -1535,8 +1558,17 @@ def start_calibration_session(pioreactor_unit: str) -> ResponseReturnValue:
15351558
)
15361559
response.raise_for_status()
15371560
except (HTTPErrorStatus, HTTPException) as exc:
1561+
detail = _extract_unit_api_error(response)
1562+
if detail:
1563+
publish_to_error_log(f"{exc}: {detail}", "start_calibration_session")
1564+
abort_with(502, f"Starting calibration session failed on {pioreactor_unit}: {detail}")
1565+
if response is not None:
1566+
abort_with(
1567+
502,
1568+
f"Starting calibration session failed on {pioreactor_unit} (HTTP {response.status_code}).",
1569+
)
15381570
publish_to_error_log(str(exc), "start_calibration_session")
1539-
abort_with(502, f"Starting calibration session failed on {pioreactor_unit}. See system logs.")
1571+
abort_with(502, f"Starting calibration session failed on {pioreactor_unit}.")
15401572

15411573
return Response(
15421574
response.content,
@@ -1550,6 +1582,7 @@ def get_calibration_session(pioreactor_unit: str, session_id: str) -> ResponseRe
15501582
if pioreactor_unit == UNIVERSAL_IDENTIFIER:
15511583
abort_with(400, "Cannot fetch sessions with $broadcast; choose a specific Pioreactor.")
15521584

1585+
response: Response | None = None
15531586
try:
15541587
response = get_from(
15551588
resolve_to_address(pioreactor_unit),
@@ -1558,8 +1591,17 @@ def get_calibration_session(pioreactor_unit: str, session_id: str) -> ResponseRe
15581591
)
15591592
response.raise_for_status()
15601593
except (HTTPErrorStatus, HTTPException) as exc:
1594+
detail = _extract_unit_api_error(response)
1595+
if detail:
1596+
publish_to_error_log(f"{exc}: {detail}", "get_calibration_session")
1597+
abort_with(502, f"Fetching calibration session failed on {pioreactor_unit}: {detail}")
1598+
if response is not None:
1599+
abort_with(
1600+
502,
1601+
f"Fetching calibration session failed on {pioreactor_unit} (HTTP {response.status_code}).",
1602+
)
15611603
publish_to_error_log(str(exc), "get_calibration_session")
1562-
abort_with(502, f"Fetching calibration session failed on {pioreactor_unit}. See system logs.")
1604+
abort_with(502, f"Fetching calibration session failed on {pioreactor_unit}.")
15631605

15641606
return Response(
15651607
response.content,
@@ -1577,6 +1619,7 @@ def advance_calibration_session(pioreactor_unit: str, session_id: str) -> Respon
15771619
if body is None:
15781620
abort_with(400, description="Missing JSON payload.")
15791621

1622+
response: Response | None = None
15801623
try:
15811624
response = post_into(
15821625
resolve_to_address(pioreactor_unit),
@@ -1586,8 +1629,17 @@ def advance_calibration_session(pioreactor_unit: str, session_id: str) -> Respon
15861629
)
15871630
response.raise_for_status()
15881631
except (HTTPErrorStatus, HTTPException) as exc:
1632+
detail = _extract_unit_api_error(response)
1633+
if detail:
1634+
publish_to_error_log(f"{exc}: {detail}", "advance_calibration_session")
1635+
abort_with(502, detail)
1636+
if response is not None:
1637+
abort_with(
1638+
502,
1639+
f"Updating calibration session failed on {pioreactor_unit} (HTTP {response.status_code}).",
1640+
)
15891641
publish_to_error_log(str(exc), "advance_calibration_session")
1590-
abort_with(502, f"Updating calibration session failed on {pioreactor_unit}. See system logs.")
1642+
abort_with(502, f"Updating calibration session failed on {pioreactor_unit}.")
15911643

15921644
return Response(
15931645
response.content,
@@ -1601,6 +1653,7 @@ def abort_calibration_session(pioreactor_unit: str, session_id: str) -> Response
16011653
if pioreactor_unit == UNIVERSAL_IDENTIFIER:
16021654
abort_with(400, "Cannot abort sessions with $broadcast; choose a specific Pioreactor.")
16031655

1656+
response: Response | None = None
16041657
try:
16051658
response = post_into(
16061659
resolve_to_address(pioreactor_unit),
@@ -1609,8 +1662,17 @@ def abort_calibration_session(pioreactor_unit: str, session_id: str) -> Response
16091662
)
16101663
response.raise_for_status()
16111664
except (HTTPErrorStatus, HTTPException) as exc:
1665+
detail = _extract_unit_api_error(response)
1666+
if detail:
1667+
publish_to_error_log(f"{exc}: {detail}", "abort_calibration_session")
1668+
abort_with(502, f"Aborting calibration session failed on {pioreactor_unit}: {detail}")
1669+
if response is not None:
1670+
abort_with(
1671+
502,
1672+
f"Aborting calibration session failed on {pioreactor_unit} (HTTP {response.status_code}).",
1673+
)
16121674
publish_to_error_log(str(exc), "abort_calibration_session")
1613-
abort_with(502, f"Aborting calibration session failed on {pioreactor_unit}. See system logs.")
1675+
abort_with(502, f"Aborting calibration session failed on {pioreactor_unit}.")
16141676

16151677
return Response(
16161678
response.content,

core/pioreactor/web/static/asset-manifest.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"files": {
33
"main.css": "/static/static/css/main.9c7a48b7.css",
4-
"main.js": "/static/static/js/main.9a56352a.js",
4+
"main.js": "/static/static/js/main.19c0ffc4.js",
55
"static/media/pioreactor_cloud.webp": "/static/static/media/pioreactor_cloud.b15b29e435797dc69d76.webp",
66
"static/media/roboto-all-500-normal.woff": "/static/static/media/roboto-all-500-normal.0ab669b7a0d19b178f57.woff",
77
"static/media/roboto-all-700-normal.woff": "/static/static/media/roboto-all-700-normal.a457fde362a540fcadff.woff",
@@ -30,10 +30,10 @@
3030
"static/media/roboto-greek-ext-700-normal.woff2": "/static/static/media/roboto-greek-ext-700-normal.bd9854c751441ccc1a70.woff2",
3131
"index.html": "/static/index.html",
3232
"main.9c7a48b7.css.map": "/static/static/css/main.9c7a48b7.css.map",
33-
"main.9a56352a.js.map": "/static/static/js/main.9a56352a.js.map"
33+
"main.19c0ffc4.js.map": "/static/static/js/main.19c0ffc4.js.map"
3434
},
3535
"entrypoints": [
3636
"static/css/main.9c7a48b7.css",
37-
"static/js/main.9a56352a.js"
37+
"static/js/main.19c0ffc4.js"
3838
]
3939
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/static/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Pioreactor"/><link rel="apple-touch-icon" href="/static/logo192.png"/><link rel="manifest" href="/static/manifest.json"/><script defer="defer" src="/static/static/js/main.9a56352a.js"></script><link href="/static/static/css/main.9c7a48b7.css" rel="stylesheet"></head><body><div id="root"></div></body></html>
1+
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/static/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Pioreactor"/><link rel="apple-touch-icon" href="/static/logo192.png"/><link rel="manifest" href="/static/manifest.json"/><script defer="defer" src="/static/static/js/main.19c0ffc4.js"></script><link href="/static/static/css/main.9c7a48b7.css" rel="stylesheet"></head><body><div id="root"></div></body></html>

core/pioreactor/web/static/static/js/main.9a56352a.js renamed to core/pioreactor/web/static/static/js/main.19c0ffc4.js

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/pioreactor/web/static/static/js/main.9a56352a.js.LICENSE.txt renamed to core/pioreactor/web/static/static/js/main.19c0ffc4.js.LICENSE.txt

File renamed without changes.

core/pioreactor/web/static/static/js/main.9a56352a.js.map renamed to core/pioreactor/web/static/static/js/main.19c0ffc4.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/pioreactor/web/unit_api.py

Lines changed: 14 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from pathlib import Path
99
from tempfile import NamedTemporaryFile
1010
from time import sleep
11+
from typing import Any
1112

1213
from flask import after_this_request
1314
from flask import Blueprint
@@ -67,63 +68,17 @@
6768
unit_api_bp = Blueprint("unit_api", __name__, url_prefix="/unit_api")
6869

6970

70-
def _float_from_payload(payload: dict[str, object], key: str) -> float:
71-
value = payload.get(key)
72-
if isinstance(value, (int, float)):
73-
return float(value)
74-
if isinstance(value, str):
75-
return float(value)
76-
raise ValueError(f"Missing or invalid '{key}'.")
77-
78-
79-
def _optional_float_from_payload(payload: dict[str, object], key: str) -> float | None:
80-
value = payload.get(key)
81-
if value is None:
82-
return None
83-
if isinstance(value, (int, float)):
84-
return float(value)
85-
if isinstance(value, str):
86-
return float(value)
87-
raise ValueError(f"Invalid '{key}'.")
88-
89-
90-
def _string_from_payload(payload: dict[str, object], key: str) -> str:
91-
value = payload.get(key)
92-
if isinstance(value, str):
93-
return value
94-
raise ValueError(f"Missing or invalid '{key}'.")
95-
96-
97-
def _string_dict_from_payload(payload: dict[str, object], key: str) -> dict[str, str]:
98-
value = payload.get(key)
99-
if not isinstance(value, dict):
100-
raise ValueError(f"Missing or invalid '{key}'.")
101-
typed: dict[str, str] = {}
102-
for item_key, item_value in value.items():
103-
if not isinstance(item_key, str) or not isinstance(item_value, str):
104-
raise ValueError(f"Invalid '{key}'.")
105-
typed[item_key] = item_value
106-
return typed
107-
108-
109-
def _object_dict_from_payload(payload: dict[str, object], key: str) -> dict[str, object]:
110-
value = payload.get(key)
111-
if not isinstance(value, dict):
112-
raise ValueError(f"Missing or invalid '{key}'.")
113-
return value
114-
115-
116-
def _execute_calibration_action(action: str, payload: dict[str, object]) -> dict[str, object]:
117-
def _raise_if_task_failed(result: object, message: str) -> None:
71+
def _execute_calibration_action(action: str, payload: dict[str, Any]) -> dict[str, Any]:
72+
def _raise_if_task_failed(result, message: str) -> None:
11873
if isinstance(result, Exception):
11974
raise ValueError(message)
12075

12176
if action == "pump":
12277
task = tasks.calibration_execute_pump(
123-
_string_from_payload(payload, "pump_device"),
124-
_float_from_payload(payload, "duration_s"),
125-
_float_from_payload(payload, "hz"),
126-
_float_from_payload(payload, "dc"),
78+
payload["pump_device"],
79+
float(payload["duration_s"]),
80+
float(payload["hz"]),
81+
float(payload["dc"]),
12782
)
12883
try:
12984
success = task(blocking=True, timeout=30)
@@ -136,8 +91,8 @@ def _raise_if_task_failed(result: object, message: str) -> None:
13691

13792
if action == "od_standards_measure":
13893
task = tasks.calibration_measure_standard(
139-
_float_from_payload(payload, "rpm"),
140-
_string_dict_from_payload(payload, "channel_angle_map"),
94+
float(payload["rpm"]),
95+
payload["channel_angle_map"],
14196
)
14297
try:
14398
voltages = task(blocking=True, timeout=30)
@@ -147,7 +102,7 @@ def _raise_if_task_failed(result: object, message: str) -> None:
147102
return {"voltages": voltages}
148103

149104
if action == "od_reference_standard_read":
150-
task = tasks.calibration_reference_standard_read(_float_from_payload(payload, "ir_led_intensity"))
105+
task = tasks.calibration_reference_standard_read(float(payload["ir_led_intensity"]))
151106
try:
152107
readings = task(blocking=True, timeout=300)
153108
except HueyException as exc:
@@ -157,8 +112,8 @@ def _raise_if_task_failed(result: object, message: str) -> None:
157112

158113
if action == "stirring_calibration":
159114
task = tasks.calibration_run_stirring(
160-
_optional_float_from_payload(payload, "min_dc"),
161-
_optional_float_from_payload(payload, "max_dc"),
115+
float(payload["min_dc"]) if "min_dc" in payload else None,
116+
float(payload["max_dc"]) if "max_dc" in payload else None,
162117
)
163118
try:
164119
calibration_payload = task(blocking=True, timeout=300)
@@ -178,8 +133,8 @@ def _raise_if_task_failed(result: object, message: str) -> None:
178133

179134
if action == "save_calibration":
180135
task = tasks.calibration_save_calibration(
181-
_string_from_payload(payload, "device"),
182-
_object_dict_from_payload(payload, "calibration"),
136+
payload["device"],
137+
payload["calibration"],
183138
)
184139
try:
185140
result = task(blocking=True, timeout=30)
@@ -1226,9 +1181,3 @@ def remove_active_status_calibration(device: str) -> ResponseReturnValue:
12261181
c.pop(device)
12271182

12281183
return {"status": "success"}, 200
1229-
1230-
1231-
@unit_api_bp.errorhandler(404)
1232-
def not_found(e):
1233-
# Return JSON for API requests, using the error description
1234-
return jsonify({"error": e.description}), 404

0 commit comments

Comments
 (0)