@molgenis/vip-report-template 6.2.0 → 7.0.1

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 (285) hide show
  1. package/.nvmrc +1 -1
  2. package/.travis.yml +9 -9
  3. package/README.md +411 -1
  4. package/eslint.config.mjs +11 -0
  5. package/package.json +40 -35
  6. package/scripts/deploy_npm_registry.sh +5 -0
  7. package/src/App.tsx +30 -24
  8. package/src/assets/sass/main.scss +12 -4
  9. package/src/components/Allele.tsx +95 -0
  10. package/src/components/Anchor.tsx +1 -1
  11. package/src/components/Breadcrumb.tsx +6 -4
  12. package/src/components/DatasetDropdown.tsx +12 -25
  13. package/src/components/ErrorNotification.tsx +9 -0
  14. package/src/components/GenomeBrowser.tsx +40 -23
  15. package/src/components/HpoTerm.tsx +1 -1
  16. package/src/components/{record/Pager.tsx → Pager.tsx} +21 -14
  17. package/src/components/RecordsPerPage.tsx +9 -7
  18. package/src/components/RecordsTable.tsx +130 -0
  19. package/src/components/SampleTable.tsx +70 -98
  20. package/src/components/SearchBox.tsx +8 -2
  21. package/src/components/Sort.tsx +28 -25
  22. package/src/components/Table.tsx +16 -0
  23. package/src/components/Tooltip.tsx +20 -0
  24. package/src/components/VariantBreadcrumb.tsx +54 -0
  25. package/src/components/VariantConsequenceContainer.tsx +100 -0
  26. package/src/components/VariantConsequenceTable.tsx +58 -0
  27. package/src/components/VariantContainer.tsx +71 -0
  28. package/src/components/VariantFilters.tsx +27 -0
  29. package/src/components/VariantGenotypeTable.tsx +44 -0
  30. package/src/components/VariantInfoTable.tsx +24 -33
  31. package/src/components/VariantResults.tsx +103 -0
  32. package/src/components/VariantTable.tsx +62 -66
  33. package/src/components/VariantTypeSelect.tsx +34 -0
  34. package/src/components/VariantsContainer.tsx +150 -0
  35. package/src/components/VariantsContainerHeader.tsx +70 -0
  36. package/src/components/field/Field.tsx +80 -0
  37. package/src/components/field/FieldAlt.tsx +19 -0
  38. package/src/components/field/FieldChrom.tsx +6 -0
  39. package/src/components/{record/Id.tsx → field/FieldFilter.tsx} +2 -1
  40. package/src/components/field/FieldFormat.tsx +10 -0
  41. package/src/components/{record/Filter.tsx → field/FieldId.tsx} +1 -1
  42. package/src/components/field/FieldPos.tsx +6 -0
  43. package/src/components/field/FieldQual.tsx +6 -0
  44. package/src/components/field/FieldRef.tsx +8 -0
  45. package/src/components/field/composed/FieldClinVar.tsx +72 -0
  46. package/src/components/field/composed/FieldComposed.tsx +68 -0
  47. package/src/components/field/composed/FieldGene.tsx +39 -0
  48. package/src/components/field/composed/FieldGenotype.tsx +35 -0
  49. package/src/components/{record/format/GenotypeField.tsx → field/composed/FieldGenotypeSnvSv.tsx} +20 -16
  50. package/src/components/field/composed/FieldGenotypeStr.tsx +31 -0
  51. package/src/components/field/composed/FieldGnomAd.tsx +58 -0
  52. package/src/components/field/composed/FieldHpo.tsx +50 -0
  53. package/src/components/field/composed/FieldInheritanceModes.tsx +32 -0
  54. package/src/components/field/composed/FieldLocus.tsx +18 -0
  55. package/src/components/field/composed/FieldVipC.tsx +25 -0
  56. package/src/components/field/composed/FieldVipCS.tsx +15 -0
  57. package/src/components/field/composed/FieldVkgl.tsx +37 -0
  58. package/src/components/field/genotype/FieldGenotype.tsx +19 -0
  59. package/src/components/field/genotype/FieldGenotypeType.tsx +9 -0
  60. package/src/components/field/info/FieldConsequence.tsx +15 -0
  61. package/src/components/{record/info/Hgvs.tsx → field/info/FieldHgvs.tsx} +4 -6
  62. package/src/components/field/info/FieldInfo.tsx +27 -0
  63. package/src/components/{record/info/PubMed.tsx → field/info/FieldPubMed.tsx} +4 -7
  64. package/src/components/field/typed/FieldCategorical.tsx +17 -0
  65. package/src/components/{record/field/FieldValueCharacter.tsx → field/typed/FieldCharacter.tsx} +3 -2
  66. package/src/components/{record/field/FieldValueFlag.tsx → field/typed/FieldFlag.tsx} +3 -2
  67. package/src/components/{record/field/FieldValueFloat.tsx → field/typed/FieldFloat.tsx} +3 -2
  68. package/src/components/{record/field/FieldValueInteger.tsx → field/typed/FieldInteger.tsx} +3 -2
  69. package/src/components/{record/field/FieldValueString.tsx → field/typed/FieldString.tsx} +3 -2
  70. package/src/components/field/typed/FieldTyped.tsx +20 -0
  71. package/src/components/field/typed/FieldTypedItem.tsx +49 -0
  72. package/src/components/field/typed/FieldTypedMultiple.tsx +21 -0
  73. package/src/components/filter/Filter.tsx +56 -48
  74. package/src/components/filter/FilterWrapper.scss +23 -0
  75. package/src/components/filter/FilterWrapper.tsx +63 -0
  76. package/src/components/filter/composed/FilterAllelicImbalance.tsx +26 -0
  77. package/src/components/filter/composed/FilterComposed.tsx +92 -0
  78. package/src/components/filter/composed/FilterDeNovo.tsx +35 -0
  79. package/src/components/filter/composed/FilterHpo.tsx +16 -0
  80. package/src/components/filter/composed/FilterInheritance.tsx +42 -0
  81. package/src/components/filter/composed/FilterLocus.tsx +75 -0
  82. package/src/components/filter/composed/FilterVipC.tsx +16 -0
  83. package/src/components/filter/composed/FilterVipCS.tsx +16 -0
  84. package/src/components/filter/fixed/FilterAlt.tsx +20 -0
  85. package/src/components/filter/fixed/FilterChrom.tsx +22 -0
  86. package/src/components/filter/fixed/FilterFilter.tsx +20 -0
  87. package/src/components/filter/fixed/FilterFixed.tsx +96 -0
  88. package/src/components/filter/fixed/FilterId.tsx +20 -0
  89. package/src/components/filter/fixed/FilterPos.tsx +22 -0
  90. package/src/components/filter/fixed/FilterQual.tsx +21 -0
  91. package/src/components/filter/fixed/FilterRef.tsx +22 -0
  92. package/src/components/filter/typed/FilterCategorical.tsx +119 -0
  93. package/src/components/filter/typed/FilterFlag.tsx +23 -0
  94. package/src/components/filter/typed/FilterInterval.tsx +72 -0
  95. package/src/components/filter/typed/FilterString.tsx +43 -0
  96. package/src/components/filter/typed/FilterTyped.tsx +56 -0
  97. package/src/components/form/ButtonApply.tsx +11 -0
  98. package/src/components/form/ButtonDownload.tsx +11 -0
  99. package/src/components/form/ButtonReset.tsx +9 -0
  100. package/src/components/{Checkbox.tsx → form/Checkbox.tsx} +4 -9
  101. package/src/components/form/Input.tsx +19 -0
  102. package/src/components/form/Select.scss +7 -0
  103. package/src/components/form/Select.tsx +34 -0
  104. package/src/components/tree/DecisionTreeBoolMultiQuery.tsx +1 -1
  105. package/src/components/tree/DecisionTreeBoolQuery.tsx +1 -1
  106. package/src/components/tree/DecisionTreeNode.tsx +41 -39
  107. package/src/components/tree/DecisionTreeNodeBool.tsx +1 -1
  108. package/src/components/tree/DecisionTreeNodeBoolMulti.tsx +1 -1
  109. package/src/components/tree/DecisionTreeNodeCategorical.tsx +1 -1
  110. package/src/components/tree/DecisionTreeNodeExists.tsx +1 -1
  111. package/src/components/tree/DecisionTreeNodeLeaf.tsx +1 -1
  112. package/src/components/tree/DecisionTreeOutcomeNode.tsx +1 -1
  113. package/src/components/tree/DecisionTreePath.tsx +1 -1
  114. package/src/igv.d.ts +2 -1
  115. package/src/index.tsx +48 -19
  116. package/src/mocks/GRCh37/decisionTree.json +23 -22
  117. package/src/mocks/GRCh37/field_metadata.json +435 -95
  118. package/src/mocks/GRCh37/sampleTree.json +21 -1
  119. package/src/mocks/GRCh37/static.ts +62 -134
  120. package/src/mocks/GRCh37/vcf/family.vcf.blob +9 -3
  121. package/src/mocks/GRCh38/decisionTree.json +52 -33
  122. package/src/mocks/GRCh38/decisionTreeStr.json +572 -0
  123. package/src/mocks/GRCh38/fasta/chr1_149380406-149403321.fasta.gz.blob +0 -0
  124. package/src/mocks/GRCh38/field_metadata.json +435 -95
  125. package/src/mocks/GRCh38/sampleTree.json +175 -0
  126. package/src/mocks/GRCh38/static.ts +101 -42
  127. package/src/mocks/GRCh38/str.cram.blob +0 -0
  128. package/src/mocks/GRCh38/str.cram.crai.blob +0 -0
  129. package/src/mocks/GRCh38/vcf/family.vcf.blob +25 -24
  130. package/src/mocks/GRCh38/vcf/no_vep.vcf.blob +29 -28
  131. package/src/mocks/GRCh38/vcf/samples_0.vcf.blob +28 -27
  132. package/src/mocks/GRCh38/vcf/samples_1.vcf.blob +29 -28
  133. package/src/mocks/GRCh38/vcf/samples_100.vcf.blob +28 -27
  134. package/src/mocks/GRCh38/vcf/str.vcf.blob +321 -0
  135. package/src/mocks/MockApiClient.ts +339 -332
  136. package/src/mocks/config_cram.json +701 -0
  137. package/src/mocks/config_vcf.json +699 -0
  138. package/src/store/app.ts +30 -0
  139. package/src/store/index.tsx +3 -168
  140. package/src/store/variants.ts +182 -0
  141. package/src/types/config.d.ts +190 -0
  142. package/src/types/configCellComposed.d.ts +86 -0
  143. package/src/types/configCells.d.ts +129 -0
  144. package/src/types/configFilter.d.ts +80 -0
  145. package/src/types/configFilterComposed.d.ts +60 -0
  146. package/src/types/configSort.d.ts +13 -0
  147. package/src/types/filter.d.ts +17 -0
  148. package/src/types/store.d.ts +34 -0
  149. package/src/utils/api.ts +281 -0
  150. package/src/utils/config/config.ts +182 -0
  151. package/src/utils/config/configCells.ts +74 -0
  152. package/src/utils/config/configCellsComposed.ts +508 -0
  153. package/src/utils/config/configCellsField.ts +61 -0
  154. package/src/utils/config/configCellsFixed.ts +126 -0
  155. package/src/utils/config/configFilters.ts +46 -0
  156. package/src/utils/config/configFiltersComposed.ts +208 -0
  157. package/src/utils/config/configFiltersField.ts +49 -0
  158. package/src/utils/config/configFiltersFixed.ts +106 -0
  159. package/src/utils/config/configSorts.ts +44 -0
  160. package/src/utils/config/configValidator.ts +380 -0
  161. package/src/utils/config/configVip.ts +25 -0
  162. package/src/utils/csq.ts +115 -0
  163. package/src/utils/decisionTree.ts +45 -0
  164. package/src/utils/download.ts +30 -0
  165. package/src/utils/error.ts +69 -0
  166. package/src/utils/query/query.ts +55 -0
  167. package/src/utils/query/queryFilter.ts +132 -0
  168. package/src/utils/query/queryFilterComposed.ts +247 -0
  169. package/src/utils/query/queryFilterField.ts +75 -0
  170. package/src/utils/query/queryFilterFixed.ts +44 -0
  171. package/src/utils/query/querySample.ts +18 -0
  172. package/src/utils/query/queryVariantType.ts +76 -0
  173. package/src/utils/query/selector.ts +41 -0
  174. package/src/utils/{sortUtils.ts → query/sort.ts} +32 -11
  175. package/src/utils/sample.ts +19 -35
  176. package/src/utils/utils.ts +66 -2
  177. package/src/utils/variantType.ts +43 -0
  178. package/src/utils/vcf.ts +352 -0
  179. package/src/views/Help.tsx +109 -114
  180. package/src/views/Home.tsx +3 -2
  181. package/src/views/Sample.tsx +12 -7
  182. package/src/views/SampleVariant.tsx +23 -112
  183. package/src/views/SampleVariantConsequence.tsx +54 -144
  184. package/src/views/SampleVariants.tsx +33 -445
  185. package/src/views/SampleVariantsRedirect.tsx +20 -0
  186. package/src/views/Samples.tsx +7 -10
  187. package/src/views/Variant.tsx +31 -61
  188. package/src/views/VariantConsequence.tsx +42 -72
  189. package/src/views/Variants.tsx +29 -138
  190. package/src/views/VariantsRedirect.tsx +25 -0
  191. package/src/views/data/data.tsx +32 -6
  192. package/tests/store/variants.test.ts +122 -0
  193. package/tests/utils/config/config.test.ts +167 -0
  194. package/tests/utils/config/configCells.test.ts +86 -0
  195. package/tests/utils/config/configCellsComposed.test.ts +1163 -0
  196. package/tests/utils/config/configCellsField.test.ts +164 -0
  197. package/tests/utils/config/configCellsFixed.test.ts +99 -0
  198. package/tests/utils/config/configFilters.test.ts +80 -0
  199. package/tests/utils/config/configFiltersComposed.test.ts +504 -0
  200. package/tests/utils/config/configFiltersField.test.ts +140 -0
  201. package/tests/utils/config/configFiltersFixed.test.ts +81 -0
  202. package/tests/utils/config/configSorts.test.ts +55 -0
  203. package/tests/utils/config/configValidator.test.ts +56 -0
  204. package/tests/utils/config/configVip.test.ts +53 -0
  205. package/tests/utils/decisionTree.test.ts +71 -0
  206. package/tests/utils/download.test.ts +20 -0
  207. package/tests/utils/query/query.test.ts +84 -0
  208. package/tests/utils/query/queryFilter.test.ts +243 -0
  209. package/tests/utils/query/queryFilterComposed.test.ts +301 -0
  210. package/tests/utils/query/queryFilterField.test.ts +75 -0
  211. package/tests/utils/query/queryFilterFixed.test.ts +86 -0
  212. package/tests/utils/query/querySample.test.ts +45 -0
  213. package/tests/utils/query/queryVariantType.test.ts +56 -0
  214. package/{src/__tests__/sortUtils.test.ts → tests/utils/query/sort.test.ts} +3 -4
  215. package/tests/utils/sample.test.ts +259 -0
  216. package/tests/utils/utils.test.ts +120 -0
  217. package/tests/utils/variantType.test.ts +48 -0
  218. package/tests/utils/vcf.test.ts +649 -0
  219. package/tsconfig.json +6 -2
  220. package/vite.config.mts +20 -3
  221. package/.eslintignore +0 -4
  222. package/.eslintrc.js +0 -23
  223. package/src/Api.ts +0 -12
  224. package/src/__tests__/decisionTreeUtils.test.ts +0 -75
  225. package/src/__tests__/field.test.ts +0 -107
  226. package/src/__tests__/query.test.ts +0 -188
  227. package/src/__tests__/sample.test.ts +0 -184
  228. package/src/__tests__/utils.test.ts +0 -24
  229. package/src/__tests__/viewUtils.test.ts +0 -125
  230. package/src/components/ConsequenceTable.tsx +0 -45
  231. package/src/components/Error.tsx +0 -9
  232. package/src/components/FieldHeader.tsx +0 -26
  233. package/src/components/InfoCollapsablePane.tsx +0 -90
  234. package/src/components/VariantInfoNestedTable.tsx +0 -127
  235. package/src/components/VariantSampleTable.tsx +0 -58
  236. package/src/components/VariantsSampleTable.tsx +0 -184
  237. package/src/components/VariantsTable.tsx +0 -125
  238. package/src/components/filter/FilterAllelicBalance.tsx +0 -81
  239. package/src/components/filter/FilterCategorical.tsx +0 -81
  240. package/src/components/filter/FilterClinVar.tsx +0 -21
  241. package/src/components/filter/FilterGene.tsx +0 -34
  242. package/src/components/filter/FilterHpo.tsx +0 -161
  243. package/src/components/filter/FilterInheritance.tsx +0 -162
  244. package/src/components/filter/FilterIntegerGq.tsx +0 -47
  245. package/src/components/filter/FilterVI.tsx +0 -68
  246. package/src/components/filter/FilterVariantType.tsx +0 -146
  247. package/src/components/filter/Filters.tsx +0 -29
  248. package/src/components/filter/InfoFilter.tsx +0 -39
  249. package/src/components/filter/InfoFilters.tsx +0 -35
  250. package/src/components/filter/SampleFilters.tsx +0 -93
  251. package/src/components/filter/SamplesFilters.tsx +0 -33
  252. package/src/components/record/Allele.tsx +0 -38
  253. package/src/components/record/AlleleBreakend.tsx +0 -5
  254. package/src/components/record/AlleleMissing.tsx +0 -5
  255. package/src/components/record/AlleleNucs.tsx +0 -49
  256. package/src/components/record/AlleleSymbolic.tsx +0 -5
  257. package/src/components/record/Alt.tsx +0 -17
  258. package/src/components/record/Chrom.tsx +0 -5
  259. package/src/components/record/Format.tsx +0 -40
  260. package/src/components/record/Info.tsx +0 -55
  261. package/src/components/record/Pos.tsx +0 -5
  262. package/src/components/record/Qual.tsx +0 -5
  263. package/src/components/record/RecordDownload.tsx +0 -66
  264. package/src/components/record/Ref.tsx +0 -6
  265. package/src/components/record/field/Field.tsx +0 -36
  266. package/src/components/record/field/FieldMultipleValue.tsx +0 -22
  267. package/src/components/record/field/FieldSingleValue.tsx +0 -35
  268. package/src/components/record/info/ClinVar.tsx +0 -81
  269. package/src/components/record/info/Consequence.tsx +0 -18
  270. package/src/components/record/info/Gene.tsx +0 -56
  271. package/src/components/record/info/GnomAD.tsx +0 -54
  272. package/src/components/record/info/Hpo.tsx +0 -52
  273. package/src/components/record/info/InheritanceModes.tsx +0 -22
  274. package/src/components/record/info/VipC.tsx +0 -23
  275. package/src/components/record/info/Vkgl.tsx +0 -42
  276. package/src/mocks/GRCh37/vcf/no_vep.vcf.blob +0 -61
  277. package/src/mocks/GRCh37/vcf/samples_0.vcf.blob +0 -93
  278. package/src/mocks/GRCh37/vcf/samples_1.vcf.blob +0 -93
  279. package/src/mocks/GRCh37/vcf/samples_100.vcf.blob +0 -93
  280. package/src/utils/ApiUtils.ts +0 -263
  281. package/src/utils/csqUtils.ts +0 -27
  282. package/src/utils/decisionTreeUtils.ts +0 -31
  283. package/src/utils/field.ts +0 -49
  284. package/src/utils/query.ts +0 -154
  285. package/src/utils/viewUtils.ts +0 -32
@@ -1,81 +1,51 @@
1
- import { Component, createResource, Show } from "solid-js";
1
+ import { Component, Show } from "solid-js";
2
2
  import { createAsync, RouteSectionProps } from "@solidjs/router";
3
3
  import { Loader } from "../components/Loader";
4
- import { DecisionTree } from "@molgenis/vip-report-api/src/Api";
5
- import { VariantTable } from "../components/VariantTable";
6
- import { VariantInfoTable } from "../components/VariantInfoTable";
7
- import { Breadcrumb } from "../components/Breadcrumb";
8
- import { getSpecificConsequence } from "../utils/viewUtils";
9
- import { ConsequenceTable } from "../components/ConsequenceTable";
10
- import { DecisionTreePath } from "../components/tree/DecisionTreePath";
11
- import { getDecisionTreePath } from "../utils/decisionTreeUtils";
12
- import {
13
- EMPTY_DECISION_TREE,
14
- EMPTY_RECORDS_METADATA,
15
- fetchDecisionTree,
16
- fetchRecordsMeta,
17
- getRecordLabel,
18
- } from "../utils/ApiUtils";
19
- import { FieldMetadata } from "@molgenis/vip-report-vcf/src/MetadataParser";
20
- import { ValueArray } from "@molgenis/vip-report-vcf/src/ValueParser";
21
- import { getVariant } from "./data/data";
4
+ import { getConfig, getDecisionTree, getMetadata, getRecordById } from "./data/data";
5
+ import { parseId } from "../utils/utils.ts";
6
+ import { VariantConsequenceContainer } from "../components/VariantConsequenceContainer.tsx";
7
+ import { parseVariantType } from "../utils/variantType.ts";
8
+ import { VariantBreadcrumb } from "../components/VariantBreadcrumb.tsx";
22
9
 
23
10
  export const VariantConsequence: Component<RouteSectionProps> = (props) => {
24
- const variant = createAsync(() => getVariant(Number(props.params.variantId)));
25
- const consequenceId = () => Number(props.params.consequenceId);
11
+ const variantType = () => parseVariantType(props.params.variantType);
12
+ const consequenceId = () => parseId(props.params.consequenceId);
13
+ const metadata = createAsync(() => getMetadata());
14
+ const record = createAsync(() => getRecordById(props.params.variantId));
15
+ const config = createAsync(() => getConfig());
16
+ const decisionTree = createAsync(() => getDecisionTree());
26
17
 
27
- const [recordsMetadata] = createResource(fetchRecordsMeta, { initialValue: EMPTY_RECORDS_METADATA });
28
- const [decisionTree] = createResource(fetchDecisionTree, { initialValue: EMPTY_DECISION_TREE });
29
-
30
- const csqFields = (): FieldMetadata[] => recordsMetadata().info.CSQ?.nested?.items || [];
31
-
32
- const hasDecisionTreePathMeta = () => csqFields().findIndex((csq) => csq.id === "VIPP") !== -1;
33
18
  return (
34
- <Show when={variant()} fallback={<Loader />} keyed>
35
- {(variant) => (
36
- <>
37
- <Breadcrumb
38
- items={[
39
- { href: "/variants", text: "Variants" },
40
- { href: `/variants/${variant.id}`, text: getRecordLabel(variant) },
41
- { text: `Consequence #${consequenceId()}` },
42
- ]}
43
- />
44
- <div class="columns">
45
- <div class="column is-6">
46
- <h1 class="title is-5">Consequence</h1>
47
- <ConsequenceTable
48
- csqMetadata={csqFields()}
49
- csqValues={getSpecificConsequence(variant.data.n.CSQ as ValueArray, consequenceId())}
50
- record={variant}
51
- />
52
- </div>
53
- {hasDecisionTreePathMeta() && (
54
- <Show when={!recordsMetadata.loading && !decisionTree.loading && (decisionTree() as DecisionTree)} keyed>
55
- {(decisionTree) => (
56
- <div class="column">
57
- <h1 class="title is-5">Classification tree path</h1>
58
- <DecisionTreePath
59
- decisionTree={decisionTree}
60
- path={getDecisionTreePath(recordsMetadata(), variant, consequenceId())}
61
- />
62
- </div>
63
- )}
64
- </Show>
65
- )}
66
- </div>
67
- <div class="columns">
68
- <div class="column is-6">
69
- <h1 class="title is-5">Record</h1>
70
- <VariantTable variant={variant.data} />
71
- </div>
72
- <div class="column">
73
- <h1 class="title is-5">Info</h1>
74
- <VariantInfoTable infoFields={recordsMetadata().info} record={variant} />
75
- </div>
76
- </div>
77
- </>
78
- )}
19
+ <Show when={record()} fallback={<Loader />}>
20
+ {(record) => {
21
+ return (
22
+ <>
23
+ <VariantBreadcrumb variantType={variantType()} record={record()} consequenceId={consequenceId()} />
24
+ <Show when={metadata()} fallback={<Loader />}>
25
+ {(metadata) => (
26
+ <Show when={config()} fallback={<Loader />}>
27
+ {(config) => (
28
+ <Show when={decisionTree()} fallback={<Loader />}>
29
+ {(decisionTree) => (
30
+ <VariantConsequenceContainer
31
+ config={config()}
32
+ metadata={metadata()}
33
+ variantType={variantType()}
34
+ consequenceId={consequenceId()}
35
+ record={record()}
36
+ sample={null}
37
+ decisionTree={decisionTree()}
38
+ sampleTree={null}
39
+ />
40
+ )}
41
+ </Show>
42
+ )}
43
+ </Show>
44
+ )}
45
+ </Show>
46
+ </>
47
+ );
48
+ }}
79
49
  </Show>
80
50
  );
81
51
  };
@@ -1,147 +1,38 @@
1
- import { Component, createResource, Show } from "solid-js";
2
- import { VariantsTable } from "../components/VariantsTable";
3
- import { Pager } from "../components/record/Pager";
4
- import { SearchBox } from "../components/SearchBox";
5
- import { InfoFilters } from "../components/filter/InfoFilters";
6
- import { Sort, SortEvent } from "../components/Sort";
7
- import { RecordDownload } from "../components/record/RecordDownload";
8
- import { Breadcrumb } from "../components/Breadcrumb";
9
- import { fetchHtsFileMetadata, fetchRecords, fetchRecordsMeta } from "../utils/ApiUtils";
10
- import { flattenFieldMetadata } from "../utils/field";
11
- import { DIRECTION_ASCENDING, DIRECTION_DESCENDING } from "../utils/sortUtils";
12
- import { HtsFileMetadata, Params, SortPath } from "@molgenis/vip-report-api/src/Api";
13
- import { useStore } from "../store";
14
- import { createQuery, infoSortPath } from "../utils/query";
1
+ import { Component, Show } from "solid-js";
15
2
  import { Loader } from "../components/Loader";
16
- import { Metadata } from "@molgenis/vip-report-vcf/src/Vcf";
17
- import { arrayEquals } from "../utils/utils";
18
- import { FilterChangeEvent, FilterClearEvent } from "../components/filter/Filters";
19
- import { RecordsPerPage, RecordsPerPageEvent } from "../components/RecordsPerPage";
3
+ import { parseVariantType } from "../utils/variantType.ts";
4
+ import { createAsync, RouteSectionProps } from "@solidjs/router";
5
+ import { VariantsContainer } from "../components/VariantsContainer";
6
+ import { getConfig, getMetadata } from "./data/data";
7
+ import { VariantBreadcrumb } from "../components/VariantBreadcrumb.tsx";
8
+ import { useStore } from "../store";
9
+ import { wrapStore } from "../store/variants.ts";
10
+
11
+ export const Variants: Component<RouteSectionProps> = (props) => {
12
+ const store = useStore();
20
13
 
21
- export const VariantsView: Component = () => {
22
- const [recordsMeta] = createResource(fetchRecordsMeta);
23
- const [htsFileMeta] = createResource(fetchHtsFileMetadata);
14
+ const variantType = () => parseVariantType(props.params.variantType);
15
+ const config = createAsync(() => getConfig());
16
+ const metadata = createAsync(() => getMetadata());
24
17
 
25
18
  return (
26
19
  <>
27
- <Breadcrumb items={[{ text: "Variants" }]} />
28
- <Show when={recordsMeta() && htsFileMeta} fallback={<Loader />}>
29
- <Variants recordsMeta={recordsMeta()!} htsFileMeta={htsFileMeta()!} />
20
+ <VariantBreadcrumb variantType={variantType()} />
21
+ <Show when={config()} fallback={<Loader />}>
22
+ {(config) => (
23
+ <Show when={metadata()} fallback={<Loader />}>
24
+ {(metadata) => (
25
+ <VariantsContainer
26
+ store={wrapStore(store, null, variantType())}
27
+ config={config()}
28
+ metadata={metadata()}
29
+ variantType={variantType()}
30
+ sample={null}
31
+ />
32
+ )}
33
+ </Show>
34
+ )}
30
35
  </Show>
31
36
  </>
32
37
  );
33
38
  };
34
-
35
- export const Variants: Component<{
36
- recordsMeta: Metadata;
37
- htsFileMeta: HtsFileMetadata;
38
- }> = (props) => {
39
- const [state, actions] = useStore();
40
-
41
- const page = () => state.variants?.page;
42
- const pageSize = () => state.variants?.pageSize;
43
- const searchQuery = () => state.variants?.searchQuery;
44
- const filterQueries = () => state.variants?.filterQueries;
45
- const sort = () => state.variants?.sort;
46
-
47
- const onPageChange = (page: number) => actions.setVariantsPage(page);
48
- const onSearchChange = (search: string) => actions.setVariantsSearchQuery(search);
49
- const onFilterChange = (event: FilterChangeEvent) => actions.setVariantsFilterQuery(event.query, event.key);
50
- const onFilterClear = (event: FilterClearEvent) => actions.clearVariantsFilterQuery(event.key);
51
- const onSortChange = (event: SortEvent) => actions.setVariantsSort(event.order);
52
- const onSortClear = () => actions.setVariantsSort(null);
53
- const onRecordsPerPageChange = (event: RecordsPerPageEvent) => actions.setVariantsPageSize(event.number);
54
- const params = (): Params => {
55
- return {
56
- query: createQuery(searchQuery(), filterQueries(), props.recordsMeta) || undefined,
57
- sort: sort() || undefined,
58
- page: page() || undefined,
59
- size: pageSize() || undefined,
60
- };
61
- };
62
-
63
- const [records] = createResource(params, fetchRecords);
64
-
65
- const sortOptions = () => {
66
- return flattenFieldMetadata(props.recordsMeta.info).flatMap((field) => [
67
- {
68
- order: { field, direction: DIRECTION_ASCENDING },
69
- selected:
70
- arrayEquals(infoSortPath(field), sort()?.property as SortPath) && sort()?.compare === DIRECTION_ASCENDING
71
- ? true
72
- : undefined,
73
- },
74
- {
75
- order: { field, direction: DIRECTION_DESCENDING },
76
- selected:
77
- arrayEquals(infoSortPath(field), sort()?.property as SortPath) && sort()?.compare === DIRECTION_DESCENDING
78
- ? true
79
- : undefined,
80
- },
81
- ]);
82
- };
83
-
84
- return (
85
- <div class="columns is-variable is-1">
86
- <div class="scrolling-div column is-1-fullhd is-2">
87
- <SearchBox onInput={onSearchChange} />
88
- <InfoFilters
89
- fields={flattenFieldMetadata(props.recordsMeta.info)}
90
- queries={filterQueries()}
91
- onChange={onFilterChange}
92
- onClear={onFilterClear}
93
- />
94
- </div>
95
- <div class="scrolling-div column">
96
- <div class="columns is-gapless">
97
- <div class="column">
98
- <p class="title is-3">Reported variants</p>
99
- <p class="subtitle is-5">Includes all reported variants without genotypes</p>
100
- </div>
101
- </div>
102
- <div class="columns is-gapless">
103
- <div class="column is-offset-1-fullhd is-3-fullhd is-4">
104
- <Show when={records()} fallback={<Loader />} keyed>
105
- {(records) => (
106
- <span class="is-pulled-left inline-control-text ml-2">{records.page.totalElements} records</span>
107
- )}
108
- </Show>
109
- </div>
110
- <div class="column is-4">
111
- <Show when={records()} fallback={<Loader />} keyed>
112
- {(records) => <Pager page={records.page} onPageChange={onPageChange} />}
113
- </Show>
114
- </div>
115
- <div class="column">
116
- <div class="field is-grouped is-grouped-right">
117
- <Sort options={sortOptions()} onChange={onSortChange} onClear={onSortClear} />
118
- <RecordDownload recordsMetadata={props.recordsMeta} query={params().query} />
119
- </div>
120
- </div>
121
- </div>
122
- <div class="columns is-gapless">
123
- <div class="column is-full">
124
- <Show when={records()} fallback={<Loader />} keyed>
125
- {(records) => (
126
- <>
127
- <VariantsTable
128
- records={records.items}
129
- recordsMetadata={props.recordsMeta}
130
- htsFileMeta={props.htsFileMeta}
131
- />
132
- <div class="columns is-gapless">
133
- <div class="column">
134
- <div class="field is-grouped is-grouped-right">
135
- <RecordsPerPage initialValue={pageSize() || 20} onChange={onRecordsPerPageChange} />
136
- </div>
137
- </div>
138
- </div>
139
- </>
140
- )}
141
- </Show>
142
- </div>
143
- </div>
144
- </div>
145
- </div>
146
- );
147
- };
@@ -0,0 +1,25 @@
1
+ import { Component, Show } from "solid-js";
2
+ import { createAsync, Navigate, RouteSectionProps } from "@solidjs/router";
3
+ import { Loader } from "../components/Loader";
4
+ import { getMetadata } from "./data/data.tsx";
5
+ import { MetadataContainer } from "../utils/api.ts";
6
+ import { href } from "../utils/utils.ts";
7
+
8
+ /**
9
+ * View that redirects to the variant type specific view based on the available variant types
10
+ */
11
+ export const VariantsRedirect: Component<RouteSectionProps> = () => {
12
+ const metadata = createAsync(() => getMetadata());
13
+
14
+ function getHref(metadata: MetadataContainer) {
15
+ const variantTypeIds = metadata.variantTypeIds;
16
+ const variantType = variantTypeIds.size === 1 ? variantTypeIds.values().next().value! : "all";
17
+ return href(["variants", variantType]);
18
+ }
19
+
20
+ return (
21
+ <Show when={metadata()} fallback={<Loader />}>
22
+ {(metadata) => <Navigate href={getHref(metadata())} />}
23
+ </Show>
24
+ );
25
+ };
@@ -1,7 +1,33 @@
1
- import { cache } from "@solidjs/router";
2
- import { Item, Sample } from "@molgenis/vip-report-api/src/Api";
3
- import { Record } from "@molgenis/vip-report-vcf/src/Vcf";
4
- import api from "../../Api";
1
+ import { query } from "@solidjs/router";
2
+ import { DecisionTree, Item } from "@molgenis/vip-report-api";
3
+ import { VcfRecord } from "@molgenis/vip-report-vcf";
4
+ import {
5
+ fetchConfig,
6
+ fetchDecisionTree,
7
+ fetchMetadata,
8
+ fetchRecordById,
9
+ fetchSampleById,
10
+ fetchSampleTree,
11
+ MetadataContainer,
12
+ SampleContainer,
13
+ } from "../../utils/api.ts";
14
+ import { parseId } from "../../utils/utils.ts";
15
+ import { ConfigJson } from "../../types/config";
5
16
 
6
- export const getSample = cache(async (id: number): Promise<Item<Sample>> => api.getSampleById(id), "sample");
7
- export const getVariant = cache(async (id: number): Promise<Item<Record>> => api.getRecordById(id), "variant");
17
+ export const getConfig = query((): Promise<ConfigJson> => fetchConfig(), "config");
18
+
19
+ export const getMetadata = query((): Promise<MetadataContainer> => fetchMetadata(), "metadata");
20
+
21
+ export const getSampleById = query(
22
+ (id: string | undefined): Promise<SampleContainer> => fetchSampleById(parseId(id)),
23
+ "sample",
24
+ );
25
+
26
+ export const getRecordById = query(
27
+ (id: string | undefined): Promise<Item<VcfRecord>> => fetchRecordById(parseId(id)),
28
+ "variant",
29
+ );
30
+
31
+ export const getDecisionTree = query((): Promise<DecisionTree | null> => fetchDecisionTree(), "decisionTree");
32
+
33
+ export const getSampleTree = query((): Promise<DecisionTree | null> => fetchSampleTree(), "sampleTree");
@@ -0,0 +1,122 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "vitest";
2
+ import { wrapStore } from "../../src/store/variants.ts";
3
+ import { createAppStore } from "../../src/store/app.ts";
4
+ import { SampleContainer } from "../../src/utils/api.ts";
5
+ import { VariantType } from "../../src/utils/variantType.ts";
6
+
7
+ describe("variants", () => {
8
+ const consoleLog = console.log;
9
+
10
+ beforeAll(() => {
11
+ console.log = () => {};
12
+ });
13
+
14
+ afterAll(() => {
15
+ console.log = consoleLog;
16
+ });
17
+
18
+ describe("wrapStore", () => {
19
+ const sample = { item: { id: 2 } } as SampleContainer;
20
+ const variantType = { id: "all" } as VariantType;
21
+
22
+ describe("setFilterValue", () => {
23
+ test("set new value", () => {
24
+ const store = wrapStore(createAppStore(), sample, variantType);
25
+ store.setFilterValue("f", ["val"]);
26
+ expect(store.getFilterValues()).toStrictEqual({ f: ["val"] });
27
+ expect(store.getPageNumber()).toStrictEqual(0);
28
+ });
29
+
30
+ test("update value", () => {
31
+ const store = wrapStore(createAppStore(), sample, variantType);
32
+ store.setFilterValue("f", ["val"]);
33
+ store.setFilterValue("f", ["val_new"]);
34
+ expect(store.getFilterValues()).toStrictEqual({ f: ["val_new"] });
35
+ expect(store.getPageNumber()).toStrictEqual(0);
36
+ });
37
+
38
+ test("set value resets page number", () => {
39
+ const store = wrapStore(createAppStore(), sample, variantType);
40
+ store.setPageNumber(2);
41
+ store.setFilterValue("f", ["val"]);
42
+ expect(store.getFilterValues()).toStrictEqual({ f: ["val"] });
43
+ expect(store.getPageNumber()).toStrictEqual(0);
44
+ });
45
+
46
+ test("do not mix sample state", () => {
47
+ const appStore = createAppStore();
48
+ const sample0 = { item: { id: 0 } } as SampleContainer;
49
+ const sample1 = { item: { id: 1 } } as SampleContainer;
50
+ const sample0Store = wrapStore(appStore, sample0, variantType);
51
+ const sample1Store = wrapStore(appStore, sample1, variantType);
52
+
53
+ sample0Store.setFilterValue("f", ["val0"]);
54
+ sample1Store.setFilterValue("f", ["val1"]);
55
+ expect(sample0Store.getFilterValues()).toStrictEqual({ f: ["val0"] });
56
+ expect(sample1Store.getFilterValues()).toStrictEqual({ f: ["val1"] });
57
+ });
58
+
59
+ test("no sample", () => {
60
+ const store = wrapStore(createAppStore(), null, variantType);
61
+ store.setFilterValue("f", ["val"]);
62
+ expect(store.getFilterValues()).toStrictEqual({ f: ["val"] });
63
+ expect(store.getPageNumber()).toStrictEqual(0);
64
+ });
65
+ });
66
+
67
+ describe("clearFilter", () => {
68
+ test("clear", () => {
69
+ const store = wrapStore(createAppStore(), sample, variantType);
70
+ store.setFilterValue("f", ["val"]);
71
+ store.clearFilter("f");
72
+ expect(store.getFilterValues()).toStrictEqual({});
73
+ });
74
+
75
+ test("clear resets page number", () => {
76
+ const store = wrapStore(createAppStore(), sample, variantType);
77
+ store.setFilterValue("f", ["val"]);
78
+ store.setPageNumber(2);
79
+ store.clearFilter("f");
80
+ expect(store.getFilterValues()).toStrictEqual({});
81
+ expect(store.getPageNumber()).toStrictEqual(0);
82
+ });
83
+ });
84
+
85
+ describe("setPageNumber", () => {
86
+ test("set value", () => {
87
+ const store = wrapStore(createAppStore(), sample, variantType);
88
+ store.setPageNumber(0);
89
+ expect(store.getPageNumber()).toStrictEqual(0);
90
+ });
91
+ });
92
+
93
+ describe("setPageSize", () => {
94
+ test("set size resets page number", () => {
95
+ const store = wrapStore(createAppStore(), sample, variantType);
96
+ store.setPageNumber(2);
97
+ store.setPageSize(10);
98
+ expect(store.getPageSize()).toStrictEqual(10);
99
+ expect(store.getPageNumber()).toStrictEqual(0);
100
+ });
101
+ });
102
+
103
+ describe("setSort", () => {
104
+ test("initial sort is undefined", () => {
105
+ const store = wrapStore(createAppStore(), sample, variantType);
106
+ expect(store.getSort()).toStrictEqual(undefined); // must not be null
107
+ });
108
+
109
+ test("clear sort", () => {
110
+ const store = wrapStore(createAppStore(), sample, variantType);
111
+ store.clearSort();
112
+ expect(store.getSort()).toStrictEqual(null); // must not be undefined
113
+ });
114
+
115
+ test("clear sort", () => {
116
+ const store = wrapStore(createAppStore(), sample, variantType);
117
+ store.setSort({ property: "f" });
118
+ expect(store.getSort()).toStrictEqual({ property: "f" });
119
+ });
120
+ });
121
+ });
122
+ });
@@ -0,0 +1,167 @@
1
+ import { afterEach, describe, expect, test, vi } from "vitest";
2
+ import { initConfig } from "../../../src/utils/config/config.ts";
3
+ import {
4
+ ConfigJson,
5
+ ConfigJsonVariant,
6
+ ConfigJsonVariantConsequence,
7
+ ConfigJsonVariants,
8
+ ConfigJsonVip,
9
+ ConfigRecordsPerPage,
10
+ ConfigVip,
11
+ } from "../../../src/types/config";
12
+ import { MetadataContainer, SampleContainer } from "../../../src/utils/api.ts";
13
+ import { VariantType } from "../../../src/utils/variantType.ts";
14
+ import { initConfigCells } from "../../../src/utils/config/configCells.ts";
15
+ import { initConfigFilters } from "../../../src/utils/config/configFilters.ts";
16
+ import { initConfigSorts } from "../../../src/utils/config/configSorts.ts";
17
+ import { ConfigCell } from "../../../src/types/configCells";
18
+ import { ConfigFilter } from "../../../src/types/configFilter";
19
+ import { ConfigSort } from "../../../src/types/configSort";
20
+ import { initConfigVip } from "../../../src/utils/config/configVip.ts";
21
+
22
+ describe("config", () => {
23
+ vi.mock(import("../../../src/utils/config/configCells.ts"));
24
+ vi.mock(import("../../../src/utils/config/configFilters.ts"));
25
+ vi.mock(import("../../../src/utils/config/configSorts.ts"));
26
+ vi.mock(import("../../../src/utils/config/configVip.ts"));
27
+
28
+ afterEach(() => {
29
+ vi.resetAllMocks();
30
+ });
31
+
32
+ const configVariantsMinimal: ConfigJsonVariants = {
33
+ cells: {
34
+ all: [
35
+ {
36
+ type: "fixed",
37
+ name: "chrom",
38
+ },
39
+ ],
40
+ },
41
+ };
42
+ const metadata: Partial<MetadataContainer> = {};
43
+ const sample: Partial<SampleContainer> | null = null;
44
+
45
+ describe("initConfig", () => {
46
+ const recordsPerPageBase = [
47
+ {
48
+ number: 10,
49
+ selected: true,
50
+ },
51
+ { number: 20 },
52
+ { number: 50 },
53
+ { number: 100 },
54
+ ];
55
+
56
+ test("regular config", () => {
57
+ vi.mocked(initConfigVip).mockReturnValue(0 as unknown as ConfigVip);
58
+ vi.mocked(initConfigCells)
59
+ .mockReturnValueOnce([4 as unknown as ConfigCell])
60
+ .mockReturnValueOnce([5 as unknown as ConfigCell])
61
+ .mockReturnValue([-1 as unknown as ConfigCell]);
62
+ vi.mocked(initConfigFilters).mockReturnValue([2 as unknown as ConfigFilter]);
63
+ vi.mocked(initConfigSorts).mockReturnValue([3 as unknown as ConfigSort]);
64
+
65
+ const recordsPerPage: ConfigRecordsPerPage = [{ number: 1 }, { number: 2, selected: true }, { number: 3 }];
66
+
67
+ const config: ConfigJson = {
68
+ vip: { params: {} } as ConfigJsonVip,
69
+ sample_variants: {} as ConfigJsonVariants,
70
+ variants: {
71
+ ...configVariantsMinimal,
72
+ filters: { all: [] },
73
+ sorts: { all: [] },
74
+ recordsPerPage: { all: recordsPerPage },
75
+ },
76
+ sample_variant: { cells: { all: [{}] } } as ConfigJsonVariant,
77
+ variant: { cells: { all: [{}] } } as ConfigJsonVariant,
78
+ sample_variant_consequence: { samples_cells: { all: [{}] } } as ConfigJsonVariantConsequence,
79
+ variant_consequence: { sample_cells: { all: [{}] } } as ConfigJsonVariantConsequence,
80
+ };
81
+ const variantType: Partial<VariantType> = { id: "snv" };
82
+
83
+ expect(
84
+ initConfig(config, variantType as VariantType, metadata as MetadataContainer, sample as SampleContainer | null),
85
+ ).toStrictEqual({
86
+ vip: 0,
87
+ variants: { cells: [4], filters: [2], sorts: [3], recordsPerPage },
88
+ variant: {
89
+ cells: [5],
90
+ samplesCells: undefined,
91
+ },
92
+ variantConsequence: {
93
+ samplesCells: undefined,
94
+ },
95
+ });
96
+ });
97
+
98
+ test("minimal config", () => {
99
+ vi.mocked(initConfigVip).mockReturnValue(0 as unknown as ConfigVip);
100
+ vi.mocked(initConfigCells)
101
+ .mockReturnValueOnce([1 as unknown as ConfigCell])
102
+ .mockReturnValueOnce([2 as unknown as ConfigCell]);
103
+
104
+ const config: ConfigJson = {
105
+ vip: { params: {} } as ConfigJsonVip,
106
+ sample_variants: {} as ConfigJsonVariants,
107
+ variants: configVariantsMinimal,
108
+ sample_variant: {} as ConfigJsonVariant,
109
+ variant: configVariantsMinimal,
110
+ sample_variant_consequence: {} as ConfigJsonVariantConsequence,
111
+ variant_consequence: {} as ConfigJsonVariantConsequence,
112
+ };
113
+
114
+ const variantType: Partial<VariantType> = { id: "snv" };
115
+
116
+ expect(
117
+ initConfig(config, variantType as VariantType, metadata as MetadataContainer, sample as SampleContainer | null),
118
+ ).toStrictEqual({
119
+ vip: 0,
120
+ variants: { cells: [1], filters: [], sorts: [], recordsPerPage: recordsPerPageBase },
121
+ variant: {
122
+ cells: [2],
123
+ samplesCells: undefined,
124
+ },
125
+ variantConsequence: {
126
+ samplesCells: undefined,
127
+ },
128
+ });
129
+ expect(initConfigFilters).toBeCalledTimes(0);
130
+ expect(initConfigSorts).toBeCalledTimes(0);
131
+ });
132
+
133
+ test("throws error on empty config property 'cells.all'", () => {
134
+ const config: ConfigJson = {
135
+ vip: { params: {} } as ConfigJsonVip,
136
+ sample_variants: { cells: { all: [] } },
137
+ variants: { cells: { all: [] } },
138
+ sample_variant: {} as ConfigJsonVariant,
139
+ variant: {} as ConfigJsonVariant,
140
+ sample_variant_consequence: {} as ConfigJsonVariantConsequence,
141
+ variant_consequence: {} as ConfigJsonVariantConsequence,
142
+ };
143
+ const variantType: Partial<VariantType> = { id: "all" };
144
+
145
+ expect(() =>
146
+ initConfig(config, variantType as VariantType, metadata as MetadataContainer, sample as SampleContainer | null),
147
+ ).toThrow(/^config invalid: property 'cells.all' requires at least one value$/);
148
+ });
149
+
150
+ test("throws error on missing config property 'cells.all'", () => {
151
+ const config: ConfigJson = {
152
+ vip: { params: {} } as ConfigJsonVip,
153
+ sample_variants: { cells: { all: [] } },
154
+ variants: { cells: {} },
155
+ sample_variant: {} as ConfigJsonVariant,
156
+ variant: {} as ConfigJsonVariant,
157
+ sample_variant_consequence: {} as ConfigJsonVariantConsequence,
158
+ variant_consequence: {} as ConfigJsonVariantConsequence,
159
+ };
160
+ const variantType: Partial<VariantType> = { id: "all" };
161
+
162
+ expect(() =>
163
+ initConfig(config, variantType as VariantType, metadata as MetadataContainer, sample as SampleContainer | null),
164
+ ).toThrow(/^config invalid: missing required property 'cells.all'$/);
165
+ });
166
+ });
167
+ });