@molgenis/vip-report-template 3.0.0-beta → 3.0.0-beta2
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 +1 -1
- package/src/__tests__/query.test.ts +60 -3
- package/src/components/DatasetDropdown.tsx +4 -0
- package/src/components/filter/Filter.tsx +7 -4
- package/src/components/filter/Filters.tsx +6 -3
- package/src/store/index.tsx +27 -21
- package/src/views/SampleVariants.tsx +13 -9
- package/src/views/Variants.tsx +5 -5
package/package.json
CHANGED
|
@@ -2,28 +2,54 @@ import { describe, expect, test } from "vitest";
|
|
|
2
2
|
import { FieldMetadata } from "@molgenis/vip-report-vcf/src/MetadataParser";
|
|
3
3
|
import { Metadata } from "@molgenis/vip-report-vcf/src/Vcf";
|
|
4
4
|
import { FilterQueries } from "../store";
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
createQuery,
|
|
7
|
+
infoFieldKey,
|
|
8
|
+
infoSelector,
|
|
9
|
+
infoSortPath,
|
|
10
|
+
sampleFieldKey,
|
|
11
|
+
sampleSelector,
|
|
12
|
+
selectorKey,
|
|
13
|
+
} from "../utils/query";
|
|
14
|
+
import { Person, QueryClause } from "@molgenis/vip-report-api/src/Api";
|
|
7
15
|
|
|
8
16
|
describe("query utilities", () => {
|
|
17
|
+
let fieldMetaCsq: FieldMetadata = {
|
|
18
|
+
id: "CSQ",
|
|
19
|
+
number: { type: "NUMBER" },
|
|
20
|
+
type: "STRING",
|
|
21
|
+
};
|
|
22
|
+
|
|
9
23
|
const fieldMeta1: FieldMetadata = {
|
|
10
24
|
id: "field1",
|
|
11
25
|
number: { type: "NUMBER" },
|
|
12
26
|
type: "CATEGORICAL",
|
|
27
|
+
parent: fieldMetaCsq,
|
|
13
28
|
};
|
|
29
|
+
|
|
14
30
|
const fieldMeta2: FieldMetadata = {
|
|
15
31
|
id: "field2",
|
|
16
32
|
number: { type: "NUMBER" },
|
|
17
33
|
type: "CATEGORICAL",
|
|
34
|
+
parent: fieldMetaCsq,
|
|
18
35
|
};
|
|
19
36
|
|
|
20
|
-
|
|
37
|
+
fieldMetaCsq = {
|
|
21
38
|
id: "CSQ",
|
|
22
39
|
number: { type: "NUMBER" },
|
|
23
40
|
type: "STRING",
|
|
24
41
|
nested: { items: [fieldMeta1, fieldMeta2], separator: "|" },
|
|
25
42
|
};
|
|
26
43
|
|
|
44
|
+
const person: Person = {
|
|
45
|
+
familyId: "FAM001",
|
|
46
|
+
individualId: "Patient",
|
|
47
|
+
sex: "FEMALE",
|
|
48
|
+
affectedStatus: "AFFECTED",
|
|
49
|
+
maternalId: "Mother",
|
|
50
|
+
paternalId: "Father",
|
|
51
|
+
};
|
|
52
|
+
|
|
27
53
|
const meta: Metadata = { info: { CSQ: fieldMetaCsq } } as unknown as Metadata;
|
|
28
54
|
|
|
29
55
|
test("create query", () => {
|
|
@@ -96,4 +122,35 @@ describe("query utilities", () => {
|
|
|
96
122
|
};
|
|
97
123
|
expect(createQuery(undefined, filterQueries, meta)).toBe(queryClause);
|
|
98
124
|
});
|
|
125
|
+
|
|
126
|
+
test("infoSelector", () => {
|
|
127
|
+
expect(infoSelector(fieldMeta1)).toStrictEqual(["n", "CSQ", "field1"]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("sampleSelector", () => {
|
|
131
|
+
expect(sampleSelector({ id: 1, data: { person: person, index: 0, proband: false } }, fieldMeta1)).toStrictEqual([
|
|
132
|
+
"s",
|
|
133
|
+
0,
|
|
134
|
+
"CSQ",
|
|
135
|
+
"field1",
|
|
136
|
+
]);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("selectorKey", () => {
|
|
140
|
+
expect(selectorKey([1, "test", 2, "3"])).toStrictEqual("1/test/2/3");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("infoFieldKey", () => {
|
|
144
|
+
expect(infoFieldKey(fieldMeta1)).toStrictEqual("n/CSQ/field1");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("sampleFieldKey", () => {
|
|
148
|
+
expect(sampleFieldKey({ id: 1, data: { person: person, index: 0, proband: false } }, fieldMeta1)).toStrictEqual(
|
|
149
|
+
"s/0/CSQ/field1"
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("infoSortPath", () => {
|
|
154
|
+
expect(infoSortPath(fieldMetaCsq)).toStrictEqual(["n", "CSQ"]);
|
|
155
|
+
});
|
|
99
156
|
});
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { Component, createSignal, For } from "solid-js";
|
|
2
2
|
import api from "../Api";
|
|
3
3
|
import { useNavigate } from "solid-app-router";
|
|
4
|
+
import { useStore } from "../store";
|
|
4
5
|
|
|
5
6
|
export const DatasetDropdown: Component = () => {
|
|
7
|
+
const [, actions] = useStore();
|
|
8
|
+
|
|
6
9
|
const navigate = useNavigate();
|
|
7
10
|
const [selectedDataset, setSelectedDataset] = createSignal("GRCh37 Family");
|
|
8
11
|
|
|
9
12
|
function switchIt(datasetName: string) {
|
|
10
13
|
setSelectedDataset(datasetName);
|
|
11
14
|
api.selectDataset(datasetName);
|
|
15
|
+
actions.reset();
|
|
12
16
|
(async () => {
|
|
13
17
|
navigate(`/`);
|
|
14
18
|
const samples = await api.getSamples({ query: { selector: ["proband"], operator: "==", args: true } });
|
|
@@ -15,19 +15,22 @@ export const Filter: Component<{
|
|
|
15
15
|
onChange: (event: FilterChangeEvent) => void;
|
|
16
16
|
onClear: (event: FilterClearEvent) => void;
|
|
17
17
|
}> = (props) => {
|
|
18
|
+
const onChange = (event: FilterChangeEvent) => props.onChange(event);
|
|
19
|
+
const onClear = (event: FilterClearEvent) => props.onClear(event);
|
|
20
|
+
|
|
18
21
|
return (
|
|
19
22
|
<Switch>
|
|
20
23
|
<Match when={props.field.id === "DP"}>
|
|
21
|
-
<FilterIntegerDp field={props.field} onChange={
|
|
24
|
+
<FilterIntegerDp field={props.field} onChange={onChange} onClear={onClear} query={props.query} />
|
|
22
25
|
</Match>
|
|
23
26
|
<Match when={props.field.id === "VID"}>
|
|
24
|
-
<FilterIntegerVid field={props.field} onChange={
|
|
27
|
+
<FilterIntegerVid field={props.field} onChange={onChange} onClear={onClear} query={props.query} />
|
|
25
28
|
</Match>
|
|
26
29
|
<Match when={props.field.id === "VIM"}>
|
|
27
|
-
<FilterIntegerVim field={props.field} onChange={
|
|
30
|
+
<FilterIntegerVim field={props.field} onChange={onChange} onClear={onClear} query={props.query} />
|
|
28
31
|
</Match>
|
|
29
32
|
<Match when={props.field.type === "CATEGORICAL"}>
|
|
30
|
-
<FilterCategorical field={props.field} onChange={
|
|
33
|
+
<FilterCategorical field={props.field} onChange={onChange} onClear={onClear} query={props.query} />
|
|
31
34
|
</Match>
|
|
32
35
|
</Switch>
|
|
33
36
|
);
|
|
@@ -13,14 +13,17 @@ export const Filters: Component<{
|
|
|
13
13
|
onChange: (event: FilterChangeEvent) => void;
|
|
14
14
|
onClear: (event: FilterClearEvent) => void;
|
|
15
15
|
}> = (props) => {
|
|
16
|
+
const onChange = (event: FilterChangeEvent) => props.onChange(event);
|
|
17
|
+
const onClear = (event: FilterClearEvent) => props.onClear(event);
|
|
18
|
+
|
|
16
19
|
return (
|
|
17
20
|
<>
|
|
18
|
-
<InfoFilters fields={props.fields} queries={props.queries} onChange={
|
|
21
|
+
<InfoFilters fields={props.fields} queries={props.queries} onChange={onChange} onClear={onClear} />
|
|
19
22
|
<SamplesFilters
|
|
20
23
|
samplesFields={props.samplesFields}
|
|
21
24
|
queries={props.queries}
|
|
22
|
-
onChange={
|
|
23
|
-
onClear={
|
|
25
|
+
onChange={onChange}
|
|
26
|
+
onClear={onClear}
|
|
24
27
|
/>
|
|
25
28
|
</>
|
|
26
29
|
);
|
package/src/store/index.tsx
CHANGED
|
@@ -14,15 +14,15 @@ type AppStateVariants = {
|
|
|
14
14
|
sort?: SortOrder | null; // null: do not sort. undefined: sort undefined
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
// TODO clear store on dataset change
|
|
18
17
|
export type AppState = {
|
|
19
|
-
variants
|
|
20
|
-
samples
|
|
21
|
-
[key:
|
|
18
|
+
variants?: AppStateVariants;
|
|
19
|
+
samples?: {
|
|
20
|
+
[key: number]: { variants: AppStateVariants };
|
|
22
21
|
};
|
|
23
22
|
};
|
|
24
23
|
|
|
25
24
|
export type AppActions = {
|
|
25
|
+
reset(): void;
|
|
26
26
|
setVariantsPage(page: number): void;
|
|
27
27
|
setVariantsPageSize(pageSize: number): void;
|
|
28
28
|
setVariantsSearchQuery(searchQuery: string): void;
|
|
@@ -49,58 +49,64 @@ export const Provider: ParentComponent = (props) => {
|
|
|
49
49
|
const [state, setState] = createStore(defaultState);
|
|
50
50
|
|
|
51
51
|
function getVariants(sample: Item<Sample>) {
|
|
52
|
-
return state.samples[sample.id]?.variants || {};
|
|
52
|
+
return state.samples ? state.samples[sample.id]?.variants || {} : {};
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
const actions: AppActions = {
|
|
56
|
+
reset() {
|
|
57
|
+
setState({ variants: undefined, samples: undefined });
|
|
58
|
+
},
|
|
56
59
|
setVariantsPage(page: number) {
|
|
57
|
-
setState({ variants: { ...state.variants, page } });
|
|
60
|
+
setState({ variants: { ...(state.variants || {}), page } });
|
|
58
61
|
},
|
|
59
62
|
setVariantsPageSize(pageSize: number) {
|
|
60
|
-
setState({ variants: { ...state.variants, pageSize } });
|
|
63
|
+
setState({ variants: { ...(state.variants || {}), pageSize } });
|
|
61
64
|
},
|
|
62
65
|
setVariantsSearchQuery(searchQuery: string) {
|
|
63
|
-
setState({ variants: { ...state.variants, searchQuery } });
|
|
66
|
+
setState({ variants: { ...(state.variants || {}), searchQuery } });
|
|
64
67
|
},
|
|
65
68
|
clearVariantsSearchQuery() {
|
|
66
|
-
setState({ variants: { ...state.variants, searchQuery: undefined } });
|
|
69
|
+
setState({ variants: { ...(state.variants || {}), searchQuery: undefined } });
|
|
67
70
|
},
|
|
68
71
|
setVariantsFilterQuery(query: QueryClause) {
|
|
69
72
|
setState({
|
|
70
73
|
variants: {
|
|
71
|
-
...state.variants,
|
|
72
|
-
filterQueries: { ...(state.variants
|
|
74
|
+
...(state.variants || {}),
|
|
75
|
+
filterQueries: { ...(state.variants?.filterQueries || {}), [selectorKey(query.selector)]: query },
|
|
73
76
|
},
|
|
74
77
|
});
|
|
75
78
|
},
|
|
76
79
|
clearVariantsFilterQuery(selector: Selector) {
|
|
77
80
|
setState({
|
|
78
81
|
variants: {
|
|
79
|
-
...state.variants,
|
|
80
|
-
filterQueries: { ...(state.variants
|
|
82
|
+
...(state.variants || {}),
|
|
83
|
+
filterQueries: { ...(state.variants?.filterQueries || {}), [selectorKey(selector)]: undefined },
|
|
81
84
|
},
|
|
82
85
|
});
|
|
83
86
|
},
|
|
84
87
|
setVariantsSort(sort: SortOrder | null) {
|
|
85
88
|
setState({
|
|
86
89
|
variants: {
|
|
87
|
-
...state.variants,
|
|
90
|
+
...(state.variants || {}),
|
|
88
91
|
sort,
|
|
89
92
|
},
|
|
90
93
|
});
|
|
91
94
|
},
|
|
92
95
|
setSampleVariantsPage(sample: Item<Sample>, page: number) {
|
|
93
|
-
setState({ samples: { ...state.samples, [sample.id]: { variants: { ...getVariants(sample), page } } } });
|
|
96
|
+
setState({ samples: { ...(state.samples || {}), [sample.id]: { variants: { ...getVariants(sample), page } } } });
|
|
94
97
|
},
|
|
95
98
|
setSampleVariantsPageSize(sample: Item<Sample>, pageSize: number) {
|
|
96
99
|
setState({
|
|
97
|
-
samples: {
|
|
100
|
+
samples: {
|
|
101
|
+
...(state.samples || {}),
|
|
102
|
+
[sample.id]: { variants: { ...getVariants(sample), pageSize, page: undefined } },
|
|
103
|
+
},
|
|
98
104
|
});
|
|
99
105
|
},
|
|
100
106
|
setSampleVariantsSearchQuery(sample: Item<Sample>, searchQuery: string) {
|
|
101
107
|
setState({
|
|
102
108
|
samples: {
|
|
103
|
-
...state.samples,
|
|
109
|
+
...(state.samples || {}),
|
|
104
110
|
[sample.id]: { variants: { ...getVariants(sample), searchQuery, page: undefined } },
|
|
105
111
|
},
|
|
106
112
|
});
|
|
@@ -108,7 +114,7 @@ export const Provider: ParentComponent = (props) => {
|
|
|
108
114
|
clearSampleVariantsSearchQuery(sample: Item<Sample>) {
|
|
109
115
|
setState({
|
|
110
116
|
samples: {
|
|
111
|
-
...state.samples,
|
|
117
|
+
...(state.samples || {}),
|
|
112
118
|
[sample.id]: { variants: { ...getVariants(sample), searchQuery: undefined, page: undefined } },
|
|
113
119
|
},
|
|
114
120
|
});
|
|
@@ -117,7 +123,7 @@ export const Provider: ParentComponent = (props) => {
|
|
|
117
123
|
const variants = getVariants(sample);
|
|
118
124
|
setState({
|
|
119
125
|
samples: {
|
|
120
|
-
...state.samples,
|
|
126
|
+
...(state.samples || {}),
|
|
121
127
|
[sample.id]: {
|
|
122
128
|
variants: {
|
|
123
129
|
...variants,
|
|
@@ -132,7 +138,7 @@ export const Provider: ParentComponent = (props) => {
|
|
|
132
138
|
const variants = getVariants(sample);
|
|
133
139
|
setState({
|
|
134
140
|
samples: {
|
|
135
|
-
...state.samples,
|
|
141
|
+
...(state.samples || {}),
|
|
136
142
|
[sample.id]: {
|
|
137
143
|
variants: {
|
|
138
144
|
...getVariants(sample),
|
|
@@ -145,7 +151,7 @@ export const Provider: ParentComponent = (props) => {
|
|
|
145
151
|
},
|
|
146
152
|
setSampleVariantsSort(sample: Item<Sample>, sort: SortOrder | null) {
|
|
147
153
|
setState({
|
|
148
|
-
samples: { ...state.samples, [sample.id]: { variants: { ...getVariants(sample), sort } } },
|
|
154
|
+
samples: { ...(state.samples || {}), [sample.id]: { variants: { ...getVariants(sample), sort } } },
|
|
149
155
|
});
|
|
150
156
|
},
|
|
151
157
|
};
|
|
@@ -56,15 +56,19 @@ export const SampleVariants: Component<{
|
|
|
56
56
|
}> = (props) => {
|
|
57
57
|
const [state, actions] = useStore();
|
|
58
58
|
|
|
59
|
+
function getStateVariants() {
|
|
60
|
+
return state.samples ? state.samples[props.sample.id]?.variants : undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
59
63
|
// state initialization - start
|
|
60
|
-
if (
|
|
64
|
+
if (getStateVariants()?.page === undefined) {
|
|
61
65
|
actions.setSampleVariantsPage(props.sample, 0);
|
|
62
66
|
}
|
|
63
|
-
if (
|
|
67
|
+
if (getStateVariants()?.pageSize === undefined) {
|
|
64
68
|
actions.setSampleVariantsPageSize(props.sample, 20);
|
|
65
69
|
}
|
|
66
70
|
|
|
67
|
-
if (
|
|
71
|
+
if (getStateVariants()?.filterQueries === undefined) {
|
|
68
72
|
const hpoField = props.recordsMeta.info?.CSQ?.nested?.items?.find((field) => field.id === "HPO");
|
|
69
73
|
if (hpoField) {
|
|
70
74
|
actions.setSampleVariantsFilterQuery(props.sample, {
|
|
@@ -92,7 +96,7 @@ export const SampleVariants: Component<{
|
|
|
92
96
|
}
|
|
93
97
|
}
|
|
94
98
|
|
|
95
|
-
if (
|
|
99
|
+
if (getStateVariants()?.sort === undefined) {
|
|
96
100
|
const capiceScField = props.recordsMeta.info?.CSQ?.nested?.items?.find((field) => field.id === "CAPICE_SC");
|
|
97
101
|
if (capiceScField) {
|
|
98
102
|
actions.setSampleVariantsSort(props.sample, {
|
|
@@ -137,11 +141,11 @@ export const SampleVariants: Component<{
|
|
|
137
141
|
: [];
|
|
138
142
|
});
|
|
139
143
|
|
|
140
|
-
const page = () =>
|
|
141
|
-
const pageSize = () =>
|
|
142
|
-
const searchQuery = () =>
|
|
143
|
-
const filterQueries = () =>
|
|
144
|
-
const sort = () =>
|
|
144
|
+
const page = () => getStateVariants()?.page;
|
|
145
|
+
const pageSize = () => getStateVariants()?.pageSize;
|
|
146
|
+
const searchQuery = () => getStateVariants()?.searchQuery;
|
|
147
|
+
const filterQueries = () => getStateVariants()?.filterQueries;
|
|
148
|
+
const sort = () => getStateVariants()?.sort;
|
|
145
149
|
|
|
146
150
|
const onPageChange = (page: number) => actions.setSampleVariantsPage(props.sample, page);
|
|
147
151
|
const onSearchChange = (search: string) => actions.setSampleVariantsSearchQuery(props.sample, search);
|
package/src/views/Variants.tsx
CHANGED
|
@@ -35,11 +35,11 @@ export const Variants: Component<{
|
|
|
35
35
|
}> = (props) => {
|
|
36
36
|
const [state, actions] = useStore();
|
|
37
37
|
|
|
38
|
-
const page = () => state.variants
|
|
39
|
-
const pageSize = () => state.variants
|
|
40
|
-
const searchQuery = () => state.variants
|
|
41
|
-
const filterQueries = () => state.variants
|
|
42
|
-
const sort = () => state.variants
|
|
38
|
+
const page = () => state.variants?.page;
|
|
39
|
+
const pageSize = () => state.variants?.pageSize;
|
|
40
|
+
const searchQuery = () => state.variants?.searchQuery;
|
|
41
|
+
const filterQueries = () => state.variants?.filterQueries;
|
|
42
|
+
const sort = () => state.variants?.sort;
|
|
43
43
|
|
|
44
44
|
const onPageChange = (page: number) => actions.setVariantsPage(page);
|
|
45
45
|
const onSearchChange = (search: string) => actions.setVariantsSearchQuery(search);
|