Skip to content

Commit

Permalink
feat: save results tables in the Django app
Browse files Browse the repository at this point in the history
Add a Results model to the Django app, and save results tables.
  • Loading branch information
eatyourgreens committed Nov 27, 2024
1 parent 66b6b46 commit f4c0e87
Show file tree
Hide file tree
Showing 15 changed files with 650 additions and 22 deletions.
172 changes: 172 additions & 0 deletions frontend-v2/src/app/backendApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,57 @@ const injectedRtkApi = api.injectEndpoints({
method: "DELETE",
}),
}),
resultsList: build.query<ResultsListApiResponse, ResultsListApiArg>({
query: (queryArg) => ({
url: `/api/results/`,
params: { project_id: queryArg.projectId },
}),
}),
resultsCreate: build.mutation<
ResultsCreateApiResponse,
ResultsCreateApiArg
>({
query: (queryArg) => ({
url: `/api/results/`,
method: "POST",
body: queryArg.results,
}),
}),
resultsRetrieve: build.query<
ResultsRetrieveApiResponse,
ResultsRetrieveApiArg
>({
query: (queryArg) => ({ url: `/api/results/${queryArg.id}/` }),
}),
resultsUpdate: build.mutation<
ResultsUpdateApiResponse,
ResultsUpdateApiArg
>({
query: (queryArg) => ({
url: `/api/results/${queryArg.id}/`,
method: "PUT",
body: queryArg.results,
}),
}),
resultsPartialUpdate: build.mutation<
ResultsPartialUpdateApiResponse,
ResultsPartialUpdateApiArg
>({
query: (queryArg) => ({
url: `/api/results/${queryArg.id}/`,
method: "PATCH",
body: queryArg.patchedResults,
}),
}),
resultsDestroy: build.mutation<
ResultsDestroyApiResponse,
ResultsDestroyApiArg
>({
query: (queryArg) => ({
url: `/api/results/${queryArg.id}/`,
method: "DELETE",
}),
}),
sessionRetrieve: build.query<
SessionRetrieveApiResponse,
SessionRetrieveApiArg
Expand Down Expand Up @@ -1569,6 +1620,37 @@ export type ProtocolDestroyApiArg = {
/** A unique integer value identifying this protocol. */
id: number;
};
export type ResultsListApiResponse = /** status 200 */ ResultsRead[];
export type ResultsListApiArg = {
/** Filter results by project ID */
projectId?: number;
};
export type ResultsCreateApiResponse = /** status 201 */ ResultsRead;
export type ResultsCreateApiArg = {
results: Results;
};
export type ResultsRetrieveApiResponse = /** status 200 */ ResultsRead;
export type ResultsRetrieveApiArg = {
/** A unique integer value identifying this results. */
id: number;
};
export type ResultsUpdateApiResponse = /** status 200 */ ResultsRead;
export type ResultsUpdateApiArg = {
/** A unique integer value identifying this results. */
id: number;
results: Results;
};
export type ResultsPartialUpdateApiResponse = /** status 200 */ ResultsRead;
export type ResultsPartialUpdateApiArg = {
/** A unique integer value identifying this results. */
id: number;
patchedResults: PatchedResults;
};
export type ResultsDestroyApiResponse = unknown;
export type ResultsDestroyApiArg = {
/** A unique integer value identifying this results. */
id: number;
};
export type SessionRetrieveApiResponse = unknown;
export type SessionRetrieveApiArg = void;
export type SimulationListApiResponse = /** status 200 */ SimulationRead[];
Expand Down Expand Up @@ -3087,6 +3169,90 @@ export type PatchedProtocolRead = {
/** Group that uses this protocol */
group?: number | null;
};
export type RowsEnum = "parameters" | "variables" | "groups" | "intervals";
export type ColumnsEnum = "parameters" | "variables" | "groups" | "intervals";
export type Results = {
/** name of the table */
name: string;
/** parameter to display as table rows
* `parameters` - Secondary parameters of the model.
* `variables` - Model variables.
* `groups` - Subject groups.
* `intervals` - Time intervals. */
rows: RowsEnum;
/** parameter to display as table columns
* `parameters` - Secondary parameters of the model.
* `variables` - Model variables.
* `groups` - Subject groups.
* `intervals` - Time intervals. */
columns: ColumnsEnum;
/** Project that this group belongs to. */
project?: number | null;
};
export type ResultsRead = {
id: number;
/** name of the table */
name: string;
/** parameter to display as table rows
* `parameters` - Secondary parameters of the model.
* `variables` - Model variables.
* `groups` - Subject groups.
* `intervals` - Time intervals. */
rows: RowsEnum;
/** parameter to display as table columns
* `parameters` - Secondary parameters of the model.
* `variables` - Model variables.
* `groups` - Subject groups.
* `intervals` - Time intervals. */
columns: ColumnsEnum;
/** Project that this group belongs to. */
project?: number | null;
};
export type PatchedResults = {
/** name of the table */
name?: string;
/** parameter to display as table rows
* `parameters` - Secondary parameters of the model.
* `variables` - Model variables.
* `groups` - Subject groups.
* `intervals` - Time intervals. */
rows?: RowsEnum;
/** parameter to display as table columns
* `parameters` - Secondary parameters of the model.
* `variables` - Model variables.
* `groups` - Subject groups.
* `intervals` - Time intervals. */
columns?: ColumnsEnum;
/** Project that this group belongs to. */
project?: number | null;
};
export type PatchedResultsRead = {
id?: number;
/** name of the table */
name?: string;
/** parameter to display as table rows
* `parameters` - Secondary parameters of the model.
* `variables` - Model variables.
* `groups` - Subject groups.
* `intervals` - Time intervals. */
rows?: RowsEnum;
/** parameter to display as table columns
* `parameters` - Secondary parameters of the model.
* `variables` - Model variables.
* `groups` - Subject groups.
* `intervals` - Time intervals. */
columns?: ColumnsEnum;
/** Project that this group belongs to. */
project?: number | null;
};
export type SimulationSlider = {
variable: number;
};
Expand Down Expand Up @@ -3794,6 +3960,12 @@ export const {
useProtocolUpdateMutation,
useProtocolPartialUpdateMutation,
useProtocolDestroyMutation,
useResultsListQuery,
useResultsCreateMutation,
useResultsRetrieveQuery,
useResultsUpdateMutation,
useResultsPartialUpdateMutation,
useResultsDestroyMutation,
useSessionRetrieveQuery,
useSimulationListQuery,
useSimulationCreateMutation,
Expand Down
42 changes: 25 additions & 17 deletions frontend-v2/src/features/results/Results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,46 @@ import { SyntheticEvent, FC, useState } from "react";
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
import ResultsTab from "./ResultsTab";
import { TableHeader } from "../../components/TableHeader";

type Table = {
name: string;
id: number;
};
import { useResults } from "./useResults";
import { ResultsRead } from "../../app/backendApi";
import { useSelector } from "react-redux";
import { RootState } from "../../app/store";

const Results: FC = () => {
const projectId = useSelector(
(state: RootState) => state.main.selectedProject,
);
const [tab, setTab] = useState(0);
const [tables, setTables] = useState<Table[]>([{ name: "Table 1", id: 0 }]);
const { results, createResults, deleteResults } = useResults();

const getHighestTableId = (): number =>
tables
results
?.map(({ id }) => id)
.sort((id1: number, id2: number) => (id1 > id2 ? 1 : 0))
.pop() || 0;

const handleTabAdd = async () => {
const newTableId = getHighestTableId() + 1;
const newTableName = `Table ${getHighestTableId() + 2}`;
const newTables = [...tables, { name: newTableName, id: newTableId }];
setTables(newTables);
setTab(newTables?.length - 1);
const lastTable = results[results.length - 1];
const newTable = {
...lastTable,
name: newTableName,
project: projectId,
};
newTable.columns ||= "parameters";
newTable.rows ||= "variables";
createResults({ results: newTable });
setTab(results.length);
};

const handleTabChange = async (event: SyntheticEvent, newValue: number) => {
setTab(newValue);
};

const handleTabRemove = async (table: Table) => {
const handleTabRemove = async (table: ResultsRead) => {
if (window.confirm("Are you sure you want to delete the current Table?")) {
const removedIndex = tables.map(({ id }) => id).indexOf(table.id);
setTables(tables.filter(({ id }) => id !== table.id));
const removedIndex = results?.map(({ id }) => id).indexOf(table.id) || -1;
deleteResults({ id: table.id });

if (removedIndex === tab) {
setTab(removedIndex - 1);
Expand Down Expand Up @@ -71,7 +79,7 @@ const Results: FC = () => {
allowScrollButtonsMobile
sx={{ width: "fit-content" }}
>
{tables?.map((table, index) => {
{results?.map((table, index) => {
return (
<Tab
key={table.id}
Expand Down Expand Up @@ -120,7 +128,7 @@ const Results: FC = () => {
</Box>
</Box>

{tables.map((table, index) => {
{results.map((table, index) => {
return (
<Box
key={table.id}
Expand All @@ -129,7 +137,7 @@ const Results: FC = () => {
hidden={tab !== index}
sx={{ p: 3 }}
>
<ResultsTab />
<ResultsTab table={table} />
</Box>
);
})}
Expand Down
15 changes: 11 additions & 4 deletions frontend-v2/src/features/results/ResultsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { useConcentrationVariables } from "./useConcentrationVariables";
import { useParameters, Parameter } from "./useParameters";
import { ResultsTable } from "./ResultsTable";
import { useModelTimeIntervals } from "../../hooks/useModelTimeIntervals";
import { Table } from "./Results";

const options = [
{ name: "Parameters", value: "parameters" },
Expand Down Expand Up @@ -42,14 +43,14 @@ type RowFilter = {
label: string;
};

const ResultsTab: FC = () => {
const ResultsTab: FC<{ table: Table }> = ({ table }) => {
const { groups = [] } = useSubjectGroups();
const [intervals] = useModelTimeIntervals();
const concentrationVariables = useConcentrationVariables();
const parameters = useParameters();

const [columns, setColumns] = useState("parameters");
const [rows, setRows] = useState("variables");
const [columns, setColumns] = useState(table.columns);
const [rows, setRows] = useState(table.rows);
const [group, setGroup] = useState(0);
const [variable, setVariable] = useState(0);
const [interval, setInterval] = useState(0);
Expand Down Expand Up @@ -93,8 +94,14 @@ const ResultsTab: FC = () => {
? "Interval"
: "Group";

function handleColumnsChange(event: SelectChangeEvent) {
setColumns(event.target.value);
table.columns = event.target.value;
}

function handleRowsChange(event: SelectChangeEvent) {
setRows(event.target.value);
table.rows = event.target.value;
}
function handleGroupChange(event: SelectChangeEvent) {
const newValue = parseInt(event.target.value);
Expand Down Expand Up @@ -208,7 +215,7 @@ const ResultsTab: FC = () => {
<InputLabel id="columns-label">Columns</InputLabel>
<Select
value={columns}
onChange={(event) => setColumns(event.target.value)}
onChange={handleColumnsChange}
label="Columns"
labelId="columns-label"
sx={{ minWidth: "10rem", marginRight: "1rem" }}
Expand Down
29 changes: 29 additions & 0 deletions frontend-v2/src/features/results/useResults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useSelector } from "react-redux";

import { RootState } from "../../app/store";
import {
useResultsCreateMutation,
useResultsDestroyMutation,
useResultsListQuery,
useResultsUpdateMutation,
} from "../../app/backendApi";

export function useResults() {
const projectId = useSelector(
(state: RootState) => state.main.selectedProject,
);
const projectIdOrZero = projectId || 0;
const { data: results } = useResultsListQuery(
{ projectId: projectIdOrZero },
{ skip: !projectId },
);
const [updateResults] = useResultsUpdateMutation();
const [createResults] = useResultsCreateMutation();
const [deleteResults] = useResultsDestroyMutation();
return {
results: results || [],
updateResults,
createResults,
deleteResults,
};
}
3 changes: 2 additions & 1 deletion pkpdapp/pkpdapp/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@
StopInferenceView,
LogLikelihoodView, InferenceWizardView,
login_view, logout_view, get_csrf, SessionView, WhoAmIView,
SimulationViewSet, SubjectGroupView
SimulationViewSet, SubjectGroupView,
ResultsView
)
1 change: 1 addition & 0 deletions pkpdapp/pkpdapp/api/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from .nca import NcaSerializer
from .project import ProjectSerializer, ProjectAccessSerializer
from .protocol import ProtocolSerializer
from .results import ResultsSerializer
from .subject_group import SubjectGroupSerializer
from .dataset import DatasetSerializer, DatasetCsvSerializer
from .subject import SubjectSerializer
Expand Down
15 changes: 15 additions & 0 deletions pkpdapp/pkpdapp/api/serializers/results.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#
# This file is part of PKPDApp (https://github.com/pkpdapp-team/pkpdapp) which
# is released under the BSD 3-clause license. See accompanying LICENSE.md for
# copyright notice and full license details.
#

from rest_framework import serializers
from pkpdapp.models import Results


class ResultsSerializer(serializers.ModelSerializer):

class Meta:
model = Results
fields = '__all__'
Loading

0 comments on commit f4c0e87

Please sign in to comment.