@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.
- package/.nvmrc +1 -1
- package/.travis.yml +9 -11
- package/README.md +411 -1
- package/eslint.config.mjs +11 -0
- package/package.json +40 -35
- package/scripts/deploy_npm_registry.sh +5 -0
- package/src/App.tsx +35 -29
- package/src/assets/sass/main.scss +12 -4
- package/src/components/Allele.tsx +95 -0
- package/src/components/Anchor.tsx +1 -1
- package/src/components/Breadcrumb.tsx +8 -5
- package/src/components/DatasetDropdown.tsx +10 -23
- package/src/components/ErrorNotification.tsx +9 -0
- package/src/components/GenomeBrowser.tsx +40 -23
- package/src/components/HpoTerm.tsx +1 -1
- package/src/components/{record/Pager.tsx → Pager.tsx} +21 -14
- package/src/components/RecordsPerPage.tsx +9 -7
- package/src/components/RecordsTable.tsx +130 -0
- package/src/components/SampleTable.tsx +70 -98
- package/src/components/SearchBox.tsx +8 -2
- package/src/components/Sort.tsx +28 -25
- package/src/components/Table.tsx +16 -0
- package/src/components/Tooltip.tsx +20 -0
- package/src/components/VariantBreadcrumb.tsx +54 -0
- package/src/components/VariantConsequenceContainer.tsx +100 -0
- package/src/components/VariantConsequenceTable.tsx +58 -0
- package/src/components/VariantContainer.tsx +71 -0
- package/src/components/VariantFilters.tsx +27 -0
- package/src/components/VariantGenotypeTable.tsx +44 -0
- package/src/components/VariantInfoTable.tsx +24 -33
- package/src/components/VariantResults.tsx +103 -0
- package/src/components/VariantTable.tsx +62 -66
- package/src/components/VariantTypeSelect.tsx +34 -0
- package/src/components/VariantsContainer.tsx +150 -0
- package/src/components/VariantsContainerHeader.tsx +70 -0
- package/src/components/field/Field.tsx +80 -0
- package/src/components/field/FieldAlt.tsx +19 -0
- package/src/components/field/FieldChrom.tsx +6 -0
- package/src/components/{record/Id.tsx → field/FieldFilter.tsx} +2 -1
- package/src/components/field/FieldFormat.tsx +10 -0
- package/src/components/{record/Filter.tsx → field/FieldId.tsx} +1 -1
- package/src/components/field/FieldPos.tsx +6 -0
- package/src/components/field/FieldQual.tsx +6 -0
- package/src/components/field/FieldRef.tsx +8 -0
- package/src/components/field/composed/FieldClinVar.tsx +72 -0
- package/src/components/field/composed/FieldComposed.tsx +68 -0
- package/src/components/field/composed/FieldGene.tsx +39 -0
- package/src/components/field/composed/FieldGenotype.tsx +35 -0
- package/src/components/{record/format/GenotypeField.tsx → field/composed/FieldGenotypeSnvSv.tsx} +20 -16
- package/src/components/field/composed/FieldGenotypeStr.tsx +31 -0
- package/src/components/field/composed/FieldGnomAd.tsx +58 -0
- package/src/components/field/composed/FieldHpo.tsx +50 -0
- package/src/components/field/composed/FieldInheritanceModes.tsx +32 -0
- package/src/components/field/composed/FieldLocus.tsx +18 -0
- package/src/components/field/composed/FieldVipC.tsx +25 -0
- package/src/components/field/composed/FieldVipCS.tsx +15 -0
- package/src/components/field/composed/FieldVkgl.tsx +37 -0
- package/src/components/field/genotype/FieldGenotype.tsx +19 -0
- package/src/components/field/genotype/FieldGenotypeType.tsx +9 -0
- package/src/components/field/info/FieldConsequence.tsx +15 -0
- package/src/components/{record/info/Hgvs.tsx → field/info/FieldHgvs.tsx} +4 -6
- package/src/components/field/info/FieldInfo.tsx +27 -0
- package/src/components/{record/info/PubMed.tsx → field/info/FieldPubMed.tsx} +4 -7
- package/src/components/field/typed/FieldCategorical.tsx +17 -0
- package/src/components/{record/field/FieldValueCharacter.tsx → field/typed/FieldCharacter.tsx} +3 -2
- package/src/components/{record/field/FieldValueFlag.tsx → field/typed/FieldFlag.tsx} +3 -2
- package/src/components/{record/field/FieldValueFloat.tsx → field/typed/FieldFloat.tsx} +3 -2
- package/src/components/{record/field/FieldValueInteger.tsx → field/typed/FieldInteger.tsx} +3 -2
- package/src/components/{record/field/FieldValueString.tsx → field/typed/FieldString.tsx} +3 -2
- package/src/components/field/typed/FieldTyped.tsx +20 -0
- package/src/components/field/typed/FieldTypedItem.tsx +49 -0
- package/src/components/field/typed/FieldTypedMultiple.tsx +21 -0
- package/src/components/filter/Filter.tsx +56 -48
- package/src/components/filter/FilterWrapper.scss +23 -0
- package/src/components/filter/FilterWrapper.tsx +63 -0
- package/src/components/filter/composed/FilterAllelicImbalance.tsx +26 -0
- package/src/components/filter/composed/FilterComposed.tsx +92 -0
- package/src/components/filter/composed/FilterDeNovo.tsx +35 -0
- package/src/components/filter/composed/FilterHpo.tsx +16 -0
- package/src/components/filter/composed/FilterInheritance.tsx +42 -0
- package/src/components/filter/composed/FilterLocus.tsx +75 -0
- package/src/components/filter/composed/FilterVipC.tsx +16 -0
- package/src/components/filter/composed/FilterVipCS.tsx +16 -0
- package/src/components/filter/fixed/FilterAlt.tsx +20 -0
- package/src/components/filter/fixed/FilterChrom.tsx +22 -0
- package/src/components/filter/fixed/FilterFilter.tsx +20 -0
- package/src/components/filter/fixed/FilterFixed.tsx +96 -0
- package/src/components/filter/fixed/FilterId.tsx +20 -0
- package/src/components/filter/fixed/FilterPos.tsx +22 -0
- package/src/components/filter/fixed/FilterQual.tsx +21 -0
- package/src/components/filter/fixed/FilterRef.tsx +22 -0
- package/src/components/filter/typed/FilterCategorical.tsx +119 -0
- package/src/components/filter/typed/FilterFlag.tsx +23 -0
- package/src/components/filter/typed/FilterInterval.tsx +72 -0
- package/src/components/filter/typed/FilterString.tsx +43 -0
- package/src/components/filter/typed/FilterTyped.tsx +56 -0
- package/src/components/form/ButtonApply.tsx +11 -0
- package/src/components/form/ButtonDownload.tsx +11 -0
- package/src/components/form/ButtonReset.tsx +9 -0
- package/src/components/{Checkbox.tsx → form/Checkbox.tsx} +4 -9
- package/src/components/form/Input.tsx +19 -0
- package/src/components/form/Select.scss +7 -0
- package/src/components/form/Select.tsx +34 -0
- package/src/components/tree/DecisionTreeBoolMultiQuery.tsx +1 -1
- package/src/components/tree/DecisionTreeBoolQuery.tsx +1 -1
- package/src/components/tree/DecisionTreeNode.tsx +41 -39
- package/src/components/tree/DecisionTreeNodeBool.tsx +1 -1
- package/src/components/tree/DecisionTreeNodeBoolMulti.tsx +1 -1
- package/src/components/tree/DecisionTreeNodeCategorical.tsx +1 -1
- package/src/components/tree/DecisionTreeNodeExists.tsx +1 -1
- package/src/components/tree/DecisionTreeNodeLeaf.tsx +1 -1
- package/src/components/tree/DecisionTreeOutcomeNode.tsx +1 -1
- package/src/components/tree/DecisionTreePath.tsx +1 -1
- package/src/igv.d.ts +2 -1
- package/src/index.tsx +48 -19
- package/src/mocks/GRCh37/decisionTree.json +23 -22
- package/src/mocks/GRCh37/field_metadata.json +435 -95
- package/src/mocks/GRCh37/sampleTree.json +143 -0
- package/src/mocks/GRCh37/static.ts +63 -133
- package/src/mocks/GRCh37/vcf/family.vcf.blob +37 -31
- package/src/mocks/GRCh38/decisionTree.json +52 -33
- package/src/mocks/GRCh38/decisionTreeStr.json +572 -0
- package/src/mocks/GRCh38/fasta/chr1_149380406-149403321.fasta.gz.blob +0 -0
- package/src/mocks/GRCh38/field_metadata.json +435 -95
- package/src/mocks/GRCh38/sampleTree.json +175 -0
- package/src/mocks/GRCh38/static.ts +101 -42
- package/src/mocks/GRCh38/str.cram.blob +0 -0
- package/src/mocks/GRCh38/str.cram.crai.blob +0 -0
- package/src/mocks/GRCh38/vcf/family.vcf.blob +25 -24
- package/src/mocks/GRCh38/vcf/no_vep.vcf.blob +29 -28
- package/src/mocks/GRCh38/vcf/samples_0.vcf.blob +28 -27
- package/src/mocks/GRCh38/vcf/samples_1.vcf.blob +29 -28
- package/src/mocks/GRCh38/vcf/samples_100.vcf.blob +28 -27
- package/src/mocks/GRCh38/vcf/str.vcf.blob +321 -0
- package/src/mocks/MockApiClient.ts +341 -328
- package/src/mocks/config_cram.json +701 -0
- package/src/mocks/config_vcf.json +699 -0
- package/src/store/app.ts +30 -0
- package/src/store/index.tsx +3 -168
- package/src/store/variants.ts +182 -0
- package/src/types/config.d.ts +190 -0
- package/src/types/configCellComposed.d.ts +86 -0
- package/src/types/configCells.d.ts +129 -0
- package/src/types/configFilter.d.ts +80 -0
- package/src/types/configFilterComposed.d.ts +60 -0
- package/src/types/configSort.d.ts +13 -0
- package/src/types/filter.d.ts +17 -0
- package/src/types/store.d.ts +34 -0
- package/src/utils/api.ts +281 -0
- package/src/utils/config/config.ts +182 -0
- package/src/utils/config/configCells.ts +74 -0
- package/src/utils/config/configCellsComposed.ts +508 -0
- package/src/utils/config/configCellsField.ts +61 -0
- package/src/utils/config/configCellsFixed.ts +126 -0
- package/src/utils/config/configFilters.ts +46 -0
- package/src/utils/config/configFiltersComposed.ts +208 -0
- package/src/utils/config/configFiltersField.ts +49 -0
- package/src/utils/config/configFiltersFixed.ts +106 -0
- package/src/utils/config/configSorts.ts +44 -0
- package/src/utils/config/configValidator.ts +380 -0
- package/src/utils/config/configVip.ts +25 -0
- package/src/utils/csq.ts +115 -0
- package/src/utils/decisionTree.ts +45 -0
- package/src/utils/download.ts +30 -0
- package/src/utils/error.ts +69 -0
- package/src/utils/query/query.ts +55 -0
- package/src/utils/query/queryFilter.ts +132 -0
- package/src/utils/query/queryFilterComposed.ts +247 -0
- package/src/utils/query/queryFilterField.ts +75 -0
- package/src/utils/query/queryFilterFixed.ts +44 -0
- package/src/utils/query/querySample.ts +18 -0
- package/src/utils/query/queryVariantType.ts +76 -0
- package/src/utils/query/selector.ts +41 -0
- package/src/utils/{sortUtils.ts → query/sort.ts} +32 -11
- package/src/utils/sample.ts +19 -35
- package/src/utils/utils.ts +66 -2
- package/src/utils/variantType.ts +43 -0
- package/src/utils/vcf.ts +352 -0
- package/src/views/Help.tsx +109 -114
- package/src/views/Home.tsx +3 -2
- package/src/views/Sample.tsx +12 -7
- package/src/views/SampleVariant.tsx +23 -112
- package/src/views/SampleVariantConsequence.tsx +54 -114
- package/src/views/SampleVariants.tsx +33 -445
- package/src/views/SampleVariantsRedirect.tsx +20 -0
- package/src/views/Samples.tsx +7 -10
- package/src/views/Variant.tsx +31 -61
- package/src/views/VariantConsequence.tsx +42 -72
- package/src/views/Variants.tsx +29 -138
- package/src/views/VariantsRedirect.tsx +25 -0
- package/src/views/data/data.tsx +32 -6
- package/tests/store/variants.test.ts +122 -0
- package/tests/utils/config/config.test.ts +167 -0
- package/tests/utils/config/configCells.test.ts +86 -0
- package/tests/utils/config/configCellsComposed.test.ts +1163 -0
- package/tests/utils/config/configCellsField.test.ts +164 -0
- package/tests/utils/config/configCellsFixed.test.ts +99 -0
- package/tests/utils/config/configFilters.test.ts +80 -0
- package/tests/utils/config/configFiltersComposed.test.ts +504 -0
- package/tests/utils/config/configFiltersField.test.ts +140 -0
- package/tests/utils/config/configFiltersFixed.test.ts +81 -0
- package/tests/utils/config/configSorts.test.ts +55 -0
- package/tests/utils/config/configValidator.test.ts +56 -0
- package/tests/utils/config/configVip.test.ts +53 -0
- package/tests/utils/decisionTree.test.ts +71 -0
- package/tests/utils/download.test.ts +20 -0
- package/tests/utils/query/query.test.ts +84 -0
- package/tests/utils/query/queryFilter.test.ts +243 -0
- package/tests/utils/query/queryFilterComposed.test.ts +301 -0
- package/tests/utils/query/queryFilterField.test.ts +75 -0
- package/tests/utils/query/queryFilterFixed.test.ts +86 -0
- package/tests/utils/query/querySample.test.ts +45 -0
- package/tests/utils/query/queryVariantType.test.ts +56 -0
- package/{src/__tests__/sortUtils.test.ts → tests/utils/query/sort.test.ts} +3 -4
- package/tests/utils/sample.test.ts +259 -0
- package/tests/utils/utils.test.ts +120 -0
- package/tests/utils/variantType.test.ts +48 -0
- package/tests/utils/vcf.test.ts +649 -0
- package/tsconfig.json +6 -2
- package/vite.config.mts +20 -3
- package/.eslintignore +0 -4
- package/.eslintrc.js +0 -23
- package/src/Api.ts +0 -12
- package/src/__tests__/decisionTreeUtils.test.ts +0 -75
- package/src/__tests__/field.test.ts +0 -107
- package/src/__tests__/query.test.ts +0 -188
- package/src/__tests__/sample.test.ts +0 -184
- package/src/__tests__/utils.test.ts +0 -24
- package/src/__tests__/viewUtils.test.ts +0 -125
- package/src/components/ConsequenceTable.tsx +0 -45
- package/src/components/Error.tsx +0 -9
- package/src/components/FieldHeader.tsx +0 -26
- package/src/components/InfoCollapsablePane.tsx +0 -90
- package/src/components/VariantInfoNestedTable.tsx +0 -127
- package/src/components/VariantSampleTable.tsx +0 -58
- package/src/components/VariantsSampleTable.tsx +0 -183
- package/src/components/VariantsTable.tsx +0 -124
- package/src/components/filter/FilterAllelicBalance.tsx +0 -81
- package/src/components/filter/FilterCategorical.tsx +0 -81
- package/src/components/filter/FilterClinVar.tsx +0 -21
- package/src/components/filter/FilterGene.tsx +0 -34
- package/src/components/filter/FilterHpo.tsx +0 -161
- package/src/components/filter/FilterInheritance.tsx +0 -162
- package/src/components/filter/FilterIntegerGq.tsx +0 -47
- package/src/components/filter/FilterVI.tsx +0 -68
- package/src/components/filter/FilterVariantType.tsx +0 -146
- package/src/components/filter/Filters.tsx +0 -29
- package/src/components/filter/InfoFilter.tsx +0 -39
- package/src/components/filter/InfoFilters.tsx +0 -35
- package/src/components/filter/SampleFilters.tsx +0 -93
- package/src/components/filter/SamplesFilters.tsx +0 -33
- package/src/components/record/Allele.tsx +0 -38
- package/src/components/record/AlleleBreakend.tsx +0 -5
- package/src/components/record/AlleleMissing.tsx +0 -5
- package/src/components/record/AlleleNucs.tsx +0 -49
- package/src/components/record/AlleleSymbolic.tsx +0 -5
- package/src/components/record/Alt.tsx +0 -17
- package/src/components/record/Chrom.tsx +0 -5
- package/src/components/record/Format.tsx +0 -40
- package/src/components/record/Info.tsx +0 -55
- package/src/components/record/Pos.tsx +0 -5
- package/src/components/record/Qual.tsx +0 -5
- package/src/components/record/RecordDownload.tsx +0 -66
- package/src/components/record/Ref.tsx +0 -6
- package/src/components/record/field/Field.tsx +0 -36
- package/src/components/record/field/FieldMultipleValue.tsx +0 -22
- package/src/components/record/field/FieldSingleValue.tsx +0 -35
- package/src/components/record/info/ClinVar.tsx +0 -81
- package/src/components/record/info/Consequence.tsx +0 -18
- package/src/components/record/info/Gene.tsx +0 -56
- package/src/components/record/info/GnomAD.tsx +0 -54
- package/src/components/record/info/Hpo.tsx +0 -52
- package/src/components/record/info/InheritanceModes.tsx +0 -22
- package/src/components/record/info/VipC.tsx +0 -23
- package/src/components/record/info/Vkgl.tsx +0 -42
- package/src/mocks/GRCh37/vcf/no_vep.vcf.blob +0 -61
- package/src/mocks/GRCh37/vcf/samples_0.vcf.blob +0 -93
- package/src/mocks/GRCh37/vcf/samples_1.vcf.blob +0 -93
- package/src/mocks/GRCh37/vcf/samples_100.vcf.blob +0 -93
- package/src/utils/ApiUtils.ts +0 -259
- package/src/utils/csqUtils.ts +0 -27
- package/src/utils/decisionTreeUtils.ts +0 -14
- package/src/utils/field.ts +0 -49
- package/src/utils/query.ts +0 -154
- package/src/utils/viewUtils.ts +0 -32
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { FieldMetadata } from "@molgenis/vip-report-vcf
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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
|
}
|
package/src/utils/sample.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { Sample } from "@molgenis/vip-report-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,
|
|
34
|
-
return isFamily(sample,
|
|
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
|
|
38
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
}
|
package/src/utils/utils.ts
CHANGED
|
@@ -1,3 +1,67 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
+
}
|
package/src/utils/vcf.ts
ADDED
|
@@ -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
|
+
}
|