@genspectrum/dashboard-components 1.3.0 → 1.4.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/README.md +9 -0
- package/dist/assets/{mutationOverTimeWorker-C7saVShx.js.map → mutationOverTimeWorker-CQxrFo53.js.map} +1 -1
- package/dist/components.d.ts +25 -25
- package/dist/components.js +5 -11
- package/dist/components.js.map +1 -1
- package/dist/util.d.ts +25 -25
- package/package.json +1 -1
- package/src/preact/mutationsOverTime/__mockData__/withDisplayMutations.ts +352 -0
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +0 -24
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +1 -9
- package/src/preact/mutationsOverTime/mutationOverTimeWorker.mock.ts +2 -0
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +3 -4
- package/src/query/queryMutationsOverTime.spec.ts +98 -0
- package/src/query/queryMutationsOverTime.ts +72 -6
- package/src/query/queryMutationsOverTimeNewEndpoint.spec.ts +122 -0
- package/standalone-bundle/assets/{mutationOverTimeWorker-DRRi3aMG.js.map → mutationOverTimeWorker-CDACUs6w.js.map} +1 -1
- package/standalone-bundle/dashboard-components.js +22 -24
- package/standalone-bundle/dashboard-components.js.map +1 -1
|
@@ -876,6 +876,104 @@ describe('queryMutationsOverTime', () => {
|
|
|
876
876
|
expect(mutationOverTimeData.getAsArray()).to.deep.equal([[null]]);
|
|
877
877
|
});
|
|
878
878
|
|
|
879
|
+
it('should respect the includeMutations parameter', async () => {
|
|
880
|
+
const lapisFilter = { field1: 'value1', field2: 'value2' };
|
|
881
|
+
const dateField = 'dateField';
|
|
882
|
+
|
|
883
|
+
lapisRequestMocks.multipleAggregated([
|
|
884
|
+
{
|
|
885
|
+
body: { ...lapisFilter, fields: [dateField] },
|
|
886
|
+
response: {
|
|
887
|
+
data: [
|
|
888
|
+
{ count: 1, [dateField]: '2023-01-05' },
|
|
889
|
+
{ count: 2, [dateField]: '2023-02-15' },
|
|
890
|
+
],
|
|
891
|
+
},
|
|
892
|
+
},
|
|
893
|
+
{
|
|
894
|
+
body: {
|
|
895
|
+
...lapisFilter,
|
|
896
|
+
dateFieldFrom: '2023-01-01',
|
|
897
|
+
dateFieldTo: '2023-01-31',
|
|
898
|
+
fields: [],
|
|
899
|
+
},
|
|
900
|
+
response: { data: [{ count: 11 }] },
|
|
901
|
+
},
|
|
902
|
+
{
|
|
903
|
+
body: {
|
|
904
|
+
...lapisFilter,
|
|
905
|
+
dateFieldFrom: '2023-02-01',
|
|
906
|
+
dateFieldTo: '2023-02-28',
|
|
907
|
+
fields: [],
|
|
908
|
+
},
|
|
909
|
+
response: { data: [{ count: 12 }] },
|
|
910
|
+
},
|
|
911
|
+
]);
|
|
912
|
+
|
|
913
|
+
lapisRequestMocks.multipleMutations(
|
|
914
|
+
[
|
|
915
|
+
{
|
|
916
|
+
body: {
|
|
917
|
+
...lapisFilter,
|
|
918
|
+
dateFieldFrom: '2023-01-01',
|
|
919
|
+
dateFieldTo: '2023-01-31',
|
|
920
|
+
minProportion: 0.001,
|
|
921
|
+
},
|
|
922
|
+
response: { data: [getSomeTestMutation(0.1, 1), getSomeOtherTestMutation(0.4, 4)] },
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
body: {
|
|
926
|
+
...lapisFilter,
|
|
927
|
+
dateFieldFrom: '2023-02-01',
|
|
928
|
+
dateFieldTo: '2023-02-28',
|
|
929
|
+
minProportion: 0.001,
|
|
930
|
+
},
|
|
931
|
+
response: { data: [getSomeTestMutation(0.2, 2)] },
|
|
932
|
+
},
|
|
933
|
+
{
|
|
934
|
+
body: {
|
|
935
|
+
...lapisFilter,
|
|
936
|
+
dateFieldFrom: '2023-01-01',
|
|
937
|
+
dateFieldTo: '2023-02-28',
|
|
938
|
+
minProportion: 0.001,
|
|
939
|
+
},
|
|
940
|
+
response: {
|
|
941
|
+
data: [getSomeTestMutation(0.21, 6), getSomeOtherTestMutation(0.22, 4)],
|
|
942
|
+
},
|
|
943
|
+
},
|
|
944
|
+
],
|
|
945
|
+
'nucleotide',
|
|
946
|
+
);
|
|
947
|
+
|
|
948
|
+
const { mutationOverTimeData } = await queryMutationsOverTimeData({
|
|
949
|
+
lapisFilter,
|
|
950
|
+
sequenceType: 'nucleotide',
|
|
951
|
+
lapis: DUMMY_LAPIS_URL,
|
|
952
|
+
lapisDateField: dateField,
|
|
953
|
+
granularity: 'month',
|
|
954
|
+
displayMutations: ['otherSequenceName:G234C', 'A44T'],
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
expect(mutationOverTimeData.getAsArray()).to.deep.equal([
|
|
958
|
+
[
|
|
959
|
+
{ type: 'belowThreshold', totalCount: 11 },
|
|
960
|
+
{ type: 'belowThreshold', totalCount: 12 },
|
|
961
|
+
],
|
|
962
|
+
[
|
|
963
|
+
{ type: 'value', proportion: 0.4, count: 4, totalCount: 11 },
|
|
964
|
+
{ type: 'belowThreshold', totalCount: 12 },
|
|
965
|
+
],
|
|
966
|
+
]);
|
|
967
|
+
|
|
968
|
+
const sequences = mutationOverTimeData.getFirstAxisKeys();
|
|
969
|
+
expect(sequences[0].code).toBe('A44T');
|
|
970
|
+
expect(sequences[1].code).toBe('otherSequenceName:G234C');
|
|
971
|
+
|
|
972
|
+
const dates = mutationOverTimeData.getSecondAxisKeys();
|
|
973
|
+
expect(dates[0].dateString).toBe('2023-01');
|
|
974
|
+
expect(dates[1].dateString).toBe('2023-02');
|
|
975
|
+
});
|
|
976
|
+
|
|
879
977
|
function getSomeTestMutation(proportion: number, count: number) {
|
|
880
978
|
return {
|
|
881
979
|
mutation: 'sequenceName:A123T',
|
|
@@ -18,7 +18,13 @@ import {
|
|
|
18
18
|
type TemporalGranularity,
|
|
19
19
|
} from '../types';
|
|
20
20
|
import { type Map2DContents } from '../utils/map2d';
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
type Deletion,
|
|
23
|
+
type Substitution,
|
|
24
|
+
toSubstitutionOrDeletion,
|
|
25
|
+
DeletionClass,
|
|
26
|
+
SubstitutionClass,
|
|
27
|
+
} from '../utils/mutations';
|
|
22
28
|
import {
|
|
23
29
|
compareTemporal,
|
|
24
30
|
dateRangeCompare,
|
|
@@ -56,12 +62,45 @@ export type MutationOverTimeMutationValue =
|
|
|
56
62
|
const MAX_NUMBER_OF_GRID_COLUMNS = 200;
|
|
57
63
|
export const MUTATIONS_OVER_TIME_MIN_PROPORTION = 0.001;
|
|
58
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Create SubstitutionOrDeletionEntry for given code with count and proportion 0.
|
|
67
|
+
* @param code a mutation code like G44T or A23-
|
|
68
|
+
*/
|
|
69
|
+
function codeToEmptyEntry(code: string): SubstitutionOrDeletionEntry | null {
|
|
70
|
+
const maybeDeletion = DeletionClass.parse(code);
|
|
71
|
+
if (maybeDeletion) {
|
|
72
|
+
return {
|
|
73
|
+
type: 'deletion',
|
|
74
|
+
mutation: maybeDeletion,
|
|
75
|
+
count: 0,
|
|
76
|
+
proportion: 0,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const maybeSubstitution = SubstitutionClass.parse(code);
|
|
80
|
+
if (maybeSubstitution) {
|
|
81
|
+
return {
|
|
82
|
+
type: 'substitution',
|
|
83
|
+
mutation: maybeSubstitution,
|
|
84
|
+
count: 0,
|
|
85
|
+
proportion: 0,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Return counts and proportions for all mutations that match the lapisFilter.
|
|
93
|
+
* If `includeMutations` are given, the result will also be filtered for those.
|
|
94
|
+
* Any mutation that isn't in the result, but is in the `includeMutations` will
|
|
95
|
+
* be in the result with count and proportion as 0.
|
|
96
|
+
*/
|
|
59
97
|
async function queryOverallMutationData({
|
|
60
98
|
lapisFilter,
|
|
61
99
|
sequenceType,
|
|
62
100
|
lapis,
|
|
63
101
|
granularity,
|
|
64
102
|
lapisDateField,
|
|
103
|
+
includeMutations,
|
|
65
104
|
signal,
|
|
66
105
|
}: {
|
|
67
106
|
lapisFilter: LapisFilter;
|
|
@@ -69,14 +108,23 @@ async function queryOverallMutationData({
|
|
|
69
108
|
lapis: string;
|
|
70
109
|
granularity: TemporalGranularity;
|
|
71
110
|
lapisDateField: string;
|
|
111
|
+
includeMutations?: string[];
|
|
72
112
|
signal?: AbortSignal;
|
|
73
113
|
}) {
|
|
74
114
|
const requestedDateRanges = await getDatesInDataset(lapisFilter, lapis, granularity, lapisDateField, signal);
|
|
75
115
|
|
|
76
116
|
if (requestedDateRanges.length === 0) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
117
|
+
if (includeMutations) {
|
|
118
|
+
return {
|
|
119
|
+
content: includeMutations
|
|
120
|
+
.map(codeToEmptyEntry)
|
|
121
|
+
.filter((e): e is SubstitutionOrDeletionEntry => e !== null),
|
|
122
|
+
};
|
|
123
|
+
} else {
|
|
124
|
+
return {
|
|
125
|
+
content: [],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
80
128
|
}
|
|
81
129
|
|
|
82
130
|
const filter = {
|
|
@@ -85,11 +133,27 @@ async function queryOverallMutationData({
|
|
|
85
133
|
[`${lapisDateField}To`]: requestedDateRanges[requestedDateRanges.length - 1].lastDay.toString(),
|
|
86
134
|
};
|
|
87
135
|
|
|
88
|
-
|
|
136
|
+
let dataPromise = fetchAndPrepareSubstitutionsOrDeletions(filter, sequenceType).evaluate(lapis, signal);
|
|
137
|
+
|
|
138
|
+
if (includeMutations) {
|
|
139
|
+
dataPromise = dataPromise.then((data) => {
|
|
140
|
+
return {
|
|
141
|
+
content: includeMutations
|
|
142
|
+
.map((code) => {
|
|
143
|
+
const found = data.content.find((m) => m.mutation.code === code);
|
|
144
|
+
return found ?? codeToEmptyEntry(code);
|
|
145
|
+
})
|
|
146
|
+
.filter((e): e is SubstitutionOrDeletionEntry => e !== null),
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return dataPromise;
|
|
89
152
|
}
|
|
90
153
|
|
|
91
154
|
export type MutationOverTimeQuery = {
|
|
92
155
|
lapisFilter: LapisFilter;
|
|
156
|
+
displayMutations?: string[];
|
|
93
157
|
sequenceType: SequenceType;
|
|
94
158
|
lapis: string;
|
|
95
159
|
lapisDateField: string;
|
|
@@ -99,7 +163,8 @@ export type MutationOverTimeQuery = {
|
|
|
99
163
|
};
|
|
100
164
|
|
|
101
165
|
export async function queryMutationsOverTimeData(query: MutationOverTimeQuery) {
|
|
102
|
-
const { lapisFilter, sequenceType, lapis, lapisDateField, granularity, useNewEndpoint, signal } =
|
|
166
|
+
const { lapisFilter, displayMutations, sequenceType, lapis, lapisDateField, granularity, useNewEndpoint, signal } =
|
|
167
|
+
query;
|
|
103
168
|
|
|
104
169
|
const requestedDateRanges = await getDatesInDataset(lapisFilter, lapis, granularity, lapisDateField, signal);
|
|
105
170
|
|
|
@@ -117,6 +182,7 @@ export async function queryMutationsOverTimeData(query: MutationOverTimeQuery) {
|
|
|
117
182
|
sequenceType,
|
|
118
183
|
lapis,
|
|
119
184
|
lapisDateField,
|
|
185
|
+
includeMutations: displayMutations,
|
|
120
186
|
granularity,
|
|
121
187
|
}).then((r) => r.content);
|
|
122
188
|
|
|
@@ -909,6 +909,128 @@ describe('queryMutationsOverTimeNewEndpoint', () => {
|
|
|
909
909
|
expect(dates.length).toBe(0);
|
|
910
910
|
});
|
|
911
911
|
|
|
912
|
+
it('should respect the includeMutations parameter', async () => {
|
|
913
|
+
const lapisFilter = { field1: 'value1', field2: 'value2' };
|
|
914
|
+
const dateField = 'dateField';
|
|
915
|
+
|
|
916
|
+
lapisRequestMocks.multipleAggregated([
|
|
917
|
+
{
|
|
918
|
+
body: { ...lapisFilter, fields: [dateField] },
|
|
919
|
+
response: {
|
|
920
|
+
data: [
|
|
921
|
+
{ count: 1, [dateField]: '2023-01-05' },
|
|
922
|
+
{ count: 2, [dateField]: '2023-02-15' },
|
|
923
|
+
],
|
|
924
|
+
},
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
body: {
|
|
928
|
+
...lapisFilter,
|
|
929
|
+
dateFieldFrom: '2023-01-01',
|
|
930
|
+
dateFieldTo: '2023-01-31',
|
|
931
|
+
fields: [],
|
|
932
|
+
},
|
|
933
|
+
response: { data: [{ count: 11 }] },
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
body: {
|
|
937
|
+
...lapisFilter,
|
|
938
|
+
dateFieldFrom: '2023-02-01',
|
|
939
|
+
dateFieldTo: '2023-02-28',
|
|
940
|
+
fields: [],
|
|
941
|
+
},
|
|
942
|
+
response: { data: [{ count: 12 }] },
|
|
943
|
+
},
|
|
944
|
+
]);
|
|
945
|
+
|
|
946
|
+
lapisRequestMocks.multipleMutations(
|
|
947
|
+
[
|
|
948
|
+
{
|
|
949
|
+
body: {
|
|
950
|
+
...lapisFilter,
|
|
951
|
+
dateFieldFrom: '2023-01-01',
|
|
952
|
+
dateFieldTo: '2023-02-28',
|
|
953
|
+
minProportion: 0.001,
|
|
954
|
+
},
|
|
955
|
+
response: {
|
|
956
|
+
data: [getSomeTestMutation(0.21, 6), getSomeOtherTestMutation(0.22, 4)],
|
|
957
|
+
},
|
|
958
|
+
},
|
|
959
|
+
],
|
|
960
|
+
'nucleotide',
|
|
961
|
+
);
|
|
962
|
+
|
|
963
|
+
const dateRanges = [
|
|
964
|
+
{
|
|
965
|
+
dateFrom: '2023-01-01',
|
|
966
|
+
dateTo: '2023-01-31',
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
dateFrom: '2023-02-01',
|
|
970
|
+
dateTo: '2023-02-28',
|
|
971
|
+
},
|
|
972
|
+
];
|
|
973
|
+
|
|
974
|
+
lapisRequestMocks.mutationsOverTime(
|
|
975
|
+
[
|
|
976
|
+
{
|
|
977
|
+
body: {
|
|
978
|
+
filters: lapisFilter,
|
|
979
|
+
dateRanges,
|
|
980
|
+
includeMutations: ['A122T', 'otherSequenceName:G234C'],
|
|
981
|
+
dateField,
|
|
982
|
+
},
|
|
983
|
+
response: {
|
|
984
|
+
data: {
|
|
985
|
+
data: [
|
|
986
|
+
[
|
|
987
|
+
{ count: 0, coverage: 0 },
|
|
988
|
+
{ count: 0, coverage: 0 },
|
|
989
|
+
],
|
|
990
|
+
[
|
|
991
|
+
{ count: 2, coverage: 10 },
|
|
992
|
+
{ count: 3, coverage: 10 },
|
|
993
|
+
],
|
|
994
|
+
],
|
|
995
|
+
dateRanges,
|
|
996
|
+
mutations: ['A122T', 'otherSequenceName:G234C'],
|
|
997
|
+
},
|
|
998
|
+
},
|
|
999
|
+
},
|
|
1000
|
+
],
|
|
1001
|
+
'nucleotide',
|
|
1002
|
+
);
|
|
1003
|
+
|
|
1004
|
+
const { mutationOverTimeData } = await queryMutationsOverTimeData({
|
|
1005
|
+
lapisFilter,
|
|
1006
|
+
sequenceType: 'nucleotide',
|
|
1007
|
+
lapis: DUMMY_LAPIS_URL,
|
|
1008
|
+
lapisDateField: dateField,
|
|
1009
|
+
granularity: 'month',
|
|
1010
|
+
useNewEndpoint: true,
|
|
1011
|
+
displayMutations: ['otherSequenceName:G234C', 'A122T'],
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
expect(mutationOverTimeData.getAsArray()).to.deep.equal([
|
|
1015
|
+
[
|
|
1016
|
+
{ type: 'value', proportion: NaN, count: 0, totalCount: 11 },
|
|
1017
|
+
{ type: 'value', proportion: NaN, count: 0, totalCount: 12 },
|
|
1018
|
+
],
|
|
1019
|
+
[
|
|
1020
|
+
{ type: 'value', proportion: 0.2, count: 2, totalCount: 11 },
|
|
1021
|
+
{ type: 'value', proportion: 0.3, count: 3, totalCount: 12 },
|
|
1022
|
+
],
|
|
1023
|
+
]);
|
|
1024
|
+
|
|
1025
|
+
const sequences = mutationOverTimeData.getFirstAxisKeys();
|
|
1026
|
+
expect(sequences[0].code).toBe('A122T');
|
|
1027
|
+
expect(sequences[1].code).toBe('otherSequenceName:G234C');
|
|
1028
|
+
|
|
1029
|
+
const dates = mutationOverTimeData.getSecondAxisKeys();
|
|
1030
|
+
expect(dates[0].dateString).toBe('2023-01');
|
|
1031
|
+
expect(dates[1].dateString).toBe('2023-02');
|
|
1032
|
+
});
|
|
1033
|
+
|
|
912
1034
|
function getSomeTestMutation(proportion: number, count: number) {
|
|
913
1035
|
return {
|
|
914
1036
|
mutation: 'sequenceName:A123T',
|