Skip to content

Commit

Permalink
Cloud func and misc cleanup (#26)
Browse files Browse the repository at this point in the history
Note: the deploy preview here won't work since it will be hitting the
live cloud functions which are not updated with the changes in this PR
yet. If you want to test just the frontend on the Netlify preview, put
`?mock=true` at the end of the url and it will use fake data instead of
a real api.

- convert cloud funcs and consuming frontend api funcs to use
json-encoded body params instead of url params
- make some frontend types more specific
- fix button component unique key warning
- remove vestigial "icon" className in a few places
- display summary of inputs on results page
- add per-species example genes
- make cloud functions use POST and request body for passing params
- return appropriate status codes and error messages from cloud funcs
- move documentation of cloud funcs to separate readme for readability
(headings to split up/organize content, code blocks for syntax
highlighting, etc). do a bit of rewriting of comments for clarity.
- rename "run_pipeline" to "ml" for consistency

---------

Co-authored-by: Christopher Andrew Mancuso <[email protected]>
  • Loading branch information
vincerubinetti and ChristopherMancuso authored Apr 18, 2024
1 parent bf489b2 commit 6ea8441
Show file tree
Hide file tree
Showing 15 changed files with 356 additions and 226 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ __pycache__/
# ignore sensitive items
.env
/.keys/*

data
18 changes: 18 additions & 0 deletions frontend/fixtures/ml.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
{
"input": {
"genes": [
"836",
"1544",
"1543",
"4780",
"1557",
"1565",
"1581",
"9971",
"7157",
"1588"
],
"sp_trn": "Human",
"sp_test": "Human",
"net_type": "BioGRID",
"gsc": "GO"
},
"avgps": [-10, -10, -10],
"df_convert_out_subset": [
{
Expand Down
38 changes: 34 additions & 4 deletions frontend/src/api/input.ts → frontend/src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import { api, request } from "@/api";
import type { ConvertIds, Species } from "@/api/types";
import type { AnalysisResults, ConvertIds, Input, Species } from "@/api/types";

/** convert input list of genes into entrez */
export const convertGeneIds = async (
ids: string[],
genes: string[],
species: Species = "Human",
) => {
const params = { geneids: ids, species };
const response = await request<ConvertIds>(`${api}/gpz-convert-ids`, params);
const headers = new Headers();
headers.append("Content-Type", "application/json");

const params = { genes, species };

const response = await request<ConvertIds>(
`${api}/gpz-convert-ids`,
undefined,
{ method: "POST", headers, body: JSON.stringify(params) },
);

/** map "couldn't convert" status to easier-to-work-with value */
for (const row of response.df_convert_out)
Expand All @@ -31,3 +39,25 @@ export const convertGeneIds = async (

return transformed;
};

/** submit analysis */
export const submitAnalysis = async (input: Input) => {
const headers = new Headers();
headers.append("Content-Type", "application/json");

const params = {
genes: input.genes,
net_type: input.network,
gsc: input.genesetContext,
sp_trn: input.species,
sp_tst: input.species,
};

const response = await request<AnalysisResults>(`${api}/gpz-ml`, undefined, {
method: "POST",
headers,
body: JSON.stringify(params),
});

return response;
};
16 changes: 0 additions & 16 deletions frontend/src/api/submit.ts

This file was deleted.

24 changes: 12 additions & 12 deletions frontend/src/api/types.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,38 @@
export type ConvertIds = {
input_count: number;
convert_ids: string[];
table_summary: {
Network: string;
NetworkGenes: number;
PositiveGenes: number;
}[];
df_convert_out: {
"Entrez ID": string;
"In BioGRID?": string;
"In IMP?": string;
"In STRING?": string;
"Original ID": string;
}[];
input_count: number;
table_summary: {
Network: string;
NetworkGenes: number;
PositiveGenes: number;
}[];
};

export type AnalysisResults = {
avgps: number[];
df_convert_out_subset: {
"Entrez ID": string;
"In BioGRID?"?: string;
"In IMP?"?: string;
"In STRING?"?: string;
"Original ID": string;
}[];
avgps: number[];
positive_genes: number;
isolated_genes: string[];
isolated_genes_sym: string[];
df_edge: { Node1: string; Node2: string }[];
df_edge_sym: { Node1: string; Node2: string }[];
df_probs: {
"Class-Label": string;
"Class-Label": "P" | "N" | "U";
Entrez: string;
"Known/Novel": string;
"Known/Novel": "Known" | "Novel";
Name: string;
Probability: number;
Rank: number;
Expand All @@ -41,9 +44,6 @@ export type AnalysisResults = {
Rank: number;
Similarity: number;
}[];
isolated_genes: string[];
isolated_genes_sym: string[];
positive_genes: number;
};

export type Species =
Expand Down
17 changes: 12 additions & 5 deletions frontend/src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {
ReactElement,
ReactNode,
} from "react";
import { cloneElement, forwardRef } from "react";
import { forwardRef } from "react";
import classNames from "classnames";
import { useForm } from "@/components/Form";
import Link from "@/components/Link";
Expand Down Expand Up @@ -59,10 +59,17 @@ const Button = forwardRef(
ref,
) => {
/** contents of main element */
const children = [text, icon && cloneElement(icon, { className: "icon" })];

/** flip icon/text */
if (flip) children.reverse();
const children = flip ? (
<>
{icon}
{text}
</>
) : (
<>
{icon}
{text}
</>
);

/** class name string */
const _class = classNames(className, classes.button, classes[design], {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ReactElement, ReactNode } from "react";
import { cloneElement, Fragment, useId } from "react";
import { Fragment, useId } from "react";
import classNames from "classnames";
import { kebabCase } from "lodash";
import { StringParam, useQueryParam } from "use-query-params";
Expand Down Expand Up @@ -61,7 +61,7 @@ const Tabs = ({ syncWithUrl = "", children }: Props) => {
type="button"
>
{tab.text}
{tab.icon && cloneElement(tab.icon, { className: "icon" })}
{tab.icon}
</button>
</Tooltip>
))}
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ console.debug(import.meta);

(async () => {
/** mock api */
if (new URL(window.location.href).searchParams.get("mock") === "true") {
if (
new URL(
window.sessionStorage.redirect || window.location.href,
).searchParams.get("mock") === "true"
) {
const { setupWorker } = await import("msw/browser");
const { handlers } = await import("../fixtures");
await setupWorker(...handlers).start({
Expand Down
28 changes: 27 additions & 1 deletion frontend/src/pages/LoadAnalysis.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Fragment, useEffect, useState } from "react";
import {
FaArrowDown,
FaArrowRightToBracket,
FaArrowUp,
FaChartBar,
FaMagnifyingGlassChart,
} from "react-icons/fa6";
import { useLocation } from "react-router";
import { submitAnalysis } from "@/api/submit";
import { startCase } from "lodash";
import { submitAnalysis } from "@/api/api";
import type { AnalysisResults, Input } from "@/api/types";
import Alert from "@/components/Alert";
import Button from "@/components/Button";
Expand Down Expand Up @@ -51,6 +54,29 @@ const LoadAnalysis = () => {
<Heading level={1} icon={<FaMagnifyingGlassChart />}>
Load Analysis
</Heading>
</Section>

{input && (
<Section>
<Heading level={2} icon={<FaArrowRightToBracket />}>
Inputs
</Heading>

<div className="mini-table">
{Object.entries(input).map(([key, value]) => (
<Fragment key={key}>
<span>{startCase(key)}</span>
<span>{Array.isArray(value) ? value.join(", ") : value}</span>
</Fragment>
))}
</div>
</Section>
)}

<Section>
<Heading level={2} icon={<FaChartBar />}>
Results
</Heading>

<div className="flex-row gap-sm">
{results && (
Expand Down
25 changes: 16 additions & 9 deletions frontend/src/pages/NewAnalysis.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Fragment, useEffect, useState } from "react";
import { FaBeer } from "react-icons/fa";
import {
FaArrowUp,
FaBacteria,
FaCheck,
FaDna,
FaEye,
Expand All @@ -17,7 +17,7 @@ import {
import { GiFly, GiRat } from "react-icons/gi";
import { useNavigate } from "react-router";
import { useDebounce } from "use-debounce";
import { convertGeneIds } from "@/api/input";
import { convertGeneIds } from "@/api/api";
import type { GenesetContext, Input, Network, Species } from "@/api/types";
import Alert from "@/components/Alert";
import Button from "@/components/Button";
Expand All @@ -38,16 +38,22 @@ import { formatNumber } from "@/util/string";
import meta from "./meta.json";
import classes from "./NewAnalysis.module.css";

const example =
"CASP3,CYP1A2,CYP1A1,NFE2L2,CYP2C19,CYP2D6,CYP7A1,NR1H4,TP53,CYP19A1";
const example: Record<Species, string> = {
Human: "CASP3,CYP1A2,CYP1A1,NFE2L2,CYP2C19,CYP2D6,CYP7A1,NR1H4,TP53,CYP19A1",
Mouse: "Mpo,Inmt,Gnmt,Fos,Calr,Selenbp2,Rgn,Stat6,Etfa,Atp5f1b",
Fly: "SC35,Rbp1-like,x16,Rsf1,B52,norpA,SF2,Srp54k,Srp54,Rbp1",
Zebrafish: "upf1,dhx34,lsm1,xrn1,xrn2,lsm7,mrto4,pnrc2,lsm4,nbas",
Worm: "egl-26,cas-1,exc-5,gex-3,gex-2,sax-2,cas-2,mig-6,cap-2,rhgf-2",
Yeast: "KL1,GND1,GND2,ZWF1,TKL2,RKI1,RPE1,SOL4,TAL1,SOL3",
};

const speciesOptions: SelectOption<Species>[] = [
{ id: "Human", text: "Human", icon: <FaPerson /> },
{ id: "Mouse", text: "Mouse", icon: <GiRat /> },
{ id: "Fly", text: "Fly", icon: <GiFly /> },
{ id: "Zebrafish", text: "Zebrafish", icon: <FaFish /> },
{ id: "Worm", text: "Worm", icon: <FaWorm /> },
{ id: "Yeast", text: "Yeast", icon: <FaBacteria /> },
{ id: "Yeast", text: "Yeast", icon: <FaBeer /> },
] as const;

const networkOptions: RadioOption<Network>[] = [
Expand Down Expand Up @@ -216,7 +222,8 @@ const NewAnalysis = () => {
<Button
text="Example"
icon={<FaLightbulb />}
onClick={() => setGeneIds(example)}
onClick={() => setGeneIds(example[species])}
tooltip="Try some example genes for this species"
/>

<div className="flex-row gap-sm">
Expand All @@ -238,7 +245,7 @@ const NewAnalysis = () => {
text="Enter Genes"
icon={<FaPaperPlane />}
design="accent"
tooltip="Converts and checks your genes in preparation for analysis."
tooltip="Converts and checks your genes in preparation for analysis"
onClick={() => splitGeneIds.length && runConvertGeneIds()}
/>
</Section>
Expand Down Expand Up @@ -357,7 +364,7 @@ const NewAnalysis = () => {
onChange={setGenesetContext}
label="Geneset Context"
options={genesetContextOptions}
tooltip="Source used to select negative genes and which sets to compare the trained model to."
tooltip="Source used to select negative genes and which sets to compare the trained model to"
/>
</div>

Expand All @@ -367,7 +374,7 @@ const NewAnalysis = () => {
options={filteredSpeciesOptions}
value={species}
onChange={setSpecies}
tooltip="The species for which model predictions will be made."
tooltip="The species for which model predictions will be made"
/>
</Section>

Expand Down
Loading

0 comments on commit 6ea8441

Please sign in to comment.