jbrowse-plugin-msaview 2.8.1 → 2.8.2

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.
@@ -1,12 +1,19 @@
1
1
  export default function LaunchMsaViewExtensionPointF(pluginManager) {
2
2
  pluginManager.addToExtensionPoint('LaunchView-MsaView', (args) => {
3
3
  const { session, data, msaFileLocation, msaIndexedLocation, msaName, treeFileLocation, querySeqName, ...rest } = args;
4
- if (!data && !msaFileLocation && !msaIndexedLocation) {
5
- throw new Error('No MSA data or file location provided when launching MSA view');
4
+ // `orthologParams` is a fourth source, and unlike the other three it names
5
+ // no alignment at all the view builds one from NCBI at launch, which is
6
+ // the dialog's Orthologs tab reached declaratively.
7
+ if (!data &&
8
+ !msaFileLocation &&
9
+ !msaIndexedLocation &&
10
+ !rest.orthologParams) {
11
+ throw new Error('No MSA data, file location or orthologParams provided when launching MSA view');
6
12
  }
7
13
  // inline data and the tree URL are native react-msaview snapshot props, set
8
- // directly. Only sources needing launch-time resolution go through `init`:
9
- // msaUrl (AlphaFold sniff) and the name-indexed bgzip block (no native loader).
14
+ // directly, and so is orthologParams (the model's own autorun picks it up).
15
+ // Only sources needing launch-time resolution go through `init`: msaUrl
16
+ // (AlphaFold sniff) and the name-indexed bgzip block (no native loader).
10
17
  session.addView('MsaView', {
11
18
  type: 'MsaView',
12
19
  ...rest,
@@ -1,6 +1,6 @@
1
1
  import { cleanProteinSequence } from '../LaunchMsaView/util';
2
2
  import { launchMSA } from '../utils/msa';
3
- import { fetchOrthologRows, fetchProteinForGene, resolveGeneId, } from '../utils/ncbiOrthologs';
3
+ import { COMMON_SPECIES, fetchOrthologRows, fetchProteinForGene, resolveGeneId, } from '../utils/ncbiOrthologs';
4
4
  /**
5
5
  * The no-search-job alternative to doLaunchBlast.
6
6
  *
@@ -18,7 +18,6 @@ import { fetchOrthologRows, fetchProteinForGene, resolveGeneId, } from '../utils
18
18
  */
19
19
  export async function doLaunchOrthologs({ self, }) {
20
20
  const { taxId, taxa, geneCandidates, msaAlgorithm, proteinSequence } = self.orthologParams;
21
- const cleanedSeq = cleanProteinSequence(proteinSequence);
22
21
  const onProgress = (arg) => {
23
22
  self.setProgress(arg);
24
23
  };
@@ -27,15 +26,33 @@ export async function doLaunchOrthologs({ self, }) {
27
26
  if (!resolved) {
28
27
  throw new Error(`Could not resolve any of ${geneCandidates.join(', ')} to an NCBI gene in taxon ${taxId}. Try the NCBI BLAST tab, which needs no gene identifier.`);
29
28
  }
30
- // the query species is represented by the user's own transcript below
31
- const wanted = new Set(taxa.filter(t => t !== taxId));
29
+ // The query row. The dialog always supplies it — it is the user's OWN
30
+ // selected transcript, which is what makes `connectedFeature` map genome
31
+ // coordinates through this row. A launch that has no transcript to translate
32
+ // (a session spec naming only a gene) falls back to NCBI's representative
33
+ // protein for the resolved gene, which is the same choice made for every
34
+ // other row, so the alignment is the one NCBI would build for that gene.
35
+ const representative = await fetchRepresentativeQueryProtein(resolved.geneId);
36
+ const cleanedSeq = proteinSequence
37
+ ? cleanProteinSequence(proteinSequence)
38
+ : representative?.sequence;
39
+ if (!cleanedSeq) {
40
+ throw new Error(`No query protein: none was supplied and NCBI returned no representative protein for gene ${resolved.geneId}.`);
41
+ }
42
+ // Every species the panel offers, when a launch names none. A spec that wants
43
+ // a narrower comparison says so; one that just wants "this gene across
44
+ // species" should not have to enumerate the list the dialog would have
45
+ // checked for it.
46
+ const wantedTaxa = taxa ?? COMMON_SPECIES.map(s => s.taxId);
47
+ // the query species is represented by the query row above
48
+ const wanted = new Set(wantedTaxa.filter(t => t !== taxId));
32
49
  const rows = await fetchOrthologRows({
33
50
  geneId: resolved.geneId,
34
51
  taxa: wanted,
35
52
  onProgress,
36
53
  });
37
54
  const treeMetadata = {
38
- QUERY: await buildQueryMetadata(self, resolved.geneId, cleanedSeq),
55
+ QUERY: buildQueryMetadata(self, resolved.geneId, cleanedSeq, representative),
39
56
  };
40
57
  for (const row of rows) {
41
58
  treeMetadata[row.label] = buildRowMetadata(row);
@@ -54,31 +71,37 @@ export async function doLaunchOrthologs({ self, }) {
54
71
  };
55
72
  }
56
73
  /**
57
- * The query row is the user's own translated transcript, so it carries an
58
- * Accession which is what drives the automatic CDD overlay
59
- * (afterCreateAutoruns.autoLoadProteinDomains -> loadProteinDomains) ONLY
60
- * when its sequence is byte-identical to the RefSeq protein that accession
74
+ * A failed lookup only costs the query row its domain overlay and, for a launch
75
+ * that supplied no sequence of its own, the alignment so it is reported by
76
+ * returning nothing rather than by throwing here.
77
+ */
78
+ async function fetchRepresentativeQueryProtein(geneId) {
79
+ try {
80
+ return await fetchProteinForGene(geneId);
81
+ }
82
+ catch (e) {
83
+ console.warn('[msaview-orthologs] query protein lookup failed:', e);
84
+ return undefined;
85
+ }
86
+ }
87
+ /**
88
+ * The query row carries an Accession — which is what drives the automatic CDD
89
+ * overlay (afterCreateAutoruns.autoLoadProteinDomains -> loadProteinDomains) —
90
+ * ONLY when its sequence is byte-identical to the RefSeq protein that accession
61
91
  * names. Attaching it unconditionally would put every domain box at an offset
62
92
  * whenever the user picked a non-representative isoform, which is a silently
63
- * wrong figure rather than a missing one.
93
+ * wrong figure rather than a missing one. A launch that took the representative
94
+ * protein as its query row passes that test by construction.
64
95
  */
65
- async function buildQueryMetadata(self, geneId, proteinSequence) {
96
+ function buildQueryMetadata(self, geneId, proteinSequence, representative) {
66
97
  const transcript = self.orthologParams?.selectedTranscript;
67
98
  const metadata = { 'Gene ID': geneId };
68
99
  const name = transcript?.get('name') ?? transcript?.get('id');
69
100
  if (name) {
70
101
  metadata.Transcript = name;
71
102
  }
72
- try {
73
- const representative = await fetchProteinForGene(geneId);
74
- if (representative?.sequence === proteinSequence) {
75
- metadata.Accession = representative.accession;
76
- }
77
- }
78
- catch (e) {
79
- // a failed lookup only costs the query row its domain overlay, so it must
80
- // not take down an alignment that is otherwise complete
81
- console.warn('[msaview-orthologs] query protein lookup failed:', e);
103
+ if (representative?.sequence === proteinSequence) {
104
+ metadata.Accession = representative.accession;
82
105
  }
83
106
  return metadata;
84
107
  }
@@ -23,13 +23,24 @@ export interface BlastParams {
23
23
  export interface OrthologParams {
24
24
  /** NCBI taxon id of the assembly the query gene came from */
25
25
  taxId: number;
26
- /** taxon ids to include as rows (the query taxon is represented by QUERY) */
27
- taxa: number[];
26
+ /**
27
+ * taxon ids to include as rows (the query taxon is represented by QUERY).
28
+ * Omitted means every species the launch dialog offers, which is what a
29
+ * launch that just wants "this gene across species" wants.
30
+ */
31
+ taxa?: number[];
28
32
  /** candidate gene identifiers off the feature, tried in order */
29
33
  geneCandidates: string[];
30
34
  msaAlgorithm: MsaAlgorithm;
31
35
  selectedTranscript?: Feature;
32
- proteinSequence: string;
36
+ /**
37
+ * The QUERY row. The launch dialog always supplies it, translated from the
38
+ * transcript the user picked, which is what `connectedFeature` maps genome
39
+ * coordinates through. Omitted — a session spec naming a gene and nothing
40
+ * else — the query row becomes NCBI's representative protein for the resolved
41
+ * gene, the same choice every other row makes.
42
+ */
43
+ proteinSequence?: string;
33
44
  }
34
45
  /**
35
46
  * #stateModel MsaViewPlugin