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

Out of range on sequence: show message on tooltip on click annotation #232

Draft
wants to merge 7 commits into
base: development
Choose a base branch
from
Draft
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
62 changes: 62 additions & 0 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,73 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: '16.14.0'
- name: Check versions
run: |
main_version=$(grep -E '^# 3DBIONOTES-WS v[0-9.]+' README.md | sed -E 's/^# 3DBIONOTES-WS v([0-9.]+)$/\1/')
viewer_protvista_version=$(cat app/views/webserver/viewer.html.haml | grep -Eo 'protvista-pdb-[0-9.]+-est-[0-9]+(-beta.[0-9]+){0,1}' | awk '{print $1}')
viewer_pdbe_molstar_version=$(cat app/views/webserver/viewer.html.haml | grep -Eo 'pdbe-molstar-plugin-[0-9.]+-est-[0-9]+(-beta.[0-9]+){0,1}' | awk '{print $1}')
cd app/assets/javascripts/covid19
bio_covid19_version=$(cat package.json | jq -r '.version')
cd ../3dbio_viewer
bio_viewer_version=$(cat package.json | jq -r '.version')
protvista_version=$(cat package.json | jq -r '.dependencies["@3dbionotes/protvista-pdb"]')
pdbe_molstar_version=$(cat package.json | jq -r '.dependencies["@3dbionotes/pdbe-molstar"]')
index_protvista_version=$(cat public/index.html | grep -Eo 'protvista-pdb-[0-9.]+-est-[0-9]+(-beta.[0-9]+){0,1}' | awk '{print $1}')
index_pdbe_molstar_version=$(cat public/index.html | grep -Eo 'pdbe-molstar-plugin-[0-9.]+-est-[0-9]+(-beta.[0-9]+){0,1}' | awk '{print $1}')
if [ "$bio_viewer_version" != "$bio_covid19_version" ] ||
[ "$main_version" != "$bio_covid19_version" ]; then
echo "3dbio_viewer version doesn't match with covid19 version"
echo "3dbio_viewer: $bio_viewer_version"
echo "covid19: $bio_covid19_version"
echo "README.md version: $main_version"
exit 1
fi

if [ "$viewer_protvista_version" != "$index_protvista_version" ] ||
[ "$viewer_pdbe_molstar_version" != "$index_pdbe_molstar_version" ] ||
[ "$(echo "$index_protvista_version" | sed 's/protvista-pdb-//')" != "$protvista_version" ] ||
[ "$(echo "$index_pdbe_molstar_version" | sed 's/pdbe-molstar-plugin-//')" != "$pdbe_molstar_version" ]; then
echo "Versions don't match:"
echo "Viewer and index protvista: $viewer_protvista_version, $index_protvista_version"
echo "Viewer and index pdbe-molstar: $viewer_pdbe_molstar_version, $index_pdbe_molstar_version"
echo "3dbio_viewer dependency and index protvista: $protvista_version, $(echo "$index_protvista_version" | sed 's/protvista-pdb-//')"
echo "3dbio_viewer dependency and index pdbe-molstar: $pdbe_molstar_version, $(echo "$index_pdbe_molstar_version" | sed 's/pdbe-molstar-plugin-//'), "
exit 1
fi

echo "3dbio_viewer versions match"
echo "README.md version: $main_version"
echo "3DBIONOTES version: $bio_viewer_version"
echo "protvista-pdb version: $protvista_version"
echo "pdbe-molstar version: $pdbe_molstar_version"
- name: Install and build (3dbio_viewer)
run: |
cd app/assets/javascripts/3dbio_viewer
yarn install
yarn build
- name: Check build dependency versions (3dbioviewer)
run: |
cd app/assets/javascripts/3dbio_viewer
index_protvista_version=$(cat public/index.html | grep -Eo 'protvista-pdb-[0-9.]+-est-[0-9]+(-beta.[0-9]+){0,1}' | awk '{print $1}')
index_pdbe_molstar_version=$(cat public/index.html | grep -Eo 'pdbe-molstar-plugin-[0-9.]+-est-[0-9]+(-beta.[0-9]+){0,1}' | awk '{print $1}')
build_pdbe_molstar_file="build/pdbe-molstar/$index_pdbe_molstar_version.js"
if [ -f "$build_pdbe_molstar_file" ]; then
echo "Build file $build_pdbe_molstar_file exists."
else
echo "Build file $build_pdbe_molstar_file does not exist."
exit 1
fi

build_protvista_file="build/protvista-pdb/$index_protvista_version.js"
if [ -f "$build_protvista_file" ]; then
echo "Build file $build_protvista_file exists."
else
echo "Build file $build_protvista_file does not exist."
exit 1
fi
- name: Run tests (3dbio_viewer)
run: |
cd app/assets/javascripts/3dbio_viewer
yarn test:nowatch
- name: Install and build (covid19)
run: |
Expand Down
2 changes: 1 addition & 1 deletion app/assets/javascripts/3dbio_viewer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,4 @@
"\\.(jpg|jpeg|png|svg)$": "<rootDir>/config/fileMock.js"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,59 +8,69 @@ import { routes } from "../../routes";
import { Future } from "../../utils/future";
import { RequestError, getFromUrl } from "../request-utils";
import { emdbsFromPdbUrl, getEmdbsFromMapping, PdbEmdbMapping } from "./mapping";
import { Maybe } from "../../utils/ts-utils";
import i18n from "../../domain/utils/i18n";

export class BionotesPdbInfoRepository implements PdbInfoRepository {
get(pdbId: PdbId): FutureData<PdbInfo> {
const proteinMappingUrl = `${routes.bionotes}/api/mappings/PDB/Uniprot/${pdbId}`;
const proteinMappingUrl = `${routes.ebi}/pdbe/api/mappings/uniprot/${pdbId}`;
const fallbackMappingUrl = `${routes.ebi}/pdbe/api/pdb/entry/polymer_coverage/${pdbId}/`;
const emdbMapping = `${emdbsFromPdbUrl}/${pdbId}`;
const data$ = {
uniprotMapping: getFromUrl<UniprotFromPdbMapping>(proteinMappingUrl).flatMapError((err) => buildError<UniprotFromPdbMapping>("serviceUnavailable", err)),
fallbackMapping: getFromUrl<ChainsFromPolymer>(fallbackMappingUrl).flatMapError((err) => buildError<ChainsFromPolymer>("noData", err)),
uniprotMapping: getFromUrl<Maybe<UniprotFromPdbMapping>>(
proteinMappingUrl
).flatMapError(_err => Future.success<Maybe<UniprotFromPdbMapping>, Error>(undefined)),
fallbackMapping: getFromUrl<ChainsFromPolymer>(fallbackMappingUrl).flatMapError(err =>
buildError<ChainsFromPolymer>("noData", err)
),
emdbMapping: getFromUrl<PdbEmdbMapping>(emdbMapping),
};

return Future.joinObj(data$).flatMap(data => {
const { uniprotMapping, emdbMapping, fallbackMapping } = data;
const proteinsMapping = uniprotMapping[pdbId.toLowerCase()];
const proteinsMapping = uniprotMapping && uniprotMapping[pdbId.toLowerCase()]?.UniProt;
const fallback = fallbackMapping[pdbId.toLowerCase()];

if (!proteinsMapping) {
if (!proteinsMapping && !fallback) {
const err = `Uniprot mapping not found for ${pdbId}`;
return buildError("noData", { message: err });
}

if (!fallback) {
const err = `No fallback chains found for ${pdbId}`;
return buildError("noData", { message: err });
}

const emdbIds = getEmdbsFromMapping(emdbMapping, pdbId);

if (proteinsMapping instanceof Array) {
return Future.success<PdbInfo, Error>(buildPdbInfo({
id: pdbId,
emdbs: emdbIds.map(emdbId => ({ id: emdbId })),
ligands: [],
proteins: [],
proteinsMapping: undefined,
chains: fallback.molecules.flatMap(({ chains }) => chains).map(chain => ({
id: chain.struct_asym_id,
shortName: chain.struct_asym_id,
name: chain.struct_asym_id,
chainId: chain.struct_asym_id,
protein: undefined
}))
}));
}
if (!proteinsMapping && fallback)
return Future.success<PdbInfo, Error>(
buildPdbInfo({
id: pdbId,
emdbs: emdbIds.map(emdbId => ({ id: emdbId })),
ligands: [],
proteins: [],
proteinsMapping: undefined,
chains: fallback.molecules
.flatMap(({ chains }) => chains)
.map(chain => ({
id: chain.struct_asym_id,
shortName: chain.struct_asym_id,
name: chain.struct_asym_id,
chainId: chain.struct_asym_id,
protein: undefined,
})),
})
);

const proteins = _(proteinsMapping).keys().join(",");
const proteinsInfoUrl = `${routes.bionotes}/api/lengths/UniprotMulti/${proteins}`;
const proteinsInfo$ = proteins
? getFromUrl<ProteinsInfo>(proteinsInfoUrl)
: Future.success<ProteinsInfo, Error>({});

const proteinsMappingChains = _.mapValues(proteinsMapping, v =>
v.mappings.map(({ struct_asym_id, chain_id }) => ({
structAsymId: struct_asym_id,
chainId: chain_id,
}))
);

return proteinsInfo$.map(proteinsInfo => {
const proteins = _(proteinsInfo)
.toPairs()
Expand All @@ -78,7 +88,7 @@ export class BionotesPdbInfoRepository implements PdbInfoRepository {
ligands: [],
chains: [],
proteins,
proteinsMapping,
proteinsMapping: proteinsMappingChains,
});
});
});
Expand All @@ -90,22 +100,35 @@ type ErrorType = "serviceUnavailable" | "noData";
function buildError<T>(type: ErrorType, err: RequestError): FutureData<T> {
console.error(err.message);
switch (type) {
case "serviceUnavailable": return Future.error({
message: i18n.t(
"We apologize. Some of the services we rely on are temporarily unavailable. Our team is working to resolve the issue, and we appreciate your patience. Please try again later."
),
});
case "noData": return Future.error({
message: i18n.t(`No data found for this PDB. But you can try and visualize another PDB. If you believe this is incorrect, please contact us using the "Send Feedback" button below.`),
})
case "serviceUnavailable":
return Future.error({
message: i18n.t(
"We apologize. Some of the services we rely on are temporarily unavailable. Our team is working to resolve the issue, and we appreciate your patience. Please try again later."
),
});
case "noData":
return Future.error({
message: i18n.t(
`No data found for this PDB. But you can try and visualize another PDB. If you believe this is incorrect, please contact us using the "Send Feedback" button below.`
),
});
}
}

type UniprotFromPdbMapping = Record<PdbId, Record<ProteinId, ChainId[]> | unknown[]>;
export type UniprotMapping = Record<
ProteinId,
{ mappings: { chain_id: ChainId; struct_asym_id: ChainId }[] }
>;

type Uniprot = {
UniProt: UniprotMapping;
};

type UniprotFromPdbMapping = Record<PdbId, Uniprot>;

type PolymerMolecules = {
chains: { struct_asym_id: string }[];
}[]
}[];

type ChainsFromPolymer = Record<PdbId, { molecules: PolymerMolecules }>;

Expand Down
17 changes: 11 additions & 6 deletions app/assets/javascripts/3dbio_viewer/src/domain/entities/PdbInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import _ from "lodash";
import { Maybe } from "../../utils/ts-utils";
import { Ligand } from "./Ligand";
import { Emdb } from "./Pdb";
import { ChainId, Protein, ProteinId } from "./Protein";
import { ChainId, Protein } from "./Protein";
import { UploadData } from "./UploadData";

export interface PdbInfo {
Expand All @@ -19,9 +19,14 @@ type Chain = {
protein: Maybe<Protein>;
};

type ChainIds = {
structAsymId: string;
chainId: string;
};

interface BuildPdbInfoOptions extends PdbInfo {
proteins: Protein[];
proteinsMapping: Maybe<Record<ProteinId, ChainId[]>>;
proteinsMapping: Maybe<Record<string, ChainIds[]>>;
}

export function buildPdbInfo(options: BuildPdbInfoOptions): PdbInfo {
Expand All @@ -33,13 +38,13 @@ export function buildPdbInfo(options: BuildPdbInfoOptions): PdbInfo {
const protein = proteinById[proteinId];
if (!protein) return [];

return chainIds.map(chainId => {
const shortName = _([chainId, protein.gen]).compact().join(" - ");
return chainIds.map(({ structAsymId, chainId: _chainId }) => {
const shortName = _([structAsymId, protein.gen]).compact().join(" - ");
return {
id: [proteinId, chainId].join("-"),
id: [proteinId, structAsymId].join("-"),
shortName,
name: _([shortName, protein.name]).compact().join(", "),
chainId,
chainId: structAsymId,
protein,
};
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,84 @@
import React from "react";
import _ from "lodash";
import { InfoOutlined as InfoOutlinedIcon } from "@material-ui/icons";
import { Reference } from "../../../domain/entities/Evidence";
import { Fragment, getFragmentToolsLink } from "../../../domain/entities/Fragment";
import { Fragment2, getConflict } from "../../../domain/entities/Fragment2";
import { Fragment2, Interval, getConflict } from "../../../domain/entities/Fragment2";
import { Pdb } from "../../../domain/entities/Pdb";
import { Subtrack } from "../../../domain/entities/Track";
import i18n from "../../utils/i18n";
import { renderJoin } from "../../utils/react";
import { Link } from "../Link";
import { Protein } from "../../../domain/entities/Protein";
import { trackDefinitions } from "../../../domain/definitions/tracks";
import i18n from "../../utils/i18n";

interface TooltipProps {
pdb: Pdb;
subtrack: Subtrack;
fragment: FragmentP;
alignment: Interval[];
}

type FragmentP = Fragment | Fragment2;

export const Tooltip: React.FC<TooltipProps> = React.memo(props => {
const { pdb, subtrack, fragment } = props;
const { pdb, subtrack, fragment, alignment } = props;
const { description, alignmentScore } = fragment;
const score = alignmentScore ? alignmentScore + " %" : undefined;
const score = alignmentScore ? alignmentScore + " %" : undefined; // aligmentScore is never being set on code
const isStructureCoverage = subtrack.accession === trackDefinitions.structureCoverage.id;

// Intervals inside intervals (Spanish): https://chat.openai.com/share/eb5816e2-d5c5-455c-bf6e-9e08e8850470
const isCovered =
isStructureCoverage ||
alignment.some(
interval => fragment.start >= interval.start && fragment.end <= interval.end
);

const isPartiallyCovered = alignment.some(
interval => fragment.start <= interval.end && fragment.end >= interval.start
);

const isNotCovered = alignment.every(
interval => fragment.start > interval.end || fragment.end < interval.start
);

return (
<TooltipTable>
<TooltipRow title={i18n.t("Feature ID")} value={fragment.id} className="description" />
{isNotCovered && (
<TooltipRow
title={i18n.t("Aligment")}
value={i18n.t("Not on PDB")}
className="error"
object={{ title: i18n.t("") }} // More descriptive text to be changed by JR
>
{info => (
<InfoOutlinedIcon
fontSize="small"
component={InfoIcon}
title={info.title}
/>
)}
</TooltipRow>
)}
{isPartiallyCovered && !isCovered && (
<TooltipRow
title={i18n.t("Aligment")}
value={i18n.t("Partially on PDB")}
className="warning"
object={{ title: i18n.t("") }} // More descriptive text to be changed by JR
>
{info => (
<InfoOutlinedIcon
fontSize="small"
component={InfoIcon}
title={info.title}
/>
)}
</TooltipRow>
)}
<TooltipRow title={i18n.t("Feature ID")} value={fragment.id} />
<TooltipRow title={i18n.t("Description")} value={description} />
<TooltipRow title={i18n.t("Alignment score")} value={score} className="description" />
<TooltipRow title={i18n.t("Alignment score")} value={score} />
<TooltipRow title={i18n.t("Conflict")} value={getConflict(pdb.sequence, fragment)} />
<Source subtrack={subtrack} />
<Evidences fragment={fragment} />
Expand Down Expand Up @@ -171,3 +222,10 @@ const ReferencesRows: React.FC<{ title?: string; sources: Reference[] }> = props
</React.Fragment>
);
};

const InfoIcon: React.FC<{ title: string }> = props => (
<svg {...props}>
<title>{props.title}</title>
{props.children}
</svg>
);
Loading
Loading