jbrowse-plugin-msaview 2.8.2 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastRIDPanel.d.ts → BlastQuery/BlastAutomaticPanel.d.ts} +2 -3
- package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastAutomaticPanel.js → BlastQuery/BlastAutomaticPanel.js} +6 -25
- package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastManualPanel.d.ts → BlastQuery/BlastManualPanel.d.ts} +2 -3
- package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastManualPanel.js → BlastQuery/BlastManualPanel.js} +7 -4
- package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastMethodSelector.d.ts → BlastQuery/BlastMethodSelector.d.ts} +2 -2
- package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastMethodSelector.js → BlastQuery/BlastMethodSelector.js} +1 -2
- package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastPanel.d.ts → BlastQuery/BlastPanel.d.ts} +3 -12
- package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastPanel.js → BlastQuery/BlastPanel.js} +14 -16
- package/dist/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.d.ts +8 -0
- package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBISettingsDialog.js → BlastQuery/BlastSettingsDialog.js} +13 -9
- package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/CachedBlastResults.js +1 -1
- package/dist/LaunchMsaView/components/BlastQuery/consts.d.ts +12 -0
- package/dist/LaunchMsaView/components/BlastQuery/consts.js +17 -0
- package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js +3 -3
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +1 -1
- package/dist/MsaViewPanel/components/JobLink.d.ts +5 -0
- package/dist/MsaViewPanel/components/JobLink.js +13 -0
- package/dist/MsaViewPanel/components/LoadingBLAST.d.ts +1 -2
- package/dist/MsaViewPanel/components/LoadingBLAST.js +8 -15
- package/dist/MsaViewPanel/components/MsaViewPanel.js +1 -1
- package/dist/MsaViewPanel/doLaunchBlast.js +11 -19
- package/dist/MsaViewPanel/doLaunchOrthologs.test.d.ts +1 -0
- package/dist/MsaViewPanel/doLaunchOrthologs.test.js +127 -0
- package/dist/MsaViewPanel/model.d.ts +1 -4
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +30 -30
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/dist/utils/blastCache.d.ts +8 -4
- package/dist/utils/blastCache.js +4 -5
- package/dist/utils/ebiBlast.d.ts +52 -0
- package/dist/utils/ebiBlast.js +63 -0
- package/dist/utils/ebiJobDispatcher.d.ts +33 -0
- package/dist/utils/ebiJobDispatcher.js +55 -0
- package/dist/utils/fetch.js +24 -1
- package/dist/utils/msa.d.ts +1 -1
- package/dist/utils/msa.js +25 -32
- package/dist/utils/types.d.ts +9 -14
- package/dist/utils/useLocalStorage.d.ts +1 -0
- package/dist/utils/useLocalStorage.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastAutomaticPanel.tsx → BlastQuery/BlastAutomaticPanel.tsx} +13 -59
- package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastManualPanel.tsx → BlastQuery/BlastManualPanel.tsx} +7 -6
- package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastMethodSelector.tsx → BlastQuery/BlastMethodSelector.tsx} +2 -7
- package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastPanel.tsx → BlastQuery/BlastPanel.tsx} +21 -25
- package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBISettingsDialog.tsx → BlastQuery/BlastSettingsDialog.tsx} +26 -13
- package/src/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/CachedBlastResults.tsx +1 -1
- package/src/LaunchMsaView/components/BlastQuery/consts.ts +22 -0
- package/src/LaunchMsaView/components/LaunchMsaViewDialog.tsx +5 -7
- package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +2 -2
- package/src/MsaViewPanel/components/JobLink.tsx +17 -0
- package/src/MsaViewPanel/components/LoadingBLAST.tsx +10 -41
- package/src/MsaViewPanel/components/MsaViewPanel.tsx +1 -1
- package/src/MsaViewPanel/doLaunchBlast.ts +12 -28
- package/src/MsaViewPanel/doLaunchOrthologs.test.ts +159 -0
- package/src/MsaViewPanel/model.ts +1 -5
- package/src/utils/blastCache.ts +8 -9
- package/src/utils/ebiBlast.ts +114 -0
- package/src/utils/ebiJobDispatcher.ts +91 -0
- package/src/utils/fetch.ts +26 -1
- package/src/utils/msa.ts +26 -47
- package/src/utils/types.ts +8 -13
- package/src/utils/useLocalStorage.ts +1 -1
- package/src/version.ts +1 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.d.ts +0 -10
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastRIDPanel.js +0 -74
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBISettingsDialog.d.ts +0 -5
- package/dist/LaunchMsaView/components/NCBIBlastQuery/consts.d.ts +0 -7
- package/dist/LaunchMsaView/components/NCBIBlastQuery/consts.js +0 -4
- package/dist/MsaViewPanel/components/RIDLink.d.ts +0 -6
- package/dist/MsaViewPanel/components/RIDLink.js +0 -12
- package/dist/utils/ncbiBlast.d.ts +0 -30
- package/dist/utils/ncbiBlast.js +0 -84
- package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastRIDPanel.tsx +0 -137
- package/src/LaunchMsaView/components/NCBIBlastQuery/consts.ts +0 -10
- package/src/MsaViewPanel/components/RIDLink.tsx +0 -19
- package/src/utils/ncbiBlast.ts +0 -143
- /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/CachedBlastResults.d.ts +0 -0
- /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/MsaAlgorithmSelect.d.ts +0 -0
- /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/MsaAlgorithmSelect.js +0 -0
- /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/blastLaunchView.d.ts +0 -0
- /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/blastLaunchView.js +0 -0
- /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/useCachedBlastResults.d.ts +0 -0
- /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/useCachedBlastResults.js +0 -0
- /package/src/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/MsaAlgorithmSelect.tsx +0 -0
- /package/src/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/blastLaunchView.ts +0 -0
- /package/src/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/useCachedBlastResults.ts +0 -0
package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastPanel.tsx → BlastQuery/BlastPanel.tsx}
RENAMED
|
@@ -4,12 +4,14 @@ import SettingsIcon from '@mui/icons-material/Settings'
|
|
|
4
4
|
import { IconButton } from '@mui/material'
|
|
5
5
|
import { makeStyles } from 'tss-react/mui'
|
|
6
6
|
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
|
|
7
|
+
import BlastAutomaticPanel from './BlastAutomaticPanel'
|
|
8
|
+
import BlastManualPanel from './BlastManualPanel'
|
|
9
|
+
import BlastMethodSelector from './BlastMethodSelector'
|
|
10
|
+
import BlastSettingsDialog from './BlastSettingsDialog'
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_EBI_EMAIL,
|
|
13
|
+
EBI_EMAIL_STORAGE_KEY,
|
|
14
|
+
} from '../../../utils/ebiJobDispatcher'
|
|
13
15
|
import { useLocalStorage } from '../../../utils/useLocalStorage'
|
|
14
16
|
|
|
15
17
|
import type { AbstractTrackModel, Feature } from '@jbrowse/core/util'
|
|
@@ -21,14 +23,13 @@ const useStyles = makeStyles()({
|
|
|
21
23
|
})
|
|
22
24
|
|
|
23
25
|
const panelMap = {
|
|
24
|
-
automatic:
|
|
25
|
-
|
|
26
|
-
manual: NCBIBlastManualPanel,
|
|
26
|
+
automatic: BlastAutomaticPanel,
|
|
27
|
+
manual: BlastManualPanel,
|
|
27
28
|
} as const
|
|
28
29
|
|
|
29
30
|
export type BlastLookupMethod = keyof typeof panelMap
|
|
30
31
|
|
|
31
|
-
export default function
|
|
32
|
+
export default function BlastPanel({
|
|
32
33
|
handleClose,
|
|
33
34
|
model,
|
|
34
35
|
feature,
|
|
@@ -39,9 +40,9 @@ export default function NCBIBlastPanel({
|
|
|
39
40
|
}) {
|
|
40
41
|
const [lookupMethod, setLookupMethod] =
|
|
41
42
|
useState<BlastLookupMethod>('automatic')
|
|
42
|
-
const [
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
const [ebiEmail, setEbiEmail] = useLocalStorage(
|
|
44
|
+
EBI_EMAIL_STORAGE_KEY,
|
|
45
|
+
DEFAULT_EBI_EMAIL,
|
|
45
46
|
)
|
|
46
47
|
const [settingsOpen, setSettingsOpen] = useState(false)
|
|
47
48
|
const { classes } = useStyles()
|
|
@@ -60,24 +61,19 @@ export default function NCBIBlastPanel({
|
|
|
60
61
|
<SettingsIcon />
|
|
61
62
|
</IconButton>
|
|
62
63
|
|
|
63
|
-
<Panel
|
|
64
|
-
|
|
65
|
-
feature={feature}
|
|
66
|
-
handleClose={handleClose}
|
|
67
|
-
baseUrl={baseUrl}
|
|
68
|
-
>
|
|
69
|
-
<NCBIBlastMethodSelector
|
|
64
|
+
<Panel model={model} feature={feature} handleClose={handleClose}>
|
|
65
|
+
<BlastMethodSelector
|
|
70
66
|
lookupMethod={lookupMethod}
|
|
71
67
|
setLookupMethod={setLookupMethod}
|
|
72
68
|
/>
|
|
73
69
|
</Panel>
|
|
74
70
|
|
|
75
71
|
{settingsOpen ? (
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
handleClose={
|
|
79
|
-
if (
|
|
80
|
-
|
|
72
|
+
<BlastSettingsDialog
|
|
73
|
+
ebiEmail={ebiEmail}
|
|
74
|
+
handleClose={settings => {
|
|
75
|
+
if (settings) {
|
|
76
|
+
setEbiEmail(settings.ebiEmail)
|
|
81
77
|
}
|
|
82
78
|
setSettingsOpen(false)
|
|
83
79
|
}}
|
|
@@ -6,27 +6,35 @@ import {
|
|
|
6
6
|
DialogActions,
|
|
7
7
|
DialogContent,
|
|
8
8
|
DialogTitle,
|
|
9
|
+
Typography,
|
|
9
10
|
} from '@mui/material'
|
|
10
11
|
import { makeStyles } from 'tss-react/mui'
|
|
11
12
|
|
|
12
|
-
import { BASE_BLAST_URL } from './consts'
|
|
13
13
|
import TextField2 from '../../../components/TextField2'
|
|
14
|
+
import { DEFAULT_EBI_EMAIL } from '../../../utils/ebiJobDispatcher'
|
|
14
15
|
|
|
15
16
|
const useStyles = makeStyles()({
|
|
16
|
-
|
|
17
|
+
field: {
|
|
17
18
|
minWidth: 300,
|
|
18
19
|
},
|
|
20
|
+
help: {
|
|
21
|
+
marginBottom: 8,
|
|
22
|
+
},
|
|
19
23
|
})
|
|
20
24
|
|
|
21
|
-
export
|
|
25
|
+
export interface BlastSettings {
|
|
26
|
+
ebiEmail: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default function BlastSettingsDialog({
|
|
22
30
|
handleClose,
|
|
23
|
-
|
|
31
|
+
ebiEmail,
|
|
24
32
|
}: {
|
|
25
|
-
handleClose: (arg?:
|
|
26
|
-
|
|
33
|
+
handleClose: (arg?: BlastSettings) => void
|
|
34
|
+
ebiEmail: string
|
|
27
35
|
}) {
|
|
28
36
|
const { classes } = useStyles()
|
|
29
|
-
const [
|
|
37
|
+
const [tempEbiEmail, setTempEbiEmail] = useState(ebiEmail)
|
|
30
38
|
return (
|
|
31
39
|
<Dialog
|
|
32
40
|
open
|
|
@@ -37,22 +45,27 @@ export default function NCBISettingsDialog({
|
|
|
37
45
|
>
|
|
38
46
|
<DialogTitle>BLAST Settings</DialogTitle>
|
|
39
47
|
<DialogContent>
|
|
48
|
+
<Typography variant="subtitle2" className={classes.help}>
|
|
49
|
+
Searches run at EBI, which asks for a contact address on every job so
|
|
50
|
+
they can reach whoever is generating the load. If your site sends real
|
|
51
|
+
volume, use your own.
|
|
52
|
+
</Typography>
|
|
40
53
|
<TextField2
|
|
41
54
|
autoFocus
|
|
42
55
|
margin="dense"
|
|
43
|
-
label="
|
|
56
|
+
label="EBI contact email"
|
|
44
57
|
fullWidth
|
|
45
58
|
variant="outlined"
|
|
46
|
-
value={
|
|
47
|
-
className={classes.
|
|
59
|
+
value={tempEbiEmail}
|
|
60
|
+
className={classes.field}
|
|
48
61
|
onChange={e => {
|
|
49
|
-
|
|
62
|
+
setTempEbiEmail(e.target.value)
|
|
50
63
|
}}
|
|
51
64
|
/>
|
|
52
65
|
<Button
|
|
53
66
|
variant="contained"
|
|
54
67
|
onClick={() => {
|
|
55
|
-
|
|
68
|
+
setTempEbiEmail(DEFAULT_EBI_EMAIL)
|
|
56
69
|
}}
|
|
57
70
|
>
|
|
58
71
|
Reset
|
|
@@ -72,7 +85,7 @@ export default function NCBISettingsDialog({
|
|
|
72
85
|
color="primary"
|
|
73
86
|
variant="contained"
|
|
74
87
|
onClick={() => {
|
|
75
|
-
handleClose(
|
|
88
|
+
handleClose({ ebiEmail: tempEbiEmail })
|
|
76
89
|
}}
|
|
77
90
|
>
|
|
78
91
|
Save
|
|
@@ -149,7 +149,7 @@ const CachedBlastResults = observer(function ({
|
|
|
149
149
|
}}
|
|
150
150
|
>
|
|
151
151
|
<ListItemText
|
|
152
|
-
primary={`${getResultDisplayName(result)} - ${result.blastDatabase}
|
|
152
|
+
primary={`${getResultDisplayName(result)} - ${result.blastDatabase}${result.blastProgram ? `/${result.blastProgram}` : ''} (${result.msaAlgorithm})`}
|
|
153
153
|
secondary={`${new Date(result.timestamp).toLocaleString()} - Seq: ${result.proteinSequence.slice(0, 30)}...`}
|
|
154
154
|
/>
|
|
155
155
|
</ListItemButton>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Only used to build the link-out on the manual panel, which sends the user to
|
|
3
|
+
* NCBI's own site to run BLAST there. Nothing fetches this url: NCBI stopped
|
|
4
|
+
* sending Access-Control-Allow-Origin to third-party origins, so a browser
|
|
5
|
+
* cannot read a response from it at all. See docs/blast.md.
|
|
6
|
+
*/
|
|
7
|
+
export const BASE_BLAST_URL = 'https://blast.ncbi.nlm.nih.gov/Blast.cgi'
|
|
8
|
+
|
|
9
|
+
export const msaAlgorithms = ['clustalo', 'muscle', 'kalign', 'mafft'] as const
|
|
10
|
+
export type MsaAlgorithm = (typeof msaAlgorithms)[number]
|
|
11
|
+
|
|
12
|
+
export const blastDatabaseOptions = [
|
|
13
|
+
'uniprotkb_swissprot',
|
|
14
|
+
'uniprotkb',
|
|
15
|
+
'uniprotkb_reference_proteomes',
|
|
16
|
+
'uniprotkb_trembl',
|
|
17
|
+
] as const
|
|
18
|
+
export type BlastDatabase = (typeof blastDatabaseOptions)[number]
|
|
19
|
+
|
|
20
|
+
// curated, so it returns roughly one good sequence per species rather than the
|
|
21
|
+
// many near-identical TrEMBL entries an alignment reads poorly
|
|
22
|
+
export const defaultBlastDatabase: BlastDatabase = 'uniprotkb_swissprot'
|
|
@@ -4,8 +4,8 @@ import { Dialog } from '@jbrowse/core/ui'
|
|
|
4
4
|
import { getSession } from '@jbrowse/core/util'
|
|
5
5
|
import { Tab, Tabs } from '@mui/material'
|
|
6
6
|
|
|
7
|
+
import BlastPanel from './BlastQuery/BlastPanel'
|
|
7
8
|
import ManualMSALoader from './ManualMSALoader/ManualMSALoader'
|
|
8
|
-
import NCBIBlastPanel from './NCBIBlastQuery/NCBIBlastPanel'
|
|
9
9
|
import OrthologPanel from './OrthologQuery/OrthologPanel'
|
|
10
10
|
import PreLoadedMSA from './PreLoadedMSA/PreLoadedMSADataPanel'
|
|
11
11
|
import { readMsaDatasets } from './PreLoadedMSA/types'
|
|
@@ -41,7 +41,9 @@ export default function LaunchMsaViewDialog({
|
|
|
41
41
|
}}
|
|
42
42
|
>
|
|
43
43
|
<Tab label="Orthologs (fast)" value="orthologs" />
|
|
44
|
-
|
|
44
|
+
{/* the tab value stays 'ncbi_blast' — it is only local state, and
|
|
45
|
+
renaming it buys nothing */}
|
|
46
|
+
<Tab label="BLAST query" value="ncbi_blast" />
|
|
45
47
|
{hasPreloadedDatasets ? (
|
|
46
48
|
<Tab label="Pre-loaded MSA datasets" value="preloaded_msa" />
|
|
47
49
|
) : null}
|
|
@@ -55,11 +57,7 @@ export default function LaunchMsaViewDialog({
|
|
|
55
57
|
/>
|
|
56
58
|
</TabPanel>
|
|
57
59
|
<TabPanel value={value} index="ncbi_blast">
|
|
58
|
-
<
|
|
59
|
-
handleClose={handleClose}
|
|
60
|
-
feature={feature}
|
|
61
|
-
model={model}
|
|
62
|
-
/>
|
|
60
|
+
<BlastPanel handleClose={handleClose} feature={feature} model={model} />
|
|
63
61
|
</TabPanel>
|
|
64
62
|
{hasPreloadedDatasets ? (
|
|
65
63
|
<TabPanel value={value} index="preloaded_msa">
|
|
@@ -13,13 +13,13 @@ import {
|
|
|
13
13
|
getLinearGenomeView,
|
|
14
14
|
getTranscriptDisplayName,
|
|
15
15
|
} from '../../util'
|
|
16
|
+
import MsaAlgorithmSelect from '../BlastQuery/MsaAlgorithmSelect'
|
|
16
17
|
import LaunchPanelContent from '../LaunchPanelContent'
|
|
17
|
-
import MsaAlgorithmSelect from '../NCBIBlastQuery/MsaAlgorithmSelect'
|
|
18
18
|
import SubmitCancelActions from '../SubmitCancelActions'
|
|
19
19
|
import TranscriptSelector from '../TranscriptSelector'
|
|
20
20
|
import { useTranscriptSelection } from '../useTranscriptSelection'
|
|
21
21
|
|
|
22
|
-
import type { MsaAlgorithm } from '../
|
|
22
|
+
import type { MsaAlgorithm } from '../BlastQuery/consts'
|
|
23
23
|
import type { AbstractTrackModel, Feature } from '@jbrowse/core/util'
|
|
24
24
|
|
|
25
25
|
const useStyles = makeStyles()({
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { Typography } from '@mui/material'
|
|
4
|
+
|
|
5
|
+
import ExternalLink from '../../components/ExternalLink'
|
|
6
|
+
import { ebiBlastResultUrl } from '../../utils/ebiBlast'
|
|
7
|
+
|
|
8
|
+
function JobLink({ jobId }: { jobId: string }) {
|
|
9
|
+
return (
|
|
10
|
+
<Typography>
|
|
11
|
+
Job {jobId} (
|
|
12
|
+
<ExternalLink href={ebiBlastResultUrl(jobId)}>see status</ExternalLink>)
|
|
13
|
+
</Typography>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default JobLink
|
|
@@ -5,7 +5,7 @@ import { Typography } from '@mui/material'
|
|
|
5
5
|
import { observer } from 'mobx-react'
|
|
6
6
|
import { makeStyles } from 'tss-react/mui'
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import JobLink from './JobLink'
|
|
9
9
|
|
|
10
10
|
import type { JBrowsePluginMsaViewModel } from '../model'
|
|
11
11
|
|
|
@@ -18,57 +18,26 @@ const useStyles = makeStyles()(theme => ({
|
|
|
18
18
|
},
|
|
19
19
|
}))
|
|
20
20
|
|
|
21
|
-
function RIDError({
|
|
22
|
-
baseUrl,
|
|
23
|
-
rid,
|
|
24
|
-
error,
|
|
25
|
-
}: {
|
|
26
|
-
baseUrl: string
|
|
27
|
-
rid?: string
|
|
28
|
-
error: unknown
|
|
29
|
-
}) {
|
|
30
|
-
return (
|
|
31
|
-
<div>
|
|
32
|
-
{rid ? <RIDLink rid={rid} baseUrl={baseUrl} /> : null}
|
|
33
|
-
<ErrorMessage error={error} />
|
|
34
|
-
</div>
|
|
35
|
-
)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function RIDProgress({
|
|
39
|
-
baseUrl,
|
|
40
|
-
rid,
|
|
41
|
-
progress,
|
|
42
|
-
}: {
|
|
43
|
-
baseUrl: string
|
|
44
|
-
rid: string
|
|
45
|
-
progress: string
|
|
46
|
-
}) {
|
|
47
|
-
const { classes } = useStyles()
|
|
48
|
-
return (
|
|
49
|
-
<div className={classes.loading}>
|
|
50
|
-
{rid ? <RIDLink baseUrl={baseUrl} rid={rid} /> : null}
|
|
51
|
-
<Typography>{progress}</Typography>
|
|
52
|
-
</div>
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
21
|
const LoadingBLAST = observer(function LoadingBLAST2({
|
|
57
22
|
model,
|
|
58
|
-
baseUrl,
|
|
59
23
|
}: {
|
|
60
24
|
model: JBrowsePluginMsaViewModel
|
|
61
|
-
baseUrl: string
|
|
62
25
|
}) {
|
|
63
26
|
const { progress, rid, error } = model
|
|
64
27
|
const { classes } = useStyles()
|
|
65
28
|
return (
|
|
66
29
|
<div className={classes.margin}>
|
|
67
|
-
<LoadingEllipses message="Running
|
|
30
|
+
<LoadingEllipses message="Running EBI BLAST" variant="h5" />
|
|
68
31
|
{error ? (
|
|
69
|
-
<
|
|
32
|
+
<div>
|
|
33
|
+
{rid ? <JobLink jobId={rid} /> : null}
|
|
34
|
+
<ErrorMessage error={error} />
|
|
35
|
+
</div>
|
|
70
36
|
) : rid ? (
|
|
71
|
-
<
|
|
37
|
+
<div className={classes.loading}>
|
|
38
|
+
<JobLink jobId={rid} />
|
|
39
|
+
<Typography>{progress}</Typography>
|
|
40
|
+
</div>
|
|
72
41
|
) : (
|
|
73
42
|
<Typography>{progress || 'Initializing BLAST query'}</Typography>
|
|
74
43
|
)}
|
|
@@ -27,7 +27,7 @@ const MsaViewPanel = observer(function MsaViewPanel2({
|
|
|
27
27
|
<ErrorBoundary>
|
|
28
28
|
<div>
|
|
29
29
|
{blastParams ? (
|
|
30
|
-
<LoadingBLAST model={model}
|
|
30
|
+
<LoadingBLAST model={model} />
|
|
31
31
|
) : loadingStoredData ? (
|
|
32
32
|
<div className={classes.loadingContainer}>
|
|
33
33
|
<LoadingEllipses message="Loading MSA data" variant="h6" />
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { makeId, strip } from '../LaunchMsaView/components/util'
|
|
2
2
|
import { cleanProteinSequence } from '../LaunchMsaView/util'
|
|
3
3
|
import { saveBlastResult } from '../utils/blastCache'
|
|
4
|
+
import { queryEbiBlast } from '../utils/ebiBlast'
|
|
4
5
|
import { launchMSA } from '../utils/msa'
|
|
5
|
-
import { queryBlast, queryBlastFromRid } from '../utils/ncbiBlast'
|
|
6
6
|
import { fetchTaxonomyInfo } from '../utils/taxonomyNames'
|
|
7
7
|
|
|
8
8
|
import type { JBrowsePluginMsaViewModel } from './model'
|
|
@@ -14,39 +14,24 @@ export async function doLaunchBlast({
|
|
|
14
14
|
}: {
|
|
15
15
|
self: JBrowsePluginMsaViewModel
|
|
16
16
|
}) {
|
|
17
|
-
const {
|
|
18
|
-
|
|
19
|
-
blastDatabase,
|
|
20
|
-
blastProgram,
|
|
21
|
-
msaAlgorithm,
|
|
22
|
-
proteinSequence,
|
|
23
|
-
selectedTranscript,
|
|
24
|
-
rid: existingRid,
|
|
25
|
-
} = self.blastParams!
|
|
17
|
+
const { blastDatabase, msaAlgorithm, proteinSequence, selectedTranscript } =
|
|
18
|
+
self.blastParams!
|
|
26
19
|
const cleanedSeq = cleanProteinSequence(proteinSequence)
|
|
27
20
|
|
|
28
21
|
const onProgress = (arg: string) => {
|
|
29
22
|
self.setProgress(arg)
|
|
30
23
|
}
|
|
31
24
|
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
const { hits, rid } = await queryEbiBlast({
|
|
26
|
+
query: cleanedSeq,
|
|
27
|
+
blastDatabase,
|
|
28
|
+
onProgress,
|
|
29
|
+
// publish the job id before the first poll so the view can link out while
|
|
34
30
|
// the job is still running
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
? await queryBlastFromRid({ rid: existingRid, baseUrl, onProgress })
|
|
40
|
-
: await queryBlast({
|
|
41
|
-
query: cleanedSeq,
|
|
42
|
-
blastDatabase,
|
|
43
|
-
blastProgram,
|
|
44
|
-
baseUrl,
|
|
45
|
-
onProgress,
|
|
46
|
-
onRid: r => {
|
|
47
|
-
self.setRid(r)
|
|
48
|
-
},
|
|
49
|
-
})
|
|
31
|
+
onRid: r => {
|
|
32
|
+
self.setRid(r)
|
|
33
|
+
},
|
|
34
|
+
})
|
|
50
35
|
|
|
51
36
|
self.setProgress('Fetching species taxonomy info...')
|
|
52
37
|
const taxids = hits
|
|
@@ -81,7 +66,6 @@ export async function doLaunchBlast({
|
|
|
81
66
|
await saveBlastResult({
|
|
82
67
|
proteinSequence: cleanedSeq,
|
|
83
68
|
blastDatabase,
|
|
84
|
-
blastProgram,
|
|
85
69
|
msaAlgorithm,
|
|
86
70
|
msa: result.msa,
|
|
87
71
|
tree: result.tree,
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { doLaunchOrthologs } from './doLaunchOrthologs'
|
|
4
|
+
import { launchMSA } from '../utils/msa'
|
|
5
|
+
import {
|
|
6
|
+
COMMON_SPECIES,
|
|
7
|
+
fetchOrthologRows,
|
|
8
|
+
fetchProteinForGene,
|
|
9
|
+
resolveGeneId,
|
|
10
|
+
} from '../utils/ncbiOrthologs'
|
|
11
|
+
|
|
12
|
+
import type { JBrowsePluginMsaViewModel } from './model'
|
|
13
|
+
import type { OrthologRow } from '../utils/ncbiOrthologs'
|
|
14
|
+
|
|
15
|
+
// Every network call is mocked and nothing else is. What is under test is the
|
|
16
|
+
// argument shaping either side of those calls -- which taxa get asked for, what
|
|
17
|
+
// becomes the QUERY row, and whether the row earns the Accession that drives the
|
|
18
|
+
// CDD overlay -- so the real COMMON_SPECIES list and the real
|
|
19
|
+
// cleanProteinSequence stay in the picture.
|
|
20
|
+
vi.mock('../utils/ncbiOrthologs', async importOriginal => ({
|
|
21
|
+
...(await importOriginal<Record<string, unknown>>()),
|
|
22
|
+
resolveGeneId: vi.fn(),
|
|
23
|
+
fetchProteinForGene: vi.fn(),
|
|
24
|
+
fetchOrthologRows: vi.fn(),
|
|
25
|
+
}))
|
|
26
|
+
vi.mock('../utils/msa', () => ({ launchMSA: vi.fn() }))
|
|
27
|
+
|
|
28
|
+
const mockResolveGeneId = vi.mocked(resolveGeneId)
|
|
29
|
+
const mockFetchProtein = vi.mocked(fetchProteinForGene)
|
|
30
|
+
const mockFetchRows = vi.mocked(fetchOrthologRows)
|
|
31
|
+
const mockLaunchMSA = vi.mocked(launchMSA)
|
|
32
|
+
|
|
33
|
+
const HUMAN = 9606
|
|
34
|
+
const GENE_ID = '22861'
|
|
35
|
+
const REPRESENTATIVE = { accession: 'NP_127497.1', sequence: 'MAGGAWGRLACY' }
|
|
36
|
+
|
|
37
|
+
function makeModel(orthologParams: Record<string, unknown>) {
|
|
38
|
+
return {
|
|
39
|
+
orthologParams,
|
|
40
|
+
setProgress: () => {},
|
|
41
|
+
} as unknown as JBrowsePluginMsaViewModel
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function params(extra: Record<string, unknown> = {}) {
|
|
45
|
+
return {
|
|
46
|
+
taxId: HUMAN,
|
|
47
|
+
geneCandidates: ['NLRP1'],
|
|
48
|
+
msaAlgorithm: 'clustalo',
|
|
49
|
+
...extra,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// What fetchOrthologRows was asked for, which is the only place the taxa
|
|
54
|
+
// default is observable.
|
|
55
|
+
function taxaAskedFor() {
|
|
56
|
+
return [...mockFetchRows.mock.calls[0]![0].taxa].sort((a, b) => a - b)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// The QUERY row as it went to the aligner, read back out of the FASTA rather
|
|
60
|
+
// than out of an intermediate, since the FASTA is what the alignment is of.
|
|
61
|
+
function queryRowSent() {
|
|
62
|
+
return mockLaunchMSA.mock.calls[0]![0].sequence.split('\n')[1]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function queryMetadata(result: { treeMetadata: string }) {
|
|
66
|
+
return JSON.parse(result.treeMetadata).QUERY as Record<string, string>
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
vi.clearAllMocks()
|
|
71
|
+
mockResolveGeneId.mockResolvedValue({ geneId: GENE_ID, matched: 'NLRP1' })
|
|
72
|
+
mockFetchProtein.mockResolvedValue(REPRESENTATIVE)
|
|
73
|
+
mockFetchRows.mockResolvedValue([] as OrthologRow[])
|
|
74
|
+
mockLaunchMSA.mockResolvedValue({ msa: '', tree: '' })
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('taxa', () => {
|
|
78
|
+
test('omitted asks for every species the dialog offers, less the query', async () => {
|
|
79
|
+
await doLaunchOrthologs({ self: makeModel(params()) })
|
|
80
|
+
expect(taxaAskedFor()).toEqual(
|
|
81
|
+
COMMON_SPECIES.map(s => s.taxId as number)
|
|
82
|
+
.filter(t => t !== HUMAN)
|
|
83
|
+
.sort((a, b) => a - b),
|
|
84
|
+
)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('given is taken as written, less the query', async () => {
|
|
88
|
+
await doLaunchOrthologs({
|
|
89
|
+
self: makeModel(params({ taxa: [HUMAN, 10090, 9615] })),
|
|
90
|
+
})
|
|
91
|
+
expect(taxaAskedFor()).toEqual([9615, 10090])
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('an empty list is a request for no rows, not a request for all of them', async () => {
|
|
95
|
+
await doLaunchOrthologs({ self: makeModel(params({ taxa: [] })) })
|
|
96
|
+
expect(taxaAskedFor()).toEqual([])
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
describe('the QUERY row', () => {
|
|
101
|
+
test('omitted proteinSequence falls back to the representative protein', async () => {
|
|
102
|
+
await doLaunchOrthologs({ self: makeModel(params()) })
|
|
103
|
+
expect(queryRowSent()).toBe(REPRESENTATIVE.sequence)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('a supplied sequence is used, and is cleaned first', async () => {
|
|
107
|
+
await doLaunchOrthologs({
|
|
108
|
+
self: makeModel(params({ proteinSequence: 'MAGG*AWGR&' })),
|
|
109
|
+
})
|
|
110
|
+
expect(queryRowSent()).toBe('MAGGAWGR')
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('throws when neither a sequence nor a representative is available', async () => {
|
|
114
|
+
mockFetchProtein.mockResolvedValue(undefined)
|
|
115
|
+
await expect(
|
|
116
|
+
doLaunchOrthologs({ self: makeModel(params()) }),
|
|
117
|
+
).rejects.toThrow(/No query protein/)
|
|
118
|
+
expect(mockLaunchMSA).not.toHaveBeenCalled()
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('a failed representative lookup does not take down a launch that brought its own sequence', async () => {
|
|
122
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
123
|
+
mockFetchProtein.mockRejectedValue(new Error('429'))
|
|
124
|
+
await doLaunchOrthologs({
|
|
125
|
+
self: makeModel(params({ proteinSequence: REPRESENTATIVE.sequence })),
|
|
126
|
+
})
|
|
127
|
+
expect(queryRowSent()).toBe(REPRESENTATIVE.sequence)
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
// The Accession is what afterCreateAutoruns.autoLoadProteinDomains keys the CDD
|
|
132
|
+
// overlay off, and attaching it to a row that is NOT the protein it names draws
|
|
133
|
+
// every domain box at an offset. So the byte-identity guard is the assertion
|
|
134
|
+
// here, in both directions.
|
|
135
|
+
describe('the Accession that drives the domain overlay', () => {
|
|
136
|
+
test('is attached when the query row IS the representative protein', async () => {
|
|
137
|
+
const result = await doLaunchOrthologs({ self: makeModel(params()) })
|
|
138
|
+
expect(queryMetadata(result)).toMatchObject({
|
|
139
|
+
'Gene ID': GENE_ID,
|
|
140
|
+
Accession: REPRESENTATIVE.accession,
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test('is withheld from a non-representative isoform', async () => {
|
|
145
|
+
const result = await doLaunchOrthologs({
|
|
146
|
+
self: makeModel(params({ proteinSequence: 'MDIFFERENTISOFORM' })),
|
|
147
|
+
})
|
|
148
|
+
expect(queryMetadata(result).Accession).toBeUndefined()
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test('is withheld when the representative lookup failed', async () => {
|
|
152
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
153
|
+
mockFetchProtein.mockRejectedValue(new Error('429'))
|
|
154
|
+
const result = await doLaunchOrthologs({
|
|
155
|
+
self: makeModel(params({ proteinSequence: REPRESENTATIVE.sequence })),
|
|
156
|
+
})
|
|
157
|
+
expect(queryMetadata(result).Accession).toBeUndefined()
|
|
158
|
+
})
|
|
159
|
+
})
|
|
@@ -28,9 +28,8 @@ import {
|
|
|
28
28
|
import type { MafRegion, MsaViewInitState } from './types'
|
|
29
29
|
import type {
|
|
30
30
|
BlastDatabase,
|
|
31
|
-
BlastProgram,
|
|
32
31
|
MsaAlgorithm,
|
|
33
|
-
} from '../LaunchMsaView/components/
|
|
32
|
+
} from '../LaunchMsaView/components/BlastQuery/consts'
|
|
34
33
|
import type { Feature } from '@jbrowse/core/util'
|
|
35
34
|
import type { Instance } from '@jbrowse/mobx-state-tree'
|
|
36
35
|
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
@@ -46,13 +45,10 @@ export interface IRegion {
|
|
|
46
45
|
}
|
|
47
46
|
|
|
48
47
|
export interface BlastParams {
|
|
49
|
-
baseUrl: string
|
|
50
48
|
blastDatabase: BlastDatabase
|
|
51
49
|
msaAlgorithm: MsaAlgorithm
|
|
52
|
-
blastProgram: BlastProgram
|
|
53
50
|
selectedTranscript?: Feature
|
|
54
51
|
proteinSequence: string
|
|
55
|
-
rid?: string
|
|
56
52
|
}
|
|
57
53
|
|
|
58
54
|
export interface OrthologParams {
|
package/src/utils/blastCache.ts
CHANGED
|
@@ -2,9 +2,8 @@ import { createDbOpener } from './idb'
|
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
4
|
BlastDatabase,
|
|
5
|
-
BlastProgram,
|
|
6
5
|
MsaAlgorithm,
|
|
7
|
-
} from '../LaunchMsaView/components/
|
|
6
|
+
} from '../LaunchMsaView/components/BlastQuery/consts'
|
|
8
7
|
import type { DBSchema } from 'idb'
|
|
9
8
|
|
|
10
9
|
const DB_NAME = 'jbrowse-msaview-blast-cache'
|
|
@@ -15,7 +14,12 @@ export interface CachedBlastResult {
|
|
|
15
14
|
id: string
|
|
16
15
|
proteinSequence: string
|
|
17
16
|
blastDatabase: BlastDatabase
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Only ever set on rows cached by a version that still queried NCBI, where
|
|
19
|
+
* the choice between blastp and quick-blastp was real. Kept so those rows
|
|
20
|
+
* still display; never written now.
|
|
21
|
+
*/
|
|
22
|
+
blastProgram?: string
|
|
19
23
|
msaAlgorithm: MsaAlgorithm
|
|
20
24
|
msa: string
|
|
21
25
|
tree: string
|
|
@@ -51,7 +55,6 @@ const getDB = createDbOpener<BlastCacheDB>(
|
|
|
51
55
|
function createCacheKey(
|
|
52
56
|
proteinSequence: string,
|
|
53
57
|
blastDatabase: BlastDatabase,
|
|
54
|
-
blastProgram: BlastProgram,
|
|
55
58
|
msaAlgorithm: MsaAlgorithm,
|
|
56
59
|
transcriptId?: string,
|
|
57
60
|
) {
|
|
@@ -59,13 +62,12 @@ function createCacheKey(
|
|
|
59
62
|
// msaAlgorithm is part of the key because the stored msa/tree are produced by
|
|
60
63
|
// it — without it, re-running the same query under a different algorithm
|
|
61
64
|
// overwrites the earlier result and drops it from the history list
|
|
62
|
-
return `${blastDatabase}:${
|
|
65
|
+
return `${blastDatabase}:${msaAlgorithm}${idPart}:${proteinSequence}`
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
export async function saveBlastResult({
|
|
66
69
|
proteinSequence,
|
|
67
70
|
blastDatabase,
|
|
68
|
-
blastProgram,
|
|
69
71
|
msaAlgorithm,
|
|
70
72
|
msa,
|
|
71
73
|
tree,
|
|
@@ -78,7 +80,6 @@ export async function saveBlastResult({
|
|
|
78
80
|
}: {
|
|
79
81
|
proteinSequence: string
|
|
80
82
|
blastDatabase: BlastDatabase
|
|
81
|
-
blastProgram: BlastProgram
|
|
82
83
|
msaAlgorithm: MsaAlgorithm
|
|
83
84
|
msa: string
|
|
84
85
|
tree: string
|
|
@@ -93,7 +94,6 @@ export async function saveBlastResult({
|
|
|
93
94
|
const id = createCacheKey(
|
|
94
95
|
proteinSequence,
|
|
95
96
|
blastDatabase,
|
|
96
|
-
blastProgram,
|
|
97
97
|
msaAlgorithm,
|
|
98
98
|
transcriptId,
|
|
99
99
|
)
|
|
@@ -101,7 +101,6 @@ export async function saveBlastResult({
|
|
|
101
101
|
id,
|
|
102
102
|
proteinSequence,
|
|
103
103
|
blastDatabase,
|
|
104
|
-
blastProgram,
|
|
105
104
|
msaAlgorithm,
|
|
106
105
|
msa,
|
|
107
106
|
tree,
|