Custom connections
A connection adds data to a JBrowse session in bulk. At its simplest it adds a pre-configured set of tracks; more powerfully, it dynamically creates tracks by querying a remote resource, such as a UCSC Track Hub.
Adding a connection type
Connections are a pluggable element, installed with the plugin manager via
addConnectionType:
import { ConnectionType } from '@jbrowse/core/pluggableElementTypes'
import configSchema from './configSchema.ts'
import stateModelFactory from './model.ts'
import type PluginManager from '@jbrowse/core/PluginManager'
export default function JB2TrackHubConnectionF(pluginManager: PluginManager) {
pluginManager.addConnectionType(() => {
return new ConnectionType({
name: 'JB2TrackHubConnection',
configSchema,
stateModel: stateModelFactory(pluginManager),
displayName: 'JB2 Track Hub',
description: 'A JBrowse 2 config file based trackhub',
url: 'https://jbrowse.org/jb2/',
})
})
}Required items
ConnectionType requires three:
name: the name JBrowse uses internally and in configuration files to refer to this type of connectionconfigSchema: a configuration schema for the connection. It defines the options a user can configure when adding the connection, and must at least have anameslot.stateModel: the@jbrowse/mobx-state-treemodel that does the queries and creates tracks (see below).
Optional items
The first three furnish the "Add connection" dialog. A connection may omit them, but every connection in the tree sets all three.
displayName: what the connection-type dropdown lists, falling back tonameas it does on every pluggable element. Worth setting to something readable, sincenameis an identifier.description: a sentence about what the connection connects to, shown as helper text under that dropdown once the type is selected.url: a link to more information about the resource or its format, rendered as an external link after the description.configEditorComponent: by default the user configures the connection with the built-in config editor. A custom React component can be supplied instead; it receives amodelprop whosetargetis the connection config. Set a slot withmodel.target.setSlot('name', 'someNewName'). No session prop is passed — reach it withgetSession(model.target).
State model
The state model composes BaseConnectionModelFactory and implements
connect(). Keep the model itself thin: connect() hands off to a lazily
imported module:
import { ConfigurationReference } from '@jbrowse/core/configuration'
import { BaseConnectionModelFactory } from '@jbrowse/core/pluggableElementTypes/models'
import { types } from '@jbrowse/mobx-state-tree'
import { lazyConnect } from '../lazyConnect.ts'
import configSchema from './configSchema.ts'
import type PluginManager from '@jbrowse/core/PluginManager'
/**
* #stateModel JB2TrackHubConnection
* Fetches another JBrowse 2 `config.json` on connect and adds its tracks to
* the session.
*/
export default function JB2TrackHubConnection(pluginManager: PluginManager) {
return types
.compose(
'JB2TrackHubConnection',
BaseConnectionModelFactory(pluginManager),
types.model({
/**
* #property
*/
configuration: ConfigurationReference(configSchema),
/**
* #property
*/
type: types.literal('JB2TrackHubConnection'),
}),
)
.actions(self => ({
/**
* #action
*/
connect() {
return lazyConnect(self, () => import('./doConnect.ts'))
},
}))
}lazyConnect keeps a connection's parsing code out of the startup bundle until
someone actually connects, and it handles connection failure — it logs,
notifies, and breaks the connection — so each doConnect is only the happy
path. It also re-checks isAlive(self) after the dynamic import, since
doConnect walks up to the session and a StrictMode double-mount can dispose
the node mid-import.
The doConnect module is where the configuration is read and the tracks are
added:
import { getConf } from '@jbrowse/core/configuration'
import { getEnv, getSession } from '@jbrowse/core/util'
import { addRelativeUris } from '@jbrowse/core/util/addRelativeUris'
import { openLocation } from '@jbrowse/core/util/io'
import { resolve } from './util.ts'
import type { ConnectionDoConnectArg } from '../lazyConnect.ts'
import type { UriLocation } from '@jbrowse/core/util'
// lazyConnect wraps this in the shared connect-failure handler
export async function doConnect(self: ConnectionDoConnectArg) {
const session = getSession(self)
const { pluginManager } = getEnv(self)
const configJsonLocation = getConf(self, 'configJsonLocation') as UriLocation
const configJson = JSON.parse(
await openLocation(configJsonLocation, pluginManager).readFile('utf8'),
)
const configUri = resolve(configJsonLocation.uri, configJsonLocation.baseUri)
addRelativeUris(configJson, new URL(configUri))
if (configJson.assemblies) {
for (const assembly of configJson.assemblies) {
if (!session.assemblyManager.has(assembly.name)) {
session.addSessionAssembly?.(assembly)
}
}
}
if (configJson.tracks) {
self.addTrackConfs(configJson.tracks)
}
if (!self.silent) {
session.notify('Successfully loaded', 'success')
}
}BaseConnectionModelFactory provides addTrackConf, addTrackConfs, and
setTrackConfs for adding tracks incrementally or all at once. Note silent:
it is set when a session is restored and the connection reconnects on load, and
it suppresses the first-connect side effects (the success snackbar, launching a
view) that would otherwise fire on every page load.
See also
- Developer guide: Add-track workflows
- Developer guide: Configuration schema
- Developer guide: MST patterns
- Developer guide: Pluggable elements