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

Refactors and typescript improvements for jbrowse-web loader #4005

Merged
merged 5 commits into from
Oct 20, 2023
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
2 changes: 1 addition & 1 deletion packages/core/PluginLoader.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export function addRelativeUris(config: Config, base: URL) {
}
}

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

Expand Down