Skip to content
Open
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
4 changes: 2 additions & 2 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ concurrency:
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 15
steps:
- name: Checkout repo
uses: actions/checkout@v6
Expand Down Expand Up @@ -74,6 +74,6 @@ jobs:
uses: actions/upload-artifact@v5
with:
name: playwright-screenshots-${{ github.run_id }}
path: tests_*.jpeg
path: tests_*.png
if-no-files-found: error
retention-days: 10
10 changes: 6 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changes

- updated template to https://github.com/robert-koch-institut/mex-template/commit/7c3e75
- harmonize mex-drop with mex-editor project setup and boilerplate
- update reflex to 0.7.3 along with fastapi, starlette and uvicorn

### Deprecated

Expand All @@ -25,7 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changes

- bumped cookiecutter template to https://github.com/robert-koch-institut/mex-template/commit/a67c71
- updated template to https://github.com/robert-koch-institut/mex-template/commit/a67c71

### Fixed

Expand All @@ -39,9 +41,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changes

- bumped cookiecutter template to https://github.com/robert-koch-institut/mex-template/commit/6009e2
- bumped cookiecutter template to https://github.com/robert-koch-institut/mex-template/commit/3c389d
- bumped cookiecutter template to https://github.com/robert-koch-institut/mex-template/commit/a287d7
- updated template to https://github.com/robert-koch-institut/mex-template/commit/6009e2
- updated template to https://github.com/robert-koch-institut/mex-template/commit/3c389d
- updated template to https://github.com/robert-koch-institut/mex-template/commit/a287d7

## [1.0.0] - 2025-09-11

Expand Down
15 changes: 14 additions & 1 deletion mex/drop/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
APIRouter,
Body,
Depends,
FastAPI,
File,
Header,
HTTPException,
Path,
Response,
UploadFile,
)
from fastapi.responses import PlainTextResponse
from starlette import status
from starlette.background import BackgroundTask, BackgroundTasks

Expand Down Expand Up @@ -120,7 +122,7 @@ async def drop_data(
status_code=200, background=BackgroundTask(write_to_file, data, out_file)
)
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
detail="Unsupported content type or format.",
)

Expand Down Expand Up @@ -330,6 +332,16 @@ def list_entity_types(
}


api = FastAPI(
title="mex-editor",
version="v0",
contact={"name": "MEx Team", "email": "[email protected]"},
description="Metadata editor web application.",
)
api.include_router(router)


@api.get("/_system/check", tags=["system"])
def check_system_status() -> VersionStatus:
"""Check that the drop server is healthy and responsive."""
return VersionStatus(status="ok", version=version("mex-drop"))
Expand Down Expand Up @@ -363,6 +375,7 @@ def get_subdirectory_stats(base_path: pathlib.Path) -> list[tuple[str, int, floa
return stats


@api.get("/_system/metrics", response_class=PlainTextResponse, tags=["system"])
def get_prometheus_metrics() -> str:
"""Get file system metrics for the drop directory."""
settings = DropSettings.get()
Expand Down
6 changes: 2 additions & 4 deletions mex/drop/file_history/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,8 @@ def index() -> rx.Component:
),
custom_attrs={"data-testid": "index-card"},
style=rx.Style(
{
"width": "100%",
"minHeight": "calc(480px * var(--scaling))",
}
width="100%",
minHeight="calc(480px * var(--scaling))",
),
),
)
9 changes: 3 additions & 6 deletions mex/drop/files_io.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import pathlib

from aiofile import async_open
from fastapi import (
HTTPException,
UploadFile,
)
from fastapi import HTTPException, UploadFile
from starlette import status

from mex.common.exceptions import MExError
Expand Down Expand Up @@ -44,7 +41,7 @@ async def validate_file_extension(content_type: str | None, filename: str) -> No
"""Validate uploaded file content type and extension."""
if content_type not in ALLOWED_CONTENT_TYPES:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
detail=(
f"Unsupported content type: {content_type}. "
f"Allowed types: {', '.join(ALLOWED_CONTENT_TYPES.values())}"
Expand All @@ -57,7 +54,7 @@ async def validate_file_extension(content_type: str | None, filename: str) -> No
"text/csv",
):
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
detail=f"Content type doesn't match extension: "
f"{content_type} != {filename}",
)
94 changes: 50 additions & 44 deletions mex/drop/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
def user_button() -> rx.Component:
"""Return a user button with an icon that indicates their access rights."""
return rx.button(
rx.icon(tag="user_round_cog"),
rx.icon("user_round_cog"),
variant="ghost",
style=rx.Style({"marginTop": "0"}),
style=rx.Style(marginTop="0"),
)


Expand All @@ -28,7 +28,9 @@ def user_menu() -> rx.Component:
rx.menu.item(
"Logout",
on_select=State.logout,
custom_attrs={"data-testid": "logout-button"},
),
align="end",
),
)

Expand All @@ -38,7 +40,7 @@ def nav_link(item: NavItem) -> rx.Component:
return rx.link(
rx.text(item.title, size="4", weight="medium"),
href=item.path,
underline=item.underline,
underline=item.underline, # type: ignore[arg-type]
class_name="nav-item",
)

Expand All @@ -48,14 +50,11 @@ def app_logo() -> rx.Component:
return rx.hover_card.root(
rx.hover_card.trigger(
rx.hstack(
rx.icon(
"droplets",
size=28,
),
rx.icon("droplets", size=28),
rx.heading(
"MEx Drop",
weight="medium",
style={"userSelect": "none"},
style=rx.Style(userSelect="none"),
),
custom_attrs={"data-testid": "app-logo"},
)
Expand All @@ -74,12 +73,9 @@ def nav_bar() -> rx.Component:
return rx.vstack(
rx.box(
style=rx.Style(
{
"height": "var(--space-6)",
"width": "100%",
"backdropFilter": " var(--backdrop-filter-panel)",
"backgroundColor": "var(--card-background-color)",
}
height="var(--space-6)",
width="100%",
backdropFilter="var(--backdrop-filter-panel)",
),
),
rx.card(
Expand All @@ -91,59 +87,69 @@ def nav_bar() -> rx.Component:
justify="start",
spacing="4",
),
rx.divider(orientation="vertical", size="2"),
user_menu(),
rx.spacer(),
rx.color_mode.button(),
rx.hstack(
user_menu(),
rx.button(
rx.icon("sun_moon"),
variant="ghost",
style=rx.Style(marginTop="0"),
on_click=rx.toggle_color_mode,
),
style=rx.Style(alignItems="center"),
spacing="4",
),
justify="between",
align_items="center",
),
size="2",
custom_attrs={"data-testid": "nav-bar"},
style=rx.Style(
{
"width": "100%",
"marginTop": "calc(-1 * var(--base-card-border-width))",
}
width="100%",
marginTop="calc(-1 * var(--base-card-border-width))",
),
),
spacing="0",
style=rx.Style(
{
"maxWidth": "calc(1480px * var(--scaling))",
"minWidth": "calc(800px * var(--scaling))",
"position": "fixed",
"top": "0",
"width": "100%",
"zIndex": "1000",
}
maxWidth="var(--app-max-width)",
minWidth="var(--app-min-width)",
position="fixed",
top="0",
width="100%",
zIndex="1000",
),
)


def page(*children: rx.Component) -> rx.Component:
"""Return a page fragment with navigation bar and given children."""
page_content = [
nav_bar(),
rx.hstack(
*children,
style=rx.Style(
maxWidth="var(--app-max-width)",
minWidth="var(--app-min-width)",
padding="calc(var(--space-6) * 4) var(--space-6) var(--space-6)",
width="100%",
),
custom_attrs={"data-testid": "page-body"},
),
]

return rx.cond(
State.user,
rx.center(
nav_bar(),
rx.hstack(
*children,
style=rx.Style(
{
"maxWidth": "calc(1480px * var(--scaling))",
"minWidth": "calc(800px * var(--scaling))",
"padding": (
"calc(var(--space-6) * 4) var(--space-6) var(--space-6)"
),
"width": "100%",
}
),
custom_attrs={"data-testid": "page-body"},
*page_content,
style=rx.Style(
{
"--app-max-width": "calc(1480px * var(--scaling))",
"--app-min-width": "calc(800px * var(--scaling))",
}
),
),
rx.center(
rx.spinner(size="3"),
style=rx.Style({"marginTop": "40vh"}),
style=rx.Style(marginTop="40vh"),
),
)
39 changes: 22 additions & 17 deletions mex/drop/login/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ def login_x_system() -> rx.Component:
placeholder="X-System",
size="3",
tab_index=1,
style={"width": "100%"},
style=rx.Style(width="100%"),
),
style={"width": "100%"},
style=rx.Style(width="100%"),
)


Expand All @@ -32,9 +32,9 @@ def login_api_key() -> rx.Component:
size="3",
tab_index=2,
type="password",
style={"width": "100%"},
style=rx.Style(width="100%"),
),
style={"width": "100%"},
style=rx.Style(width="100%"),
)


Expand All @@ -46,14 +46,14 @@ def login_button() -> rx.Component:
"Login",
size="3",
tab_index=3,
style={
"padding": "0 var(--space-6)",
"marginTop": "var(--space-4)",
},
style=rx.Style(
padding="0 var(--space-6)",
marginTop="var(--space-4)",
),
custom_attrs={"data-testid": "login-button"},
type="submit",
),
style={"width": "100%"},
style=rx.Style(width="100%"),
)


Expand All @@ -65,26 +65,31 @@ def index() -> rx.Component:
rx.hstack(
app_logo(),
rx.spacer(spacing="4"),
rx.color_mode.button(),
style={"width": "100%"},
rx.button(
rx.icon("sun_moon"),
variant="ghost",
style=rx.Style(marginTop="0"),
on_click=rx.toggle_color_mode,
),
style=rx.Style(width="100%"),
),
rx.divider(size="4"),
rx.form(
rx.vstack(
login_x_system(),
login_api_key(),
login_button(),
style={"width": "100%"},
style=rx.Style(width="100%"),
),
on_submit=LoginState.login,
spacing="4",
),
),
style={
"width": "calc(340px * var(--scaling))",
"padding": "var(--space-4)",
"top": "20vh",
},
style=rx.Style(
width="calc(340px * var(--scaling))",
padding="var(--space-4)",
top="20vh",
),
custom_attrs={"data-testid": "login-card"},
)
)
Loading