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
@@ -1,9 +1,14 @@
1
- import type { BlastDatabase, BlastProgram, MsaAlgorithm } from '../LaunchMsaView/components/NCBIBlastQuery/consts';
1
+ import type { BlastDatabase, MsaAlgorithm } from '../LaunchMsaView/components/BlastQuery/consts';
2
2
  export interface CachedBlastResult {
3
3
  id: string;
4
4
  proteinSequence: string;
5
5
  blastDatabase: BlastDatabase;
6
- blastProgram: BlastProgram;
6
+ /**
7
+ * Only ever set on rows cached by a version that still queried NCBI, where
8
+ * the choice between blastp and quick-blastp was real. Kept so those rows
9
+ * still display; never written now.
10
+ */
11
+ blastProgram?: string;
7
12
  msaAlgorithm: MsaAlgorithm;
8
13
  msa: string;
9
14
  tree: string;
@@ -15,10 +20,9 @@ export interface CachedBlastResult {
15
20
  transcriptName?: string;
16
21
  geneName?: string;
17
22
  }
18
- export declare function saveBlastResult({ proteinSequence, blastDatabase, blastProgram, msaAlgorithm, msa, tree, treeMetadata, rid, geneId, transcriptId, transcriptName, geneName, }: {
23
+ export declare function saveBlastResult({ proteinSequence, blastDatabase, msaAlgorithm, msa, tree, treeMetadata, rid, geneId, transcriptId, transcriptName, geneName, }: {
19
24
  proteinSequence: string;
20
25
  blastDatabase: BlastDatabase;
21
- blastProgram: BlastProgram;
22
26
  msaAlgorithm: MsaAlgorithm;
23
27
  msa: string;
24
28
  tree: string;
@@ -10,21 +10,20 @@ const getDB = createDbOpener(DB_NAME, DB_VERSION, (db, oldVersion) => {
10
10
  db.createObjectStore(STORE_NAME, { keyPath: 'id' });
11
11
  }
12
12
  });
13
- function createCacheKey(proteinSequence, blastDatabase, blastProgram, msaAlgorithm, transcriptId) {
13
+ function createCacheKey(proteinSequence, blastDatabase, msaAlgorithm, transcriptId) {
14
14
  const idPart = transcriptId ? `:${transcriptId}` : '';
15
15
  // msaAlgorithm is part of the key because the stored msa/tree are produced by
16
16
  // it — without it, re-running the same query under a different algorithm
17
17
  // overwrites the earlier result and drops it from the history list
18
- return `${blastDatabase}:${blastProgram}:${msaAlgorithm}${idPart}:${proteinSequence}`;
18
+ return `${blastDatabase}:${msaAlgorithm}${idPart}:${proteinSequence}`;
19
19
  }
20
- export async function saveBlastResult({ proteinSequence, blastDatabase, blastProgram, msaAlgorithm, msa, tree, treeMetadata, rid, geneId, transcriptId, transcriptName, geneName, }) {
20
+ export async function saveBlastResult({ proteinSequence, blastDatabase, msaAlgorithm, msa, tree, treeMetadata, rid, geneId, transcriptId, transcriptName, geneName, }) {
21
21
  const db = await getDB();
22
- const id = createCacheKey(proteinSequence, blastDatabase, blastProgram, msaAlgorithm, transcriptId);
22
+ const id = createCacheKey(proteinSequence, blastDatabase, msaAlgorithm, transcriptId);
23
23
  const entry = {
24
24
  id,
25
25
  proteinSequence,
26
26
  blastDatabase,
27
- blastProgram,
28
27
  msaAlgorithm,
29
28
  msa,
30
29
  tree,
@@ -0,0 +1,52 @@
1
+ import type { BlastHit } from './types';
2
+ import type { BlastDatabase } from '../LaunchMsaView/components/BlastQuery/consts';
3
+ /**
4
+ * The subset of EBI's ncbiblast JSON result this plugin reads. The service
5
+ * returns a great deal more per hit (urls, bit scores, e-values, the match
6
+ * string); only what the MSA rows are built from is typed here.
7
+ */
8
+ interface EbiBlastJson {
9
+ hits?: {
10
+ hit_acc?: string;
11
+ hit_id?: string;
12
+ hit_desc?: string;
13
+ /** the UniProt fields are absent on hits from non-UniProt databases */
14
+ hit_os?: string;
15
+ hit_uni_de?: string;
16
+ hit_uni_os?: string;
17
+ /** NCBI taxon id, delivered as a string */
18
+ hit_uni_ox?: string;
19
+ hit_hsps?: {
20
+ hsp_hseq?: string;
21
+ }[];
22
+ }[];
23
+ }
24
+ /**
25
+ * Map EBI's hit shape onto the normalized one. Exported for testing against a
26
+ * captured response — the field names are the whole risk here, and nothing else
27
+ * in CI would notice if EBI renamed one.
28
+ */
29
+ export declare function normalizeEbiBlastHits(result: EbiBlastJson): BlastHit[];
30
+ /**
31
+ * Human-facing link to a job, shown while it runs and on error — so it has to
32
+ * be EBI's own results UI, not the REST result endpoint, which does not exist
33
+ * yet at the moment the link is on screen.
34
+ */
35
+ export declare function ebiBlastResultUrl(jobId: string): string;
36
+ export declare function queryEbiBlastFromJobId({ jobId, onProgress, }: {
37
+ jobId: string;
38
+ onProgress: (arg: string) => void;
39
+ }): Promise<{
40
+ rid: string;
41
+ hits: BlastHit[];
42
+ }>;
43
+ export declare function queryEbiBlast({ query, blastDatabase, onProgress, onRid, }: {
44
+ query: string;
45
+ blastDatabase: BlastDatabase;
46
+ onProgress: (arg: string) => void;
47
+ onRid: (arg: string) => void;
48
+ }): Promise<{
49
+ rid: string;
50
+ hits: BlastHit[];
51
+ }>;
52
+ export {};
@@ -0,0 +1,63 @@
1
+ import { fetchEbiResult, submitEbiJob, waitForEbiJob } from './ebiJobDispatcher';
2
+ const TOOL = 'ncbiblast';
3
+ /**
4
+ * Map EBI's hit shape onto the normalized one. Exported for testing against a
5
+ * captured response — the field names are the whole risk here, and nothing else
6
+ * in CI would notice if EBI renamed one.
7
+ */
8
+ export function normalizeEbiBlastHits(result) {
9
+ return (result.hits ?? []).map(hit => {
10
+ const taxid = Number.parseInt(hit.hit_uni_ox ?? '', 10);
11
+ return {
12
+ description: [
13
+ {
14
+ accession: hit.hit_acc ?? 'unknown',
15
+ id: hit.hit_id ?? hit.hit_acc ?? 'unknown',
16
+ sciname: hit.hit_uni_os ?? hit.hit_os ?? 'unknown',
17
+ taxid: Number.isNaN(taxid) ? undefined : taxid,
18
+ // hit_uni_de is the bare protein name; hit_desc repeats it with the
19
+ // OS=/OX=/GN= suffix that makeId already covers with real columns
20
+ title: hit.hit_uni_de ?? hit.hit_desc,
21
+ },
22
+ ],
23
+ hsps: (hit.hit_hsps ?? []).flatMap(hsp => hsp.hsp_hseq ? [{ hseq: hsp.hsp_hseq }] : []),
24
+ };
25
+ });
26
+ }
27
+ /**
28
+ * Human-facing link to a job, shown while it runs and on error — so it has to
29
+ * be EBI's own results UI, not the REST result endpoint, which does not exist
30
+ * yet at the moment the link is on screen.
31
+ */
32
+ export function ebiBlastResultUrl(jobId) {
33
+ return `https://www.ebi.ac.uk/jdispatcher/sss/${TOOL}/summary?jobId=${jobId}`;
34
+ }
35
+ export async function queryEbiBlastFromJobId({ jobId, onProgress, }) {
36
+ onProgress(`Checking BLAST status for job: ${jobId}...`);
37
+ await waitForEbiJob({
38
+ tool: TOOL,
39
+ jobId,
40
+ onCountdown: s => {
41
+ onProgress(`Re-checking BLAST status in... ${s}`);
42
+ },
43
+ });
44
+ const hits = normalizeEbiBlastHits(JSON.parse(await fetchEbiResult({ tool: TOOL, jobId, type: 'json' })));
45
+ if (hits.length === 0) {
46
+ throw new Error('No hits found');
47
+ }
48
+ return { rid: jobId, hits };
49
+ }
50
+ export async function queryEbiBlast({ query, blastDatabase, onProgress, onRid, }) {
51
+ onProgress('Submitting to EBI BLAST...');
52
+ const jobId = await submitEbiJob({
53
+ tool: TOOL,
54
+ params: {
55
+ program: 'blastp',
56
+ stype: 'protein',
57
+ database: blastDatabase,
58
+ sequence: query,
59
+ },
60
+ });
61
+ onRid(jobId);
62
+ return queryEbiBlastFromJobId({ jobId, onProgress });
63
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * EBI's Job Dispatcher REST services (clustalo, muscle, ncbiblast, ...) all
3
+ * speak the same run/status/result protocol, so the transport lives here and
4
+ * each tool only supplies its own parameters and result types.
5
+ *
6
+ * Unlike NCBI's Blast.cgi these endpoints send `Access-Control-Allow-Origin: *`,
7
+ * which is why the BLAST backend moved here — see docs/blast.md.
8
+ */
9
+ export declare const EBI_BASE = "https://www.ebi.ac.uk/Tools/services/rest";
10
+ /**
11
+ * EBI asks for a contact address on every submission so they can reach whoever
12
+ * is generating the load. A deployment that sends real volume should point this
13
+ * at its own maintainer via the BLAST settings dialog — otherwise every
14
+ * msaview job in the world is attributed to one person.
15
+ */
16
+ export declare const EBI_EMAIL_STORAGE_KEY = "msa-ebiContactEmail";
17
+ export declare const DEFAULT_EBI_EMAIL = "colin.diesh@gmail.com";
18
+ export declare function getEbiEmail(): string;
19
+ export declare function submitEbiJob({ tool, params, }: {
20
+ tool: string;
21
+ params: Record<string, string>;
22
+ }): Promise<string>;
23
+ export declare function waitForEbiJob({ tool, jobId, intervalSeconds, onCountdown, }: {
24
+ tool: string;
25
+ jobId: string;
26
+ intervalSeconds?: number;
27
+ onCountdown: (secondsRemaining: number) => void;
28
+ }): Promise<void>;
29
+ export declare function fetchEbiResult({ tool, jobId, type, }: {
30
+ tool: string;
31
+ jobId: string;
32
+ type: string;
33
+ }): Promise<string>;
@@ -0,0 +1,55 @@
1
+ import { textfetch } from './fetch';
2
+ import { pollLoop } from './poll';
3
+ import { readLocalStorage } from './useLocalStorage';
4
+ /**
5
+ * EBI's Job Dispatcher REST services (clustalo, muscle, ncbiblast, ...) all
6
+ * speak the same run/status/result protocol, so the transport lives here and
7
+ * each tool only supplies its own parameters and result types.
8
+ *
9
+ * Unlike NCBI's Blast.cgi these endpoints send `Access-Control-Allow-Origin: *`,
10
+ * which is why the BLAST backend moved here — see docs/blast.md.
11
+ */
12
+ export const EBI_BASE = 'https://www.ebi.ac.uk/Tools/services/rest';
13
+ /**
14
+ * EBI asks for a contact address on every submission so they can reach whoever
15
+ * is generating the load. A deployment that sends real volume should point this
16
+ * at its own maintainer via the BLAST settings dialog — otherwise every
17
+ * msaview job in the world is attributed to one person.
18
+ */
19
+ export const EBI_EMAIL_STORAGE_KEY = 'msa-ebiContactEmail';
20
+ export const DEFAULT_EBI_EMAIL = 'colin.diesh@gmail.com';
21
+ export function getEbiEmail() {
22
+ const configured = readLocalStorage(EBI_EMAIL_STORAGE_KEY, DEFAULT_EBI_EMAIL).trim();
23
+ return configured || DEFAULT_EBI_EMAIL;
24
+ }
25
+ /** Statuses that mean the job is over and produced no result. */
26
+ const FAILED_STATUSES = new Set(['ERROR', 'FAILURE', 'NOT_FOUND']);
27
+ export async function submitEbiJob({ tool, params, }) {
28
+ const jobId = await textfetch(`${EBI_BASE}/${tool}/run`, {
29
+ method: 'POST',
30
+ body: new URLSearchParams({ email: getEbiEmail(), ...params }),
31
+ });
32
+ return jobId.trim();
33
+ }
34
+ export async function waitForEbiJob({ tool, jobId, intervalSeconds = 10, onCountdown, }) {
35
+ await pollLoop({
36
+ intervalSeconds,
37
+ onCountdown,
38
+ check: async () => {
39
+ // exact match, not includes(): a job whose status is ERROR must not be
40
+ // able to poll forever waiting for a FINISHED that will never arrive
41
+ const status = (await textfetch(`${EBI_BASE}/${tool}/status/${jobId}`)).trim();
42
+ if (status === 'FINISHED') {
43
+ return true;
44
+ }
45
+ if (FAILED_STATUSES.has(status)) {
46
+ throw new Error(`EBI ${tool} job ${jobId} returned status ${status}`);
47
+ }
48
+ // RUNNING, QUEUED, PENDING and anything else EBI adds later
49
+ return false;
50
+ },
51
+ });
52
+ }
53
+ export async function fetchEbiResult({ tool, jobId, type, }) {
54
+ return textfetch(`${EBI_BASE}/${tool}/result/${jobId}/${type}`);
55
+ }
@@ -1,5 +1,28 @@
1
+ function hostOf(url) {
2
+ try {
3
+ return new URL(url).host;
4
+ }
5
+ catch {
6
+ // a relative url, e.g. a same-origin proxy path
7
+ return url;
8
+ }
9
+ }
1
10
  export async function handleFetch(url, args) {
2
- const response = await fetch(url, args);
11
+ let response;
12
+ try {
13
+ response = await fetch(url, args);
14
+ }
15
+ catch (e) {
16
+ // fetch rejects with a bare "TypeError: Failed to fetch" for every network
17
+ // level failure, including a cross-origin response the browser refused to
18
+ // hand over. That is not a hypothetical: NCBI's Blast.cgi stopped sending
19
+ // Access-Control-Allow-Origin to third-party origins, and the raw TypeError
20
+ // told users nothing at all about why. See docs/blast.md.
21
+ if (e instanceof TypeError) {
22
+ throw new Error(`Could not reach ${hostOf(url)} — the request failed at the network level. This is usually a CORS restriction (the server refused to let this site read the response), an offline connection, or a blocked request.`, { cause: e });
23
+ }
24
+ throw e;
25
+ }
3
26
  if (!response.ok) {
4
27
  throw new Error(`HTTP ${response.status} fetching ${url} ${await response.text()}`);
5
28
  }
@@ -1,4 +1,4 @@
1
- import type { MsaAlgorithm } from '../LaunchMsaView/components/NCBIBlastQuery/consts';
1
+ import type { MsaAlgorithm } from '../LaunchMsaView/components/BlastQuery/consts';
2
2
  export declare function launchMSA({ algorithm, sequence, onProgress, }: {
3
3
  algorithm: MsaAlgorithm;
4
4
  sequence: string;
package/dist/utils/msa.js CHANGED
@@ -1,57 +1,50 @@
1
- import { textfetch } from './fetch';
2
- import { pollLoop } from './poll';
3
- const base = `https://www.ebi.ac.uk/Tools/services/rest`;
4
- const email = 'colin.diesh@gmail.com';
1
+ import { fetchEbiResult, submitEbiJob, waitForEbiJob } from './ebiJobDispatcher';
5
2
  const algorithms = {
6
3
  clustalo: {
7
- params: { email },
4
+ params: {},
8
5
  msaResult: 'aln-clustal_num',
9
6
  treeResult: 'phylotree',
10
7
  },
11
8
  muscle: {
12
- params: { email, format: 'clw', tree: 'tree1' },
9
+ params: { format: 'clw', tree: 'tree1' },
13
10
  msaResult: 'fa',
14
11
  treeResult: 'phylotree',
15
12
  },
16
13
  kalign: {
17
- params: { email, stype: 'protein' },
14
+ params: { stype: 'protein' },
18
15
  msaResult: 'fa',
19
16
  treeResult: 'phylotree',
20
17
  },
21
18
  mafft: {
22
- params: { email, stype: 'protein' },
19
+ params: { stype: 'protein' },
23
20
  msaResult: 'fa',
24
21
  treeResult: 'phylotree',
25
22
  },
26
23
  };
27
- async function wait({ onProgress, jobId, algorithm, }) {
28
- await pollLoop({
29
- intervalSeconds: 10,
30
- onCountdown: s => {
31
- onProgress(`Re-checking MSA status in... ${s}`);
32
- },
33
- check: async () => {
34
- const result = await textfetch(`${base}/${algorithm}/status/${jobId}`);
35
- if (result.includes('FINISHED')) {
36
- return true;
37
- }
38
- if (result.includes('FAILURE')) {
39
- throw new Error(`Failed to run: jobId ${jobId}`);
40
- }
41
- return false;
42
- },
43
- });
44
- }
45
24
  export async function launchMSA({ algorithm, sequence, onProgress, }) {
46
25
  const config = algorithms[algorithm];
47
26
  onProgress(`Launching ${algorithm} MSA...`);
48
- const jobId = await textfetch(`${base}/${algorithm}/run`, {
49
- method: 'POST',
50
- body: new URLSearchParams({ ...config.params, sequence }),
27
+ const jobId = await submitEbiJob({
28
+ tool: algorithm,
29
+ params: { ...config.params, sequence },
30
+ });
31
+ await waitForEbiJob({
32
+ tool: algorithm,
33
+ jobId,
34
+ onCountdown: s => {
35
+ onProgress(`Re-checking MSA status in... ${s}`);
36
+ },
51
37
  });
52
- await wait({ jobId, algorithm, onProgress });
53
38
  return {
54
- msa: await textfetch(`${base}/${algorithm}/result/${jobId}/${config.msaResult}`),
55
- tree: await textfetch(`${base}/${algorithm}/result/${jobId}/${config.treeResult}`),
39
+ msa: await fetchEbiResult({
40
+ tool: algorithm,
41
+ jobId,
42
+ type: config.msaResult,
43
+ }),
44
+ tree: await fetchEbiResult({
45
+ tool: algorithm,
46
+ jobId,
47
+ type: config.treeResult,
48
+ }),
56
49
  };
57
50
  }
@@ -5,19 +5,14 @@ export interface BlastHitDescription {
5
5
  taxid?: number;
6
6
  title?: string;
7
7
  }
8
- export interface BlastResults {
9
- BlastOutput2: {
10
- report: {
11
- results: {
12
- search: {
13
- hits: {
14
- description: BlastHitDescription[];
15
- hsps: {
16
- hseq: string;
17
- }[];
18
- }[];
19
- };
20
- };
21
- };
8
+ /**
9
+ * The shape utils/ebiBlast.ts normalizes EBI's hits into. It keeps the field
10
+ * names NCBI's JSON2 used, so everything downstream of the search — row naming,
11
+ * taxonomy lookup, the MSA — was left untouched when the backend moved.
12
+ */
13
+ export interface BlastHit {
14
+ description: BlastHitDescription[];
15
+ hsps: {
16
+ hseq: string;
22
17
  }[];
23
18
  }
@@ -1 +1,2 @@
1
+ export declare function readLocalStorage<T>(key: string, initialValue: T): T;
1
2
  export declare function useLocalStorage<T>(key: string, initialValue: T): readonly [T, (value: T) => void];
@@ -4,7 +4,7 @@ import { useState } from 'react';
4
4
  // throw "(0, PR.useLocalStorage) is not a function" on hosts built during that
5
5
  // window. Same failure mode as `defaultCodonTable`; keeping our own copy takes
6
6
  // this plugin out of the whack-a-mole.
7
- function readLocalStorage(key, initialValue) {
7
+ export function readLocalStorage(key, initialValue) {
8
8
  try {
9
9
  const item = globalThis.localStorage.getItem(key);
10
10
  return item === null ? initialValue : JSON.parse(item);
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "2.8.2";
1
+ export declare const version = "2.9.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '2.8.2';
1
+ export const version = '2.9.0';
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.8.2",
2
+ "version": "2.9.0",
3
3
  "license": "MIT",
4
4
  "name": "jbrowse-plugin-msaview",
5
5
  "repository": {
@@ -14,7 +14,7 @@ import { makeStyles } from 'tss-react/mui'
14
14
  import CachedBlastResults from './CachedBlastResults'
15
15
  import MsaAlgorithmSelect from './MsaAlgorithmSelect'
16
16
  import { blastLaunchView } from './blastLaunchView'
17
- import { blastDatabaseOptions, blastPrograms } from './consts'
17
+ import { blastDatabaseOptions, defaultBlastDatabase } from './consts'
18
18
  import { useCachedBlastResults } from './useCachedBlastResults'
19
19
  import TextField2 from '../../../components/TextField2'
20
20
  import {
@@ -27,20 +27,13 @@ import SubmitCancelActions from '../SubmitCancelActions'
27
27
  import TranscriptSelector from '../TranscriptSelector'
28
28
  import { useTranscriptSelection } from '../useTranscriptSelection'
29
29
 
30
- import type { BlastDatabase, BlastProgram, MsaAlgorithm } from './consts'
30
+ import type { BlastDatabase, MsaAlgorithm } from './consts'
31
31
  import type { AbstractTrackModel, Feature } from '@jbrowse/core/util'
32
32
 
33
33
  const useStyles = makeStyles()({
34
34
  selectField: {
35
35
  width: 150,
36
36
  },
37
- databaseFieldContainer: {
38
- display: 'flex',
39
- },
40
- clusterSeqMessage: {
41
- marginLeft: 4,
42
- alignContent: 'center',
43
- },
44
37
  cachedResultsAccordion: {
45
38
  marginTop: 20,
46
39
  },
@@ -49,16 +42,14 @@ const useStyles = makeStyles()({
49
42
  },
50
43
  })
51
44
 
52
- const NCBIBlastAutomaticPanel = observer(function ({
45
+ const BlastAutomaticPanel = observer(function ({
53
46
  handleClose,
54
47
  feature,
55
48
  model,
56
49
  children,
57
- baseUrl,
58
50
  }: {
59
51
  model: AbstractTrackModel
60
52
  feature: Feature
61
- baseUrl: string
62
53
  handleClose: () => void
63
54
  children: React.ReactNode
64
55
  }) {
@@ -66,11 +57,9 @@ const NCBIBlastAutomaticPanel = observer(function ({
66
57
  const view = getLinearGenomeView(model)
67
58
  const [launchViewError, setLaunchViewError] = useState<unknown>()
68
59
  const [selectedBlastDatabase, setSelectedBlastDatabase] =
69
- useState<BlastDatabase>('nr')
60
+ useState<BlastDatabase>(defaultBlastDatabase)
70
61
  const [selectedMsaAlgorithm, setSelectedMsaAlgorithm] =
71
62
  useState<MsaAlgorithm>('clustalo')
72
- const [selectedBlastProgram, setSelectedBlastProgram] =
73
- useState<BlastProgram>('quick-blastp')
74
63
 
75
64
  const geneIds = useMemo(() => getGeneIdentifiers(feature), [feature])
76
65
  const { results: cachedResults, error: cachedResultsError } =
@@ -90,11 +79,7 @@ const NCBIBlastAutomaticPanel = observer(function ({
90
79
  select
91
80
  value={selectedBlastDatabase}
92
81
  onChange={event => {
93
- const newDb = event.target.value as BlastDatabase
94
- setSelectedBlastDatabase(newDb)
95
- if (newDb === 'nr_cluster_seq') {
96
- setSelectedBlastProgram('blastp')
97
- }
82
+ setSelectedBlastDatabase(event.target.value as BlastDatabase)
98
83
  }}
99
84
  >
100
85
  {blastDatabaseOptions.map(val => (
@@ -110,45 +95,16 @@ const NCBIBlastAutomaticPanel = observer(function ({
110
95
  onChange={setSelectedMsaAlgorithm}
111
96
  />
112
97
 
113
- <div className={classes.databaseFieldContainer}>
114
- <TextField2
115
- variant="outlined"
116
- label="BLAST program"
117
- disabled={selectedBlastDatabase === 'nr_cluster_seq'}
118
- className={classes.selectField}
119
- select
120
- value={selectedBlastProgram}
121
- onChange={event => {
122
- setSelectedBlastProgram(event.target.value as BlastProgram)
123
- }}
124
- >
125
- {blastPrograms.map(val => (
126
- <MenuItem value={val} key={val}>
127
- {val}
128
- </MenuItem>
129
- ))}
130
- </TextField2>
131
- {selectedBlastDatabase === 'nr_cluster_seq' ? (
132
- <Typography
133
- variant="subtitle2"
134
- className={classes.clusterSeqMessage}
135
- >
136
- Can only use blastp on nr_cluster_seq
137
- </Typography>
138
- ) : null}
139
- </div>
140
-
141
98
  <TranscriptSelector feature={feature} {...transcriptSelection} />
142
99
 
143
100
  <Typography className={classes.infoText}>
144
- This panel will automatically submit a query to NCBI. Using blastp can
145
- take 10+ minutes to run, quick-blastp is generally a lot faster but is
146
- not available for the clustered database. After completion, all the
147
- hits will be run through a multiple sequence alignment. Note: we are
148
- not able to currently run NCBI COBALT automatically on the BLAST
149
- results, even though that is the method NCBI uses on their website. If
150
- you need a COBALT alignment, please use the manual approach of
151
- submitting BLAST yourself and downloading the resulting files
101
+ This panel will automatically submit a blastp query to EBI, which
102
+ searches UniProtKB. Searches usually finish in under a minute, and
103
+ swissprot returns curated sequences that align more cleanly than the
104
+ many near-identical entries a TrEMBL search brings back. After
105
+ completion, all the hits will be run through a multiple sequence
106
+ alignment. Searching NCBI's nr needs the manual approach: NCBI no
107
+ longer lets a browser read responses from Blast.cgi.
152
108
  </Typography>
153
109
 
154
110
  {cachedResults.length > 0 ? (
@@ -177,8 +133,6 @@ const NCBIBlastAutomaticPanel = observer(function ({
177
133
  view,
178
134
  newViewTitle: getBlastViewTitle(feature, selectedTranscript),
179
135
  blastParams: {
180
- baseUrl,
181
- blastProgram: selectedBlastProgram,
182
136
  blastDatabase: selectedBlastDatabase,
183
137
  msaAlgorithm: selectedMsaAlgorithm,
184
138
  selectedTranscript,
@@ -198,4 +152,4 @@ const NCBIBlastAutomaticPanel = observer(function ({
198
152
  )
199
153
  })
200
154
 
201
- export default NCBIBlastAutomaticPanel
155
+ export default BlastAutomaticPanel
@@ -5,6 +5,7 @@ import { Button, DialogActions, Typography } from '@mui/material'
5
5
  import { observer } from 'mobx-react'
6
6
  import { makeStyles } from 'tss-react/mui'
7
7
 
8
+ import { BASE_BLAST_URL } from './consts'
8
9
  import ExternalLink from '../../../components/ExternalLink'
9
10
  import { cleanProteinSequence, getLinearGenomeView } from '../../util'
10
11
  import LaunchPanelContent from '../LaunchPanelContent'
@@ -24,17 +25,15 @@ const useStyles = makeStyles()({
24
25
  },
25
26
  })
26
27
 
27
- const NCBIBlastManualPanel = observer(function ({
28
+ const BlastManualPanel = observer(function ({
28
29
  handleClose,
29
30
  feature,
30
31
  model,
31
32
  children,
32
- baseUrl,
33
33
  }: {
34
34
  children: React.ReactNode
35
35
  model: AbstractTrackModel
36
36
  feature: Feature
37
- baseUrl: string
38
37
  handleClose: () => void
39
38
  }) {
40
39
  const { classes } = useStyles()
@@ -43,8 +42,10 @@ const NCBIBlastManualPanel = observer(function ({
43
42
  const { proteinSequence, error } = transcriptSelection
44
43
 
45
44
  const s2 = cleanProteinSequence(proteinSequence)
46
- const link = `${baseUrl}?PAGE_TYPE=BlastSearch&PAGE=Proteins&PROGRAM=blastp&QUERY=${s2}`
47
- const link2 = `${baseUrl}?PAGE_TYPE=BlastSearch&PAGE=Proteins&PROGRAM=blastp&QUERY=${shorten2(s2, 10)}`
45
+ // a link the user follows to NCBI's own site, not something we fetch — which
46
+ // is exactly why this route still works when the automatic one cannot
47
+ const link = `${BASE_BLAST_URL}?PAGE_TYPE=BlastSearch&PAGE=Proteins&PROGRAM=blastp&QUERY=${s2}`
48
+ const link2 = `${BASE_BLAST_URL}?PAGE_TYPE=BlastSearch&PAGE=Proteins&PROGRAM=blastp&QUERY=${shorten2(s2, 10)}`
48
49
 
49
50
  return (
50
51
  <>
@@ -83,4 +84,4 @@ const NCBIBlastManualPanel = observer(function ({
83
84
  )
84
85
  })
85
86
 
86
- export default NCBIBlastManualPanel
87
+ export default BlastManualPanel
@@ -2,9 +2,9 @@ import React from 'react'
2
2
 
3
3
  import { FormControl, FormControlLabel, Radio, RadioGroup } from '@mui/material'
4
4
 
5
- import type { BlastLookupMethod } from './NCBIBlastPanel'
5
+ import type { BlastLookupMethod } from './BlastPanel'
6
6
 
7
- export default function NCBIBlastMethodSelector({
7
+ export default function BlastMethodSelector({
8
8
  lookupMethod,
9
9
  setLookupMethod,
10
10
  }: {
@@ -25,11 +25,6 @@ export default function NCBIBlastMethodSelector({
25
25
  control={<Radio />}
26
26
  label="Automatic"
27
27
  />
28
- <FormControlLabel
29
- value="rid"
30
- control={<Radio />}
31
- label="Load from RID"
32
- />
33
28
  <FormControlLabel value="manual" control={<Radio />} label="Manual" />
34
29
  </RadioGroup>
35
30
  </FormControl>