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
15 changes: 7 additions & 8 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import {
BrowserRouter,
Navigate,
Route,
Routes,
useParams,
} from "react-router-dom";
import { BrowserRouter, Route, Routes, useParams } from "react-router-dom";
import Typography from "@mui/material/Typography";
import { HistoryPage } from "./components/HistoryPage";
import { Layout } from "./components/Layout";
import { TaskPage } from "./components/TaskPage";
Expand All @@ -14,12 +9,16 @@ function TaskPageRoute() {
return <TaskPage key={taskId ?? ""} />;
}

function SelectTaskPage() {
return <Typography>Select task to continue.</Typography>;
}

export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Navigate to="/task/upload" replace />} />
<Route index element={<SelectTaskPage />} />
<Route path="history" element={<HistoryPage />} />
<Route path="task/:taskId" element={<TaskPageRoute />} />
</Route>
Expand Down
36 changes: 36 additions & 0 deletions frontend/src/components/FoldableObjectFieldTemplate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Accordion from "@mui/material/Accordion";
import AccordionDetails from "@mui/material/AccordionDetails";
import AccordionSummary from "@mui/material/AccordionSummary";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import type { ObjectFieldTemplateProps } from "@rjsf/utils";

export function FoldableObjectFieldTemplate(props: ObjectFieldTemplateProps) {
const isRoot = props.fieldPathId.$id === "root";
const title = props.title || "Section";

const content = (
<Box>
{props.description}
{props.properties.map((property) => (
<Box key={property.name} sx={{ mb: 2 }}>
{property.content}
</Box>
))}
</Box>
);

if (isRoot) {
return content;
}

return (
<Accordion disableGutters>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant="subtitle1">{title}</Typography>
</AccordionSummary>
<AccordionDetails>{content}</AccordionDetails>
</Accordion>
);
}
2 changes: 2 additions & 0 deletions frontend/src/components/TaskPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Box from "@mui/material/Box";
import CircularProgress from "@mui/material/CircularProgress";
import Typography from "@mui/material/Typography";
import { fetchTaskSchema, submitTask } from "../api";
import { FoldableObjectFieldTemplate } from "./FoldableObjectFieldTemplate";
import { ProgressView } from "./ProgressView";

export function TaskPage() {
Expand Down Expand Up @@ -84,6 +85,7 @@ export function TaskPage() {
schema={schema}
validator={validator}
formData={prefillData}
templates={{ ObjectFieldTemplate: FoldableObjectFieldTemplate }}
onSubmit={async ({ formData }) => {
setSubmitError(null);
try {
Expand Down
14 changes: 14 additions & 0 deletions uploader/forms/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,17 @@ def __new__(cls, *, default: TableType = "regular") -> FieldInfo:
title="Table type",
description="Type of data that table represents.",
)


class BibcodeField(BaseTextField):
base_description = "NASA ADS bibcode."
title = "Bibcode"

def __new__(cls, *, default: str = "", additional_description: str = "") -> FieldInfo:
description = cls.build_description(additional_description)
return Field(
default=default,
title=cls.title,
description=description,
pattern=r"^$|^\d{4}[A-Za-z0-9.&]{14}[A-Za-z0-9]$",
)
30 changes: 19 additions & 11 deletions uploader/forms/structured_designation.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,31 @@
from uploader.credentials import load_credentials


class StructuredDesignationForm(BaseModel):
class StructuredDesignationAdvancedSettings(BaseModel):
endpoint: Literal["dev", "test", "prod"] = Field(default="prod", title="API endpoint")
batch_size: int = Field(default=10000, title="Batch size", ge=1, le=500_000)
print_unmatched: bool = Field(
default=False,
title="Log unmatched names",
description="Append each unmatched name to the log stream.",
)


class StructuredDesignationForm(BaseModel):
table_name: str = Field(..., title="Name of the table")
column_name: str = Field(
...,
title="Object name column",
description="Name of the column that represents object designation in the table.",
)
batch_size: int = Field(default=10000, title="Batch size", ge=1, le=500_000)
write: bool = Field(
default=False,
title="Upload results?",
description="If enabled, upload results; otherwise dry-run (statistics only).",
)
print_unmatched: bool = Field(
default=False,
title="Log unmatched names",
description="Append each unmatched name to the log stream.",
advanced: StructuredDesignationAdvancedSettings = Field(
default_factory=StructuredDesignationAdvancedSettings,
title="Advanced settings",
)


Expand All @@ -39,13 +46,14 @@ def handle_structured_designation(
report_func: Callable[[report.Event], None],
) -> None:
f = cast(StructuredDesignationForm, form)
advanced = f.advanced
db_user, db_password = load_credentials()
dsn = db_dsn_map[f.endpoint].format(
dsn = db_dsn_map[advanced.endpoint].format(
user=quote_plus(db_user),
password=quote_plus(db_password),
)
client = adminapi.AuthenticatedClient(
base_url=env_map[f.endpoint],
base_url=env_map[advanced.endpoint],
token="fake",
)
with connect(dsn) as conn:
Expand All @@ -54,9 +62,9 @@ def handle_structured_designation(
storage,
f.table_name.strip(),
f.column_name.strip(),
f.batch_size,
advanced.batch_size,
client,
write=f.write,
print_unmatched=f.print_unmatched,
write=advanced.write,
print_unmatched=advanced.print_unmatched,
report_func=report_func,
)
20 changes: 14 additions & 6 deletions uploader/forms/structured_icrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,43 @@
from uploader.credentials import load_credentials


class StructuredIcrsForm(BaseModel):
class StructuredIcrsAdvancedSettings(BaseModel):
endpoint: Literal["dev", "test", "prod"] = Field(default="prod", title="API endpoint")
batch_size: int = Field(default=10000, title="Batch size", ge=1, le=500_000)


class StructuredIcrsForm(BaseModel):
table_name: str = Field(..., title="Rawdata table name")
ra_column: str = Field(..., title="RA column", description="Column containing right ascension.")
dec_column: str = Field(..., title="Dec column", description="Column containing declination.")
ra_error: float = Field(..., title="RA error", description="Positional error for RA (all rows).")
ra_error_unit: str = Field(..., title="RA error unit", description="e.g. arcsec")
dec_error: float = Field(..., title="Dec error", description="Positional error for Dec (all rows).")
dec_error_unit: str = Field(..., title="Dec error unit", description="e.g. arcsec")
batch_size: int = Field(default=10000, title="Batch size", ge=1, le=500_000)
write: bool = Field(
default=False,
title="Write to API",
description="If enabled, upload results; otherwise dry-run (statistics only).",
)
advanced: StructuredIcrsAdvancedSettings = Field(
default_factory=StructuredIcrsAdvancedSettings,
title="Advanced settings",
)


def handle_structured_icrs(
form: BaseModel,
report_func: Callable[[report.Event], None],
) -> None:
f = cast(StructuredIcrsForm, form)
advanced = f.advanced
db_user, db_password = load_credentials()
dsn = db_dsn_map[f.endpoint].format(
dsn = db_dsn_map[advanced.endpoint].format(
user=quote_plus(db_user),
password=quote_plus(db_password),
)
client = adminapi.AuthenticatedClient(
base_url=env_map[f.endpoint],
base_url=env_map[advanced.endpoint],
token="fake",
)
with connect(dsn) as conn:
Expand All @@ -51,9 +59,9 @@ def handle_structured_icrs(
f.table_name.strip(),
f.ra_column.strip(),
f.dec_column.strip(),
f.batch_size,
advanced.batch_size,
client,
write=f.write,
write=advanced.write,
ra_error=f.ra_error,
ra_error_unit=f.ra_error_unit.strip(),
dec_error=f.dec_error,
Expand Down
34 changes: 14 additions & 20 deletions uploader/forms/structured_nature.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from urllib.parse import quote_plus

from psycopg import connect
from pydantic import BaseModel, ConfigDict, Field
from pydantic import BaseModel, Field

import uploader.app.report as report
from uploader.app.endpoints import db_dsn_map, env_map
Expand All @@ -13,8 +13,12 @@
from uploader.credentials import load_credentials


class StructuredNatureForm(BaseModel):
class StructuredNatureAdvancedSettings(BaseModel):
endpoint: Literal["dev", "test", "prod"] = Field(default="prod", title="API endpoint")
batch_size: int = Field(default=10000, title="Batch size", ge=1, le=500_000)


class StructuredNatureForm(BaseModel):
table_name: str = Field(..., title="Rawdata table name")
column_name: str = Field(
default="",
Expand All @@ -31,26 +35,15 @@ class StructuredNatureForm(BaseModel):
title="Type mappings",
description='Each entry "raw_value:leda_type" (e.g. G:galaxy).',
)
batch_size: int = Field(default=10000, title="Batch size", ge=1, le=500_000)
write: bool = Field(
default=False,
title="Write to API",
description="If enabled, upload results; otherwise dry-run (statistics only).",
)

model_config = ConfigDict(
json_schema_extra={
"anyOf": [
{
"properties": {"column_name": {"type": "string", "minLength": 1}},
"required": ["column_name"],
},
{
"properties": {"default_type": {"type": "string", "minLength": 1}},
"required": ["default_type"],
},
],
},
advanced: StructuredNatureAdvancedSettings = Field(
default_factory=StructuredNatureAdvancedSettings,
title="Advanced settings",
)


Expand All @@ -74,19 +67,20 @@ def handle_structured_nature(
report_func: Callable[[report.Event], None],
) -> None:
f = cast(StructuredNatureForm, form)
advanced = f.advanced
col = f.column_name.strip() or None
default_t = f.default_type.strip() or None
if col is None and default_t is None:
raise ValueError("Either type column or default LEDA type (or both) must be set.")
type_mapping = _parse_type_mappings(f.type_mappings)

db_user, db_password = load_credentials()
dsn = db_dsn_map[f.endpoint].format(
dsn = db_dsn_map[advanced.endpoint].format(
user=quote_plus(db_user),
password=quote_plus(db_password),
)
client = adminapi.AuthenticatedClient(
base_url=env_map[f.endpoint],
base_url=env_map[advanced.endpoint],
token="fake",
)
with connect(dsn) as conn:
Expand All @@ -97,8 +91,8 @@ def handle_structured_nature(
col,
type_mapping,
default_t,
f.batch_size,
advanced.batch_size,
client,
write=f.write,
write=advanced.write,
report_func=report_func,
)
20 changes: 14 additions & 6 deletions uploader/forms/structured_photometry_hyperleda.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,46 @@
from uploader.credentials import load_credentials


class StructuredPhotometryHyperledaForm(BaseModel):
class StructuredPhotometryHyperledaAdvancedSettings(BaseModel):
endpoint: Literal["dev", "test", "prod"] = Field(default="prod", title="API endpoint")
table_name: str = Field(..., title="Rawdata table name")
batch_size: int = Field(default=10000, title="Batch size", ge=1, le=500_000)


class StructuredPhotometryHyperledaForm(BaseModel):
table_name: str = Field(..., title="Rawdata table name")
write: bool = Field(
default=False,
title="Write to API",
description="If enabled, upload results; otherwise dry-run (statistics only).",
)
advanced: StructuredPhotometryHyperledaAdvancedSettings = Field(
default_factory=StructuredPhotometryHyperledaAdvancedSettings,
title="Advanced settings",
)


def handle_structured_photometry_hyperleda(
form: BaseModel,
report_func: Callable[[report.Event], None],
) -> None:
f = cast(StructuredPhotometryHyperledaForm, form)
advanced = f.advanced
db_user, db_password = load_credentials()
dsn = db_dsn_map[f.endpoint].format(
dsn = db_dsn_map[advanced.endpoint].format(
user=quote_plus(db_user),
password=quote_plus(db_password),
)
client = adminapi.AuthenticatedClient(
base_url=env_map[f.endpoint],
base_url=env_map[advanced.endpoint],
token="fake",
)
with connect(dsn) as conn:
storage = PgStorage(conn)
run_upload_photometry_hyperleda(
storage,
f.table_name.strip(),
f.batch_size,
advanced.batch_size,
client,
write=f.write,
write=advanced.write,
report_func=report_func,
)
Loading
Loading