diff --git a/frontend/build/static/sample_media/bar_chart.png b/frontend/build/static/sample_media/bar_chart.png deleted file mode 100644 index 6a6a72b9c..000000000 Binary files a/frontend/build/static/sample_media/bar_chart.png and /dev/null differ diff --git a/frontend/build/static/sample_media/bar_chart_1.png b/frontend/build/static/sample_media/bar_chart_1.png new file mode 100644 index 000000000..33843027b Binary files /dev/null and b/frontend/build/static/sample_media/bar_chart_1.png differ diff --git a/frontend/build/static/sample_media/bar_chart_1_thumb.png b/frontend/build/static/sample_media/bar_chart_1_thumb.png new file mode 100644 index 000000000..3e4a3cdde Binary files /dev/null and b/frontend/build/static/sample_media/bar_chart_1_thumb.png differ diff --git a/frontend/build/static/sample_media/bar_chart_2.png b/frontend/build/static/sample_media/bar_chart_2.png new file mode 100644 index 000000000..aa02c144e Binary files /dev/null and b/frontend/build/static/sample_media/bar_chart_2.png differ diff --git a/frontend/build/static/sample_media/bar_chart_2_thumb.png b/frontend/build/static/sample_media/bar_chart_2_thumb.png new file mode 100644 index 000000000..acc58d050 Binary files /dev/null and b/frontend/build/static/sample_media/bar_chart_2_thumb.png differ diff --git a/frontend/build/static/sample_media/bar_chart_3.png b/frontend/build/static/sample_media/bar_chart_3.png new file mode 100644 index 000000000..cb48f6d84 Binary files /dev/null and b/frontend/build/static/sample_media/bar_chart_3.png differ diff --git a/frontend/build/static/sample_media/bar_chart_3_thumb.png b/frontend/build/static/sample_media/bar_chart_3_thumb.png new file mode 100644 index 000000000..f071e4e57 Binary files /dev/null and b/frontend/build/static/sample_media/bar_chart_3_thumb.png differ diff --git a/frontend/build/static/sample_media/pie_chart.png b/frontend/build/static/sample_media/pie_chart_thumb.png similarity index 100% rename from frontend/build/static/sample_media/pie_chart.png rename to frontend/build/static/sample_media/pie_chart_thumb.png diff --git a/frontend/build/static/sample_media/pixel_image.png b/frontend/build/static/sample_media/pixel_image.png index f2fbc62c1..309749fc8 100644 Binary files a/frontend/build/static/sample_media/pixel_image.png and b/frontend/build/static/sample_media/pixel_image.png differ diff --git a/frontend/build/static/sample_media/pixel_image_thumb.png b/frontend/build/static/sample_media/pixel_image_thumb.png new file mode 100644 index 000000000..2c10996fe Binary files /dev/null and b/frontend/build/static/sample_media/pixel_image_thumb.png differ diff --git a/frontend/src/components/Database/DatabaseCells.tsx b/frontend/src/components/Database/DatabaseCells.tsx index bc62aa494..229a905ad 100644 --- a/frontend/src/components/Database/DatabaseCells.tsx +++ b/frontend/src/components/Database/DatabaseCells.tsx @@ -1,4 +1,4 @@ -import { Box, Pagination, styled } from '@mui/material' +import { Box, Input, Pagination, styled } from '@mui/material' import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react' import { useSearchParams } from 'react-router-dom' import DialogImage from '../common/DialogImage' @@ -12,6 +12,7 @@ import { DataGridPro } from '@mui/x-data-grid-pro' import { DatabaseType, DATABASE_SLICE_NAME, + ImageUrls, } from '../../store/slice/Database/DatabaseType' import { useSelector, useDispatch } from 'react-redux' import { RootState } from '../../store/store' @@ -26,7 +27,37 @@ type CellProps = { user?: Object } -const columns = (handleOpenDialog: (value: string[]) => void) => [ +let timeout: NodeJS.Timeout | undefined = undefined + +const columns = (handleOpenDialog: (value: ImageUrls[], expId?: string) => void) => [ + { + field: 'experiment_id', + headerName: 'Experiment ID', + filterOperators: [ + { + label: 'Contains', value: 'contains', + InputComponent: ({applyValue, item}: any) => { + return { + if(timeout) clearTimeout(timeout) + timeout = setTimeout(() => { + applyValue({...item, value: e.target.value}) + }, 300) + } + } /> + } + }, + ], + type: "string", + width: 160, + renderCell: (params: { row: DatabaseType }) => params.row?.experiment_id, + }, + { + field: 'id', + headerName: 'Cell ID', + width: 160, + filterable: false, + renderCell: (params: { value: number }) => params.value, + }, { field: 'brain_area', headerName: 'Brain area', @@ -74,7 +105,7 @@ const columns = (handleOpenDialog: (value: string[]) => void) => [ onClick={() => handleOpenDialog([cell_image_url])} > {''} { const [dataDialog, setDataDialog] = useState<{ type: string - data: string | string[] | undefined + data?: string | string[] + expId?: string + nameCol?: string }>({ type: '', data: undefined, }) + const [searchParams, setParams] = useSearchParams() const dispatch = useDispatch() - const pagiFilter = useMemo(() => { - return `limit=${dataExperiments.limit}&offset=${dataExperiments.offset}` - }, [dataExperiments.limit, dataExperiments.offset]) + const pagiFilter = useCallback( + (page?: number) => { + return `limit=${dataExperiments.limit}&offset=${ + page ? page - 1 : dataExperiments.offset + }` + }, + [dataExperiments.limit, dataExperiments.offset], + ) - const exp_id = searchParams.get('exp_id') + const id = searchParams.get('id') const offset = searchParams.get('offset') const limit = searchParams.get('limit') - const sort = searchParams.get('sort') + const sort = searchParams.getAll('sort') const dataParams = useMemo(() => { return { - exp_id: Number(exp_id) || undefined, - offset: Number(offset) || 0, - limit: Number(limit) || 50, + exp_id: Number(id) || undefined, sort: sort || [], + limit: Number(limit) || 50, + offset: Number(offset) || 0, } - }, [offset, limit, sort, exp_id]) + //eslint-disable-next-line + }, [offset, limit, JSON.stringify(sort), id]) const dataParamsFilter = useMemo( () => ({ + experiment_id: searchParams.get('experiment_id') || undefined, brain_area: searchParams.get('brain_area') || undefined, cre_driver: searchParams.get('cre_driver') || undefined, reporter_line: searchParams.get('reporter_line') || undefined, @@ -143,8 +184,12 @@ const DatabaseCells = ({ user }: CellProps) => { //eslint-disable-next-line }, [dataParams, user, dataParamsFilter]) - const handleOpenDialog = (data: string[]) => { - setDataDialog({ type: 'image', data }) + const handleOpenDialog = (data: ImageUrls[] | ImageUrls, expId?: string, graphTitle?: string) => { + let newData: string | (string[]) = [] + if(Array.isArray(data)) { + newData = data.map(d => d.url); + } else newData = data.url + setDataDialog({ type: 'image', data: newData, expId: expId, nameCol: graphTitle }) } const handleCloseDialog = () => { @@ -162,7 +207,7 @@ const DatabaseCells = ({ user }: CellProps) => { const handlePage = (e: ChangeEvent, page: number) => { const filter = getParamsData() setParams( - `${filter}&sort=${dataParams.sort[0]}&sort=${dataParams.sort[1]}&${pagiFilter}`, + `${filter}&sort=${dataParams.sort[0]}&sort=${dataParams.sort[1]}&${pagiFilter(page)}`, ) } @@ -170,11 +215,11 @@ const DatabaseCells = ({ user }: CellProps) => { (rowSelectionModel: GridSortModel) => { const filter = getParamsData() if (!rowSelectionModel[0]) { - setParams(`${filter}&sort=&sort=&${pagiFilter}`) + setParams(`${filter}&sort=&sort=&${pagiFilter()}`) return } setParams( - `${filter}&sort=${rowSelectionModel[0].field}&sort=${rowSelectionModel[0].sort}&${pagiFilter}`, + `${filter}&sort=${rowSelectionModel[0].field}&sort=${rowSelectionModel[0].sort}&${pagiFilter()}`, ) }, //eslint-disable-next-line @@ -193,7 +238,7 @@ const DatabaseCells = ({ user }: CellProps) => { } const { sort } = dataParams setParams( - `${filter}&sort=${sort[0] || ''}&sort=${sort[1] || ''}&${pagiFilter}`, + `${filter}&sort=${sort[0] || ''}&sort=${sort[1] || ''}&${pagiFilter()}`, ) } @@ -205,16 +250,21 @@ const DatabaseCells = ({ user }: CellProps) => { filterable: false, sortable: false, renderCell: (params: { row: DatabaseType }) => { + const {row} = params + const {graph_urls} = row + const graph_url = graph_urls[index] + if(!graph_url) return null return ( - - {params.row.graph_urls?.[index]?.[0] ? ( - {''} - ) : null} + handleOpenDialog(graph_url, params.row.experiment_id, graphTitle)} + > + {''} ) }, @@ -248,6 +298,11 @@ const DatabaseCells = ({ user }: CellProps) => { filter: { filterModel: { items: [ + { + field: 'experiment_id', + operator: 'contains', + value: dataParamsFilter.experiment_id, + }, { field: 'brain_area', operator: 'contains', @@ -283,6 +338,8 @@ const DatabaseCells = ({ user }: CellProps) => { {loading ? : null} diff --git a/frontend/src/components/Database/DatabaseExperiments.tsx b/frontend/src/components/Database/DatabaseExperiments.tsx index 88dded77d..21b3ea247 100644 --- a/frontend/src/components/Database/DatabaseExperiments.tsx +++ b/frontend/src/components/Database/DatabaseExperiments.tsx @@ -1,13 +1,14 @@ -import { Box, Pagination, styled } from '@mui/material' +import { Box, DialogTitle, FormControl, FormControlLabel, Input, Pagination, Radio, RadioGroup, styled } from '@mui/material' import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react' import { useNavigate, useSearchParams } from 'react-router-dom' -import DialogImage from '../common/DialogImage' -import LaunchIcon from '@mui/icons-material/Launch' +import ContentPasteSearchIcon from '@mui/icons-material/ContentPasteSearch'; import Button from '@mui/material/Button' import Dialog from '@mui/material/Dialog' import DialogActions from '@mui/material/DialogActions' import DialogContent from '@mui/material/DialogContent' import DialogContentText from '@mui/material/DialogContentText' +import { DataGrid, GridRenderCellParams, GridRowParams } from '@mui/x-data-grid' +import DialogImage from '../common/DialogImage' import SwitchCustom from '../common/SwitchCustom' import { GridEnrichedColDef, @@ -20,6 +21,7 @@ import GroupsIcon from '@mui/icons-material/Groups' import { DatabaseType, DATABASE_SLICE_NAME, + ImageUrls, } from '../../store/slice/Database/DatabaseType' import { useSelector, useDispatch } from 'react-redux' import { RootState } from '../../store/store' @@ -30,13 +32,38 @@ import { } from '../../store/slice/Database/DatabaseActions' import Loading from 'components/common/Loading' import { TypeData } from 'store/slice/Database/DatabaseSlice' +import CancelIcon from '@mui/icons-material/Cancel' + +export type Data = { + id: number + fields: { + brain_area: string + cre_driver: string + reporter_line: string + imaging_depth: number + } + experiment_id: string + attributes: string + cell_image_urls: string[] + graph_urls: string[] + share_type: number + publish_status: number + created_time: string + updated_time: string +} type PopupAttributesProps = { - data: string + data?: string | (string[]) open: boolean handleClose: () => void role?: boolean handleChangeAttributes: (e: any) => void + exp_id?: string +} + +type PopupType = { + open: boolean + handleClose: () => void } type DatabaseProps = { @@ -44,21 +71,33 @@ type DatabaseProps = { cellPath: string } +let timeout: NodeJS.Timeout | undefined = undefined + const columns = ( handleOpenAttributes: (value: string) => void, - handleOpenDialog: (value: string[]) => void, + handleOpenDialog: (value: ImageUrls[], exp_id?: string) => void, cellPath: string, - navigate: ( - path: string, - params: { [key: string]: string | undefined }, - ) => void, + navigate: (path: string) => void, ) => [ { field: 'experiment_id', headerName: 'Experiment ID', width: 160, - filterable: false, - sort: 'asc', + filterOperators: [ + { + label: 'Contains', value: 'contains', + InputComponent: ({applyValue, item}: any) => { + return { + if(timeout) clearTimeout(timeout) + timeout = setTimeout(() => { + applyValue({...item, value: e.target.value}) + }, 300) + } + } /> + } + }, + ], + type: "string", renderCell: (params: { row: DatabaseType }) => params.row?.experiment_id, }, { @@ -116,12 +155,12 @@ const columns = ( sortable: false, renderCell: (params: { row: DatabaseType }) => ( - navigate(cellPath, { exp_id: params.row?.experiment_id }) + navigate(`${cellPath}?experiment_id=${params.row?.experiment_id}` ) } > - + ), }, @@ -131,33 +170,175 @@ const columns = ( width: 160, filterable: false, sortable: false, - renderCell: (params: { row: DatabaseType }) => ( - handleOpenDialog(params.row?.cell_image_urls)} - > - {params.row?.cell_image_urls?.length > 0 && ( - {''} - )} - + renderCell: (params: { row: DatabaseType }) => { + return ( + handleOpenDialog(params.row?.cell_image_urls)} + > + {params.row?.cell_image_urls?.length > 0 && ( + {''} + )} + + ) + }, + }, +] + +const columnsShare = (handleShareFalse: (parmas: GridRenderCellParams) => void) => [ + { + field: "name", + headerName: "Name", + minWidth: 140, + renderCell: (params: GridRenderCellParams) => ( + {params.row.name} + ), + }, + { + field: "lab", + headerName: "Lab", + minWidth: 280, + renderCell: (params: GridRenderCellParams) => ( + {params.row.email} + ), + }, + { + field: "email", + headerName: "Email", + minWidth: 280, + renderCell: (params: GridRenderCellParams) => ( + {params.row.email} ), }, + { + field: "share", + headerName: "", + minWidth: 130, + renderCell: (params: GridRenderCellParams) => { + if(!params.row.share) return "" + return ( + + ) + } + }, +] + +const dataShare = [ + { + id: 1, + name: "User 1", + lab: "Labxxxx", + email: "aaaaa@gmail.com", + share: false + }, + { + id: 2, + name: "User 2", + lab: "Labxxxx", + email: "aaaaa@gmail.com", + share: true + }, + { + id: 3, + name: "User 3", + lab: "Labxxxx", + email: "aaaaa@gmail.com", + share: true + } ] +const PopupShare = ({open, handleClose}: PopupType) => { + const [value, setValue] = useState("Organization") + const [tableShare, setTableShare] = useState(dataShare) + + const handleShareTrue = (params: GridRowParams) => { + if(params.row.share) return + const index = tableShare.findIndex(item => item.id === params.id) + setTableShare(pre => { + pre[index].share = true + return pre + }) + } + + const handleShareFalse = (params: GridRenderCellParams) => { + const indexSearch = tableShare.findIndex(item => item.id === params.id) + const newData = tableShare.map((item, index) => { + if(index === indexSearch) return {...item, share: false} + return item + }) + setTableShare(newData) + } + + const handleValue = (event: ChangeEvent) => { + setValue((event.target as HTMLInputElement).value); + } + + if(!open) return null; + + return ( + + + Share Database record + Experiment ID: XXXXXX + + + + } label={"Share for Organization"} /> + } label={"Share for Users"} /> + + + + + { + value !== "Organization" ? + <> +

Permitted users

+ + + : null + } +
+ + + + +
+
+ ) +} + const PopupAttributes = ({ data, open, handleClose, role = false, handleChangeAttributes, + exp_id }: PopupAttributesProps) => { return ( @@ -185,26 +366,30 @@ const PopupAttributes = ({ ) } - const DatabaseExperiments = ({ user, cellPath }: DatabaseProps) => { - const type: keyof TypeData = user ? 'private' : 'public' - const { data: dataExperiments, loading } = useSelector( - (state: RootState) => ({ - data: state[DATABASE_SLICE_NAME].data[type], - loading: state[DATABASE_SLICE_NAME].loading, - }), - ) + const [openShare, setOpenShare] = useState(false) const [dataDialog, setDataDialog] = useState<{ type: string - data: string | string[] | undefined + data?: string | string[] + expId?: string + nameCol?: string }>({ type: '', data: undefined, }) + const [searchParams, setParams] = useSearchParams() - const navigate = useNavigate() const dispatch = useDispatch() + const navigate = useNavigate() + + const type: keyof TypeData = user ? 'private' : 'public' + const { data: dataExperiments, loading } = useSelector( + (state: RootState) => ({ + data: state[DATABASE_SLICE_NAME].data[type], + loading: state[DATABASE_SLICE_NAME].loading, + }), + ) const pagiFilter = useCallback( (page?: number) => { @@ -230,6 +415,7 @@ const DatabaseExperiments = ({ user, cellPath }: DatabaseProps) => { const dataParamsFilter = useMemo( () => ({ + experiment_id: searchParams.get('experiment_id') || undefined, brain_area: searchParams.get('brain_area') || undefined, cre_driver: searchParams.get('cre_driver') || undefined, reporter_line: searchParams.get('reporter_line') || undefined, @@ -248,8 +434,12 @@ const DatabaseExperiments = ({ user, cellPath }: DatabaseProps) => { //eslint-disable-next-line }, [dataParams, user, dataParamsFilter]) - const handleOpenDialog = (data: string[]) => { - setDataDialog({ type: 'image', data }) + const handleOpenDialog = (data: ImageUrls[] | ImageUrls, expId?: string, graphTitle?: string) => { + let newData: string | (string[]) = [] + if(Array.isArray(data)) { + newData = data.map(d => d.url); + } else newData = data.url + setDataDialog({ type: 'image', data: newData, expId: expId, nameCol: graphTitle }) } const handleCloseDialog = () => { @@ -257,13 +447,17 @@ const DatabaseExperiments = ({ user, cellPath }: DatabaseProps) => { } const handleOpenAttributes = (data: string) => { - setDataDialog({ type: 'attribute', data }) + setDataDialog({ type: 'attribute', data}) } const handleChangeAttributes = (event: any) => { setDataDialog((pre) => ({ ...pre, data: event.target.value })) } + const handleOpenShare = () => { + setOpenShare(true) + } + const getParamsData = () => { const dataFilter = Object.keys(dataParamsFilter) .filter((key) => (dataParamsFilter as any)[key]) @@ -328,16 +522,21 @@ const DatabaseExperiments = ({ user, cellPath }: DatabaseProps) => { sortable: false, filterable: false, renderCell: (params: { row: DatabaseType }) => { + const {row} = params + const {graph_urls} = row + const graph_url = graph_urls[index] + if(!graph_url) return null return ( - - {params.row.graph_urls[index] ? ( - {''} - ) : null} + handleOpenDialog(graph_url, row.experiment_id, graphTitle)} + > + {''} ) }, @@ -355,7 +554,7 @@ const DatabaseExperiments = ({ user, cellPath }: DatabaseProps) => { sortable: false, filterable: false, renderCell: (params: { row: DatabaseType }) => ( - + ), @@ -395,8 +594,7 @@ const DatabaseExperiments = ({ user, cellPath }: DatabaseProps) => { columns={ user ? ([...columnsTable, ...ColumnPrivate] as any) - : (columnsTable as any) - } + : (columnsTable as any)} rows={dataExperiments?.items || []} hideFooter={true} filterMode={'server'} @@ -414,6 +612,11 @@ const DatabaseExperiments = ({ user, cellPath }: DatabaseProps) => { filter: { filterModel: { items: [ + { + field: 'experiment_id', + operator: 'contains', + value: dataParamsFilter.experiment_id, + }, { field: 'brain_area', operator: 'is', @@ -449,28 +652,43 @@ const DatabaseExperiments = ({ user, cellPath }: DatabaseProps) => { {loading ? : null} + setOpenShare(false)} + /> ) } -const DatabaseExperimentsWrapper = styled(Box)(({ theme }) => ({ +const DatabaseExperimentsWrapper = styled(Box)(() => ({ width: '100%', height: 'calc(100vh - 250px)', })) -const Content = styled('textarea')(({ theme }) => ({ +const Content = styled('textarea')(() => ({ width: 400, height: 'fit-content', })) +const DialogCustom = styled(Dialog)(({ theme }) => ({ + "& .MuiDialog-container": { + "& .MuiPaper-root": { + width: "70%", + maxWidth: "890px", + }, + }, +})) + export default DatabaseExperiments diff --git a/frontend/src/components/common/DialogImage.tsx b/frontend/src/components/common/DialogImage.tsx index becca2577..ef0bbce17 100644 --- a/frontend/src/components/common/DialogImage.tsx +++ b/frontend/src/components/common/DialogImage.tsx @@ -3,12 +3,14 @@ import {Box, styled} from "@mui/material"; type DialogImageProps = { open: boolean - data?: string[] | string + data?: string | (string[]) handleCloseDialog: () => void + expId?: string + nameCol?: string } -const DialogImage = ({data, handleCloseDialog, open}: DialogImageProps) => { - if(!data) return <> +const DialogImage = ({data, handleCloseDialog, open, expId, nameCol}: DialogImageProps) => { + if(!data) return null return ( <> { @@ -25,11 +27,14 @@ const DialogImage = ({data, handleCloseDialog, open}: DialogImageProps) => { }} > { - typeof data === "string" ? + !Array.isArray(data) ? +

Expriment ID: {expId}

+

{nameCol}

+ {""} { Share Workspace - アクセス許可ユーザー + Permitted users Sequence: DUMMY_EXPERIMENTS_CELL_IMAGE_URLS = [ ImageInfo( url="http://localhost:8000/static/sample_media/pixel_image.png", - thumb_url="http://localhost:8000/static/sample_media/pixel_image.png", + thumb_url="http://localhost:8000/static/sample_media/pixel_image_thumb.png", ) for _ in range(5) ] @@ -106,20 +106,20 @@ def experiment_transformer(items: Sequence) -> Sequence: # TODO: set dummy data. DUMMY_EXPERIMENTS_GRAPH_URLS = [ ImageInfo( - url="http://localhost:8000/static/sample_media/bar_chart.png", - thumb_url="http://localhost:8000/static/sample_media/bar_chart.png", + url=f"http://localhost:8000/static/sample_media/bar_chart_{(_ % 3) + 1}.png", + thumb_url=f"http://localhost:8000/static/sample_media/bar_chart_{(_ % 3) + 1}.png", ) - for _ in DUMMY_EXPERIMENTS_GRAPH_TITLES + for _, __ in enumerate(DUMMY_EXPERIMENTS_GRAPH_TITLES) ] # TODO: set dummy data. DUMMY_CELLS_GRAPH_URLS = [ ImageInfo( - url="http://localhost:8000/static/sample_media/bar_chart.png", - thumb_url="http://localhost:8000/static/sample_media/bar_chart.png", + url=f"http://localhost:8000/static/sample_media/bar_chart_{(_ % 3) + 1}.png", + thumb_url=f"http://localhost:8000/static/sample_media/bar_chart_{(_ % 3) + 1}.png", params={"param1": 10, "param2": 20}, ) - for _ in DUMMY_CELLS_GRAPH_TITLES + for _, __ in enumerate(DUMMY_CELLS_GRAPH_TITLES) ] @@ -135,7 +135,7 @@ async def search_public_experiments( sortOptions: SortOptions = Depends(), db: Session = Depends(get_db), ): - sort_column = getattr(optinist_model.Experiment, sortOptions.sort[0] or "id") + sa_sort_list = sortOptions.get_sa_sort_list(sa_table=optinist_model.Experiment) # TODO: set dummy data. graph_titles = DUMMY_EXPERIMENTS_GRAPH_TITLES @@ -150,11 +150,7 @@ async def search_public_experiments( ) ) .group_by(optinist_model.Experiment.id) - .order_by( - sort_column.desc() - if sortOptions.sort[1] == SortDirection.desc - else sort_column.asc() - ), + .order_by(*sa_sort_list), transformer=experiment_transformer, additional_data={"header": ExpDbExperimentHeader(graph_titles=graph_titles)}, ) @@ -173,7 +169,10 @@ async def search_public_cells( sortOptions: SortOptions = Depends(), db: Session = Depends(get_db), ): - sort_column = getattr(optinist_model.Cell, sortOptions.sort[0] or "id") + sa_sort_list = sortOptions.get_sa_sort_list( + sa_table=optinist_model.Cell, + mapping={"experiment_id": optinist_model.Experiment.experiment_id}, + ) query = ( select(optinist_model.Cell, optinist_model.Experiment.experiment_id) .join( @@ -187,11 +186,7 @@ async def search_public_cells( ) ) ) - query = query.group_by(optinist_model.Cell.id).order_by( - sort_column.desc() - if sortOptions.sort[1] == SortDirection.desc - else sort_column.asc() - ) + query = query.group_by(optinist_model.Cell.id).order_by(*sa_sort_list) # TODO: set dummy data. graph_titles = DUMMY_CELLS_GRAPH_TITLES @@ -218,7 +213,7 @@ async def search_db_experiments( sortOptions: SortOptions = Depends(), current_user: User = Depends(get_current_user), ): - sort_column = getattr(optinist_model.Experiment, sortOptions.sort[0] or "id") + sa_sort_list = sortOptions.get_sa_sort_list(sa_table=optinist_model.Experiment) query = select(optinist_model.Experiment).join( common_model.Organization, optinist_model.Experiment.organization_id == common_model.Organization.id, @@ -252,11 +247,7 @@ async def search_db_experiments( ) ) .group_by(optinist_model.Experiment.id) - .order_by( - sort_column.desc() - if sortOptions.sort[1] == SortDirection.desc - else sort_column.asc() - ) + .order_by(*sa_sort_list) ) # TODO: set dummy data. @@ -284,7 +275,10 @@ async def search_db_cells( sortOptions: SortOptions = Depends(), current_user: User = Depends(get_current_user), ): - sort_column = getattr(optinist_model.Cell, sortOptions.sort[0] or "id") + sa_sort_list = sortOptions.get_sa_sort_list( + sa_table=optinist_model.Cell, + mapping={"experiment_id": optinist_model.Experiment.experiment_id}, + ) query = ( select(optinist_model.Cell, optinist_model.Experiment.experiment_id) .join( @@ -325,11 +319,7 @@ async def search_db_cells( ) ) .group_by(optinist_model.Cell.id) - .order_by( - sort_column.desc() - if sortOptions.sort[1] == SortDirection.desc - else sort_column.asc() - ) + .order_by(*sa_sort_list) ) # TODO: set dummy data. diff --git a/studio/app/optinist/schemas/base.py b/studio/app/optinist/schemas/base.py index 852abfb54..1100a545a 100644 --- a/studio/app/optinist/schemas/base.py +++ b/studio/app/optinist/schemas/base.py @@ -1,8 +1,9 @@ from enum import Enum -from typing import List +from typing import Dict, List, Union from fastapi import Query from pydantic import dataclasses +from sqlmodel.main import SQLModelMetaclass class SortDirection(str, Enum): @@ -16,3 +17,23 @@ class SortOptions: default=(None, SortDirection.asc), description="field-0: sort column
field-1: order ('asc' or 'desc')", ) + + def get_sa_sort_list( + self, sa_table, mapping: Dict[str, Union[str, SQLModelMetaclass]] = None + ) -> List: + sort_list = [] + for i in range(0, len(self.sort), 2): + sort_field, sort_type = self.sort[i] or "id", self.sort[i + 1] + + sort_column = ( + mapping.get(sort_field) or sort_field if mapping else sort_field + ) + if isinstance(sort_column, str): + sort_column = getattr(sa_table, sort_column) + + sort_list.append( + sort_column.desc() + if sort_type and sort_type == SortDirection.desc + else sort_column.asc() + ) + return sort_list