jbrowse-plugin-msaview 5.1.0 → 5.2.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 (196) hide show
  1. package/dist/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.d.ts +3 -4
  2. package/dist/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.js +22 -17
  3. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +4 -4
  4. package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.js +1 -1
  5. package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.d.ts +4 -1
  6. package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.js +5 -8
  7. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +10 -9
  8. package/dist/LaunchMsaView/components/OrthologQuery/QuerySpeciesSelect.js +10 -1
  9. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +12 -9
  10. package/dist/LaunchMsaView/components/SubmitCancelActions.d.ts +1 -2
  11. package/dist/LaunchMsaView/components/SubmitCancelActions.js +2 -2
  12. package/dist/LaunchMsaView/components/TabPanel.d.ts +1 -1
  13. package/dist/LaunchMsaView/components/TabPanel.js +2 -2
  14. package/dist/LaunchMsaView/components/TranscriptSelector.js +3 -1
  15. package/dist/LaunchMsaView/components/launchConnectedView.js +8 -1
  16. package/dist/LaunchMsaView/components/useFeatureSequence.js +6 -4
  17. package/dist/LaunchMsaView/useQueryRowName.js +6 -2
  18. package/dist/LaunchMsaView/useQueryRowName.test.js +11 -0
  19. package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +3 -13
  20. package/dist/MsaViewPanel/afterCreateAutoruns.js +57 -57
  21. package/dist/MsaViewPanel/components/MsaViewPanel.js +6 -6
  22. package/dist/MsaViewPanel/components/MsaViewPanel.test.js +13 -1
  23. package/dist/MsaViewPanel/doLaunchBlast.d.ts +2 -6
  24. package/dist/MsaViewPanel/doLaunchBlast.js +28 -15
  25. package/dist/MsaViewPanel/doLaunchOrthologs.d.ts +1 -0
  26. package/dist/MsaViewPanel/doLaunchOrthologs.js +3 -1
  27. package/dist/MsaViewPanel/fetchIndexedMsa.js +1 -1
  28. package/dist/MsaViewPanel/loadProteinDomains.d.ts +5 -14
  29. package/dist/MsaViewPanel/loadProteinDomains.js +6 -20
  30. package/dist/MsaViewPanel/loadProteinDomains.test.js +1 -2
  31. package/dist/MsaViewPanel/model.d.ts +45 -43
  32. package/dist/MsaViewPanel/model.js +0 -3
  33. package/dist/MsaViewPanel/runLaunch.d.ts +3 -2
  34. package/dist/MsaViewPanel/runLaunch.js +4 -1
  35. package/dist/MsaViewPanel/util.d.ts +1 -0
  36. package/dist/MsaViewPanel/util.js +1 -1
  37. package/dist/chunks/{AboutDialog-WT6GXMXU.js → AboutDialog-IRBPUPJK.js} +2 -2
  38. package/dist/chunks/{AboutDialog-WT6GXMXU.js.map → AboutDialog-IRBPUPJK.js.map} +2 -2
  39. package/dist/chunks/AnnotationFileDialog-FCYYZ4JN.js +2 -0
  40. package/dist/chunks/{AnnotationFileDialog-34R7LFHB.js.map → AnnotationFileDialog-FCYYZ4JN.js.map} +2 -2
  41. package/dist/chunks/{BgzipFastaMsaAdapter-ZMUARSUQ.js → BgzipFastaMsaAdapter-WWIGI57O.js} +2 -2
  42. package/dist/chunks/BgzipFastaMsaAdapter-WWIGI57O.js.map +7 -0
  43. package/dist/chunks/ExportSVGDialog-TYYPI6VB.js +2 -0
  44. package/dist/chunks/ExportSVGDialog-TYYPI6VB.js.map +7 -0
  45. package/dist/chunks/{FeatureDialog-XNHIA5PS.js → FeatureDialog-ZBX3XQZ5.js} +1 -1
  46. package/dist/chunks/{FeatureDialog-XNHIA5PS.js.map → FeatureDialog-ZBX3XQZ5.js.map} +1 -1
  47. package/dist/chunks/LaunchMsaViewDialog-GJAUPGFC.js +5 -0
  48. package/dist/chunks/LaunchMsaViewDialog-GJAUPGFC.js.map +7 -0
  49. package/dist/chunks/MetadataDialog-KZWQFRF7.js +2 -0
  50. package/dist/chunks/{MetadataDialog-2WT6EX4M.js.map → MetadataDialog-KZWQFRF7.js.map} +2 -2
  51. package/dist/chunks/MsaViewPanel-RUZAKFCB.js +2 -0
  52. package/dist/chunks/MsaViewPanel-RUZAKFCB.js.map +7 -0
  53. package/dist/chunks/{TrackInfoDialog-TYCMLUKG.js → TrackInfoDialog-IFIG77WO.js} +2 -2
  54. package/dist/chunks/{TrackInfoDialog-TYCMLUKG.js.map → TrackInfoDialog-IFIG77WO.js.map} +1 -1
  55. package/dist/chunks/TreeNodeInfoDialog-VRCVIF26.js +2 -0
  56. package/dist/chunks/{TreeNodeInfoDialog-BFF6JRCO.js.map → TreeNodeInfoDialog-VRCVIF26.js.map} +2 -2
  57. package/dist/chunks/{chunk-O7JNDIKD.js → chunk-F3EC6OCO.js} +1 -1
  58. package/dist/chunks/{chunk-O7JNDIKD.js.map → chunk-F3EC6OCO.js.map} +1 -1
  59. package/dist/chunks/chunk-F726IWCW.js +2 -0
  60. package/dist/chunks/chunk-F726IWCW.js.map +7 -0
  61. package/dist/chunks/{chunk-ZTQYZDWK.js → chunk-GDFVLZYX.js} +2 -2
  62. package/dist/chunks/{chunk-ZTQYZDWK.js.map → chunk-GDFVLZYX.js.map} +1 -1
  63. package/dist/chunks/{chunk-5ORPTTMR.js → chunk-GIHMPOCO.js} +1 -1
  64. package/dist/chunks/{chunk-5ORPTTMR.js.map → chunk-GIHMPOCO.js.map} +1 -1
  65. package/dist/chunks/{chunk-ENTWPX2K.js → chunk-H6IQBAW7.js} +5 -5
  66. package/dist/chunks/{chunk-ENTWPX2K.js.map → chunk-H6IQBAW7.js.map} +1 -1
  67. package/dist/chunks/{chunk-TITP6LCV.js → chunk-JFL3LJPO.js} +2 -2
  68. package/dist/chunks/{chunk-TITP6LCV.js.map → chunk-JFL3LJPO.js.map} +2 -2
  69. package/dist/chunks/chunk-NI3VGKQP.js +3 -0
  70. package/dist/chunks/chunk-NI3VGKQP.js.map +7 -0
  71. package/dist/chunks/chunk-NMYD2GWF.js +18 -0
  72. package/dist/chunks/chunk-NMYD2GWF.js.map +7 -0
  73. package/dist/chunks/chunk-QQ5LZUIO.js +11 -0
  74. package/dist/chunks/chunk-QQ5LZUIO.js.map +7 -0
  75. package/dist/chunks/chunk-QUWPHTZQ.js +2 -0
  76. package/dist/chunks/chunk-QUWPHTZQ.js.map +7 -0
  77. package/dist/chunks/{chunk-P77DPK6B.js → chunk-YOTSFJMU.js} +1 -1
  78. package/dist/chunks/{chunk-P77DPK6B.js.map → chunk-YOTSFJMU.js.map} +1 -1
  79. package/dist/chunks/chunk-ZCT7RD24.js +2 -0
  80. package/dist/chunks/chunk-ZCT7RD24.js.map +7 -0
  81. package/dist/chunks/{dist-UX5CKLQJ.js → dist-CMWKVHC7.js} +1 -1
  82. package/dist/chunks/{dist-UX5CKLQJ.js.map → dist-CMWKVHC7.js.map} +1 -1
  83. package/dist/chunks/dist-R73J3CC3.js +2 -0
  84. package/dist/chunks/model-VE7IYWNU.js +12 -0
  85. package/dist/chunks/model-VE7IYWNU.js.map +7 -0
  86. package/dist/chunks/{renderToSvg-B7RCHRO3.js → renderToSvg-VDXAB7TT.js} +2 -2
  87. package/dist/jbrowse-plugin-msaview.esm.js +2 -2
  88. package/dist/jbrowse-plugin-msaview.esm.js.map +1 -1
  89. package/dist/utils/ebiBlast.d.ts +8 -13
  90. package/dist/utils/ebiBlast.js +20 -45
  91. package/dist/utils/ebiJobDispatcher.d.ts +4 -4
  92. package/dist/utils/ebiJobDispatcher.js +4 -4
  93. package/dist/utils/eutils.js +1 -1
  94. package/dist/utils/fetch.d.ts +4 -0
  95. package/dist/utils/fetch.js +7 -1
  96. package/dist/utils/homologSearch.d.ts +6 -2
  97. package/dist/utils/msaRows.d.ts +1 -2
  98. package/dist/utils/msaRows.js +13 -1
  99. package/dist/utils/ncbiDomains.d.ts +1 -1
  100. package/dist/utils/ncbiDomains.js +19 -7
  101. package/dist/utils/ncbiDomains.test.js +36 -0
  102. package/dist/utils/ncbiOrthologs.d.ts +24 -5
  103. package/dist/utils/ncbiOrthologs.js +55 -19
  104. package/dist/utils/ncbiOrthologs.test.js +58 -8
  105. package/dist/utils/pantherOrthologs.js +4 -1
  106. package/dist/utils/phmmer.js +4 -8
  107. package/dist/utils/taxonomyNames.d.ts +6 -1
  108. package/dist/utils/taxonomyNames.js +7 -1
  109. package/dist/utils/taxonomyNames.test.js +12 -0
  110. package/dist/utils/unirefHomologs.d.ts +6 -0
  111. package/dist/utils/unirefHomologs.js +25 -10
  112. package/dist/utils/unirefHomologs.test.js +49 -2
  113. package/dist/version.d.ts +1 -1
  114. package/dist/version.js +1 -1
  115. package/package.json +18 -18
  116. package/src/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.ts +23 -20
  117. package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +4 -4
  118. package/src/LaunchMsaView/components/BlastQuery/BlastPanel.tsx +3 -1
  119. package/src/LaunchMsaView/components/BlastQuery/CachedBlastResults.tsx +7 -19
  120. package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +10 -9
  121. package/src/LaunchMsaView/components/OrthologQuery/QuerySpeciesSelect.tsx +12 -1
  122. package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +12 -8
  123. package/src/LaunchMsaView/components/SubmitCancelActions.tsx +1 -3
  124. package/src/LaunchMsaView/components/TabPanel.tsx +1 -2
  125. package/src/LaunchMsaView/components/TranscriptSelector.tsx +3 -1
  126. package/src/LaunchMsaView/components/launchConnectedView.ts +8 -1
  127. package/src/LaunchMsaView/components/useFeatureSequence.ts +8 -2
  128. package/src/LaunchMsaView/useQueryRowName.test.ts +15 -0
  129. package/src/LaunchMsaView/useQueryRowName.ts +7 -2
  130. package/src/MsaViewPanel/afterCreateAutoruns.ts +74 -58
  131. package/src/MsaViewPanel/components/MsaViewPanel.test.tsx +18 -1
  132. package/src/MsaViewPanel/components/MsaViewPanel.tsx +15 -16
  133. package/src/MsaViewPanel/doLaunchBlast.ts +37 -17
  134. package/src/MsaViewPanel/doLaunchOrthologs.ts +4 -1
  135. package/src/MsaViewPanel/fetchIndexedMsa.ts +1 -1
  136. package/src/MsaViewPanel/loadProteinDomains.test.ts +1 -2
  137. package/src/MsaViewPanel/loadProteinDomains.ts +13 -28
  138. package/src/MsaViewPanel/model.ts +0 -3
  139. package/src/MsaViewPanel/runLaunch.ts +8 -8
  140. package/src/MsaViewPanel/types.ts +1 -1
  141. package/src/MsaViewPanel/util.ts +1 -1
  142. package/src/utils/ebiBlast.ts +29 -56
  143. package/src/utils/ebiJobDispatcher.ts +4 -4
  144. package/src/utils/eutils.ts +1 -1
  145. package/src/utils/fetch.ts +12 -2
  146. package/src/utils/homologSearch.ts +6 -2
  147. package/src/utils/msaRows.ts +18 -4
  148. package/src/utils/ncbiDomains.test.ts +53 -0
  149. package/src/utils/ncbiDomains.ts +21 -8
  150. package/src/utils/ncbiOrthologs.test.ts +70 -7
  151. package/src/utils/ncbiOrthologs.ts +81 -24
  152. package/src/utils/pantherOrthologs.ts +7 -1
  153. package/src/utils/phmmer.ts +3 -7
  154. package/src/utils/taxonomyNames.test.ts +15 -0
  155. package/src/utils/taxonomyNames.ts +7 -0
  156. package/src/utils/unirefHomologs.test.ts +57 -2
  157. package/src/utils/unirefHomologs.ts +38 -13
  158. package/src/version.ts +1 -1
  159. package/dist/LaunchMsaView/components/util.d.ts +0 -7
  160. package/dist/LaunchMsaView/components/util.js +0 -16
  161. package/dist/MsaViewPanel/components/ErrorBoundary.d.ts +0 -19
  162. package/dist/MsaViewPanel/components/ErrorBoundary.js +0 -21
  163. package/dist/chunks/AnnotationFileDialog-34R7LFHB.js +0 -2
  164. package/dist/chunks/BgzipFastaMsaAdapter-ZMUARSUQ.js.map +0 -7
  165. package/dist/chunks/ExportSVGDialog-NMVUHFGY.js +0 -2
  166. package/dist/chunks/ExportSVGDialog-NMVUHFGY.js.map +0 -7
  167. package/dist/chunks/LaunchMsaViewDialog-WBXEEHJL.js +0 -5
  168. package/dist/chunks/LaunchMsaViewDialog-WBXEEHJL.js.map +0 -7
  169. package/dist/chunks/MetadataDialog-2WT6EX4M.js +0 -2
  170. package/dist/chunks/MsaViewPanel-YYP4CYYO.js +0 -2
  171. package/dist/chunks/MsaViewPanel-YYP4CYYO.js.map +0 -7
  172. package/dist/chunks/TreeNodeInfoDialog-BFF6JRCO.js +0 -2
  173. package/dist/chunks/chunk-FCNT6B47.js +0 -2
  174. package/dist/chunks/chunk-FCNT6B47.js.map +0 -7
  175. package/dist/chunks/chunk-MBI4TMKR.js +0 -2
  176. package/dist/chunks/chunk-MBI4TMKR.js.map +0 -7
  177. package/dist/chunks/chunk-MHBTV7BZ.js +0 -3
  178. package/dist/chunks/chunk-MHBTV7BZ.js.map +0 -7
  179. package/dist/chunks/chunk-PJZ3XTMG.js +0 -6
  180. package/dist/chunks/chunk-PJZ3XTMG.js.map +0 -7
  181. package/dist/chunks/chunk-QAC6JSDZ.js +0 -5
  182. package/dist/chunks/chunk-QAC6JSDZ.js.map +0 -7
  183. package/dist/chunks/chunk-QMLPITS4.js +0 -18
  184. package/dist/chunks/chunk-QMLPITS4.js.map +0 -7
  185. package/dist/chunks/chunk-RTKNCGDF.js +0 -8
  186. package/dist/chunks/chunk-RTKNCGDF.js.map +0 -7
  187. package/dist/chunks/dist-2T65ZS4E.js +0 -2
  188. package/dist/chunks/model-7VBTOIJX.js +0 -12
  189. package/dist/chunks/model-7VBTOIJX.js.map +0 -7
  190. package/dist/utils/types.d.ts +0 -18
  191. package/src/LaunchMsaView/components/util.ts +0 -22
  192. package/src/MsaViewPanel/components/ErrorBoundary.tsx +0 -41
  193. package/src/utils/types.ts +0 -17
  194. /package/dist/chunks/{dist-2T65ZS4E.js.map → dist-R73J3CC3.js.map} +0 -0
  195. /package/dist/chunks/{renderToSvg-B7RCHRO3.js.map → renderToSvg-VDXAB7TT.js.map} +0 -0
  196. /package/dist/utils/{types.js → ncbiDomains.test.d.ts} +0 -0
@@ -1,5 +1,7 @@
1
1
  import { getSession } from '@jbrowse/core/util';
2
+ import { addDisposer, isAlive } from '@jbrowse/mobx-state-tree';
2
3
  import { transaction } from 'mobx';
4
+ import { isAbortError } from '../utils/fetch';
3
5
  import { doLaunchBlast } from './doLaunchBlast';
4
6
  import { doLaunchOrthologs } from './doLaunchOrthologs';
5
7
  import { fetchIndexedMsa } from './fetchIndexedMsa';
@@ -13,6 +15,7 @@ import { getUniprotIdFromAlphaFoldUrl, hasQueryRow, transcriptPosToVisibleCol, }
13
15
  const EXPIRED_MESSAGE = "This view's alignment is no longer in browser storage. Stored alignments are kept for 7 days after they were last used, and are lost when site data is cleared.";
14
16
  const RELAUNCHABLE = ' Retry runs the original search again and rebuilds it.';
15
17
  const START_OVER = ' Relaunch it from the gene to rebuild it.';
18
+ const UNREADABLE_MESSAGE = "This view's alignment could not be read from browser storage.";
16
19
  const EXPIRED_EXTRAS_WARNING = 'Part of this view is no longer in browser storage: annotations, a tree or row metadata too large for the session. Stored documents are kept for 7 days after they were last used, and are lost when site data is cleared.';
17
20
  export function loadStoredData(self) {
18
21
  const { dataStoreId } = self;
@@ -59,6 +62,10 @@ export function loadStoredData(self) {
59
62
  }
60
63
  catch (e) {
61
64
  console.error('Failed to load MSA data from IndexedDB:', e);
65
+ // the row may still be there, so the id is kept for the next reload
66
+ if (isAlive(self) && !hasAlignmentSource(self)) {
67
+ self.setError(new Error(`${UNREADABLE_MESSAGE} ${e}.${(self.blastParams ?? self.orthologParams) ? RELAUNCHABLE : START_OVER}`));
68
+ }
62
69
  }
63
70
  finally {
64
71
  self.setLoadingStoredData(false);
@@ -126,75 +133,70 @@ export function storeDataToIndexedDB(self) {
126
133
  })();
127
134
  }
128
135
  /**
129
- * Same shape as launchBlastIfNeeded, for the ortholog path: the params ARE the
130
- * request, and `launchCompleted` is what marks it done. They are left in place
131
- * either way -- on failure so the error stays attributable to a specific
132
- * request, and on success because they are the only durable statement of what
133
- * the view is, which is what a stored alignment that expired is rebuilt from.
134
- * The autorun tracks those two reads alone, so nothing refires until a new
135
- * request replaces them or a retry clears the mark.
136
- */
137
- export function launchOrthologsIfNeeded(self) {
138
- if (self.orthologParams &&
139
- !self.launchCompleted &&
140
- !awaitingTranscript(self)) {
141
- runLaunch({
142
- self,
143
- message: 'Resolving orthologs',
144
- launch: scope => doLaunchOrthologs({ self, scope }),
145
- // marked rather than dropped: the request is the only durable statement
146
- // of what this view is, and a view whose stored alignment expired runs it
147
- // again
148
- onLaunched: () => {
149
- self.setLaunchCompleted(true);
150
- },
151
- });
152
- }
153
- }
154
- /**
155
- * A launch that names its transcript rather than its feature has to wait for
156
- * the lookup: the query row is that transcript's translation, and the launch
157
- * cannot start without a query. Both launchers read the same two fields, so
158
- * the resolver setting `connectedFeature` is what refires them.
136
+ * A launch that names its transcript rather than its feature waits for the
137
+ * lookup, since the query row is that transcript's translation. The resolver
138
+ * setting `connectedFeature` is what refires the launchers.
159
139
  */
160
140
  function awaitingTranscript(self) {
161
141
  return !!self.connectedTranscript && !self.connectedFeature;
162
142
  }
163
- export function launchBlastIfNeeded(self) {
164
- if (self.blastParams && !self.launchCompleted && !awaitingTranscript(self)) {
143
+ /**
144
+ * The params are the request and `launchCompleted` marks it done. They stay in
145
+ * place on failure, so the error is attributable, and on success, because a
146
+ * stored alignment that expired is rebuilt from them.
147
+ */
148
+ function launchIfNeeded(self, requested, message, launch) {
149
+ if (requested && !self.launchCompleted && !awaitingTranscript(self)) {
165
150
  runLaunch({
166
151
  self,
167
- message: 'Submitting query',
168
- launch: scope => doLaunchBlast({ self, scope }),
152
+ message,
153
+ launch,
169
154
  onLaunched: () => {
170
155
  self.setLaunchCompleted(true);
171
156
  },
172
157
  });
173
158
  }
174
159
  }
160
+ export function launchOrthologsIfNeeded(self) {
161
+ launchIfNeeded(self, !!self.orthologParams, 'Resolving orthologs', scope => doLaunchOrthologs({ self, scope }));
162
+ }
163
+ export function launchBlastIfNeeded(self) {
164
+ launchIfNeeded(self, !!self.blastParams, 'Submitting query', scope => doLaunchBlast({ self, scope }));
165
+ }
175
166
  /**
176
- * Once an accession-bearing alignment is present (fresh from BLAST or restored
177
- * from cache), fetch NCBI CDD domains for those accessions and overlay them.
178
- * Runs once per view; the domainsRequested guard prevents refiring when NCBI
179
- * returns no domains (which leaves the annotation list empty).
167
+ * Once an alignment whose rows carry accessions is present, fetch NCBI CDD
168
+ * domains for them and overlay them. `domainsRequested` makes this once per
169
+ * view, so NCBI returning no domains does not refire it.
180
170
  */
181
171
  export function autoLoadProteinDomains(self) {
182
- const { rows, domainsRequested, annotations } = self;
183
- const hasAccessions = self.data.treeMetadata?.includes('"Accession"') ?? false;
172
+ const { rows, domainsRequested, annotations, rowData } = self;
184
173
  if (rows.length > 0 &&
185
- hasAccessions &&
186
174
  annotations.length === 0 &&
187
- !domainsRequested) {
175
+ !domainsRequested &&
176
+ Object.values(rowData).some(row => row?.Accession)) {
177
+ const controller = new AbortController();
178
+ const abort = () => {
179
+ controller.abort();
180
+ };
181
+ addDisposer(self, abort);
188
182
  self.setDomainsRequested(true);
183
+ self.setStatus({
184
+ msg: 'Fetching protein domains from NCBI',
185
+ onCancel: abort,
186
+ });
189
187
  void (async () => {
190
188
  try {
191
- await loadProteinDomains(self);
189
+ await loadProteinDomains(self, controller.signal);
192
190
  }
193
191
  catch (e) {
194
- console.error('[msaview-domains] auto-load failed:', e);
192
+ if (!isAbortError(e)) {
193
+ console.error('[msaview-domains] auto-load failed:', e);
194
+ }
195
195
  }
196
196
  finally {
197
- self.setProgress('');
197
+ if (isAlive(self)) {
198
+ self.setStatus(undefined);
199
+ }
198
200
  }
199
201
  })();
200
202
  }
@@ -224,15 +226,15 @@ export function processInit(self) {
224
226
  void (async () => {
225
227
  try {
226
228
  self.setError(undefined);
227
- if (msaUrl) {
228
- const id = getUniprotIdFromAlphaFoldUrl(msaUrl);
229
- if (id) {
230
- self.setUniprotId(id);
231
- self.setQuerySeqName('query');
232
- }
229
+ const uniprotId = msaUrl
230
+ ? getUniprotIdFromAlphaFoldUrl(msaUrl)
231
+ : undefined;
232
+ if (uniprotId) {
233
+ self.setUniprotId(uniprotId);
233
234
  }
234
- if (querySeqName) {
235
- self.setQuerySeqName(querySeqName);
235
+ const rowName = querySeqName ?? (uniprotId ? 'query' : undefined);
236
+ if (rowName) {
237
+ self.setQuerySeqName(rowName);
236
238
  }
237
239
  if (msaUrl) {
238
240
  self.setMSAFilehandle({ uri: msaUrl, locationType: 'UriLocation' });
@@ -242,12 +244,10 @@ export function processInit(self) {
242
244
  location: msaIndexedLocation,
243
245
  name: msaName,
244
246
  });
245
- if (fasta) {
246
- self.setMSA(fasta);
247
- }
248
- else {
247
+ if (!fasta) {
249
248
  throw new Error(`No alignment named ${msaName} in ${msaIndexedLocation.uri}`);
250
249
  }
250
+ self.setMSA(fasta);
251
251
  }
252
252
  if (!indexed) {
253
253
  self.setInit(undefined);
@@ -3,7 +3,6 @@ import { LoadingEllipses } from '@jbrowse/core/ui';
3
3
  import { observer } from 'mobx-react';
4
4
  import { MSAView } from 'react-msaview';
5
5
  import { makeStyles } from 'tss-react/mui';
6
- import { ErrorBoundary } from './ErrorBoundary';
7
6
  import LaunchProgress from './LaunchProgress';
8
7
  const useStyles = makeStyles()({
9
8
  loadingContainer: {
@@ -21,11 +20,12 @@ const MsaViewPanel = observer(function MsaViewPanel2({ model, }) {
21
20
  const pending = !!request && !model.launchCompleted;
22
21
  // a request that finished and then lost its alignment -- browser storage
23
22
  // expires one after 7 days -- is a request again, and LaunchProgress draws it
24
- // with the Retry that runs it
25
- const expired = !!request && !!model.error && !model.dataInitialized;
23
+ // with the Retry that runs it. An error over an alignment still in hand is
24
+ // react-msaview's to draw.
25
+ const hasAlignment = !!(model.data.msa || model.data.tree);
26
+ const expired = !!request && !!model.error && !hasAlignment;
26
27
  const launching = pending || expired || (!!init && !model.dataInitialized);
27
- return (React.createElement(ErrorBoundary, null,
28
- React.createElement("div", null, launching ? (React.createElement(LaunchProgress, { model: model })) : loadingStoredData && !model.dataInitialized ? (React.createElement("div", { className: classes.loadingContainer },
29
- React.createElement(LoadingEllipses, { message: "Loading MSA data", variant: "h6" }))) : (React.createElement(MSAView, { model: model })))));
28
+ return (React.createElement("div", null, launching ? (React.createElement(LaunchProgress, { model: model })) : loadingStoredData && !model.dataInitialized ? (React.createElement("div", { className: classes.loadingContainer },
29
+ React.createElement(LoadingEllipses, { message: "Loading MSA data", variant: "h6" }))) : (React.createElement(MSAView, { model: model }))));
30
30
  });
31
31
  export default MsaViewPanel;
@@ -9,7 +9,7 @@ vi.mock('react-msaview', () => ({
9
9
  MSAView: () => React.createElement("div", null, "the alignment"),
10
10
  }));
11
11
  function panel(model) {
12
- return render(React.createElement(MsaViewPanel, { model: model }));
12
+ return render(React.createElement(MsaViewPanel, { model: { data: {}, ...model } }));
13
13
  }
14
14
  afterEach(() => {
15
15
  cleanup();
@@ -115,6 +115,18 @@ test('an expired alignment offers to run its original search again', () => {
115
115
  screen.getByRole('button', { name: 'Retry' }).click();
116
116
  expect(retryLaunch).toHaveBeenCalled();
117
117
  });
118
+ // react-msaview draws an error over an alignment it still holds, with its own
119
+ // way back; rerunning a fifteen minute search is the wrong offer there
120
+ test('an error over an alignment still in hand is not an expiry', () => {
121
+ panel({
122
+ progress: '',
123
+ data: { msa: '>a\nMKV' },
124
+ error: new Error('bad tree'),
125
+ blastParams: { blastDatabase: 'uniprotkb_swissprot' },
126
+ launchCompleted: true,
127
+ });
128
+ expect(screen.getByText('the alignment')).toBeTruthy();
129
+ });
118
130
  // with nothing to retry from -- a pasted alignment, say -- the panel stays out
119
131
  // of the way and react-msaview's own import form is what the user gets
120
132
  test('an expired alignment with no request behind it draws the view', () => {
@@ -1,5 +1,5 @@
1
1
  import type { JBrowsePluginMsaViewModel } from './model';
2
- import type { LaunchScope } from './runLaunch';
2
+ import type { LaunchScope, LaunchedData } from './runLaunch';
3
3
  /**
4
4
  * A similarity search, then an alignment of what it found. The program is a
5
5
  * backend behind one interface (utils/homologSearch.ts); what differs between
@@ -11,8 +11,4 @@ import type { LaunchScope } from './runLaunch';
11
11
  export declare function doLaunchBlast({ self, scope, }: {
12
12
  self: JBrowsePluginMsaViewModel;
13
13
  scope: LaunchScope;
14
- }): Promise<{
15
- msa: string;
16
- tree: string;
17
- treeMetadata: string;
18
- }>;
14
+ }): Promise<LaunchedData>;
@@ -1,3 +1,4 @@
1
+ import { defaultMaxHits } from '../LaunchMsaView/components/BlastQuery/consts';
1
2
  import { cleanProteinSequence } from '../LaunchMsaView/util';
2
3
  import { saveBlastResult } from '../utils/blastCache';
3
4
  import { checkPairSize } from '../utils/browserAlign';
@@ -7,10 +8,7 @@ import { buildSearchMsa } from '../utils/msaRows';
7
8
  import { getCachedSearch, saveSearch, searchKey } from '../utils/searchCache';
8
9
  import { fetchTaxonomyInfo } from '../utils/taxonomyNames';
9
10
  import { resolveUniProtEntry } from '../utils/unirefHomologs';
10
- import { transcriptFields, transcriptName } from './util';
11
- function asString(val) {
12
- return typeof val === 'string' ? val : undefined;
13
- }
11
+ import { str, transcriptFields, transcriptName } from './util';
14
12
  /**
15
13
  * The query sequence, and what its row is called. The dialog hands over the
16
14
  * translated transcript and the row stays `QUERY`; a spec naming a UniProt
@@ -40,13 +38,13 @@ async function resolveQuery(self, scope) {
40
38
  }
41
39
  throw new Error('No query: a search needs a proteinSequence, a UniProt accession, or a connectedTranscript to translate');
42
40
  }
43
- async function search({ self, scope, query, querySeqName, key, }) {
41
+ async function search({ self, scope, query, querySeqName, maxHits, key, }) {
44
42
  const params = self.blastParams;
45
43
  const { onProgress, onRid, signal } = scope;
46
44
  const { hits, queryRow, rid } = await searchBackends[params.searchProgram ?? 'blastp']({
47
45
  query,
48
46
  database: params.blastDatabase,
49
- maxHits: params.maxHits,
47
+ maxHits,
50
48
  onProgress,
51
49
  onRid,
52
50
  signal,
@@ -55,7 +53,10 @@ async function search({ self, scope, query, querySeqName, key, }) {
55
53
  throw new Error('No hits found');
56
54
  }
57
55
  onProgress('Fetching species taxonomy info...');
58
- const taxonomyInfo = await fetchTaxonomyInfo(hits.map(h => h.taxid).filter((t) => t !== undefined), signal);
56
+ const nameFailures = [];
57
+ const taxonomyInfo = await fetchTaxonomyInfo(hits.map(h => h.taxid).filter((t) => t !== undefined), signal, e => {
58
+ nameFailures.push(e);
59
+ });
59
60
  const { msa: fasta, treeMetadata } = buildSearchMsa({
60
61
  hits,
61
62
  query,
@@ -63,10 +64,22 @@ async function search({ self, scope, query, querySeqName, key, }) {
63
64
  taxonomyInfo,
64
65
  querySeqName,
65
66
  });
66
- if (!queryRow) {
67
+ // rows named without NCBI's answer are not worth reusing for a week
68
+ const namesFailed = nameFailures.length > 0;
69
+ if (!queryRow && !namesFailed) {
67
70
  await saveSearch({ id: key, fasta, treeMetadata, rid });
68
71
  }
69
- return { fasta, treeMetadata, rid, queryRow };
72
+ return {
73
+ fasta,
74
+ treeMetadata,
75
+ rid,
76
+ queryRow,
77
+ warnings: namesFailed
78
+ ? [
79
+ 'NCBI did not answer for some species names, so those rows are named by scientific name alone.',
80
+ ]
81
+ : [],
82
+ };
70
83
  }
71
84
  /**
72
85
  * A similarity search, then an alignment of what it found. The program is a
@@ -78,7 +91,7 @@ async function search({ self, scope, query, querySeqName, key, }) {
78
91
  */
79
92
  export async function doLaunchBlast({ self, scope, }) {
80
93
  const params = self.blastParams;
81
- const { selectedTranscript, maxHits, searchProgram = 'blastp' } = params;
94
+ const { selectedTranscript, maxHits = defaultMaxHits, searchProgram = 'blastp', } = params;
82
95
  const msaAlgorithm = params.msaAlgorithm ?? 'browser';
83
96
  const { sequence: query, name: querySeqName } = await resolveQuery(self, scope);
84
97
  if (searchProgram === 'blastp' && msaAlgorithm === 'browser') {
@@ -96,7 +109,7 @@ export async function doLaunchBlast({ self, scope, }) {
96
109
  if (found) {
97
110
  onProgress(`Reusing the ${searchProgram} search from ${new Date(found.timestamp).toLocaleString()}...`);
98
111
  }
99
- const { fasta, treeMetadata, rid, queryRow } = found ?? (await search({ self, scope, query, querySeqName, key }));
112
+ const { fasta, treeMetadata, rid, queryRow, warnings } = found ?? (await search({ self, scope, query, querySeqName, maxHits, key }));
100
113
  const { msa, tree } = queryRow
101
114
  ? { msa: fasta, tree: '' }
102
115
  : await launchMSA({
@@ -117,10 +130,10 @@ export async function doLaunchBlast({ self, scope, }) {
117
130
  tree,
118
131
  treeMetadata: treeMetadataJson,
119
132
  rid: rid ?? '',
120
- geneId: asString(transcript.parentId),
121
- transcriptId: asString(transcript.uniqueId),
133
+ geneId: str(transcript.parentId),
134
+ transcriptId: str(transcript.uniqueId),
122
135
  transcriptName: transcriptName(selectedTranscript),
123
- geneName: asString(transcript.gene_name) ?? asString(transcript.parentId),
136
+ geneName: str(transcript.gene_name) ?? str(transcript.parentId),
124
137
  });
125
- return { msa, tree, treeMetadata: treeMetadataJson };
138
+ return { msa, tree, treeMetadata: treeMetadataJson, warnings };
126
139
  }
@@ -22,6 +22,7 @@ export declare function doLaunchOrthologs({ self, scope, }: {
22
22
  scope: LaunchScope;
23
23
  }): Promise<{
24
24
  treeMetadata: string;
25
+ warnings: string[];
25
26
  msa: string;
26
27
  tree: string;
27
28
  }>;
@@ -36,7 +36,7 @@ export async function doLaunchOrthologs({ self, scope, }) {
36
36
  onProgress,
37
37
  signal,
38
38
  };
39
- const { geneId, representative, rows } = source === 'panther'
39
+ const { geneId, representative, rows, warning } = source === 'panther'
40
40
  ? await findPantherOrthologs(request)
41
41
  : source === 'uniref'
42
42
  ? await findUnirefHomologs({
@@ -91,6 +91,7 @@ export async function doLaunchOrthologs({ self, scope, }) {
91
91
  return {
92
92
  ...result,
93
93
  treeMetadata: JSON.stringify(treeMetadata),
94
+ warnings: warning ? [warning] : [],
94
95
  };
95
96
  }
96
97
  /**
@@ -138,6 +139,7 @@ async function findUnirefHomologs({ geneCandidates, identity, referenceProteomes
138
139
  sequence: found.query.sequence,
139
140
  },
140
141
  rows: found.rows,
142
+ warning: found.warning,
141
143
  };
142
144
  }
143
145
  /**
@@ -6,7 +6,7 @@ import { openLocation } from '@jbrowse/core/util/io';
6
6
  // offset + length) are found by suffix. We fetch the small `.idx` once, look up
7
7
  // the name, and random-read just that block — already valid FASTA
8
8
  // (`>hg38\nSEQ\n>panTro4\nSEQ\n...`). One genome-scale alignment serves any gene
9
- // with no per-gene files or coordinates. See react-msaview's gene-explorer.
9
+ // with no per-gene files or coordinates.
10
10
  export async function fetchIndexedMsa({ location, name, }) {
11
11
  const open = (uri) => openLocation({ uri, locationType: 'UriLocation' });
12
12
  const idxText = await open(`${location.uri}.idx`).readFile('utf8');
@@ -1,22 +1,13 @@
1
1
  import type { Annotation } from 'react-msaview';
2
2
  interface DomainModel {
3
- data: {
4
- treeMetadata?: string;
5
- };
6
- setProgress: (arg: string) => void;
3
+ rowData: Record<string, Record<string, string> | undefined>;
7
4
  setAnnotations: (annotations: Annotation[]) => void;
8
5
  }
9
6
  /**
10
7
  * Overlay protein domains on the alignment using NCBI's pre-computed CDD
11
- * annotations. The BLAST workflow stores each hit's accession in treeMetadata,
12
- * so we look those up via efetch and key the results by MSA row name (which is
13
- * what react-msaview matches domains against).
14
- *
15
- * The overlay is handed over as `Annotation[]`, the shape every source flattens
16
- * to. It used to be dressed up as an InterProScan response — an `xref` invented
17
- * to carry the row name — so that `setDomains` could unwrap it again; that entry
18
- * point exists for plugins holding the actual EBI wire format, which this is
19
- * not.
8
+ * annotations. Each launch stores a row's accession in its row data, so those
9
+ * are looked up via efetch and keyed by MSA row name, which is what
10
+ * react-msaview matches annotations against.
20
11
  */
21
- export declare function loadProteinDomains(self: DomainModel): Promise<void>;
12
+ export declare function loadProteinDomains(self: DomainModel, signal?: AbortSignal): Promise<void>;
22
13
  export {};
@@ -1,30 +1,16 @@
1
1
  import { fetchProteinDomains } from '../utils/ncbiDomains';
2
2
  /**
3
3
  * Overlay protein domains on the alignment using NCBI's pre-computed CDD
4
- * annotations. The BLAST workflow stores each hit's accession in treeMetadata,
5
- * so we look those up via efetch and key the results by MSA row name (which is
6
- * what react-msaview matches domains against).
7
- *
8
- * The overlay is handed over as `Annotation[]`, the shape every source flattens
9
- * to. It used to be dressed up as an InterProScan response — an `xref` invented
10
- * to carry the row name — so that `setDomains` could unwrap it again; that entry
11
- * point exists for plugins holding the actual EBI wire format, which this is
12
- * not.
4
+ * annotations. Each launch stores a row's accession in its row data, so those
5
+ * are looked up via efetch and keyed by MSA row name, which is what
6
+ * react-msaview matches annotations against.
13
7
  */
14
- export async function loadProteinDomains(self) {
15
- const metadataJson = self.data.treeMetadata;
16
- if (!metadataJson) {
17
- throw new Error('No sequence metadata available to look up domains');
18
- }
19
- const metadata = JSON.parse(metadataJson);
20
- const rowAccessions = Object.entries(metadata)
21
- .map(([rowName, meta]) => ({ rowName, accession: meta.Accession }))
22
- .filter((r) => !!r.accession);
8
+ export async function loadProteinDomains(self, signal) {
9
+ const rowAccessions = Object.entries(self.rowData).flatMap(([rowName, row]) => row?.Accession ? [{ rowName, accession: row.Accession }] : []);
23
10
  if (rowAccessions.length === 0) {
24
11
  throw new Error('No NCBI accessions found in alignment rows');
25
12
  }
26
- self.setProgress(`Fetching protein domains from NCBI for ${rowAccessions.length} sequences...`);
27
- const byAccession = await fetchProteinDomains(rowAccessions.map(r => r.accession));
13
+ const byAccession = await fetchProteinDomains(rowAccessions.map(r => r.accession), signal);
28
14
  const annotations = rowAccessions.flatMap(({ rowName, accession }) => (byAccession.get(accession) ?? []).flatMap(({ signature, locations }) => signature.entry
29
15
  ? locations.map(({ start, end }) => ({
30
16
  id: rowName,
@@ -10,8 +10,7 @@ const MATCH = {
10
10
  };
11
11
  function model(treeMetadata) {
12
12
  return {
13
- data: { treeMetadata: JSON.stringify(treeMetadata) },
14
- setProgress: vi.fn(),
13
+ rowData: treeMetadata,
15
14
  setAnnotations: vi.fn(),
16
15
  };
17
16
  }