@molgenis/vip-report-template 3.0.0-beta2 → 3.1.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.
- package/package.json +3 -3
- package/src/__tests__/query.test.ts +21 -1
- package/src/components/Anchor.tsx +10 -7
- package/src/components/ConsequenceTable.tsx +11 -4
- package/src/components/HpoTerm.tsx +13 -0
- package/src/components/InfoCollapsablePane.tsx +29 -22
- package/src/components/SampleTable.tsx +4 -3
- package/src/components/VariantInfoNestedTable.tsx +26 -16
- package/src/components/VariantInfoTable.tsx +16 -6
- package/src/components/VariantSampleTable.tsx +4 -4
- package/src/components/VariantsSampleTable.tsx +3 -2
- package/src/components/VariantsTable.tsx +13 -27
- package/src/components/filter/Filter.tsx +12 -8
- package/src/components/filter/FilterCategorical.tsx +17 -10
- package/src/components/filter/FilterClinVar.tsx +21 -0
- package/src/components/filter/FilterIntegerDp.tsx +2 -9
- package/src/components/filter/FilterIntegerVid.tsx +2 -9
- package/src/components/filter/FilterIntegerVim.tsx +2 -9
- package/src/components/filter/InfoFilter.tsx +3 -9
- package/src/components/record/Format.tsx +13 -4
- package/src/components/record/Info.tsx +18 -32
- package/src/components/record/field/Field.tsx +22 -7
- package/src/components/record/info/ClinVar.tsx +77 -33
- package/src/components/record/info/Consequence.tsx +7 -8
- package/src/components/record/info/Gene.tsx +52 -7
- package/src/components/record/info/GnomAD.tsx +40 -20
- package/src/components/record/info/Hgvs.tsx +11 -9
- package/src/components/record/info/PubMed.tsx +17 -13
- package/src/mocks/GRCh37/static.ts +1 -14
- package/src/mocks/GRCh37/vcf/family.vcf.blob +39 -39
- package/src/utils/ApiUtils.ts +10 -9
- package/src/utils/csqUtils.ts +27 -0
- package/src/utils/query.ts +14 -0
- package/src/views/Home.tsx +1 -1
- package/src/views/SampleVariant.tsx +6 -3
- package/src/views/SampleVariantConsequence.tsx +12 -5
- package/src/views/SampleVariants.tsx +17 -8
- package/src/views/Samples.tsx +1 -1
- package/src/views/Variant.tsx +1 -1
- package/src/views/VariantConsequence.tsx +2 -2
- package/src/views/Variants.tsx +11 -5
- package/src/components/record/info/HpoTerm.tsx +0 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@molgenis/vip-report-template",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "Report Template for Variant Call Format (VCF) Report Generator",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "vite build",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@fortawesome/fontawesome-svg-core": "^6.1.1",
|
|
37
37
|
"@fortawesome/free-solid-svg-icons": "^6.1.1",
|
|
38
|
-
"@molgenis/vip-report-api": "^3.
|
|
39
|
-
"@molgenis/vip-report-vcf": "^1.
|
|
38
|
+
"@molgenis/vip-report-api": "^3.5.2",
|
|
39
|
+
"@molgenis/vip-report-vcf": "^1.2.2",
|
|
40
40
|
"base64-js": "^1.5.1",
|
|
41
41
|
"igv": "^2.12.6",
|
|
42
42
|
"solid-app-router": "^0.3.3",
|
|
@@ -4,6 +4,7 @@ import { Metadata } from "@molgenis/vip-report-vcf/src/Vcf";
|
|
|
4
4
|
import { FilterQueries } from "../store";
|
|
5
5
|
import {
|
|
6
6
|
createQuery,
|
|
7
|
+
createSampleQuery,
|
|
7
8
|
infoFieldKey,
|
|
8
9
|
infoSelector,
|
|
9
10
|
infoSortPath,
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
sampleSelector,
|
|
12
13
|
selectorKey,
|
|
13
14
|
} from "../utils/query";
|
|
14
|
-
import { Person, QueryClause } from "@molgenis/vip-report-api/src/Api";
|
|
15
|
+
import { Item, Person, QueryClause, Sample } from "@molgenis/vip-report-api/src/Api";
|
|
15
16
|
|
|
16
17
|
describe("query utilities", () => {
|
|
17
18
|
let fieldMetaCsq: FieldMetadata = {
|
|
@@ -52,6 +53,25 @@ describe("query utilities", () => {
|
|
|
52
53
|
|
|
53
54
|
const meta: Metadata = { info: { CSQ: fieldMetaCsq } } as unknown as Metadata;
|
|
54
55
|
|
|
56
|
+
test("create sample query - filters", () => {
|
|
57
|
+
const sample = { data: { index: 1 } } as Item<Sample>;
|
|
58
|
+
const queryClause: QueryClause = { selector: ["s", 1, "GT", "t"], operator: "!=", args: "hom_r" };
|
|
59
|
+
const filterQueryClause: QueryClause = { selector: ["n", "CSQ", "*", 0], operator: "==", args: [0] };
|
|
60
|
+
const filterQueries: FilterQueries = {
|
|
61
|
+
"CSQ/field1": filterQueryClause,
|
|
62
|
+
};
|
|
63
|
+
expect(createSampleQuery(sample, undefined, filterQueries, meta)).toStrictEqual({
|
|
64
|
+
operator: "and",
|
|
65
|
+
args: [queryClause, filterQueryClause],
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("create sample query - undefined search and undefined filters", () => {
|
|
70
|
+
const sample = { data: { index: 1 } } as Item<Sample>;
|
|
71
|
+
const queryClause: QueryClause = { selector: ["s", 1, "GT", "t"], operator: "!=", args: "hom_r" };
|
|
72
|
+
expect(createSampleQuery(sample, undefined, undefined, meta)).toStrictEqual(queryClause);
|
|
73
|
+
});
|
|
74
|
+
|
|
55
75
|
test("create query", () => {
|
|
56
76
|
const searchText = "my search text";
|
|
57
77
|
const filterQueries: FilterQueries = {
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ParentComponent, Show } from "solid-js";
|
|
2
2
|
|
|
3
|
-
export const Anchor:
|
|
4
|
-
href: string;
|
|
5
|
-
value: string | number;
|
|
3
|
+
export const Anchor: ParentComponent<{
|
|
4
|
+
href: string | null | undefined;
|
|
6
5
|
}> = (props) => {
|
|
7
6
|
return (
|
|
8
|
-
<
|
|
9
|
-
{
|
|
10
|
-
|
|
7
|
+
<Show when={props.href} fallback={props.children}>
|
|
8
|
+
{(href) => (
|
|
9
|
+
<a href={href} target="_blank" rel="noopener noreferrer nofollow">
|
|
10
|
+
{props.children}
|
|
11
|
+
</a>
|
|
12
|
+
)}
|
|
13
|
+
</Show>
|
|
11
14
|
);
|
|
12
15
|
};
|
|
@@ -4,10 +4,13 @@ import { FieldMetadata } from "@molgenis/vip-report-vcf/src/MetadataParser";
|
|
|
4
4
|
import { Info } from "./record/Info";
|
|
5
5
|
import { FieldHeader } from "./FieldHeader";
|
|
6
6
|
import { Record } from "@molgenis/vip-report-vcf/src/Vcf";
|
|
7
|
+
import { Item } from "@molgenis/vip-report-api/src/Api";
|
|
7
8
|
|
|
8
|
-
export const ConsequenceTable: Component<{
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
export const ConsequenceTable: Component<{
|
|
10
|
+
csqMetadata: FieldMetadata[];
|
|
11
|
+
csqValues: ValueArray;
|
|
12
|
+
record: Item<Record>;
|
|
13
|
+
}> = (props) => {
|
|
11
14
|
function getValues(index: number): ValueArray {
|
|
12
15
|
return props.csqValues[index] !== null ? (props.csqValues[index] as ValueArray) : [];
|
|
13
16
|
}
|
|
@@ -24,7 +27,11 @@ export const ConsequenceTable: Component<{ csqMetadata: FieldMetadata[]; csqValu
|
|
|
24
27
|
<tr>
|
|
25
28
|
<FieldHeader field={field} />
|
|
26
29
|
<td>
|
|
27
|
-
<Info
|
|
30
|
+
<Info
|
|
31
|
+
info={{ value: props.csqValues[index()], valueParent: props.csqValues, record: props.record }}
|
|
32
|
+
infoMeta={field}
|
|
33
|
+
context={{}}
|
|
34
|
+
/>
|
|
28
35
|
</td>
|
|
29
36
|
</tr>
|
|
30
37
|
</Show>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Component } from "solid-js";
|
|
2
|
+
import { Anchor } from "./Anchor";
|
|
3
|
+
import { OntologyClass } from "@molgenis/vip-report-api/src/Api";
|
|
4
|
+
|
|
5
|
+
export const HpoTerm: Component<{
|
|
6
|
+
ontologyClass: OntologyClass;
|
|
7
|
+
}> = (props) => {
|
|
8
|
+
return (
|
|
9
|
+
<Anchor href={`https://hpo.jax.org/app/browse/term/${props.ontologyClass.id}`}>
|
|
10
|
+
<span>{props.ontologyClass.label}</span>
|
|
11
|
+
</Anchor>
|
|
12
|
+
);
|
|
13
|
+
};
|
|
@@ -1,39 +1,41 @@
|
|
|
1
1
|
import { Component, createMemo, createSignal, For, Signal } from "solid-js";
|
|
2
|
-
import { FieldMetadata
|
|
2
|
+
import { FieldMetadata } from "@molgenis/vip-report-vcf/src/MetadataParser";
|
|
3
3
|
import { Record } from "@molgenis/vip-report-vcf/src/Vcf";
|
|
4
|
-
import { Item } from "@molgenis/vip-report-api/src/Api";
|
|
4
|
+
import { HtsFileMetadata, Item } from "@molgenis/vip-report-api/src/Api";
|
|
5
5
|
import { ValueArray } from "@molgenis/vip-report-vcf/src/ValueParser";
|
|
6
6
|
import { Info } from "./record/Info";
|
|
7
|
-
import {
|
|
7
|
+
import { FieldValue } from "./record/field/Field";
|
|
8
|
+
import { isCsqInfo } from "../utils/csqUtils";
|
|
9
|
+
import { Link, useLocation } from "solid-app-router";
|
|
8
10
|
|
|
9
11
|
export const InfoCollapsablePane: Component<{
|
|
10
12
|
fields: FieldMetadata[];
|
|
11
13
|
record: Item<Record>;
|
|
14
|
+
htsFileMeta: HtsFileMetadata;
|
|
12
15
|
}> = (props) => {
|
|
13
16
|
const [collapsed, setCollapsed]: Signal<boolean> = createSignal(false);
|
|
14
17
|
|
|
15
|
-
function getHref(field: InfoMetadata, consequenceIndex: number): string | undefined {
|
|
16
|
-
let href;
|
|
17
|
-
if (field.id === "Consequence" && field.parent?.id === "CSQ") {
|
|
18
|
-
href = `${useLocation().pathname}/${props.record.id}/consequences/${consequenceIndex}`;
|
|
19
|
-
}
|
|
20
|
-
return href;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
18
|
function toggleCollapse() {
|
|
24
19
|
setCollapsed(!collapsed());
|
|
25
20
|
}
|
|
26
21
|
|
|
27
|
-
const values = createMemo(() =>
|
|
22
|
+
const values = createMemo((): FieldValue[][] =>
|
|
28
23
|
props.fields.map((field) => {
|
|
29
24
|
if (field.parent) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
25
|
+
const values = (props.record.data.n[field.parent.id] || []) as ValueArray;
|
|
26
|
+
return values.map((nestedValues) => {
|
|
27
|
+
const value =
|
|
28
|
+
field.parent && field.parent.nested
|
|
29
|
+
? (nestedValues as ValueArray)[
|
|
30
|
+
field.parent.nested.items.findIndex((nestedField) => nestedField.id === field.id)
|
|
31
|
+
]
|
|
32
|
+
: null;
|
|
33
|
+
return {
|
|
34
|
+
record: props.record,
|
|
35
|
+
value: value,
|
|
36
|
+
valueParent: nestedValues,
|
|
37
|
+
};
|
|
38
|
+
});
|
|
37
39
|
}
|
|
38
40
|
throw new Error(`Nested field '${field.id}' needs to have a parent field.`);
|
|
39
41
|
})
|
|
@@ -54,9 +56,14 @@ export const InfoCollapsablePane: Component<{
|
|
|
54
56
|
{(value, j) => (
|
|
55
57
|
<>
|
|
56
58
|
{j() != 0 && collapsed() && <br />}
|
|
57
|
-
{(j() == 0 || collapsed()) &&
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
{(j() == 0 || collapsed()) &&
|
|
60
|
+
(isCsqInfo(field, "Consequence") ? (
|
|
61
|
+
<Link href={`${useLocation().pathname}/${props.record.id}/consequences/${j()}`}>
|
|
62
|
+
<Info info={value} infoMeta={field} context={props.htsFileMeta} />
|
|
63
|
+
</Link>
|
|
64
|
+
) : (
|
|
65
|
+
<Info info={value} infoMeta={field} context={props.htsFileMeta} />
|
|
66
|
+
))}
|
|
60
67
|
</>
|
|
61
68
|
)}
|
|
62
69
|
</For>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Link } from "solid-app-router";
|
|
2
2
|
import { Component, createMemo, For, Show } from "solid-js";
|
|
3
3
|
import { Item, Phenotype, PhenotypicFeature, Sample } from "@molgenis/vip-report-api/src/Api";
|
|
4
|
-
import { HpoTerm } from "./
|
|
4
|
+
import { HpoTerm } from "./HpoTerm";
|
|
5
|
+
import { Anchor } from "./Anchor";
|
|
5
6
|
|
|
6
7
|
function getAffectedStatusLabel(affectedStatus: string) {
|
|
7
8
|
let label;
|
|
@@ -96,7 +97,7 @@ export const SampleTable: Component<{
|
|
|
96
97
|
</td>
|
|
97
98
|
<td>
|
|
98
99
|
<Show when={getPhenotypes(sample.data.person.individualId).length > 0}>
|
|
99
|
-
<
|
|
100
|
+
<Anchor
|
|
100
101
|
href={`https://vibe.molgeniscloud.org/?phenotypes=${getPhenotypes(
|
|
101
102
|
sample.data.person.individualId
|
|
102
103
|
)
|
|
@@ -104,7 +105,7 @@ export const SampleTable: Component<{
|
|
|
104
105
|
.join(",")}`}
|
|
105
106
|
>
|
|
106
107
|
<i class="fas fa-external-link" />
|
|
107
|
-
</
|
|
108
|
+
</Anchor>
|
|
108
109
|
</Show>
|
|
109
110
|
</td>
|
|
110
111
|
</tr>
|
|
@@ -5,6 +5,8 @@ import { FieldMetadata, InfoMetadata } from "@molgenis/vip-report-vcf/src/Metada
|
|
|
5
5
|
import { FieldHeader } from "./FieldHeader";
|
|
6
6
|
import { Record } from "@molgenis/vip-report-vcf/src/Vcf";
|
|
7
7
|
import { Item } from "@molgenis/vip-report-api/src/Api";
|
|
8
|
+
import { isCsqInfo } from "../utils/csqUtils";
|
|
9
|
+
import { Link, useLocation } from "solid-app-router";
|
|
8
10
|
|
|
9
11
|
function isNonEmptyNestedInfoItem(nestedInfoField: FieldMetadata, index: number, value: Value[] | Value[][]): boolean {
|
|
10
12
|
const infoField = nestedInfoField.nested?.items[index];
|
|
@@ -43,15 +45,6 @@ export const VariantInfoNestedTable: Component<{
|
|
|
43
45
|
record: Item<Record>;
|
|
44
46
|
sample: { id: number; label: string } | null;
|
|
45
47
|
}> = (props) => {
|
|
46
|
-
function getHref(field: InfoMetadata, consequenceIndex: number): string | undefined {
|
|
47
|
-
let href;
|
|
48
|
-
if (field.id === "Consequence" && field.parent?.id === "CSQ") {
|
|
49
|
-
href = `/variants/${props.record.id}/consequences/${consequenceIndex}`;
|
|
50
|
-
if (props.sample !== null) href = `/samples/${props.sample.id}` + href;
|
|
51
|
-
}
|
|
52
|
-
return href;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
48
|
return (
|
|
56
49
|
<div style={{ display: "grid" }}>
|
|
57
50
|
{/* workaround for https://github.com/jgthms/bulma/issues/2572#issuecomment-523099776 */}
|
|
@@ -76,7 +69,7 @@ export const VariantInfoNestedTable: Component<{
|
|
|
76
69
|
<>
|
|
77
70
|
{isNonEmptyNestedInfoItem(props.infoField, -1, props.infoValue) && (
|
|
78
71
|
<td>
|
|
79
|
-
<Info info={value}
|
|
72
|
+
<Info info={{ value: value, record: props.record }} infoMeta={props.infoField} context={{}} />
|
|
80
73
|
</td>
|
|
81
74
|
)}
|
|
82
75
|
</>
|
|
@@ -93,12 +86,29 @@ export const VariantInfoNestedTable: Component<{
|
|
|
93
86
|
<>
|
|
94
87
|
{isNonEmptyNestedInfoItem(props.infoField, i(), props.infoValue) && (
|
|
95
88
|
<td>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
89
|
+
{isCsqInfo(infoFieldItem, "Consequence") ? (
|
|
90
|
+
<Link href={`${useLocation().pathname}/consequences/${j()}`}>
|
|
91
|
+
<Info
|
|
92
|
+
info={{
|
|
93
|
+
value: Array.isArray(value) ? value[i()] : null,
|
|
94
|
+
valueParent: value,
|
|
95
|
+
record: props.record,
|
|
96
|
+
}}
|
|
97
|
+
infoMeta={infoFieldItem}
|
|
98
|
+
context={{}}
|
|
99
|
+
/>
|
|
100
|
+
</Link>
|
|
101
|
+
) : (
|
|
102
|
+
<Info
|
|
103
|
+
info={{
|
|
104
|
+
value: Array.isArray(value) ? value[i()] : null,
|
|
105
|
+
valueParent: value,
|
|
106
|
+
record: props.record,
|
|
107
|
+
}}
|
|
108
|
+
infoMeta={infoFieldItem}
|
|
109
|
+
context={{}}
|
|
110
|
+
/>
|
|
111
|
+
)}
|
|
102
112
|
</td>
|
|
103
113
|
)}
|
|
104
114
|
</>
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { Component, For } from "solid-js";
|
|
2
|
-
import { FieldMetadataContainer
|
|
2
|
+
import { FieldMetadataContainer } from "@molgenis/vip-report-vcf/src/VcfParser";
|
|
3
|
+
import { Info } from "./record/Info";
|
|
4
|
+
import { Record } from "@molgenis/vip-report-vcf/src/Vcf";
|
|
5
|
+
import { Item } from "@molgenis/vip-report-api/src/Api";
|
|
3
6
|
|
|
4
|
-
export const VariantInfoTable: Component<{
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
export const VariantInfoTable: Component<{
|
|
8
|
+
infoFields: FieldMetadataContainer;
|
|
9
|
+
record: Item<Record>;
|
|
10
|
+
}> = (props) => {
|
|
7
11
|
return (
|
|
8
12
|
<div style={{ display: "grid" }}>
|
|
9
13
|
{/* workaround for https://github.com/jgthms/bulma/issues/2572#issuecomment-523099776 */}
|
|
@@ -12,13 +16,19 @@ export const VariantInfoTable: Component<{ infoValues: InfoContainer; infoFields
|
|
|
12
16
|
<tbody>
|
|
13
17
|
<For
|
|
14
18
|
each={Object.values(props.infoFields).filter(
|
|
15
|
-
(info) => !info.nested && props.
|
|
19
|
+
(info) => !info.nested && props.record.data.n[info.id] !== undefined
|
|
16
20
|
)}
|
|
17
21
|
>
|
|
18
22
|
{(infoField) => (
|
|
19
23
|
<tr>
|
|
20
24
|
<td>{infoField.id}</td>
|
|
21
|
-
<td>
|
|
25
|
+
<td>
|
|
26
|
+
<Info
|
|
27
|
+
info={{ value: props.record.data.n[infoField.id], record: props.record }}
|
|
28
|
+
infoMeta={infoField}
|
|
29
|
+
context={{}}
|
|
30
|
+
/>
|
|
31
|
+
</td>
|
|
22
32
|
</tr>
|
|
23
33
|
)}
|
|
24
34
|
</For>
|
|
@@ -3,14 +3,14 @@ import { Record, RecordSample } from "@molgenis/vip-report-vcf/src/Vcf";
|
|
|
3
3
|
import { FieldMetadataContainer } from "@molgenis/vip-report-vcf/src/VcfParser";
|
|
4
4
|
import { FieldMetadata } from "@molgenis/vip-report-vcf/src/MetadataParser";
|
|
5
5
|
import { Format } from "./record/Format";
|
|
6
|
-
import { Sample } from "@molgenis/vip-report-api/src/Api";
|
|
6
|
+
import { Item, Sample } from "@molgenis/vip-report-api/src/Api";
|
|
7
7
|
import { FieldHeader } from "./FieldHeader";
|
|
8
8
|
|
|
9
9
|
export const VariantSampleTable: Component<{
|
|
10
10
|
formatFields: FieldMetadataContainer;
|
|
11
11
|
samples: Sample[];
|
|
12
12
|
sampleValues: RecordSample[];
|
|
13
|
-
record: Record
|
|
13
|
+
record: Item<Record>;
|
|
14
14
|
}> = (props) => {
|
|
15
15
|
const sampleFields = createMemo((): FieldMetadata[] =>
|
|
16
16
|
Object.keys(props.sampleValues[0]).map((fieldId) => props.formatFields[fieldId])
|
|
@@ -39,8 +39,8 @@ export const VariantSampleTable: Component<{
|
|
|
39
39
|
formatMetadata={formatField}
|
|
40
40
|
record={props.record}
|
|
41
41
|
isAbbreviate={false}
|
|
42
|
-
allelicDepth={props.record.s[props.samples[i()].index]["AD"] as number[]}
|
|
43
|
-
readDepth={props.record.s[props.samples[i()].index]["DP"] as number}
|
|
42
|
+
allelicDepth={props.record.data.s[props.samples[i()].index]["AD"] as number[]}
|
|
43
|
+
readDepth={props.record.data.s[props.samples[i()].index]["DP"] as number}
|
|
44
44
|
/>
|
|
45
45
|
</td>
|
|
46
46
|
)}
|
|
@@ -3,7 +3,7 @@ import { Ref } from "./record/Ref";
|
|
|
3
3
|
import { Chrom } from "./record/Chrom";
|
|
4
4
|
import { Pos } from "./record/Pos";
|
|
5
5
|
import { Link } from "solid-app-router";
|
|
6
|
-
import { Item, Sample } from "@molgenis/vip-report-api/src/Api";
|
|
6
|
+
import { HtsFileMetadata, Item, Sample } from "@molgenis/vip-report-api/src/Api";
|
|
7
7
|
import { GenotypeField } from "./record/format/GenotypeField";
|
|
8
8
|
import { InfoCollapsablePane } from "./InfoCollapsablePane";
|
|
9
9
|
import { Component, createMemo, For } from "solid-js";
|
|
@@ -18,6 +18,7 @@ export const VariantsSampleTable: Component<{
|
|
|
18
18
|
records: Item<Record>[];
|
|
19
19
|
recordsMetadata: Metadata;
|
|
20
20
|
nestedFields: FieldMetadata[];
|
|
21
|
+
htsFileMeta: HtsFileMetadata;
|
|
21
22
|
}> = (props) => {
|
|
22
23
|
const samples = createMemo(() => [props.item.data, ...props.pedigreeSamples.map((item) => item.data)]);
|
|
23
24
|
|
|
@@ -75,7 +76,7 @@ export const VariantsSampleTable: Component<{
|
|
|
75
76
|
</td>
|
|
76
77
|
)}
|
|
77
78
|
</For>
|
|
78
|
-
<InfoCollapsablePane fields={props.nestedFields} record={record} />
|
|
79
|
+
<InfoCollapsablePane fields={props.nestedFields} record={record} htsFileMeta={props.htsFileMeta} />
|
|
79
80
|
</tr>
|
|
80
81
|
)}
|
|
81
82
|
</For>
|
|
@@ -8,11 +8,11 @@ import { Alt } from "./record/Alt";
|
|
|
8
8
|
import { Qual } from "./record/Qual";
|
|
9
9
|
import { Filter } from "./record/Filter";
|
|
10
10
|
import { Info } from "./record/Info";
|
|
11
|
-
import { Value } from "@molgenis/vip-report-vcf/src/ValueParser";
|
|
12
11
|
import { FieldMetadata } from "@molgenis/vip-report-vcf/src/MetadataParser";
|
|
13
12
|
import { Link } from "solid-app-router";
|
|
14
|
-
import { Item } from "@molgenis/vip-report-api/src/Api";
|
|
13
|
+
import { HtsFileMetadata, Item } from "@molgenis/vip-report-api/src/Api";
|
|
15
14
|
import { FieldHeader } from "./FieldHeader";
|
|
15
|
+
import { InfoCollapsablePane } from "./InfoCollapsablePane";
|
|
16
16
|
|
|
17
17
|
const computeRowspan = (recordsMetadata: Metadata) =>
|
|
18
18
|
Object.values(recordsMetadata.info).find((field) => field.nested) !== undefined ? 2 : 1;
|
|
@@ -21,6 +21,7 @@ const computeColspan = (field: FieldMetadata) => (field.nested ? field.nested.it
|
|
|
21
21
|
export const VariantsTable: Component<{
|
|
22
22
|
records: Item<Record>[];
|
|
23
23
|
recordsMetadata: Metadata;
|
|
24
|
+
htsFileMeta: HtsFileMetadata;
|
|
24
25
|
}> = (props) => {
|
|
25
26
|
const infoFields = () => Object.values(props.recordsMetadata.info);
|
|
26
27
|
const infoFieldsNested = () => infoFields().filter((infoField) => infoField.nested);
|
|
@@ -93,33 +94,18 @@ export const VariantsTable: Component<{
|
|
|
93
94
|
<For each={infoFields()}>
|
|
94
95
|
{(infoField) =>
|
|
95
96
|
infoField.nested ? (
|
|
96
|
-
<
|
|
97
|
-
{
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<Info
|
|
102
|
-
info={(record.data.n[infoField.id] as Value[])[i()]}
|
|
103
|
-
infoMetadata={infoFieldNested}
|
|
104
|
-
variant={record.data}
|
|
105
|
-
/>
|
|
106
|
-
</>
|
|
107
|
-
) : (
|
|
108
|
-
<For each={record.data.n[infoField.id] as unknown as Value[][]}>
|
|
109
|
-
{(value, j) => (
|
|
110
|
-
<>
|
|
111
|
-
{j() !== 0 && <br />}
|
|
112
|
-
<Info info={value[i()]} infoMetadata={infoFieldNested} variant={record.data} />
|
|
113
|
-
</>
|
|
114
|
-
)}
|
|
115
|
-
</For>
|
|
116
|
-
)}
|
|
117
|
-
</td>
|
|
118
|
-
)}
|
|
119
|
-
</For>
|
|
97
|
+
<InfoCollapsablePane
|
|
98
|
+
fields={infoField.nested.items}
|
|
99
|
+
record={record}
|
|
100
|
+
htsFileMeta={props.htsFileMeta}
|
|
101
|
+
/>
|
|
120
102
|
) : (
|
|
121
103
|
<td>
|
|
122
|
-
<Info
|
|
104
|
+
<Info
|
|
105
|
+
info={{ value: record.data.n[infoField.id], record: record }}
|
|
106
|
+
infoMeta={infoField}
|
|
107
|
+
context={props.htsFileMeta}
|
|
108
|
+
/>
|
|
123
109
|
</td>
|
|
124
110
|
)
|
|
125
111
|
}
|
|
@@ -5,32 +5,36 @@ import { FilterIntegerVim } from "./FilterIntegerVim";
|
|
|
5
5
|
import { FilterIntegerDp } from "./FilterIntegerDp";
|
|
6
6
|
import { FilterIntegerVid } from "./FilterIntegerVid";
|
|
7
7
|
import { QueryClause, Selector } from "@molgenis/vip-report-api/src/Api";
|
|
8
|
+
import { FilterClinVar } from "./FilterClinVar";
|
|
9
|
+
import { isAnyCsqInfo } from "../../utils/csqUtils";
|
|
8
10
|
|
|
9
11
|
export type FilterChangeEvent = { query: QueryClause };
|
|
10
12
|
export type FilterClearEvent = { selector: Selector };
|
|
11
13
|
|
|
12
|
-
export
|
|
14
|
+
export type FilterProps = {
|
|
13
15
|
field: FieldMetadata;
|
|
14
16
|
query?: QueryClause;
|
|
15
17
|
onChange: (event: FilterChangeEvent) => void;
|
|
16
18
|
onClear: (event: FilterClearEvent) => void;
|
|
17
|
-
}
|
|
18
|
-
const onChange = (event: FilterChangeEvent) => props.onChange(event);
|
|
19
|
-
const onClear = (event: FilterClearEvent) => props.onClear(event);
|
|
19
|
+
};
|
|
20
20
|
|
|
21
|
+
export const Filter: Component<FilterProps> = (props) => {
|
|
21
22
|
return (
|
|
22
23
|
<Switch>
|
|
23
24
|
<Match when={props.field.id === "DP"}>
|
|
24
|
-
<FilterIntegerDp
|
|
25
|
+
<FilterIntegerDp {...props} />
|
|
25
26
|
</Match>
|
|
26
27
|
<Match when={props.field.id === "VID"}>
|
|
27
|
-
<FilterIntegerVid
|
|
28
|
+
<FilterIntegerVid {...props} />
|
|
28
29
|
</Match>
|
|
29
30
|
<Match when={props.field.id === "VIM"}>
|
|
30
|
-
<FilterIntegerVim
|
|
31
|
+
<FilterIntegerVim {...props} />
|
|
32
|
+
</Match>
|
|
33
|
+
<Match when={isAnyCsqInfo(props.field, ["clinVar_CLNSIG", "clinVar_CLNSIGINCL"])}>
|
|
34
|
+
<FilterClinVar {...props} />
|
|
31
35
|
</Match>
|
|
32
36
|
<Match when={props.field.type === "CATEGORICAL"}>
|
|
33
|
-
<FilterCategorical
|
|
37
|
+
<FilterCategorical {...props} />
|
|
34
38
|
</Match>
|
|
35
39
|
</Switch>
|
|
36
40
|
);
|
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
import { Component, For } from "solid-js";
|
|
2
|
-
import { FieldMetadata } from "@molgenis/vip-report-vcf/src/MetadataParser";
|
|
3
2
|
import { Checkbox, CheckboxEvent } from "../Checkbox";
|
|
4
|
-
import {
|
|
3
|
+
import { FilterProps } from "./Filter";
|
|
5
4
|
import { selector } from "../../utils/query";
|
|
6
|
-
import { QueryClause } from "@molgenis/vip-report-api/src/Api";
|
|
7
5
|
|
|
8
6
|
export type CheckboxGroup = {
|
|
9
7
|
[key: string]: boolean;
|
|
10
8
|
};
|
|
11
9
|
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
export type CategoryLabels = {
|
|
11
|
+
[key: string]: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const FilterCategorical: Component<
|
|
15
|
+
FilterProps & {
|
|
16
|
+
labels?: CategoryLabels;
|
|
17
|
+
}
|
|
18
|
+
> = (props) => {
|
|
18
19
|
const group: CheckboxGroup = {};
|
|
20
|
+
if (props.query !== undefined) {
|
|
21
|
+
(props.query?.args as string[]).forEach((key) => {
|
|
22
|
+
group[key] = true;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
const nullValue = "__null";
|
|
20
27
|
|
|
21
28
|
// enable null category for any_has_any case if someone asks for it (requires query to be composed)
|
|
@@ -46,7 +53,7 @@ export const FilterCategorical: Component<{
|
|
|
46
53
|
<div class="control">
|
|
47
54
|
<Checkbox
|
|
48
55
|
value={category}
|
|
49
|
-
label={category}
|
|
56
|
+
label={props.labels ? props.labels[category] : category}
|
|
50
57
|
checked={props.query && (props.query.args as string[]).includes(category)}
|
|
51
58
|
onChange={onChange}
|
|
52
59
|
/>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Component } from "solid-js";
|
|
2
|
+
import { FilterProps } from "./Filter";
|
|
3
|
+
import { FilterCategorical } from "./FilterCategorical";
|
|
4
|
+
|
|
5
|
+
export const FilterClinVar: Component<FilterProps> = (props) => {
|
|
6
|
+
return (
|
|
7
|
+
<FilterCategorical
|
|
8
|
+
field={props.field}
|
|
9
|
+
labels={{
|
|
10
|
+
Benign: "B",
|
|
11
|
+
Likely_benign: "LB",
|
|
12
|
+
Uncertain_significance: "VUS",
|
|
13
|
+
Likely_pathogenic: "LP",
|
|
14
|
+
Pathogenic: "P",
|
|
15
|
+
Conflicting_interpretations_of_pathogenicity: "Conflict",
|
|
16
|
+
}}
|
|
17
|
+
onChange={props.onChange}
|
|
18
|
+
onClear={props.onClear}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
import { Component } from "solid-js";
|
|
2
|
-
import {
|
|
3
|
-
import { FilterChangeEvent, FilterClearEvent } from "./Filter";
|
|
2
|
+
import { FilterProps } from "./Filter";
|
|
4
3
|
import { Checkbox, CheckboxEvent } from "../Checkbox";
|
|
5
4
|
import { selector } from "../../utils/query";
|
|
6
|
-
import { QueryClause } from "@molgenis/vip-report-api/src/Api";
|
|
7
5
|
|
|
8
|
-
export const FilterIntegerDp: Component<{
|
|
9
|
-
field: FieldMetadata;
|
|
10
|
-
query?: QueryClause;
|
|
11
|
-
onChange: (event: FilterChangeEvent) => void;
|
|
12
|
-
onClear: (event: FilterClearEvent) => void;
|
|
13
|
-
}> = (props) => {
|
|
6
|
+
export const FilterIntegerDp: Component<FilterProps> = (props) => {
|
|
14
7
|
const onFilterChange = (event: CheckboxEvent) => {
|
|
15
8
|
if (event.checked) props.onChange({ query: { selector: selector(props.field), operator: ">=", args: 20 } });
|
|
16
9
|
else props.onClear({ selector: selector(props.field) });
|
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
import { Component } from "solid-js";
|
|
2
|
-
import {
|
|
3
|
-
import { FilterChangeEvent, FilterClearEvent } from "./Filter";
|
|
2
|
+
import { FilterProps } from "./Filter";
|
|
4
3
|
import { Checkbox, CheckboxEvent } from "../Checkbox";
|
|
5
4
|
import { selector } from "../../utils/query";
|
|
6
|
-
import { QueryClause } from "@molgenis/vip-report-api/src/Api";
|
|
7
5
|
|
|
8
|
-
export const FilterIntegerVid: Component<{
|
|
9
|
-
field: FieldMetadata;
|
|
10
|
-
query?: QueryClause;
|
|
11
|
-
onChange: (event: FilterChangeEvent) => void;
|
|
12
|
-
onClear: (event: FilterClearEvent) => void;
|
|
13
|
-
}> = (props) => {
|
|
6
|
+
export const FilterIntegerVid: Component<FilterProps> = (props) => {
|
|
14
7
|
const onFilterChange = (event: CheckboxEvent) => {
|
|
15
8
|
if (event.checked) props.onChange({ query: { selector: selector(props.field), operator: "==", args: 1 } });
|
|
16
9
|
else props.onClear({ selector: selector(props.field) });
|
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
import { Component } from "solid-js";
|
|
2
|
-
import {
|
|
3
|
-
import { FilterChangeEvent, FilterClearEvent } from "./Filter";
|
|
2
|
+
import { FilterProps } from "./Filter";
|
|
4
3
|
import { Checkbox, CheckboxEvent } from "../Checkbox";
|
|
5
4
|
import { selector } from "../../utils/query";
|
|
6
|
-
import { QueryClause } from "@molgenis/vip-report-api/src/Api";
|
|
7
5
|
|
|
8
|
-
export const FilterIntegerVim: Component<{
|
|
9
|
-
field: FieldMetadata;
|
|
10
|
-
query?: QueryClause;
|
|
11
|
-
onChange: (event: FilterChangeEvent) => void;
|
|
12
|
-
onClear: (event: FilterClearEvent) => void;
|
|
13
|
-
}> = (props) => {
|
|
6
|
+
export const FilterIntegerVim: Component<FilterProps> = (props) => {
|
|
14
7
|
const onFilterChange = (event: CheckboxEvent) => {
|
|
15
8
|
if (event.checked) props.onChange({ query: { selector: selector(props.field), operator: "==", args: 1 } });
|
|
16
9
|
else props.onClear({ selector: selector(props.field) });
|