jbrowse-plugin-msaview 3.4.1 → 3.6.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 (222) hide show
  1. package/README.md +7 -0
  2. package/dist/AddHighlightModel/index.js +3 -3
  3. package/dist/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.d.ts +8 -2
  4. package/dist/BgzipFastaMsaAdapter/configSchema.d.ts +3 -3
  5. package/dist/BgzipFastaMsaAdapter/configSchema.js +1 -1
  6. package/dist/LaunchMsaView/codingFeature.d.ts +14 -0
  7. package/dist/LaunchMsaView/codingFeature.js +38 -0
  8. package/dist/LaunchMsaView/codingFeature.test.js +75 -0
  9. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.d.ts +3 -1
  10. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +29 -26
  11. package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.d.ts +3 -1
  12. package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.js +7 -2
  13. package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.d.ts +7 -3
  14. package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.js +8 -7
  15. package/dist/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.js +2 -2
  16. package/dist/LaunchMsaView/components/BlastQuery/MsaAlgorithmSelect.js +5 -3
  17. package/dist/LaunchMsaView/components/BlastQuery/consts.d.ts +26 -4
  18. package/dist/LaunchMsaView/components/BlastQuery/consts.js +68 -2
  19. package/dist/LaunchMsaView/components/BlastQuery/searchChoiceStorage.d.ts +1 -1
  20. package/dist/LaunchMsaView/components/HelpButton.d.ts +2 -0
  21. package/dist/LaunchMsaView/components/HelpButton.js +17 -0
  22. package/dist/LaunchMsaView/components/HelpDialog.d.ts +4 -0
  23. package/dist/LaunchMsaView/components/HelpDialog.js +18 -0
  24. package/dist/LaunchMsaView/components/LaunchMsaViewDialog.d.ts +3 -1
  25. package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js +21 -16
  26. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.d.ts +3 -1
  27. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +10 -4
  28. package/dist/LaunchMsaView/components/ManualMSALoader/launchView.d.ts +3 -1
  29. package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js +2 -1
  30. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.d.ts +3 -1
  31. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +21 -7
  32. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.js +4 -1
  33. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.d.ts +3 -1
  34. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +33 -15
  35. package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.d.ts +3 -1
  36. package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.js +2 -1
  37. package/dist/LaunchMsaView/components/QueryRowSelector.js +21 -5
  38. package/dist/LaunchMsaView/components/SequenceStatus.d.ts +14 -0
  39. package/dist/LaunchMsaView/components/SequenceStatus.js +15 -0
  40. package/dist/LaunchMsaView/components/SubmitCancelActions.d.ts +3 -1
  41. package/dist/LaunchMsaView/components/SubmitCancelActions.js +25 -13
  42. package/dist/LaunchMsaView/components/SubmitCancelActions.test.js +33 -3
  43. package/dist/LaunchMsaView/components/TabPanel.js +15 -3
  44. package/dist/LaunchMsaView/components/TabPanel.test.d.ts +1 -0
  45. package/dist/LaunchMsaView/components/TabPanel.test.js +36 -0
  46. package/dist/LaunchMsaView/components/TranscriptSelector.d.ts +3 -1
  47. package/dist/LaunchMsaView/components/TranscriptSelector.js +4 -2
  48. package/dist/LaunchMsaView/components/calculateProteinSequence.d.ts +4 -1
  49. package/dist/LaunchMsaView/components/calculateProteinSequence.js +20 -10
  50. package/dist/LaunchMsaView/components/fetchSeq.d.ts +4 -1
  51. package/dist/LaunchMsaView/components/fetchSeq.js +14 -4
  52. package/dist/LaunchMsaView/components/launchPlacement.d.ts +8 -0
  53. package/dist/LaunchMsaView/components/launchPlacement.js +25 -0
  54. package/dist/LaunchMsaView/components/useFeatureSequence.d.ts +2 -0
  55. package/dist/LaunchMsaView/components/useFeatureSequence.js +14 -11
  56. package/dist/LaunchMsaView/components/useTranscriptSelection.d.ts +10 -1
  57. package/dist/LaunchMsaView/components/useTranscriptSelection.js +21 -3
  58. package/dist/LaunchMsaView/components/useTranscriptSelection.test.d.ts +1 -0
  59. package/dist/LaunchMsaView/components/useTranscriptSelection.test.js +57 -0
  60. package/dist/LaunchMsaView/detectQueryRow.d.ts +11 -0
  61. package/dist/LaunchMsaView/detectQueryRow.js +11 -4
  62. package/dist/LaunchMsaView/detectQueryRow.test.js +30 -0
  63. package/dist/LaunchMsaView/index.js +28 -9
  64. package/dist/LaunchMsaView/launchTarget.d.ts +36 -8
  65. package/dist/LaunchMsaView/launchTarget.js +23 -13
  66. package/dist/LaunchMsaView/launchTarget.test.js +80 -15
  67. package/dist/LaunchMsaView/useQueryRowName.d.ts +13 -0
  68. package/dist/LaunchMsaView/useQueryRowName.js +16 -1
  69. package/dist/LaunchMsaView/useQueryRowName.test.d.ts +1 -0
  70. package/dist/LaunchMsaView/useQueryRowName.test.js +40 -0
  71. package/dist/LaunchMsaView/util.js +5 -2
  72. package/dist/LaunchMsaViewExtensionPoint/index.js +8 -6
  73. package/dist/LaunchMsaViewExtensionPoint/index.test.js +21 -0
  74. package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +9 -6
  75. package/dist/MsaViewPanel/afterCreateAutoruns.js +72 -28
  76. package/dist/MsaViewPanel/components/LaunchProgress.d.ts +4 -3
  77. package/dist/MsaViewPanel/components/LaunchProgress.js +22 -10
  78. package/dist/MsaViewPanel/components/MsaViewPanel.js +10 -2
  79. package/dist/MsaViewPanel/components/MsaViewPanel.test.js +47 -2
  80. package/dist/MsaViewPanel/doLaunchBlast.d.ts +8 -0
  81. package/dist/MsaViewPanel/doLaunchBlast.js +78 -93
  82. package/dist/MsaViewPanel/doLaunchBlast.test.d.ts +1 -0
  83. package/dist/MsaViewPanel/doLaunchBlast.test.js +158 -0
  84. package/dist/MsaViewPanel/doLaunchOrthologs.js +40 -5
  85. package/dist/MsaViewPanel/doLaunchOrthologs.test.js +10 -0
  86. package/dist/MsaViewPanel/genomeToMSA.js +25 -7
  87. package/dist/MsaViewPanel/genomeToMSA.test.js +214 -19
  88. package/dist/MsaViewPanel/loadProteinDomains.d.ts +8 -2
  89. package/dist/MsaViewPanel/loadProteinDomains.js +18 -9
  90. package/dist/MsaViewPanel/loadProteinDomains.test.d.ts +1 -0
  91. package/dist/MsaViewPanel/loadProteinDomains.test.js +41 -0
  92. package/dist/MsaViewPanel/model.d.ts +265 -103
  93. package/dist/MsaViewPanel/model.js +174 -48
  94. package/dist/MsaViewPanel/model.test.d.ts +1 -0
  95. package/dist/MsaViewPanel/model.test.js +166 -0
  96. package/dist/MsaViewPanel/msaCoordToGenomeCoord.d.ts +12 -1
  97. package/dist/MsaViewPanel/msaCoordToGenomeCoord.js +8 -8
  98. package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.js +133 -32
  99. package/dist/MsaViewPanel/msaDataStore.d.ts +2 -0
  100. package/dist/MsaViewPanel/msaDataStore.js +10 -0
  101. package/dist/MsaViewPanel/observeProteinHighlights.test.js +20 -3
  102. package/dist/MsaViewPanel/processInit.test.d.ts +1 -0
  103. package/dist/MsaViewPanel/processInit.test.js +72 -0
  104. package/dist/MsaViewPanel/resolveConnectedTranscript.d.ts +9 -0
  105. package/dist/MsaViewPanel/resolveConnectedTranscript.js +128 -0
  106. package/dist/MsaViewPanel/runLaunch.d.ts +1 -0
  107. package/dist/MsaViewPanel/runLaunch.js +35 -0
  108. package/dist/MsaViewPanel/runLaunch.test.js +46 -0
  109. package/dist/MsaViewPanel/storedData.test.js +20 -0
  110. package/dist/MsaViewPanel/structureConnection.d.ts +0 -4
  111. package/dist/MsaViewPanel/structureConnection.js +0 -19
  112. package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +12 -5
  113. package/dist/MsaViewPanel/util.d.ts +39 -0
  114. package/dist/MsaViewPanel/util.js +44 -0
  115. package/dist/index.d.ts +11 -11
  116. package/dist/jbrowse-plugin-msaview.umd.production.min.js +56 -68
  117. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  118. package/dist/utils/browserAlign.d.ts +42 -0
  119. package/dist/utils/browserAlign.js +297 -0
  120. package/dist/utils/browserAlign.test.d.ts +1 -0
  121. package/dist/utils/browserAlign.test.js +85 -0
  122. package/dist/utils/ebiBlast.d.ts +10 -1
  123. package/dist/utils/ebiBlast.js +27 -1
  124. package/dist/utils/homologSearch.d.ts +31 -0
  125. package/dist/utils/homologSearch.js +6 -0
  126. package/dist/utils/msa.d.ts +3 -11
  127. package/dist/utils/msa.js +14 -29
  128. package/dist/utils/msaRows.d.ts +11 -8
  129. package/dist/utils/msaRows.js +14 -12
  130. package/dist/utils/phmmer.d.ts +11 -1
  131. package/dist/utils/phmmer.js +49 -12
  132. package/dist/utils/unirefHomologs.d.ts +94 -0
  133. package/dist/utils/unirefHomologs.js +193 -0
  134. package/dist/utils/unirefHomologs.test.d.ts +1 -0
  135. package/dist/utils/unirefHomologs.test.js +118 -0
  136. package/dist/version.d.ts +1 -1
  137. package/dist/version.js +1 -1
  138. package/package.json +23 -22
  139. package/src/AddHighlightModel/index.tsx +1 -1
  140. package/src/BgzipFastaMsaAdapter/configSchema.ts +1 -1
  141. package/src/LaunchMsaView/codingFeature.test.ts +96 -0
  142. package/src/LaunchMsaView/codingFeature.ts +49 -0
  143. package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +52 -26
  144. package/src/LaunchMsaView/components/BlastQuery/BlastManualPanel.tsx +9 -1
  145. package/src/LaunchMsaView/components/BlastQuery/BlastPanel.tsx +24 -11
  146. package/src/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.tsx +4 -4
  147. package/src/LaunchMsaView/components/BlastQuery/MsaAlgorithmSelect.tsx +7 -2
  148. package/src/LaunchMsaView/components/BlastQuery/consts.ts +77 -2
  149. package/src/LaunchMsaView/components/HelpButton.tsx +33 -0
  150. package/src/LaunchMsaView/components/HelpDialog.tsx +78 -0
  151. package/src/LaunchMsaView/components/LaunchMsaViewDialog.tsx +54 -34
  152. package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +13 -2
  153. package/src/LaunchMsaView/components/ManualMSALoader/launchView.ts +4 -0
  154. package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +28 -5
  155. package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.tsx +4 -1
  156. package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +63 -13
  157. package/src/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.ts +4 -0
  158. package/src/LaunchMsaView/components/QueryRowSelector.tsx +35 -7
  159. package/src/LaunchMsaView/components/SequenceStatus.tsx +29 -0
  160. package/src/LaunchMsaView/components/SubmitCancelActions.test.tsx +62 -3
  161. package/src/LaunchMsaView/components/SubmitCancelActions.tsx +37 -15
  162. package/src/LaunchMsaView/components/TabPanel.test.tsx +51 -0
  163. package/src/LaunchMsaView/components/TabPanel.tsx +16 -4
  164. package/src/LaunchMsaView/components/TranscriptSelector.tsx +6 -1
  165. package/src/LaunchMsaView/components/calculateProteinSequence.ts +29 -10
  166. package/src/LaunchMsaView/components/fetchSeq.ts +25 -5
  167. package/src/LaunchMsaView/components/launchPlacement.tsx +41 -0
  168. package/src/LaunchMsaView/components/useFeatureSequence.ts +18 -11
  169. package/src/LaunchMsaView/components/useTranscriptSelection.test.tsx +66 -0
  170. package/src/LaunchMsaView/components/useTranscriptSelection.ts +41 -2
  171. package/src/LaunchMsaView/detectQueryRow.test.ts +34 -0
  172. package/src/LaunchMsaView/detectQueryRow.ts +22 -4
  173. package/src/LaunchMsaView/index.ts +32 -13
  174. package/src/LaunchMsaView/launchTarget.test.ts +88 -15
  175. package/src/LaunchMsaView/launchTarget.ts +49 -14
  176. package/src/LaunchMsaView/useQueryRowName.test.ts +54 -0
  177. package/src/LaunchMsaView/useQueryRowName.ts +22 -1
  178. package/src/LaunchMsaView/util.ts +8 -4
  179. package/src/LaunchMsaViewExtensionPoint/index.test.ts +23 -0
  180. package/src/LaunchMsaViewExtensionPoint/index.ts +32 -6
  181. package/src/MsaViewPanel/afterCreateAutoruns.ts +85 -27
  182. package/src/MsaViewPanel/components/LaunchProgress.tsx +38 -7
  183. package/src/MsaViewPanel/components/MsaViewPanel.test.tsx +50 -2
  184. package/src/MsaViewPanel/components/MsaViewPanel.tsx +10 -2
  185. package/src/MsaViewPanel/doLaunchBlast.test.ts +207 -0
  186. package/src/MsaViewPanel/doLaunchBlast.ts +92 -142
  187. package/src/MsaViewPanel/doLaunchOrthologs.test.ts +13 -0
  188. package/src/MsaViewPanel/doLaunchOrthologs.ts +55 -5
  189. package/src/MsaViewPanel/genomeToMSA.test.ts +251 -31
  190. package/src/MsaViewPanel/genomeToMSA.ts +35 -7
  191. package/src/MsaViewPanel/loadProteinDomains.test.ts +52 -0
  192. package/src/MsaViewPanel/loadProteinDomains.ts +24 -11
  193. package/src/MsaViewPanel/model.test.ts +196 -0
  194. package/src/MsaViewPanel/model.ts +204 -55
  195. package/src/MsaViewPanel/msaCoordToGenomeCoord.test.ts +152 -32
  196. package/src/MsaViewPanel/msaCoordToGenomeCoord.ts +21 -11
  197. package/src/MsaViewPanel/msaDataStore.ts +10 -0
  198. package/src/MsaViewPanel/observeProteinHighlights.test.ts +27 -3
  199. package/src/MsaViewPanel/processInit.test.ts +84 -0
  200. package/src/MsaViewPanel/resolveConnectedTranscript.ts +160 -0
  201. package/src/MsaViewPanel/runLaunch.test.ts +54 -0
  202. package/src/MsaViewPanel/runLaunch.ts +39 -0
  203. package/src/MsaViewPanel/storedData.test.ts +26 -0
  204. package/src/MsaViewPanel/structureConnection.ts +0 -26
  205. package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +14 -5
  206. package/src/MsaViewPanel/util.ts +75 -0
  207. package/src/utils/browserAlign.test.ts +102 -0
  208. package/src/utils/browserAlign.ts +352 -0
  209. package/src/utils/ebiBlast.ts +34 -0
  210. package/src/utils/homologSearch.ts +51 -0
  211. package/src/utils/msa.ts +19 -41
  212. package/src/utils/msaRows.ts +20 -16
  213. package/src/utils/phmmer.ts +57 -11
  214. package/src/utils/unirefHomologs.test.ts +149 -0
  215. package/src/utils/unirefHomologs.ts +323 -0
  216. package/src/version.ts +1 -1
  217. package/dist/LaunchMsaView/components/geneticCodes.d.ts +0 -15
  218. package/dist/LaunchMsaView/components/geneticCodes.js +0 -227
  219. package/dist/MsaViewPanel/structureConnection.test.js +0 -53
  220. package/src/LaunchMsaView/components/geneticCodes.ts +0 -298
  221. package/src/MsaViewPanel/structureConnection.test.ts +0 -62
  222. /package/dist/{MsaViewPanel/structureConnection.test.d.ts → LaunchMsaView/codingFeature.test.d.ts} +0 -0
package/README.md CHANGED
@@ -29,6 +29,13 @@ https://jbrowse.org/code/jb2/main/index.html?config=https://unpkg.com/jbrowse-pl
29
29
  - Can share sessions with other users which will send relevant settings and
30
30
  links to files to automatically open your results
31
31
  - The tree or the MSA panel can be loaded separately from each other
32
+ - Builds a cross-species alignment for any gene from the genome view's
33
+ right-click menu: precomputed orthologs (NCBI, PANTHER), the gene's UniRef
34
+ cluster across all of UniProtKB, or a phmmer/blastp search at EBI
35
+ - Aligns and builds trees in the browser, so a UniRef launch needs no job at any
36
+ external service; EBI's aligners remain an option
37
+ - Every launch is also a session-spec URL (`orthologParams`, `searchParams`,
38
+ `connectedTranscript`), see [DEVELOPERS.md](DEVELOPERS.md)
32
39
 
33
40
  ## File format supports
34
41
 
@@ -3,9 +3,9 @@ import { getSession } from '@jbrowse/core/util';
3
3
  import { isMsaView } from '../MsaViewPanel/model';
4
4
  import HighlightComponents from './HighlightComponents';
5
5
  export default function AddHighlightComponentsModelF(pluginManager) {
6
- pluginManager.addToExtensionPoint('LinearGenomeView-TracksContainerComponent',
7
- // @ts-expect-error
8
- (rest, { model }) => {
6
+ pluginManager.addToExtensionPoint(
7
+ // @ts-expect-error v4 hosts have no contributeToExtensionPoint
8
+ 'LinearGenomeView-TracksContainerComponent', (rest, { model }) => {
9
9
  // Quick check: don't add any components if no MSA view exists
10
10
  const { views } = getSession(model);
11
11
  const hasMsaView = views.some(v => isMsaView(v) && v.connectedViewId === model.id);
@@ -3,8 +3,14 @@ import type { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAda
3
3
  export default class BgzipFastaMsaAdapter extends BaseAdapter {
4
4
  configureP: Promise<BaseFeatureDataAdapter> | undefined;
5
5
  refNamesP: Promise<string[]> | undefined;
6
- configurePre(): Promise<BaseFeatureDataAdapter>;
7
- configure(): Promise<BaseFeatureDataAdapter>;
6
+ configurePre(): Promise<BaseFeatureDataAdapter<import("@jbrowse/mobx-state-tree").ModelInstanceTypeProps<Record<string, any>> & {
7
+ setSubschema(slotName: string, data: Record<string, unknown>): any;
8
+ setSlot(slotName: string, value: unknown): void;
9
+ } & import("@jbrowse/mobx-state-tree").IStateTreeNode<import("@jbrowse/core/configuration").AnyConfigurationSchemaType>>>;
10
+ configure(): Promise<BaseFeatureDataAdapter<import("@jbrowse/mobx-state-tree").ModelInstanceTypeProps<Record<string, any>> & {
11
+ setSubschema(slotName: string, data: Record<string, unknown>): any;
12
+ setSlot(slotName: string, value: unknown): void;
13
+ } & import("@jbrowse/mobx-state-tree").IStateTreeNode<import("@jbrowse/core/configuration").AnyConfigurationSchemaType>>>;
8
14
  getMSARefs(): Promise<string[]>;
9
15
  getMsaRegex(): RegExp;
10
16
  refNameToMsaId(refName: string): string;
@@ -1,7 +1,7 @@
1
1
  import type PluginManager from '@jbrowse/core/PluginManager';
2
2
  export default function configSchemaF(pluginManager: PluginManager): import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
3
- msaRegex: {
4
- type: string;
5
- defaultValue: string;
3
+ readonly msaRegex: {
4
+ readonly type: "string";
5
+ readonly defaultValue: "_";
6
6
  };
7
7
  }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<import("@jbrowse/core/configuration").AnyConfigurationSchemaType, undefined>>;
@@ -7,6 +7,6 @@ export default function configSchemaF(pluginManager) {
7
7
  defaultValue: '_',
8
8
  },
9
9
  }, {
10
- baseConfiguration: base?.configSchema,
10
+ baseConfiguration: base.configSchema,
11
11
  });
12
12
  }
@@ -0,0 +1,14 @@
1
+ import type { Feature } from '@jbrowse/core/util';
2
+ export declare function isGeneLikeType(type: unknown): boolean;
3
+ export declare function isCodingFeature(feature: Feature): boolean;
4
+ /**
5
+ * Whether the feature is known not to code for anything.
6
+ *
7
+ * Not simply `!isCodingFeature`: a feature that arrived with no subfeatures at
8
+ * all says nothing either way, and a host is free to hand one over that way.
9
+ * Reading that as "no protein here" takes the menu item off a perfectly
10
+ * ordinary gene, silently, which is worse than opening a dialog that then has
11
+ * nothing to translate.
12
+ */
13
+ export declare function isKnownNonCoding(feature: Feature): boolean;
14
+ export declare function geneLikeRoot(feature: Feature): Feature;
@@ -0,0 +1,38 @@
1
+ // Copied from @jbrowse/core's featureTypes rather than imported: the barrel
2
+ // only exports it from v5, and a bundle resolving it on a v4 host reads
3
+ // undefined.
4
+ const GENE_LIKE_TYPE = /gene(_segment)?$|rna$|transcript/;
5
+ // `unknown` rather than `string | undefined`, because the type comes off a
6
+ // host's hit-test item and nothing here built it.
7
+ export function isGeneLikeType(type) {
8
+ return typeof type === 'string' && GENE_LIKE_TYPE.test(type.toLowerCase());
9
+ }
10
+ function isCDS(feature) {
11
+ return feature.get('type')?.toLowerCase() === 'cds';
12
+ }
13
+ // The feature itself counts: a viral polyprotein hangs its cleavage products
14
+ // off its CDS rather than off further CDSs.
15
+ export function isCodingFeature(feature) {
16
+ return isCDS(feature) || !!feature.get('subfeatures')?.some(isCodingFeature);
17
+ }
18
+ /**
19
+ * Whether the feature is known not to code for anything.
20
+ *
21
+ * Not simply `!isCodingFeature`: a feature that arrived with no subfeatures at
22
+ * all says nothing either way, and a host is free to hand one over that way.
23
+ * Reading that as "no protein here" takes the menu item off a perfectly
24
+ * ordinary gene, silently, which is worse than opening a dialog that then has
25
+ * nothing to translate.
26
+ */
27
+ export function isKnownNonCoding(feature) {
28
+ return !!feature.get('subfeatures')?.length && !isCodingFeature(feature);
29
+ }
30
+ // The outermost gene-like ancestor, so a click on an isoform opens the dialog
31
+ // on the gene with every transcript to choose from, as the canvas host does.
32
+ export function geneLikeRoot(feature) {
33
+ let root = feature;
34
+ for (let parent = root.parent?.(); parent && isGeneLikeType(parent.get('type')); parent = parent.parent?.()) {
35
+ root = parent;
36
+ }
37
+ return root;
38
+ }
@@ -0,0 +1,75 @@
1
+ import { SimpleFeature } from '@jbrowse/core/util';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { geneLikeRoot, isCodingFeature, isGeneLikeType, isKnownNonCoding, } from './codingFeature';
4
+ function feature(type, subfeatures = []) {
5
+ return new SimpleFeature({
6
+ uniqueId: `${type}-${Math.random()}`,
7
+ refName: 'chr1',
8
+ start: 0,
9
+ end: 100,
10
+ type,
11
+ subfeatures: subfeatures.map(s => s.toJSON()),
12
+ });
13
+ }
14
+ describe('isGeneLikeType', () => {
15
+ it('accepts the SO spellings of gene, transcript and RNA', () => {
16
+ for (const t of [
17
+ 'gene',
18
+ 'mRNA',
19
+ 'transcript',
20
+ 'ncRNA_gene',
21
+ 'protein_coding_gene',
22
+ 'V_gene_segment',
23
+ 'primary_transcript',
24
+ 'pseudogenic_transcript',
25
+ 'lnc_RNA',
26
+ ]) {
27
+ expect(isGeneLikeType(t)).toBe(true);
28
+ }
29
+ });
30
+ it('rejects regions, matches and CDS', () => {
31
+ for (const t of ['intergenic_region', 'cDNA_match', 'CDS', 'exon']) {
32
+ expect(isGeneLikeType(t)).toBe(false);
33
+ }
34
+ expect(isGeneLikeType(undefined)).toBe(false);
35
+ });
36
+ });
37
+ describe('isCodingFeature', () => {
38
+ it('finds a CDS anywhere below the feature', () => {
39
+ const gene = feature('gene', [
40
+ feature('mRNA', [feature('exon'), feature('CDS')]),
41
+ ]);
42
+ expect(isCodingFeature(gene)).toBe(true);
43
+ });
44
+ it('is false for a transcript with exons only', () => {
45
+ const lnc = feature('lnc_RNA', [feature('exon'), feature('exon')]);
46
+ expect(isCodingFeature(lnc)).toBe(false);
47
+ });
48
+ it('counts the feature itself', () => {
49
+ expect(isCodingFeature(feature('CDS'))).toBe(true);
50
+ });
51
+ });
52
+ describe('isKnownNonCoding', () => {
53
+ it('is true for a transcript whose subfeatures are all exons', () => {
54
+ expect(isKnownNonCoding(feature('lnc_RNA', [feature('exon'), feature('exon')]))).toBe(true);
55
+ });
56
+ // a host is free to hand over a bare record, and reading that as "no protein
57
+ // here" takes the menu item off an ordinary gene without a word
58
+ it('is false for a feature that arrived with no subfeatures at all', () => {
59
+ expect(isKnownNonCoding(feature('gene'))).toBe(false);
60
+ });
61
+ it('is false when a CDS is somewhere below', () => {
62
+ expect(isKnownNonCoding(feature('gene', [feature('mRNA', [feature('CDS')])]))).toBe(false);
63
+ });
64
+ });
65
+ describe('geneLikeRoot', () => {
66
+ it('climbs from an isoform to its gene', () => {
67
+ const gene = feature('gene', [feature('mRNA', [feature('CDS')])]);
68
+ const mrna = gene.get('subfeatures')[0];
69
+ expect(geneLikeRoot(mrna)).toBe(gene);
70
+ });
71
+ it('stays put with no gene-like parent', () => {
72
+ const mrna = feature('mRNA', [feature('CDS')]);
73
+ expect(geneLikeRoot(mrna)).toBe(mrna);
74
+ });
75
+ });
@@ -1,9 +1,11 @@
1
1
  import React from 'react';
2
2
  import type { AbstractTrackModel, Feature } from '@jbrowse/core/util';
3
- declare const BlastAutomaticPanel: ({ handleClose, feature, model, children, }: {
3
+ declare const BlastAutomaticPanel: ({ handleClose, feature, model, children, preferredTranscriptId, }: {
4
4
  model: AbstractTrackModel;
5
5
  feature: Feature;
6
6
  handleClose: () => void;
7
7
  children: React.ReactNode;
8
+ /** the isoform the user right-clicked, preselected in the picker */
9
+ preferredTranscriptId?: string;
8
10
  }) => React.JSX.Element;
9
11
  export default BlastAutomaticPanel;
@@ -6,24 +6,24 @@ import { makeStyles } from 'tss-react/mui';
6
6
  import TextField2 from '../../../components/TextField2';
7
7
  import { getBlastViewTitle, getGeneIdentifiers, getLinearGenomeView, } from '../../util';
8
8
  import LaunchPanelContent from '../LaunchPanelContent';
9
+ import SequenceStatusMessage from '../SequenceStatus';
9
10
  import SubmitCancelActions from '../SubmitCancelActions';
10
11
  import TranscriptSelector from '../TranscriptSelector';
11
12
  import { useTranscriptSelection } from '../useTranscriptSelection';
12
13
  import CachedBlastResults from './CachedBlastResults';
13
14
  import MsaAlgorithmSelect from './MsaAlgorithmSelect';
14
15
  import { blastLaunchView } from './blastLaunchView';
15
- import { databaseOptionsFor, defaultSearchFor, searchPrograms } from './consts';
16
+ import { databaseLabel, databaseOptionsFor, defaultMaxHits, defaultSearchFor, searchProgramLabels, searchPrograms, } from './consts';
16
17
  import { useStoredMsaAlgorithm, useStoredSearchChoice, } from './searchChoiceStorage';
17
18
  import { useCachedBlastResults } from './useCachedBlastResults';
18
19
  const useStyles = makeStyles()({
19
20
  selectField: {
20
21
  width: 150,
21
22
  },
22
- // wider than the rest because the values are what the user came to read, and
23
- // `uniprotkb_swissprot` is 19 characters — at 150 the field showed
24
- // `uniprotkb_swis…`, which does not distinguish it from `uniprotkb_trembl`
23
+ // wider than the rest because the collection names are what the user came to
24
+ // read, and truncating them does not distinguish Swiss-Prot from TrEMBL
25
25
  databaseField: {
26
- width: 230,
26
+ width: 260,
27
27
  },
28
28
  cachedResultsAccordion: {
29
29
  marginTop: 20,
@@ -32,7 +32,7 @@ const useStyles = makeStyles()({
32
32
  marginTop: 20,
33
33
  },
34
34
  });
35
- const BlastAutomaticPanel = observer(function ({ handleClose, feature, model, children, }) {
35
+ const BlastAutomaticPanel = observer(function ({ handleClose, feature, model, children, preferredTranscriptId, }) {
36
36
  const { classes } = useStyles();
37
37
  const view = getLinearGenomeView(model);
38
38
  const [launchViewError, setLaunchViewError] = useState();
@@ -41,11 +41,18 @@ const BlastAutomaticPanel = observer(function ({ handleClose, feature, model, ch
41
41
  // would let them drift into that
42
42
  const [search, setSearch] = useStoredSearchChoice();
43
43
  const [selectedMsaAlgorithm, setSelectedMsaAlgorithm] = useStoredMsaAlgorithm();
44
+ const [maxHits, setMaxHits] = useState(String(defaultMaxHits));
45
+ const hitCount = Number(maxHits);
46
+ const hitCountValid = Number.isInteger(hitCount) && hitCount >= 1;
44
47
  const isPhmmer = search.program === 'phmmer';
45
48
  const geneIds = useMemo(() => getGeneIdentifiers(feature), [feature]);
46
49
  const { results: cachedResults, error: cachedResultsError } = useCachedBlastResults(geneIds);
47
- const transcriptSelection = useTranscriptSelection({ feature, view });
48
- const { selectedTranscript, proteinSequence } = transcriptSelection;
50
+ const transcriptSelection = useTranscriptSelection({
51
+ feature,
52
+ view,
53
+ preferredTranscriptId,
54
+ });
55
+ const { selectedTranscript, proteinSequence, sequenceStatus } = transcriptSelection;
49
56
  const e = transcriptSelection.error ?? launchViewError ?? cachedResultsError;
50
57
  return (React.createElement(React.Fragment, null,
51
58
  React.createElement(LaunchPanelContent, { error: e },
@@ -55,36 +62,30 @@ const BlastAutomaticPanel = observer(function ({ handleClose, feature, model, ch
55
62
  // program replaces the database rather than keeping a name the new
56
63
  // one has never heard of
57
64
  setSearch(defaultSearchFor(event.target.value));
58
- } }, searchPrograms.map(val => (React.createElement(MenuItem, { value: val, key: val }, val)))),
65
+ } }, searchPrograms.map(val => (React.createElement(MenuItem, { value: val, key: val }, searchProgramLabels[val])))),
59
66
  React.createElement(TextField2, { variant: "outlined", label: "Database", className: classes.databaseField, select: true, value: search.database, onChange: event => {
60
67
  setSearch({
61
68
  program: search.program,
62
69
  database: event.target.value,
63
70
  });
64
- } }, databaseOptionsFor(search.program).map(val => (React.createElement(MenuItem, { value: val, key: val }, val)))),
71
+ } }, databaseOptionsFor(search.program).map(val => (React.createElement(MenuItem, { value: val, key: val }, databaseLabel(val))))),
65
72
  isPhmmer ? null : (React.createElement(MsaAlgorithmSelect, { className: classes.selectField, value: selectedMsaAlgorithm, onChange: setSelectedMsaAlgorithm })),
73
+ React.createElement(TextField2, { variant: "outlined", label: "Hits", className: classes.selectField, type: "number", value: maxHits, onChange: event => {
74
+ setMaxHits(event.target.value);
75
+ }, error: !hitCountValid, helperText: "best-scoring sequences to keep" }),
66
76
  React.createElement(TranscriptSelector, { feature: feature, ...transcriptSelection }),
67
- React.createElement(Typography, { className: classes.infoText },
77
+ React.createElement(Typography, { variant: "body2", className: classes.infoText },
68
78
  isPhmmer
69
- ? `phmmer searches UniProtKB with a profile HMM built from the query,
70
- so it aligns the hits as it finds them and that alignment is used
71
- directly — nothing is realigned afterwards. The tree is then built
72
- from it by neighbour-joining. A hit matching the query in more
73
- than one place appears once per matched region.`
74
- : `This panel will automatically submit a blastp query to EBI, which
75
- searches UniProtKB. Searches usually finish in under a minute, and
76
- swissprot returns curated sequences that align more cleanly than
77
- the many near-identical entries a TrEMBL search brings back. After
78
- completion, all the hits will be run through a multiple sequence
79
- alignment.`,
79
+ ? 'phmmer aligns the hits as it finds them, so nothing is realigned afterwards.'
80
+ : 'The hits come back from EBI and are then run through the chosen aligner.',
80
81
  ' ',
81
- "Searching NCBI's nr needs the manual approach: NCBI no longer lets a browser read responses from Blast.cgi."),
82
+ "The EBI queue is the wait, and it runs from seconds to many minutes."),
82
83
  cachedResults.length > 0 ? (React.createElement(Accordion, { className: classes.cachedResultsAccordion },
83
84
  React.createElement(AccordionSummary, { expandIcon: React.createElement(ExpandMoreIcon, null) },
84
85
  React.createElement(Typography, null, "Previous BLAST Results")),
85
86
  React.createElement(AccordionDetails, null,
86
87
  React.createElement(CachedBlastResults, { model: model, handleClose: handleClose, feature: feature })))) : null),
87
- React.createElement(SubmitCancelActions, { model: model, submitDisabled: !proteinSequence, onSubmit: () => {
88
+ React.createElement(SubmitCancelActions, { model: model, hint: React.createElement(SequenceStatusMessage, { status: sequenceStatus }), submitDisabled: !proteinSequence || !hitCountValid, onSubmit: () => {
88
89
  try {
89
90
  if (selectedTranscript) {
90
91
  setLaunchViewError(undefined);
@@ -96,14 +97,16 @@ const BlastAutomaticPanel = observer(function ({ handleClose, feature, model, ch
96
97
  ? {
97
98
  searchProgram: 'phmmer',
98
99
  blastDatabase: search.database,
99
- selectedTranscript,
100
+ maxHits: hitCount,
101
+ selectedTranscript: selectedTranscript.toJSON(),
100
102
  proteinSequence,
101
103
  }
102
104
  : {
103
105
  searchProgram: 'blastp',
104
106
  blastDatabase: search.database,
105
107
  msaAlgorithm: selectedMsaAlgorithm,
106
- selectedTranscript,
108
+ maxHits: hitCount,
109
+ selectedTranscript: selectedTranscript.toJSON(),
107
110
  proteinSequence,
108
111
  },
109
112
  });
@@ -11,10 +11,12 @@ import type { AbstractTrackModel, Feature } from '@jbrowse/core/util';
11
11
  * button -- leaving them to find the Manual upload tab, re-pick the transcript
12
12
  * they had already chosen here, and hand-type the row name.
13
13
  */
14
- declare const BlastManualPanel: ({ handleClose, feature, model, children, }: {
14
+ declare const BlastManualPanel: ({ handleClose, feature, model, children, preferredTranscriptId, }: {
15
15
  children: React.ReactNode;
16
16
  model: AbstractTrackModel;
17
17
  feature: Feature;
18
18
  handleClose: () => void;
19
+ /** the isoform the user right-clicked, preselected in the picker */
20
+ preferredTranscriptId?: string;
19
21
  }) => React.JSX.Element;
20
22
  export default BlastManualPanel;
@@ -43,13 +43,17 @@ const useStyles = makeStyles()({
43
43
  * button -- leaving them to find the Manual upload tab, re-pick the transcript
44
44
  * they had already chosen here, and hand-type the row name.
45
45
  */
46
- const BlastManualPanel = observer(function ({ handleClose, feature, model, children, }) {
46
+ const BlastManualPanel = observer(function ({ handleClose, feature, model, children, preferredTranscriptId, }) {
47
47
  const { classes } = useStyles();
48
48
  const view = getLinearGenomeView(model);
49
49
  const [launchViewError, setLaunchViewError] = useState();
50
50
  const [msaText, setMsaText] = useState('');
51
51
  const [treeText, setTreeText] = useState('');
52
- const transcriptSelection = useTranscriptSelection({ feature, view });
52
+ const transcriptSelection = useTranscriptSelection({
53
+ feature,
54
+ view,
55
+ preferredTranscriptId,
56
+ });
53
57
  const { proteinSequence, selectedTranscript, error } = transcriptSelection;
54
58
  const queryRow = useQueryRowName(msaText, proteinSequence);
55
59
  const s2 = cleanProteinSequence(proteinSequence);
@@ -88,6 +92,7 @@ const BlastManualPanel = observer(function ({ handleClose, feature, model, child
88
92
  view,
89
93
  feature: selectedTranscript,
90
94
  querySeqName: queryRow.querySeqName,
95
+ querySeqOffset: queryRow.querySeqOffset,
91
96
  data: { msa: msaText, tree: treeText },
92
97
  });
93
98
  handleClose();
@@ -1,23 +1,27 @@
1
1
  import React from 'react';
2
2
  import type { AbstractTrackModel, Feature } from '@jbrowse/core/util';
3
3
  declare const panelMap: {
4
- readonly automatic: ({ handleClose, feature, model, children, }: {
4
+ readonly automatic: ({ handleClose, feature, model, children, preferredTranscriptId, }: {
5
5
  model: AbstractTrackModel;
6
6
  feature: Feature;
7
7
  handleClose: () => void;
8
8
  children: React.ReactNode;
9
+ preferredTranscriptId?: string;
9
10
  }) => React.JSX.Element;
10
- readonly manual: ({ handleClose, feature, model, children, }: {
11
+ readonly manual: ({ handleClose, feature, model, children, preferredTranscriptId, }: {
11
12
  children: React.ReactNode;
12
13
  model: AbstractTrackModel;
13
14
  feature: Feature;
14
15
  handleClose: () => void;
16
+ preferredTranscriptId?: string;
15
17
  }) => React.JSX.Element;
16
18
  };
17
19
  export type BlastLookupMethod = keyof typeof panelMap;
18
- export default function BlastPanel({ handleClose, model, feature, }: {
20
+ export default function BlastPanel({ handleClose, model, feature, preferredTranscriptId, }: {
19
21
  handleClose: () => void;
20
22
  model: AbstractTrackModel;
21
23
  feature: Feature;
24
+ /** the isoform the user right-clicked, preselected in the picker */
25
+ preferredTranscriptId?: string;
22
26
  }): React.JSX.Element;
23
27
  export {};
@@ -1,6 +1,6 @@
1
1
  import React, { useState } from 'react';
2
2
  import SettingsIcon from '@mui/icons-material/Settings';
3
- import { IconButton } from '@mui/material';
3
+ import { IconButton, Tooltip } from '@mui/material';
4
4
  import { makeStyles } from 'tss-react/mui';
5
5
  import { DEFAULT_EBI_EMAIL, EBI_EMAIL_STORAGE_KEY, } from '../../../utils/ebiJobDispatcher';
6
6
  import { useLocalStorage } from '../../../utils/useLocalStorage';
@@ -17,18 +17,19 @@ const panelMap = {
17
17
  automatic: BlastAutomaticPanel,
18
18
  manual: BlastManualPanel,
19
19
  };
20
- export default function BlastPanel({ handleClose, model, feature, }) {
20
+ export default function BlastPanel({ handleClose, model, feature, preferredTranscriptId, }) {
21
21
  const [lookupMethod, setLookupMethod] = useState('automatic');
22
22
  const [ebiEmail, setEbiEmail] = useLocalStorage(EBI_EMAIL_STORAGE_KEY, DEFAULT_EBI_EMAIL);
23
23
  const [settingsOpen, setSettingsOpen] = useState(false);
24
24
  const { classes } = useStyles();
25
25
  const Panel = panelMap[lookupMethod];
26
26
  return (React.createElement(React.Fragment, null,
27
- React.createElement(IconButton, { className: classes.settingsButton, size: "small", onClick: () => {
28
- setSettingsOpen(true);
29
- } },
30
- React.createElement(SettingsIcon, null)),
31
- React.createElement(Panel, { model: model, feature: feature, handleClose: handleClose },
27
+ React.createElement(Tooltip, { title: `EBI settings — jobs are submitted with ${ebiEmail}` },
28
+ React.createElement(IconButton, { className: classes.settingsButton, "aria-label": "EBI settings", size: "small", onClick: () => {
29
+ setSettingsOpen(true);
30
+ } },
31
+ React.createElement(SettingsIcon, null))),
32
+ React.createElement(Panel, { model: model, feature: feature, handleClose: handleClose, preferredTranscriptId: preferredTranscriptId },
32
33
  React.createElement(BlastMethodSelector, { lookupMethod: lookupMethod, setLookupMethod: setLookupMethod })),
33
34
  settingsOpen ? (React.createElement(BlastSettingsDialog, { ebiEmail: ebiEmail, handleClose: settings => {
34
35
  if (settings) {
@@ -17,9 +17,9 @@ export default function BlastSettingsDialog({ handleClose, ebiEmail, }) {
17
17
  return (React.createElement(Dialog, { open: true, maxWidth: "lg", onClose: () => {
18
18
  handleClose();
19
19
  } },
20
- React.createElement(DialogTitle, null, "BLAST Settings"),
20
+ React.createElement(DialogTitle, null, "EBI settings"),
21
21
  React.createElement(DialogContent, null,
22
- React.createElement(Typography, { variant: "subtitle2", className: classes.help }, "Searches run at EBI, which asks for a contact address on every job so they can reach whoever is generating the load. If your site sends real volume, use your own."),
22
+ React.createElement(Typography, { variant: "subtitle2", className: classes.help }, "Every search this tab runs is submitted to EBI with this address, which is how they reach whoever is generating the load. If your site sends real volume, use your own."),
23
23
  React.createElement(TextField2, { autoFocus: true, margin: "dense", label: "EBI contact email", fullWidth: true, variant: "outlined", value: tempEbiEmail, className: classes.field, onChange: e => {
24
24
  setTempEbiEmail(e.target.value);
25
25
  } }),
@@ -1,9 +1,11 @@
1
1
  import React from 'react';
2
2
  import { MenuItem } from '@mui/material';
3
3
  import TextField2 from '../../../components/TextField2';
4
- import { msaAlgorithms } from './consts';
4
+ import { msaAlgorithmLabels, msaAlgorithms } from './consts';
5
5
  export default function MsaAlgorithmSelect({ value, onChange, className, }) {
6
- return (React.createElement(TextField2, { variant: "outlined", label: "MSA Algorithm", className: className, select: true, value: value, onChange: event => {
6
+ return (React.createElement(TextField2, { variant: "outlined", label: "MSA Algorithm", className: className, select: true, value: value, helperText: value === 'browser'
7
+ ? 'no EBI job; rows aligned to the query'
8
+ : undefined, onChange: event => {
7
9
  onChange(event.target.value);
8
- } }, msaAlgorithms.map(val => (React.createElement(MenuItem, { value: val, key: val }, val)))));
10
+ } }, msaAlgorithms.map(val => (React.createElement(MenuItem, { value: val, key: val }, msaAlgorithmLabels[val])))));
9
11
  }
@@ -5,8 +5,20 @@
5
5
  * cannot read a response from it at all. See docs/blast.md.
6
6
  */
7
7
  export declare const BASE_BLAST_URL = "https://blast.ncbi.nlm.nih.gov/Blast.cgi";
8
- export declare const msaAlgorithms: readonly ["clustalo", "muscle", "kalign", "mafft"];
8
+ /** The aligners EBI's Job Dispatcher runs, each a tool name at their REST api. */
9
+ export declare const ebiMsaAlgorithms: readonly ["clustalo", "muscle", "kalign", "mafft"];
10
+ export type EbiMsaAlgorithm = (typeof ebiMsaAlgorithms)[number];
11
+ /**
12
+ * `browser` is no job at all: each sequence is aligned to the query in the
13
+ * page and the rows merged on the query (utils/browserAlign.ts), with the tree
14
+ * built by react-msaview's neighbour joining. It is the aligner for a launch
15
+ * that must not depend on EBI, and for one that wants to be quick -- a
16
+ * hundred rows take a second or two against a Job Dispatcher queue that has
17
+ * been measured at anything from ten seconds to fifteen minutes.
18
+ */
19
+ export declare const msaAlgorithms: readonly ["clustalo", "muscle", "kalign", "mafft", "browser"];
9
20
  export type MsaAlgorithm = (typeof msaAlgorithms)[number];
21
+ export declare const msaAlgorithmLabels: Record<MsaAlgorithm, string>;
10
22
  /**
11
23
  * EBI rejects a submission naming a database outside its own list with a 400,
12
24
  * so every value here has to appear in
@@ -19,15 +31,24 @@ export type BlastDatabase = (typeof blastDatabaseOptions)[number];
19
31
  export declare const defaultBlastDatabase: BlastDatabase;
20
32
  export declare const searchPrograms: readonly ["blastp", "phmmer"];
21
33
  export type SearchProgram = (typeof searchPrograms)[number];
34
+ export declare const searchProgramLabels: Record<SearchProgram, string>;
22
35
  /**
23
36
  * phmmer offers PDB, AlphaFold, Ensembl Genomes, MEROPS and ChEMBL too, but
24
- * targets outside UniProt carry no OS=/OX= in their description, so those rows
37
+ * targets outside UniProt carry no species in their description, so those rows
25
38
  * would lose their species and common name. Only the databases that label their
26
39
  * hits are offered.
40
+ *
41
+ * rp15..rp75 are the Representative Proteomes: UniProt's reference proteomes
42
+ * thinned so that no two are more than 15% (35%, 55%, 75%) similar, which is
43
+ * the widest taxonomic spread per hit that any of these databases gives. rp15
44
+ * is the one to reach for when the question is "what is this like across all
45
+ * of life"; swissprot when it is "what is this like in the curated set".
27
46
  */
28
- export declare const phmmerDatabaseOptions: readonly ["swissprot", "uniprotkb", "uniprotrefprot"];
47
+ export declare const phmmerDatabaseOptions: readonly ["swissprot", "uniprotkb", "uniprotrefprot", "rp75", "rp55", "rp35", "rp15"];
29
48
  export type PhmmerDatabase = (typeof phmmerDatabaseOptions)[number];
30
49
  export declare const defaultPhmmerDatabase: PhmmerDatabase;
50
+ export declare const defaultMaxHits = 100;
51
+ export declare function snapBlastHitCount(maxHits: number): number;
31
52
  /**
32
53
  * A program together with a database that program actually has.
33
54
  *
@@ -44,4 +65,5 @@ export type SearchChoice = {
44
65
  database: PhmmerDatabase;
45
66
  };
46
67
  export declare function defaultSearchFor(program: SearchProgram): SearchChoice;
47
- export declare function databaseOptionsFor(program: SearchProgram): readonly ["uniprotkb_swissprot", "uniprotkb", "pan_proteomes", "uniprotkb_trembl"] | readonly ["swissprot", "uniprotkb", "uniprotrefprot"];
68
+ export declare function databaseOptionsFor(program: SearchProgram): readonly ["uniprotkb_swissprot", "uniprotkb", "pan_proteomes", "uniprotkb_trembl"] | readonly ["swissprot", "uniprotkb", "uniprotrefprot", "rp75", "rp55", "rp35", "rp15"];
69
+ export declare function databaseLabel(database: BlastDatabase | PhmmerDatabase): string;
@@ -5,7 +5,29 @@
5
5
  * cannot read a response from it at all. See docs/blast.md.
6
6
  */
7
7
  export const BASE_BLAST_URL = 'https://blast.ncbi.nlm.nih.gov/Blast.cgi';
8
- export const msaAlgorithms = ['clustalo', 'muscle', 'kalign', 'mafft'];
8
+ /** The aligners EBI's Job Dispatcher runs, each a tool name at their REST api. */
9
+ export const ebiMsaAlgorithms = [
10
+ 'clustalo',
11
+ 'muscle',
12
+ 'kalign',
13
+ 'mafft',
14
+ ];
15
+ /**
16
+ * `browser` is no job at all: each sequence is aligned to the query in the
17
+ * page and the rows merged on the query (utils/browserAlign.ts), with the tree
18
+ * built by react-msaview's neighbour joining. It is the aligner for a launch
19
+ * that must not depend on EBI, and for one that wants to be quick -- a
20
+ * hundred rows take a second or two against a Job Dispatcher queue that has
21
+ * been measured at anything from ten seconds to fifteen minutes.
22
+ */
23
+ export const msaAlgorithms = [...ebiMsaAlgorithms, 'browser'];
24
+ export const msaAlgorithmLabels = {
25
+ clustalo: 'Clustal Omega (EBI)',
26
+ muscle: 'MUSCLE (EBI)',
27
+ kalign: 'Kalign (EBI)',
28
+ mafft: 'MAFFT (EBI)',
29
+ browser: 'in browser, query-anchored',
30
+ };
9
31
  /**
10
32
  * EBI rejects a submission naming a database outside its own list with a 400,
11
33
  * so every value here has to appear in
@@ -23,18 +45,42 @@ export const blastDatabaseOptions = [
23
45
  // many near-identical TrEMBL entries an alignment reads poorly
24
46
  export const defaultBlastDatabase = 'uniprotkb_swissprot';
25
47
  export const searchPrograms = ['blastp', 'phmmer'];
48
+ export const searchProgramLabels = {
49
+ blastp: 'blastp (sequence search)',
50
+ phmmer: 'phmmer (profile HMM search)',
51
+ };
26
52
  /**
27
53
  * phmmer offers PDB, AlphaFold, Ensembl Genomes, MEROPS and ChEMBL too, but
28
- * targets outside UniProt carry no OS=/OX= in their description, so those rows
54
+ * targets outside UniProt carry no species in their description, so those rows
29
55
  * would lose their species and common name. Only the databases that label their
30
56
  * hits are offered.
57
+ *
58
+ * rp15..rp75 are the Representative Proteomes: UniProt's reference proteomes
59
+ * thinned so that no two are more than 15% (35%, 55%, 75%) similar, which is
60
+ * the widest taxonomic spread per hit that any of these databases gives. rp15
61
+ * is the one to reach for when the question is "what is this like across all
62
+ * of life"; swissprot when it is "what is this like in the curated set".
31
63
  */
32
64
  export const phmmerDatabaseOptions = [
33
65
  'swissprot',
34
66
  'uniprotkb',
35
67
  'uniprotrefprot',
68
+ 'rp75',
69
+ 'rp55',
70
+ 'rp35',
71
+ 'rp15',
36
72
  ];
37
73
  export const defaultPhmmerDatabase = 'swissprot';
74
+ /**
75
+ * The hit counts EBI's ncbiblast accepts for `alignments` and `scores`. A value
76
+ * off this list is a 400 at submit time, so a request is rounded up to the next
77
+ * one on it.
78
+ */
79
+ const blastHitCounts = [5, 10, 20, 50, 100, 150, 200, 250, 500, 750, 1000];
80
+ export const defaultMaxHits = 100;
81
+ export function snapBlastHitCount(maxHits) {
82
+ return blastHitCounts.find(n => n >= maxHits) ?? blastHitCounts.at(-1);
83
+ }
38
84
  export function defaultSearchFor(program) {
39
85
  return program === 'phmmer'
40
86
  ? { program, database: defaultPhmmerDatabase }
@@ -43,3 +89,23 @@ export function defaultSearchFor(program) {
43
89
  export function databaseOptionsFor(program) {
44
90
  return program === 'phmmer' ? phmmerDatabaseOptions : blastDatabaseOptions;
45
91
  }
92
+ /**
93
+ * What each database is called in the menu. The keys are what EBI is sent and
94
+ * are not negotiable; `uniprotkb_swissprot` and `swissprot` are the same
95
+ * collection under each service's own name, so they read the same here.
96
+ */
97
+ const databaseLabels = {
98
+ uniprotkb_swissprot: 'UniProtKB/Swiss-Prot (curated)',
99
+ swissprot: 'UniProtKB/Swiss-Prot (curated)',
100
+ uniprotkb: 'UniProtKB (all entries)',
101
+ uniprotkb_trembl: 'UniProtKB/TrEMBL (unreviewed)',
102
+ pan_proteomes: 'Pan-proteomes',
103
+ uniprotrefprot: 'UniProt reference proteomes',
104
+ rp75: 'Representative proteomes, 75%',
105
+ rp55: 'Representative proteomes, 55%',
106
+ rp35: 'Representative proteomes, 35%',
107
+ rp15: 'Representative proteomes, 15% (widest spread)',
108
+ };
109
+ export function databaseLabel(database) {
110
+ return databaseLabels[database];
111
+ }