jbrowse-plugin-msaview 3.4.1 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +20 -12
- package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.js +1 -0
- package/dist/LaunchMsaView/components/BlastQuery/MsaAlgorithmSelect.js +5 -3
- package/dist/LaunchMsaView/components/BlastQuery/consts.d.ts +24 -4
- package/dist/LaunchMsaView/components/BlastQuery/consts.js +44 -2
- package/dist/LaunchMsaView/components/BlastQuery/searchChoiceStorage.d.ts +1 -1
- package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +1 -0
- package/dist/LaunchMsaView/components/ManualMSALoader/launchView.d.ts +3 -1
- package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js +2 -1
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +8 -3
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.js +4 -1
- package/dist/LaunchMsaView/components/QueryRowSelector.js +21 -5
- package/dist/LaunchMsaView/detectQueryRow.d.ts +11 -0
- package/dist/LaunchMsaView/detectQueryRow.js +11 -4
- package/dist/LaunchMsaView/detectQueryRow.test.js +30 -0
- package/dist/LaunchMsaView/useQueryRowName.d.ts +1 -0
- package/dist/LaunchMsaView/useQueryRowName.js +5 -1
- package/dist/LaunchMsaView/useQueryRowName.test.js +23 -0
- package/dist/LaunchMsaViewExtensionPoint/index.js +8 -6
- package/dist/LaunchMsaViewExtensionPoint/index.test.js +21 -0
- package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +3 -2
- package/dist/MsaViewPanel/afterCreateAutoruns.js +48 -19
- package/dist/MsaViewPanel/components/LaunchProgress.js +12 -3
- package/dist/MsaViewPanel/components/MsaViewPanel.js +6 -2
- package/dist/MsaViewPanel/components/MsaViewPanel.test.js +23 -2
- package/dist/MsaViewPanel/doLaunchBlast.d.ts +8 -0
- package/dist/MsaViewPanel/doLaunchBlast.js +78 -93
- package/dist/MsaViewPanel/doLaunchBlast.test.d.ts +1 -0
- package/dist/MsaViewPanel/doLaunchBlast.test.js +158 -0
- package/dist/MsaViewPanel/doLaunchOrthologs.js +40 -5
- package/dist/MsaViewPanel/doLaunchOrthologs.test.js +10 -0
- package/dist/MsaViewPanel/genomeToMSA.js +4 -4
- package/dist/MsaViewPanel/genomeToMSA.test.js +67 -0
- package/dist/MsaViewPanel/loadProteinDomains.d.ts +8 -2
- package/dist/MsaViewPanel/loadProteinDomains.js +18 -9
- package/dist/MsaViewPanel/loadProteinDomains.test.d.ts +1 -0
- package/dist/MsaViewPanel/loadProteinDomains.test.js +41 -0
- package/dist/MsaViewPanel/model.d.ts +160 -89
- package/dist/MsaViewPanel/model.js +125 -48
- package/dist/MsaViewPanel/model.test.d.ts +1 -0
- package/dist/MsaViewPanel/model.test.js +100 -0
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.d.ts +12 -1
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.js +8 -8
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.js +133 -32
- package/dist/MsaViewPanel/msaDataStore.d.ts +2 -0
- package/dist/MsaViewPanel/msaDataStore.js +10 -0
- package/dist/MsaViewPanel/observeProteinHighlights.test.js +20 -3
- package/dist/MsaViewPanel/processInit.test.d.ts +1 -0
- package/dist/MsaViewPanel/processInit.test.js +72 -0
- package/dist/MsaViewPanel/resolveConnectedTranscript.d.ts +9 -0
- package/dist/MsaViewPanel/resolveConnectedTranscript.js +122 -0
- package/dist/MsaViewPanel/runLaunch.d.ts +1 -0
- package/dist/MsaViewPanel/runLaunch.js +35 -0
- package/dist/MsaViewPanel/runLaunch.test.js +46 -0
- package/dist/MsaViewPanel/structureConnection.d.ts +0 -4
- package/dist/MsaViewPanel/structureConnection.js +0 -19
- package/dist/MsaViewPanel/util.d.ts +39 -0
- package/dist/MsaViewPanel/util.js +44 -0
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +44 -43
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/dist/utils/browserAlign.d.ts +42 -0
- package/dist/utils/browserAlign.js +297 -0
- package/dist/utils/browserAlign.test.d.ts +1 -0
- package/dist/utils/browserAlign.test.js +85 -0
- package/dist/utils/ebiBlast.d.ts +10 -1
- package/dist/utils/ebiBlast.js +27 -1
- package/dist/utils/homologSearch.d.ts +31 -0
- package/dist/utils/homologSearch.js +6 -0
- package/dist/utils/msa.d.ts +3 -11
- package/dist/utils/msa.js +14 -29
- package/dist/utils/msaRows.d.ts +11 -8
- package/dist/utils/msaRows.js +14 -12
- package/dist/utils/phmmer.d.ts +11 -1
- package/dist/utils/phmmer.js +49 -12
- package/dist/utils/unirefHomologs.d.ts +94 -0
- package/dist/utils/unirefHomologs.js +193 -0
- package/dist/utils/unirefHomologs.test.d.ts +1 -0
- package/dist/utils/unirefHomologs.test.js +118 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -4
- package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +38 -13
- package/src/LaunchMsaView/components/BlastQuery/BlastManualPanel.tsx +1 -0
- package/src/LaunchMsaView/components/BlastQuery/MsaAlgorithmSelect.tsx +7 -2
- package/src/LaunchMsaView/components/BlastQuery/consts.ts +50 -2
- package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +1 -0
- package/src/LaunchMsaView/components/ManualMSALoader/launchView.ts +4 -0
- package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +10 -3
- package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.tsx +4 -1
- package/src/LaunchMsaView/components/QueryRowSelector.tsx +35 -7
- package/src/LaunchMsaView/detectQueryRow.test.ts +34 -0
- package/src/LaunchMsaView/detectQueryRow.ts +22 -4
- package/src/LaunchMsaView/useQueryRowName.test.ts +27 -0
- package/src/LaunchMsaView/useQueryRowName.ts +5 -1
- package/src/LaunchMsaViewExtensionPoint/index.test.ts +23 -0
- package/src/LaunchMsaViewExtensionPoint/index.ts +32 -6
- package/src/MsaViewPanel/afterCreateAutoruns.ts +53 -18
- package/src/MsaViewPanel/components/LaunchProgress.tsx +26 -2
- package/src/MsaViewPanel/components/MsaViewPanel.test.tsx +24 -2
- package/src/MsaViewPanel/components/MsaViewPanel.tsx +8 -2
- package/src/MsaViewPanel/doLaunchBlast.test.ts +207 -0
- package/src/MsaViewPanel/doLaunchBlast.ts +92 -142
- package/src/MsaViewPanel/doLaunchOrthologs.test.ts +13 -0
- package/src/MsaViewPanel/doLaunchOrthologs.ts +55 -5
- package/src/MsaViewPanel/genomeToMSA.test.ts +76 -0
- package/src/MsaViewPanel/genomeToMSA.ts +8 -4
- package/src/MsaViewPanel/loadProteinDomains.test.ts +52 -0
- package/src/MsaViewPanel/loadProteinDomains.ts +24 -11
- package/src/MsaViewPanel/model.test.ts +121 -0
- package/src/MsaViewPanel/model.ts +153 -55
- package/src/MsaViewPanel/msaCoordToGenomeCoord.test.ts +152 -32
- package/src/MsaViewPanel/msaCoordToGenomeCoord.ts +21 -11
- package/src/MsaViewPanel/msaDataStore.ts +10 -0
- package/src/MsaViewPanel/observeProteinHighlights.test.ts +27 -3
- package/src/MsaViewPanel/processInit.test.ts +84 -0
- package/src/MsaViewPanel/resolveConnectedTranscript.ts +150 -0
- package/src/MsaViewPanel/runLaunch.test.ts +54 -0
- package/src/MsaViewPanel/runLaunch.ts +39 -0
- package/src/MsaViewPanel/structureConnection.ts +0 -26
- package/src/MsaViewPanel/util.ts +75 -0
- package/src/utils/browserAlign.test.ts +102 -0
- package/src/utils/browserAlign.ts +352 -0
- package/src/utils/ebiBlast.ts +34 -0
- package/src/utils/homologSearch.ts +51 -0
- package/src/utils/msa.ts +19 -41
- package/src/utils/msaRows.ts +20 -16
- package/src/utils/phmmer.ts +57 -11
- package/src/utils/unirefHomologs.test.ts +149 -0
- package/src/utils/unirefHomologs.ts +323 -0
- package/src/version.ts +1 -1
- package/dist/MsaViewPanel/structureConnection.test.js +0 -53
- package/src/MsaViewPanel/structureConnection.test.ts +0 -62
- /package/dist/{MsaViewPanel/structureConnection.test.d.ts → LaunchMsaView/useQueryRowName.test.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -29,6 +29,13 @@ https://jbrowse.org/code/jb2/main/index.html?config=https://unpkg.com/jbrowse-pl
|
|
|
29
29
|
- Can share sessions with other users which will send relevant settings and
|
|
30
30
|
links to files to automatically open your results
|
|
31
31
|
- The tree or the MSA panel can be loaded separately from each other
|
|
32
|
+
- Builds a cross-species alignment for any gene from the genome view's
|
|
33
|
+
right-click menu: precomputed orthologs (NCBI, PANTHER), the gene's UniRef
|
|
34
|
+
cluster across all of UniProtKB, or a phmmer/blastp search at EBI
|
|
35
|
+
- Aligns and builds trees in the browser, so a UniRef launch needs no job at any
|
|
36
|
+
external service; EBI's aligners remain an option
|
|
37
|
+
- Every launch is also a session-spec URL (`orthologParams`, `searchParams`,
|
|
38
|
+
`connectedTranscript`), see [DEVELOPERS.md](DEVELOPERS.md)
|
|
32
39
|
|
|
33
40
|
## File format supports
|
|
34
41
|
|
|
@@ -12,7 +12,7 @@ import { useTranscriptSelection } from '../useTranscriptSelection';
|
|
|
12
12
|
import CachedBlastResults from './CachedBlastResults';
|
|
13
13
|
import MsaAlgorithmSelect from './MsaAlgorithmSelect';
|
|
14
14
|
import { blastLaunchView } from './blastLaunchView';
|
|
15
|
-
import { databaseOptionsFor, defaultSearchFor, searchPrograms } from './consts';
|
|
15
|
+
import { databaseOptionsFor, defaultMaxHits, defaultSearchFor, searchPrograms, } from './consts';
|
|
16
16
|
import { useStoredMsaAlgorithm, useStoredSearchChoice, } from './searchChoiceStorage';
|
|
17
17
|
import { useCachedBlastResults } from './useCachedBlastResults';
|
|
18
18
|
const useStyles = makeStyles()({
|
|
@@ -41,6 +41,9 @@ const BlastAutomaticPanel = observer(function ({ handleClose, feature, model, ch
|
|
|
41
41
|
// would let them drift into that
|
|
42
42
|
const [search, setSearch] = useStoredSearchChoice();
|
|
43
43
|
const [selectedMsaAlgorithm, setSelectedMsaAlgorithm] = useStoredMsaAlgorithm();
|
|
44
|
+
const [maxHits, setMaxHits] = useState(String(defaultMaxHits));
|
|
45
|
+
const hitCount = Number(maxHits);
|
|
46
|
+
const hitCountValid = Number.isInteger(hitCount) && hitCount >= 1;
|
|
44
47
|
const isPhmmer = search.program === 'phmmer';
|
|
45
48
|
const geneIds = useMemo(() => getGeneIdentifiers(feature), [feature]);
|
|
46
49
|
const { results: cachedResults, error: cachedResultsError } = useCachedBlastResults(geneIds);
|
|
@@ -63,28 +66,31 @@ const BlastAutomaticPanel = observer(function ({ handleClose, feature, model, ch
|
|
|
63
66
|
});
|
|
64
67
|
} }, databaseOptionsFor(search.program).map(val => (React.createElement(MenuItem, { value: val, key: val }, val)))),
|
|
65
68
|
isPhmmer ? null : (React.createElement(MsaAlgorithmSelect, { className: classes.selectField, value: selectedMsaAlgorithm, onChange: setSelectedMsaAlgorithm })),
|
|
69
|
+
React.createElement(TextField2, { variant: "outlined", label: "Hits", className: classes.selectField, type: "number", value: maxHits, onChange: event => {
|
|
70
|
+
setMaxHits(event.target.value);
|
|
71
|
+
}, error: !hitCountValid, helperText: "best-scoring sequences to keep" }),
|
|
66
72
|
React.createElement(TranscriptSelector, { feature: feature, ...transcriptSelection }),
|
|
67
73
|
React.createElement(Typography, { className: classes.infoText },
|
|
68
74
|
isPhmmer
|
|
69
75
|
? `phmmer searches UniProtKB with a profile HMM built from the query,
|
|
70
76
|
so it aligns the hits as it finds them and that alignment is used
|
|
71
77
|
directly — nothing is realigned afterwards. The tree is then built
|
|
72
|
-
from it
|
|
73
|
-
|
|
78
|
+
from it in the browser. A hit matching the query in more than one
|
|
79
|
+
place appears once per matched region. rp15 spreads the hits
|
|
80
|
+
across all of life; swissprot keeps to the curated set.`
|
|
74
81
|
: `This panel will automatically submit a blastp query to EBI, which
|
|
75
|
-
searches UniProtKB.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
alignment.`,
|
|
82
|
+
searches UniProtKB. swissprot returns curated sequences that align
|
|
83
|
+
more cleanly than the many near-identical entries a TrEMBL search
|
|
84
|
+
brings back. After completion, all the hits will be run through
|
|
85
|
+
the chosen aligner; "in browser" needs no second EBI job.`,
|
|
80
86
|
' ',
|
|
81
|
-
"Searching NCBI's nr needs the manual approach: NCBI no longer lets a browser read responses from Blast.cgi."),
|
|
87
|
+
"EBI's queue is the wait, and it varies from seconds to many minutes. For a homolog panel with no job at all, the Orthologs tab's UniRef source is a lookup. Searching NCBI's nr needs the manual approach: NCBI no longer lets a browser read responses from Blast.cgi."),
|
|
82
88
|
cachedResults.length > 0 ? (React.createElement(Accordion, { className: classes.cachedResultsAccordion },
|
|
83
89
|
React.createElement(AccordionSummary, { expandIcon: React.createElement(ExpandMoreIcon, null) },
|
|
84
90
|
React.createElement(Typography, null, "Previous BLAST Results")),
|
|
85
91
|
React.createElement(AccordionDetails, null,
|
|
86
92
|
React.createElement(CachedBlastResults, { model: model, handleClose: handleClose, feature: feature })))) : null),
|
|
87
|
-
React.createElement(SubmitCancelActions, { model: model, submitDisabled: !proteinSequence, onSubmit: () => {
|
|
93
|
+
React.createElement(SubmitCancelActions, { model: model, submitDisabled: !proteinSequence || !hitCountValid, onSubmit: () => {
|
|
88
94
|
try {
|
|
89
95
|
if (selectedTranscript) {
|
|
90
96
|
setLaunchViewError(undefined);
|
|
@@ -96,14 +102,16 @@ const BlastAutomaticPanel = observer(function ({ handleClose, feature, model, ch
|
|
|
96
102
|
? {
|
|
97
103
|
searchProgram: 'phmmer',
|
|
98
104
|
blastDatabase: search.database,
|
|
99
|
-
|
|
105
|
+
maxHits: hitCount,
|
|
106
|
+
selectedTranscript: selectedTranscript.toJSON(),
|
|
100
107
|
proteinSequence,
|
|
101
108
|
}
|
|
102
109
|
: {
|
|
103
110
|
searchProgram: 'blastp',
|
|
104
111
|
blastDatabase: search.database,
|
|
105
112
|
msaAlgorithm: selectedMsaAlgorithm,
|
|
106
|
-
|
|
113
|
+
maxHits: hitCount,
|
|
114
|
+
selectedTranscript: selectedTranscript.toJSON(),
|
|
107
115
|
proteinSequence,
|
|
108
116
|
},
|
|
109
117
|
});
|
|
@@ -88,6 +88,7 @@ const BlastManualPanel = observer(function ({ handleClose, feature, model, child
|
|
|
88
88
|
view,
|
|
89
89
|
feature: selectedTranscript,
|
|
90
90
|
querySeqName: queryRow.querySeqName,
|
|
91
|
+
querySeqOffset: queryRow.querySeqOffset,
|
|
91
92
|
data: { msa: msaText, tree: treeText },
|
|
92
93
|
});
|
|
93
94
|
handleClose();
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { MenuItem } from '@mui/material';
|
|
3
3
|
import TextField2 from '../../../components/TextField2';
|
|
4
|
-
import { msaAlgorithms } from './consts';
|
|
4
|
+
import { msaAlgorithmLabels, msaAlgorithms } from './consts';
|
|
5
5
|
export default function MsaAlgorithmSelect({ value, onChange, className, }) {
|
|
6
|
-
return (React.createElement(TextField2, { variant: "outlined", label: "MSA Algorithm", className: className, select: true, value: value,
|
|
6
|
+
return (React.createElement(TextField2, { variant: "outlined", label: "MSA Algorithm", className: className, select: true, value: value, helperText: value === 'browser'
|
|
7
|
+
? 'no EBI job; rows aligned to the query'
|
|
8
|
+
: undefined, onChange: event => {
|
|
7
9
|
onChange(event.target.value);
|
|
8
|
-
} }, msaAlgorithms.map(val => (React.createElement(MenuItem, { value: val, key: val }, val)))));
|
|
10
|
+
} }, msaAlgorithms.map(val => (React.createElement(MenuItem, { value: val, key: val }, msaAlgorithmLabels[val])))));
|
|
9
11
|
}
|
|
@@ -5,8 +5,20 @@
|
|
|
5
5
|
* cannot read a response from it at all. See docs/blast.md.
|
|
6
6
|
*/
|
|
7
7
|
export declare const BASE_BLAST_URL = "https://blast.ncbi.nlm.nih.gov/Blast.cgi";
|
|
8
|
-
|
|
8
|
+
/** The aligners EBI's Job Dispatcher runs, each a tool name at their REST api. */
|
|
9
|
+
export declare const ebiMsaAlgorithms: readonly ["clustalo", "muscle", "kalign", "mafft"];
|
|
10
|
+
export type EbiMsaAlgorithm = (typeof ebiMsaAlgorithms)[number];
|
|
11
|
+
/**
|
|
12
|
+
* `browser` is no job at all: each sequence is aligned to the query in the
|
|
13
|
+
* page and the rows merged on the query (utils/browserAlign.ts), with the tree
|
|
14
|
+
* built by react-msaview's neighbour joining. It is the aligner for a launch
|
|
15
|
+
* that must not depend on EBI, and for one that wants to be quick -- a
|
|
16
|
+
* hundred rows take a second or two against a Job Dispatcher queue that has
|
|
17
|
+
* been measured at anything from ten seconds to fifteen minutes.
|
|
18
|
+
*/
|
|
19
|
+
export declare const msaAlgorithms: readonly ["clustalo", "muscle", "kalign", "mafft", "browser"];
|
|
9
20
|
export type MsaAlgorithm = (typeof msaAlgorithms)[number];
|
|
21
|
+
export declare const msaAlgorithmLabels: Record<MsaAlgorithm, string>;
|
|
10
22
|
/**
|
|
11
23
|
* EBI rejects a submission naming a database outside its own list with a 400,
|
|
12
24
|
* so every value here has to appear in
|
|
@@ -21,13 +33,21 @@ export declare const searchPrograms: readonly ["blastp", "phmmer"];
|
|
|
21
33
|
export type SearchProgram = (typeof searchPrograms)[number];
|
|
22
34
|
/**
|
|
23
35
|
* phmmer offers PDB, AlphaFold, Ensembl Genomes, MEROPS and ChEMBL too, but
|
|
24
|
-
* targets outside UniProt carry no
|
|
36
|
+
* targets outside UniProt carry no species in their description, so those rows
|
|
25
37
|
* would lose their species and common name. Only the databases that label their
|
|
26
38
|
* hits are offered.
|
|
39
|
+
*
|
|
40
|
+
* rp15..rp75 are the Representative Proteomes: UniProt's reference proteomes
|
|
41
|
+
* thinned so that no two are more than 15% (35%, 55%, 75%) similar, which is
|
|
42
|
+
* the widest taxonomic spread per hit that any of these databases gives. rp15
|
|
43
|
+
* is the one to reach for when the question is "what is this like across all
|
|
44
|
+
* of life"; swissprot when it is "what is this like in the curated set".
|
|
27
45
|
*/
|
|
28
|
-
export declare const phmmerDatabaseOptions: readonly ["swissprot", "uniprotkb", "uniprotrefprot"];
|
|
46
|
+
export declare const phmmerDatabaseOptions: readonly ["swissprot", "uniprotkb", "uniprotrefprot", "rp75", "rp55", "rp35", "rp15"];
|
|
29
47
|
export type PhmmerDatabase = (typeof phmmerDatabaseOptions)[number];
|
|
30
48
|
export declare const defaultPhmmerDatabase: PhmmerDatabase;
|
|
49
|
+
export declare const defaultMaxHits = 100;
|
|
50
|
+
export declare function snapBlastHitCount(maxHits: number): number;
|
|
31
51
|
/**
|
|
32
52
|
* A program together with a database that program actually has.
|
|
33
53
|
*
|
|
@@ -44,4 +64,4 @@ export type SearchChoice = {
|
|
|
44
64
|
database: PhmmerDatabase;
|
|
45
65
|
};
|
|
46
66
|
export declare function defaultSearchFor(program: SearchProgram): SearchChoice;
|
|
47
|
-
export declare function databaseOptionsFor(program: SearchProgram): readonly ["uniprotkb_swissprot", "uniprotkb", "pan_proteomes", "uniprotkb_trembl"] | readonly ["swissprot", "uniprotkb", "uniprotrefprot"];
|
|
67
|
+
export declare function databaseOptionsFor(program: SearchProgram): readonly ["uniprotkb_swissprot", "uniprotkb", "pan_proteomes", "uniprotkb_trembl"] | readonly ["swissprot", "uniprotkb", "uniprotrefprot", "rp75", "rp55", "rp35", "rp15"];
|
|
@@ -5,7 +5,29 @@
|
|
|
5
5
|
* cannot read a response from it at all. See docs/blast.md.
|
|
6
6
|
*/
|
|
7
7
|
export const BASE_BLAST_URL = 'https://blast.ncbi.nlm.nih.gov/Blast.cgi';
|
|
8
|
-
|
|
8
|
+
/** The aligners EBI's Job Dispatcher runs, each a tool name at their REST api. */
|
|
9
|
+
export const ebiMsaAlgorithms = [
|
|
10
|
+
'clustalo',
|
|
11
|
+
'muscle',
|
|
12
|
+
'kalign',
|
|
13
|
+
'mafft',
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* `browser` is no job at all: each sequence is aligned to the query in the
|
|
17
|
+
* page and the rows merged on the query (utils/browserAlign.ts), with the tree
|
|
18
|
+
* built by react-msaview's neighbour joining. It is the aligner for a launch
|
|
19
|
+
* that must not depend on EBI, and for one that wants to be quick -- a
|
|
20
|
+
* hundred rows take a second or two against a Job Dispatcher queue that has
|
|
21
|
+
* been measured at anything from ten seconds to fifteen minutes.
|
|
22
|
+
*/
|
|
23
|
+
export const msaAlgorithms = [...ebiMsaAlgorithms, 'browser'];
|
|
24
|
+
export const msaAlgorithmLabels = {
|
|
25
|
+
clustalo: 'clustalo (EBI)',
|
|
26
|
+
muscle: 'muscle (EBI)',
|
|
27
|
+
kalign: 'kalign (EBI)',
|
|
28
|
+
mafft: 'mafft (EBI)',
|
|
29
|
+
browser: 'in browser, query-anchored',
|
|
30
|
+
};
|
|
9
31
|
/**
|
|
10
32
|
* EBI rejects a submission naming a database outside its own list with a 400,
|
|
11
33
|
* so every value here has to appear in
|
|
@@ -25,16 +47,36 @@ export const defaultBlastDatabase = 'uniprotkb_swissprot';
|
|
|
25
47
|
export const searchPrograms = ['blastp', 'phmmer'];
|
|
26
48
|
/**
|
|
27
49
|
* phmmer offers PDB, AlphaFold, Ensembl Genomes, MEROPS and ChEMBL too, but
|
|
28
|
-
* targets outside UniProt carry no
|
|
50
|
+
* targets outside UniProt carry no species in their description, so those rows
|
|
29
51
|
* would lose their species and common name. Only the databases that label their
|
|
30
52
|
* hits are offered.
|
|
53
|
+
*
|
|
54
|
+
* rp15..rp75 are the Representative Proteomes: UniProt's reference proteomes
|
|
55
|
+
* thinned so that no two are more than 15% (35%, 55%, 75%) similar, which is
|
|
56
|
+
* the widest taxonomic spread per hit that any of these databases gives. rp15
|
|
57
|
+
* is the one to reach for when the question is "what is this like across all
|
|
58
|
+
* of life"; swissprot when it is "what is this like in the curated set".
|
|
31
59
|
*/
|
|
32
60
|
export const phmmerDatabaseOptions = [
|
|
33
61
|
'swissprot',
|
|
34
62
|
'uniprotkb',
|
|
35
63
|
'uniprotrefprot',
|
|
64
|
+
'rp75',
|
|
65
|
+
'rp55',
|
|
66
|
+
'rp35',
|
|
67
|
+
'rp15',
|
|
36
68
|
];
|
|
37
69
|
export const defaultPhmmerDatabase = 'swissprot';
|
|
70
|
+
/**
|
|
71
|
+
* The hit counts EBI's ncbiblast accepts for `alignments` and `scores`. A value
|
|
72
|
+
* off this list is a 400 at submit time, so a request is rounded up to the next
|
|
73
|
+
* one on it.
|
|
74
|
+
*/
|
|
75
|
+
const blastHitCounts = [5, 10, 20, 50, 100, 150, 200, 250, 500, 750, 1000];
|
|
76
|
+
export const defaultMaxHits = 100;
|
|
77
|
+
export function snapBlastHitCount(maxHits) {
|
|
78
|
+
return blastHitCounts.find(n => n >= maxHits) ?? blastHitCounts.at(-1);
|
|
79
|
+
}
|
|
38
80
|
export function defaultSearchFor(program) {
|
|
39
81
|
return program === 'phmmer'
|
|
40
82
|
? { program, database: defaultPhmmerDatabase }
|
|
@@ -4,4 +4,4 @@ export declare const MSA_ALGORITHM_STORAGE_KEY = "msaView-msaAlgorithm";
|
|
|
4
4
|
export declare function validSearchChoice(stored: unknown): SearchChoice;
|
|
5
5
|
export declare function validMsaAlgorithm(stored: unknown): MsaAlgorithm;
|
|
6
6
|
export declare function useStoredSearchChoice(): readonly [SearchChoice, (choice: SearchChoice) => void];
|
|
7
|
-
export declare function useStoredMsaAlgorithm(): readonly ["clustalo" | "muscle" | "kalign" | "mafft", (algorithm: MsaAlgorithm) => void];
|
|
7
|
+
export declare function useStoredMsaAlgorithm(): readonly ["clustalo" | "muscle" | "kalign" | "mafft" | "browser", (algorithm: MsaAlgorithm) => void];
|
|
@@ -69,6 +69,7 @@ const ManualMSALoader = observer(function PreLoadedMSA2({ model, feature, handle
|
|
|
69
69
|
view,
|
|
70
70
|
feature: selectedTranscript,
|
|
71
71
|
querySeqName: queryRow.querySeqName,
|
|
72
|
+
querySeqOffset: queryRow.querySeqOffset,
|
|
72
73
|
...(inputMethod === 'file'
|
|
73
74
|
? {
|
|
74
75
|
msaFilehandle: msaFileLocation,
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { Feature, FileLocation } from '@jbrowse/core/util';
|
|
2
2
|
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view';
|
|
3
|
-
export declare function launchView({ newViewTitle, view, feature, msaFilehandle, treeFilehandle, querySeqName, data, }: {
|
|
3
|
+
export declare function launchView({ newViewTitle, view, feature, msaFilehandle, treeFilehandle, querySeqName, querySeqOffset, data, }: {
|
|
4
4
|
newViewTitle: string;
|
|
5
5
|
view: LinearGenomeViewModel;
|
|
6
6
|
feature: Feature;
|
|
7
7
|
msaFilehandle?: FileLocation;
|
|
8
8
|
treeFilehandle?: FileLocation;
|
|
9
9
|
querySeqName?: string;
|
|
10
|
+
/** transcript residues before the query row's first residue */
|
|
11
|
+
querySeqOffset?: number;
|
|
10
12
|
data?: {
|
|
11
13
|
msa: string;
|
|
12
14
|
tree?: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getSession } from '@jbrowse/core/util';
|
|
2
2
|
import { launchMsaView } from '../../../utils/launchMsaView';
|
|
3
3
|
import { readLaunchPlacement } from '../../../utils/workspaces';
|
|
4
|
-
export function launchView({ newViewTitle, view, feature, msaFilehandle, treeFilehandle, querySeqName, data, }) {
|
|
4
|
+
export function launchView({ newViewTitle, view, feature, msaFilehandle, treeFilehandle, querySeqName, querySeqOffset, data, }) {
|
|
5
5
|
launchMsaView(getSession(view), {
|
|
6
6
|
placement: readLaunchPlacement(),
|
|
7
7
|
displayName: newViewTitle,
|
|
@@ -10,6 +10,7 @@ export function launchView({ newViewTitle, view, feature, msaFilehandle, treeFil
|
|
|
10
10
|
msaFilehandle,
|
|
11
11
|
treeFilehandle,
|
|
12
12
|
querySeqName,
|
|
13
|
+
querySeqOffset,
|
|
13
14
|
data,
|
|
14
15
|
});
|
|
15
16
|
}
|
|
@@ -20,6 +20,11 @@ const useStyles = makeStyles()({
|
|
|
20
20
|
width: 180,
|
|
21
21
|
},
|
|
22
22
|
});
|
|
23
|
+
const rowsHint = {
|
|
24
|
+
ncbi: 'the closest N species NCBI has',
|
|
25
|
+
panther: 'the closest N species PANTHER has',
|
|
26
|
+
uniref: 'one per species, reviewed entries first',
|
|
27
|
+
};
|
|
23
28
|
const OrthologPanel = observer(function ({ handleClose, feature, model, }) {
|
|
24
29
|
const { classes } = useStyles();
|
|
25
30
|
const view = getLinearGenomeView(model);
|
|
@@ -36,14 +41,14 @@ const OrthologPanel = observer(function ({ handleClose, feature, model, }) {
|
|
|
36
41
|
const rowCountValid = Number.isInteger(rowCount) && rowCount >= 2;
|
|
37
42
|
return (React.createElement(React.Fragment, null,
|
|
38
43
|
React.createElement(LaunchPanelContent, { error: e },
|
|
39
|
-
React.createElement(Typography, { variant: "body2" }, "Precomputed orthologs, one gene per species, looked up rather than searched for. No BLAST job to queue."),
|
|
44
|
+
React.createElement(Typography, { variant: "body2" }, "Precomputed orthologs, one gene per species, looked up rather than searched for. No BLAST job to queue, and with the in-browser aligner no job at all."),
|
|
40
45
|
React.createElement("div", null,
|
|
41
46
|
React.createElement(OrthologSourceSelect, { className: classes.selectField, value: source, onChange: setSource }),
|
|
42
47
|
React.createElement(QuerySpeciesSelect, { className: classes.selectField, value: taxId, assemblyName: view.assemblyNames[0], onChange: setTaxId }),
|
|
43
48
|
React.createElement(MsaAlgorithmSelect, { className: classes.selectField, value: msaAlgorithm, onChange: setMsaAlgorithm }),
|
|
44
49
|
React.createElement(TextField2, { variant: "outlined", label: "Rows to align", className: classes.selectField, type: "number", value: maxSpecies, onChange: event => {
|
|
45
50
|
setMaxSpecies(event.target.value);
|
|
46
|
-
}, error: !rowCountValid, helperText:
|
|
51
|
+
}, error: !rowCountValid, helperText: rowsHint[source] })),
|
|
47
52
|
React.createElement(TranscriptSelector, { feature: feature, ...transcriptSelection })),
|
|
48
53
|
React.createElement(SubmitCancelActions, { model: model, submitDisabled: !proteinSequence || !rowCountValid, onSubmit: () => {
|
|
49
54
|
try {
|
|
@@ -59,7 +64,7 @@ const OrthologPanel = observer(function ({ handleClose, feature, model, }) {
|
|
|
59
64
|
maxSpecies: rowCount,
|
|
60
65
|
geneCandidates,
|
|
61
66
|
msaAlgorithm,
|
|
62
|
-
selectedTranscript,
|
|
67
|
+
selectedTranscript: selectedTranscript.toJSON(),
|
|
63
68
|
proteinSequence,
|
|
64
69
|
},
|
|
65
70
|
});
|
|
@@ -5,13 +5,16 @@ export const ORTHOLOG_SOURCE_STORAGE_KEY = 'msaview-ortholog-source';
|
|
|
5
5
|
export const orthologSourceLabels = {
|
|
6
6
|
ncbi: 'NCBI orthologs',
|
|
7
7
|
panther: 'PANTHER',
|
|
8
|
+
uniref: 'UniRef cluster',
|
|
8
9
|
};
|
|
9
10
|
// Which species a source can answer for, in the words a reader picking one
|
|
10
11
|
// needs: NCBI's ortholog sets stop at vertebrates and insects, PANTHER's run
|
|
11
|
-
// from human to yeast and Arabidopsis
|
|
12
|
+
// from human to yeast and Arabidopsis, and a UniRef cluster is every UniProtKB
|
|
13
|
+
// entry within 50% identity of the query, whatever it came from.
|
|
12
14
|
const hints = {
|
|
13
15
|
ncbi: 'vertebrates and insects',
|
|
14
16
|
panther: 'also yeast, worm, fly and plants',
|
|
17
|
+
uniref: 'all of UniProtKB within 50% identity',
|
|
15
18
|
};
|
|
16
19
|
export default function OrthologSourceSelect({ value, onChange, className, }) {
|
|
17
20
|
return (React.createElement(TextField2, { variant: "outlined", label: "Source", className: className, select: true, value: value, helperText: hints[value], onChange: event => {
|
|
@@ -10,6 +10,19 @@ const useStyles = makeStyles()({
|
|
|
10
10
|
marginTop: 10,
|
|
11
11
|
},
|
|
12
12
|
});
|
|
13
|
+
/**
|
|
14
|
+
* Where a trimmed row sits in the protein. The offset is what the view maps
|
|
15
|
+
* through, so the alert says it rather than leaving the user to wonder why the
|
|
16
|
+
* row is shorter than their transcript.
|
|
17
|
+
*/
|
|
18
|
+
function partialCoverage({ identity, offset, }) {
|
|
19
|
+
const covering = `, covering ${Math.round(identity * 100)}% of it`;
|
|
20
|
+
return offset > 0
|
|
21
|
+
? `${covering} from residue ${offset + 1}`
|
|
22
|
+
: offset < 0
|
|
23
|
+
? `${covering} and running ${-offset} residues past its start`
|
|
24
|
+
: covering;
|
|
25
|
+
}
|
|
13
26
|
/**
|
|
14
27
|
* Which MSA row corresponds to the selected transcript. Clicking and hovering in
|
|
15
28
|
* the alignment reach the genome only through this name, and a wrong one fails
|
|
@@ -27,12 +40,15 @@ export default function QueryRowSelector({ names, detected, querySeqName, setQue
|
|
|
27
40
|
detected?.name === name ? ' — matches your protein' : ''))))) : (React.createElement(TextField2, { variant: "outlined", label: "MSA row matching the selected transcript", fullWidth: true, className: classes.field, helperText: "Paste an alignment above and this fills in on its own", value: querySeqName, onChange: event => {
|
|
28
41
|
setQuerySeqName(event.target.value);
|
|
29
42
|
} })),
|
|
30
|
-
isAutoDetected && detected ? (React.createElement(Alert, { severity: "
|
|
43
|
+
isAutoDetected && detected ? (detected.quality === 'similar' ? (React.createElement(Alert, { severity: "warning", className: classes.alert },
|
|
44
|
+
React.createElement("strong", null, detected.name),
|
|
45
|
+
" is",
|
|
46
|
+
' ',
|
|
47
|
+
Math.round(detected.identity * 100),
|
|
48
|
+
"% identical to your protein but is not the same sequence. Clicking the alignment navigates the genome view, approximately: wherever the two differ by an insertion or a deletion, every residue after it lands one codon off.")) : (React.createElement(Alert, { severity: "success", className: classes.alert },
|
|
31
49
|
"Matched ",
|
|
32
50
|
React.createElement("strong", null, detected.name),
|
|
33
51
|
" to your protein sequence",
|
|
34
|
-
detected.quality === 'exact'
|
|
35
|
-
|
|
36
|
-
: `, covering ${Math.round(detected.identity * 100)}% of it`,
|
|
37
|
-
". Clicking the alignment will navigate the genome view.")) : names.length > 0 && !querySeqName ? (React.createElement(Alert, { severity: "warning", className: classes.alert }, "No row matched your protein sequence \u2014 pick the one for your gene above. Without it the alignment still renders, but clicking it will not navigate the genome view.")) : null));
|
|
52
|
+
detected.quality === 'exact' ? '' : partialCoverage(detected),
|
|
53
|
+
". Clicking the alignment will navigate the genome view."))) : names.length > 0 && !querySeqName ? (React.createElement(Alert, { severity: "warning", className: classes.alert }, "No row matched your protein sequence \u2014 pick the one for your gene above. Without it the alignment still renders, but clicking it will not navigate the genome view.")) : null));
|
|
38
54
|
}
|
|
@@ -18,6 +18,17 @@ export interface QueryRowMatch {
|
|
|
18
18
|
quality: MatchQuality;
|
|
19
19
|
/** identity over the compared region, 0-1 */
|
|
20
20
|
identity: number;
|
|
21
|
+
/**
|
|
22
|
+
* Transcript residues before the row's first residue, so transcript position
|
|
23
|
+
* = row position + offset. BLAST reports the aligned region, so a row is
|
|
24
|
+
* often the query with its termini trimmed, and mapping its first residue to
|
|
25
|
+
* the transcript's first residue would put every navigation off by the
|
|
26
|
+
* trimmed amount. Negative when the row is the longer of the two.
|
|
27
|
+
*
|
|
28
|
+
* Zero for a `similar` match, which is the one arm this cannot measure: the
|
|
29
|
+
* row is not the query, so where it starts is a guess.
|
|
30
|
+
*/
|
|
31
|
+
offset: number;
|
|
21
32
|
}
|
|
22
33
|
export interface MsaQueryRow {
|
|
23
34
|
/** every row name, in file order, for the picker to offer */
|
|
@@ -70,20 +70,27 @@ function bestMatch(parsed, names, query) {
|
|
|
70
70
|
}
|
|
71
71
|
if (row === query) {
|
|
72
72
|
// nothing beats an exact match, and a second one would be a duplicate row
|
|
73
|
-
return { name, quality: 'exact', identity: 1 };
|
|
73
|
+
return { name, quality: 'exact', identity: 1, offset: 0 };
|
|
74
74
|
}
|
|
75
75
|
// BLAST reports the aligned region, so the row is often the query trimmed
|
|
76
76
|
// at one or both ends rather than the whole protein
|
|
77
|
-
|
|
77
|
+
const contained = query.indexOf(row);
|
|
78
|
+
const containing = row.indexOf(query);
|
|
79
|
+
if (contained !== -1 || containing !== -1) {
|
|
78
80
|
const coverage = Math.min(row.length, query.length) / Math.max(row.length, query.length);
|
|
79
81
|
if (coverage >= PARTIAL_COVERAGE_FLOOR) {
|
|
80
|
-
candidates.push({
|
|
82
|
+
candidates.push({
|
|
83
|
+
name,
|
|
84
|
+
quality: 'partial',
|
|
85
|
+
identity: coverage,
|
|
86
|
+
offset: contained === -1 ? -containing : contained,
|
|
87
|
+
});
|
|
81
88
|
}
|
|
82
89
|
continue;
|
|
83
90
|
}
|
|
84
91
|
const identity = identityOverOverlap(row, query);
|
|
85
92
|
if (identity >= SIMILARITY_FLOOR) {
|
|
86
|
-
candidates.push({ name, quality: 'similar', identity });
|
|
93
|
+
candidates.push({ name, quality: 'similar', identity, offset: 0 });
|
|
87
94
|
}
|
|
88
95
|
}
|
|
89
96
|
// an exact match returns above, so only these two can be here
|
|
@@ -33,6 +33,36 @@ describe('findQueryRow', () => {
|
|
|
33
33
|
quality: 'partial',
|
|
34
34
|
});
|
|
35
35
|
});
|
|
36
|
+
// the offset is what the view maps genome coordinates through: without it an
|
|
37
|
+
// N-terminally trimmed row puts every click five codons off, silently
|
|
38
|
+
test('says how far into the protein a trimmed row starts', () => {
|
|
39
|
+
const trimmed = `>aligned_query\n${protein.slice(5, 40)}\n>hit_one\nWRONGWRONGWRONGWRONG\n`;
|
|
40
|
+
expect(findQueryRow(trimmed, protein).match).toMatchObject({
|
|
41
|
+
name: 'aligned_query',
|
|
42
|
+
quality: 'partial',
|
|
43
|
+
offset: 5,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
test('an exact match starts where the protein does', () => {
|
|
47
|
+
expect(findQueryRow(clustal, protein).match?.offset).toBe(0);
|
|
48
|
+
});
|
|
49
|
+
test('a row running past the protein gets a negative offset', () => {
|
|
50
|
+
const extended = `>longer_row\nMMM${protein}\n`;
|
|
51
|
+
expect(findQueryRow(extended, protein).match).toMatchObject({
|
|
52
|
+
name: 'longer_row',
|
|
53
|
+
quality: 'partial',
|
|
54
|
+
offset: -3,
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
// the one arm with nothing to measure: a 90%-identical row is not the query,
|
|
58
|
+
// so QueryRowSelector warns rather than claiming a match
|
|
59
|
+
test('a merely similar row claims no offset', () => {
|
|
60
|
+
const similar = protein.slice(0, 30) + 'W' + protein.slice(31);
|
|
61
|
+
expect(findQueryRow(`>near\n${similar}\n`, protein).match).toMatchObject({
|
|
62
|
+
quality: 'similar',
|
|
63
|
+
offset: 0,
|
|
64
|
+
});
|
|
65
|
+
});
|
|
36
66
|
// the failure that matters: silently wiring the view to a homolog would look
|
|
37
67
|
// like it worked, and every navigation afterwards would land in the wrong place
|
|
38
68
|
test('returns nothing when only diverged homologs are present', () => {
|
|
@@ -10,6 +10,7 @@ export declare function useQueryRowName(msaText: string, proteinSequence: string
|
|
|
10
10
|
detected: import("./detectQueryRow").QueryRowMatch | undefined;
|
|
11
11
|
names: string[];
|
|
12
12
|
querySeqName: string;
|
|
13
|
+
querySeqOffset: number;
|
|
13
14
|
setQuerySeqName: import("react").Dispatch<import("react").SetStateAction<string | undefined>>;
|
|
14
15
|
isAutoDetected: boolean;
|
|
15
16
|
};
|
|
@@ -13,10 +13,14 @@ export function useQueryRowName(msaText, proteinSequence) {
|
|
|
13
13
|
// parsing runs on every keystroke in the paste box otherwise, and an
|
|
14
14
|
// alignment of a few hundred rows is not free
|
|
15
15
|
const { names, match } = useMemo(() => findQueryRow(msaText, proteinSequence), [msaText, proteinSequence]);
|
|
16
|
+
const querySeqName = override ?? match?.name ?? '';
|
|
16
17
|
return {
|
|
17
18
|
detected: match,
|
|
18
19
|
names,
|
|
19
|
-
querySeqName
|
|
20
|
+
querySeqName,
|
|
21
|
+
// only the detected row's offset is known; a row the user picked instead is
|
|
22
|
+
// taken at face value, residue 1 to residue 1
|
|
23
|
+
querySeqOffset: querySeqName === match?.name ? match.offset : 0,
|
|
20
24
|
setQuerySeqName: setOverride,
|
|
21
25
|
isAutoDetected: override === undefined && !!match,
|
|
22
26
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { act, renderHook } from '@testing-library/react';
|
|
3
|
+
import { describe, expect, test } from 'vitest';
|
|
4
|
+
import { useQueryRowName } from './useQueryRowName';
|
|
5
|
+
const protein = 'MKWVTFISLLLLFSSAYSRGVFRRDTHKSEIAHRFKDLGEEHFKGLVLIAFSQYLQQCPFD';
|
|
6
|
+
const trimmed = `>aligned_query\n${protein.slice(5, 45)}\n>other\nWRONGWRONGWRONGWRONGWRONGWRONGWRONGWRONG\n`;
|
|
7
|
+
describe('useQueryRowName', () => {
|
|
8
|
+
test('carries the detected row and where it starts', () => {
|
|
9
|
+
const { result } = renderHook(() => useQueryRowName(trimmed, protein));
|
|
10
|
+
expect(result.current.querySeqName).toBe('aligned_query');
|
|
11
|
+
expect(result.current.querySeqOffset).toBe(5);
|
|
12
|
+
});
|
|
13
|
+
// the offset was measured against the detected row; another row is a row the
|
|
14
|
+
// user vouched for, and nothing here knows where in the protein it sits
|
|
15
|
+
test('a row the user picks instead is taken at face value', () => {
|
|
16
|
+
const { result } = renderHook(() => useQueryRowName(trimmed, protein));
|
|
17
|
+
act(() => {
|
|
18
|
+
result.current.setQuerySeqName('other');
|
|
19
|
+
});
|
|
20
|
+
expect(result.current.querySeqName).toBe('other');
|
|
21
|
+
expect(result.current.querySeqOffset).toBe(0);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { launchMsaView } from '../utils/launchMsaView';
|
|
2
2
|
export default function LaunchMsaViewExtensionPointF(pluginManager) {
|
|
3
3
|
pluginManager.addToExtensionPoint('LaunchView-MsaView', (args) => {
|
|
4
|
-
const { session, data, msaFileLocation, msaIndexedLocation, msaName, treeFileLocation, querySeqName, ...rest } = args;
|
|
5
|
-
// `orthologParams`
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
const { session, data, msaFileLocation, msaIndexedLocation, msaName, treeFileLocation, querySeqName, searchParams, ...rest } = args;
|
|
5
|
+
// `orthologParams` and `searchParams` name no alignment at all — the
|
|
6
|
+
// view builds one at launch, which is the dialog's Orthologs and BLAST
|
|
7
|
+
// tabs reached declaratively.
|
|
8
8
|
if (!data &&
|
|
9
9
|
!msaFileLocation &&
|
|
10
10
|
!msaIndexedLocation &&
|
|
11
|
-
!rest.orthologParams
|
|
12
|
-
|
|
11
|
+
!rest.orthologParams &&
|
|
12
|
+
!searchParams) {
|
|
13
|
+
throw new Error('No MSA data, file location, orthologParams or searchParams provided when launching MSA view');
|
|
13
14
|
}
|
|
14
15
|
// inline data and the tree URL are native react-msaview snapshot props, set
|
|
15
16
|
// directly, and so is orthologParams (the model's own autorun picks it up).
|
|
@@ -27,6 +28,7 @@ export default function LaunchMsaViewExtensionPointF(pluginManager) {
|
|
|
27
28
|
};
|
|
28
29
|
launchMsaView(session, {
|
|
29
30
|
...rest,
|
|
31
|
+
...(searchParams ? { blastParams: searchParams } : {}),
|
|
30
32
|
data,
|
|
31
33
|
...(treeFileLocation
|
|
32
34
|
? {
|
|
@@ -32,6 +32,27 @@ test('a file location travels through init', () => {
|
|
|
32
32
|
querySeqName: undefined,
|
|
33
33
|
});
|
|
34
34
|
});
|
|
35
|
+
test('searchParams is a source, stored under the name the dialog uses', () => {
|
|
36
|
+
const snapshot = launch({
|
|
37
|
+
searchParams: {
|
|
38
|
+
searchProgram: 'phmmer',
|
|
39
|
+
blastDatabase: 'rp15',
|
|
40
|
+
accession: 'P04637',
|
|
41
|
+
},
|
|
42
|
+
connectedViewId: 'lgv1',
|
|
43
|
+
connectedTranscript: 'NM_000546.6',
|
|
44
|
+
});
|
|
45
|
+
expect(snapshot.blastParams).toEqual({
|
|
46
|
+
searchProgram: 'phmmer',
|
|
47
|
+
blastDatabase: 'rp15',
|
|
48
|
+
accession: 'P04637',
|
|
49
|
+
});
|
|
50
|
+
expect('searchParams' in snapshot).toBe(false);
|
|
51
|
+
expect(snapshot.connectedTranscript).toBe('NM_000546.6');
|
|
52
|
+
});
|
|
53
|
+
test('a launch naming no source at all is refused', () => {
|
|
54
|
+
expect(() => launch({ connectedViewId: 'lgv1' })).toThrow(/searchParams/);
|
|
55
|
+
});
|
|
35
56
|
test('one field set is enough to need init', () => {
|
|
36
57
|
const snapshot = launch({ data: { msa: '>a\nMEEP' }, querySeqName: 'QUERY' });
|
|
37
58
|
expect(snapshot.init).toEqual({
|
|
@@ -12,8 +12,9 @@ export declare function loadStoredData(self: JBrowsePluginMsaViewModel): void;
|
|
|
12
12
|
* it is recorded whether or not the write succeeded, so a browser refusing
|
|
13
13
|
* IndexedDB (private mode) fails once rather than in a loop.
|
|
14
14
|
*
|
|
15
|
-
* A view whose data comes from a filehandle
|
|
16
|
-
*
|
|
15
|
+
* A view whose data comes from a filehandle -- or from the indexed block its
|
|
16
|
+
* kept `init` names -- stores nothing at all: the file is the source of truth
|
|
17
|
+
* and it is refetched at startup.
|
|
17
18
|
*/
|
|
18
19
|
export declare function storeDataToIndexedDB(self: JBrowsePluginMsaViewModel): void;
|
|
19
20
|
/**
|