Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow 'hydrating' a defaultSession that does not specify the display type of a track #4444

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion packages/core/pluggableElementTypes/models/BaseTrackModel.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { transaction } from 'mobx'
import { autorun, transaction } from 'mobx'
import {
getRoot,
resolveIdentifier,
types,
Instance,
IAnyStateTreeNode,
addDisposer,
} from 'mobx-state-tree'

// locals
Expand Down Expand Up @@ -210,6 +211,19 @@ export function createBaseTrackModel(
: []),
]
},
afterAttach() {
addDisposer(
self,
autorun(() => {
if (!self.displays.length) {
const compatDisp = getCompatibleDisplays(self)
self.showDisplay(
self.configuration.trackId + '-' + compatDisp[0].type,
)
}
}),
)
},
}))
}

Expand Down
12 changes: 9 additions & 3 deletions products/jbrowse-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -543,26 +543,32 @@ Set a default session with views and tracks

```
USAGE
$ jbrowse set-default-session [-s <value>] [-n <value>] [-c] [--target <value>] [--out <value>] [--delete] [-h]
$ jbrowse set-default-session [-s <value>] [-n <value>] [-v <value>] [--viewId <value>] [-t <value>] [-c] [--target
<value>] [--out <value>] [--delete] [-h]

FLAGS
-c, --currentSession List out the current default session
-h, --help Show CLI help.
-n, --name=<value> [default: New Default Session] Give a name for the default session
-s, --session=<value> set path to a file containing session in json format
-t, --tracks=<value> Track id or track ids as comma separated string to put into default session
-v, --view=<value> View type in config to be added as default session, i.e LinearGenomeView, CircularView,
DotplotView.
Must be provided if no default session file provided
--delete Delete any existing default session.
--out=<value> synonym for target
--target=<value> path to config file in JB2 installation directory to write out to
--viewId=<value> Identifier for the view. Will be generated on default

DESCRIPTION
Set a default session with views and tracks

EXAMPLES
$ jbrowse set-default-session --session /path/to/default/session.json

$ jbrowse set-default-session --target /path/to/jb2/installation/config.json
$ jbrowse set-default-session --target /path/to/jb2/installation/config.json --view LinearGenomeView --tracks track1, track2, track3

$ jbrowse set-default-session --view LinearGenomeView, --name newName
$ jbrowse set-default-session --view LinearGenomeView, --name newName --viewId view-no-tracks

$ jbrowse set-default-session --currentSession # Prints out current default session
```
Expand Down
67 changes: 65 additions & 2 deletions products/jbrowse-cli/src/commands/set-default-session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,17 @@ const simpleBam = dataDir('simple.bam')
const simpleDefaultSession = dataDir('sampleDefaultSession.json')
const testConfig = dataDir('test_config.json')

// Cleaning up exitCode in Node.js 20, xref
// https://github.com/jestjs/jest/issues/14501
const setupWithAddTrack = setup
.do(async ctx => {
await copyFile(testConfig, path.join(ctx.dir, path.basename(testConfig)))
await rename(
path.join(ctx.dir, path.basename(testConfig)),
path.join(ctx.dir, 'config.json'),
)
})
.command(['add-track', simpleBam, '--load', 'copy'])

// Cleaning up exitCode in Node.js 20, xref https://github.com/jestjs/jest/issues/14501
afterAll(() => (process.exitCode = 0))

describe('set-default-session', () => {
Expand Down Expand Up @@ -121,6 +130,28 @@ describe('set-default-session', () => {
.exit(160)
.it('fails when file is does not have a default session to read')

setupWithAddTrack
.do(async ctx => {
await copyFile(testConfig, path.join(ctx.dir, path.basename(testConfig)))

await rename(
path.join(ctx.dir, path.basename(testConfig)),
path.join(ctx.dir, 'config.json'),
)
})
.command(['set-default-session', '--tracks', 'simple'])
.exit(130)
.it('fails when specifying a track without specifying a view')
setupWithAddTrack
.command([
'set-default-session',
'--view',
'LinearGenomeView',
'--tracks',
'track-non-exist',
])
.exit(140)
.it('fails when specifying a track that does not exist')
setup
.do(async ctx => {
await copyFile(testConfig, path.join(ctx.dir, path.basename(testConfig)))
Expand Down Expand Up @@ -172,4 +203,36 @@ describe('set-default-session', () => {
},
})
})
setupWithAddTrack
.command([
'set-default-session',
'--view',
'LinearGenomeView',
'--tracks',
'simple',
])
.it(
'adds a default session that is a linear genome view and a simple track',
async ctx => {
const contents = readConf(ctx)
expect(contents).toEqual({
...defaultConfig,
defaultSession: {
name: 'New Default Session',
views: [
{
id: 'LinearGenomeView-1',
type: 'LinearGenomeView',
tracks: [
{
type: 'AlignmentsTrack',
configuration: 'simple',
},
],
},
],
},
})
},
)
})
114 changes: 101 additions & 13 deletions products/jbrowse-cli/src/commands/set-default-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import JBrowseCommand from '../base'

const fsPromises = fs.promises

type DefaultSession = Record<string, unknown>
type Track = Record<string, unknown>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type DefaultSession = Record<string, any>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Track = Record<string, any>

interface Config {
assemblies?: { name: string; sequence: Record<string, unknown> }[]
// eslint-disable-next-line @typescript-eslint/no-explicit-any
assemblies?: { name: string; sequence: Record<string, any> }[]
configuration?: {}
connections?: unknown[]
defaultSession?: DefaultSession
Expand All @@ -25,8 +28,8 @@ export default class SetDefaultSession extends JBrowseCommand {

static examples = [
'$ jbrowse set-default-session --session /path/to/default/session.json',
'$ jbrowse set-default-session --target /path/to/jb2/installation/config.json',
'$ jbrowse set-default-session --view LinearGenomeView, --name newName',
'$ jbrowse set-default-session --target /path/to/jb2/installation/config.json --view LinearGenomeView --tracks track1, track2, track3',
'$ jbrowse set-default-session --view LinearGenomeView, --name newName --viewId view-no-tracks',
'$ jbrowse set-default-session --currentSession # Prints out current default session',
]

Expand All @@ -40,6 +43,19 @@ export default class SetDefaultSession extends JBrowseCommand {
description: 'Give a name for the default session',
default: 'New Default Session',
}),
view: Flags.string({
char: 'v',
description:
'View type in config to be added as default session, i.e LinearGenomeView, CircularView, DotplotView.\nMust be provided if no default session file provided',
}),
viewId: Flags.string({
description: 'Identifier for the view. Will be generated on default',
}),
tracks: Flags.string({
char: 't',
description:
'Track id or track ids as comma separated string to put into default session',
}),
currentSession: Flags.boolean({
char: 'c',
description: 'List out the current default session',
Expand All @@ -61,30 +77,102 @@ export default class SetDefaultSession extends JBrowseCommand {

async run() {
const { flags: runFlags } = await this.parse(SetDefaultSession)
const { session, currentSession, delete: deleteDefaultSession } = runFlags
const {
session,
name,
tracks,
currentSession,
view,
viewId,
delete: deleteDefaultSession,
} = runFlags
const output = runFlags.target || runFlags.out || '.'
const isDir = (await fsPromises.lstat(output)).isDirectory()
this.target = isDir ? `${output}/config.json` : output
const configContents: Config = await this.readJsonFile(this.target)

if (deleteDefaultSession) {
delete configContents.defaultSession
this.debug(`Writing configuration to file ${this.target}`)
await this.writeJsonFile(this.target, configContents)
} else if (currentSession) {
this.log(`Deleted defaultSession from ${this.target}`)
return
}

// if user passes current session flag, print out and exit
if (currentSession) {
this.log(
`The current default session is ${JSON.stringify(
configContents.defaultSession,
)}`,
)
this.exit()
} else if (!session) {
this.error(`Please either provide a default session file`, { exit: 120 })
}

const foundTracks: Track[] = []
const existingDefaultSession = configContents.defaultSession?.length > 0

// must provide default session, or view, or tracks + view
if (!session && !view && !tracks) {
this.error(
`No default session information provided, Please either provide a default session file or enter information to build a default session`,
{ exit: 120 },
)
} else if (session) {
await this.writeJsonFile(this.target, {
...configContents,
defaultSession: await this.readDefaultSessionFile(session),
})
// if user provides a file, process and set as default session and exit
const defaultJson = await this.readDefaultSessionFile(session)
configContents.defaultSession = defaultJson
} else {
// use trackids if any to match to tracks in the config
let trackIds = []
if (tracks && configContents.tracks) {
if (!view) {
this.error(
'Tracks must have a view type specified. Please rerun using the --view flag',
{ exit: 130 },
)
}
trackIds = tracks.split(',').map(c => c.trim())
trackIds.forEach(trackId => {
this.log(trackId)
const matchingTrack = configContents.tracks?.find(
track => trackId === track.trackId,
)
if (!matchingTrack) {
this.error(
`Track ${trackId} has not been added to config yet.\nPlease add the track with the add-track command before adding to the default session`,
{ exit: 140 },
)
} else {
foundTracks.push({
type: matchingTrack.type,
configuration: matchingTrack.trackId,
})
}
})
}

configContents.defaultSession = {
name,
views: [
{
id: viewId || `${view}-${foundTracks.length}`,
type: view,
tracks: foundTracks,
},
],
}
}
this.debug(`Writing configuration to file ${this.target}`)
await this.writeJsonFile(this.target, configContents)

this.log(
`${
existingDefaultSession ? 'Overwrote' : 'Added'
} defaultSession "${name}" ${existingDefaultSession ? 'in' : 'to'} ${
this.target
}`,
)
}

async readDefaultSessionFile(defaultSessionFile: string) {
Expand Down
27 changes: 6 additions & 21 deletions test_data/volvox/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -218,34 +218,19 @@
},
"connections": [],
"defaultSession": {
"name": "Integration test example",
"name": "New Default Session",
"views": [
{
"id": "integration_test",
"id": "LinearGenomeView-1",
"type": "LinearGenomeView",
"offsetPx": 2000,
"bpPerPx": 0.05,
"displayedRegions": [
"tracks": [
{
"refName": "ctgA",
"start": 0,
"end": 50001,
"assemblyName": "volvox"
"type": "FeatureTrack",
"configuration": "gff3tabix_genes"
}
]
}
],
"widgets": {
"hierarchicalTrackSelector": {
"id": "hierarchicalTrackSelector",
"type": "HierarchicalTrackSelectorWidget",
"filterText": "",
"view": "integration_test"
}
},
"activeWidgets": {
"hierarchicalTrackSelector": "hierarchicalTrackSelector"
}
]
},
"tracks": [
{
Expand Down
Loading