Skip to content

Commit fea3bae

Browse files
Fix for adding features to assembly or refSeq that's not loaded yet (#723)
* Misc. AddAssembly dialog fix * Standardize usage of getBackendDriver * Add assembly/ref to data store when adding feature * Generate refseq id map on demand, not in config * Fix AddFeature dialog if no features present yet * Further AddAssembly dialog fix
1 parent 80a4c9e commit fea3bae

File tree

11 files changed

+183
-125
lines changed

11 files changed

+183
-125
lines changed

packages/apollo-collaboration-server/src/jbrowse/jbrowse.service.ts

Lines changed: 36 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Injectable, Logger } from '@nestjs/common'
66
import { ConfigService } from '@nestjs/config'
77
import { InjectModel } from '@nestjs/mongoose'
88
import merge from 'deepmerge'
9-
import { Model, Types } from 'mongoose'
9+
import { Model } from 'mongoose'
1010

1111
import { AssembliesService } from '../assemblies/assemblies.service'
1212
import { RefSeqsService } from '../refSeqs/refSeqs.service'
@@ -138,58 +138,45 @@ export class JBrowseService {
138138
async getAssemblies() {
139139
const url = this.configService.get('URL', { infer: true })
140140
const assemblies = await this.assembliesService.findAll()
141-
return Promise.all(
142-
assemblies.map(async (assembly) => {
143-
const assemblyId = assembly._id.toHexString()
144-
const refSeqs = await this.refSeqsService.findAll({
145-
assembly: assemblyId,
146-
})
147-
const ids: Record<string, string> = {}
148-
refSeqs.map((refSeq) => {
149-
ids[refSeq.name] = (refSeq._id as Types.ObjectId).toHexString()
150-
})
151-
this.logger.debug(`generating assembly ${assemblyId}`)
152-
const trackId = `sequenceConfigId-${assembly.name}`
153-
return {
154-
name: assemblyId,
155-
aliases:
156-
assembly.aliases.length > 0
157-
? [...assembly.aliases]
158-
: [assembly.name],
159-
displayName: assembly.displayName || assembly.name,
160-
sequence: {
161-
trackId,
162-
type: 'ReferenceSequenceTrack',
163-
adapter: {
164-
type: 'ApolloSequenceAdapter',
165-
assemblyId,
166-
baseURL: {
167-
uri: url,
168-
locationType: 'UriLocation',
169-
},
170-
},
171-
displays: [
172-
{
173-
type: 'LinearApolloReferenceSequenceDisplay',
174-
displayId: `${trackId}-LinearApolloReferenceSequenceDisplay`,
175-
},
176-
],
177-
metadata: {
178-
apollo: true,
179-
internetAccountConfigId: this.internetAccountId,
180-
ids,
141+
return assemblies.map((assembly) => {
142+
const assemblyId = assembly._id.toHexString()
143+
const trackId = `sequenceConfigId-${assembly.name}`
144+
return {
145+
name: assemblyId,
146+
aliases:
147+
assembly.aliases.length > 0 ? [...assembly.aliases] : [assembly.name],
148+
displayName: assembly.displayName || assembly.name,
149+
sequence: {
150+
trackId,
151+
type: 'ReferenceSequenceTrack',
152+
adapter: {
153+
type: 'ApolloSequenceAdapter',
154+
assemblyId,
155+
baseURL: {
156+
uri: url,
157+
locationType: 'UriLocation',
181158
},
182159
},
183-
refNameAliases: {
184-
adapter: {
185-
type: 'ApolloRefNameAliasAdapter',
186-
assemblyId,
187-
baseURL: { uri: url, locationType: 'UriLocation' },
160+
displays: [
161+
{
162+
type: 'LinearApolloReferenceSequenceDisplay',
163+
displayId: `${trackId}-LinearApolloReferenceSequenceDisplay`,
188164
},
165+
],
166+
metadata: {
167+
apollo: true,
168+
internetAccountConfigId: this.internetAccountId,
169+
},
170+
},
171+
refNameAliases: {
172+
adapter: {
173+
type: 'ApolloRefNameAliasAdapter',
174+
assemblyId,
175+
baseURL: { uri: url, locationType: 'UriLocation' },
189176
},
190-
}
191-
}),
192-
)
177+
},
178+
}
179+
})
193180
}
194181

195182
async getTracks() {

packages/apollo-common/src/Change.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export interface ClientDataStore {
2727
assemblyName?: string,
2828
internetAccountId?: string,
2929
): AppRootModel['internetAccounts'][0]
30-
loadFeatures(regions: Region[]): void
30+
loadFeatures(regions: Region[]): Promise<void>
3131
loadRefSeq(regions: Region[]): void
3232
getFeature(featureId: string): AnnotationFeature | undefined
3333
addFeature(assemblyId: string, feature: AnnotationFeatureSnapshot): void

packages/apollo-shared/src/Changes/AddFeatureChange.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,23 @@ export class AddFeatureChange extends FeatureChange {
167167
for (const change of changes) {
168168
const { addedFeature, parentFeatureId } = change
169169
if (parentFeatureId) {
170-
const parentFeature = dataStore.getFeature(parentFeatureId)
170+
let parentFeature = dataStore.getFeature(parentFeatureId)
171+
// maybe the parent feature hasn't been loaded yet
171172
if (!parentFeature) {
172-
throw new Error(`Could not find parent feature "${parentFeatureId}"`)
173+
await dataStore.loadFeatures([
174+
{
175+
assemblyName: assembly,
176+
refName: addedFeature.refSeq,
177+
start: addedFeature.min,
178+
end: addedFeature.max,
179+
},
180+
])
181+
parentFeature = dataStore.getFeature(parentFeatureId)
182+
if (!parentFeature) {
183+
throw new Error(
184+
`Could not find parent feature "${parentFeatureId}"`,
185+
)
186+
}
173187
}
174188
// create an ID for the parent feature if it does not have one
175189
if (!parentFeature.attributes.get('_id')) {

packages/jbrowse-plugin-apollo/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
import type RpcServer from 'librpc-web-mod/dist/server'
77
import { nanoid } from 'nanoid'
88

9-
import { type BackendDriver } from '../BackendDrivers'
109
import { type ApolloSessionModel } from '../session'
1110

1211
import { type RefNameAliases } from './../BackendDrivers/BackendDriver'
@@ -50,9 +49,10 @@ export default class RefNameAliasAdapter
5049
if (!dataStore) {
5150
throw new Error('No Apollo data store found')
5251
}
53-
const backendDriver = dataStore.getBackendDriver(
54-
assemblyId,
55-
) as BackendDriver
52+
const backendDriver = dataStore.getBackendDriver(assemblyId)
53+
if (!backendDriver) {
54+
throw new Error('No backend driver found')
55+
}
5656
const refNameAliases = await backendDriver.getRefNameAliases(assemblyId)
5757
return refNameAliases
5858
}

packages/jbrowse-plugin-apollo/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import SimpleFeature, { type Feature } from '@jbrowse/core/util/simpleFeature'
1010
import { type NoAssemblyRegion, type Region } from '@jbrowse/core/util/types'
1111
import { nanoid } from 'nanoid'
1212

13-
import { type BackendDriver } from '../BackendDrivers'
1413
import { type ApolloSessionModel } from '../session'
1514

1615
// declare global {
@@ -62,9 +61,10 @@ export class ApolloSequenceAdapter extends BaseSequenceAdapter {
6261
if (!dataStore) {
6362
throw new Error('No Apollo data store found')
6463
}
65-
const backendDriver = dataStore.getBackendDriver(
66-
assemblyId,
67-
) as BackendDriver
64+
const backendDriver = dataStore.getBackendDriver(assemblyId)
65+
if (!backendDriver) {
66+
throw new Error('No backend driver found')
67+
}
6868
const regions = await backendDriver.getRegions(assemblyId)
6969
this.regions = regions
7070
return regions
@@ -124,9 +124,11 @@ export class ApolloSequenceAdapter extends BaseSequenceAdapter {
124124
observer.error('No Apollo data store found')
125125
return
126126
}
127-
const backendDriver = dataStore.getBackendDriver(
128-
assemblyId,
129-
) as BackendDriver
127+
const backendDriver = dataStore.getBackendDriver(assemblyId)
128+
if (!backendDriver) {
129+
observer.error('No backend driver found')
130+
return
131+
}
130132
const regions = await backendDriver.getRegions(
131133
regionWithAssemblyName.assemblyName,
132134
)

packages/jbrowse-plugin-apollo/src/BackendDrivers/CollaborationServerDriver.ts

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ export interface ApolloRefSeqResponse {
3838
assembly: string
3939
}
4040

41+
interface RefSeq {
42+
refName: string
43+
id: string
44+
aliases: string[]
45+
}
46+
47+
type RefSeqMap = Map<string, RefSeq>
48+
4149
export interface ApolloInternetAccount extends BaseInternetAccountModel {
4250
baseURL: string
4351
socket: Socket
@@ -48,6 +56,8 @@ export interface ApolloInternetAccount extends BaseInternetAccountModel {
4856
export class CollaborationServerDriver extends BackendDriver {
4957
private inFlight = new Map<string, Promise<string>>()
5058

59+
private refSeqMaps = new Map<string, RefSeqMap>()
60+
5161
private async fetch(
5262
internetAccount: ApolloInternetAccount,
5363
info: RequestInfo,
@@ -97,13 +107,12 @@ export class CollaborationServerDriver extends BackendDriver {
97107
if (!assembly) {
98108
throw new Error(`Could not find assembly with name "${assemblyName}"`)
99109
}
100-
const { ids } = getConf(assembly, ['sequence', 'metadata']) as {
101-
ids: Record<string, string>
102-
}
103-
const refSeq = ids[refName]
104-
if (!refSeq) {
110+
const refSeqMap = await this.getRefSeqMapping(assemblyName)
111+
const refSeqEntry = refSeqMap.get(refName)
112+
if (!refSeqEntry) {
105113
throw new Error(`Could not find refSeq "${refName}"`)
106114
}
115+
const refSeq = refSeqEntry.id
107116
const internetAccount = this.clientStore.getInternetAccount(
108117
assemblyName,
109118
) as ApolloInternetAccount
@@ -192,13 +201,12 @@ export class CollaborationServerDriver extends BackendDriver {
192201
if (!assembly) {
193202
throw new Error(`Could not find assembly with name "${assemblyName}"`)
194203
}
195-
const { ids } = getConf(assembly, ['sequence', 'metadata']) as {
196-
ids: Record<string, string>
197-
}
198-
const refSeq = ids[refName]
199-
if (!refSeq) {
204+
const refSeqMap = await this.getRefSeqMapping(assemblyName)
205+
const refSeqEntry = refSeqMap.get(refName)
206+
if (!refSeqEntry) {
200207
throw new Error(`Could not find refSeq "${refName}"`)
201208
}
209+
const refSeq = refSeqEntry.id
202210
if (inFlightPromise) {
203211
const seq = await inFlightPromise
204212
return { seq, refSeq }
@@ -269,7 +277,11 @@ export class CollaborationServerDriver extends BackendDriver {
269277
return seq
270278
}
271279

272-
async getRefNameAliases(assemblyName: string): Promise<RefNameAliases[]> {
280+
async getRefSeqMapping(assemblyName: string): Promise<RefSeqMap> {
281+
const cachedRefSeqMap = this.refSeqMaps.get(assemblyName)
282+
if (cachedRefSeqMap) {
283+
return cachedRefSeqMap
284+
}
273285
const { assemblyManager } = getSession(this.clientStore)
274286
const assembly = assemblyManager.get(assemblyName)
275287
if (!assembly) {
@@ -299,13 +311,32 @@ export class CollaborationServerDriver extends BackendDriver {
299311
)
300312
}
301313
const refSeqs = (await response.json()) as ApolloRefSeqResponse[]
302-
return refSeqs.map((refSeq) => {
303-
return {
304-
refName: refSeq.name,
305-
aliases: [...new Set([refSeq._id, ...refSeq.aliases])],
306-
uniqueId: `alias-${refSeq._id}`,
307-
}
308-
}) as RefNameAliases[]
314+
const refSeqMap = new Map<string, RefSeq>(
315+
refSeqs.map((refSeq) => [
316+
refSeq.name,
317+
{ refName: refSeq.name, id: refSeq._id, aliases: refSeq.aliases },
318+
]),
319+
)
320+
this.refSeqMaps.set(assemblyName, refSeqMap)
321+
return refSeqMap
322+
}
323+
324+
async getRefNameAliases(assemblyName: string): Promise<RefNameAliases[]> {
325+
const refSeqMap = await this.getRefSeqMapping(assemblyName)
326+
return [...refSeqMap.values()].map((refSeq) => ({
327+
refName: refSeq.refName,
328+
aliases: [...new Set([refSeq.id, ...refSeq.aliases])],
329+
uniqueId: `alias-${refSeq.id}`,
330+
}))
331+
}
332+
333+
async getRefSeqId(assemblyName: string, refName: string) {
334+
const refSeqMap = await this.getRefSeqMapping(assemblyName)
335+
if (!refSeqMap) {
336+
return
337+
}
338+
const refSeq = refSeqMap.get(refName)
339+
return refSeq?.id
309340
}
310341

311342
async getRegions(assemblyName: string): Promise<Region[]> {

packages/jbrowse-plugin-apollo/src/ChangeManager.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ export class ChangeManager {
106106
// submit to driver
107107
const { collaborationServerDriver, getBackendDriver } = this.dataStore
108108
const backendDriver = isAssemblySpecificChange(change)
109-
? getBackendDriver(change.assembly)
109+
? // for assembly-specific change, fall back in case it's an
110+
// add-assembly change, since that won't exist in the driver yet
111+
getBackendDriver(change.assembly) ?? collaborationServerDriver
110112
: collaborationServerDriver
111113
let backendResult: ValidationResultSet
112114
try {

packages/jbrowse-plugin-apollo/src/components/AddAssembly.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,12 @@ export function AddAssembly({
178178
const formData = new FormData()
179179
let filename = file.name
180180
const isGzip =
181+
fileType === FileType.BGZIP_FASTA ||
181182
(fileType === FileType.FASTA &&
182183
(!sequenceIsEditable || fastaGzipChecked)) ||
183184
(fileType === FileType.GFF3 && gff3GzipChecked)
184185

185-
if (fileType === FileType.FAI || fileType === FileType.GZI) {
186-
filename = `${filename}.txt`
187-
} else if (isGzip && !file.name.toLocaleLowerCase().endsWith('.gz')) {
186+
if (isGzip && !file.name.toLocaleLowerCase().endsWith('.gz')) {
188187
filename = `${filename}.gz`
189188
} else if (!isGzip && file.name.toLocaleLowerCase().endsWith('.gz')) {
190189
filename = `${filename}.txt`

0 commit comments

Comments
 (0)