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.
Files changed (171) hide show
  1. package/README.md +7 -0
  2. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +23 -17
  3. package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.js +1 -0
  4. package/dist/LaunchMsaView/components/BlastQuery/MsaAlgorithmSelect.js +5 -3
  5. package/dist/LaunchMsaView/components/BlastQuery/consts.d.ts +24 -4
  6. package/dist/LaunchMsaView/components/BlastQuery/consts.js +44 -2
  7. package/dist/LaunchMsaView/components/BlastQuery/searchChoiceStorage.d.ts +7 -0
  8. package/dist/LaunchMsaView/components/BlastQuery/searchChoiceStorage.js +42 -0
  9. package/dist/LaunchMsaView/components/BlastQuery/searchChoiceStorage.test.js +32 -0
  10. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +1 -0
  11. package/dist/LaunchMsaView/components/ManualMSALoader/launchView.d.ts +3 -1
  12. package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js +2 -1
  13. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +10 -4
  14. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.js +4 -1
  15. package/dist/LaunchMsaView/components/QueryRowSelector.js +21 -5
  16. package/dist/LaunchMsaView/detectQueryRow.d.ts +11 -0
  17. package/dist/LaunchMsaView/detectQueryRow.js +11 -4
  18. package/dist/LaunchMsaView/detectQueryRow.test.js +30 -0
  19. package/dist/LaunchMsaView/useQueryRowName.d.ts +1 -0
  20. package/dist/LaunchMsaView/useQueryRowName.js +5 -1
  21. package/dist/LaunchMsaView/useQueryRowName.test.d.ts +1 -0
  22. package/dist/LaunchMsaView/useQueryRowName.test.js +23 -0
  23. package/dist/LaunchMsaViewExtensionPoint/index.js +19 -12
  24. package/dist/LaunchMsaViewExtensionPoint/index.test.d.ts +1 -0
  25. package/dist/LaunchMsaViewExtensionPoint/index.test.js +64 -0
  26. package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +16 -0
  27. package/dist/MsaViewPanel/afterCreateAutoruns.js +138 -86
  28. package/dist/MsaViewPanel/components/LaunchProgress.js +23 -5
  29. package/dist/MsaViewPanel/components/MsaViewPanel.js +6 -2
  30. package/dist/MsaViewPanel/components/MsaViewPanel.test.js +40 -0
  31. package/dist/MsaViewPanel/doLaunchBlast.d.ts +11 -1
  32. package/dist/MsaViewPanel/doLaunchBlast.js +79 -96
  33. package/dist/MsaViewPanel/doLaunchBlast.test.d.ts +1 -0
  34. package/dist/MsaViewPanel/doLaunchBlast.test.js +158 -0
  35. package/dist/MsaViewPanel/doLaunchOrthologs.d.ts +3 -1
  36. package/dist/MsaViewPanel/doLaunchOrthologs.js +46 -10
  37. package/dist/MsaViewPanel/doLaunchOrthologs.test.js +48 -24
  38. package/dist/MsaViewPanel/genomeToMSA.js +4 -4
  39. package/dist/MsaViewPanel/genomeToMSA.test.js +67 -0
  40. package/dist/MsaViewPanel/loadProteinDomains.d.ts +8 -2
  41. package/dist/MsaViewPanel/loadProteinDomains.js +18 -9
  42. package/dist/MsaViewPanel/loadProteinDomains.test.d.ts +1 -0
  43. package/dist/MsaViewPanel/loadProteinDomains.test.js +41 -0
  44. package/dist/MsaViewPanel/model.d.ts +306 -85
  45. package/dist/MsaViewPanel/model.js +169 -42
  46. package/dist/MsaViewPanel/model.test.d.ts +1 -0
  47. package/dist/MsaViewPanel/model.test.js +100 -0
  48. package/dist/MsaViewPanel/msaCoordToGenomeCoord.d.ts +12 -1
  49. package/dist/MsaViewPanel/msaCoordToGenomeCoord.js +8 -8
  50. package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.js +133 -32
  51. package/dist/MsaViewPanel/msaDataStore.d.ts +7 -3
  52. package/dist/MsaViewPanel/msaDataStore.js +26 -7
  53. package/dist/MsaViewPanel/msaDataStore.test.d.ts +1 -0
  54. package/dist/MsaViewPanel/msaDataStore.test.js +44 -0
  55. package/dist/MsaViewPanel/observeProteinHighlights.test.js +20 -3
  56. package/dist/MsaViewPanel/processInit.test.d.ts +1 -0
  57. package/dist/MsaViewPanel/processInit.test.js +72 -0
  58. package/dist/MsaViewPanel/resolveConnectedTranscript.d.ts +9 -0
  59. package/dist/MsaViewPanel/resolveConnectedTranscript.js +122 -0
  60. package/dist/MsaViewPanel/runLaunch.d.ts +39 -0
  61. package/dist/MsaViewPanel/runLaunch.js +100 -0
  62. package/dist/MsaViewPanel/runLaunch.test.d.ts +1 -0
  63. package/dist/MsaViewPanel/runLaunch.test.js +175 -0
  64. package/dist/MsaViewPanel/storedData.test.d.ts +1 -0
  65. package/dist/MsaViewPanel/storedData.test.js +160 -0
  66. package/dist/MsaViewPanel/structureConnection.d.ts +0 -4
  67. package/dist/MsaViewPanel/structureConnection.js +0 -19
  68. package/dist/MsaViewPanel/util.d.ts +39 -0
  69. package/dist/MsaViewPanel/util.js +44 -0
  70. package/dist/jbrowse-plugin-msaview.umd.production.min.js +46 -45
  71. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  72. package/dist/utils/blastCache.js +32 -0
  73. package/dist/utils/blastCache.test.d.ts +1 -0
  74. package/dist/utils/blastCache.test.js +72 -0
  75. package/dist/utils/browserAlign.d.ts +42 -0
  76. package/dist/utils/browserAlign.js +297 -0
  77. package/dist/utils/browserAlign.test.d.ts +1 -0
  78. package/dist/utils/browserAlign.test.js +85 -0
  79. package/dist/utils/ebiBlast.d.ts +13 -2
  80. package/dist/utils/ebiBlast.js +32 -4
  81. package/dist/utils/ebiJobDispatcher.d.ts +6 -3
  82. package/dist/utils/ebiJobDispatcher.js +13 -6
  83. package/dist/utils/fetch.d.ts +8 -1
  84. package/dist/utils/fetch.js +28 -2
  85. package/dist/utils/homologSearch.d.ts +31 -0
  86. package/dist/utils/homologSearch.js +6 -0
  87. package/dist/utils/msa.d.ts +5 -11
  88. package/dist/utils/msa.js +19 -30
  89. package/dist/utils/msaRows.d.ts +11 -8
  90. package/dist/utils/msaRows.js +14 -12
  91. package/dist/utils/ncbiDomains.d.ts +2 -2
  92. package/dist/utils/ncbiOrthologs.d.ts +11 -1
  93. package/dist/utils/ncbiOrthologs.js +26 -7
  94. package/dist/utils/ncbiOrthologs.test.js +23 -1
  95. package/dist/utils/pantherOrthologs.js +2 -10
  96. package/dist/utils/phmmer.d.ts +12 -1
  97. package/dist/utils/phmmer.js +52 -13
  98. package/dist/utils/poll.d.ts +6 -1
  99. package/dist/utils/poll.js +7 -2
  100. package/dist/utils/unirefHomologs.d.ts +94 -0
  101. package/dist/utils/unirefHomologs.js +193 -0
  102. package/dist/utils/unirefHomologs.test.d.ts +1 -0
  103. package/dist/utils/unirefHomologs.test.js +118 -0
  104. package/dist/version.d.ts +1 -1
  105. package/dist/version.js +1 -1
  106. package/package.json +17 -16
  107. package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +40 -19
  108. package/src/LaunchMsaView/components/BlastQuery/BlastManualPanel.tsx +1 -0
  109. package/src/LaunchMsaView/components/BlastQuery/MsaAlgorithmSelect.tsx +7 -2
  110. package/src/LaunchMsaView/components/BlastQuery/consts.ts +50 -2
  111. package/src/LaunchMsaView/components/BlastQuery/searchChoiceStorage.test.ts +43 -0
  112. package/src/LaunchMsaView/components/BlastQuery/searchChoiceStorage.ts +64 -0
  113. package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +1 -0
  114. package/src/LaunchMsaView/components/ManualMSALoader/launchView.ts +4 -0
  115. package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +12 -5
  116. package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.tsx +4 -1
  117. package/src/LaunchMsaView/components/QueryRowSelector.tsx +35 -7
  118. package/src/LaunchMsaView/detectQueryRow.test.ts +34 -0
  119. package/src/LaunchMsaView/detectQueryRow.ts +22 -4
  120. package/src/LaunchMsaView/useQueryRowName.test.ts +27 -0
  121. package/src/LaunchMsaView/useQueryRowName.ts +5 -1
  122. package/src/LaunchMsaViewExtensionPoint/index.test.ts +74 -0
  123. package/src/LaunchMsaViewExtensionPoint/index.ts +53 -12
  124. package/src/MsaViewPanel/afterCreateAutoruns.ts +150 -81
  125. package/src/MsaViewPanel/components/LaunchProgress.tsx +46 -4
  126. package/src/MsaViewPanel/components/MsaViewPanel.test.tsx +43 -0
  127. package/src/MsaViewPanel/components/MsaViewPanel.tsx +8 -2
  128. package/src/MsaViewPanel/doLaunchBlast.test.ts +207 -0
  129. package/src/MsaViewPanel/doLaunchBlast.ts +94 -140
  130. package/src/MsaViewPanel/doLaunchOrthologs.test.ts +54 -26
  131. package/src/MsaViewPanel/doLaunchOrthologs.ts +63 -9
  132. package/src/MsaViewPanel/genomeToMSA.test.ts +76 -0
  133. package/src/MsaViewPanel/genomeToMSA.ts +8 -4
  134. package/src/MsaViewPanel/loadProteinDomains.test.ts +52 -0
  135. package/src/MsaViewPanel/loadProteinDomains.ts +24 -11
  136. package/src/MsaViewPanel/model.test.ts +121 -0
  137. package/src/MsaViewPanel/model.ts +200 -49
  138. package/src/MsaViewPanel/msaCoordToGenomeCoord.test.ts +152 -32
  139. package/src/MsaViewPanel/msaCoordToGenomeCoord.ts +21 -11
  140. package/src/MsaViewPanel/msaDataStore.test.ts +54 -0
  141. package/src/MsaViewPanel/msaDataStore.ts +32 -13
  142. package/src/MsaViewPanel/observeProteinHighlights.test.ts +27 -3
  143. package/src/MsaViewPanel/processInit.test.ts +84 -0
  144. package/src/MsaViewPanel/resolveConnectedTranscript.ts +150 -0
  145. package/src/MsaViewPanel/runLaunch.test.ts +208 -0
  146. package/src/MsaViewPanel/runLaunch.ts +141 -0
  147. package/src/MsaViewPanel/storedData.test.ts +196 -0
  148. package/src/MsaViewPanel/structureConnection.ts +0 -26
  149. package/src/MsaViewPanel/util.ts +75 -0
  150. package/src/utils/blastCache.test.ts +86 -0
  151. package/src/utils/blastCache.ts +34 -1
  152. package/src/utils/browserAlign.test.ts +102 -0
  153. package/src/utils/browserAlign.ts +352 -0
  154. package/src/utils/ebiBlast.ts +42 -2
  155. package/src/utils/ebiJobDispatcher.ts +18 -3
  156. package/src/utils/fetch.ts +29 -2
  157. package/src/utils/homologSearch.ts +51 -0
  158. package/src/utils/msa.ts +25 -39
  159. package/src/utils/msaRows.ts +20 -16
  160. package/src/utils/ncbiDomains.ts +4 -2
  161. package/src/utils/ncbiOrthologs.test.ts +25 -0
  162. package/src/utils/ncbiOrthologs.ts +27 -7
  163. package/src/utils/pantherOrthologs.ts +6 -11
  164. package/src/utils/phmmer.ts +62 -12
  165. package/src/utils/poll.ts +8 -1
  166. package/src/utils/unirefHomologs.test.ts +149 -0
  167. package/src/utils/unirefHomologs.ts +323 -0
  168. package/src/version.ts +1 -1
  169. package/dist/MsaViewPanel/structureConnection.test.js +0 -53
  170. package/src/MsaViewPanel/structureConnection.test.ts +0 -62
  171. /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 ortholog set comes from. NCBI's sets cover vertebrates and
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, so a gene from outside NCBI's scope aligns only through it.
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
- * guards the one-shot auto-fetch of protein domains so it doesn't refire
94
- * when NCBI returns no domains (leaving the annotation list empty)
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
- domainsRequested: false,
97
- }))
98
- .views(self => ({
117
+ lastStoredData: undefined,
99
118
  /**
100
- * #method
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
- getRowByName(rowName) {
103
- return self.rows.find(r => r[0] === rowName);
104
- },
125
+ launchController: undefined,
105
126
  /**
106
- * #method
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
- getSequenceByRowName(rowName) {
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
- .views(self => ({
285
- /**
286
- * #method
287
- */
288
- extraViewMenuItems() {
289
- return [
290
- {
291
- label: 'Zoom to base level on click?',
292
- checked: self.zoomToBaseLevel,
293
- type: 'checkbox',
294
- onClick: () => {
295
- self.setZoomToBaseLevel(!self.zoomToBaseLevel);
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
- rows: string[][];
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, rows } = model;
13
- const querySeq = rows.find(f => f[0] === querySeqName)?.[1];
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, ungappedPos)?.map(([start, end]) => ({
35
+ return (getCodonRanges(p2gCodon, proteinPos)?.map(([start, end]) => ({
36
36
  refName,
37
37
  start,
38
38
  end,