@molgenis/vip-report-template 6.1.1 → 7.0.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 (285) hide show
  1. package/.nvmrc +1 -1
  2. package/.travis.yml +9 -11
  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 +35 -29
  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 +8 -5
  12. package/src/components/DatasetDropdown.tsx +10 -23
  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 +143 -0
  119. package/src/mocks/GRCh37/static.ts +63 -133
  120. package/src/mocks/GRCh37/vcf/family.vcf.blob +37 -31
  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 +341 -328
  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 -114
  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 -183
  237. package/src/components/VariantsTable.tsx +0 -124
  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 -259
  281. package/src/utils/csqUtils.ts +0 -27
  282. package/src/utils/decisionTreeUtils.ts +0 -14
  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,6 +1,9 @@
1
- import { FieldMetadata } from "@molgenis/vip-report-vcf/src/MetadataParser";
2
- import { Metadata } from "@molgenis/vip-report-vcf/src/Vcf";
3
- import { CompareFn, SortOrder, SortPath } from "@molgenis/vip-report-api/src/Api";
1
+ import { FieldMetadata, VcfMetadata } from "@molgenis/vip-report-vcf";
2
+ import { CompareFn, SortOrder, SortPath } from "@molgenis/vip-report-api";
3
+ import { ConfigSort, ConfigSortOrder } from "../../types/configSort";
4
+
5
+ import { createInfoSortPath } from "./selector.ts";
6
+ import { InvalidSortPathError } from "../error.ts";
4
7
 
5
8
  export type Direction = "asc" | "desc";
6
9
 
@@ -16,25 +19,42 @@ export type Sort = {
16
19
  export const DIRECTION_ASCENDING = "asc" as Direction;
17
20
  export const DIRECTION_DESCENDING = "desc" as Direction;
18
21
 
19
- class InvalidSortPathError extends Error {
20
- constructor(path: SortPath) {
21
- super(`invalid record sort path '[${path.join(",")}]'`);
22
- this.name = "InvalidSortPathError";
22
+ /**
23
+ * @param storeSort null=do not sort, undefined=sort behavior is undefined, fall back to default sort
24
+ * @param configSort default sort
25
+ */
26
+ export function createSort(
27
+ storeSort: SortOrder | SortOrder[] | null | undefined,
28
+ configSort: ConfigSort | undefined,
29
+ ): SortOrder | SortOrder[] {
30
+ if (storeSort !== undefined) {
31
+ return storeSort || [];
32
+ } else {
33
+ const sortOrders = configSort != undefined ? configSort.orders.map((order) => mapOrder(order)) : undefined;
34
+ return sortOrders !== undefined ? sortOrders : [];
23
35
  }
24
36
  }
25
- export function createRecordSort(recordsMeta: Metadata, sort?: SortOrder | SortOrder[]): Sort {
37
+
38
+ export function mapOrder(sort: ConfigSortOrder): SortOrder {
39
+ return {
40
+ property: createInfoSortPath(sort.field),
41
+ compare: createDirection(sort.direction),
42
+ };
43
+ }
44
+
45
+ export function createRecordSort(recordsMeta: VcfMetadata, sort?: SortOrder | SortOrder[]): Sort {
26
46
  const orders = sort ? (Array.isArray(sort) ? sort : [sort]) : [];
27
47
  return { orders: orders.map((order) => createOrder(order, recordsMeta)) };
28
48
  }
29
49
 
30
- function createOrder(sort: SortOrder, recordsMeta: Metadata): Order {
50
+ function createOrder(sort: SortOrder, recordsMeta: VcfMetadata): Order {
31
51
  return {
32
52
  field: createField(sort.property, recordsMeta),
33
53
  direction: createDirection(sort.compare),
34
54
  };
35
55
  }
36
56
 
37
- function createField(property: string | SortPath, recordsMeta: Metadata): FieldMetadata {
57
+ function createField(property: string | SortPath, recordsMeta: VcfMetadata): FieldMetadata {
38
58
  const path = Array.isArray(property) ? property : [property];
39
59
  if (path.length < 2 || path.length > 3 || path[0] !== "n" || typeof path[1] !== "string") {
40
60
  throw new Error(`invalid record sort path '[${path.join(",")}]'`);
@@ -52,6 +72,7 @@ function createField(property: string | SortPath, recordsMeta: Metadata): FieldM
52
72
  if (field.nested === undefined) throw new InvalidSortPathError(path);
53
73
 
54
74
  field = field.nested.items[pathIndex];
75
+ if (field === undefined) throw new InvalidSortPathError(path);
55
76
  }
56
77
  return field;
57
78
  }
@@ -60,6 +81,6 @@ function createDirection(compare?: "asc" | "desc" | CompareFn): Direction {
60
81
  if (compare === undefined) return DIRECTION_ASCENDING;
61
82
  else if (compare === DIRECTION_ASCENDING) return DIRECTION_ASCENDING;
62
83
  else if (compare === DIRECTION_DESCENDING) return DIRECTION_DESCENDING;
63
- else if (typeof compare === "function") throw new Error("cannot convert sort with custom compare function");
84
+ else if (typeof compare === "function") throw new Error("cannot convert sort with composed compare function");
64
85
  else throw new Error(`invalid sort compare '${compare}'`);
65
86
  }
@@ -1,11 +1,12 @@
1
- import { Sample } from "@molgenis/vip-report-api/src/Api";
1
+ import { Item, Sample } from "@molgenis/vip-report-api";
2
+ import { SampleContainer } from "./api.ts";
2
3
 
3
- export function getSampleLabel(sample: Sample) {
4
- return sample.person.individualId;
4
+ export function getSampleLabel(sample: Item<Sample>) {
5
+ return sample.data.person.individualId;
5
6
  }
6
7
 
7
- export function getSampleSexLabel(sample: Sample): string {
8
- switch (sample.person.sex) {
8
+ export function getSampleSexLabel(sample: Item<Sample>): string {
9
+ switch (sample.data.person.sex) {
9
10
  case "FEMALE":
10
11
  return "female";
11
12
  case "MALE":
@@ -15,8 +16,8 @@ export function getSampleSexLabel(sample: Sample): string {
15
16
  }
16
17
  }
17
18
 
18
- export function getSampleAffectedStatusLabel(sample: Sample): string {
19
- switch (sample.person.affectedStatus) {
19
+ export function getSampleAffectedStatusLabel(sample: Item<Sample>): string {
20
+ switch (sample.data.person.affectedStatus) {
20
21
  case "AFFECTED":
21
22
  return "affected";
22
23
  case "UNAFFECTED":
@@ -30,36 +31,19 @@ function isFamily(sample: Sample, samples: Sample): boolean {
30
31
  return sample.person.familyId === samples.person.familyId;
31
32
  }
32
33
 
33
- function isSampleMother(sample: Sample, samples: Sample): boolean {
34
- return isFamily(sample, samples) && samples.person.individualId === sample.person.maternalId;
34
+ export function isSampleMother(sample: Sample, sampleMaternal: Sample): boolean {
35
+ return isFamily(sample, sampleMaternal) && sampleMaternal.person.individualId === sample.person.maternalId;
35
36
  }
36
37
 
37
- export function getSampleMother(sample: Sample, samples: Sample[]): Sample | undefined {
38
- if (sample.person.maternalId !== "0") {
39
- for (const otherSample of samples) {
40
- if (isSampleMother(sample, otherSample)) return otherSample;
41
- }
42
- }
43
- }
44
-
45
- function isSampleFather(sample: Sample, samples: Sample): boolean {
46
- return isFamily(sample, samples) && samples.person.individualId === sample.person.paternalId;
38
+ export function isSampleFather(sample: Sample, samplePaternal: Sample): boolean {
39
+ return isFamily(sample, samplePaternal) && samplePaternal.person.individualId === sample.person.paternalId;
47
40
  }
48
41
 
49
- export function getSampleFather(sample: Sample, samples: Sample[]): Sample | undefined {
50
- if (sample.person.paternalId !== "0") {
51
- for (const otherSample of samples) {
52
- if (isSampleFather(sample, otherSample)) return otherSample;
53
- }
54
- }
55
- }
56
-
57
- export function getSampleFamilyMembersWithoutParents(sample: Sample, samples: Sample[]): Sample[] {
58
- const familyMembersWithoutParents: Sample[] = [];
59
- for (const otherSample of samples) {
60
- if (isFamily(sample, otherSample) && !isSampleFather(sample, otherSample) && !isSampleMother(sample, otherSample)) {
61
- if (sample.person.individualId !== otherSample.person.individualId) familyMembersWithoutParents.push(otherSample);
62
- }
63
- }
64
- return familyMembersWithoutParents;
42
+ /**
43
+ * Returns all samples for the given sample pedigree including the given sample itself
44
+ */
45
+ export function getPedigreeSamples(sample: SampleContainer): Item<Sample>[] {
46
+ return [sample.item, sample.maternalSample, sample.paternalSample, ...sample.otherPedigreeSamples].filter(
47
+ (sample) => sample !== null,
48
+ );
65
49
  }
@@ -1,3 +1,67 @@
1
- export function arrayEquals<T>(a: T[], b: T[]) {
2
- return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((val, index) => val === b[index]);
1
+ import { Item } from "@molgenis/vip-report-api";
2
+ import { VcfRecord } from "@molgenis/vip-report-vcf";
3
+ import { InvalidIdException } from "./error.ts";
4
+
5
+ export function getRecordLabel(item: Item<VcfRecord>) {
6
+ const record = item.data;
7
+ const refLabel = getAlleleLabel(record.r);
8
+ return `${record.c}:${record.p} ${record.a.map((a) => `${refLabel}>${getAlleleLabel(a)}`).join(" / ")}`;
9
+ }
10
+
11
+ function getAlleleLabel(allele: string | null) {
12
+ return allele !== null
13
+ ? allele.length > 4
14
+ ? allele.substring(0, 2) + "\u2026" + allele.charAt(allele.length - 1)
15
+ : allele
16
+ : ".";
17
+ }
18
+
19
+ /**
20
+ * Create an absolute url path from a list of components
21
+ */
22
+ export function href(urlComponents: (string | number)[]) {
23
+ return "/" + urlComponents.map((urlComponent) => encodeURIComponent(urlComponent)).join("/");
24
+ }
25
+
26
+ /**
27
+ * Parse non-negative integer id
28
+ */
29
+ export function parseId(id: string | undefined): number {
30
+ const numberValue = Number(id);
31
+ if (!isPositiveInteger(numberValue)) {
32
+ throw new InvalidIdException(id);
33
+ }
34
+ return numberValue;
35
+ }
36
+
37
+ export function abbreviateHeader(header: string) {
38
+ return header.length > 13 ? header.slice(0, 11) + "\u2026" : header;
39
+ }
40
+
41
+ export function isPositiveInteger(numberValue: number) {
42
+ return !isNaN(numberValue) && Number.isInteger(numberValue) && numberValue >= 0;
43
+ }
44
+
45
+ /**
46
+ * Validates that interval left and/or right values are integers or floats
47
+ *
48
+ * @return validation error message or undefined if interval is valid
49
+ */
50
+ export function validateIntervalInput(
51
+ id: string,
52
+ left: string | undefined,
53
+ right: string | undefined,
54
+ ): string | undefined {
55
+ const leftNum = Number(left);
56
+ const rightNum = Number(right);
57
+
58
+ if (left && isNaN(leftNum)) {
59
+ return `Input 'from' (${left}) for filter '${id}' should be a number.`;
60
+ }
61
+ if (right && isNaN(rightNum)) {
62
+ return `Input 'to' (${right}) for filter '${id}' should be a number.`;
63
+ }
64
+ if (left && right && rightNum < leftNum) {
65
+ return `Input 'to' (${right}) for filter '${id}' should have a higher value than the 'from' input (${left}).`;
66
+ }
3
67
  }
@@ -0,0 +1,43 @@
1
+ import { ValueString } from "@molgenis/vip-report-vcf";
2
+
3
+ export type VariantTypeId = "all" | "snv" | "str" | "sv";
4
+ export type VariantType = {
5
+ id: VariantTypeId;
6
+ label: string;
7
+ description: string;
8
+ };
9
+
10
+ const variantTypeMap: { [key: string]: VariantType } = {
11
+ all: { id: "all", label: "All", description: "All variants" },
12
+ snv: { id: "snv", label: "SNV", description: "Single nucleotide variants and Indels < 50 bases" },
13
+ str: { id: "str", label: "STR", description: "Short tandem repeats" },
14
+ sv: { id: "sv", label: "SV/CNV", description: "Structural variants including copy number variants" },
15
+ };
16
+
17
+ export function getVariantTypes(variantTypeIds: Set<VariantTypeId>): VariantType[] {
18
+ return Object.values(variantTypeMap).filter((variantType) => variantTypeIds.has(variantType.id));
19
+ }
20
+
21
+ export function parseVariantType(variantTypeId: string | undefined): VariantType {
22
+ if (variantTypeId === undefined) {
23
+ throw new Error();
24
+ }
25
+
26
+ const variantType = variantTypeMap[variantTypeId];
27
+ if (variantType === undefined) {
28
+ throw new Error(`unknown variant type '${variantTypeId}'`);
29
+ }
30
+ return variantType;
31
+ }
32
+
33
+ export function mapSvTypeToVariantTypeId(svType: ValueString | undefined): VariantTypeId {
34
+ let variantType: VariantTypeId;
35
+ if (svType === undefined || svType === null) {
36
+ variantType = "snv";
37
+ } else if (svType === "STR") {
38
+ variantType = "str";
39
+ } else {
40
+ variantType = "sv";
41
+ }
42
+ return variantType;
43
+ }
@@ -0,0 +1,352 @@
1
+ import {
2
+ FieldId,
3
+ FieldMetadata,
4
+ Genotype,
5
+ InfoContainer,
6
+ RecordSample,
7
+ Value,
8
+ ValueDescription,
9
+ ValueString,
10
+ VcfMetadata,
11
+ VcfRecord,
12
+ } from "@molgenis/vip-report-vcf";
13
+ import { Item } from "@molgenis/vip-report-api";
14
+ import { SampleContainer, VcfMetadataContainer } from "./api.ts";
15
+ import { ArrayIndexOutOfBoundsException, InvalidVcfError, RuntimeError } from "./error.ts";
16
+
17
+ export type ValueCategorical =
18
+ | (ValueDescription & {
19
+ value: ValueString;
20
+ })
21
+ | null;
22
+ export type FieldValue = Value | Genotype | ValueCategorical | ValueCategorical[];
23
+
24
+ export type FieldMetadataWrapper = FieldMetadata & {
25
+ index: number;
26
+ };
27
+ export type FieldPath = string;
28
+ export type FieldMap = { [key: FieldPath]: FieldMetadataWrapper };
29
+
30
+ export function createFieldMap(metadata: VcfMetadata): FieldMap {
31
+ const infoFields = createFieldMapTypedRec("INFO", Object.values(metadata.info));
32
+ const formatFields = createFieldMapTypedRec("FORMAT", Object.values(metadata.format));
33
+ return { ...infoFields, ...formatFields };
34
+ }
35
+
36
+ function createFieldMapTypedRec(keyPrefix: string, fieldMetadataList: FieldMetadata[]): FieldMap {
37
+ const fields: { [key: string]: FieldMetadataWrapper } = {};
38
+ fieldMetadataList.forEach((fieldMetadata, index) => {
39
+ const key = `${keyPrefix}/${fieldMetadata.id}`;
40
+ if (fieldMetadata.nested) {
41
+ const nestedFields = createFieldMapTypedRec(key, fieldMetadata.nested.items);
42
+ Object.assign(fields, nestedFields);
43
+
44
+ // add parent field, but update items with items in nestedFields
45
+ fields[key] = {
46
+ ...fieldMetadata,
47
+ index,
48
+ nested: {
49
+ ...fieldMetadata.nested,
50
+ items: fieldMetadata.nested.items.map((field) => nestedFields[`${key}/${field.id}`]!),
51
+ },
52
+ };
53
+ } else {
54
+ fields[key] = { ...fieldMetadata, index };
55
+ }
56
+ });
57
+ return fields;
58
+ }
59
+
60
+ function mapFieldValueDefined(value: Value | Genotype, fieldMetadata: FieldMetadataWrapper): FieldValue {
61
+ let mappedValue: FieldValue;
62
+
63
+ if (fieldMetadata.type === "CATEGORICAL") {
64
+ // map categorical values to categories with label and description
65
+ if (fieldMetadata.number.count === 1) {
66
+ const valueString = value as ValueString;
67
+ mappedValue = createCategoricalValue(fieldMetadata, valueString);
68
+ } else {
69
+ if (!Array.isArray(value)) throw new RuntimeError();
70
+ const valueArray = value as ValueString[];
71
+ if (valueArray.length !== 0) {
72
+ mappedValue = valueArray.map((value) => createCategoricalValue(fieldMetadata, value));
73
+ } else {
74
+ // empty categorical array value should return array with nullValue if it is defined
75
+ mappedValue = fieldMetadata.nullValue ? [{ ...fieldMetadata.nullValue, value: null }] : [];
76
+ }
77
+ }
78
+ } else {
79
+ mappedValue = value;
80
+ }
81
+
82
+ return mappedValue;
83
+ }
84
+
85
+ function mapFieldValue(value: Value | Genotype | undefined, fieldMetadata: FieldMetadataWrapper): FieldValue {
86
+ let definedValue: Value | Genotype;
87
+ if (fieldMetadata.number.count === 1) {
88
+ definedValue = value !== undefined ? value : null;
89
+ } else {
90
+ definedValue = value !== undefined && value !== null ? value : [];
91
+ }
92
+ return mapFieldValueDefined(definedValue, fieldMetadata);
93
+ }
94
+
95
+ function getFieldValue(
96
+ valueContainer: ValueContainer,
97
+ valueIndex: number,
98
+ fieldMetadata: FieldMetadataWrapper,
99
+ ): FieldValue {
100
+ let value: Value | Genotype | undefined;
101
+ if (fieldMetadata.parent) {
102
+ if (fieldMetadata.parent.id in valueContainer) {
103
+ const parentValue = valueContainer[fieldMetadata.parent.id]!;
104
+ if (!Array.isArray(parentValue)) throw new InvalidVcfError();
105
+ const parentValueArray = parentValue as Value[];
106
+
107
+ if (fieldMetadata.parent.number.count === 1) {
108
+ if (parentValueArray.length < fieldMetadata.index) throw new InvalidVcfError();
109
+ value = parentValueArray[fieldMetadata.index]!;
110
+ } else {
111
+ if (parentValueArray.length < valueIndex) throw new RuntimeError();
112
+ const multiValue = parentValueArray[valueIndex]!;
113
+ if (!Array.isArray(multiValue)) throw new InvalidVcfError();
114
+ const multiValueArray = multiValue as Value[];
115
+
116
+ if (multiValueArray.length < fieldMetadata.index) throw new InvalidVcfError();
117
+ value = multiValueArray[fieldMetadata.index]!;
118
+ }
119
+ } else {
120
+ value = undefined;
121
+ }
122
+ } else {
123
+ if (fieldMetadata.id in valueContainer) {
124
+ value = valueContainer[fieldMetadata.id]!;
125
+ } else {
126
+ value = undefined;
127
+ }
128
+ }
129
+
130
+ return mapFieldValue(value, fieldMetadata);
131
+ }
132
+
133
+ function getFieldValues(
134
+ valueContainer: ValueContainer,
135
+ valueIndex: number,
136
+ ...fieldMetadataList: (FieldMetadataWrapper | undefined)[]
137
+ ): (FieldValue | undefined)[] {
138
+ return fieldMetadataList.map(
139
+ (fieldMetadata) => fieldMetadata && getFieldValue(valueContainer, valueIndex, fieldMetadata),
140
+ );
141
+ }
142
+
143
+ export function getInfoValue(
144
+ record: Item<VcfRecord>,
145
+ valueIndex: number,
146
+ fieldMetadata: FieldMetadataWrapper | undefined,
147
+ ): FieldValue | undefined {
148
+ const valueContainer: ValueContainer = record.data.n;
149
+ return fieldMetadata && getFieldValue(valueContainer, valueIndex, fieldMetadata);
150
+ }
151
+
152
+ export function getInfoValues(
153
+ record: Item<VcfRecord>,
154
+ valueIndex: number,
155
+ ...fieldMetadataList: (FieldMetadataWrapper | undefined)[]
156
+ ): (FieldValue | undefined)[] {
157
+ const valueContainer: ValueContainer = record.data.n;
158
+ return getFieldValues(valueContainer, valueIndex, ...fieldMetadataList);
159
+ }
160
+
161
+ export function getSampleValue(
162
+ sample: SampleContainer,
163
+ record: Item<VcfRecord>,
164
+ valueIndex: number,
165
+ fieldMetadata: FieldMetadataWrapper | undefined,
166
+ ): FieldValue | Genotype | undefined {
167
+ const valueContainer: ValueContainer = getRecordSample(sample, record);
168
+ return fieldMetadata && getFieldValue(valueContainer, valueIndex, fieldMetadata);
169
+ }
170
+
171
+ export function getSampleValues(
172
+ sample: SampleContainer,
173
+ record: Item<VcfRecord>,
174
+ valueIndex: number,
175
+ ...fieldMetadataList: (FieldMetadataWrapper | undefined)[]
176
+ ): (FieldValue | Genotype | undefined)[] {
177
+ const valueContainer: ValueContainer = getRecordSample(sample, record);
178
+ return getFieldValues(valueContainer, valueIndex, ...fieldMetadataList);
179
+ }
180
+
181
+ export const isNumerical = (fieldMetadata: FieldMetadata): boolean => {
182
+ return fieldMetadata.type === "FLOAT" || fieldMetadata.type === "INTEGER";
183
+ };
184
+
185
+ function createCategoricalValue(infoMetadata: FieldMetadata, value: ValueString): ValueCategorical {
186
+ if (infoMetadata.categories === undefined) throw new RuntimeError();
187
+
188
+ let valueDescription: ValueDescription | null;
189
+ if (value !== null) {
190
+ if (!(value in infoMetadata.categories)) {
191
+ throw new RuntimeError(
192
+ `invalid categorical field '${infoMetadata.id}' value '${value}' is not one of [${Object.keys(infoMetadata.categories).join(", ")}]`,
193
+ );
194
+ }
195
+ valueDescription = infoMetadata.categories[value]!;
196
+ } else {
197
+ valueDescription = infoMetadata.nullValue || null;
198
+ }
199
+ return valueDescription && { ...valueDescription, value };
200
+ }
201
+
202
+ function getRecordSample(sample: SampleContainer, record: Item<VcfRecord>): RecordSample {
203
+ const recordSample = record.data.s[sample.item.data.index];
204
+ if (recordSample === undefined) throw new ArrayIndexOutOfBoundsException();
205
+ return recordSample;
206
+ }
207
+
208
+ type ValueContainer = InfoContainer | RecordSample;
209
+
210
+ /**
211
+ * Returns number of values for info field or for parent info field if exists
212
+ */
213
+ export function getInfoValueCount(record: Item<VcfRecord>, fieldMetadata: FieldMetadata): number {
214
+ const valueContainer: ValueContainer = record.data.n;
215
+ return getFieldValueCount(fieldMetadata, valueContainer);
216
+ }
217
+
218
+ /**
219
+ * Returns number of values for sample field or for parent sample field if exists
220
+ */
221
+ export function getSampleValueCount(
222
+ sample: SampleContainer,
223
+ record: Item<VcfRecord>,
224
+ fieldMetadata: FieldMetadata,
225
+ ): number {
226
+ const valueContainer: ValueContainer = getRecordSample(sample, record);
227
+ return getFieldValueCount(fieldMetadata, valueContainer);
228
+ }
229
+
230
+ function getFieldValueCount(fieldMetadata: FieldMetadata, valueContainer: ValueContainer): number {
231
+ let count: number;
232
+
233
+ const parentFieldMetadata = fieldMetadata.parent;
234
+ if (parentFieldMetadata && parentFieldMetadata.number.count !== 1) {
235
+ if (parentFieldMetadata.id in valueContainer) {
236
+ const value = valueContainer[parentFieldMetadata.id]!;
237
+ if (!Array.isArray(value)) throw new RuntimeError();
238
+ count = (value as Value[]).length;
239
+ } else {
240
+ count = 0;
241
+ }
242
+ } else {
243
+ count = 1;
244
+ }
245
+ return count;
246
+ }
247
+
248
+ export function getInfoNestedField(
249
+ metadata: VcfMetadataContainer,
250
+ parentFieldId: FieldId,
251
+ fieldId: FieldId,
252
+ ): FieldMetadataWrapper | undefined {
253
+ return getInfoNestedFields(metadata, parentFieldId, fieldId)[0];
254
+ }
255
+
256
+ export function getInfoNestedFields(
257
+ metadata: VcfMetadataContainer,
258
+ parentFieldId: FieldId,
259
+ ...fieldIds: FieldId[]
260
+ ): (FieldMetadataWrapper | undefined)[] {
261
+ return getFields(metadata, `INFO/${parentFieldId}`, ...fieldIds);
262
+ }
263
+
264
+ export function getInfoField(metadata: VcfMetadataContainer, fieldId: FieldId): FieldMetadataWrapper | undefined {
265
+ return getField(metadata, "INFO", fieldId);
266
+ }
267
+
268
+ export function getInfoFields(
269
+ metadata: VcfMetadataContainer,
270
+ ...fieldIds: FieldId[]
271
+ ): (FieldMetadataWrapper | undefined)[] {
272
+ return getFields(metadata, "INFO", ...fieldIds);
273
+ }
274
+
275
+ export function getInfoFieldsRegex(metadata: VcfMetadataContainer, regex: RegExp): FieldMetadataWrapper[] {
276
+ return getFieldsRegex(metadata, "INFO", regex);
277
+ }
278
+
279
+ export function getSampleField(metadata: VcfMetadataContainer, fieldId: FieldId): FieldMetadataWrapper | undefined {
280
+ return getField(metadata, "FORMAT", fieldId);
281
+ }
282
+
283
+ export function getSampleFields(
284
+ metadata: VcfMetadataContainer,
285
+ ...fieldIds: FieldId[]
286
+ ): (FieldMetadataWrapper | undefined)[] {
287
+ return getFields(metadata, "FORMAT", ...fieldIds);
288
+ }
289
+
290
+ export function getSampleNestedField(
291
+ metadata: VcfMetadataContainer,
292
+ parentFieldId: FieldId,
293
+ fieldId: FieldId,
294
+ ): FieldMetadataWrapper | undefined {
295
+ return getSampleNestedFields(metadata, parentFieldId, fieldId)[0];
296
+ }
297
+
298
+ export function getSampleNestedFields(
299
+ metadata: VcfMetadataContainer,
300
+ parentFieldId: FieldId,
301
+ ...fieldIds: FieldId[]
302
+ ): (FieldMetadataWrapper | undefined)[] {
303
+ return getFields(metadata, `FORMAT/${parentFieldId}`, ...fieldIds);
304
+ }
305
+
306
+ export function getSampleFieldsRegex(metadata: VcfMetadataContainer, regex: RegExp): FieldMetadataWrapper[] {
307
+ return getFieldsRegex(metadata, "FORMAT", regex);
308
+ }
309
+
310
+ function getFields(
311
+ metadata: VcfMetadataContainer,
312
+ prefix: string,
313
+ ...fieldIds: FieldId[]
314
+ ): (FieldMetadataWrapper | undefined)[] {
315
+ return fieldIds.map((fieldId) => getField(metadata, prefix, fieldId));
316
+ }
317
+
318
+ function getField(metadata: VcfMetadataContainer, prefix: string, fieldId: FieldId): FieldMetadataWrapper | undefined {
319
+ return metadata.fieldMap[`${prefix}/${fieldId}`];
320
+ }
321
+
322
+ function getFieldsRegex(metadata: VcfMetadataContainer, prefix: string, regex: RegExp): FieldMetadataWrapper[] {
323
+ return Object.entries(metadata.fieldMap)
324
+ .filter(([key]) => key.startsWith(`${prefix}/`) && regex.test(key.substring(`${prefix}/`.length)))
325
+ .map(([, value]) => value);
326
+ }
327
+
328
+ export function parseContigIds(metadata: VcfMetadata): string[] {
329
+ return metadata.lines
330
+ .filter((line) => line.startsWith("##contig="))
331
+ .map((line) => {
332
+ const tokens: { [index: string]: string } = {};
333
+ for (const token of line.substring(10, line.length - 1).split(",")) {
334
+ const keyValue = token.split("=");
335
+ if (keyValue.length !== 2) throw new InvalidVcfError();
336
+ tokens[keyValue[0]!] = keyValue[1]!;
337
+ }
338
+ const token = tokens["ID"];
339
+ if (token === undefined) throw new InvalidVcfError();
340
+ return token;
341
+ });
342
+ }
343
+
344
+ export function getHeaderValue(metadata: VcfMetadata, key: string): string | null {
345
+ const token = `##${key}=`;
346
+ for (const line of metadata.lines) {
347
+ if (line.startsWith(token)) {
348
+ return line.substring(token.length);
349
+ }
350
+ }
351
+ return null;
352
+ }