Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merge mvp1 into master #309

Merged
merged 23 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4acb184
add video tutorials
martinjrobins Oct 24, 2023
bbe3895
replace view with preview
martinjrobins Oct 24, 2023
3f03e00
update nginx config
martinjrobins Oct 24, 2023
debeebb
make sure we update model after updating mappings (plus test)
martinjrobins Oct 27, 2023
4609794
update default parameters and make variables in tmdd models consistent
martinjrobins Oct 30, 2023
9fd4013
move set defaults to backend, any change to pk model triggers
martinjrobins Oct 31, 2023
477acbc
Add to the title the "Name" of the project as defined in Project's page
martinjrobins Nov 1, 2023
eb23ec6
export to csv button
martinjrobins Nov 1, 2023
77b4015
fix amount unit for preclinical models
martinjrobins Nov 1, 2023
c0eb2bf
filter out F and ka for library models if Aa is not dosed
martinjrobins Nov 1, 2023
a929585
add variable description for slider add pulldown
martinjrobins Nov 1, 2023
119942c
improve display of help videos
martinjrobins Nov 1, 2023
922f5b8
parameter slidesr
martinjrobins Nov 1, 2023
19bc44d
remove cm units
martinjrobins Nov 1, 2023
0e8dd85
remove tlag dv when tlag checkbox unchecked
martinjrobins Nov 6, 2023
c48e216
auto default new params
martinjrobins Nov 6, 2023
d2e7651
plot default pan
martinjrobins Nov 6, 2023
162086b
fix default params for qss constant target models
martinjrobins Nov 6, 2023
429947c
remove questions, help label
martinjrobins Nov 10, 2023
663edde
add proper icons
martinjrobins Nov 10, 2023
b41530d
flake8
martinjrobins Nov 20, 2023
9bc95b6
copyright
martinjrobins Nov 20, 2023
dab4a6a
Merge branch 'master' into mvp1
martinjrobins Nov 20, 2023
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
3 changes: 3 additions & 0 deletions frontend-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
"@types/react": "^18.0.6",
"@types/react-dom": "^18.0.2",
"http-proxy-middleware": "^2.0.6",
"papaparse": "^5.4.1",
"plotly.js": "^2.23.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.43.9",
"react-player": "^2.13.0",
"react-plotly.js": "^2.6.0",
"react-redux": "^8.0.1",
"react-scripts": "5.0.1",
Expand Down Expand Up @@ -54,6 +56,7 @@
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@types/papaparse": "^5.3.10",
"@types/react-plotly.js": "^2.6.0",
"cypress": "^12.16.0",
"start-server-and-test": "^2.0.0"
Expand Down
3 changes: 3 additions & 0 deletions frontend-v2/src/app/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ export const api = backendApi.enhanceEndpoints({
],
},
// CombinedModel
combinedModelSetParamsToDefaultsUpdate: {
invalidatesTags: (result, error, { id }) => [{ type: 'Variable', id: 'LIST' }],
},
combinedModelList: {
providesTags: (result) =>
result
Expand Down
25 changes: 19 additions & 6 deletions frontend-v2/src/app/backendApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,16 @@ const injectedRtkApi = api.injectEndpoints({
method: "DELETE",
}),
}),
combinedModelSetParamsToDefaultsUpdate: build.mutation<
CombinedModelSetParamsToDefaultsUpdateApiResponse,
CombinedModelSetParamsToDefaultsUpdateApiArg
>({
query: (queryArg) => ({
url: `/api/combined_model/${queryArg.id}/set_params_to_defaults/`,
method: "PUT",
body: queryArg.combinedModel,
}),
}),
combinedModelSetVariablesFromInferenceUpdate: build.mutation<
CombinedModelSetVariablesFromInferenceUpdateApiResponse,
CombinedModelSetVariablesFromInferenceUpdateApiArg
Expand Down Expand Up @@ -849,10 +859,7 @@ const injectedRtkApi = api.injectEndpoints({
unitList: build.query<UnitListApiResponse, UnitListApiArg>({
query: (queryArg) => ({
url: `/api/unit/`,
params: {
compound_id: queryArg.compoundId,
is_target: queryArg.isTarget,
},
params: { compound_id: queryArg.compoundId },
}),
}),
unitCreate: build.mutation<UnitCreateApiResponse, UnitCreateApiArg>({
Expand Down Expand Up @@ -1092,6 +1099,13 @@ export type CombinedModelDestroyApiArg = {
/** A unique integer value identifying this combined model. */
id: number;
};
export type CombinedModelSetParamsToDefaultsUpdateApiResponse =
/** status 200 */ CombinedModelRead;
export type CombinedModelSetParamsToDefaultsUpdateApiArg = {
/** A unique integer value identifying this combined model. */
id: number;
combinedModel: CombinedModel;
};
export type CombinedModelSetVariablesFromInferenceUpdateApiResponse =
/** status 200 */ CombinedModelRead;
export type CombinedModelSetVariablesFromInferenceUpdateApiArg = {
Expand Down Expand Up @@ -1522,8 +1536,6 @@ export type UnitListApiResponse = /** status 200 */ UnitRead[];
export type UnitListApiArg = {
/** Enable conversions based on compound information */
compoundId?: number;
/** (optional, default=false) true if unit is a target concentration unit */
isTarget?: boolean;
};
export type UnitCreateApiResponse = /** status 201 */ UnitRead;
export type UnitCreateApiArg = {
Expand Down Expand Up @@ -2717,6 +2729,7 @@ export const {
useCombinedModelUpdateMutation,
useCombinedModelPartialUpdateMutation,
useCombinedModelDestroyMutation,
useCombinedModelSetParamsToDefaultsUpdateMutation,
useCombinedModelSetVariablesFromInferenceUpdateMutation,
useCombinedModelSimulateCreateMutation,
useCompoundListQuery,
Expand Down
79 changes: 57 additions & 22 deletions frontend-v2/src/features/help/Help.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,47 @@
import React, { useEffect } from 'react';
import { DynamicTabs, TabPanel } from '../../components/DynamicTabs';
import HelpTab from './HelpTab';
import { parse } from 'papaparse';
import { set } from 'react-hook-form';
import { Container } from '@mui/material';

export type Question = {
question: string;
answer: string;
}

export type TutorialVideo = {
title: string;
type: string;
link: string;
keywords: string[];
}

const tutorialVideosUrl: string = '/backend/tutorial_videos.csv';

const Help: React.FC = () => {
const [ tutorialVideos, setTutorialVideos ] = React.useState<TutorialVideo[]>([]);
useEffect(() => {
parse(tutorialVideosUrl, {
download: true,
error: (err) => {
console.error('Error downloading tutorial videos:', err);
},
complete: (results) => {
setTutorialVideos(
results.data.map((row) => {
const rowList = row as string[];
return {
title: rowList[0],
type: rowList[1],
link: rowList[2].replace('view?usp=sharing', 'preview'),
keywords: rowList[3].split(',').map((keyword) => keyword.trim())
};
})
);
}
})
}, []);
let generic_questions: Question[] = Array(5).fill({
question: "Question 1?",
answer: "Answer 1"
Expand All @@ -16,30 +51,30 @@ const Help: React.FC = () => {
question: `Question ${index + 1}?`,
answer: `Answer ${index + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`
}});

const questions = [
generic_questions.slice(0, 0),
generic_questions.slice(0, 0),
generic_questions.slice(0, 0),
generic_questions.slice(0, 0),
generic_questions.slice(0, 0),
]
const tutorials = [
tutorialVideos.filter((video) => video.type.includes('Tutorial')),
tutorialVideos.filter((video) => video.type === 'Project'),
tutorialVideos.filter((video) => video.type === 'Drug'),
tutorialVideos.filter((video) => video.type === 'Model'),
tutorialVideos.filter((video) => video.type === 'Trial Design'),
tutorialVideos.filter((video) => video.type === 'Simulation'),
]

const project_questions = generic_questions.slice(0, 2);
const drug_questions = generic_questions.slice(0, 4)
const model_questions = generic_questions.slice(0, 3);
const trial_questions = generic_questions.slice(0, 5);
const simulation_questions = generic_questions.slice(0, 1)

return (
<DynamicTabs tabNames={["Projects", "Drug", "Model", "Trial Design", "Simulation"]}>
<TabPanel>
<HelpTab questions={project_questions} />
</TabPanel>
<TabPanel>
<HelpTab questions={drug_questions} />
</TabPanel>
<TabPanel>
<HelpTab questions={model_questions} />
</TabPanel>
<TabPanel>
<HelpTab questions={trial_questions} />
</TabPanel>
<TabPanel>
<HelpTab questions={simulation_questions} />
</TabPanel>
<DynamicTabs tabNames={["Tutorials", "Projects", "Drug", "Model", "Trial Design", "Simulation"]}>
{ questions.map((question, index) => (
<TabPanel key={index}>
<HelpTab questions={question} videos={tutorials[index]} />
</TabPanel>
))}
</DynamicTabs>
);
}
Expand Down
62 changes: 45 additions & 17 deletions frontend-v2/src/features/help/HelpTab.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,57 @@
import { Accordion, AccordionDetails, AccordionSummary, Typography } from "@mui/material";
import { Accordion, AccordionDetails, AccordionSummary, Box, Card, Chip, Grid, Stack, Typography } from "@mui/material";
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { Question } from "./Help";
import { Question, TutorialVideo } from "./Help";
import ReactPlayer from 'react-player/youtube'


interface Props {
questions: Question[];
videos: TutorialVideo[];
}

// tab that lists questions as mui accordian components
const HelpTab: React.FC<Props> = ({ questions }) => {
const HelpTab: React.FC<Props> = ({ questions, videos }) => {
return (
<div>
{questions.map((question, index) => {
return (
<Accordion key={index}>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
>
<Typography>{question.question}</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>{question.answer}</Typography>
</AccordionDetails>
</Accordion>
)
})}
<Stack spacing={1} sx={{ marginBottom: 2 }}>
{questions.map((question, index) => {
return (
<Accordion key={index}>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
>
<Typography>{question.question}</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>{question.answer}</Typography>
</AccordionDetails>
</Accordion>
)
})}
</Stack>
<Box sx={{ display: 'flex', flexWrap: 'wrap' }}>
{videos.map((video, index) => {
return (
<Card sx={{ width: '500px', margin: 2, padding: 1 }}>
<Typography variant='h6' >{video.title}</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap' }}>
{ video.keywords.map((keyword, index) => {
return (
<Chip key={index} size="small" label={keyword} sx={{ margin: 0.2 }} />
)
})}
</Box>
<iframe
key={index}
allowFullScreen
src={video.link}
style={{ border: "none", width: "100%", aspectRatio: "16/9" }}
title={video.title}
/>
</Card>
)
})}
</Box>
</div>
);
}
Expand Down
25 changes: 22 additions & 3 deletions frontend-v2/src/features/main/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,15 @@ import { logout } from '../login/loginSlice';
import { useAppDispatch } from '../../app/hooks';
import ErrorIcon from '@mui/icons-material/Error';
import { Tooltip } from '@mui/material';
import { useCombinedModelListQuery, useProtocolListQuery } from '../../app/backendApi';
import { useCombinedModelListQuery, useProjectRetrieveQuery, useProtocolListQuery } from '../../app/backendApi';
import { Protocol } from "../../app/backendApi";
import DnsIcon from '@mui/icons-material/Dns';
import BiotechIcon from '@mui/icons-material/Biotech';
import FunctionsIcon from '@mui/icons-material/Functions';
import VaccinesIcon from '@mui/icons-material/Vaccines';
import SsidChartIcon from '@mui/icons-material/SsidChart';
import ContactSupportIcon from '@mui/icons-material/ContactSupport';
import TableViewIcon from '@mui/icons-material/TableView';

const drawerWidth = 240;

Expand All @@ -41,6 +48,7 @@ export default function Sidebar() {
const { data: models, isLoading: isModelsLoading } = useCombinedModelListQuery({projectId: projectId || 0}, { skip: !projectId})
const { data: protocols, error: protocolsError, isLoading: isProtocolsLoading } = useProtocolListQuery({projectId: projectId || 0}, { skip: !projectId})
const model = models?.[0] || null;
const { data: project, isLoading: isProjectLoading } = useProjectRetrieveQuery({id: projectId || 0}, { skip: !projectId })

let errors: { [key: string]: string } = {};
if ((model && model.pk_model === null) || (model && model.pd_model && model.mappings.length === 0) || (protocols && protocols.length === 0)) {
Expand All @@ -55,6 +63,16 @@ export default function Sidebar() {
</Tooltip>
)
}

let icons: { [key: string]: React.ReactNode } = {
[PageName.PROJECTS]: <DnsIcon />,
[PageName.DRUG]: <BiotechIcon />,
[PageName.MODEL]: <FunctionsIcon />,
[PageName.TRIAL_DESIGN]: <VaccinesIcon />,
[PageName.DATA]: <TableViewIcon />,
[PageName.SIMULATIONS]: <SsidChartIcon />,
[PageName.HELP]: <ContactSupportIcon />,
};

const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
Expand Down Expand Up @@ -110,7 +128,7 @@ export default function Sidebar() {
<ListItem key={key} disablePadding selected={isPageSelected(key)}>
<ListItemButton onClick={handlePageClick(key)} disabled={isPageDisabled(key)} disableRipple={true}>
<ListItemIcon>
{value in errorComponents ? errorComponents[value] : index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
{value in errorComponents ? errorComponents[value] : icons[value]}
</ListItemIcon>
<ListItemText primary={value} />
</ListItemButton>
Expand Down Expand Up @@ -143,7 +161,8 @@ export default function Sidebar() {
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
PK/PD Simulator

PK/PD Simulator {project && ` - ${project.name}`}
</Typography>
<IconButton
onClick={() => dispatch(logout())}
Expand Down
2 changes: 1 addition & 1 deletion frontend-v2/src/features/main/mainSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export enum PageName {
DATA = 'Data',
TRIAL_DESIGN = 'Trial Design',
SIMULATIONS = 'Simulations',
HELP = 'Help',
HELP = 'Help & Feedback',
}

interface MainState {
Expand Down
28 changes: 11 additions & 17 deletions frontend-v2/src/features/model/MapVariablesTab.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,25 @@
import * as React from 'react';
import { CombinedModel, CombinedModelRead, Project, ProjectRead, Variable, VariableRead, useCompoundRetrieveQuery, useUnitListQuery, useUnitRetrieveQuery } from '../../app/backendApi';
import { Control, useFieldArray } from 'react-hook-form';
import { CombinedModelRead, CompoundRead, ProjectRead, UnitRead, VariableRead } from '../../app/backendApi';
import { Control } from 'react-hook-form';
import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Tooltip } from '@mui/material';
import VariableRow from './VariableRow';
import HelpButton from '../../components/HelpButton';
import { FormData } from './Model';

interface Props {
model: CombinedModelRead;
project: ProjectRead;
control: Control<CombinedModel>;
control: Control<FormData>;
variables: VariableRead[];
units: UnitRead[];
compound: CompoundRead;
}

const MapVariablesTab: React.FC<Props> = ({ model, project, control, variables }: Props ) => {
const MapVariablesTab: React.FC<Props> = ({ model, project, control, variables, units, compound }: Props ) => {

const { data: units, isLoading: isLoadingUnits } = useUnitListQuery({ compoundId: project.compound}, { skip: !project.compound });
const { data: compound, isLoading: isLoadingCompound } = useCompoundRetrieveQuery({id: project.compound})

if (isLoadingUnits || isLoadingCompound) {
return null;
}
if (units === undefined || compound === undefined) {
return null;
}

const isPreclinical = project.species !== 'H' && model.is_library_model;
const concentrationUnit = units.find((unit) => unit.symbol === "pmol/L");
const amountUnit = units.find((unit) => unit.symbol === "pmol");
const amountUnit = isPreclinical ? units.find((unit) => unit.symbol === "pmol/kg") : units.find((unit) => unit.symbol === "pmol");
if (concentrationUnit === undefined || amountUnit === undefined) {
return (<>No concentration or amount unit found</>);
}
Expand All @@ -45,8 +39,8 @@ const MapVariablesTab: React.FC<Props> = ({ model, project, control, variables }
const bIsConcentration = concentrationUnit?.compatible_units.find((unit) => parseInt(unit.id) === b.unit) !== undefined;
const bIsAmount = amountUnit?.compatible_units.find((unit) => parseInt(unit.id) === b.unit) !== undefined;

const aValue = aIsConcentration ? 1 : (aIsAmount ? 2 : 0);
const bValue = bIsConcentration ? 1 : (bIsAmount ? 2 : 0);
const aValue = aIsConcentration ? 2 : (aIsAmount ? 1 : 0);
const bValue = bIsConcentration ? 2 : (bIsAmount ? 1 : 0);

if (aValue !== bValue) {
return bValue - aValue;
Expand Down
Loading
Loading