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

Todos #158

Merged
merged 12 commits into from
Oct 8, 2024
Merged

Todos #158

Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ on:
tags:
- v*
pull_request:
# TODO have way to rebuild bartender image when it and its deps have updated

jobs:
bartender:
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ node_modules

/app/styles/tailwind.css
/coverage
/docs/demo
14 changes: 14 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,17 @@ Then run the app in production mode:
export $(cat .env |grep -v '#' |xargs)
npm start
```

## Creating a release

If you made changes to the webapp and want to create a new release, follow these steps:

1. Update the version in `package.json`
2. Goto the [releases page](https://github.com/i-VRESSE/haddock3-webapp/releases)
3. Copy the first line in the description of the latest release to you clipboard.
4. Click on the `Draft a new release` button
5. Paste the line from the clipboard into the `Tag version` field
6. Set tag and title to same version used in `package.json` with a `v` prefix.
7. Press `Generate release notes` button
8. Adjust description if needed
9. Press `Publish release` button
2 changes: 0 additions & 2 deletions app/auth.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ if (
userAgent: "Haddock3WebApp",
},
async ({ profile }) => {
// TODO store users display name in database for more personal greeting
const primaryEmail = profile.emails[0].value;
const photo = profile.photos[0].value ?? undefined;
const userId = await oauthregister(primaryEmail, photo);
Expand Down Expand Up @@ -181,7 +180,6 @@ if (
});
const profile = await profileResponse.json();
const emails = await this.userEmails(profile.sub);
// TODO store Orcid id into database
return {
...profile,
emails,
Expand Down
1 change: 0 additions & 1 deletion app/browse/OutputReport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ export const OutputReport = ({
</div>
<details>
<summary className="cursor-pointer">Files</summary>
{/* TODO should we hide io.json and params.cfg? */}
<ListFiles files={module.output} jobid={jobid} />
</details>
</li>
Expand Down
1 change: 0 additions & 1 deletion app/builder/Form.css
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ Btn classes are copied from app/components/ui/button.tsx

.workflow-builder-app .array-item-add,
.workflow-builder-app .object-property-expand {
/* TODO full width + center plus sign */
@apply m-4 !w-auto;
}

Expand Down
36 changes: 35 additions & 1 deletion app/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,39 @@
import { useInPortalMode } from "~/portal";

function PortalFooter() {
// Copied from frontend/src/Components/Footer.tsx
// TODO add PoweredBy component on certain page
const currentYear = new Date().getFullYear();
const firstYear = 2008;
return (
<footer className="bottom-0 left-0 right-0 bg-slate-200 text-white p-2">
<div className="text-center text-xs flex-col space-y-1 container mx-auto flex justify-center items-center">
<div>
<p className=" text-gray-400">
{firstYear}-{currentYear} &copy; Computational Structural Biology
group (Utrecht University). All rights reserved.
</p>
</div>
<div className="flex items-center">
<a href="/conditions" className="text-blue-500 hover:underline">
Terms and Conditions
</a>
<div className="h-4 bg-gray-600 w-px mx-4"></div>
<a href="/privacy" className="text-blue-500 hover:underline">
Privacy
</a>
</div>
</div>
</footer>
);
}

export function Footer() {
// TODO in portal mode mimic the footer from the portal
const inPortalMode = useInPortalMode();
if (inPortalMode) {
return <PortalFooter />;
}

return (
<footer className="bg-primary p-1 text-center text-primary-foreground">
<p className="text-sm">
Expand Down
1 change: 0 additions & 1 deletion app/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const LoggedInButton = () => {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
{/* TODO button is not vertically centered */}
<Button
variant="outline"
size="icon"
Expand Down
73 changes: 69 additions & 4 deletions app/routes/jobs.$id._index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,30 @@ import {
type LoaderFunctionArgs,
ActionFunctionArgs,
} from "@remix-run/node";
import { useLoaderData, useRevalidator } from "@remix-run/react";
import {
Link,
useFetcher,
useLoaderData,
useRevalidator,
useSubmit,
} from "@remix-run/react";

import {
deleteJob,
getJobById,
jobIdFromParams,
HADDOCK3WEBAPP_REFRESH_RATE_MS,
listInputFiles,
} from "~/models/job.server";
import { getUser } from "~/auth.server";
import { JobStatus } from "~/components/JobStatus";
import { getBartenderTokenByUser } from "~/bartender-client/token.server";
import DotsLoader from "~/components/ui/DotsLoader";
import { ListFiles } from "~/browse/ListFiles";
import { WORKFLOW_CONFIG_FILENAME } from "~/bartender-client/constants";
import { prefix } from "~/prefix";
import { DirectoryItem } from "~/bartender-client/types";
import { Button } from "~/components/ui/button";

export const loader = async ({ params, request }: LoaderFunctionArgs) => {
const jobId = jobIdFromParams(params);
Expand All @@ -30,12 +42,25 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
return redirect("browse");
}
}
let inputFiles: DirectoryItem = {
path: "",
name: "",
is_file: false,
is_dir: true,
children: [],
};
try {
inputFiles = await listInputFiles(jobId, token);
} catch (error) {
// Do nothing, users can look in log files for more information
}

return json({
job,
// provide refresh rate from env or default of 5 sec.
HADDOCK3WEBAPP_REFRESH_RATE_MS,
lastCheckedOn: new Date().toISOString(),
inputFiles,
});
};

Expand All @@ -47,11 +72,15 @@ export const action = async ({ params, request }: ActionFunctionArgs) => {
const user = await getUser(request);
const token = await getBartenderTokenByUser(user);
await deleteJob(jobId, token);
if (request.headers.get("referer") === request.url) {
return redirect(prefix);
}
return null;
};

export default function JobPage() {
const { job, HADDOCK3WEBAPP_REFRESH_RATE_MS, lastCheckedOn } =
const submit = useSubmit();
const { job, HADDOCK3WEBAPP_REFRESH_RATE_MS, lastCheckedOn, inputFiles } =
useLoaderData<typeof loader>();
const { revalidate } = useRevalidator();

Expand Down Expand Up @@ -80,10 +109,23 @@ export default function JobPage() {
};
}, [HADDOCK3WEBAPP_REFRESH_RATE_MS, revalidate, job.state]);

const hasWorkflow = inputFiles.children?.some(
(file) => file.is_file && file.name === WORKFLOW_CONFIG_FILENAME,
);

return (
<main className="flex gap-16">
<div>
<JobStatus job={job} lastStateCheckOn={lastCheckedOn} />
{(job.state === "queued" || job.state === "running") && (
<Button
variant="outline"
title="Cancel job"
onClick={() => deleteJobConfirm(submit)}
>
Cancel
</Button>
)}
{/* show dots loader indicating we monitor state change */}
{job.state !== "error" && job.state !== "ok" ? (
<DotsLoader
Expand All @@ -93,8 +135,31 @@ export default function JobPage() {
/>
) : null}
</div>
{/* TODO allow to read input files when job is not completed */}
{/* TODO allow job to be cancelled, need bartender support first */}
{inputFiles.children && inputFiles.children.length > 0 && (
<div>
<h2 className="text-2xl">Input</h2>
<ListFiles files={inputFiles!} jobid={job.id} />
<p>
<a href={`${prefix}jobs/${job.id}/input.zip`}>
&#128230; Download archive
</a>
</p>
{hasWorkflow && job.state === "error" && (
<p>
<Link to={`${prefix}jobs/${job.id}/edit`}>&#128393; Edit</Link>
</p>
)}
</div>
)}
</main>
);
}

type Submit = ReturnType<typeof useFetcher>["submit"];

function deleteJobConfirm(submit: Submit): void {
if (window.confirm("Are you sure you want to cancel job?") === false) {
return;
}
submit({}, { method: "delete" });
}
2 changes: 0 additions & 2 deletions app/routes/jobs.$id.report.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import { CaprievalReport } from "~/caprieval/CaprievalReport.client";
import { JobName } from "~/components/JobName";
import { buttonVariants } from "~/components/ui/button";
import { prefix } from "~/prefix";
// TODO rescore is not used here, so imports should not be from this module
// move to a separate module called ~/model/modules and ~/models/caprieval

export const loader = async ({ params, request }: LoaderFunctionArgs) => {
const jobid = jobIdFromParams(params);
Expand Down
9 changes: 0 additions & 9 deletions app/routes/profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,6 @@ export default function Page() {
setTheme(t === "" ? null : (t as Theme));
}}
>
{/*
TODO system is not yet supported by remix-themes,
but issue by author was created on 2 feb 2024.

<div className="flex items-center space-x-2">
<RadioGroupItem value="" id="system" />
<Label htmlFor="system">System</Label>
</div>
*/}
<div className="flex items-center space-x-2">
<RadioGroupItem value={Theme.LIGHT} id={Theme.LIGHT} />
<Label htmlFor={Theme.LIGHT}>Light</Label>
Expand Down
1 change: 0 additions & 1 deletion app/routes/scenarios.protein-dna.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
export const action = uploadaction;

const Schema = object({
// TODO check content of pdb files are valid
protein: instance(File, "Protein structure as PDB file"),
dna: instance(File, "DNA structure as PDB file"),
nrSelectedProteinResidues: pipe(
Expand Down
4 changes: 0 additions & 4 deletions app/routes/scenarios.protein-glycan.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,10 +259,6 @@ export default function ProteinProteinScenario() {
targetChain="A"
preprocessPipeline="delhetatmkeepcoord"
/>
{/* TODO treat glycan as glycan,
disable surface calculation (example fails calc),
only active as restraints flavour kind,
render as ball+stick */}
{/* TODO molstar has carbohydrate representation, check if there is equiv in ngl */}
<GlycanMoleculeSubForm
name="glycan"
Expand Down
1 change: 0 additions & 1 deletion app/routes/scenarios.protein-protein.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
export const action = uploadaction;

const Schema = object({
// TODO check content of pdb files are valid
protein1: instance(File, "First protein structure as PDB file"),
protein2: instance(File, "Second protein structure as PDB file"),
nrSelectedProtein1Residues: pipe(
Expand Down
7 changes: 5 additions & 2 deletions app/scenarios/BindingMoleculeSubForm.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { ResidueHeaderItem } from "@i-vresse/haddock3-ui/toggles/ResidueHeader";

import { ActPassSelection } from "./ActPassSelection";
import { BIG_MOLECULE } from "./constants";
import { toggleResidue } from "./toggleResidue";
import { PreprocessPipeline } from "./restraints";
import { Label } from "~/components/ui/label";
import { Spinner } from "~/components/ui/spinner";
Expand All @@ -29,6 +28,7 @@ import {
ResidueCheckbox,
ResidueNeighbourSelection,
ResidueSelection,
toggleResidue,
useResidueChangeHandler,
} from "@i-vresse/haddock3-ui/toggles";

Expand Down Expand Up @@ -207,7 +207,10 @@ function BindingResiduesSubForm({
) {
return;
}
const newSelection = toggleResidue(resno, "act", actpass);
const newSelection = toggleResidue(resno, "act", {
act: actpass.active,
pass: actpass.passive,
});
handle2DResidueChange(newSelection);
}

Expand Down
8 changes: 5 additions & 3 deletions app/scenarios/GlycanMoleculeSubForm.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Residue,
ResidueCheckbox,
ResidueSelection,
toggleResidue,
useResidueChangeHandler,
} from "@i-vresse/haddock3-ui/toggles";
import { useTheme } from "remix-themes";
Expand All @@ -20,7 +21,6 @@ import {
} from "./AtomStructureSubForm.client";
import { PreprocessPipeline } from "./restraints";
import { LabeledRadioGroup } from "./LabeledRadioGroup";
import { toggleResidue } from "./toggleResidue";
import { ImportResidues, PickIn3D } from "./ResiduesSelect";
import { useSafeFile } from "./useSafeFile";
import { Spinner } from "~/components/ui/spinner";
Expand Down Expand Up @@ -86,7 +86,6 @@ export function GlycanResiduesSelect({
key={r.resno}
resno={r.resno}
resname={r.resname}
// TODO render resname as seq is always uninformative 'X'
seq={r.seq}
highlight={highlight === r.resno}
activeChecked={selected.act.includes(r.resno)}
Expand Down Expand Up @@ -179,7 +178,10 @@ function ResiduesSubForm({
if (molecule.targetChain !== chain) {
return;
}
const newSelection = toggleResidue(resno, picker3D, actpass);
const newSelection = toggleResidue(resno, picker3D, {
act: actpass.active,
pass: actpass.passive,
});
handle2DResidueChange(newSelection);
}

Expand Down
12 changes: 9 additions & 3 deletions app/scenarios/MacroMoleculeSubForm.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,17 @@ import {
Molecule,
ResidueSubFormProps,
} from "./AtomStructureSubForm.client";
import { toggleResidue } from "./toggleResidue";
import { BIG_MOLECULE } from "./constants";
import { useSurfaceCutoff } from "./useSurfaceCutoff";
import { computeNeighbours, useNeighbourRadius } from "./useNeighbourRadius";
import { useSafeFile } from "./useSafeFile";
import { useTheme } from "remix-themes";
import { Viewer } from "@i-vresse/haddock3-ui";
import { ActPass, ResidueSelection } from "@i-vresse/haddock3-ui/toggles";
import {
ActPass,
ResidueSelection,
toggleResidue,
} from "@i-vresse/haddock3-ui/toggles";

export function ResiduesSubForm({
molecule,
Expand Down Expand Up @@ -157,7 +160,10 @@ export function ResiduesSubForm({
) {
return;
}
const newSelection = toggleResidue(resno, picker3D, actpass);
const newSelection = toggleResidue(resno, picker3D, {
act: actpass.active,
pass: actpass.passive,
});
handle2DResidueChange(newSelection);
}

Expand Down
Loading
Loading