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
|
@@ -6,12 +6,17 @@ import { autorun } from 'mobx';
|
|
|
6
6
|
import { MSAModelF } from 'react-msaview';
|
|
7
7
|
import { autoLoadProteinDomains, launchBlastIfNeeded, launchOrthologsIfNeeded, loadStoredData, observeProteinHighlights, processInit, runCleanup, storeDataToIndexedDB, syncGenomeHoverToMsaColumn, } from './afterCreateAutoruns';
|
|
8
8
|
import { msaCoordToGenomeCoord, msaCoordToGenomeRegions, } from './msaCoordToGenomeCoord';
|
|
9
|
+
import { deleteMsaData } from './msaDataStore';
|
|
10
|
+
import { resolveConnectedTranscriptIfNeeded } from './resolveConnectedTranscript';
|
|
9
11
|
/**
|
|
10
|
-
* Where the
|
|
12
|
+
* Where the homolog set comes from. NCBI's ortholog sets cover vertebrates and
|
|
11
13
|
* insects; PANTHER's span its 144 reference proteomes, human to yeast to
|
|
12
|
-
* Arabidopsis
|
|
14
|
+
* Arabidopsis; a UniRef cluster is every UniProtKB entry within 50% (or 90%)
|
|
15
|
+
* identity of the query, one per species, from any organism at all -- and the
|
|
16
|
+
* one source of the three whose rows need no ortholog call, only UniProt's
|
|
17
|
+
* REST api (see utils/unirefHomologs.ts).
|
|
13
18
|
*/
|
|
14
|
-
export const orthologSources = ['ncbi', 'panther'];
|
|
19
|
+
export const orthologSources = ['ncbi', 'panther', 'uniref'];
|
|
15
20
|
/**
|
|
16
21
|
* #stateModel MsaViewPlugin
|
|
17
22
|
* extends
|
|
@@ -28,6 +33,14 @@ export default function stateModelFactory() {
|
|
|
28
33
|
* #property
|
|
29
34
|
*/
|
|
30
35
|
connectedFeature: types.frozen(),
|
|
36
|
+
/**
|
|
37
|
+
* #property
|
|
38
|
+
* the short form of `connectedFeature`: a transcript id (NM_000546.6)
|
|
39
|
+
* looked up in the connected genome view's tracks at launch, so a
|
|
40
|
+
* session spec can name the transcript instead of carrying its whole
|
|
41
|
+
* exon model. Resolved once into `connectedFeature` and left in place.
|
|
42
|
+
*/
|
|
43
|
+
connectedTranscript: types.maybe(types.string),
|
|
31
44
|
/**
|
|
32
45
|
* #property
|
|
33
46
|
*/
|
|
@@ -45,6 +58,17 @@ export default function stateModelFactory() {
|
|
|
45
58
|
// error-pages the whole app rather than just this view. Restore
|
|
46
59
|
// stripDefault once a release ships it.
|
|
47
60
|
querySeqName: 'QUERY',
|
|
61
|
+
/**
|
|
62
|
+
* #property
|
|
63
|
+
* transcript residues before the query row's first residue, so a
|
|
64
|
+
* transcript position is a row position plus this. A pasted alignment
|
|
65
|
+
* often carries only the aligned region of the query -- BLAST reports
|
|
66
|
+
* what it aligned -- and mapping that row's first residue to the
|
|
67
|
+
* transcript's first residue put every click off by the trimmed
|
|
68
|
+
* amount. Negative when the row runs past the transcript's start.
|
|
69
|
+
*/
|
|
70
|
+
// see querySeqName above re: types.stripDefault
|
|
71
|
+
querySeqOffset: 0,
|
|
48
72
|
/**
|
|
49
73
|
* #property
|
|
50
74
|
*/
|
|
@@ -76,10 +100,6 @@ export default function stateModelFactory() {
|
|
|
76
100
|
* #volatile
|
|
77
101
|
*/
|
|
78
102
|
progress: '',
|
|
79
|
-
/**
|
|
80
|
-
* #volatile
|
|
81
|
-
*/
|
|
82
|
-
error: undefined,
|
|
83
103
|
/**
|
|
84
104
|
* #volatile
|
|
85
105
|
*/
|
|
@@ -90,24 +110,25 @@ export default function stateModelFactory() {
|
|
|
90
110
|
isStoringData: false,
|
|
91
111
|
/**
|
|
92
112
|
* #volatile
|
|
93
|
-
*
|
|
94
|
-
*
|
|
113
|
+
* what IndexedDB was last known to hold for this view, so an edit made
|
|
114
|
+
* after the first write (react-msaview replacing data.tree with a
|
|
115
|
+
* neighbor-joining tree, say) is recognized as needing a new one
|
|
95
116
|
*/
|
|
96
|
-
|
|
97
|
-
}))
|
|
98
|
-
.views(self => ({
|
|
117
|
+
lastStoredData: undefined,
|
|
99
118
|
/**
|
|
100
|
-
* #
|
|
119
|
+
* #volatile
|
|
120
|
+
* aborts the launch currently in flight -- from the Cancel button, and
|
|
121
|
+
* from the disposer afterCreate registers, so a closed view stops
|
|
122
|
+
* polling EBI instead of running to completion and writing to a node
|
|
123
|
+
* that is gone
|
|
101
124
|
*/
|
|
102
|
-
|
|
103
|
-
return self.rows.find(r => r[0] === rowName);
|
|
104
|
-
},
|
|
125
|
+
launchController: undefined,
|
|
105
126
|
/**
|
|
106
|
-
* #
|
|
127
|
+
* #volatile
|
|
128
|
+
* guards the one-shot auto-fetch of protein domains so it doesn't refire
|
|
129
|
+
* when NCBI returns no domains (leaving the annotation list empty)
|
|
107
130
|
*/
|
|
108
|
-
|
|
109
|
-
return self.rows.find(r => r[0] === rowName)?.[1];
|
|
110
|
-
},
|
|
131
|
+
domainsRequested: false,
|
|
111
132
|
}))
|
|
112
133
|
.views(self => ({
|
|
113
134
|
/**
|
|
@@ -169,12 +190,6 @@ export default function stateModelFactory() {
|
|
|
169
190
|
setZoomToBaseLevel(arg) {
|
|
170
191
|
self.zoomToBaseLevel = arg;
|
|
171
192
|
},
|
|
172
|
-
/**
|
|
173
|
-
* #action
|
|
174
|
-
*/
|
|
175
|
-
setError(e) {
|
|
176
|
-
self.error = e;
|
|
177
|
-
},
|
|
178
193
|
/**
|
|
179
194
|
* #action
|
|
180
195
|
*/
|
|
@@ -193,6 +208,12 @@ export default function stateModelFactory() {
|
|
|
193
208
|
setBlastParams(args) {
|
|
194
209
|
self.blastParams = args;
|
|
195
210
|
},
|
|
211
|
+
/**
|
|
212
|
+
* #action
|
|
213
|
+
*/
|
|
214
|
+
setConnectedFeature(arg) {
|
|
215
|
+
self.connectedFeature = arg;
|
|
216
|
+
},
|
|
196
217
|
/**
|
|
197
218
|
* #action
|
|
198
219
|
*/
|
|
@@ -211,6 +232,12 @@ export default function stateModelFactory() {
|
|
|
211
232
|
setQuerySeqName(arg) {
|
|
212
233
|
self.querySeqName = arg;
|
|
213
234
|
},
|
|
235
|
+
/**
|
|
236
|
+
* #action
|
|
237
|
+
*/
|
|
238
|
+
setQuerySeqOffset(arg) {
|
|
239
|
+
self.querySeqOffset = arg;
|
|
240
|
+
},
|
|
214
241
|
/**
|
|
215
242
|
* #action
|
|
216
243
|
*/
|
|
@@ -241,6 +268,59 @@ export default function stateModelFactory() {
|
|
|
241
268
|
setIsStoringData(arg) {
|
|
242
269
|
self.isStoringData = arg;
|
|
243
270
|
},
|
|
271
|
+
/**
|
|
272
|
+
* #action
|
|
273
|
+
*/
|
|
274
|
+
setLastStoredData(arg) {
|
|
275
|
+
self.lastStoredData = arg;
|
|
276
|
+
},
|
|
277
|
+
/**
|
|
278
|
+
* #action
|
|
279
|
+
*/
|
|
280
|
+
setLaunchController(arg) {
|
|
281
|
+
self.launchController = arg;
|
|
282
|
+
},
|
|
283
|
+
/**
|
|
284
|
+
* #action
|
|
285
|
+
* Abandon the launch, in flight or failed. The EBI job keeps running on
|
|
286
|
+
* their side — nothing here can recall it, and its JobLink stays valid —
|
|
287
|
+
* so this only stops the polling and the writes it would make. Dropping
|
|
288
|
+
* the request (the params, or the init) is what keeps it from refiring on
|
|
289
|
+
* the next reload; the view falls back to the import form, which is where
|
|
290
|
+
* react-msaview's own cancel leaves it too.
|
|
291
|
+
*/
|
|
292
|
+
cancelLaunch() {
|
|
293
|
+
self.launchController?.abort();
|
|
294
|
+
self.launchController = undefined;
|
|
295
|
+
self.blastParams = undefined;
|
|
296
|
+
self.orthologParams = undefined;
|
|
297
|
+
self.init = undefined;
|
|
298
|
+
self.progress = '';
|
|
299
|
+
self.rid = undefined;
|
|
300
|
+
self.error = undefined;
|
|
301
|
+
},
|
|
302
|
+
/**
|
|
303
|
+
* #action
|
|
304
|
+
* Run the failed request again. The request IS the params, so re-stating
|
|
305
|
+
* them is the whole retry: each is a frozen property, and a fresh object
|
|
306
|
+
* is a change the launch autoruns wake on.
|
|
307
|
+
*/
|
|
308
|
+
retryLaunch() {
|
|
309
|
+
self.launchController?.abort();
|
|
310
|
+
self.launchController = undefined;
|
|
311
|
+
self.progress = '';
|
|
312
|
+
self.rid = undefined;
|
|
313
|
+
self.error = undefined;
|
|
314
|
+
if (self.blastParams) {
|
|
315
|
+
self.blastParams = { ...self.blastParams };
|
|
316
|
+
}
|
|
317
|
+
if (self.orthologParams) {
|
|
318
|
+
self.orthologParams = { ...self.orthologParams };
|
|
319
|
+
}
|
|
320
|
+
if (self.init) {
|
|
321
|
+
self.init = { ...self.init };
|
|
322
|
+
}
|
|
323
|
+
},
|
|
244
324
|
/**
|
|
245
325
|
* #action
|
|
246
326
|
*/
|
|
@@ -281,29 +361,76 @@ export default function stateModelFactory() {
|
|
|
281
361
|
},
|
|
282
362
|
};
|
|
283
363
|
})
|
|
284
|
-
.
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
364
|
+
.actions(self => {
|
|
365
|
+
const superReset = self.reset.bind(self);
|
|
366
|
+
return {
|
|
367
|
+
/**
|
|
368
|
+
* #action
|
|
369
|
+
* overrides base
|
|
370
|
+
*
|
|
371
|
+
* react-msaview's reset applies a snapshot filtered to its own
|
|
372
|
+
* `preservedOnReset` list, and a downstream property is never on it:
|
|
373
|
+
* returning to the import form dropped the view's name, un-minimized
|
|
374
|
+
* it, and forgot the zoom-on-click preference. The volatiles fail the
|
|
375
|
+
* other way -- applySnapshot cannot reach them, so the last file's
|
|
376
|
+
* state carried into the next one and the domain auto-load, which fires
|
|
377
|
+
* once per view, never fired again.
|
|
378
|
+
*/
|
|
379
|
+
reset() {
|
|
380
|
+
const { displayName, minimized, zoomToBaseLevel, dataStoreId } = self;
|
|
381
|
+
superReset();
|
|
382
|
+
if (displayName !== undefined) {
|
|
383
|
+
self.setDisplayName(displayName);
|
|
384
|
+
}
|
|
385
|
+
self.setMinimized(minimized);
|
|
386
|
+
self.setZoomToBaseLevel(zoomToBaseLevel);
|
|
387
|
+
self.setDomainsRequested(false);
|
|
388
|
+
self.setLastStoredData(undefined);
|
|
389
|
+
if (dataStoreId) {
|
|
390
|
+
// nothing points at that row now
|
|
391
|
+
void deleteMsaData(dataStoreId);
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
})
|
|
396
|
+
.views(self => {
|
|
397
|
+
// react-msaview's extraViewMenuItems() has had no caller since v5.6.0, in
|
|
398
|
+
// that package or in JBrowse; the view hamburger renders menuItems(), so
|
|
399
|
+
// extend that
|
|
400
|
+
const superMenuItems = self.menuItems.bind(self);
|
|
401
|
+
return {
|
|
402
|
+
/**
|
|
403
|
+
* #method
|
|
404
|
+
* overrides base
|
|
405
|
+
*/
|
|
406
|
+
menuItems() {
|
|
407
|
+
return [
|
|
408
|
+
...superMenuItems(),
|
|
409
|
+
{
|
|
410
|
+
label: 'Zoom to base level on click?',
|
|
411
|
+
type: 'checkbox',
|
|
412
|
+
checked: self.zoomToBaseLevel,
|
|
413
|
+
onClick: () => {
|
|
414
|
+
self.setZoomToBaseLevel(!self.zoomToBaseLevel);
|
|
415
|
+
},
|
|
296
416
|
},
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
})
|
|
417
|
+
];
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
})
|
|
301
421
|
.actions(self => ({
|
|
302
422
|
afterCreate() {
|
|
303
423
|
runCleanup();
|
|
424
|
+
// ties the launch to the view: closing one mid-BLAST used to leave the
|
|
425
|
+
// poller running for the job's lifetime and then call actions on a
|
|
426
|
+
// destroyed node, which surfaced as an unhandled rejection
|
|
427
|
+
addDisposer(self, () => {
|
|
428
|
+
self.launchController?.abort();
|
|
429
|
+
});
|
|
304
430
|
for (const fn of [
|
|
305
431
|
loadStoredData,
|
|
306
432
|
storeDataToIndexedDB,
|
|
433
|
+
resolveConnectedTranscriptIfNeeded,
|
|
307
434
|
launchBlastIfNeeded,
|
|
308
435
|
launchOrthologsIfNeeded,
|
|
309
436
|
processInit,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { doLaunchBlast } from './doLaunchBlast';
|
|
3
|
+
import stateModelFactory from './model';
|
|
4
|
+
import { deleteMsaData } from './msaDataStore';
|
|
5
|
+
// the launch autoruns are live on a real model, and a blastParams write is what
|
|
6
|
+
// wakes them -- so the search is mocked rather than sent to EBI. The session is
|
|
7
|
+
// mocked for the same reason: the hover and highlight autoruns reach for one on
|
|
8
|
+
// every change, and there is no session around a bare model.
|
|
9
|
+
vi.mock('@jbrowse/core/util', async (importOriginal) => ({
|
|
10
|
+
...(await importOriginal()),
|
|
11
|
+
getSession: () => ({ views: [], hovered: undefined }),
|
|
12
|
+
}));
|
|
13
|
+
vi.mock('./doLaunchBlast', () => ({ doLaunchBlast: vi.fn() }));
|
|
14
|
+
vi.mock('./msaDataStore', () => ({
|
|
15
|
+
cleanupOldData: vi.fn(async () => { }),
|
|
16
|
+
deleteMsaData: vi.fn(async () => { }),
|
|
17
|
+
generateDataStoreId: vi.fn(),
|
|
18
|
+
retrieveMsaData: vi.fn(),
|
|
19
|
+
storeMsaData: vi.fn(),
|
|
20
|
+
}));
|
|
21
|
+
const BLAST_PARAMS = {
|
|
22
|
+
searchProgram: 'blastp',
|
|
23
|
+
blastDatabase: 'uniprotkb_swissprot',
|
|
24
|
+
msaAlgorithm: 'clustalo',
|
|
25
|
+
proteinSequence: 'MKV',
|
|
26
|
+
};
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
vi.clearAllMocks();
|
|
29
|
+
vi.mocked(doLaunchBlast).mockReturnValue(new Promise(() => { }));
|
|
30
|
+
});
|
|
31
|
+
function view() {
|
|
32
|
+
return stateModelFactory().create({ type: 'MsaView', id: 'msaview1' });
|
|
33
|
+
}
|
|
34
|
+
// The item lived in extraViewMenuItems(), which react-msaview stopped calling
|
|
35
|
+
// in v5.6.0 and is deleting: the view hamburger renders menuItems(), so a
|
|
36
|
+
// checkbox nobody can reach is a feature that silently left.
|
|
37
|
+
describe('view menu', () => {
|
|
38
|
+
test('offers the zoom-to-base-level toggle', () => {
|
|
39
|
+
expect(view()
|
|
40
|
+
.menuItems()
|
|
41
|
+
.map(f => ('label' in f ? f.label : ''))).toContain('Zoom to base level on click?');
|
|
42
|
+
});
|
|
43
|
+
test('the toggle drives zoomToBaseLevel', () => {
|
|
44
|
+
const model = view();
|
|
45
|
+
const item = model.menuItems().at(-1);
|
|
46
|
+
expect(item.checked).toBe(false);
|
|
47
|
+
item.onClick();
|
|
48
|
+
expect(model.zoomToBaseLevel).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
// the params ARE the request, and they outlive the failure: a view that only
|
|
52
|
+
// drew the error resubmitted the job on every reload and offered no way out
|
|
53
|
+
describe('a failed launch', () => {
|
|
54
|
+
test('retries by re-stating the request', () => {
|
|
55
|
+
const model = view();
|
|
56
|
+
model.setBlastParams({ ...BLAST_PARAMS });
|
|
57
|
+
expect(doLaunchBlast).toHaveBeenCalledTimes(1);
|
|
58
|
+
model.setError(new Error('EBI said no'));
|
|
59
|
+
model.retryLaunch();
|
|
60
|
+
expect(model.error).toBeUndefined();
|
|
61
|
+
expect(model.blastParams).toEqual(BLAST_PARAMS);
|
|
62
|
+
expect(doLaunchBlast).toHaveBeenCalledTimes(2);
|
|
63
|
+
});
|
|
64
|
+
test('dismisses by dropping it', () => {
|
|
65
|
+
const model = view();
|
|
66
|
+
model.setBlastParams({ ...BLAST_PARAMS });
|
|
67
|
+
model.setError(new Error('EBI said no'));
|
|
68
|
+
model.cancelLaunch();
|
|
69
|
+
expect(model.blastParams).toBeUndefined();
|
|
70
|
+
expect(model.error).toBeUndefined();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
// react-msaview's reset keeps only what is on its own preservedOnReset list,
|
|
74
|
+
// and a downstream property is never on it; its volatiles survive instead,
|
|
75
|
+
// which is the direction that carries the last file's state into the next one
|
|
76
|
+
describe('returning to the import form', () => {
|
|
77
|
+
test('keeps what is about the view, not about the file', () => {
|
|
78
|
+
const model = view();
|
|
79
|
+
model.setDisplayName('BLAST - TP53');
|
|
80
|
+
model.setMinimized(true);
|
|
81
|
+
model.setZoomToBaseLevel(true);
|
|
82
|
+
model.setMSA('>a\nMK');
|
|
83
|
+
model.reset();
|
|
84
|
+
expect(model.displayName).toBe('BLAST - TP53');
|
|
85
|
+
expect(model.minimized).toBe(true);
|
|
86
|
+
expect(model.zoomToBaseLevel).toBe(true);
|
|
87
|
+
expect(model.dataInitialized).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
test('clears the volatiles applySnapshot cannot reach, and the stored row', () => {
|
|
90
|
+
const model = view();
|
|
91
|
+
model.setDomainsRequested(true);
|
|
92
|
+
model.setLastStoredData({ msa: '>a\nMK' });
|
|
93
|
+
model.setDataStoreId('msa-1');
|
|
94
|
+
model.reset();
|
|
95
|
+
expect(model.domainsRequested).toBe(false);
|
|
96
|
+
expect(model.lastStoredData).toBeUndefined();
|
|
97
|
+
expect(model.dataStoreId).toBeUndefined();
|
|
98
|
+
expect(deleteMsaData).toHaveBeenCalledWith('msa-1');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -11,12 +11,23 @@ interface CoordModel {
|
|
|
11
11
|
p2gCodon: Record<number, number[]>;
|
|
12
12
|
} | undefined;
|
|
13
13
|
mafRegion?: MafRegion;
|
|
14
|
-
|
|
14
|
+
/** transcript residues before the query row's first residue */
|
|
15
|
+
querySeqOffset?: number;
|
|
16
|
+
/**
|
|
17
|
+
* react-msaview's converter, which is the only thing that knows which columns
|
|
18
|
+
* are on screen. `rows` carries the whole gapped alignment, so counting
|
|
19
|
+
* residues along one of them answers for a column index the user cannot
|
|
20
|
+
* produce once `hideGaps` drops columns -- and it drops them as soon as a
|
|
21
|
+
* clade collapses or allowedGappyness falls below 100.
|
|
22
|
+
*/
|
|
23
|
+
visibleColToSeqPos: (rowName: string, visibleCol: number) => number | undefined;
|
|
15
24
|
}
|
|
16
25
|
/**
|
|
17
26
|
* The genome regions covered by MSA column `coord` of the query row, in 0-based
|
|
18
27
|
* half-open coordinates (what bpToPx and navTo take).
|
|
19
28
|
*
|
|
29
|
+
* `coord` is a VISIBLE column -- what mouseCol and mouseClickCol carry.
|
|
30
|
+
*
|
|
20
31
|
* Usually one region -- one codon, or one base in a MAF alignment -- but a
|
|
21
32
|
* codon split across an exon boundary yields one region per contiguous piece,
|
|
22
33
|
* which is why this returns a list.
|
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
import { getCodonRanges } from 'g2p_mapper';
|
|
2
|
-
import { gappedToUngappedPosition } from './structureConnection';
|
|
3
2
|
/**
|
|
4
3
|
* The genome regions covered by MSA column `coord` of the query row, in 0-based
|
|
5
4
|
* half-open coordinates (what bpToPx and navTo take).
|
|
6
5
|
*
|
|
6
|
+
* `coord` is a VISIBLE column -- what mouseCol and mouseClickCol carry.
|
|
7
|
+
*
|
|
7
8
|
* Usually one region -- one codon, or one base in a MAF alignment -- but a
|
|
8
9
|
* codon split across an exon boundary yields one region per contiguous piece,
|
|
9
10
|
* which is why this returns a list.
|
|
10
11
|
*/
|
|
11
12
|
export function msaCoordToGenomeRegions({ model, coord: mouseCol, }) {
|
|
12
|
-
const { querySeqName, transcriptToMsaMap, mafRegion
|
|
13
|
-
const
|
|
14
|
-
if (!querySeq) {
|
|
15
|
-
return [];
|
|
16
|
-
}
|
|
17
|
-
const ungappedPos = gappedToUngappedPosition(querySeq, mouseCol);
|
|
13
|
+
const { querySeqName, transcriptToMsaMap, mafRegion } = model;
|
|
14
|
+
const ungappedPos = model.visibleColToSeqPos(querySeqName, mouseCol);
|
|
18
15
|
if (ungappedPos === undefined) {
|
|
19
16
|
return [];
|
|
20
17
|
}
|
|
@@ -26,13 +23,16 @@ export function msaCoordToGenomeRegions({ model, coord: mouseCol, }) {
|
|
|
26
23
|
}
|
|
27
24
|
if (transcriptToMsaMap) {
|
|
28
25
|
const { refName, p2gCodon } = transcriptToMsaMap;
|
|
26
|
+
// the row is often only the aligned region of the transcript's protein, so
|
|
27
|
+
// its residues are the transcript's shifted by querySeqOffset
|
|
28
|
+
const proteinPos = ungappedPos + (model.querySeqOffset ?? 0);
|
|
29
29
|
// p2gCodon holds every genomic base of the codon, so the range is exact on
|
|
30
30
|
// either strand. Deriving it from consecutive p2g entries instead
|
|
31
31
|
// (p2g[pos]..p2g[pos+1]) was off by one base on the reverse strand -- where
|
|
32
32
|
// p2g stores the codon's *highest* coordinate -- dropped the final residue,
|
|
33
33
|
// whose successor has no p2g entry, and spanned the whole intron for a
|
|
34
34
|
// codon split across an exon boundary.
|
|
35
|
-
return (getCodonRanges(p2gCodon,
|
|
35
|
+
return (getCodonRanges(p2gCodon, proteinPos)?.map(([start, end]) => ({
|
|
36
36
|
refName,
|
|
37
37
|
start,
|
|
38
38
|
end,
|