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

Add 'matrix' display for multi-sample VCFs #4511

Merged
merged 17 commits into from
Dec 7, 2024
Merged
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
1 change: 0 additions & 1 deletion packages/core/ReExports/DataGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
// convert named export to default for lazy react

export { DataGrid as default } from '@mui/x-data-grid'
61 changes: 27 additions & 34 deletions packages/core/configuration/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,8 @@ export function readConfObject<CONFMODEL extends AnyConfigurationModel>(
args: Record<string, unknown> = {},
): any {
if (!slotPath) {
return JSON.parse(JSON.stringify(getSnapshot(confObject)))
}
if (typeof slotPath === 'string') {
return structuredClone(getSnapshot(confObject))
} else if (typeof slotPath === 'string') {
let slot = confObject[slotPath]
// check for the subconf being a map if we don't find it immediately
if (
Expand All @@ -66,15 +65,13 @@ export function readConfObject<CONFMODEL extends AnyConfigurationModel>(
// schemaType.name
// })`,
// )
} else {
const val = slot.expr ? slot.expr.evalSync(args) : slot
return isStateTreeNode(val)
? JSON.parse(JSON.stringify(getSnapshot(val)))
: val
}

const val = slot.expr ? slot.expr.evalSync(args) : slot
return isStateTreeNode(val)
? JSON.parse(JSON.stringify(getSnapshot(val)))
: val
}

if (Array.isArray(slotPath)) {
} else if (Array.isArray(slotPath)) {
const slotName = slotPath[0]!
if (slotPath.length > 1) {
const newPath = slotPath.slice(1)
Expand Down Expand Up @@ -172,39 +169,35 @@ export function isBareConfigurationSchemaType(
export function isConfigurationSchemaType(
thing: unknown,
): thing is AnyConfigurationSchemaType {
if (!isType(thing)) {
return false
}

// written as a series of if-statements instead of a big logical OR
// because this construction gives much better debugging backtraces.
// written as a series of if-statements instead of a big logical because this
// construction gives much better debugging backtraces.

// also, note that the order of these statements matters, because
// for example some union types are also optional types
// also, note that the order of these statements matters, because for example
// some union types are also optional types

if (isBareConfigurationSchemaType(thing)) {
if (!isType(thing)) {
return false
} else if (isBareConfigurationSchemaType(thing)) {
return true
}

if (isUnionType(thing)) {
} else if (isUnionType(thing)) {
return getUnionSubTypes(thing).every(
t => isConfigurationSchemaType(t) || t.name === 'undefined',
)
}

if (isOptionalType(thing) && isConfigurationSchemaType(getSubType(thing))) {
} else if (
isOptionalType(thing) &&
isConfigurationSchemaType(getSubType(thing))
) {
return true
}

if (isArrayType(thing) && isConfigurationSchemaType(getSubType(thing))) {
} else if (
isArrayType(thing) &&
isConfigurationSchemaType(getSubType(thing))
) {
return true
}

if (isMapType(thing) && isConfigurationSchemaType(getSubType(thing))) {
} else if (isMapType(thing) && isConfigurationSchemaType(getSubType(thing))) {
return true
} else {
return false
}

return false
}

export function isConfigurationModel(
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
},
"dependencies": {
"@babel/runtime": "^7.17.9",
"@floating-ui/react": "^0.27.0",
"@gmod/abortable-promise-cache": "^2.0.0",
"@gmod/bgzf-filehandle": "^1.4.3",
"@gmod/http-range-fetcher": "^3.0.4",
Expand Down
6 changes: 3 additions & 3 deletions packages/core/pluggableElementTypes/PluggableElementBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ export default abstract class PluggableElementBase {
name: string
maybeDisplayName?: string

constructor(args: { name?: string; displayName?: string }) {
this.name = args.name || 'UNKNOWN'
this.maybeDisplayName = args.displayName
constructor(args?: { name?: string; displayName?: string }) {
this.name = args?.name || 'UNKNOWN'
this.maybeDisplayName = args?.displayName
}

get displayName() {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/pluggableElementTypes/RpcMethodType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type RpcMethodConstructor = new (pm: PluginManager) => RpcMethodType

export default abstract class RpcMethodType extends PluggableElementBase {
constructor(public pluginManager: PluginManager) {
super({})
super()
}

async serializeArguments(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import RpcMethodType from './RpcMethodType'
import { renameRegionsIfNeeded } from '../util'
import SerializableFilterChain from './renderers/util/serializableFilterChain'

import type { RenderArgs } from '@jbrowse/core/rpc/coreRpcMethods'

export default abstract class RpcMethodTypeWithFiltersAndRenameRegions extends RpcMethodType {
async deserializeArguments(args: any, rpcDriverClassName: string) {
const l = await super.deserializeArguments(args, rpcDriverClassName)
return {
...l,
filters: args.filters
? new SerializableFilterChain({
filters: args.filters,
})
: undefined,
}
}

async serializeArguments(
args: RenderArgs & {
stopToken?: string
statusCallback?: (arg: string) => void
},
rpcDriverClassName: string,
) {
const pm = this.pluginManager
const assemblyManager = pm.rootModel?.session?.assemblyManager
if (!assemblyManager) {
throw new Error('no assembly manager')
}

const renamedArgs = await renameRegionsIfNeeded(assemblyManager, {
...args,
filters: args.filters?.toJSON().filters,
})

return super.serializeArguments(renamedArgs, rpcDriverClassName)
}
}
90 changes: 11 additions & 79 deletions packages/core/rpc/BaseRpcDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,32 +31,10 @@ function isCloneable(thing: unknown) {
return !(typeof thing === 'function') && !(thing instanceof Error)
}

// watches the given worker object, returns a promise that will be rejected if
// the worker times out
export async function watchWorker(
worker: WorkerHandle,
pingTime: number,
rpcDriverClassName: string,
) {
// after first ping succeeds, apply wait for timeout

// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
while (true) {
await worker.call('ping', [], {
timeout: pingTime * 2,
rpcDriverClassName,
})
await new Promise(resolve => setTimeout(resolve, pingTime))
}
}

function detectHardwareConcurrency() {
const mainThread = typeof window !== 'undefined'
const canDetect = mainThread && 'hardwareConcurrency' in window.navigator
if (mainThread && canDetect) {
return window.navigator.hardwareConcurrency
}
return 1
return mainThread && canDetect ? window.navigator.hardwareConcurrency : 1
}
class LazyWorker {
workerP?: Promise<WorkerHandle> | undefined
Expand All @@ -65,27 +43,10 @@ class LazyWorker {

async getWorker() {
if (!this.workerP) {
this.workerP = this.driver
.makeWorker()
.then(worker => {
watchWorker(worker, this.driver.maxPingTime, this.driver.name).catch(
(error: unknown) => {
console.error(
'worker did not respond, killing and generating new one',
)
console.error(error)
worker.destroy()
worker.status = 'killed'
worker.error = error
this.workerP = undefined
},
)
return worker
})
.catch((e: unknown) => {
this.workerP = undefined
throw e
})
this.workerP = this.driver.makeWorker().catch((e: unknown) => {
this.workerP = undefined
throw e
})
}
return this.workerP
}
Expand Down Expand Up @@ -197,7 +158,6 @@ export default abstract class BaseRpcDriver {
if (!sessionId) {
throw new TypeError('sessionId is required')
}
let done = false
const unextendedWorker = await this.getWorker(sessionId)
const worker = pluginManager.evaluateExtensionPoint(
'Core-extendWorker',
Expand All @@ -211,41 +171,13 @@ export default abstract class BaseRpcDriver {
const filteredAndSerializedArgs = this.filterArgs(serializedArgs, sessionId)

// now actually call the worker
const callP = worker
.call(functionName, filteredAndSerializedArgs, {
timeout: 5 * 60 * 1000, // 5 minutes
statusCallback: args.statusCallback,
rpcDriverClassName: this.name,
...options,
})
.finally(() => {
done = true
})

// check every 5 seconds to see if the worker has been killed, and
// reject the killedP promise if it has
let killedCheckInterval: ReturnType<typeof setInterval>
const killedP = new Promise((resolve, reject) => {
killedCheckInterval = setInterval(() => {
// must've been killed
if (worker.status === 'killed') {
reject(
new Error(
`operation timed out, worker process stopped responding, ${worker.error}`,
),
)
} else if (done) {
resolve(true)
}
}, this.workerCheckFrequency)
}).finally(() => {
clearInterval(killedCheckInterval)
const call = await worker.call(functionName, filteredAndSerializedArgs, {
timeout: 5 * 60 * 1000, // 5 minutes
statusCallback: args.statusCallback,
rpcDriverClassName: this.name,
...options,
})

// the result is a race between the actual result promise, and the "killed"
// promise. the killed promise will only actually win if the worker was
// killed before the call could return
const resultP = Promise.race([callP, killedP])
return rpcMethod.deserializeReturn(resultP, args, this.name)
return rpcMethod.deserializeReturn(call, args, this.name)
}
}
8 changes: 6 additions & 2 deletions packages/core/ui/LoadingEllipses.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,13 @@ export default function LoadingEllipses({
variant = 'body2',
...rest
}: Props) {
const { classes } = useStyles()
const { cx, classes } = useStyles()
return (
<Typography className={classes.dots} {...rest} variant={variant}>
<Typography
className={cx(classes.dots, rest.className)}
{...rest}
variant={variant}
>
{message || 'Loading'}
</Typography>
)
Expand Down
8 changes: 1 addition & 7 deletions packages/product-core/src/rpcWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,7 @@ export async function initializeWorker(
)

// @ts-expect-error
self.rpcServer = new RpcServer.Server({
...rpcConfig,
ping: async () => {
// the ping method is required by the worker driver for checking the
// health of the worker
},
})
self.rpcServer = new RpcServer.Server(rpcConfig)
postMessage({ message: 'ready' })
} catch (e) {
postMessage({ message: 'error', error: serializeError(e) })
Expand Down
6 changes: 3 additions & 3 deletions packages/sv-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@
"useSrc": "node ../../scripts/useSrc.js"
},
"dependencies": {
"@jbrowse/plugin-linear-genome-view": "^2.17.0",
"@gmod/vcf": "^6.0.0",
"@jbrowse/core": "^2.17.0",
"@jbrowse/plugin-linear-genome-view": "^2.17.0",
"@mui/icons-material": "^6.0.0",
"@mui/material": "^6.0.0",
"@gmod/vcf": "^5.0.9"
"@mui/material": "^6.0.0"
},
"peerDependencies": {
"mobx": "^6.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@ import React from 'react'
import { Typography } from '@mui/material'
import { observer } from 'mobx-react'

import type { SortedBy } from '../../shared/types'

const LinearPileupDisplayBlurb = observer(function ({
model,
}: {
model: {
sortedBy?: {
pos: number
refName: number
type: string
tag?: string
}
sortedBy?: SortedBy
}
}) {
const { sortedBy } = model
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export const ExternalTokenEntryForm = ({
open
maxWidth="xl"
data-testid="externalToken-form"
onClose={() => {
handleClose()
}}
title={`Enter token for ${internetAccountId}`}
>
<DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export function HTTPBasicLoginForm({
maxWidth="xl"
data-testid="login-httpbasic"
title={`Log in for ${internetAccountId}`}
onClose={() => {
handleClose()
}}
>
<form
onSubmit={event => {
Expand Down
2 changes: 1 addition & 1 deletion plugins/breakpoint-split-view/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"clean": "rimraf dist esm *.tsbuildinfo"
},
"dependencies": {
"@gmod/vcf": "^5.0.9",
"@gmod/vcf": "^6.0.0",
"@mui/icons-material": "^6.0.0",
"@types/file-saver": "^2.0.1",
"file-saver": "^2.0.0"
Expand Down
Loading