jbrowse-plugin-msaview 3.4.0 → 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 +23 -17
- 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 +7 -0
- package/dist/LaunchMsaView/components/BlastQuery/searchChoiceStorage.js +42 -0
- package/dist/LaunchMsaView/components/BlastQuery/searchChoiceStorage.test.js +32 -0
- 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 +10 -4
- 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.d.ts +1 -0
- package/dist/LaunchMsaView/useQueryRowName.test.js +23 -0
- package/dist/LaunchMsaViewExtensionPoint/index.js +19 -12
- package/dist/LaunchMsaViewExtensionPoint/index.test.d.ts +1 -0
- package/dist/LaunchMsaViewExtensionPoint/index.test.js +64 -0
- package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +16 -0
- package/dist/MsaViewPanel/afterCreateAutoruns.js +138 -86
- package/dist/MsaViewPanel/components/LaunchProgress.js +23 -5
- package/dist/MsaViewPanel/components/MsaViewPanel.js +6 -2
- package/dist/MsaViewPanel/components/MsaViewPanel.test.js +40 -0
- package/dist/MsaViewPanel/doLaunchBlast.d.ts +11 -1
- package/dist/MsaViewPanel/doLaunchBlast.js +79 -96
- package/dist/MsaViewPanel/doLaunchBlast.test.d.ts +1 -0
- package/dist/MsaViewPanel/doLaunchBlast.test.js +158 -0
- package/dist/MsaViewPanel/doLaunchOrthologs.d.ts +3 -1
- package/dist/MsaViewPanel/doLaunchOrthologs.js +46 -10
- package/dist/MsaViewPanel/doLaunchOrthologs.test.js +48 -24
- 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 +306 -85
- package/dist/MsaViewPanel/model.js +169 -42
- 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 +7 -3
- package/dist/MsaViewPanel/msaDataStore.js +26 -7
- package/dist/MsaViewPanel/msaDataStore.test.d.ts +1 -0
- package/dist/MsaViewPanel/msaDataStore.test.js +44 -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 +39 -0
- package/dist/MsaViewPanel/runLaunch.js +100 -0
- package/dist/MsaViewPanel/runLaunch.test.d.ts +1 -0
- package/dist/MsaViewPanel/runLaunch.test.js +175 -0
- package/dist/MsaViewPanel/storedData.test.d.ts +1 -0
- package/dist/MsaViewPanel/storedData.test.js +160 -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 +46 -45
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/dist/utils/blastCache.js +32 -0
- package/dist/utils/blastCache.test.d.ts +1 -0
- package/dist/utils/blastCache.test.js +72 -0
- 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 +13 -2
- package/dist/utils/ebiBlast.js +32 -4
- package/dist/utils/ebiJobDispatcher.d.ts +6 -3
- package/dist/utils/ebiJobDispatcher.js +13 -6
- package/dist/utils/fetch.d.ts +8 -1
- package/dist/utils/fetch.js +28 -2
- package/dist/utils/homologSearch.d.ts +31 -0
- package/dist/utils/homologSearch.js +6 -0
- package/dist/utils/msa.d.ts +5 -11
- package/dist/utils/msa.js +19 -30
- package/dist/utils/msaRows.d.ts +11 -8
- package/dist/utils/msaRows.js +14 -12
- package/dist/utils/ncbiDomains.d.ts +2 -2
- package/dist/utils/ncbiOrthologs.d.ts +11 -1
- package/dist/utils/ncbiOrthologs.js +26 -7
- package/dist/utils/ncbiOrthologs.test.js +23 -1
- package/dist/utils/pantherOrthologs.js +2 -10
- package/dist/utils/phmmer.d.ts +12 -1
- package/dist/utils/phmmer.js +52 -13
- package/dist/utils/poll.d.ts +6 -1
- package/dist/utils/poll.js +7 -2
- 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 +17 -16
- package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +40 -19
- 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/BlastQuery/searchChoiceStorage.test.ts +43 -0
- package/src/LaunchMsaView/components/BlastQuery/searchChoiceStorage.ts +64 -0
- 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 +12 -5
- 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 +74 -0
- package/src/LaunchMsaViewExtensionPoint/index.ts +53 -12
- package/src/MsaViewPanel/afterCreateAutoruns.ts +150 -81
- package/src/MsaViewPanel/components/LaunchProgress.tsx +46 -4
- package/src/MsaViewPanel/components/MsaViewPanel.test.tsx +43 -0
- package/src/MsaViewPanel/components/MsaViewPanel.tsx +8 -2
- package/src/MsaViewPanel/doLaunchBlast.test.ts +207 -0
- package/src/MsaViewPanel/doLaunchBlast.ts +94 -140
- package/src/MsaViewPanel/doLaunchOrthologs.test.ts +54 -26
- package/src/MsaViewPanel/doLaunchOrthologs.ts +63 -9
- 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 +200 -49
- package/src/MsaViewPanel/msaCoordToGenomeCoord.test.ts +152 -32
- package/src/MsaViewPanel/msaCoordToGenomeCoord.ts +21 -11
- package/src/MsaViewPanel/msaDataStore.test.ts +54 -0
- package/src/MsaViewPanel/msaDataStore.ts +32 -13
- 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 +208 -0
- package/src/MsaViewPanel/runLaunch.ts +141 -0
- package/src/MsaViewPanel/storedData.test.ts +196 -0
- package/src/MsaViewPanel/structureConnection.ts +0 -26
- package/src/MsaViewPanel/util.ts +75 -0
- package/src/utils/blastCache.test.ts +86 -0
- package/src/utils/blastCache.ts +34 -1
- package/src/utils/browserAlign.test.ts +102 -0
- package/src/utils/browserAlign.ts +352 -0
- package/src/utils/ebiBlast.ts +42 -2
- package/src/utils/ebiJobDispatcher.ts +18 -3
- package/src/utils/fetch.ts +29 -2
- package/src/utils/homologSearch.ts +51 -0
- package/src/utils/msa.ts +25 -39
- package/src/utils/msaRows.ts +20 -16
- package/src/utils/ncbiDomains.ts +4 -2
- package/src/utils/ncbiOrthologs.test.ts +25 -0
- package/src/utils/ncbiOrthologs.ts +27 -7
- package/src/utils/pantherOrthologs.ts +6 -11
- package/src/utils/phmmer.ts +62 -12
- package/src/utils/poll.ts +8 -1
- 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/components/BlastQuery/searchChoiceStorage.test.d.ts} +0 -0
|
@@ -1,116 +1,99 @@
|
|
|
1
|
-
import { makeId, strip } from '../LaunchMsaView/components/util';
|
|
2
1
|
import { cleanProteinSequence } from '../LaunchMsaView/util';
|
|
3
2
|
import { saveBlastResult } from '../utils/blastCache';
|
|
4
|
-
import {
|
|
5
|
-
import { launchMSA
|
|
6
|
-
import {
|
|
7
|
-
import { queryPhmmer } from '../utils/phmmer';
|
|
3
|
+
import { searchBackends } from '../utils/homologSearch';
|
|
4
|
+
import { launchMSA } from '../utils/msa';
|
|
5
|
+
import { buildSearchMsa } from '../utils/msaRows';
|
|
8
6
|
import { fetchTaxonomyInfo } from '../utils/taxonomyNames';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const { selectedTranscript } = params;
|
|
14
|
-
const cleanedSeq = cleanProteinSequence(params.proteinSequence);
|
|
15
|
-
const onProgress = (arg) => {
|
|
16
|
-
self.setProgress(arg);
|
|
17
|
-
};
|
|
18
|
-
// publish the job id before the first poll so the view can link out while the
|
|
19
|
-
// job is still running
|
|
20
|
-
const onRid = (r) => {
|
|
21
|
-
self.setRid(r);
|
|
22
|
-
};
|
|
23
|
-
const { msa, tree, treeMetadata, rid } = params.searchProgram === 'phmmer'
|
|
24
|
-
? await runPhmmer({
|
|
25
|
-
query: cleanedSeq,
|
|
26
|
-
database: params.blastDatabase,
|
|
27
|
-
onProgress,
|
|
28
|
-
onRid,
|
|
29
|
-
})
|
|
30
|
-
: await runBlast({
|
|
31
|
-
query: cleanedSeq,
|
|
32
|
-
blastDatabase: params.blastDatabase,
|
|
33
|
-
msaAlgorithm: params.msaAlgorithm,
|
|
34
|
-
onProgress,
|
|
35
|
-
onRid,
|
|
36
|
-
});
|
|
37
|
-
const treeMetadataJson = JSON.stringify(treeMetadata);
|
|
38
|
-
await saveBlastResult({
|
|
39
|
-
proteinSequence: cleanedSeq,
|
|
40
|
-
blastDatabase: params.blastDatabase,
|
|
41
|
-
msaAlgorithm: params.msaAlgorithm,
|
|
42
|
-
searchProgram: params.searchProgram,
|
|
43
|
-
msa,
|
|
44
|
-
tree,
|
|
45
|
-
treeMetadata: treeMetadataJson,
|
|
46
|
-
rid,
|
|
47
|
-
geneId: selectedTranscript?.get('parentId'),
|
|
48
|
-
transcriptId: selectedTranscript?.id(),
|
|
49
|
-
transcriptName: selectedTranscript?.get('name') ?? selectedTranscript?.get('id'),
|
|
50
|
-
geneName: selectedTranscript?.get('gene_name') ??
|
|
51
|
-
selectedTranscript?.get('parentId'),
|
|
52
|
-
});
|
|
53
|
-
return { msa, tree, treeMetadata: treeMetadataJson };
|
|
7
|
+
import { resolveUniProtEntry } from '../utils/unirefHomologs';
|
|
8
|
+
import { transcriptFields, transcriptName } from './util';
|
|
9
|
+
function asString(val) {
|
|
10
|
+
return typeof val === 'string' ? val : undefined;
|
|
54
11
|
}
|
|
55
12
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
13
|
+
* The query sequence, and what its row is called. The dialog hands over the
|
|
14
|
+
* translated transcript and the row stays `QUERY`; a spec naming a UniProt
|
|
15
|
+
* accession has the sequence fetched and the row named after the entry
|
|
16
|
+
* (`P53_HUMAN_query`), since a search of swissprot returns the entry itself
|
|
17
|
+
* as a hit and two rows called the same thing collapse into one.
|
|
59
18
|
*/
|
|
60
|
-
async function
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
});
|
|
67
|
-
onProgress('Fetching species taxonomy info...');
|
|
68
|
-
const taxonomyInfo = await fetchTaxonomyInfo(hits
|
|
69
|
-
.map(h => h.description[0]?.taxid)
|
|
70
|
-
.filter((t) => t !== undefined));
|
|
71
|
-
const treeMetadata = {};
|
|
72
|
-
const sequences = hits.map(h => {
|
|
73
|
-
const desc = h.description[0] ?? {
|
|
74
|
-
accession: 'unknown',
|
|
75
|
-
id: 'unknown',
|
|
76
|
-
sciname: 'unknown',
|
|
19
|
+
async function resolveQuery(self, scope) {
|
|
20
|
+
const params = self.blastParams;
|
|
21
|
+
if (params.proteinSequence) {
|
|
22
|
+
return {
|
|
23
|
+
sequence: cleanProteinSequence(params.proteinSequence),
|
|
24
|
+
name: self.querySeqName,
|
|
77
25
|
};
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
26
|
+
}
|
|
27
|
+
if (params.accession) {
|
|
28
|
+
scope.onProgress(`Fetching ${params.accession} from UniProt...`);
|
|
29
|
+
const entry = await resolveUniProtEntry([params.accession], 0, scope.signal);
|
|
30
|
+
if (!entry) {
|
|
31
|
+
throw new Error(`UniProt has no entry ${params.accession}`);
|
|
32
|
+
}
|
|
33
|
+
const name = `${entry.id}_query`;
|
|
34
|
+
scope.act(() => {
|
|
35
|
+
self.setQuerySeqName(name);
|
|
36
|
+
});
|
|
37
|
+
return { sequence: entry.sequence, name };
|
|
38
|
+
}
|
|
39
|
+
throw new Error('No query: a search needs a proteinSequence, a UniProt accession, or a connectedTranscript to translate');
|
|
88
40
|
}
|
|
89
41
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
42
|
+
* A similarity search, then an alignment of what it found. The program is a
|
|
43
|
+
* backend behind one interface (utils/homologSearch.ts); what differs between
|
|
44
|
+
* them is settled by whether the result came back aligned. A program that
|
|
45
|
+
* aligns as it searches (phmmer) hands over the alignment and the tree is
|
|
46
|
+
* built from it in the browser; one that does not (blastp) hands over bare
|
|
47
|
+
* hits and the chosen aligner runs on them.
|
|
95
48
|
*/
|
|
96
|
-
async function
|
|
97
|
-
const
|
|
49
|
+
export async function doLaunchBlast({ self, scope, }) {
|
|
50
|
+
const params = self.blastParams;
|
|
51
|
+
const { selectedTranscript, maxHits, searchProgram = 'blastp' } = params;
|
|
52
|
+
const { sequence: query, name: querySeqName } = await resolveQuery(self, scope);
|
|
53
|
+
const { onProgress, onRid, signal } = scope;
|
|
54
|
+
const { hits, queryRow, rid } = await searchBackends[searchProgram]({
|
|
98
55
|
query,
|
|
99
|
-
database,
|
|
56
|
+
database: params.blastDatabase,
|
|
57
|
+
maxHits,
|
|
100
58
|
onProgress,
|
|
101
59
|
onRid,
|
|
60
|
+
signal,
|
|
102
61
|
});
|
|
62
|
+
if (hits.length === 0) {
|
|
63
|
+
throw new Error('No hits found');
|
|
64
|
+
}
|
|
103
65
|
onProgress('Fetching species taxonomy info...');
|
|
104
|
-
const taxonomyInfo = await fetchTaxonomyInfo(
|
|
105
|
-
const { msa, treeMetadata } =
|
|
106
|
-
|
|
66
|
+
const taxonomyInfo = await fetchTaxonomyInfo(hits.map(h => h.taxid).filter((t) => t !== undefined));
|
|
67
|
+
const { msa: fasta, treeMetadata } = buildSearchMsa({
|
|
68
|
+
hits,
|
|
69
|
+
query,
|
|
107
70
|
queryRow,
|
|
108
71
|
taxonomyInfo,
|
|
72
|
+
querySeqName,
|
|
109
73
|
});
|
|
110
|
-
|
|
74
|
+
const { msa, tree } = queryRow
|
|
75
|
+
? { msa: fasta, tree: '' }
|
|
76
|
+
: await launchMSA({
|
|
77
|
+
algorithm: params.msaAlgorithm ?? 'browser',
|
|
78
|
+
sequence: fasta,
|
|
79
|
+
onProgress,
|
|
80
|
+
signal,
|
|
81
|
+
});
|
|
82
|
+
const transcript = transcriptFields(selectedTranscript);
|
|
83
|
+
const treeMetadataJson = JSON.stringify(treeMetadata);
|
|
84
|
+
await saveBlastResult({
|
|
85
|
+
proteinSequence: query,
|
|
86
|
+
blastDatabase: params.blastDatabase,
|
|
87
|
+
msaAlgorithm: params.msaAlgorithm,
|
|
88
|
+
searchProgram: params.searchProgram,
|
|
111
89
|
msa,
|
|
112
|
-
tree
|
|
113
|
-
treeMetadata,
|
|
114
|
-
rid,
|
|
115
|
-
|
|
90
|
+
tree,
|
|
91
|
+
treeMetadata: treeMetadataJson,
|
|
92
|
+
rid: rid ?? '',
|
|
93
|
+
geneId: asString(transcript.parentId),
|
|
94
|
+
transcriptId: asString(transcript.uniqueId),
|
|
95
|
+
transcriptName: transcriptName(selectedTranscript),
|
|
96
|
+
geneName: asString(transcript.gene_name) ?? asString(transcript.parentId),
|
|
97
|
+
});
|
|
98
|
+
return { msa, tree, treeMetadata: treeMetadataJson };
|
|
116
99
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { beforeEach, expect, test, vi } from 'vitest';
|
|
2
|
+
import { saveBlastResult } from '../utils/blastCache';
|
|
3
|
+
import { searchBackends } from '../utils/homologSearch';
|
|
4
|
+
import { launchMSA } from '../utils/msa';
|
|
5
|
+
import { fetchTaxonomyInfo } from '../utils/taxonomyNames';
|
|
6
|
+
import { resolveUniProtEntry } from '../utils/unirefHomologs';
|
|
7
|
+
import { doLaunchBlast } from './doLaunchBlast';
|
|
8
|
+
// Every network call is mocked: what is under test is how the launch turns a
|
|
9
|
+
// request into a search and the search's answer into rows -- which backend it
|
|
10
|
+
// asks, where the query comes from, and whether an aligner runs.
|
|
11
|
+
vi.mock('../utils/homologSearch', () => ({
|
|
12
|
+
searchBackends: { blastp: vi.fn(), phmmer: vi.fn() },
|
|
13
|
+
}));
|
|
14
|
+
vi.mock('../utils/msa', () => ({ launchMSA: vi.fn() }));
|
|
15
|
+
vi.mock('../utils/taxonomyNames', () => ({ fetchTaxonomyInfo: vi.fn() }));
|
|
16
|
+
vi.mock('../utils/blastCache', () => ({ saveBlastResult: vi.fn() }));
|
|
17
|
+
vi.mock('../utils/unirefHomologs', () => ({ resolveUniProtEntry: vi.fn() }));
|
|
18
|
+
const blastp = vi.mocked(searchBackends.blastp);
|
|
19
|
+
const phmmer = vi.mocked(searchBackends.phmmer);
|
|
20
|
+
const mockLaunchMSA = vi.mocked(launchMSA);
|
|
21
|
+
const setQuerySeqName = vi.fn();
|
|
22
|
+
const HIT = { accession: 'P1', id: 'P1_MOUSE', sciname: 'Mus musculus' };
|
|
23
|
+
function makeModel(blastParams) {
|
|
24
|
+
return {
|
|
25
|
+
blastParams,
|
|
26
|
+
querySeqName: 'QUERY',
|
|
27
|
+
setQuerySeqName,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function launch(self) {
|
|
31
|
+
return doLaunchBlast({
|
|
32
|
+
self,
|
|
33
|
+
scope: {
|
|
34
|
+
signal: new AbortController().signal,
|
|
35
|
+
act: fn => {
|
|
36
|
+
fn();
|
|
37
|
+
},
|
|
38
|
+
onProgress: () => { },
|
|
39
|
+
onRid: () => { },
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
vi.clearAllMocks();
|
|
45
|
+
vi.mocked(fetchTaxonomyInfo).mockResolvedValue(new Map());
|
|
46
|
+
vi.mocked(saveBlastResult).mockResolvedValue(undefined);
|
|
47
|
+
});
|
|
48
|
+
test('bare hits go to the chosen aligner, with the query first', async () => {
|
|
49
|
+
blastp.mockResolvedValue({ rid: 'job', hits: [{ ...HIT, sequence: 'MKWV' }] });
|
|
50
|
+
mockLaunchMSA.mockResolvedValue({ msa: 'aligned', tree: 'tree' });
|
|
51
|
+
const result = await launch(makeModel({
|
|
52
|
+
searchProgram: 'blastp',
|
|
53
|
+
blastDatabase: 'uniprotkb_swissprot',
|
|
54
|
+
msaAlgorithm: 'clustalo',
|
|
55
|
+
maxHits: 20,
|
|
56
|
+
proteinSequence: 'MKWVTF*',
|
|
57
|
+
}));
|
|
58
|
+
expect(blastp).toHaveBeenCalledWith(expect.objectContaining({
|
|
59
|
+
query: 'MKWVTF',
|
|
60
|
+
database: 'uniprotkb_swissprot',
|
|
61
|
+
maxHits: 20,
|
|
62
|
+
}));
|
|
63
|
+
expect(mockLaunchMSA).toHaveBeenCalledWith(expect.objectContaining({
|
|
64
|
+
algorithm: 'clustalo',
|
|
65
|
+
sequence: '>QUERY\nMKWVTF\n>P1-Mus_musculus\nMKWV',
|
|
66
|
+
}));
|
|
67
|
+
expect(result.msa).toBe('aligned');
|
|
68
|
+
expect(result.tree).toBe('tree');
|
|
69
|
+
});
|
|
70
|
+
test('an aligned result skips the aligner and leaves the tree to the browser', async () => {
|
|
71
|
+
phmmer.mockResolvedValue({
|
|
72
|
+
rid: 'job',
|
|
73
|
+
queryRow: 'MKWV-TF',
|
|
74
|
+
hits: [{ ...HIT, sequence: 'MKWVSTF' }],
|
|
75
|
+
});
|
|
76
|
+
const result = await launch(makeModel({
|
|
77
|
+
searchProgram: 'phmmer',
|
|
78
|
+
blastDatabase: 'rp15',
|
|
79
|
+
proteinSequence: 'MKWVTF',
|
|
80
|
+
}));
|
|
81
|
+
expect(mockLaunchMSA).not.toHaveBeenCalled();
|
|
82
|
+
expect(result.msa).toBe('>QUERY\nMKWV-TF\n>P1-Mus_musculus\nMKWVSTF');
|
|
83
|
+
expect(result.tree).toBe('');
|
|
84
|
+
});
|
|
85
|
+
test('a UniProt accession supplies the query and names its row', async () => {
|
|
86
|
+
vi.mocked(resolveUniProtEntry).mockResolvedValue({
|
|
87
|
+
accession: 'P04637',
|
|
88
|
+
id: 'P53_HUMAN',
|
|
89
|
+
reviewed: true,
|
|
90
|
+
taxId: 9606,
|
|
91
|
+
scientificName: 'Homo sapiens',
|
|
92
|
+
sequence: 'MEEPQ',
|
|
93
|
+
});
|
|
94
|
+
phmmer.mockResolvedValue({
|
|
95
|
+
queryRow: 'MEEPQ',
|
|
96
|
+
hits: [{ ...HIT, sequence: 'MEEPQ' }],
|
|
97
|
+
});
|
|
98
|
+
const result = await launch(makeModel({
|
|
99
|
+
searchProgram: 'phmmer',
|
|
100
|
+
blastDatabase: 'swissprot',
|
|
101
|
+
accession: 'P04637',
|
|
102
|
+
}));
|
|
103
|
+
expect(resolveUniProtEntry).toHaveBeenCalledWith(['P04637'], 0, expect.any(AbortSignal));
|
|
104
|
+
expect(setQuerySeqName).toHaveBeenCalledWith('P53_HUMAN_query');
|
|
105
|
+
expect(result.msa.split('\n')[0]).toBe('>P53_HUMAN_query');
|
|
106
|
+
});
|
|
107
|
+
test('a request with no query at all is refused before any search runs', async () => {
|
|
108
|
+
await expect(launch(makeModel({ searchProgram: 'phmmer', blastDatabase: 'swissprot' }))).rejects.toThrow(/connectedTranscript/);
|
|
109
|
+
expect(phmmer).not.toHaveBeenCalled();
|
|
110
|
+
});
|
|
111
|
+
// blastParams is a frozen snapshot property, so the Feature the dialog put in
|
|
112
|
+
// it comes back from a session reload as the plain JSON it serialized to. The
|
|
113
|
+
// launch used to call .get() on that, minutes after the EBI job the reload
|
|
114
|
+
// resubmitted had come back, and threw where nothing was catching.
|
|
115
|
+
test('a transcript restored from a session snapshot still labels the cache row', async () => {
|
|
116
|
+
blastp.mockResolvedValue({ rid: 'job', hits: [{ ...HIT, sequence: 'MKWV' }] });
|
|
117
|
+
mockLaunchMSA.mockResolvedValue({ msa: 'aligned', tree: 'tree' });
|
|
118
|
+
await launch(makeModel({
|
|
119
|
+
searchProgram: 'blastp',
|
|
120
|
+
blastDatabase: 'uniprotkb_swissprot',
|
|
121
|
+
msaAlgorithm: 'clustalo',
|
|
122
|
+
proteinSequence: 'MKWV',
|
|
123
|
+
selectedTranscript: {
|
|
124
|
+
uniqueId: 'NM_000546.6',
|
|
125
|
+
name: 'TP53-201',
|
|
126
|
+
parentId: 'TP53',
|
|
127
|
+
gene_name: 'TP53',
|
|
128
|
+
},
|
|
129
|
+
}));
|
|
130
|
+
expect(saveBlastResult).toHaveBeenCalledWith(expect.objectContaining({
|
|
131
|
+
geneId: 'TP53',
|
|
132
|
+
transcriptId: 'NM_000546.6',
|
|
133
|
+
transcriptName: 'TP53-201',
|
|
134
|
+
geneName: 'TP53',
|
|
135
|
+
}));
|
|
136
|
+
});
|
|
137
|
+
test('a live Feature handed over in the same session labels it the same way', async () => {
|
|
138
|
+
blastp.mockResolvedValue({ rid: 'job', hits: [{ ...HIT, sequence: 'MKWV' }] });
|
|
139
|
+
mockLaunchMSA.mockResolvedValue({ msa: 'aligned', tree: 'tree' });
|
|
140
|
+
const json = {
|
|
141
|
+
uniqueId: 'NM_000546.6',
|
|
142
|
+
name: 'TP53-201',
|
|
143
|
+
parentId: 'TP53',
|
|
144
|
+
};
|
|
145
|
+
await launch(makeModel({
|
|
146
|
+
searchProgram: 'blastp',
|
|
147
|
+
blastDatabase: 'uniprotkb_swissprot',
|
|
148
|
+
msaAlgorithm: 'clustalo',
|
|
149
|
+
proteinSequence: 'MKWV',
|
|
150
|
+
selectedTranscript: { toJSON: () => json },
|
|
151
|
+
}));
|
|
152
|
+
expect(saveBlastResult).toHaveBeenCalledWith(expect.objectContaining({
|
|
153
|
+
geneId: 'TP53',
|
|
154
|
+
transcriptId: 'NM_000546.6',
|
|
155
|
+
transcriptName: 'TP53-201',
|
|
156
|
+
geneName: 'TP53',
|
|
157
|
+
}));
|
|
158
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { JBrowsePluginMsaViewModel } from './model';
|
|
2
|
+
import type { LaunchScope } from './runLaunch';
|
|
2
3
|
/**
|
|
3
4
|
* The no-search-job alternative to doLaunchBlast.
|
|
4
5
|
*
|
|
@@ -16,8 +17,9 @@ import type { JBrowsePluginMsaViewModel } from './model';
|
|
|
16
17
|
* would silently break the genome<->MSA linkage. The query species is therefore
|
|
17
18
|
* excluded from the ortholog set rather than appearing twice.
|
|
18
19
|
*/
|
|
19
|
-
export declare function doLaunchOrthologs({ self, }: {
|
|
20
|
+
export declare function doLaunchOrthologs({ self, scope, }: {
|
|
20
21
|
self: JBrowsePluginMsaViewModel;
|
|
22
|
+
scope: LaunchScope;
|
|
21
23
|
}): Promise<{
|
|
22
24
|
treeMetadata: string;
|
|
23
25
|
msa: string;
|
|
@@ -3,6 +3,8 @@ import { launchMSA } from '../utils/msa';
|
|
|
3
3
|
import { dedupeLabels, fetchOrthologRows, fetchProteinForGene, resolveGeneId, } from '../utils/ncbiOrthologs';
|
|
4
4
|
import { fetchPantherOrthologs } from '../utils/pantherOrthologs';
|
|
5
5
|
import { fetchTaxonomyInfo } from '../utils/taxonomyNames';
|
|
6
|
+
import { fetchUnirefHomologs } from '../utils/unirefHomologs';
|
|
7
|
+
import { transcriptName } from './util';
|
|
6
8
|
/**
|
|
7
9
|
* The no-search-job alternative to doLaunchBlast.
|
|
8
10
|
*
|
|
@@ -20,11 +22,9 @@ import { fetchTaxonomyInfo } from '../utils/taxonomyNames';
|
|
|
20
22
|
* would silently break the genome<->MSA linkage. The query species is therefore
|
|
21
23
|
* excluded from the ortholog set rather than appearing twice.
|
|
22
24
|
*/
|
|
23
|
-
export async function doLaunchOrthologs({ self, }) {
|
|
24
|
-
const { taxId, taxa, maxSpecies, geneCandidates, msaAlgorithm, proteinSequence, source = 'ncbi', } = self.orthologParams;
|
|
25
|
-
const onProgress
|
|
26
|
-
self.setProgress(arg);
|
|
27
|
-
};
|
|
25
|
+
export async function doLaunchOrthologs({ self, scope, }) {
|
|
26
|
+
const { taxId, taxa, maxSpecies, geneCandidates, msaAlgorithm, proteinSequence, source = 'ncbi', identity, referenceProteomesOnly, } = self.orthologParams;
|
|
27
|
+
const { onProgress, act, signal } = scope;
|
|
28
28
|
const request = {
|
|
29
29
|
taxId,
|
|
30
30
|
geneCandidates,
|
|
@@ -36,7 +36,14 @@ export async function doLaunchOrthologs({ self, }) {
|
|
|
36
36
|
};
|
|
37
37
|
const { geneId, representative, rows } = source === 'panther'
|
|
38
38
|
? await findPantherOrthologs(request)
|
|
39
|
-
:
|
|
39
|
+
: source === 'uniref'
|
|
40
|
+
? await findUnirefHomologs({
|
|
41
|
+
...request,
|
|
42
|
+
identity,
|
|
43
|
+
referenceProteomesOnly,
|
|
44
|
+
signal,
|
|
45
|
+
})
|
|
46
|
+
: await findNcbiOrthologs(request);
|
|
40
47
|
// The query row. The dialog always supplies it — it is the user's OWN
|
|
41
48
|
// selected transcript, which is what makes `connectedFeature` map genome
|
|
42
49
|
// coordinates through this row. A launch that has no transcript to translate
|
|
@@ -48,7 +55,7 @@ export async function doLaunchOrthologs({ self, }) {
|
|
|
48
55
|
? cleanProteinSequence(proteinSequence)
|
|
49
56
|
: representative?.sequence;
|
|
50
57
|
if (!cleanedSeq) {
|
|
51
|
-
throw new Error(`No query protein: none was supplied and ${source
|
|
58
|
+
throw new Error(`No query protein: none was supplied and ${sourceNames[source]} returned no representative protein for gene ${geneId}.`);
|
|
52
59
|
}
|
|
53
60
|
// The query row is named for its species like every other row, with a suffix
|
|
54
61
|
// marking it as the one the genome view is linked to. A bare `QUERY` among
|
|
@@ -62,7 +69,9 @@ export async function doLaunchOrthologs({ self, }) {
|
|
|
62
69
|
// token, and a collision would silently point the coordinate mapping at
|
|
63
70
|
// another animal's row.
|
|
64
71
|
const queryLabel = await queryRowLabel(taxId, rows);
|
|
65
|
-
|
|
72
|
+
act(() => {
|
|
73
|
+
self.setQuerySeqName(queryLabel);
|
|
74
|
+
});
|
|
66
75
|
const treeMetadata = {
|
|
67
76
|
[queryLabel]: buildQueryMetadata(self, geneId, cleanedSeq, representative),
|
|
68
77
|
};
|
|
@@ -76,6 +85,7 @@ export async function doLaunchOrthologs({ self, }) {
|
|
|
76
85
|
...rows.map(r => `>${r.label}\n${r.sequence}`),
|
|
77
86
|
].join('\n'),
|
|
78
87
|
onProgress,
|
|
88
|
+
signal,
|
|
79
89
|
});
|
|
80
90
|
return {
|
|
81
91
|
...result,
|
|
@@ -102,6 +112,33 @@ async function findNcbiOrthologs({ taxId, geneCandidates, onProgress, ...rest })
|
|
|
102
112
|
});
|
|
103
113
|
return { geneId: resolved.geneId, representative, rows };
|
|
104
114
|
}
|
|
115
|
+
const sourceNames = {
|
|
116
|
+
ncbi: 'NCBI',
|
|
117
|
+
panther: 'PANTHER',
|
|
118
|
+
uniref: 'UniProt',
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* The query's own UniProt entry stands in for a representative protein: the
|
|
122
|
+
* entry the cluster was looked up from, whose sequence a spec-launched row
|
|
123
|
+
* takes and whose accession earns the CDD overlay when the two match.
|
|
124
|
+
*/
|
|
125
|
+
async function findUnirefHomologs({ geneCandidates, identity, referenceProteomesOnly, signal, ...rest }) {
|
|
126
|
+
const found = await fetchUnirefHomologs({
|
|
127
|
+
candidates: geneCandidates,
|
|
128
|
+
identity,
|
|
129
|
+
referenceProteomesOnly,
|
|
130
|
+
signal,
|
|
131
|
+
...rest,
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
geneId: found.query.id,
|
|
135
|
+
representative: {
|
|
136
|
+
accession: found.query.accession,
|
|
137
|
+
sequence: found.query.sequence,
|
|
138
|
+
},
|
|
139
|
+
rows: found.rows,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
105
142
|
/**
|
|
106
143
|
* One `matchortho` call resolves the gene, names its own UniProt entry and
|
|
107
144
|
* lists an ortholog per genome, so the representative protein needs no second
|
|
@@ -161,9 +198,8 @@ async function fetchRepresentativeQueryProtein(geneId) {
|
|
|
161
198
|
* protein as its query row passes that test by construction.
|
|
162
199
|
*/
|
|
163
200
|
function buildQueryMetadata(self, geneId, proteinSequence, representative) {
|
|
164
|
-
const transcript = self.orthologParams?.selectedTranscript;
|
|
165
201
|
const metadata = { 'Gene ID': geneId };
|
|
166
|
-
const name =
|
|
202
|
+
const name = transcriptName(self.orthologParams?.selectedTranscript);
|
|
167
203
|
if (name) {
|
|
168
204
|
metadata.Transcript = name;
|
|
169
205
|
}
|