jbrowse-plugin-msaview 5.15.0 → 5.16.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 +2 -0
- package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +18 -0
- package/dist/MsaViewPanel/afterCreateAutoruns.js +25 -3
- package/dist/MsaViewPanel/loadProteinDomains.d.ts +5 -1
- package/dist/MsaViewPanel/loadProteinDomains.js +16 -4
- package/dist/MsaViewPanel/loadProteinDomains.test.js +36 -5
- package/dist/MsaViewPanel/model.d.ts +158 -6
- package/dist/MsaViewPanel/model.js +162 -8
- package/dist/MsaViewPanel/resolveConnectedTranscript.d.ts +32 -3
- package/dist/MsaViewPanel/resolveConnectedTranscript.js +70 -22
- package/dist/MsaViewPanel/resolveConnectedTranscript.test.js +162 -5
- package/dist/MsaViewPanel/transcriptMap.d.ts +17 -1
- package/dist/MsaViewPanel/transcriptMap.js +13 -13
- package/dist/MsaViewPanel/transcriptMap.test.js +20 -34
- package/dist/MsaViewPanel/util.d.ts +3 -0
- package/dist/MsaViewPanel/util.js +7 -0
- package/dist/MsaViewPanel/viewReport.d.ts +113 -0
- package/dist/MsaViewPanel/viewReport.js +124 -0
- package/dist/MsaViewPanel/viewState.test.d.ts +1 -0
- package/dist/MsaViewPanel/viewState.test.js +303 -0
- package/dist/chunks/chunk-JUE34WZU.js +2 -0
- package/dist/chunks/chunk-JUE34WZU.js.map +7 -0
- package/dist/chunks/model-DAIL6BNQ.js +12 -0
- package/dist/chunks/{model-JKWKYCBL.js.map → model-DAIL6BNQ.js.map} +4 -4
- package/dist/jbrowse-plugin-msaview.esm.js +2 -2
- package/dist/jbrowse-plugin-msaview.esm.js.map +1 -1
- package/dist/utils/ncbiDomains.d.ts +14 -1
- package/dist/utils/ncbiDomains.js +23 -7
- package/dist/utils/ncbiDomains.test.js +54 -5
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/MsaViewPanel/afterCreateAutoruns.ts +39 -3
- package/src/MsaViewPanel/loadProteinDomains.test.ts +42 -7
- package/src/MsaViewPanel/loadProteinDomains.ts +16 -9
- package/src/MsaViewPanel/model.ts +189 -9
- package/src/MsaViewPanel/resolveConnectedTranscript.test.ts +198 -5
- package/src/MsaViewPanel/resolveConnectedTranscript.ts +89 -23
- package/src/MsaViewPanel/transcriptMap.test.ts +21 -38
- package/src/MsaViewPanel/transcriptMap.ts +15 -17
- package/src/MsaViewPanel/util.ts +9 -0
- package/src/MsaViewPanel/viewReport.ts +185 -0
- package/src/MsaViewPanel/viewState.test.ts +340 -0
- package/src/utils/ncbiDomains.test.ts +62 -5
- package/src/utils/ncbiDomains.ts +32 -8
- package/src/version.ts +1 -1
- package/dist/chunks/chunk-MXZ7GONI.js +0 -2
- package/dist/chunks/chunk-MXZ7GONI.js.map +0 -7
- package/dist/chunks/model-JKWKYCBL.js +0 -12
package/README.md
CHANGED
|
@@ -79,6 +79,8 @@ Linking and embedding:
|
|
|
79
79
|
- [Launch parameters](docs/launch-parameters.md): every argument and field.
|
|
80
80
|
- [Linked views](docs/linked-views.md): how a column reaches its codon and a
|
|
81
81
|
protein3d structure.
|
|
82
|
+
- [Reading the view](docs/reading-the-view.md): `view.report` and `notices`,
|
|
83
|
+
what a launch resolved to, as data a script or an agent reads.
|
|
82
84
|
|
|
83
85
|
Working on the plugin:
|
|
84
86
|
|
|
@@ -8,6 +8,18 @@ export declare function loadStoredData(self: JBrowsePluginMsaViewModel): void;
|
|
|
8
8
|
* refusing IndexedDB fails once rather than in a loop.
|
|
9
9
|
*/
|
|
10
10
|
export declare function storeDataToIndexedDB(self: JBrowsePluginMsaViewModel): void;
|
|
11
|
+
interface TranscriptWait {
|
|
12
|
+
connectedTranscript?: string;
|
|
13
|
+
connectedFeature?: unknown;
|
|
14
|
+
blastParams?: unknown;
|
|
15
|
+
orthologParams?: unknown;
|
|
16
|
+
launchCompleted: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A launch that has not run yet and is held back by the transcript lookup, so
|
|
20
|
+
* a lookup that fails is a launch that cannot proceed.
|
|
21
|
+
*/
|
|
22
|
+
export declare function launchAwaitsTranscript(self: TranscriptWait): boolean;
|
|
11
23
|
export declare function launchOrthologsIfNeeded(self: JBrowsePluginMsaViewModel): void;
|
|
12
24
|
export declare function launchBlastIfNeeded(self: JBrowsePluginMsaViewModel): void;
|
|
13
25
|
/**
|
|
@@ -57,4 +69,10 @@ export declare function syncGenomeHoverToMsaColumn(self: JBrowsePluginMsaViewMod
|
|
|
57
69
|
*/
|
|
58
70
|
export declare function observeProteinHighlights(self: JBrowsePluginMsaViewModel): () => void;
|
|
59
71
|
export declare function runCleanup(): void;
|
|
72
|
+
/**
|
|
73
|
+
* Waits for the genome view to show an assembly, since the track is added to
|
|
74
|
+
* that assembly: before navigation `showDomainsOnGenome` has none to name, and
|
|
75
|
+
* clearing the request then dropped it unfulfilled.
|
|
76
|
+
*/
|
|
60
77
|
export declare function showDomainsOnGenomeIfAsked(self: JBrowsePluginMsaViewModel): void;
|
|
78
|
+
export {};
|
|
@@ -11,7 +11,7 @@ import { cleanupOldData, generateDataStoreId, retrieveMsaData, storeMsaData, } f
|
|
|
11
11
|
import { runLaunch } from './runLaunch';
|
|
12
12
|
import { getProteinViews } from './structureConnection';
|
|
13
13
|
import { proteinPositionsInRange } from './transcriptMap';
|
|
14
|
-
import { getUniprotIdFromAlphaFoldUrl, hasQueryRow, transcriptPosToVisibleCol, } from './util';
|
|
14
|
+
import { errorMessage, getUniprotIdFromAlphaFoldUrl, hasQueryRow, transcriptPosToVisibleCol, } from './util';
|
|
15
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.";
|
|
16
16
|
const RELAUNCHABLE = ' Retry runs the original search again and rebuilds it.';
|
|
17
17
|
const START_OVER = ' Relaunch it from the gene to rebuild it.';
|
|
@@ -140,6 +140,15 @@ export function storeDataToIndexedDB(self) {
|
|
|
140
140
|
function awaitingTranscript(self) {
|
|
141
141
|
return !!self.connectedTranscript && !self.connectedFeature;
|
|
142
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* A launch that has not run yet and is held back by the transcript lookup, so
|
|
145
|
+
* a lookup that fails is a launch that cannot proceed.
|
|
146
|
+
*/
|
|
147
|
+
export function launchAwaitsTranscript(self) {
|
|
148
|
+
return (!!(self.blastParams ?? self.orthologParams) &&
|
|
149
|
+
!self.launchCompleted &&
|
|
150
|
+
awaitingTranscript(self));
|
|
151
|
+
}
|
|
143
152
|
/**
|
|
144
153
|
* The params are the request and `launchCompleted` marks it done. They stay in
|
|
145
154
|
* place on failure, so the error is attributable, and on success, because a
|
|
@@ -180,22 +189,30 @@ export function autoLoadProteinDomains(self) {
|
|
|
180
189
|
};
|
|
181
190
|
addDisposer(self, abort);
|
|
182
191
|
self.setDomainsRequested(true);
|
|
192
|
+
self.setLoadingDomains(true);
|
|
183
193
|
self.setStatus({
|
|
184
194
|
msg: 'Fetching protein domains from NCBI',
|
|
185
195
|
onCancel: abort,
|
|
186
196
|
});
|
|
187
197
|
void (async () => {
|
|
188
198
|
try {
|
|
189
|
-
await loadProteinDomains(self, controller.signal);
|
|
199
|
+
const notice = await loadProteinDomains(self, controller.signal);
|
|
200
|
+
if (notice && isAlive(self)) {
|
|
201
|
+
self.addWarning(notice);
|
|
202
|
+
}
|
|
190
203
|
}
|
|
191
204
|
catch (e) {
|
|
192
205
|
if (!isAbortError(e)) {
|
|
193
206
|
console.error('[msaview-domains] auto-load failed:', e);
|
|
207
|
+
if (isAlive(self)) {
|
|
208
|
+
self.addWarning(`Protein domains did not load from NCBI: ${errorMessage(e)}`);
|
|
209
|
+
}
|
|
194
210
|
}
|
|
195
211
|
}
|
|
196
212
|
finally {
|
|
197
213
|
if (isAlive(self)) {
|
|
198
214
|
self.setStatus(undefined);
|
|
215
|
+
self.setLoadingDomains(false);
|
|
199
216
|
}
|
|
200
217
|
}
|
|
201
218
|
})();
|
|
@@ -390,9 +407,14 @@ export function runCleanup() {
|
|
|
390
407
|
console.error('Failed to cleanup old MSA data:', e);
|
|
391
408
|
});
|
|
392
409
|
}
|
|
410
|
+
/**
|
|
411
|
+
* Waits for the genome view to show an assembly, since the track is added to
|
|
412
|
+
* that assembly: before navigation `showDomainsOnGenome` has none to name, and
|
|
413
|
+
* clearing the request then dropped it unfulfilled.
|
|
414
|
+
*/
|
|
393
415
|
export function showDomainsOnGenomeIfAsked(self) {
|
|
394
416
|
if (self.domainsOnGenome &&
|
|
395
|
-
self.connectedView &&
|
|
417
|
+
self.connectedView?.assemblyNames[0] &&
|
|
396
418
|
self.connectedDomains.features.length > 0) {
|
|
397
419
|
self.showDomainsOnGenome();
|
|
398
420
|
self.setDomainsOnGenome(false);
|
|
@@ -8,6 +8,10 @@ interface DomainModel {
|
|
|
8
8
|
* annotations. Each launch stores a row's accession in its row data, so those
|
|
9
9
|
* are looked up via efetch and keyed by MSA row name, which is what
|
|
10
10
|
* react-msaview matches annotations against.
|
|
11
|
+
*
|
|
12
|
+
* Throws when NCBI answered for none of the rows. When it answered for some,
|
|
13
|
+
* their domains are drawn and the returned notice names what is missing. A
|
|
14
|
+
* set of proteins with no CDD annotation leaves the overlay as it was.
|
|
11
15
|
*/
|
|
12
|
-
export declare function loadProteinDomains(self: DomainModel, signal?: AbortSignal): Promise<
|
|
16
|
+
export declare function loadProteinDomains(self: DomainModel, signal?: AbortSignal): Promise<string | undefined>;
|
|
13
17
|
export {};
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
import { fetchProteinDomains } from '../utils/ncbiDomains';
|
|
2
|
+
import { errorMessage } from './util';
|
|
2
3
|
/**
|
|
3
4
|
* Overlay protein domains on the alignment using NCBI's pre-computed CDD
|
|
4
5
|
* annotations. Each launch stores a row's accession in its row data, so those
|
|
5
6
|
* are looked up via efetch and keyed by MSA row name, which is what
|
|
6
7
|
* react-msaview matches annotations against.
|
|
8
|
+
*
|
|
9
|
+
* Throws when NCBI answered for none of the rows. When it answered for some,
|
|
10
|
+
* their domains are drawn and the returned notice names what is missing. A
|
|
11
|
+
* set of proteins with no CDD annotation leaves the overlay as it was.
|
|
7
12
|
*/
|
|
8
13
|
export async function loadProteinDomains(self, signal) {
|
|
9
14
|
const rowAccessions = Object.entries(self.rowData).flatMap(([rowName, row]) => row?.Accession ? [{ rowName, accession: row.Accession }] : []);
|
|
10
15
|
if (rowAccessions.length === 0) {
|
|
11
16
|
throw new Error('No NCBI accessions found in alignment rows');
|
|
12
17
|
}
|
|
13
|
-
const
|
|
18
|
+
const asked = new Set(rowAccessions.map(r => r.accession));
|
|
19
|
+
const { byAccession, failure } = await fetchProteinDomains([...asked], signal);
|
|
20
|
+
const answered = [...asked].filter(a => byAccession.has(a)).length;
|
|
21
|
+
if (failure !== undefined && answered === 0) {
|
|
22
|
+
throw new Error(errorMessage(failure), { cause: failure });
|
|
23
|
+
}
|
|
14
24
|
const annotations = rowAccessions.flatMap(({ rowName, accession }) => (byAccession.get(accession) ?? []).flatMap(({ signature, locations, featureType }) => signature.entry
|
|
15
25
|
? locations.map(({ start, end }) => ({
|
|
16
26
|
id: rowName,
|
|
@@ -22,8 +32,10 @@ export async function loadProteinDomains(self, signal) {
|
|
|
22
32
|
end,
|
|
23
33
|
}))
|
|
24
34
|
: []));
|
|
25
|
-
if (annotations.length
|
|
26
|
-
|
|
35
|
+
if (annotations.length > 0) {
|
|
36
|
+
self.setAnnotations(annotations);
|
|
27
37
|
}
|
|
28
|
-
|
|
38
|
+
return failure === undefined
|
|
39
|
+
? undefined
|
|
40
|
+
: `Protein domains loaded for ${answered} of ${asked.size} proteins; NCBI failed on the rest: ${errorMessage(failure)}`;
|
|
29
41
|
}
|
|
@@ -16,12 +16,15 @@ function model(treeMetadata) {
|
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
test('an accession row becomes one annotation per location, keyed by row name', async () => {
|
|
19
|
-
vi.mocked(fetchProteinDomains).mockResolvedValue(
|
|
19
|
+
vi.mocked(fetchProteinDomains).mockResolvedValue({
|
|
20
|
+
byAccession: new Map([['NP_1', [MATCH]]]),
|
|
21
|
+
failure: undefined,
|
|
22
|
+
});
|
|
20
23
|
const self = model({
|
|
21
24
|
QUERY: { Accession: 'NP_1' },
|
|
22
25
|
other: { 'Scientific name': 'Mus musculus' },
|
|
23
26
|
});
|
|
24
|
-
await loadProteinDomains(self);
|
|
27
|
+
expect(await loadProteinDomains(self)).toBeUndefined();
|
|
25
28
|
expect(self.setAnnotations).toHaveBeenCalledWith([
|
|
26
29
|
{
|
|
27
30
|
id: 'QUERY',
|
|
@@ -34,9 +37,37 @@ test('an accession row becomes one annotation per location, keyed by row name',
|
|
|
34
37
|
},
|
|
35
38
|
]);
|
|
36
39
|
});
|
|
37
|
-
test('
|
|
38
|
-
vi.mocked(fetchProteinDomains).mockResolvedValue(
|
|
40
|
+
test('proteins with no CDD annotation leave the overlay alone', async () => {
|
|
41
|
+
vi.mocked(fetchProteinDomains).mockResolvedValue({
|
|
42
|
+
byAccession: new Map([['NP_1', []]]),
|
|
43
|
+
failure: undefined,
|
|
44
|
+
});
|
|
45
|
+
const self = model({ QUERY: { Accession: 'NP_1' } });
|
|
46
|
+
expect(await loadProteinDomains(self)).toBeUndefined();
|
|
47
|
+
expect(self.setAnnotations).not.toHaveBeenCalled();
|
|
48
|
+
});
|
|
49
|
+
test('a fetch that failed outright throws its failure', async () => {
|
|
50
|
+
const failure = new Error('HTTP 429 fetching efetch');
|
|
51
|
+
vi.mocked(fetchProteinDomains).mockResolvedValue({
|
|
52
|
+
byAccession: new Map(),
|
|
53
|
+
failure,
|
|
54
|
+
});
|
|
39
55
|
const self = model({ QUERY: { Accession: 'NP_1' } });
|
|
40
|
-
await expect(loadProteinDomains(self)).rejects.
|
|
56
|
+
await expect(loadProteinDomains(self)).rejects.toMatchObject({
|
|
57
|
+
message: 'HTTP 429 fetching efetch',
|
|
58
|
+
cause: failure,
|
|
59
|
+
});
|
|
41
60
|
expect(self.setAnnotations).not.toHaveBeenCalled();
|
|
42
61
|
});
|
|
62
|
+
test('a fetch that half failed draws what loaded and names what did not', async () => {
|
|
63
|
+
vi.mocked(fetchProteinDomains).mockResolvedValue({
|
|
64
|
+
byAccession: new Map([['NP_1', [MATCH]]]),
|
|
65
|
+
failure: new Error('HTTP 429 fetching efetch'),
|
|
66
|
+
});
|
|
67
|
+
const self = model({
|
|
68
|
+
QUERY: { Accession: 'NP_1' },
|
|
69
|
+
mouse: { Accession: 'NP_2' },
|
|
70
|
+
});
|
|
71
|
+
expect(await loadProteinDomains(self)).toBe('Protein domains loaded for 1 of 2 proteins; NCBI failed on the rest: HTTP 429 fetching efetch');
|
|
72
|
+
expect(self.setAnnotations).toHaveBeenCalledTimes(1);
|
|
73
|
+
});
|
|
@@ -6,10 +6,10 @@ import type { MsaDataPayload } from './msaDataStore';
|
|
|
6
6
|
import type { MafRegion, MsaViewInitState } from './types';
|
|
7
7
|
import type { TranscriptRef } from './util';
|
|
8
8
|
import type { MenuItem } from '@jbrowse/core/ui';
|
|
9
|
+
import type { AbstractViewModel } from '@jbrowse/core/util';
|
|
9
10
|
import type { Instance } from '@jbrowse/mobx-state-tree';
|
|
10
11
|
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view';
|
|
11
12
|
type LGV = LinearGenomeViewModel;
|
|
12
|
-
type MaybeLGV = LGV | undefined;
|
|
13
13
|
export interface IRegion {
|
|
14
14
|
refName: string;
|
|
15
15
|
start: number;
|
|
@@ -524,7 +524,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
524
524
|
readonly effectiveBodyMounted: boolean;
|
|
525
525
|
menuItems(): MenuItem[];
|
|
526
526
|
readonly ownTracks: import("@jbrowse/core/util").AbstractTrackModel[];
|
|
527
|
-
readonly ownViews:
|
|
527
|
+
readonly ownViews: AbstractViewModel[];
|
|
528
528
|
} & {
|
|
529
529
|
setDisplayName(name: string): void;
|
|
530
530
|
setWidth(newWidth: number): void;
|
|
@@ -977,15 +977,48 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
977
977
|
ownsDataStoreRow: boolean;
|
|
978
978
|
launchController: AbortController | undefined;
|
|
979
979
|
domainsRequested: boolean;
|
|
980
|
+
loadingDomains: boolean;
|
|
981
|
+
connectionError: unknown;
|
|
980
982
|
} & {
|
|
981
983
|
/**
|
|
982
984
|
* #getter
|
|
985
|
+
* the linked transcript's genome mapping, or why it has none
|
|
983
986
|
*/
|
|
984
|
-
readonly
|
|
987
|
+
readonly transcriptMapping: {
|
|
988
|
+
reason: string;
|
|
989
|
+
} | {
|
|
990
|
+
map: {
|
|
991
|
+
codingPositions: number[];
|
|
992
|
+
g2p: Record<number, number>;
|
|
993
|
+
p2g: Record<number, number>;
|
|
994
|
+
p2gCodon: Record<number, number[]>;
|
|
995
|
+
refName: string;
|
|
996
|
+
strand: number;
|
|
997
|
+
};
|
|
998
|
+
} | undefined;
|
|
999
|
+
/**
|
|
1000
|
+
* #getter
|
|
1001
|
+
* whatever view `connectedViewId` names, genome view or not
|
|
1002
|
+
*/
|
|
1003
|
+
readonly viewNamedByConnectedViewId: AbstractViewModel | undefined;
|
|
1004
|
+
} & {
|
|
985
1005
|
/**
|
|
986
1006
|
* #getter
|
|
1007
|
+
* the view `connectedViewId` names, when it is a genome view
|
|
987
1008
|
*/
|
|
988
|
-
readonly connectedView:
|
|
1009
|
+
readonly connectedView: LGV | undefined;
|
|
1010
|
+
} & {
|
|
1011
|
+
/**
|
|
1012
|
+
* #getter
|
|
1013
|
+
*/
|
|
1014
|
+
readonly transcriptToMsaMap: {
|
|
1015
|
+
codingPositions: number[];
|
|
1016
|
+
g2p: Record<number, number>;
|
|
1017
|
+
p2g: Record<number, number>;
|
|
1018
|
+
p2gCodon: Record<number, number[]>;
|
|
1019
|
+
refName: string;
|
|
1020
|
+
strand: number;
|
|
1021
|
+
} | undefined;
|
|
989
1022
|
} & {
|
|
990
1023
|
/**
|
|
991
1024
|
* #getter
|
|
@@ -1035,11 +1068,108 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
1035
1068
|
readonly unsavedDocuments: MsaDataPayload;
|
|
1036
1069
|
/**
|
|
1037
1070
|
* #getter
|
|
1038
|
-
*
|
|
1039
|
-
*
|
|
1071
|
+
* what is wrong with the link to the genome, derived from the view's
|
|
1072
|
+
* state so each clears when its cause does. A connection failure that
|
|
1073
|
+
* is already the view's `error` is not repeated here.
|
|
1074
|
+
*/
|
|
1075
|
+
readonly linkNotices: string[];
|
|
1076
|
+
/**
|
|
1077
|
+
* #getter
|
|
1078
|
+
* true while a launch, stored data, a file, the transcript lookup or the
|
|
1079
|
+
* protein domains are still loading and no error has ended it. Every
|
|
1080
|
+
* term settles on failure. jb.waitReady, the app's ready marker and
|
|
1040
1081
|
* screenshot tools read it for a view.
|
|
1041
1082
|
*/
|
|
1042
1083
|
readonly showLoading: boolean;
|
|
1084
|
+
} & {
|
|
1085
|
+
/**
|
|
1086
|
+
* #getter
|
|
1087
|
+
* overrides base
|
|
1088
|
+
*
|
|
1089
|
+
* react-msaview's load and data warnings, plus the plugin's link
|
|
1090
|
+
* notices, which the header's warning chip lists and dismisses alike
|
|
1091
|
+
*/
|
|
1092
|
+
readonly warnings: string[];
|
|
1093
|
+
} & {
|
|
1094
|
+
/**
|
|
1095
|
+
* #getter
|
|
1096
|
+
* what failed and left the view working: the messages the header's
|
|
1097
|
+
* warning chip lists. A notice does not end `showLoading`.
|
|
1098
|
+
*/
|
|
1099
|
+
readonly notices: string[];
|
|
1100
|
+
} & {
|
|
1101
|
+
/**
|
|
1102
|
+
* #getter
|
|
1103
|
+
* what the view resolved to, as plain JSON. See docs/reading-the-view.md
|
|
1104
|
+
*/
|
|
1105
|
+
readonly report: {
|
|
1106
|
+
loading: boolean;
|
|
1107
|
+
error: string | undefined;
|
|
1108
|
+
notices: string[];
|
|
1109
|
+
source: {
|
|
1110
|
+
kind: string;
|
|
1111
|
+
database: "uniprotkb_swissprot" | "uniprotkb" | "pan_proteomes" | "uniprotkb_trembl" | "swissprot" | "uniprotrefprot" | "rp75" | "rp55" | "rp35" | "rp15";
|
|
1112
|
+
msaAlgorithm: "clustalo" | "muscle" | "kalign" | "mafft" | "browser" | undefined;
|
|
1113
|
+
maxHits: number | undefined;
|
|
1114
|
+
accession: string | undefined;
|
|
1115
|
+
queryLength: number | undefined;
|
|
1116
|
+
jobId: string | undefined;
|
|
1117
|
+
completed: boolean;
|
|
1118
|
+
} | {
|
|
1119
|
+
kind: string;
|
|
1120
|
+
orthologSource: "ncbi" | "panther" | "uniref";
|
|
1121
|
+
taxId: number;
|
|
1122
|
+
identity: UnirefIdentity | undefined;
|
|
1123
|
+
maxSpecies: number | undefined;
|
|
1124
|
+
taxa: number[] | undefined;
|
|
1125
|
+
geneCandidates: string[];
|
|
1126
|
+
msaAlgorithm: "clustalo" | "muscle" | "kalign" | "mafft" | "browser";
|
|
1127
|
+
queryLength: number | undefined;
|
|
1128
|
+
jobId: string | undefined;
|
|
1129
|
+
completed: boolean;
|
|
1130
|
+
} | {
|
|
1131
|
+
kind: string;
|
|
1132
|
+
url: string;
|
|
1133
|
+
uri?: undefined;
|
|
1134
|
+
name?: undefined;
|
|
1135
|
+
location?: undefined;
|
|
1136
|
+
} | {
|
|
1137
|
+
kind: string;
|
|
1138
|
+
uri: string;
|
|
1139
|
+
name: string | undefined;
|
|
1140
|
+
url?: undefined;
|
|
1141
|
+
location?: undefined;
|
|
1142
|
+
} | {
|
|
1143
|
+
kind: string;
|
|
1144
|
+
location: string | undefined;
|
|
1145
|
+
url?: undefined;
|
|
1146
|
+
uri?: undefined;
|
|
1147
|
+
name?: undefined;
|
|
1148
|
+
} | {
|
|
1149
|
+
kind: string;
|
|
1150
|
+
url?: undefined;
|
|
1151
|
+
uri?: undefined;
|
|
1152
|
+
name?: undefined;
|
|
1153
|
+
location?: undefined;
|
|
1154
|
+
};
|
|
1155
|
+
rows: number;
|
|
1156
|
+
columns: number;
|
|
1157
|
+
domainAnnotations: number;
|
|
1158
|
+
uniprotId: string | undefined;
|
|
1159
|
+
queryRow: {
|
|
1160
|
+
name: string;
|
|
1161
|
+
found: boolean;
|
|
1162
|
+
offset: number;
|
|
1163
|
+
};
|
|
1164
|
+
connection: {
|
|
1165
|
+
viewId: string | undefined;
|
|
1166
|
+
viewOpen: boolean;
|
|
1167
|
+
transcript: string | undefined;
|
|
1168
|
+
refName: string | undefined;
|
|
1169
|
+
mapped: boolean;
|
|
1170
|
+
unmappedBecause: string | undefined;
|
|
1171
|
+
} | undefined;
|
|
1172
|
+
};
|
|
1043
1173
|
} & {
|
|
1044
1174
|
/**
|
|
1045
1175
|
* #action
|
|
@@ -1130,6 +1260,21 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
1130
1260
|
* #action
|
|
1131
1261
|
*/
|
|
1132
1262
|
setDomainsRequested(arg: boolean): void;
|
|
1263
|
+
/**
|
|
1264
|
+
* #action
|
|
1265
|
+
*/
|
|
1266
|
+
setLoadingDomains(arg: boolean): void;
|
|
1267
|
+
/**
|
|
1268
|
+
* #action
|
|
1269
|
+
* `connectedTranscript` could not be resolved. A launch waiting on it
|
|
1270
|
+
* cannot run, so for one that is the view's error; an alignment from
|
|
1271
|
+
* anywhere else is still worth showing, so then it is a notice.
|
|
1272
|
+
*/
|
|
1273
|
+
failConnection(e: unknown): void;
|
|
1274
|
+
/**
|
|
1275
|
+
* #action
|
|
1276
|
+
*/
|
|
1277
|
+
clearConnectionFailure(): void;
|
|
1133
1278
|
/**
|
|
1134
1279
|
* #action
|
|
1135
1280
|
*/
|
|
@@ -1163,6 +1308,13 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
1163
1308
|
* once per view, never fired again.
|
|
1164
1309
|
*/
|
|
1165
1310
|
reset(): void;
|
|
1311
|
+
} & {
|
|
1312
|
+
/**
|
|
1313
|
+
* #action
|
|
1314
|
+
* overrides base, so dismissing the warning chip hides the link
|
|
1315
|
+
* notices too until their cause changes
|
|
1316
|
+
*/
|
|
1317
|
+
clearWarnings(): void;
|
|
1166
1318
|
} & {
|
|
1167
1319
|
/**
|
|
1168
1320
|
* #method
|
|
@@ -3,14 +3,19 @@ import { getSession, isSessionWithAddSessionTrack, isSessionWithDeleteTrackConf,
|
|
|
3
3
|
import { addDisposer, getSnapshot, types } from '@jbrowse/mobx-state-tree';
|
|
4
4
|
import { autorun } from 'mobx';
|
|
5
5
|
import { MSAModelF } from 'react-msaview';
|
|
6
|
-
import { autoLoadProteinDomains, launchBlastIfNeeded, launchOrthologsIfNeeded, loadStoredData, observeProteinHighlights, processInit, runCleanup, storeDataToIndexedDB, showDomainsOnGenomeIfAsked, syncGenomeHoverToMsaColumn, } from './afterCreateAutoruns';
|
|
6
|
+
import { autoLoadProteinDomains, launchAwaitsTranscript, launchBlastIfNeeded, launchOrthologsIfNeeded, loadStoredData, observeProteinHighlights, processInit, runCleanup, storeDataToIndexedDB, showDomainsOnGenomeIfAsked, syncGenomeHoverToMsaColumn, } from './afterCreateAutoruns';
|
|
7
7
|
import { connectedDomains } from './connectedDomains';
|
|
8
8
|
import { domainTrackConf } from './domainTrack';
|
|
9
9
|
import { isLaunching } from './isLaunching';
|
|
10
10
|
import { msaCoordToGenomeCoord, msaCoordToGenomeRegions, } from './msaCoordToGenomeCoord';
|
|
11
11
|
import { resolveConnectedTranscriptIfNeeded } from './resolveConnectedTranscript';
|
|
12
12
|
import { syncSessionHighlights } from './sessionHighlights';
|
|
13
|
-
import {
|
|
13
|
+
import { transcriptMapping } from './transcriptMap';
|
|
14
|
+
import { errorMessage, hasQueryRow } from './util';
|
|
15
|
+
import { viewReport } from './viewReport';
|
|
16
|
+
function isLinearGenomeView(view) {
|
|
17
|
+
return view.type === 'LinearGenomeView';
|
|
18
|
+
}
|
|
14
19
|
/**
|
|
15
20
|
* Where the homolog set comes from. NCBI's ortholog sets cover vertebrates and
|
|
16
21
|
* insects; PANTHER's span its 144 reference proteomes, human to yeast to
|
|
@@ -163,23 +168,57 @@ export default function stateModelFactory() {
|
|
|
163
168
|
* when NCBI returns no domains (leaving the annotation list empty)
|
|
164
169
|
*/
|
|
165
170
|
domainsRequested: false,
|
|
171
|
+
/**
|
|
172
|
+
* #volatile
|
|
173
|
+
* the protein domain auto-load is in flight
|
|
174
|
+
*/
|
|
175
|
+
loadingDomains: false,
|
|
176
|
+
/**
|
|
177
|
+
* #volatile
|
|
178
|
+
* why `connectedTranscript` could not be resolved: the lookup missed,
|
|
179
|
+
* or the genome view it needs is absent or has no feature tracks. It
|
|
180
|
+
* is also the view's `error` when a launch was waiting on the
|
|
181
|
+
* transcript, and a notice otherwise
|
|
182
|
+
*/
|
|
183
|
+
connectionError: undefined,
|
|
166
184
|
}))
|
|
167
185
|
.views(self => ({
|
|
168
186
|
/**
|
|
169
187
|
* #getter
|
|
188
|
+
* the linked transcript's genome mapping, or why it has none
|
|
170
189
|
*/
|
|
171
|
-
get
|
|
190
|
+
get transcriptMapping() {
|
|
172
191
|
return self.connectedFeature
|
|
173
|
-
?
|
|
192
|
+
? transcriptMapping(self.connectedFeature)
|
|
174
193
|
: undefined;
|
|
175
194
|
},
|
|
176
195
|
/**
|
|
177
196
|
* #getter
|
|
197
|
+
* whatever view `connectedViewId` names, genome view or not
|
|
178
198
|
*/
|
|
179
|
-
get
|
|
199
|
+
get viewNamedByConnectedViewId() {
|
|
180
200
|
const { views } = getSession(self);
|
|
181
201
|
return views.find(f => f.id === self.connectedViewId);
|
|
182
202
|
},
|
|
203
|
+
}))
|
|
204
|
+
.views(self => ({
|
|
205
|
+
/**
|
|
206
|
+
* #getter
|
|
207
|
+
* the view `connectedViewId` names, when it is a genome view
|
|
208
|
+
*/
|
|
209
|
+
get connectedView() {
|
|
210
|
+
const view = self.viewNamedByConnectedViewId;
|
|
211
|
+
return view && isLinearGenomeView(view) ? view : undefined;
|
|
212
|
+
},
|
|
213
|
+
}))
|
|
214
|
+
.views(self => ({
|
|
215
|
+
/**
|
|
216
|
+
* #getter
|
|
217
|
+
*/
|
|
218
|
+
get transcriptToMsaMap() {
|
|
219
|
+
const mapping = self.transcriptMapping;
|
|
220
|
+
return mapping && 'map' in mapping ? mapping.map : undefined;
|
|
221
|
+
},
|
|
183
222
|
}))
|
|
184
223
|
.views(self => ({
|
|
185
224
|
/**
|
|
@@ -267,15 +306,84 @@ export default function stateModelFactory() {
|
|
|
267
306
|
},
|
|
268
307
|
/**
|
|
269
308
|
* #getter
|
|
270
|
-
*
|
|
271
|
-
*
|
|
309
|
+
* what is wrong with the link to the genome, derived from the view's
|
|
310
|
+
* state so each clears when its cause does. A connection failure that
|
|
311
|
+
* is already the view's `error` is not repeated here.
|
|
312
|
+
*/
|
|
313
|
+
get linkNotices() {
|
|
314
|
+
const { connectionError, error, transcriptMapping, connectedFeature, mafRegion, querySeqName, dataInitialized, } = self;
|
|
315
|
+
return [
|
|
316
|
+
...(connectionError !== undefined && connectionError !== error
|
|
317
|
+
? [errorMessage(connectionError)]
|
|
318
|
+
: []),
|
|
319
|
+
...(transcriptMapping && 'reason' in transcriptMapping
|
|
320
|
+
? [
|
|
321
|
+
`The linked transcript is not mapped to the genome: ${transcriptMapping.reason}`,
|
|
322
|
+
]
|
|
323
|
+
: []),
|
|
324
|
+
...((connectedFeature || mafRegion) &&
|
|
325
|
+
dataInitialized &&
|
|
326
|
+
!hasQueryRow(self)
|
|
327
|
+
? [
|
|
328
|
+
`No row is named "${querySeqName}", the query row, so the alignment is not linked to the genome`,
|
|
329
|
+
]
|
|
330
|
+
: []),
|
|
331
|
+
];
|
|
332
|
+
},
|
|
333
|
+
/**
|
|
334
|
+
* #getter
|
|
335
|
+
* true while a launch, stored data, a file, the transcript lookup or the
|
|
336
|
+
* protein domains are still loading and no error has ended it. Every
|
|
337
|
+
* term settles on failure. jb.waitReady, the app's ready marker and
|
|
272
338
|
* screenshot tools read it for a view.
|
|
273
339
|
*/
|
|
274
340
|
get showLoading() {
|
|
341
|
+
const { connectedView, connectedTranscript, connectedFeature, connectionError, } = self;
|
|
275
342
|
return (!self.error &&
|
|
276
343
|
(isLaunching(self) ||
|
|
277
344
|
(self.loadingStoredData && !self.dataInitialized) ||
|
|
278
|
-
self.isLoading
|
|
345
|
+
self.isLoading ||
|
|
346
|
+
self.loadingDomains ||
|
|
347
|
+
(!!connectedTranscript &&
|
|
348
|
+
!connectedFeature &&
|
|
349
|
+
connectionError === undefined) ||
|
|
350
|
+
(self.domainsOnGenome &&
|
|
351
|
+
self.connectedDomains.features.length > 0 &&
|
|
352
|
+
!!connectedView?.showLoading)));
|
|
353
|
+
},
|
|
354
|
+
}))
|
|
355
|
+
.views(self => ({
|
|
356
|
+
/**
|
|
357
|
+
* #getter
|
|
358
|
+
* overrides base
|
|
359
|
+
*
|
|
360
|
+
* react-msaview's load and data warnings, plus the plugin's link
|
|
361
|
+
* notices, which the header's warning chip lists and dismisses alike
|
|
362
|
+
*/
|
|
363
|
+
get warnings() {
|
|
364
|
+
return [
|
|
365
|
+
...self.loadWarnings,
|
|
366
|
+
...[...self.dataWarnings, ...self.linkNotices].filter(w => !self.dismissedWarnings.includes(w)),
|
|
367
|
+
];
|
|
368
|
+
},
|
|
369
|
+
}))
|
|
370
|
+
.views(self => ({
|
|
371
|
+
/**
|
|
372
|
+
* #getter
|
|
373
|
+
* what failed and left the view working: the messages the header's
|
|
374
|
+
* warning chip lists. A notice does not end `showLoading`.
|
|
375
|
+
*/
|
|
376
|
+
get notices() {
|
|
377
|
+
return self.warnings;
|
|
378
|
+
},
|
|
379
|
+
}))
|
|
380
|
+
.views(self => ({
|
|
381
|
+
/**
|
|
382
|
+
* #getter
|
|
383
|
+
* what the view resolved to, as plain JSON. See docs/reading-the-view.md
|
|
384
|
+
*/
|
|
385
|
+
get report() {
|
|
386
|
+
return viewReport(self);
|
|
279
387
|
},
|
|
280
388
|
}))
|
|
281
389
|
.actions(self => ({
|
|
@@ -428,6 +536,34 @@ export default function stateModelFactory() {
|
|
|
428
536
|
setDomainsRequested(arg) {
|
|
429
537
|
self.domainsRequested = arg;
|
|
430
538
|
},
|
|
539
|
+
/**
|
|
540
|
+
* #action
|
|
541
|
+
*/
|
|
542
|
+
setLoadingDomains(arg) {
|
|
543
|
+
self.loadingDomains = arg;
|
|
544
|
+
},
|
|
545
|
+
/**
|
|
546
|
+
* #action
|
|
547
|
+
* `connectedTranscript` could not be resolved. A launch waiting on it
|
|
548
|
+
* cannot run, so for one that is the view's error; an alignment from
|
|
549
|
+
* anywhere else is still worth showing, so then it is a notice.
|
|
550
|
+
*/
|
|
551
|
+
failConnection(e) {
|
|
552
|
+
self.connectionError = e;
|
|
553
|
+
if (launchAwaitsTranscript(self)) {
|
|
554
|
+
self.setError(e);
|
|
555
|
+
}
|
|
556
|
+
},
|
|
557
|
+
/**
|
|
558
|
+
* #action
|
|
559
|
+
*/
|
|
560
|
+
clearConnectionFailure() {
|
|
561
|
+
if (self.connectionError !== undefined &&
|
|
562
|
+
self.error === self.connectionError) {
|
|
563
|
+
self.setError(undefined);
|
|
564
|
+
}
|
|
565
|
+
self.connectionError = undefined;
|
|
566
|
+
},
|
|
431
567
|
/**
|
|
432
568
|
* #action
|
|
433
569
|
*/
|
|
@@ -528,6 +664,24 @@ export default function stateModelFactory() {
|
|
|
528
664
|
self.setDomainsRequested(false);
|
|
529
665
|
self.setLastStoredData(undefined);
|
|
530
666
|
self.setOwnsDataStoreRow(false);
|
|
667
|
+
self.clearConnectionFailure();
|
|
668
|
+
},
|
|
669
|
+
};
|
|
670
|
+
})
|
|
671
|
+
.actions(self => {
|
|
672
|
+
const superClearWarnings = self.clearWarnings.bind(self);
|
|
673
|
+
return {
|
|
674
|
+
/**
|
|
675
|
+
* #action
|
|
676
|
+
* overrides base, so dismissing the warning chip hides the link
|
|
677
|
+
* notices too until their cause changes
|
|
678
|
+
*/
|
|
679
|
+
clearWarnings() {
|
|
680
|
+
superClearWarnings();
|
|
681
|
+
self.dismissedWarnings = [
|
|
682
|
+
...self.dismissedWarnings,
|
|
683
|
+
...self.linkNotices,
|
|
684
|
+
];
|
|
531
685
|
},
|
|
532
686
|
};
|
|
533
687
|
})
|