Add Image models and clean up test code structure
Introduced `ImageCreate` and `Image` models to handle image-related data in the backend. Improved the organization and readability of the testing notebook by consolidating and formatting code into distinct sections with markdown cells.
This commit is contained in:
parent
1606e80f81
commit
b04c7b8c95
@ -11,12 +11,14 @@ from app.schemas import (
|
||||
Sample,
|
||||
Image,
|
||||
ImageCreate,
|
||||
SampleResult,
|
||||
)
|
||||
from app.models import (
|
||||
Puck as PuckModel,
|
||||
Sample as SampleModel,
|
||||
SampleEvent as SampleEventModel,
|
||||
Image as ImageModel,
|
||||
Dewar as DewarModel,
|
||||
)
|
||||
from app.dependencies import get_db
|
||||
import logging
|
||||
@ -165,3 +167,40 @@ async def upload_sample_image(
|
||||
# Returning the mapped SQLAlchemy object, which will be converted to the
|
||||
# Pydantic response model.
|
||||
return new_image
|
||||
|
||||
|
||||
@router.get("/results", response_model=List[SampleResult])
|
||||
async def get_sample_results(active_pgroup: str, db: Session = Depends(get_db)):
|
||||
# Query samples for the active pgroup using joins
|
||||
samples = (
|
||||
db.query(SampleModel)
|
||||
.join(SampleModel.puck)
|
||||
.join(PuckModel.dewar)
|
||||
.filter(DewarModel.pgroups == active_pgroup)
|
||||
.all()
|
||||
)
|
||||
if not samples:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="No samples found for the active pgroup"
|
||||
)
|
||||
|
||||
results = []
|
||||
for sample in samples:
|
||||
# Query images associated with each sample
|
||||
images = db.query(ImageModel).filter(ImageModel.sample_id == sample.id).all()
|
||||
results.append(
|
||||
{
|
||||
"sample_id": sample.id,
|
||||
"sample_name": sample.sample_name,
|
||||
"puck_name": sample.puck.puck_name if sample.puck else None,
|
||||
"dewar_name": sample.puck.dewar.dewar_name
|
||||
if (sample.puck and sample.puck.dewar)
|
||||
else None,
|
||||
"images": [
|
||||
{"id": img.id, "filepath": img.filepath, "comment": img.comment}
|
||||
for img in images
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
return results
|
||||
|
@ -804,3 +804,17 @@ class Image(ImageCreate):
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ImageInfo(BaseModel):
|
||||
id: int
|
||||
filepath: str
|
||||
comment: Optional[str] = None
|
||||
|
||||
|
||||
class SampleResult(BaseModel):
|
||||
sample_id: int
|
||||
sample_name: str
|
||||
puck_name: Optional[str]
|
||||
dewar_name: Optional[str]
|
||||
images: List[ImageInfo]
|
||||
|
@ -4,6 +4,7 @@ import tomllib
|
||||
from pathlib import Path
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from app import ssl_heidi
|
||||
from app.routers import (
|
||||
proposal,
|
||||
@ -159,6 +160,9 @@ app.include_router(spreadsheet.router, tags=["spreadsheet"])
|
||||
app.include_router(logistics.router, prefix="/logistics", tags=["logistics"])
|
||||
app.include_router(sample.router, prefix="/samples", tags=["samples"])
|
||||
|
||||
app.mount("/images", StaticFiles(directory="images"), name="images")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
from dotenv import load_dotenv
|
||||
|
@ -84,7 +84,7 @@ const App: React.FC = () => {
|
||||
<Route path="/" element={<ProtectedRoute element={<HomePage />} />} />
|
||||
<Route path="/shipments" element={<ProtectedRoute element={<ShipmentView pgroups={pgroups} activePgroup={activePgroup} />} />} />
|
||||
<Route path="/planning" element={<ProtectedRoute element={<PlanningView />} />} />
|
||||
<Route path="/results" element={<ProtectedRoute element={<ResultsView />} />} />
|
||||
<Route path="/results" element={<ProtectedRoute element={<ResultsView pgroups={pgroups} activePgroup={activePgroup} />} />} />
|
||||
</Routes>
|
||||
<Modal open={openAddressManager} onClose={handleCloseAddressManager} title="Address Management">
|
||||
<AddressManager pgroups={pgroups} activePgroup={activePgroup} />
|
||||
|
114
frontend/src/components/ResultGrid.tsx
Normal file
114
frontend/src/components/ResultGrid.tsx
Normal file
@ -0,0 +1,114 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { DataGrid, GridColDef } from '@mui/x-data-grid';
|
||||
import { OpenAPI, SamplesService } from '../../openapi';
|
||||
|
||||
interface ImageInfo {
|
||||
id: number;
|
||||
filepath: string;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
interface SampleResult {
|
||||
sample_id: number;
|
||||
sample_name: string;
|
||||
puck_name?: string;
|
||||
dewar_name?: string;
|
||||
images: ImageInfo[];
|
||||
}
|
||||
|
||||
interface ResultGridProps {
|
||||
activePgroup: string;
|
||||
}
|
||||
|
||||
const ResultGrid: React.FC<ResultGridProps> = ({ activePgroup }) => {
|
||||
const [rows, setRows] = useState<SampleResult[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Fetching sample results for active_pgroup:", activePgroup);
|
||||
SamplesService.getSampleResultsSamplesResultsGet(activePgroup)
|
||||
.then((response: SampleResult[]) => {
|
||||
console.log("Response received:", response);
|
||||
setRows(response);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
console.error('Error fetching sample results:', err);
|
||||
});
|
||||
}, [activePgroup]);
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
{ field: 'sample_id', headerName: 'ID', width: 70 },
|
||||
{ field: 'sample_name', headerName: 'Sample Name', width: 150 },
|
||||
{ field: 'puck_name', headerName: 'Puck Name', width: 150 },
|
||||
{ field: 'dewar_name', headerName: 'Dewar Name', width: 150 },
|
||||
{
|
||||
field: 'images',
|
||||
headerName: 'Images',
|
||||
width: 300,
|
||||
renderCell: (params) => {
|
||||
const imageList: ImageInfo[] = params.value;
|
||||
if (imageList && imageList.length) {
|
||||
const primaryImage = imageList[0];
|
||||
// Define the base path to your backend images directory
|
||||
const basePath = "https://localhost:8000/";
|
||||
const imageUrl = basePath + primaryImage.filepath;
|
||||
|
||||
console.log("Local relative path:", imageUrl);
|
||||
|
||||
console.log("Updated image URL:", imageUrl);
|
||||
|
||||
return (
|
||||
<div style={{display: 'flex', alignItems: 'center', position: 'relative'}}>
|
||||
<img
|
||||
src={imageUrl}
|
||||
alt="sample"
|
||||
style={{width: 50, height: 50, marginRight: 5, borderRadius: 4}}
|
||||
/>
|
||||
{imageList.length > 1 && (
|
||||
<div className="tooltip" style={{position: 'relative', cursor: 'pointer'}}>
|
||||
<span>+{imageList.length - 1}</span>
|
||||
<div
|
||||
className="tooltip-content"
|
||||
style={{
|
||||
display: 'none',
|
||||
position: 'absolute',
|
||||
top: '60px',
|
||||
left: 0,
|
||||
background: '#fff',
|
||||
padding: '5px',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
|
||||
zIndex: 100,
|
||||
}}
|
||||
>
|
||||
{imageList.slice(1).map((img) => {
|
||||
const tooltipImageUrl = basePath + img.filepath;
|
||||
return (
|
||||
<img
|
||||
key={img.id}
|
||||
src={tooltipImageUrl}
|
||||
alt="sample"
|
||||
style={{width: 50, height: 50, margin: 2, borderRadius: 4}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Map each row so that DataGrid can use a unique "id" prop; here we use sample_id.
|
||||
const gridRows = rows.map((row) => ({ ...row, id: row.sample_id }));
|
||||
|
||||
return (
|
||||
<div style={{ height: 500, width: '100%' }}>
|
||||
<DataGrid rows={gridRows} columns={columns} pageSize={5} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResultGrid;
|
@ -50,7 +50,7 @@ const SampleTracker: React.FC = () => {
|
||||
// Set up polling every 1 second
|
||||
const interval = setInterval(() => {
|
||||
fetchPucks();
|
||||
}, 1000);
|
||||
}, 100000);
|
||||
|
||||
// Clear interval on component unmount
|
||||
return () => clearInterval(interval);
|
||||
|
@ -2,13 +2,22 @@
|
||||
|
||||
import React from 'react';
|
||||
import SampleTracker from '../components/SampleTracker';
|
||||
import ResultGrid from '../components/ResultGrid';
|
||||
|
||||
interface ResultsViewProps {
|
||||
activePgroup: string;
|
||||
}
|
||||
|
||||
const ResultsView: React.FC<ResultsViewProps> = ({activePgroup
|
||||
}) => {
|
||||
|
||||
const ResultsView: React.FC = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Results Page</h1>
|
||||
<SampleTracker />
|
||||
<ResultGrid activePgroup={activePgroup} />
|
||||
</div>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -3,8 +3,8 @@
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-02-26T12:02:38.993926Z",
|
||||
"start_time": "2025-02-26T12:02:38.991283Z"
|
||||
"end_time": "2025-02-26T13:09:58.719218Z",
|
||||
"start_time": "2025-02-26T13:09:58.716771Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
@ -46,7 +46,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 74
|
||||
"execution_count": 86
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
@ -528,8 +528,8 @@
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-02-26T12:29:51.615501Z",
|
||||
"start_time": "2025-02-26T12:29:51.592886Z"
|
||||
"end_time": "2025-02-26T13:17:13.591355Z",
|
||||
"start_time": "2025-02-26T13:17:13.561947Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
@ -546,7 +546,7 @@
|
||||
" mime_type = \"application/octet-stream\"\n",
|
||||
"\n",
|
||||
"# Sample ID (ensure this exists on your backend)\n",
|
||||
"sample_id = 58\n",
|
||||
"sample_id = 16\n",
|
||||
"\n",
|
||||
"# Build the URL for the upload endpoint.\n",
|
||||
"url = f\"https://127.0.0.1:8000/samples/{sample_id}/upload-images\"\n",
|
||||
@ -580,7 +580,7 @@
|
||||
"text": [
|
||||
"API Response:\n",
|
||||
"200\n",
|
||||
"{'pgroup': 'p20001, p20002', 'sample_id': 58, 'filepath': 'images/p20001, p20002/2025-02-26/Dewar One/PUCK007/12/IMG_1942.jpg', 'status': 'active', 'comment': None, 'id': 1}\n"
|
||||
"{'pgroup': 'p20001', 'sample_id': 16, 'filepath': 'images/p20001/2025-02-26/Dewar One/PUCK-001/16/IMG_1942.jpg', 'status': 'active', 'comment': None, 'id': 3}\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -592,7 +592,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 85
|
||||
"execution_count": 88
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
|
Loading…
x
Reference in New Issue
Block a user