Skip to content

Commit

Permalink
Refactors and typescript improvements for jbrowse-web loader (#4005)
Browse files Browse the repository at this point in the history
  • Loading branch information
cmdcolin committed Oct 20, 2023
1 parent 400b77c commit bb2ad7c
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 89 deletions.
2 changes: 1 addition & 1 deletion packages/core/PluginLoader.ts
Expand Up @@ -111,7 +111,7 @@ export interface LoadedPlugin {
default: PluginConstructor
}

function pluginDescriptionString(pluginDefinition: PluginDefinition) {
export function pluginDescriptionString(pluginDefinition: PluginDefinition) {
if (isUMDPluginDefinition(pluginDefinition)) {
return `UMD plugin ${pluginDefinition.name}`
}
Expand Down
34 changes: 15 additions & 19 deletions products/jbrowse-web/src/SessionLoader.ts
Expand Up @@ -12,6 +12,12 @@ import { nanoid } from '@jbrowse/core/util/nanoid'
import { readSessionFromDynamo } from './sessionSharing'
import { addRelativeUris, checkPlugins, fromUrlSafeB64, readConf } from './util'

export interface SessionTriagedInfo {
snap: unknown
origin: string
reason: PluginDefinition[]
}

const SessionLoader = types
.model({
configPath: types.maybe(types.string),
Expand All @@ -25,18 +31,11 @@ const SessionLoader = types
initialTimestamp: types.number,
})
.volatile(() => ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any,
blankSession: false as any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any,
sessionTriaged: undefined as any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any,
shareWarningOpen: false as any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any,
configSnapshot: undefined as any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any,
sessionSnapshot: undefined as any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any,
sessionSpec: undefined as any,
sessionTriaged: undefined as SessionTriagedInfo | undefined,
configSnapshot: undefined as Record<string, unknown> | undefined,
sessionSnapshot: undefined as Record<string, unknown> | undefined,
sessionSpec: undefined as Record<string, unknown> | undefined,
blankSession: false,
runtimePlugins: [] as PluginRecord[],
sessionPlugins: [] as PluginRecord[],
sessionError: undefined as unknown,
Expand Down Expand Up @@ -114,21 +113,17 @@ const SessionLoader = types
setSessionPlugins(plugins: PluginRecord[]) {
self.sessionPlugins = plugins
},
setConfigSnapshot(snap: unknown) {
setConfigSnapshot(snap: Record<string, unknown>) {
self.configSnapshot = snap
},

setBlankSession(flag: boolean) {
self.blankSession = flag
},
setSessionTriaged(args?: {
snap: unknown
origin: string
reason: { url?: string }[]
}) {
setSessionTriaged(args?: SessionTriagedInfo) {
self.sessionTriaged = args
},
setSessionSnapshotSuccess(snap: unknown) {
setSessionSnapshotSuccess(snap: Record<string, unknown>) {
self.sessionSnapshot = snap
},
}))
Expand Down Expand Up @@ -254,6 +249,7 @@ const SessionLoader = types
async fetchSharedSession() {
const defaultURL = 'https://share.jbrowse.org/api/v1/'
const decryptedSession = await readSessionFromDynamo(
// @ts-expect-error
`${readConf(self.configSnapshot, 'shareURL', defaultURL)}load`,
self.sessionQuery || '',
self.password || '',
Expand Down
25 changes: 12 additions & 13 deletions products/jbrowse-web/src/components/ConfigWarningDialog.tsx
Expand Up @@ -11,6 +11,10 @@ import factoryReset from '../factoryReset'
import { SessionLoaderModel } from '../SessionLoader'

import WarningIcon from '@mui/icons-material/Warning'
import {
PluginDefinition,
pluginDescriptionString,
} from '@jbrowse/core/PluginLoader'

function ConfigWarningDialog({
onConfirm,
Expand All @@ -19,24 +23,18 @@ function ConfigWarningDialog({
}: {
onConfirm: () => void
onCancel: () => void
reason: { url: string }[]
reason: PluginDefinition[]
}) {
return (
<Dialog
open
maxWidth="xl"
data-testid="session-warning-modal"
title="Warning"
aria-labelledby="alert-dialog-title"
>
<Dialog open maxWidth="xl" title="Warning">
<DialogContent>
<WarningIcon fontSize="large" />
<DialogContentText>
This link contains a cross origin config that has the following
unknown plugins:
<ul>
{reason.map(r => (
<li key={JSON.stringify(r)}>URL: {r.url}</li>
<li key={JSON.stringify(r)}>{pluginDescriptionString(r)}</li>
))}
</ul>
Please ensure you trust the source of this link.
Expand Down Expand Up @@ -65,10 +63,11 @@ export default function ConfigTriaged({
loader: SessionLoaderModel
handleClose: () => void
}) {
return (
const { sessionTriaged } = loader
return sessionTriaged ? (
<ConfigWarningDialog
onConfirm={async () => {
const session = JSON.parse(JSON.stringify(loader.sessionTriaged.snap))
const session = JSON.parse(JSON.stringify(sessionTriaged.snap))
await loader.fetchPlugins(session)
loader.setConfigSnapshot({ ...session, id: nanoid() })
handleClose()
Expand All @@ -77,7 +76,7 @@ export default function ConfigTriaged({
await factoryReset()
handleClose()
}}
reason={loader.sessionTriaged.reason}
reason={sessionTriaged.reason}
/>
)
) : null
}
98 changes: 59 additions & 39 deletions products/jbrowse-web/src/components/Loader.tsx
Expand Up @@ -14,13 +14,16 @@ import '@fontsource/roboto'
import Loading from './Loading'
import JBrowse from './JBrowse'
import factoryReset from '../factoryReset'
import SessionLoader, { SessionLoaderModel } from '../SessionLoader'
import SessionLoader, {
SessionLoaderModel,
SessionTriagedInfo,
} from '../SessionLoader'
import StartScreenErrorMessage from './StartScreenErrorMessage'
import PluginManager from '@jbrowse/core/PluginManager'
import { createPluginManager } from '../createPluginManager'

const ConfigTriaged = lazy(() => import('./ConfigWarningDialog'))
const SessionTriaged = lazy(() => import('./SessionWarningDialog'))
const ConfigWarningDialog = lazy(() => import('./ConfigWarningDialog'))
const SessionWarningDialog = lazy(() => import('./SessionWarningDialog'))
const StartScreen = lazy(() => import('./StartScreen'))

export function Loader({
Expand Down Expand Up @@ -67,61 +70,78 @@ export function Loader({
return <Renderer loader={loader} />
}

const SessionTriaged = observer(function ({
sessionTriaged,
loader,
}: {
loader: SessionLoaderModel
sessionTriaged: SessionTriagedInfo
}) {
return (
<Suspense fallback={<React.Fragment />}>
{sessionTriaged?.origin === 'session' ? (
<SessionWarningDialog
loader={loader}
handleClose={() => loader.setSessionTriaged(undefined)}
/>
) : (
<ConfigWarningDialog
loader={loader}
handleClose={() => loader.setSessionTriaged(undefined)}
/>
)}
</Suspense>
)
})

const PluginManagerLoaded = observer(function ({
pluginManager,
}: {
pluginManager: PluginManager
}) {
const { rootModel } = pluginManager
return !rootModel?.session ? (
<Suspense fallback={<LoadingEllipses />}>
<StartScreen rootModel={rootModel} onFactoryReset={factoryReset} />
</Suspense>
) : (
<JBrowse pluginManager={pluginManager} />
)
})

const Renderer = observer(function ({
loader,
}: {
loader: SessionLoaderModel
}) {
const { configError, ready, shareWarningOpen } = loader
const { configError, ready, sessionTriaged } = loader
const [pluginManager, setPluginManager] = useState<PluginManager>()
const [error, setError] = useState<unknown>()

useEffect(() => {
let pm: PluginManager | undefined
try {
if (!ready || shareWarningOpen) {
if (!ready) {
return
}
const pm = createPluginManager(loader)
pm = createPluginManager(loader)
setPluginManager(pm)
} catch (e) {
console.error(e)
setError(e)
}
}, [loader, ready, shareWarningOpen])
if (configError || error) {
return <StartScreenErrorMessage error={configError || error} />
}
}, [loader, ready])

if (loader.sessionTriaged) {
return (
<Suspense fallback={<React.Fragment />}>
{loader.sessionTriaged.origin === 'session' ? (
<SessionTriaged
loader={loader}
handleClose={() => loader.setSessionTriaged(undefined)}
/>
) : (
<ConfigTriaged
loader={loader}
handleClose={() => loader.setSessionTriaged(undefined)}
/>
)}
</Suspense>
)
}
if (pluginManager) {
return !pluginManager.rootModel?.session ? (
<Suspense fallback={<LoadingEllipses />}>
<StartScreen
rootModel={pluginManager.rootModel}
onFactoryReset={factoryReset}
/>
</Suspense>
) : (
<JBrowse pluginManager={pluginManager} />
)
const err = configError || error
if (err) {
return <StartScreenErrorMessage error={err} />
} else if (sessionTriaged) {
return <SessionTriaged loader={loader} sessionTriaged={sessionTriaged} />
} else if (pluginManager) {
return <PluginManagerLoaded pluginManager={pluginManager} />
} else {
return <Loading />
}
return <Loading />
})

const LoaderWrapper = ({ initialTimestamp }: { initialTimestamp: number }) => {
Expand Down
24 changes: 12 additions & 12 deletions products/jbrowse-web/src/components/SessionWarningDialog.tsx
Expand Up @@ -10,6 +10,10 @@ import { nanoid } from '@jbrowse/core/util/nanoid'
import { SessionLoaderModel } from '../SessionLoader'

import WarningIcon from '@mui/icons-material/Warning'
import {
PluginDefinition,
pluginDescriptionString,
} from '@jbrowse/core/PluginLoader'

function SessionWarningDialog({
onConfirm,
Expand All @@ -18,22 +22,17 @@ function SessionWarningDialog({
}: {
onConfirm: () => void
onCancel: () => void
reason: { url: string }[]
reason: PluginDefinition[]
}) {
return (
<Dialog
open
maxWidth="xl"
data-testid="session-warning-modal"
title="Warning"
>
<Dialog open maxWidth="xl" title="Warning">
<DialogContent>
<WarningIcon fontSize="large" />
<DialogContentText>
This link contains a session that has the following unknown plugins:
<ul>
{reason.map(r => (
<li key={JSON.stringify(r)}>URL: {r.url}</li>
<li key={JSON.stringify(r)}>{pluginDescriptionString(r)}</li>
))}
</ul>
Please ensure you trust the source of this session.
Expand Down Expand Up @@ -62,10 +61,11 @@ export default function SessionTriaged({
loader: SessionLoaderModel
handleClose: () => void
}) {
return (
const { sessionTriaged } = loader
return sessionTriaged ? (
<SessionWarningDialog
onConfirm={async () => {
const session = JSON.parse(JSON.stringify(loader.sessionTriaged.snap))
const session = JSON.parse(JSON.stringify(sessionTriaged.snap))

// second param true says we passed user confirmation
await loader.setSessionSnapshot({ ...session, id: nanoid() }, true)
Expand All @@ -75,7 +75,7 @@ export default function SessionTriaged({
loader.setBlankSession(true)
handleClose()
}}
reason={loader.sessionTriaged.reason}
reason={sessionTriaged.reason}
/>
)
) : null
}
2 changes: 2 additions & 0 deletions products/jbrowse-web/src/createPluginManager.ts
Expand Up @@ -45,6 +45,7 @@ export function createPluginManager(self: SessionLoaderModel) {
{ pluginManager },
)

// @ts-expect-error
if (!self.configSnapshot?.configuration?.rpc?.defaultDriver) {
const { rpc } = rootModel.jbrowse.configuration
rpc.defaultDriver.set('WebWorkerRpcDriver')
Expand All @@ -67,6 +68,7 @@ export function createPluginManager(self: SessionLoaderModel) {
} else if (self.sessionSnapshot) {
rootModel.setSession(self.sessionSnapshot)
} else if (self.sessionSpec) {
// @ts-expect-error
afterInitializedCb = loadSessionSpec(self.sessionSpec, pluginManager)
} else if (rootModel.jbrowse.defaultSession?.views?.length) {
rootModel.setDefaultSession()
Expand Down
Expand Up @@ -9,7 +9,7 @@ beforeEach(() => {
doBeforeEach()
})

const delay = { timeout: 10000 }
const delay = { timeout: 20000 }

test('nav to volvox2', async () => {
const { getInputValue, findByText } = await doSetupForImportForm()
Expand All @@ -31,5 +31,5 @@ test('select misc', async () => {
const { getInputValue, findByText } = await doSetupForImportForm()
fireEvent.mouseDown(await findByText('volvox'))
fireEvent.click(await findByText('misc'))
await waitFor(() => expect(getInputValue()).toBe('t1'))
await waitFor(() => expect(getInputValue()).toBe('t1'), delay)
}, 30000)
4 changes: 2 additions & 2 deletions products/jbrowse-web/src/tests/Loader.test.tsx
Expand Up @@ -124,10 +124,10 @@ test('approves sessionPlugins from plugin list', async () => {
// minimal session,
// {"session":{"id":"xSHu7qGJN","name":"test","sessionPlugins":[{"url":"https://unpkg.com/jbrowse-plugin-msaview/dist/jbrowse-plugin-msaview.umd.production.min.js"}]}}
test('pops up a warning for evil plugin in sessionPlugins', async () => {
const { findByTestId } = render(
const { findByText } = render(
<App search='?config=test_data/volvox/config_main_thread.json&session=json-{"session":{"id":"xSHu7qGJN","name":"test","sessionPlugins":[{"url":"https://evil.com/evil.js"}]}}' />,
)
await findByTestId('session-warning-modal')
await findByText(/Warning/, {}, delay)
}, 20000)

test('can use config from a url with nonexistent share param ', async () => {
Expand Down
2 changes: 1 addition & 1 deletion products/jbrowse-web/src/util.ts
Expand Up @@ -142,7 +142,7 @@ export function addRelativeUris(config: Config, base: URL) {
}
}

interface Root {
export interface Root {
configuration?: Config
}

Expand Down

0 comments on commit bb2ad7c

Please sign in to comment.