jbrowse-plugin-msaview 2.6.2 → 2.6.3
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/LaunchMsaViewExtensionPoint/index.js +12 -5
- package/dist/MsaViewPanel/afterCreateAutoruns.js +10 -21
- package/dist/MsaViewPanel/autoConnectStructures.test.d.ts +1 -0
- package/dist/MsaViewPanel/autoConnectStructures.test.js +60 -0
- package/dist/MsaViewPanel/structureConnection.d.ts +20 -0
- package/dist/MsaViewPanel/structureConnection.js +20 -0
- package/dist/MsaViewPanel/structureConnection.test.js +35 -1
- package/dist/MsaViewPanel/types.d.ts +1 -4
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +20 -20
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/LaunchMsaViewExtensionPoint/index.ts +12 -5
- package/src/MsaViewPanel/afterCreateAutoruns.ts +10 -28
- package/src/MsaViewPanel/autoConnectStructures.test.ts +76 -0
- package/src/MsaViewPanel/structureConnection.test.ts +53 -1
- package/src/MsaViewPanel/structureConnection.ts +30 -0
- package/src/MsaViewPanel/types.ts +14 -9
- package/src/version.ts +1 -1
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "2.6.
|
|
1
|
+
export declare const version = "2.6.3";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '2.6.
|
|
1
|
+
export const version = '2.6.3';
|
package/package.json
CHANGED
|
@@ -46,18 +46,25 @@ export default function LaunchMsaViewExtensionPointF(
|
|
|
46
46
|
)
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
//
|
|
50
|
-
//
|
|
49
|
+
// inline data and the tree URL are native react-msaview snapshot props, set
|
|
50
|
+
// directly. Only sources needing launch-time resolution go through `init`:
|
|
51
|
+
// msaUrl (AlphaFold sniff) and the name-indexed bgzip block (no native loader).
|
|
51
52
|
session.addView('MsaView', {
|
|
52
53
|
type: 'MsaView',
|
|
53
54
|
...rest,
|
|
55
|
+
data,
|
|
56
|
+
...(treeFileLocation
|
|
57
|
+
? {
|
|
58
|
+
treeFilehandle: {
|
|
59
|
+
...treeFileLocation,
|
|
60
|
+
locationType: 'UriLocation',
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
: {}),
|
|
54
64
|
init: {
|
|
55
|
-
msaData: data?.msa,
|
|
56
|
-
treeData: data?.tree,
|
|
57
65
|
msaUrl: msaFileLocation?.uri,
|
|
58
66
|
msaIndexedLocation,
|
|
59
67
|
msaName,
|
|
60
|
-
treeUrl: treeFileLocation?.uri,
|
|
61
68
|
querySeqName,
|
|
62
69
|
},
|
|
63
70
|
})
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
gappedToUngappedPosition,
|
|
15
15
|
getProteinViews,
|
|
16
|
+
structureMatchesMsa,
|
|
16
17
|
} from './structureConnection'
|
|
17
18
|
import { getUniprotIdFromAlphaFoldUrl } from './util'
|
|
18
19
|
|
|
@@ -128,22 +129,15 @@ export function autoLoadProteinDomains(self: JBrowsePluginMsaViewModel) {
|
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
// Resolve the declarative `init` launch contract once
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
132
|
+
// Resolve the declarative `init` launch contract once, then clear it. msaUrl is
|
|
133
|
+
// handed to react-msaview's native filehandle loader (openLocation + progress +
|
|
134
|
+
// abort + CORS-proxy) and sniffed for an AlphaFold uniprotId; the bgzip
|
|
135
|
+
// name-indexed block is the one source with no native loader, so it's fetched
|
|
136
|
+
// here. Inline data and tree URLs arrive as native snapshot props, not via init.
|
|
135
137
|
export function processInit(self: JBrowsePluginMsaViewModel) {
|
|
136
138
|
const { init } = self
|
|
137
139
|
if (init) {
|
|
138
|
-
const {
|
|
139
|
-
msaData,
|
|
140
|
-
msaUrl,
|
|
141
|
-
msaIndexedLocation,
|
|
142
|
-
msaName,
|
|
143
|
-
treeData,
|
|
144
|
-
treeUrl,
|
|
145
|
-
querySeqName,
|
|
146
|
-
} = init
|
|
140
|
+
const { msaUrl, msaIndexedLocation, msaName, querySeqName } = init
|
|
147
141
|
void (async () => {
|
|
148
142
|
try {
|
|
149
143
|
self.setError(undefined)
|
|
@@ -159,9 +153,7 @@ export function processInit(self: JBrowsePluginMsaViewModel) {
|
|
|
159
153
|
self.setQuerySeqName(querySeqName)
|
|
160
154
|
}
|
|
161
155
|
|
|
162
|
-
if (
|
|
163
|
-
self.setMSA(msaData)
|
|
164
|
-
} else if (msaUrl) {
|
|
156
|
+
if (msaUrl) {
|
|
165
157
|
self.setMSAFilehandle({ uri: msaUrl, locationType: 'UriLocation' })
|
|
166
158
|
} else if (msaIndexedLocation && msaName) {
|
|
167
159
|
const fasta = await fetchIndexedMsa({
|
|
@@ -177,12 +169,6 @@ export function processInit(self: JBrowsePluginMsaViewModel) {
|
|
|
177
169
|
}
|
|
178
170
|
}
|
|
179
171
|
|
|
180
|
-
if (treeData) {
|
|
181
|
-
self.setTree(treeData)
|
|
182
|
-
} else if (treeUrl) {
|
|
183
|
-
self.setTreeFilehandle({ uri: treeUrl, locationType: 'UriLocation' })
|
|
184
|
-
}
|
|
185
|
-
|
|
186
172
|
self.setInit(undefined)
|
|
187
173
|
} catch (e) {
|
|
188
174
|
self.setError(e)
|
|
@@ -253,7 +239,7 @@ export function highlightConnectedStructures(self: JBrowsePluginMsaViewModel) {
|
|
|
253
239
|
export function autoConnectStructures(self: JBrowsePluginMsaViewModel) {
|
|
254
240
|
const { connectedViewId, uniprotId, rows, connectedStructures } = self
|
|
255
241
|
|
|
256
|
-
if (
|
|
242
|
+
if (rows.length === 0) {
|
|
257
243
|
return
|
|
258
244
|
}
|
|
259
245
|
|
|
@@ -268,11 +254,7 @@ export function autoConnectStructures(self: JBrowsePluginMsaViewModel) {
|
|
|
268
254
|
continue
|
|
269
255
|
}
|
|
270
256
|
|
|
271
|
-
if (structure
|
|
272
|
-
continue
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (structure.uniprotId !== uniprotId) {
|
|
257
|
+
if (!structureMatchesMsa({ structure, connectedViewId, uniprotId })) {
|
|
276
258
|
continue
|
|
277
259
|
}
|
|
278
260
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { getSession } from '@jbrowse/core/util'
|
|
2
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { autoConnectStructures } from './afterCreateAutoruns'
|
|
5
|
+
|
|
6
|
+
import type { JBrowsePluginMsaViewModel } from './model'
|
|
7
|
+
import type { ProteinView } from './structureConnection'
|
|
8
|
+
|
|
9
|
+
// Integration coverage for the autorun itself — the structure-matching matrix
|
|
10
|
+
// lives in structureConnection.test.ts (structureMatchesMsa). Here we check the
|
|
11
|
+
// autorun wires a match through to connectToStructure and respects its guards.
|
|
12
|
+
|
|
13
|
+
// Mock only getSession; keep the rest of the util module real so the
|
|
14
|
+
// afterCreateAutoruns import graph still loads.
|
|
15
|
+
vi.mock('@jbrowse/core/util', async importOriginal => ({
|
|
16
|
+
...(await importOriginal<Record<string, unknown>>()),
|
|
17
|
+
getSession: vi.fn(),
|
|
18
|
+
}))
|
|
19
|
+
|
|
20
|
+
const mockGetSession = vi.mocked(getSession)
|
|
21
|
+
|
|
22
|
+
function makeModel(opts: { connectedViewId?: string; uniprotId?: string }) {
|
|
23
|
+
const connected: { proteinViewId: string; structureIdx: number }[] = []
|
|
24
|
+
const model = {
|
|
25
|
+
connectedViewId: opts.connectedViewId,
|
|
26
|
+
uniprotId: opts.uniprotId,
|
|
27
|
+
rows: [['hg38', 'MKATEST']],
|
|
28
|
+
connectedStructures: connected,
|
|
29
|
+
connectToStructure: (proteinViewId: string, structureIdx: number) => {
|
|
30
|
+
connected.push({ proteinViewId, structureIdx })
|
|
31
|
+
},
|
|
32
|
+
} as unknown as JBrowsePluginMsaViewModel
|
|
33
|
+
return { model, connected }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function withStructure(structure: {
|
|
37
|
+
connectedViewId?: string
|
|
38
|
+
uniprotId?: string
|
|
39
|
+
}) {
|
|
40
|
+
const view: ProteinView = {
|
|
41
|
+
type: 'ProteinView',
|
|
42
|
+
id: 'pv1',
|
|
43
|
+
structures: [{ ...structure, structureSequences: ['MKATEST'] }],
|
|
44
|
+
}
|
|
45
|
+
mockGetSession.mockReturnValue({
|
|
46
|
+
views: [view],
|
|
47
|
+
} as unknown as ReturnType<typeof getSession>)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe('autoConnectStructures', () => {
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
vi.clearAllMocks()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('connects a matching structure (genome-view link, no UniProt id)', () => {
|
|
56
|
+
const { model, connected } = makeModel({ connectedViewId: 'lgv-TP53' })
|
|
57
|
+
withStructure({ connectedViewId: 'lgv-TP53' })
|
|
58
|
+
autoConnectStructures(model)
|
|
59
|
+
expect(connected).toEqual([{ proteinViewId: 'pv1', structureIdx: 0 }])
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('does not connect a non-matching structure', () => {
|
|
63
|
+
const { model, connected } = makeModel({ connectedViewId: 'lgv-TP53' })
|
|
64
|
+
withStructure({ connectedViewId: 'lgv-OTHER' })
|
|
65
|
+
autoConnectStructures(model)
|
|
66
|
+
expect(connected).toEqual([])
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('does not connect before the alignment has loaded (no rows)', () => {
|
|
70
|
+
const { model, connected } = makeModel({ connectedViewId: 'lgv-TP53' })
|
|
71
|
+
;(model as unknown as { rows: unknown[] }).rows = []
|
|
72
|
+
withStructure({ connectedViewId: 'lgv-TP53' })
|
|
73
|
+
autoConnectStructures(model)
|
|
74
|
+
expect(connected).toEqual([])
|
|
75
|
+
})
|
|
76
|
+
})
|
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
import { describe, expect, test } from 'vitest'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
gappedToUngappedPosition,
|
|
5
|
+
structureMatchesMsa,
|
|
6
|
+
} from './structureConnection'
|
|
7
|
+
|
|
8
|
+
describe('structureMatchesMsa', () => {
|
|
9
|
+
test('matches on a shared genome view alone (no UniProt id)', () => {
|
|
10
|
+
expect(
|
|
11
|
+
structureMatchesMsa({
|
|
12
|
+
structure: { connectedViewId: 'lgv-TP53' },
|
|
13
|
+
connectedViewId: 'lgv-TP53',
|
|
14
|
+
}),
|
|
15
|
+
).toBe(true)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('matches on a shared UniProt id alone (no genome view)', () => {
|
|
19
|
+
expect(
|
|
20
|
+
structureMatchesMsa({
|
|
21
|
+
structure: { uniprotId: 'P04637' },
|
|
22
|
+
uniprotId: 'P04637',
|
|
23
|
+
}),
|
|
24
|
+
).toBe(true)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('shared genome view wins even when UniProt ids differ', () => {
|
|
28
|
+
expect(
|
|
29
|
+
structureMatchesMsa({
|
|
30
|
+
structure: { connectedViewId: 'lgv-TP53', uniprotId: 'OTHER' },
|
|
31
|
+
connectedViewId: 'lgv-TP53',
|
|
32
|
+
uniprotId: 'P04637',
|
|
33
|
+
}),
|
|
34
|
+
).toBe(true)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('no match when neither key matches', () => {
|
|
38
|
+
expect(
|
|
39
|
+
structureMatchesMsa({
|
|
40
|
+
structure: { connectedViewId: 'lgv-OTHER', uniprotId: 'OTHER' },
|
|
41
|
+
connectedViewId: 'lgv-TP53',
|
|
42
|
+
uniprotId: 'P04637',
|
|
43
|
+
}),
|
|
44
|
+
).toBe(false)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('two undefined connectedViewIds do not count as a shared genome view', () => {
|
|
48
|
+
// both sides lacking a genome view must NOT auto-pair on `undefined ===
|
|
49
|
+
// undefined`; only an explicit shared id (or UniProt id) connects
|
|
50
|
+
expect(structureMatchesMsa({ structure: {} })).toBe(false)
|
|
51
|
+
expect(
|
|
52
|
+
structureMatchesMsa({ structure: { uniprotId: 'P04637' } }),
|
|
53
|
+
).toBe(false)
|
|
54
|
+
})
|
|
55
|
+
})
|
|
4
56
|
|
|
5
57
|
describe('gappedToUngappedPosition', () => {
|
|
6
58
|
test('returns correct ungapped position for non-gap character', () => {
|
|
@@ -28,6 +28,36 @@ export function getProteinViews(views: { type: string }[]): ProteinView[] {
|
|
|
28
28
|
return (views as unknown[]).filter(isProteinView)
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Whether a 3D structure belongs to a given alignment — the single source of
|
|
33
|
+
* truth for pairing an MsaView with a ProteinView's structure. A structure
|
|
34
|
+
* matches when it either:
|
|
35
|
+
* - shares the alignment's genome view (both pinned to the same
|
|
36
|
+
* LinearGenomeView via `connectedViewId` — the genome-centric gene-explorer
|
|
37
|
+
* flow, the same key genome↔MSA and genome↔structure already bridge through),
|
|
38
|
+
* or
|
|
39
|
+
* - shares the alignment's UniProt accession (the BLAST/AlphaFold flow, which
|
|
40
|
+
* has no genome view to bridge through).
|
|
41
|
+
*
|
|
42
|
+
* The residue map itself is built by sequence (connectToStructure pairwise-
|
|
43
|
+
* aligns the query row against the structure), so neither key is mechanically
|
|
44
|
+
* required — they only scope WHICH structure pairs with the alignment.
|
|
45
|
+
*/
|
|
46
|
+
export function structureMatchesMsa({
|
|
47
|
+
structure,
|
|
48
|
+
connectedViewId,
|
|
49
|
+
uniprotId,
|
|
50
|
+
}: {
|
|
51
|
+
structure: Pick<ProteinViewStructure, 'connectedViewId' | 'uniprotId'>
|
|
52
|
+
connectedViewId?: string
|
|
53
|
+
uniprotId?: string
|
|
54
|
+
}) {
|
|
55
|
+
const sharesGenomeView =
|
|
56
|
+
!!connectedViewId && structure.connectedViewId === connectedViewId
|
|
57
|
+
const sharesUniprot = !!uniprotId && structure.uniprotId === uniprotId
|
|
58
|
+
return sharesGenomeView || sharesUniprot
|
|
59
|
+
}
|
|
60
|
+
|
|
31
61
|
/**
|
|
32
62
|
* Represents a connection between the MSA view and a protein structure
|
|
33
63
|
*/
|
|
@@ -1,19 +1,24 @@
|
|
|
1
|
-
// Declarative launch contract, resolved once by processInit
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Declarative launch contract, resolved once by processInit, then cleared. Only
|
|
2
|
+
// sources that need launch-time resolution belong here. Inline data and tree URLs
|
|
3
|
+
// do NOT: they are native react-msaview snapshot props (`data`, `treeFilehandle`)
|
|
4
|
+
// applied directly from the addView snapshot, no resolution required.
|
|
5
|
+
//
|
|
6
|
+
// Cross-repo contract: jbrowse-plugin-protein3d builds an MsaView snapshot directly
|
|
7
|
+
// with `init: { msaUrl }`, so these keys must stay stable.
|
|
4
8
|
export interface MsaViewInitState {
|
|
5
|
-
|
|
9
|
+
// resolved here (not as a native msaFilehandle) so the AlphaFold-URL → uniprotId
|
|
10
|
+
// sniff runs once at launch; querySeqName is coupled to it (AlphaFold files name
|
|
11
|
+
// the query row 'query'), which is why it rides along in init rather than being a
|
|
12
|
+
// plain top-level prop.
|
|
6
13
|
msaUrl?: string
|
|
14
|
+
querySeqName?: string
|
|
7
15
|
// a single bgzip `.fa.gz` of per-transcript FASTA blocks; its `.gzi` and name
|
|
8
16
|
// index `.idx` (name<TAB>offset<TAB>length) are found by suffix. `msaName`
|
|
9
17
|
// selects one transcript's block by name (a random read), so one genome-scale
|
|
10
|
-
// alignment serves any gene without per-gene files or coordinates.
|
|
11
|
-
// react-msaview's gene-explorer.
|
|
18
|
+
// alignment serves any gene without per-gene files or coordinates. This is the
|
|
19
|
+
// one alignment source with no native loader. See react-msaview's gene-explorer.
|
|
12
20
|
msaIndexedLocation?: { uri: string }
|
|
13
21
|
msaName?: string
|
|
14
|
-
treeData?: string
|
|
15
|
-
treeUrl?: string
|
|
16
|
-
querySeqName?: string
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
export interface MafRegion {
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '2.6.
|
|
1
|
+
export const version = '2.6.3'
|