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.
Files changed (87) hide show
  1. package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastRIDPanel.d.ts → BlastQuery/BlastAutomaticPanel.d.ts} +2 -3
  2. package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastAutomaticPanel.js → BlastQuery/BlastAutomaticPanel.js} +6 -25
  3. package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastManualPanel.d.ts → BlastQuery/BlastManualPanel.d.ts} +2 -3
  4. package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastManualPanel.js → BlastQuery/BlastManualPanel.js} +7 -4
  5. package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastMethodSelector.d.ts → BlastQuery/BlastMethodSelector.d.ts} +2 -2
  6. package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastMethodSelector.js → BlastQuery/BlastMethodSelector.js} +1 -2
  7. package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastPanel.d.ts → BlastQuery/BlastPanel.d.ts} +3 -12
  8. package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastPanel.js → BlastQuery/BlastPanel.js} +14 -16
  9. package/dist/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.d.ts +8 -0
  10. package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBISettingsDialog.js → BlastQuery/BlastSettingsDialog.js} +13 -9
  11. package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/CachedBlastResults.js +1 -1
  12. package/dist/LaunchMsaView/components/BlastQuery/consts.d.ts +12 -0
  13. package/dist/LaunchMsaView/components/BlastQuery/consts.js +17 -0
  14. package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js +3 -3
  15. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +1 -1
  16. package/dist/MsaViewPanel/components/JobLink.d.ts +5 -0
  17. package/dist/MsaViewPanel/components/JobLink.js +13 -0
  18. package/dist/MsaViewPanel/components/LoadingBLAST.d.ts +1 -2
  19. package/dist/MsaViewPanel/components/LoadingBLAST.js +8 -15
  20. package/dist/MsaViewPanel/components/MsaViewPanel.js +1 -1
  21. package/dist/MsaViewPanel/doLaunchBlast.js +11 -19
  22. package/dist/MsaViewPanel/doLaunchOrthologs.test.d.ts +1 -0
  23. package/dist/MsaViewPanel/doLaunchOrthologs.test.js +127 -0
  24. package/dist/MsaViewPanel/model.d.ts +1 -4
  25. package/dist/jbrowse-plugin-msaview.umd.production.min.js +30 -30
  26. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  27. package/dist/utils/blastCache.d.ts +8 -4
  28. package/dist/utils/blastCache.js +4 -5
  29. package/dist/utils/ebiBlast.d.ts +52 -0
  30. package/dist/utils/ebiBlast.js +63 -0
  31. package/dist/utils/ebiJobDispatcher.d.ts +33 -0
  32. package/dist/utils/ebiJobDispatcher.js +55 -0
  33. package/dist/utils/fetch.js +24 -1
  34. package/dist/utils/msa.d.ts +1 -1
  35. package/dist/utils/msa.js +25 -32
  36. package/dist/utils/types.d.ts +9 -14
  37. package/dist/utils/useLocalStorage.d.ts +1 -0
  38. package/dist/utils/useLocalStorage.js +1 -1
  39. package/dist/version.d.ts +1 -1
  40. package/dist/version.js +1 -1
  41. package/package.json +1 -1
  42. package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastAutomaticPanel.tsx → BlastQuery/BlastAutomaticPanel.tsx} +13 -59
  43. package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastManualPanel.tsx → BlastQuery/BlastManualPanel.tsx} +7 -6
  44. package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastMethodSelector.tsx → BlastQuery/BlastMethodSelector.tsx} +2 -7
  45. package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastPanel.tsx → BlastQuery/BlastPanel.tsx} +21 -25
  46. package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBISettingsDialog.tsx → BlastQuery/BlastSettingsDialog.tsx} +26 -13
  47. package/src/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/CachedBlastResults.tsx +1 -1
  48. package/src/LaunchMsaView/components/BlastQuery/consts.ts +22 -0
  49. package/src/LaunchMsaView/components/LaunchMsaViewDialog.tsx +5 -7
  50. package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +2 -2
  51. package/src/MsaViewPanel/components/JobLink.tsx +17 -0
  52. package/src/MsaViewPanel/components/LoadingBLAST.tsx +10 -41
  53. package/src/MsaViewPanel/components/MsaViewPanel.tsx +1 -1
  54. package/src/MsaViewPanel/doLaunchBlast.ts +12 -28
  55. package/src/MsaViewPanel/doLaunchOrthologs.test.ts +159 -0
  56. package/src/MsaViewPanel/model.ts +1 -5
  57. package/src/utils/blastCache.ts +8 -9
  58. package/src/utils/ebiBlast.ts +114 -0
  59. package/src/utils/ebiJobDispatcher.ts +91 -0
  60. package/src/utils/fetch.ts +26 -1
  61. package/src/utils/msa.ts +26 -47
  62. package/src/utils/types.ts +8 -13
  63. package/src/utils/useLocalStorage.ts +1 -1
  64. package/src/version.ts +1 -1
  65. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.d.ts +0 -10
  66. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastRIDPanel.js +0 -74
  67. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBISettingsDialog.d.ts +0 -5
  68. package/dist/LaunchMsaView/components/NCBIBlastQuery/consts.d.ts +0 -7
  69. package/dist/LaunchMsaView/components/NCBIBlastQuery/consts.js +0 -4
  70. package/dist/MsaViewPanel/components/RIDLink.d.ts +0 -6
  71. package/dist/MsaViewPanel/components/RIDLink.js +0 -12
  72. package/dist/utils/ncbiBlast.d.ts +0 -30
  73. package/dist/utils/ncbiBlast.js +0 -84
  74. package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastRIDPanel.tsx +0 -137
  75. package/src/LaunchMsaView/components/NCBIBlastQuery/consts.ts +0 -10
  76. package/src/MsaViewPanel/components/RIDLink.tsx +0 -19
  77. package/src/utils/ncbiBlast.ts +0 -143
  78. /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/CachedBlastResults.d.ts +0 -0
  79. /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/MsaAlgorithmSelect.d.ts +0 -0
  80. /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/MsaAlgorithmSelect.js +0 -0
  81. /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/blastLaunchView.d.ts +0 -0
  82. /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/blastLaunchView.js +0 -0
  83. /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/useCachedBlastResults.d.ts +0 -0
  84. /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/useCachedBlastResults.js +0 -0
  85. /package/src/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/MsaAlgorithmSelect.tsx +0 -0
  86. /package/src/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/blastLaunchView.ts +0 -0
  87. /package/src/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/useCachedBlastResults.ts +0 -0
@@ -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 NCBIBlastAutomaticPanel from './NCBIBlastAutomaticPanel'
8
- import NCBIBlastManualPanel from './NCBIBlastManualPanel'
9
- import NCBIBlastMethodSelector from './NCBIBlastMethodSelector'
10
- import NCBIBlastRIDPanel from './NCBIBlastRIDPanel'
11
- import NCBISettingsDialog from './NCBISettingsDialog'
12
- import { BASE_BLAST_URL } from './consts'
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: NCBIBlastAutomaticPanel,
25
- rid: NCBIBlastRIDPanel,
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 NCBIBlastPanel({
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 [baseUrl, setBaseUrl] = useLocalStorage(
43
- 'msa-blastRootUrl',
44
- BASE_BLAST_URL,
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
- model={model}
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
- <NCBISettingsDialog
77
- baseUrl={baseUrl}
78
- handleClose={newUrl => {
79
- if (newUrl) {
80
- setBaseUrl(newUrl)
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
- urlField: {
17
+ field: {
17
18
  minWidth: 300,
18
19
  },
20
+ help: {
21
+ marginBottom: 8,
22
+ },
19
23
  })
20
24
 
21
- export default function NCBISettingsDialog({
25
+ export interface BlastSettings {
26
+ ebiEmail: string
27
+ }
28
+
29
+ export default function BlastSettingsDialog({
22
30
  handleClose,
23
- baseUrl,
31
+ ebiEmail,
24
32
  }: {
25
- handleClose: (arg?: string) => void
26
- baseUrl: string
33
+ handleClose: (arg?: BlastSettings) => void
34
+ ebiEmail: string
27
35
  }) {
28
36
  const { classes } = useStyles()
29
- const [tempBaseUrl, setTempBaseUrl] = useState(baseUrl)
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="BLAST Base URL"
56
+ label="EBI contact email"
44
57
  fullWidth
45
58
  variant="outlined"
46
- value={tempBaseUrl}
47
- className={classes.urlField}
59
+ value={tempEbiEmail}
60
+ className={classes.field}
48
61
  onChange={e => {
49
- setTempBaseUrl(e.target.value)
62
+ setTempEbiEmail(e.target.value)
50
63
  }}
51
64
  />
52
65
  <Button
53
66
  variant="contained"
54
67
  onClick={() => {
55
- setTempBaseUrl(BASE_BLAST_URL)
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(tempBaseUrl)
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}/${result.blastProgram} (${result.msaAlgorithm})`}
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
- <Tab label="NCBI BLAST query" value="ncbi_blast" />
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
- <NCBIBlastPanel
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 '../NCBIBlastQuery/consts'
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 RIDLink from './RIDLink'
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 NCBI BLAST" variant="h5" />
30
+ <LoadingEllipses message="Running EBI BLAST" variant="h5" />
68
31
  {error ? (
69
- <RIDError baseUrl={baseUrl} rid={rid} error={error} />
32
+ <div>
33
+ {rid ? <JobLink jobId={rid} /> : null}
34
+ <ErrorMessage error={error} />
35
+ </div>
70
36
  ) : rid ? (
71
- <RIDProgress baseUrl={baseUrl} rid={rid} progress={progress} />
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} baseUrl={blastParams.baseUrl} />
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
- baseUrl,
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
- if (existingRid) {
33
- // publish it before the first poll so the view can link out to NCBI while
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
- self.setRid(existingRid)
36
- }
37
-
38
- const { hits, rid } = existingRid
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/NCBIBlastQuery/consts'
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 {
@@ -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/NCBIBlastQuery/consts'
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
- blastProgram: BlastProgram
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}:${blastProgram}:${msaAlgorithm}${idPart}:${proteinSequence}`
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,