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.
- package/dist/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.d.ts +3 -4
- package/dist/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.js +22 -17
- package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +4 -4
- package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.js +1 -1
- package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.d.ts +4 -1
- package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.js +5 -8
- package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +10 -9
- package/dist/LaunchMsaView/components/OrthologQuery/QuerySpeciesSelect.js +10 -1
- package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +12 -9
- package/dist/LaunchMsaView/components/SubmitCancelActions.d.ts +1 -2
- package/dist/LaunchMsaView/components/SubmitCancelActions.js +2 -2
- package/dist/LaunchMsaView/components/TabPanel.d.ts +1 -1
- package/dist/LaunchMsaView/components/TabPanel.js +2 -2
- package/dist/LaunchMsaView/components/TranscriptSelector.js +3 -1
- package/dist/LaunchMsaView/components/launchConnectedView.js +8 -1
- package/dist/LaunchMsaView/components/useFeatureSequence.js +6 -4
- package/dist/LaunchMsaView/useQueryRowName.js +6 -2
- package/dist/LaunchMsaView/useQueryRowName.test.js +11 -0
- package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +3 -13
- package/dist/MsaViewPanel/afterCreateAutoruns.js +57 -57
- package/dist/MsaViewPanel/components/MsaViewPanel.js +6 -6
- package/dist/MsaViewPanel/components/MsaViewPanel.test.js +13 -1
- package/dist/MsaViewPanel/doLaunchBlast.d.ts +2 -6
- package/dist/MsaViewPanel/doLaunchBlast.js +28 -15
- package/dist/MsaViewPanel/doLaunchOrthologs.d.ts +1 -0
- package/dist/MsaViewPanel/doLaunchOrthologs.js +3 -1
- package/dist/MsaViewPanel/fetchIndexedMsa.js +1 -1
- package/dist/MsaViewPanel/loadProteinDomains.d.ts +5 -14
- package/dist/MsaViewPanel/loadProteinDomains.js +6 -20
- package/dist/MsaViewPanel/loadProteinDomains.test.js +1 -2
- package/dist/MsaViewPanel/model.d.ts +45 -43
- package/dist/MsaViewPanel/model.js +0 -3
- package/dist/MsaViewPanel/runLaunch.d.ts +3 -2
- package/dist/MsaViewPanel/runLaunch.js +4 -1
- package/dist/MsaViewPanel/util.d.ts +1 -0
- package/dist/MsaViewPanel/util.js +1 -1
- package/dist/chunks/{AboutDialog-WT6GXMXU.js → AboutDialog-IRBPUPJK.js} +2 -2
- package/dist/chunks/{AboutDialog-WT6GXMXU.js.map → AboutDialog-IRBPUPJK.js.map} +2 -2
- package/dist/chunks/AnnotationFileDialog-FCYYZ4JN.js +2 -0
- package/dist/chunks/{AnnotationFileDialog-34R7LFHB.js.map → AnnotationFileDialog-FCYYZ4JN.js.map} +2 -2
- package/dist/chunks/{BgzipFastaMsaAdapter-ZMUARSUQ.js → BgzipFastaMsaAdapter-WWIGI57O.js} +2 -2
- package/dist/chunks/BgzipFastaMsaAdapter-WWIGI57O.js.map +7 -0
- package/dist/chunks/ExportSVGDialog-TYYPI6VB.js +2 -0
- package/dist/chunks/ExportSVGDialog-TYYPI6VB.js.map +7 -0
- package/dist/chunks/{FeatureDialog-XNHIA5PS.js → FeatureDialog-ZBX3XQZ5.js} +1 -1
- package/dist/chunks/{FeatureDialog-XNHIA5PS.js.map → FeatureDialog-ZBX3XQZ5.js.map} +1 -1
- package/dist/chunks/LaunchMsaViewDialog-GJAUPGFC.js +5 -0
- package/dist/chunks/LaunchMsaViewDialog-GJAUPGFC.js.map +7 -0
- package/dist/chunks/MetadataDialog-KZWQFRF7.js +2 -0
- package/dist/chunks/{MetadataDialog-2WT6EX4M.js.map → MetadataDialog-KZWQFRF7.js.map} +2 -2
- package/dist/chunks/MsaViewPanel-RUZAKFCB.js +2 -0
- package/dist/chunks/MsaViewPanel-RUZAKFCB.js.map +7 -0
- package/dist/chunks/{TrackInfoDialog-TYCMLUKG.js → TrackInfoDialog-IFIG77WO.js} +2 -2
- package/dist/chunks/{TrackInfoDialog-TYCMLUKG.js.map → TrackInfoDialog-IFIG77WO.js.map} +1 -1
- package/dist/chunks/TreeNodeInfoDialog-VRCVIF26.js +2 -0
- package/dist/chunks/{TreeNodeInfoDialog-BFF6JRCO.js.map → TreeNodeInfoDialog-VRCVIF26.js.map} +2 -2
- package/dist/chunks/{chunk-O7JNDIKD.js → chunk-F3EC6OCO.js} +1 -1
- package/dist/chunks/{chunk-O7JNDIKD.js.map → chunk-F3EC6OCO.js.map} +1 -1
- package/dist/chunks/chunk-F726IWCW.js +2 -0
- package/dist/chunks/chunk-F726IWCW.js.map +7 -0
- package/dist/chunks/{chunk-ZTQYZDWK.js → chunk-GDFVLZYX.js} +2 -2
- package/dist/chunks/{chunk-ZTQYZDWK.js.map → chunk-GDFVLZYX.js.map} +1 -1
- package/dist/chunks/{chunk-5ORPTTMR.js → chunk-GIHMPOCO.js} +1 -1
- package/dist/chunks/{chunk-5ORPTTMR.js.map → chunk-GIHMPOCO.js.map} +1 -1
- package/dist/chunks/{chunk-ENTWPX2K.js → chunk-H6IQBAW7.js} +5 -5
- package/dist/chunks/{chunk-ENTWPX2K.js.map → chunk-H6IQBAW7.js.map} +1 -1
- package/dist/chunks/{chunk-TITP6LCV.js → chunk-JFL3LJPO.js} +2 -2
- package/dist/chunks/{chunk-TITP6LCV.js.map → chunk-JFL3LJPO.js.map} +2 -2
- package/dist/chunks/chunk-NI3VGKQP.js +3 -0
- package/dist/chunks/chunk-NI3VGKQP.js.map +7 -0
- package/dist/chunks/chunk-NMYD2GWF.js +18 -0
- package/dist/chunks/chunk-NMYD2GWF.js.map +7 -0
- package/dist/chunks/chunk-QQ5LZUIO.js +11 -0
- package/dist/chunks/chunk-QQ5LZUIO.js.map +7 -0
- package/dist/chunks/chunk-QUWPHTZQ.js +2 -0
- package/dist/chunks/chunk-QUWPHTZQ.js.map +7 -0
- package/dist/chunks/{chunk-P77DPK6B.js → chunk-YOTSFJMU.js} +1 -1
- package/dist/chunks/{chunk-P77DPK6B.js.map → chunk-YOTSFJMU.js.map} +1 -1
- package/dist/chunks/chunk-ZCT7RD24.js +2 -0
- package/dist/chunks/chunk-ZCT7RD24.js.map +7 -0
- package/dist/chunks/{dist-UX5CKLQJ.js → dist-CMWKVHC7.js} +1 -1
- package/dist/chunks/{dist-UX5CKLQJ.js.map → dist-CMWKVHC7.js.map} +1 -1
- package/dist/chunks/dist-R73J3CC3.js +2 -0
- package/dist/chunks/model-VE7IYWNU.js +12 -0
- package/dist/chunks/model-VE7IYWNU.js.map +7 -0
- package/dist/chunks/{renderToSvg-B7RCHRO3.js → renderToSvg-VDXAB7TT.js} +2 -2
- package/dist/jbrowse-plugin-msaview.esm.js +2 -2
- package/dist/jbrowse-plugin-msaview.esm.js.map +1 -1
- package/dist/utils/ebiBlast.d.ts +8 -13
- package/dist/utils/ebiBlast.js +20 -45
- package/dist/utils/ebiJobDispatcher.d.ts +4 -4
- package/dist/utils/ebiJobDispatcher.js +4 -4
- package/dist/utils/eutils.js +1 -1
- package/dist/utils/fetch.d.ts +4 -0
- package/dist/utils/fetch.js +7 -1
- package/dist/utils/homologSearch.d.ts +6 -2
- package/dist/utils/msaRows.d.ts +1 -2
- package/dist/utils/msaRows.js +13 -1
- package/dist/utils/ncbiDomains.d.ts +1 -1
- package/dist/utils/ncbiDomains.js +19 -7
- package/dist/utils/ncbiDomains.test.js +36 -0
- package/dist/utils/ncbiOrthologs.d.ts +24 -5
- package/dist/utils/ncbiOrthologs.js +55 -19
- package/dist/utils/ncbiOrthologs.test.js +58 -8
- package/dist/utils/pantherOrthologs.js +4 -1
- package/dist/utils/phmmer.js +4 -8
- package/dist/utils/taxonomyNames.d.ts +6 -1
- package/dist/utils/taxonomyNames.js +7 -1
- package/dist/utils/taxonomyNames.test.js +12 -0
- package/dist/utils/unirefHomologs.d.ts +6 -0
- package/dist/utils/unirefHomologs.js +25 -10
- package/dist/utils/unirefHomologs.test.js +49 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +18 -18
- package/src/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.ts +23 -20
- package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +4 -4
- package/src/LaunchMsaView/components/BlastQuery/BlastPanel.tsx +3 -1
- package/src/LaunchMsaView/components/BlastQuery/CachedBlastResults.tsx +7 -19
- package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +10 -9
- package/src/LaunchMsaView/components/OrthologQuery/QuerySpeciesSelect.tsx +12 -1
- package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +12 -8
- package/src/LaunchMsaView/components/SubmitCancelActions.tsx +1 -3
- package/src/LaunchMsaView/components/TabPanel.tsx +1 -2
- package/src/LaunchMsaView/components/TranscriptSelector.tsx +3 -1
- package/src/LaunchMsaView/components/launchConnectedView.ts +8 -1
- package/src/LaunchMsaView/components/useFeatureSequence.ts +8 -2
- package/src/LaunchMsaView/useQueryRowName.test.ts +15 -0
- package/src/LaunchMsaView/useQueryRowName.ts +7 -2
- package/src/MsaViewPanel/afterCreateAutoruns.ts +74 -58
- package/src/MsaViewPanel/components/MsaViewPanel.test.tsx +18 -1
- package/src/MsaViewPanel/components/MsaViewPanel.tsx +15 -16
- package/src/MsaViewPanel/doLaunchBlast.ts +37 -17
- package/src/MsaViewPanel/doLaunchOrthologs.ts +4 -1
- package/src/MsaViewPanel/fetchIndexedMsa.ts +1 -1
- package/src/MsaViewPanel/loadProteinDomains.test.ts +1 -2
- package/src/MsaViewPanel/loadProteinDomains.ts +13 -28
- package/src/MsaViewPanel/model.ts +0 -3
- package/src/MsaViewPanel/runLaunch.ts +8 -8
- package/src/MsaViewPanel/types.ts +1 -1
- package/src/MsaViewPanel/util.ts +1 -1
- package/src/utils/ebiBlast.ts +29 -56
- package/src/utils/ebiJobDispatcher.ts +4 -4
- package/src/utils/eutils.ts +1 -1
- package/src/utils/fetch.ts +12 -2
- package/src/utils/homologSearch.ts +6 -2
- package/src/utils/msaRows.ts +18 -4
- package/src/utils/ncbiDomains.test.ts +53 -0
- package/src/utils/ncbiDomains.ts +21 -8
- package/src/utils/ncbiOrthologs.test.ts +70 -7
- package/src/utils/ncbiOrthologs.ts +81 -24
- package/src/utils/pantherOrthologs.ts +7 -1
- package/src/utils/phmmer.ts +3 -7
- package/src/utils/taxonomyNames.test.ts +15 -0
- package/src/utils/taxonomyNames.ts +7 -0
- package/src/utils/unirefHomologs.test.ts +57 -2
- package/src/utils/unirefHomologs.ts +38 -13
- package/src/version.ts +1 -1
- package/dist/LaunchMsaView/components/util.d.ts +0 -7
- package/dist/LaunchMsaView/components/util.js +0 -16
- package/dist/MsaViewPanel/components/ErrorBoundary.d.ts +0 -19
- package/dist/MsaViewPanel/components/ErrorBoundary.js +0 -21
- package/dist/chunks/AnnotationFileDialog-34R7LFHB.js +0 -2
- package/dist/chunks/BgzipFastaMsaAdapter-ZMUARSUQ.js.map +0 -7
- package/dist/chunks/ExportSVGDialog-NMVUHFGY.js +0 -2
- package/dist/chunks/ExportSVGDialog-NMVUHFGY.js.map +0 -7
- package/dist/chunks/LaunchMsaViewDialog-WBXEEHJL.js +0 -5
- package/dist/chunks/LaunchMsaViewDialog-WBXEEHJL.js.map +0 -7
- package/dist/chunks/MetadataDialog-2WT6EX4M.js +0 -2
- package/dist/chunks/MsaViewPanel-YYP4CYYO.js +0 -2
- package/dist/chunks/MsaViewPanel-YYP4CYYO.js.map +0 -7
- package/dist/chunks/TreeNodeInfoDialog-BFF6JRCO.js +0 -2
- package/dist/chunks/chunk-FCNT6B47.js +0 -2
- package/dist/chunks/chunk-FCNT6B47.js.map +0 -7
- package/dist/chunks/chunk-MBI4TMKR.js +0 -2
- package/dist/chunks/chunk-MBI4TMKR.js.map +0 -7
- package/dist/chunks/chunk-MHBTV7BZ.js +0 -3
- package/dist/chunks/chunk-MHBTV7BZ.js.map +0 -7
- package/dist/chunks/chunk-PJZ3XTMG.js +0 -6
- package/dist/chunks/chunk-PJZ3XTMG.js.map +0 -7
- package/dist/chunks/chunk-QAC6JSDZ.js +0 -5
- package/dist/chunks/chunk-QAC6JSDZ.js.map +0 -7
- package/dist/chunks/chunk-QMLPITS4.js +0 -18
- package/dist/chunks/chunk-QMLPITS4.js.map +0 -7
- package/dist/chunks/chunk-RTKNCGDF.js +0 -8
- package/dist/chunks/chunk-RTKNCGDF.js.map +0 -7
- package/dist/chunks/dist-2T65ZS4E.js +0 -2
- package/dist/chunks/model-7VBTOIJX.js +0 -12
- package/dist/chunks/model-7VBTOIJX.js.map +0 -7
- package/dist/utils/types.d.ts +0 -18
- package/src/LaunchMsaView/components/util.ts +0 -22
- package/src/MsaViewPanel/components/ErrorBoundary.tsx +0 -41
- package/src/utils/types.ts +0 -17
- /package/dist/chunks/{dist-2T65ZS4E.js.map → dist-R73J3CC3.js.map} +0 -0
- /package/dist/chunks/{renderToSvg-B7RCHRO3.js.map → renderToSvg-VDXAB7TT.js.map} +0 -0
- /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
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
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
|
-
|
|
164
|
-
|
|
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
|
|
168
|
-
launch
|
|
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
|
|
177
|
-
*
|
|
178
|
-
*
|
|
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
|
-
|
|
192
|
+
if (!isAbortError(e)) {
|
|
193
|
+
console.error('[msaview-domains] auto-load failed:', e);
|
|
194
|
+
}
|
|
195
195
|
}
|
|
196
196
|
finally {
|
|
197
|
-
self
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
229
|
+
const uniprotId = msaUrl
|
|
230
|
+
? getUniprotIdFromAlphaFoldUrl(msaUrl)
|
|
231
|
+
: undefined;
|
|
232
|
+
if (uniprotId) {
|
|
233
|
+
self.setUniprotId(uniprotId);
|
|
233
234
|
}
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
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(
|
|
28
|
-
React.createElement(
|
|
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
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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:
|
|
121
|
-
transcriptId:
|
|
133
|
+
geneId: str(transcript.parentId),
|
|
134
|
+
transcriptId: str(transcript.uniqueId),
|
|
122
135
|
transcriptName: transcriptName(selectedTranscript),
|
|
123
|
-
geneName:
|
|
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
|
}
|
|
@@ -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.
|
|
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
|
-
|
|
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.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
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
|
-
|
|
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,
|