jbrowse-plugin-msaview 3.5.0 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/AddHighlightModel/index.js +3 -3
- package/dist/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.d.ts +8 -2
- package/dist/BgzipFastaMsaAdapter/configSchema.d.ts +3 -3
- package/dist/BgzipFastaMsaAdapter/configSchema.js +1 -1
- package/dist/LaunchMsaView/cleanProteinSequence.test.d.ts +1 -0
- package/dist/LaunchMsaView/cleanProteinSequence.test.js +77 -0
- package/dist/LaunchMsaView/codingFeature.d.ts +14 -0
- package/dist/LaunchMsaView/codingFeature.js +38 -0
- package/dist/LaunchMsaView/codingFeature.test.d.ts +1 -0
- package/dist/LaunchMsaView/codingFeature.test.js +75 -0
- package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.d.ts +3 -1
- package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +47 -55
- package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.d.ts +3 -1
- package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.js +16 -17
- package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.d.ts +7 -3
- package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.js +8 -7
- package/dist/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.js +2 -2
- package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.js +14 -13
- package/dist/LaunchMsaView/components/BlastQuery/consts.d.ts +2 -0
- package/dist/LaunchMsaView/components/BlastQuery/consts.js +28 -4
- package/dist/LaunchMsaView/components/HelpButton.d.ts +2 -0
- package/dist/LaunchMsaView/components/HelpButton.js +17 -0
- package/dist/LaunchMsaView/components/HelpDialog.d.ts +4 -0
- package/dist/LaunchMsaView/components/HelpDialog.js +18 -0
- package/dist/LaunchMsaView/components/LaunchMsaViewDialog.d.ts +3 -1
- package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js +21 -16
- package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.d.ts +3 -1
- package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +34 -26
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.d.ts +3 -1
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +30 -26
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.d.ts +2 -1
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.js +18 -2
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.d.ts +1 -0
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.js +13 -0
- package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.d.ts +3 -1
- package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +50 -27
- package/dist/LaunchMsaView/components/SequenceStatus.d.ts +14 -0
- package/dist/LaunchMsaView/components/SequenceStatus.js +15 -0
- package/dist/LaunchMsaView/components/SubmitCancelActions.d.ts +6 -2
- package/dist/LaunchMsaView/components/SubmitCancelActions.js +28 -13
- package/dist/LaunchMsaView/components/SubmitCancelActions.test.js +52 -4
- package/dist/LaunchMsaView/components/TabPanel.js +15 -3
- package/dist/LaunchMsaView/components/TabPanel.test.d.ts +1 -0
- package/dist/LaunchMsaView/components/TabPanel.test.js +36 -0
- package/dist/LaunchMsaView/components/TranscriptSelector.d.ts +3 -1
- package/dist/LaunchMsaView/components/TranscriptSelector.js +4 -2
- package/dist/LaunchMsaView/components/calculateProteinSequence.d.ts +12 -2
- package/dist/LaunchMsaView/components/calculateProteinSequence.js +38 -23
- package/dist/LaunchMsaView/components/fetchSeq.d.ts +4 -1
- package/dist/LaunchMsaView/components/fetchSeq.js +14 -4
- package/dist/LaunchMsaView/components/launchConnectedView.d.ts +23 -0
- package/dist/LaunchMsaView/components/launchConnectedView.js +39 -0
- package/dist/LaunchMsaView/components/launchPlacement.d.ts +8 -0
- package/dist/LaunchMsaView/components/launchPlacement.js +25 -0
- package/dist/LaunchMsaView/components/useFeatureSequence.d.ts +2 -0
- package/dist/LaunchMsaView/components/useFeatureSequence.js +14 -11
- package/dist/LaunchMsaView/components/useTranscriptSelection.d.ts +10 -1
- package/dist/LaunchMsaView/components/useTranscriptSelection.js +21 -3
- package/dist/LaunchMsaView/components/useTranscriptSelection.test.d.ts +1 -0
- package/dist/LaunchMsaView/components/useTranscriptSelection.test.js +57 -0
- package/dist/LaunchMsaView/detectQueryRow.js +4 -7
- package/dist/LaunchMsaView/index.js +28 -9
- package/dist/LaunchMsaView/launchTarget.d.ts +36 -8
- package/dist/LaunchMsaView/launchTarget.js +23 -13
- package/dist/LaunchMsaView/launchTarget.test.js +80 -15
- package/dist/LaunchMsaView/useQueryRowName.d.ts +12 -0
- package/dist/LaunchMsaView/useQueryRowName.js +11 -0
- package/dist/LaunchMsaView/useQueryRowName.test.js +18 -1
- package/dist/LaunchMsaView/util.d.ts +6 -0
- package/dist/LaunchMsaView/util.js +12 -3
- package/dist/LaunchMsaViewExtensionPoint/index.test.js +15 -0
- package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +6 -4
- package/dist/MsaViewPanel/afterCreateAutoruns.js +38 -14
- package/dist/MsaViewPanel/applyRegion.d.ts +23 -0
- package/dist/MsaViewPanel/applyRegion.js +19 -0
- package/dist/MsaViewPanel/applyRegion.test.d.ts +1 -0
- package/dist/MsaViewPanel/applyRegion.test.js +45 -0
- package/dist/MsaViewPanel/components/LaunchProgress.d.ts +4 -3
- package/dist/MsaViewPanel/components/LaunchProgress.js +10 -7
- package/dist/MsaViewPanel/components/MsaViewPanel.js +7 -3
- package/dist/MsaViewPanel/components/MsaViewPanel.test.js +24 -0
- package/dist/MsaViewPanel/doLaunchBlast.js +2 -1
- package/dist/MsaViewPanel/doLaunchBlast.test.js +12 -0
- package/dist/MsaViewPanel/doLaunchOrthologs.test.js +1 -1
- package/dist/MsaViewPanel/genomeToMSA.js +24 -6
- package/dist/MsaViewPanel/genomeToMSA.test.js +218 -44
- package/dist/MsaViewPanel/launchAutoruns.test.d.ts +1 -0
- package/dist/MsaViewPanel/launchAutoruns.test.js +36 -0
- package/dist/MsaViewPanel/model.d.ts +156 -61
- package/dist/MsaViewPanel/model.js +69 -3
- package/dist/MsaViewPanel/model.test.js +66 -0
- package/dist/MsaViewPanel/msaDataStore.d.ts +5 -3
- package/dist/MsaViewPanel/msaDataStore.js +3 -12
- package/dist/MsaViewPanel/msaDataStore.test.js +8 -1
- package/dist/MsaViewPanel/observeProteinHighlights.test.js +4 -4
- package/dist/MsaViewPanel/regionSnapshot.test.d.ts +1 -0
- package/dist/MsaViewPanel/regionSnapshot.test.js +29 -0
- package/dist/MsaViewPanel/resolveConnectedTranscript.d.ts +41 -4
- package/dist/MsaViewPanel/resolveConnectedTranscript.js +109 -68
- package/dist/MsaViewPanel/resolveConnectedTranscript.test.d.ts +1 -0
- package/dist/MsaViewPanel/resolveConnectedTranscript.test.js +149 -0
- package/dist/MsaViewPanel/runLaunch.d.ts +8 -4
- package/dist/MsaViewPanel/runLaunch.js +15 -4
- package/dist/MsaViewPanel/runLaunch.test.js +16 -0
- package/dist/MsaViewPanel/storedData.test.js +43 -0
- package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +15 -7
- package/dist/MsaViewPanel/util.d.ts +23 -23
- package/dist/MsaViewPanel/util.js +23 -24
- package/dist/index.d.ts +11 -11
- package/dist/index.js +17 -10
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +54 -67
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/dist/utils/blastCache.d.ts +26 -2
- package/dist/utils/blastCache.js +51 -39
- package/dist/utils/blastCache.test.js +46 -19
- package/dist/utils/domainCache.d.ts +1 -1
- package/dist/utils/domainCache.js +18 -14
- package/dist/utils/ebiJobDispatcher.d.ts +11 -0
- package/dist/utils/ebiJobDispatcher.js +12 -13
- package/dist/utils/ebiJobDispatcher.test.js +17 -3
- package/dist/utils/eutils.d.ts +14 -0
- package/dist/utils/eutils.js +48 -14
- package/dist/utils/eutils.test.d.ts +1 -0
- package/dist/utils/eutils.test.js +31 -0
- package/dist/utils/idb.d.ts +16 -3
- package/dist/utils/idb.js +51 -5
- package/dist/utils/idb.test.d.ts +1 -0
- package/dist/utils/idb.test.js +78 -0
- package/dist/utils/ncbiDomains.js +4 -4
- package/dist/utils/ncbiOrthologs.d.ts +1 -1
- package/dist/utils/ncbiOrthologs.js +18 -17
- package/dist/utils/ncbiOrthologs.test.js +17 -1
- package/dist/utils/ncbiTaxonomy.js +14 -15
- package/dist/utils/taxonomyNames.js +23 -19
- package/dist/utils/taxonomyNames.test.d.ts +1 -0
- package/dist/utils/taxonomyNames.test.js +33 -0
- package/dist/utils/unirefHomologs.js +2 -2
- package/dist/utils/unirefHomologs.test.js +8 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +23 -23
- package/src/AddHighlightModel/index.tsx +1 -1
- package/src/BgzipFastaMsaAdapter/configSchema.ts +1 -1
- package/src/LaunchMsaView/cleanProteinSequence.test.ts +91 -0
- package/src/LaunchMsaView/codingFeature.test.ts +96 -0
- package/src/LaunchMsaView/codingFeature.ts +49 -0
- package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +60 -56
- package/src/LaunchMsaView/components/BlastQuery/BlastManualPanel.tsx +18 -15
- package/src/LaunchMsaView/components/BlastQuery/BlastPanel.tsx +24 -11
- package/src/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.tsx +4 -4
- package/src/LaunchMsaView/components/BlastQuery/CachedBlastResults.tsx +16 -15
- package/src/LaunchMsaView/components/BlastQuery/consts.ts +31 -4
- package/src/LaunchMsaView/components/HelpButton.tsx +33 -0
- package/src/LaunchMsaView/components/HelpDialog.tsx +78 -0
- package/src/LaunchMsaView/components/LaunchMsaViewDialog.tsx +54 -34
- package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +46 -23
- package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +41 -28
- package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.ts +16 -0
- package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.tsx +24 -2
- package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +81 -25
- package/src/LaunchMsaView/components/SequenceStatus.tsx +29 -0
- package/src/LaunchMsaView/components/SubmitCancelActions.test.tsx +88 -4
- package/src/LaunchMsaView/components/SubmitCancelActions.tsx +43 -16
- package/src/LaunchMsaView/components/TabPanel.test.tsx +51 -0
- package/src/LaunchMsaView/components/TabPanel.tsx +16 -4
- package/src/LaunchMsaView/components/TranscriptSelector.tsx +6 -1
- package/src/LaunchMsaView/components/calculateProteinSequence.ts +51 -22
- package/src/LaunchMsaView/components/fetchSeq.ts +25 -5
- package/src/LaunchMsaView/components/launchConnectedView.ts +56 -0
- package/src/LaunchMsaView/components/launchPlacement.tsx +41 -0
- package/src/LaunchMsaView/components/useFeatureSequence.ts +18 -11
- package/src/LaunchMsaView/components/useTranscriptSelection.test.tsx +66 -0
- package/src/LaunchMsaView/components/useTranscriptSelection.ts +41 -2
- package/src/LaunchMsaView/detectQueryRow.ts +5 -7
- package/src/LaunchMsaView/index.ts +32 -13
- package/src/LaunchMsaView/launchTarget.test.ts +88 -15
- package/src/LaunchMsaView/launchTarget.ts +49 -14
- package/src/LaunchMsaView/useQueryRowName.test.ts +28 -1
- package/src/LaunchMsaView/useQueryRowName.ts +17 -0
- package/src/LaunchMsaView/util.ts +15 -5
- package/src/LaunchMsaViewExtensionPoint/index.test.ts +16 -0
- package/src/LaunchMsaViewExtensionPoint/index.ts +28 -1
- package/src/MsaViewPanel/afterCreateAutoruns.ts +46 -14
- package/src/MsaViewPanel/applyRegion.test.ts +55 -0
- package/src/MsaViewPanel/applyRegion.ts +33 -0
- package/src/MsaViewPanel/components/LaunchProgress.tsx +12 -5
- package/src/MsaViewPanel/components/MsaViewPanel.test.tsx +26 -0
- package/src/MsaViewPanel/components/MsaViewPanel.tsx +7 -5
- package/src/MsaViewPanel/doLaunchBlast.test.ts +20 -1
- package/src/MsaViewPanel/doLaunchBlast.ts +2 -1
- package/src/MsaViewPanel/doLaunchOrthologs.test.ts +1 -1
- package/src/MsaViewPanel/genomeToMSA.test.ts +255 -59
- package/src/MsaViewPanel/genomeToMSA.ts +30 -5
- package/src/MsaViewPanel/launchAutoruns.test.ts +45 -0
- package/src/MsaViewPanel/model.test.ts +75 -0
- package/src/MsaViewPanel/model.ts +76 -3
- package/src/MsaViewPanel/msaDataStore.test.ts +14 -2
- package/src/MsaViewPanel/msaDataStore.ts +4 -12
- package/src/MsaViewPanel/observeProteinHighlights.test.ts +4 -4
- package/src/MsaViewPanel/regionSnapshot.test.ts +34 -0
- package/src/MsaViewPanel/resolveConnectedTranscript.test.ts +183 -0
- package/src/MsaViewPanel/resolveConnectedTranscript.ts +160 -67
- package/src/MsaViewPanel/runLaunch.test.ts +19 -0
- package/src/MsaViewPanel/runLaunch.ts +21 -4
- package/src/MsaViewPanel/storedData.test.ts +58 -0
- package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +17 -7
- package/src/MsaViewPanel/util.ts +38 -29
- package/src/index.ts +19 -10
- package/src/utils/blastCache.test.ts +59 -20
- package/src/utils/blastCache.ts +62 -39
- package/src/utils/domainCache.ts +27 -15
- package/src/utils/ebiJobDispatcher.test.ts +18 -3
- package/src/utils/ebiJobDispatcher.ts +12 -16
- package/src/utils/eutils.test.ts +39 -0
- package/src/utils/eutils.ts +64 -14
- package/src/utils/idb.test.ts +106 -0
- package/src/utils/idb.ts +57 -5
- package/src/utils/ncbiDomains.ts +4 -4
- package/src/utils/ncbiOrthologs.test.ts +20 -0
- package/src/utils/ncbiOrthologs.ts +26 -28
- package/src/utils/ncbiTaxonomy.ts +17 -20
- package/src/utils/taxonomyNames.test.ts +41 -0
- package/src/utils/taxonomyNames.ts +33 -19
- package/src/utils/unirefHomologs.test.ts +11 -1
- package/src/utils/unirefHomologs.ts +2 -2
- package/src/version.ts +1 -1
- package/dist/LaunchMsaView/components/BlastQuery/blastLaunchView.d.ts +0 -16
- package/dist/LaunchMsaView/components/BlastQuery/blastLaunchView.js +0 -31
- package/dist/LaunchMsaView/components/ManualMSALoader/launchView.d.ts +0 -16
- package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js +0 -16
- package/dist/LaunchMsaView/components/OrthologQuery/orthologLaunchView.d.ts +0 -9
- package/dist/LaunchMsaView/components/OrthologQuery/orthologLaunchView.js +0 -15
- package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.d.ts +0 -11
- package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.js +0 -21
- package/dist/LaunchMsaView/components/geneticCodes.d.ts +0 -15
- package/dist/LaunchMsaView/components/geneticCodes.js +0 -227
- package/src/LaunchMsaView/components/BlastQuery/blastLaunchView.ts +0 -59
- package/src/LaunchMsaView/components/ManualMSALoader/launchView.ts +0 -43
- package/src/LaunchMsaView/components/OrthologQuery/orthologLaunchView.ts +0 -31
- package/src/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.ts +0 -38
- package/src/LaunchMsaView/components/geneticCodes.ts +0 -298
|
@@ -2,11 +2,14 @@ import { getContainingView, sum } from '@jbrowse/core/util';
|
|
|
2
2
|
export function getLinearGenomeView(model) {
|
|
3
3
|
return getContainingView(model);
|
|
4
4
|
}
|
|
5
|
+
function isNonEmptyString(v) {
|
|
6
|
+
return typeof v === 'string' && v !== '';
|
|
7
|
+
}
|
|
5
8
|
function uniqueDefined(vals) {
|
|
6
|
-
return [...new Set(vals.filter(
|
|
9
|
+
return [...new Set(vals.filter(isNonEmptyString))];
|
|
7
10
|
}
|
|
8
11
|
function joinDefined(sep, parts) {
|
|
9
|
-
return parts.filter(
|
|
12
|
+
return parts.filter(isNonEmptyString).join(sep);
|
|
10
13
|
}
|
|
11
14
|
export function getTranscriptFeatures(feature) {
|
|
12
15
|
// check if we are looking at a 'two-level' or 'three-level' feature by
|
|
@@ -67,8 +70,14 @@ export function getSortedTranscriptFeatures(feature) {
|
|
|
67
70
|
const transcripts = getTranscriptFeatures(feature);
|
|
68
71
|
return transcripts.toSorted((a, b) => getTranscriptLength(b).len - getTranscriptLength(a).len);
|
|
69
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* A translation as a query, still one residue per codon: g2p numbers codons,
|
|
75
|
+
* and the query row is read through it. So the partial first codon (`&`), an
|
|
76
|
+
* internal stop and an unreadable codon become X, and only what trails the
|
|
77
|
+
* last whole codon is dropped.
|
|
78
|
+
*/
|
|
70
79
|
export function cleanProteinSequence(seq) {
|
|
71
|
-
return seq.
|
|
80
|
+
return seq.replace(/[*&]+$/, '').replaceAll(/[*&]/g, 'X');
|
|
72
81
|
}
|
|
73
82
|
export function getGeneIdentifiers(feature) {
|
|
74
83
|
return uniqueDefined([
|
|
@@ -53,6 +53,21 @@ test('searchParams is a source, stored under the name the dialog uses', () => {
|
|
|
53
53
|
test('a launch naming no source at all is refused', () => {
|
|
54
54
|
expect(() => launch({ connectedViewId: 'lgv1' })).toThrow(/searchParams/);
|
|
55
55
|
});
|
|
56
|
+
test('region and the data-layer keys reach the view as snapshot properties', () => {
|
|
57
|
+
const region = { row: 'Human', start: 245, end: 249 };
|
|
58
|
+
const clades = [{ mrca: ['Human', 'Mouse'], tips: 2, mark: 'bracket' }];
|
|
59
|
+
const snapshot = launch({
|
|
60
|
+
data: { msa: '>Human\nMEEP' },
|
|
61
|
+
region,
|
|
62
|
+
clades,
|
|
63
|
+
relativeTo: 'Human',
|
|
64
|
+
gffFilehandle: { uri: 'http://example.com/a.gff' },
|
|
65
|
+
});
|
|
66
|
+
expect(snapshot.region).toEqual(region);
|
|
67
|
+
expect(snapshot.clades).toEqual(clades);
|
|
68
|
+
expect(snapshot.relativeTo).toBe('Human');
|
|
69
|
+
expect(snapshot.gffFilehandle).toEqual({ uri: 'http://example.com/a.gff' });
|
|
70
|
+
});
|
|
56
71
|
test('one field set is enough to need init', () => {
|
|
57
72
|
const snapshot = launch({ data: { msa: '>a\nMEEP' }, querySeqName: 'QUERY' });
|
|
58
73
|
expect(snapshot.init).toEqual({
|
|
@@ -19,10 +19,12 @@ export declare function loadStoredData(self: JBrowsePluginMsaViewModel): void;
|
|
|
19
19
|
export declare function storeDataToIndexedDB(self: JBrowsePluginMsaViewModel): void;
|
|
20
20
|
/**
|
|
21
21
|
* Same shape as launchBlastIfNeeded, for the ortholog path: the params ARE the
|
|
22
|
-
* request, and
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
22
|
+
* request, and `launchCompleted` is what marks it done. They are left in place
|
|
23
|
+
* either way -- on failure so the error stays attributable to a specific
|
|
24
|
+
* request, and on success because they are the only durable statement of what
|
|
25
|
+
* the view is, which is what a stored alignment that expired is rebuilt from.
|
|
26
|
+
* The autorun tracks those two reads alone, so nothing refires until a new
|
|
27
|
+
* request replaces them or a retry clears the mark.
|
|
26
28
|
*/
|
|
27
29
|
export declare function launchOrthologsIfNeeded(self: JBrowsePluginMsaViewModel): void;
|
|
28
30
|
export declare function launchBlastIfNeeded(self: JBrowsePluginMsaViewModel): void;
|
|
@@ -9,7 +9,9 @@ import { cleanupOldData, generateDataStoreId, retrieveMsaData, storeMsaData, } f
|
|
|
9
9
|
import { runLaunch } from './runLaunch';
|
|
10
10
|
import { getProteinViews } from './structureConnection';
|
|
11
11
|
import { getUniprotIdFromAlphaFoldUrl, hasQueryRow, transcriptPosToVisibleCol, } from './util';
|
|
12
|
-
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.
|
|
12
|
+
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.";
|
|
13
|
+
const RELAUNCHABLE = ' Retry runs the original search again and rebuilds it.';
|
|
14
|
+
const START_OVER = ' Relaunch it from the gene to rebuild it.';
|
|
13
15
|
export function loadStoredData(self) {
|
|
14
16
|
const { dataStoreId, rows } = self;
|
|
15
17
|
if (dataStoreId && rows.length === 0) {
|
|
@@ -30,15 +32,24 @@ export function loadStoredData(self) {
|
|
|
30
32
|
if (storedData.treeMetadata) {
|
|
31
33
|
self.setTreeMetadata(storedData.treeMetadata);
|
|
32
34
|
}
|
|
35
|
+
if (storedData.gff) {
|
|
36
|
+
self.setGFF(storedData.gff);
|
|
37
|
+
}
|
|
33
38
|
self.setLastStoredData(currentData(self));
|
|
34
39
|
});
|
|
35
40
|
}
|
|
36
41
|
else {
|
|
37
42
|
// the id names nothing, so clearing it is what lets react-msaview's
|
|
38
|
-
// "Return to import form" actually return instead of landing back
|
|
43
|
+
// "Return to import form" actually return instead of landing back
|
|
44
|
+
// here. The request that built the alignment is kept, though: it is
|
|
45
|
+
// the whole of a retry, and dropping it left the user reading an
|
|
46
|
+
// apology with nothing to press.
|
|
39
47
|
transaction(() => {
|
|
40
48
|
self.setDataStoreId(undefined);
|
|
41
|
-
self.setError(new Error(EXPIRED_MESSAGE
|
|
49
|
+
self.setError(new Error(EXPIRED_MESSAGE +
|
|
50
|
+
((self.blastParams ?? self.orthologParams)
|
|
51
|
+
? RELAUNCHABLE
|
|
52
|
+
: START_OVER)));
|
|
42
53
|
});
|
|
43
54
|
}
|
|
44
55
|
}
|
|
@@ -51,15 +62,21 @@ export function loadStoredData(self) {
|
|
|
51
62
|
})();
|
|
52
63
|
}
|
|
53
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Everything react-msaview drops from the snapshot once it passes 50kB. The
|
|
67
|
+
* GFF is among them: a local one's filehandle is cleared once it loads, so
|
|
68
|
+
* this row is the only copy.
|
|
69
|
+
*/
|
|
54
70
|
function currentData(self) {
|
|
55
|
-
const { msa, tree, treeMetadata } = self.data;
|
|
56
|
-
return { msa, tree, treeMetadata };
|
|
71
|
+
const { msa, tree, treeMetadata, gff } = self.data;
|
|
72
|
+
return { msa, tree, treeMetadata, gff };
|
|
57
73
|
}
|
|
58
74
|
function sameData(a, b) {
|
|
59
75
|
return (!!a &&
|
|
60
76
|
a.msa === b.msa &&
|
|
61
77
|
a.tree === b.tree &&
|
|
62
|
-
a.treeMetadata === b.treeMetadata
|
|
78
|
+
a.treeMetadata === b.treeMetadata &&
|
|
79
|
+
a.gff === b.gff);
|
|
63
80
|
}
|
|
64
81
|
/**
|
|
65
82
|
* Keep IndexedDB holding what the view holds. The first run writes a new row and
|
|
@@ -115,19 +132,26 @@ export function storeDataToIndexedDB(self) {
|
|
|
115
132
|
}
|
|
116
133
|
/**
|
|
117
134
|
* Same shape as launchBlastIfNeeded, for the ortholog path: the params ARE the
|
|
118
|
-
* request, and
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
135
|
+
* request, and `launchCompleted` is what marks it done. They are left in place
|
|
136
|
+
* either way -- on failure so the error stays attributable to a specific
|
|
137
|
+
* request, and on success because they are the only durable statement of what
|
|
138
|
+
* the view is, which is what a stored alignment that expired is rebuilt from.
|
|
139
|
+
* The autorun tracks those two reads alone, so nothing refires until a new
|
|
140
|
+
* request replaces them or a retry clears the mark.
|
|
122
141
|
*/
|
|
123
142
|
export function launchOrthologsIfNeeded(self) {
|
|
124
|
-
if (self.orthologParams &&
|
|
143
|
+
if (self.orthologParams &&
|
|
144
|
+
!self.launchCompleted &&
|
|
145
|
+
!awaitingTranscript(self)) {
|
|
125
146
|
runLaunch({
|
|
126
147
|
self,
|
|
127
148
|
message: 'Resolving orthologs',
|
|
128
149
|
launch: scope => doLaunchOrthologs({ self, scope }),
|
|
150
|
+
// marked rather than dropped: the request is the only durable statement
|
|
151
|
+
// of what this view is, and a view whose stored alignment expired runs it
|
|
152
|
+
// again
|
|
129
153
|
onLaunched: () => {
|
|
130
|
-
self.
|
|
154
|
+
self.setLaunchCompleted(true);
|
|
131
155
|
},
|
|
132
156
|
});
|
|
133
157
|
}
|
|
@@ -142,13 +166,13 @@ function awaitingTranscript(self) {
|
|
|
142
166
|
return !!self.connectedTranscript && !self.connectedFeature;
|
|
143
167
|
}
|
|
144
168
|
export function launchBlastIfNeeded(self) {
|
|
145
|
-
if (self.blastParams && !awaitingTranscript(self)) {
|
|
169
|
+
if (self.blastParams && !self.launchCompleted && !awaitingTranscript(self)) {
|
|
146
170
|
runLaunch({
|
|
147
171
|
self,
|
|
148
172
|
message: 'Submitting query',
|
|
149
173
|
launch: scope => doLaunchBlast({ self, scope }),
|
|
150
174
|
onLaunched: () => {
|
|
151
|
-
self.
|
|
175
|
+
self.setLaunchCompleted(true);
|
|
152
176
|
},
|
|
153
177
|
});
|
|
154
178
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Region } from 'react-msaview';
|
|
2
|
+
interface RegionModel {
|
|
3
|
+
region?: Region;
|
|
4
|
+
viewInitialized: boolean;
|
|
5
|
+
numColumns: number;
|
|
6
|
+
treeFilehandle?: unknown;
|
|
7
|
+
data: {
|
|
8
|
+
tree?: string;
|
|
9
|
+
};
|
|
10
|
+
zoomToRegion: (region: Region) => void;
|
|
11
|
+
setRegion: (region?: Region) => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Zoom onto the launch's `region` once, then forget it, so a reloaded session
|
|
15
|
+
* opens where the reader left it rather than back on the region.
|
|
16
|
+
*
|
|
17
|
+
* zoomToRegion is a no-op until the view has a width, and it resolves residues
|
|
18
|
+
* through the visible columns, so it waits for the alignment and for a tree
|
|
19
|
+
* file too: a clade the tree collapses hides columns and would move the
|
|
20
|
+
* target after the zoom.
|
|
21
|
+
*/
|
|
22
|
+
export declare function applyRegion(self: RegionModel): void;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zoom onto the launch's `region` once, then forget it, so a reloaded session
|
|
3
|
+
* opens where the reader left it rather than back on the region.
|
|
4
|
+
*
|
|
5
|
+
* zoomToRegion is a no-op until the view has a width, and it resolves residues
|
|
6
|
+
* through the visible columns, so it waits for the alignment and for a tree
|
|
7
|
+
* file too: a clade the tree collapses hides columns and would move the
|
|
8
|
+
* target after the zoom.
|
|
9
|
+
*/
|
|
10
|
+
export function applyRegion(self) {
|
|
11
|
+
const { region } = self;
|
|
12
|
+
if (region &&
|
|
13
|
+
self.viewInitialized &&
|
|
14
|
+
self.numColumns > 0 &&
|
|
15
|
+
!(self.treeFilehandle && !self.data.tree)) {
|
|
16
|
+
self.zoomToRegion(region);
|
|
17
|
+
self.setRegion(undefined);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { expect, test, vi } from 'vitest';
|
|
2
|
+
import { applyRegion } from './applyRegion';
|
|
3
|
+
const REGION = { row: 'Human', start: 245, end: 249 };
|
|
4
|
+
function makeModel(over = {}) {
|
|
5
|
+
const model = {
|
|
6
|
+
region: REGION,
|
|
7
|
+
viewInitialized: true,
|
|
8
|
+
numColumns: 400,
|
|
9
|
+
treeFilehandle: undefined,
|
|
10
|
+
data: {},
|
|
11
|
+
zoomToRegion: vi.fn(),
|
|
12
|
+
setRegion: vi.fn((arg) => {
|
|
13
|
+
model.region = arg;
|
|
14
|
+
}),
|
|
15
|
+
...over,
|
|
16
|
+
};
|
|
17
|
+
return model;
|
|
18
|
+
}
|
|
19
|
+
test('zooms onto the region once, then forgets it', () => {
|
|
20
|
+
const model = makeModel();
|
|
21
|
+
applyRegion(model);
|
|
22
|
+
applyRegion(model);
|
|
23
|
+
expect(model.zoomToRegion).toHaveBeenCalledTimes(1);
|
|
24
|
+
expect(model.zoomToRegion).toHaveBeenCalledWith(REGION);
|
|
25
|
+
expect(model.region).toBeUndefined();
|
|
26
|
+
});
|
|
27
|
+
test('waits for the view to have a width', () => {
|
|
28
|
+
const model = makeModel({ viewInitialized: false });
|
|
29
|
+
applyRegion(model);
|
|
30
|
+
expect(model.zoomToRegion).not.toHaveBeenCalled();
|
|
31
|
+
expect(model.region).toEqual(REGION);
|
|
32
|
+
});
|
|
33
|
+
test('waits for the alignment', () => {
|
|
34
|
+
const model = makeModel({ numColumns: 0 });
|
|
35
|
+
applyRegion(model);
|
|
36
|
+
expect(model.zoomToRegion).not.toHaveBeenCalled();
|
|
37
|
+
});
|
|
38
|
+
test('waits for a tree file, whose collapsed clades move the columns', () => {
|
|
39
|
+
const model = makeModel({ treeFilehandle: { uri: 'x.nh' } });
|
|
40
|
+
applyRegion(model);
|
|
41
|
+
expect(model.zoomToRegion).not.toHaveBeenCalled();
|
|
42
|
+
model.data.tree = '(a,b);';
|
|
43
|
+
applyRegion(model);
|
|
44
|
+
expect(model.zoomToRegion).toHaveBeenCalledTimes(1);
|
|
45
|
+
});
|
|
@@ -4,9 +4,10 @@ import type { JBrowsePluginMsaViewModel } from '../model';
|
|
|
4
4
|
* What a view shows while it is still building its alignment, and what it shows
|
|
5
5
|
* when that fails.
|
|
6
6
|
*
|
|
7
|
-
* Every launch that resolves something
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Every launch that resolves something states its request on the model --
|
|
8
|
+
* `blastParams`, `orthologParams`, `init` -- and one not yet marked
|
|
9
|
+
* `launchCompleted` IS "no alignment yet", as is a completed one whose stored
|
|
10
|
+
* alignment has since expired. The error a failed launch records is only
|
|
10
11
|
* readable here. This used to key on `blastParams` alone, which left an ortholog
|
|
11
12
|
* launch rendering an empty MSAView for the minutes its alignment takes and, on
|
|
12
13
|
* failure, forever: the error was set and nothing drew it.
|
|
@@ -18,16 +18,21 @@ const useStyles = makeStyles()({
|
|
|
18
18
|
* What a view shows while it is still building its alignment, and what it shows
|
|
19
19
|
* when that fails.
|
|
20
20
|
*
|
|
21
|
-
* Every launch that resolves something
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* Every launch that resolves something states its request on the model --
|
|
22
|
+
* `blastParams`, `orthologParams`, `init` -- and one not yet marked
|
|
23
|
+
* `launchCompleted` IS "no alignment yet", as is a completed one whose stored
|
|
24
|
+
* alignment has since expired. The error a failed launch records is only
|
|
24
25
|
* readable here. This used to key on `blastParams` alone, which left an ortholog
|
|
25
26
|
* launch rendering an empty MSAView for the minutes its alignment takes and, on
|
|
26
27
|
* failure, forever: the error was set and nothing drew it.
|
|
27
28
|
*/
|
|
28
29
|
const LaunchProgress = observer(function LaunchProgress2({ model, }) {
|
|
29
|
-
const {
|
|
30
|
+
const { progress, rid, error } = model;
|
|
30
31
|
const { classes } = useStyles();
|
|
32
|
+
// the request stays on the model after it succeeds, so an expired
|
|
33
|
+
// alignment's panel is still named after the search that built it
|
|
34
|
+
const pending = !model.launchCompleted;
|
|
35
|
+
const { blastParams, orthologParams } = model;
|
|
31
36
|
const message = blastParams
|
|
32
37
|
? `Running EBI ${blastParams.searchProgram === 'phmmer' ? 'phmmer' : 'BLAST'}`
|
|
33
38
|
: orthologParams
|
|
@@ -36,9 +41,7 @@ const LaunchProgress = observer(function LaunchProgress2({ model, }) {
|
|
|
36
41
|
: 'Building ortholog alignment'
|
|
37
42
|
: 'Loading alignment';
|
|
38
43
|
return (React.createElement("div", { className: classes.margin }, error ? (React.createElement(React.Fragment, null,
|
|
39
|
-
React.createElement(Typography, { variant: "h5" },
|
|
40
|
-
message,
|
|
41
|
-
" failed"),
|
|
44
|
+
React.createElement(Typography, { variant: "h5" }, pending ? `${message} failed` : 'Alignment no longer available'),
|
|
42
45
|
rid ? React.createElement(JobLink, { jobId: rid }) : null,
|
|
43
46
|
React.createElement(ErrorMessage, { error: error }),
|
|
44
47
|
React.createElement("div", { className: classes.progressRow },
|
|
@@ -17,9 +17,13 @@ const MsaViewPanel = observer(function MsaViewPanel2({ model, }) {
|
|
|
17
17
|
// three gate the same panel -- see LaunchProgress. An indexed view keeps its
|
|
18
18
|
// init for the life of the view (it is how the block is refetched), so that
|
|
19
19
|
// one is only "launching" until the alignment arrives.
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const request = blastParams ?? orthologParams;
|
|
21
|
+
const pending = !!request && !model.launchCompleted;
|
|
22
|
+
// a request that finished and then lost its alignment -- browser storage
|
|
23
|
+
// 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;
|
|
26
|
+
const launching = pending || expired || (!!init && !model.dataInitialized);
|
|
23
27
|
return (React.createElement(ErrorBoundary, null,
|
|
24
28
|
React.createElement("div", null, launching ? (React.createElement(LaunchProgress, { model: model })) : loadingStoredData ? (React.createElement("div", { className: classes.loadingContainer },
|
|
25
29
|
React.createElement(LoadingEllipses, { message: "Loading MSA data", variant: "h6" }))) : (React.createElement(MSAView, { model: model })))));
|
|
@@ -97,6 +97,30 @@ test('a failed launch can be retried or dismissed', () => {
|
|
|
97
97
|
screen.getByRole('button', { name: 'Dismiss' }).click();
|
|
98
98
|
expect(cancelLaunch).toHaveBeenCalled();
|
|
99
99
|
});
|
|
100
|
+
// a stored alignment expires after 7 days, and the view used to say so and
|
|
101
|
+
// stop there -- the request that built it had been dropped on success, so the
|
|
102
|
+
// only way back was to find the gene again
|
|
103
|
+
test('an expired alignment offers to run its original search again', () => {
|
|
104
|
+
const retryLaunch = vi.fn();
|
|
105
|
+
panel({
|
|
106
|
+
progress: '',
|
|
107
|
+
dataInitialized: false,
|
|
108
|
+
error: new Error("This view's alignment is no longer in browser storage."),
|
|
109
|
+
blastParams: { blastDatabase: 'uniprotkb_swissprot' },
|
|
110
|
+
launchCompleted: true,
|
|
111
|
+
retryLaunch,
|
|
112
|
+
});
|
|
113
|
+
expect(screen.queryByText('the alignment')).toBeNull();
|
|
114
|
+
expect(screen.getByText('Alignment no longer available')).toBeTruthy();
|
|
115
|
+
screen.getByRole('button', { name: 'Retry' }).click();
|
|
116
|
+
expect(retryLaunch).toHaveBeenCalled();
|
|
117
|
+
});
|
|
118
|
+
// with nothing to retry from -- a pasted alignment, say -- the panel stays out
|
|
119
|
+
// of the way and react-msaview's own import form is what the user gets
|
|
120
|
+
test('an expired alignment with no request behind it draws the view', () => {
|
|
121
|
+
panel({ progress: '', dataInitialized: false, error: new Error('gone') });
|
|
122
|
+
expect(screen.getByText('the alignment')).toBeTruthy();
|
|
123
|
+
});
|
|
100
124
|
test('a running job links out to it', () => {
|
|
101
125
|
panel({
|
|
102
126
|
blastParams: { proteinSequence: 'MKV' },
|
|
@@ -84,8 +84,9 @@ export async function doLaunchBlast({ self, scope, }) {
|
|
|
84
84
|
await saveBlastResult({
|
|
85
85
|
proteinSequence: query,
|
|
86
86
|
blastDatabase: params.blastDatabase,
|
|
87
|
-
msaAlgorithm: params.msaAlgorithm,
|
|
87
|
+
msaAlgorithm: queryRow ? undefined : (params.msaAlgorithm ?? 'browser'),
|
|
88
88
|
searchProgram: params.searchProgram,
|
|
89
|
+
maxHits,
|
|
89
90
|
msa,
|
|
90
91
|
tree,
|
|
91
92
|
treeMetadata: treeMetadataJson,
|
|
@@ -67,6 +67,18 @@ test('bare hits go to the chosen aligner, with the query first', async () => {
|
|
|
67
67
|
expect(result.msa).toBe('aligned');
|
|
68
68
|
expect(result.tree).toBe('tree');
|
|
69
69
|
});
|
|
70
|
+
// the history names the aligner that ran, which for a spec naming none is the
|
|
71
|
+
// in-browser one -- not a key reading `db:undefined:...`
|
|
72
|
+
test('a search naming no aligner is saved as aligned in the browser', async () => {
|
|
73
|
+
blastp.mockResolvedValue({ rid: 'job', hits: [{ ...HIT, sequence: 'MKWV' }] });
|
|
74
|
+
mockLaunchMSA.mockResolvedValue({ msa: 'aligned', tree: '' });
|
|
75
|
+
await launch(makeModel({
|
|
76
|
+
blastDatabase: 'uniprotkb_swissprot',
|
|
77
|
+
maxHits: 20,
|
|
78
|
+
proteinSequence: 'MKWVTF',
|
|
79
|
+
}));
|
|
80
|
+
expect(saveBlastResult).toHaveBeenCalledWith(expect.objectContaining({ msaAlgorithm: 'browser', maxHits: 20 }));
|
|
81
|
+
});
|
|
70
82
|
test('an aligned result skips the aligner and leaves the tree to the browser', async () => {
|
|
71
83
|
phmmer.mockResolvedValue({
|
|
72
84
|
rid: 'job',
|
|
@@ -175,7 +175,7 @@ describe('the query row sequence', () => {
|
|
|
175
175
|
await launch({
|
|
176
176
|
self: makeModel(params({ proteinSequence: 'MAGG*AWGR&' })),
|
|
177
177
|
});
|
|
178
|
-
expect(queryRowSent()).toBe('
|
|
178
|
+
expect(queryRowSent()).toBe('MAGGXAWGR');
|
|
179
179
|
});
|
|
180
180
|
test('throws when neither a sequence nor a representative is available', async () => {
|
|
181
181
|
mockFetchProtein.mockResolvedValue(undefined);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { getSession } from '@jbrowse/core/util';
|
|
2
|
-
import { hasHoverPosition, hasQueryRow, transcriptPosToVisibleCol, } from './util';
|
|
2
|
+
import { hasHoverPosition, hasQueryRow, querySeqPosToVisibleCol, transcriptPosToVisibleCol, } from './util';
|
|
3
3
|
export function genomeToMSA({ model }) {
|
|
4
|
-
const { hovered } = getSession(model);
|
|
5
|
-
const {
|
|
4
|
+
const { assemblyManager, hovered } = getSession(model);
|
|
5
|
+
const { transcriptToMsaMap, connectedView, mafRegion } = model;
|
|
6
6
|
if (!connectedView?.initialized ||
|
|
7
7
|
!hasHoverPosition(hovered) ||
|
|
8
8
|
!hasQueryRow(model)) {
|
|
@@ -12,20 +12,38 @@ export function genomeToMSA({ model }) {
|
|
|
12
12
|
// hoverPosition.coord is a 1-based display coordinate (core's pxToBp adds the
|
|
13
13
|
// +1), while g2p and mafRegion are keyed by 0-based genome position
|
|
14
14
|
const genomePos = coord - 1;
|
|
15
|
+
// The two sides name the chromosome differently. A hover carries the
|
|
16
|
+
// assembly's canonical name -- `1` on jbrowse.org's hg38 -- while mafRegion
|
|
17
|
+
// and the transcript's g2p map carry the feature's, straight out of the
|
|
18
|
+
// annotation file, which is `chr1` in GENCODE. Compared raw they agree only
|
|
19
|
+
// where a config happens to pair files that agree, and everywhere else
|
|
20
|
+
// hovering a codon lit nothing at all, with no throw and no console line.
|
|
21
|
+
//
|
|
22
|
+
// `initialized` is the gate rather than a try/catch: getCanonicalRefName
|
|
23
|
+
// throws until the assembly's aliases load, and this runs from a hover
|
|
24
|
+
// autorun over an assembly nothing here waited for -- mafRegion names its own
|
|
25
|
+
// on a frozen spec, which need not be one the connected view loaded. Until
|
|
26
|
+
// the aliases arrive the raw name is the whole answer, and the hover lights
|
|
27
|
+
// nothing for those frames rather than throwing out of the autorun.
|
|
28
|
+
const assembly = assemblyManager.get(mafRegion?.assemblyName ?? connectedView.assemblyNames[0] ?? '');
|
|
29
|
+
const canonical = (name) => (assembly?.initialized ? assembly.getCanonicalRefName(name) : undefined) ??
|
|
30
|
+
name;
|
|
31
|
+
const hoveredRefName = canonical(refName);
|
|
15
32
|
if (mafRegion) {
|
|
16
|
-
if (
|
|
33
|
+
if (hoveredRefName !== canonical(mafRegion.refName) ||
|
|
17
34
|
!connectedView.assemblyNames.includes(mafRegion.assemblyName) ||
|
|
18
35
|
genomePos < mafRegion.start ||
|
|
19
36
|
genomePos >= mafRegion.end) {
|
|
20
37
|
return undefined;
|
|
21
38
|
}
|
|
22
|
-
return model
|
|
39
|
+
return querySeqPosToVisibleCol(model, genomePos - mafRegion.start);
|
|
23
40
|
}
|
|
24
41
|
// session.hovered is global -- set by whichever LinearGenomeView the cursor
|
|
25
42
|
// was last over, on any assembly -- so the refName gate is load bearing:
|
|
26
43
|
// without it the same numeric coordinate on an unrelated chromosome matches a
|
|
27
44
|
// g2p key and lights up a column for a different locus
|
|
28
|
-
if (
|
|
45
|
+
if (transcriptToMsaMap &&
|
|
46
|
+
hoveredRefName === canonical(transcriptToMsaMap.refName)) {
|
|
29
47
|
const proteinPos = transcriptToMsaMap.g2p[genomePos];
|
|
30
48
|
if (proteinPos !== undefined) {
|
|
31
49
|
return transcriptPosToVisibleCol(model, proteinPos);
|