@genspectrum/dashboard-components 1.2.0 → 1.3.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.
Files changed (33) hide show
  1. package/README.md +9 -0
  2. package/custom-elements.json +20 -1
  3. package/dist/assets/mutationOverTimeWorker-CQxrFo53.js.map +1 -0
  4. package/dist/components.d.ts +34 -28
  5. package/dist/components.js +48 -16
  6. package/dist/components.js.map +1 -1
  7. package/dist/util.d.ts +28 -28
  8. package/package.json +1 -1
  9. package/src/lapisApi/lapisApi.ts +29 -0
  10. package/src/lapisApi/lapisTypes.ts +35 -1
  11. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutationsByDay.ts +1 -0
  12. package/src/preact/mutationsOverTime/__mockData__/byWeek.ts +1 -0
  13. package/src/preact/mutationsOverTime/__mockData__/defaultMockData.ts +1 -0
  14. package/src/preact/mutationsOverTime/__mockData__/noDataWhenNoMutationsAreInFilter.ts +1 -0
  15. package/src/preact/mutationsOverTime/__mockData__/noDataWhenThereAreNoDatesInFilter.ts +1 -0
  16. package/src/preact/mutationsOverTime/__mockData__/showsMessageWhenTooManyMutations.ts +1 -0
  17. package/src/preact/mutationsOverTime/__mockData__/withDisplayMutations.ts +352 -0
  18. package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +0 -24
  19. package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +0 -8
  20. package/src/preact/mutationsOverTime/mutationOverTimeWorker.mock.ts +2 -0
  21. package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +2 -0
  22. package/src/preact/mutationsOverTime/mutations-over-time.tsx +11 -6
  23. package/src/query/queryMutationsOverTime.spec.ts +98 -0
  24. package/src/query/queryMutationsOverTime.ts +172 -28
  25. package/src/query/queryMutationsOverTimeNewEndpoint.spec.ts +1057 -0
  26. package/src/web-components/visualization/gs-mutations-over-time.spec-d.ts +3 -0
  27. package/src/web-components/visualization/gs-mutations-over-time.stories.ts +3 -0
  28. package/src/web-components/visualization/gs-mutations-over-time.tsx +9 -0
  29. package/standalone-bundle/assets/mutationOverTimeWorker-CDACUs6w.js.map +1 -0
  30. package/standalone-bundle/dashboard-components.js +3628 -3594
  31. package/standalone-bundle/dashboard-components.js.map +1 -1
  32. package/dist/assets/mutationOverTimeWorker-DpW4YOGl.js.map +0 -1
  33. package/standalone-bundle/assets/mutationOverTimeWorker-CZVvQBze.js.map +0 -1
@@ -1,4 +1,5 @@
1
1
  import { mapDateToGranularityRange } from './queryAggregatedDataOverTime';
2
+ import { fetchMutationsOverTime } from '../lapisApi/lapisApi';
2
3
  import { FetchAggregatedOperator } from '../operator/FetchAggregatedOperator';
3
4
  import { FetchSubstitutionsOrDeletionsOperator } from '../operator/FetchSubstitutionsOrDeletionsOperator';
4
5
  import { GroupByAndSumOperator } from '../operator/GroupByAndSumOperator';
@@ -16,7 +17,14 @@ import {
16
17
  type SubstitutionOrDeletionEntry,
17
18
  type TemporalGranularity,
18
19
  } from '../types';
19
- import { type Deletion, type Substitution, toSubstitutionOrDeletion } from '../utils/mutations';
20
+ import { type Map2DContents } from '../utils/map2d';
21
+ import {
22
+ type Deletion,
23
+ type Substitution,
24
+ toSubstitutionOrDeletion,
25
+ DeletionClass,
26
+ SubstitutionClass,
27
+ } from '../utils/mutations';
20
28
  import {
21
29
  compareTemporal,
22
30
  dateRangeCompare,
@@ -54,12 +62,45 @@ export type MutationOverTimeMutationValue =
54
62
  const MAX_NUMBER_OF_GRID_COLUMNS = 200;
55
63
  export const MUTATIONS_OVER_TIME_MIN_PROPORTION = 0.001;
56
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
+ */
57
97
  async function queryOverallMutationData({
58
98
  lapisFilter,
59
99
  sequenceType,
60
100
  lapis,
61
101
  granularity,
62
102
  lapisDateField,
103
+ includeMutations,
63
104
  signal,
64
105
  }: {
65
106
  lapisFilter: LapisFilter;
@@ -67,14 +108,23 @@ async function queryOverallMutationData({
67
108
  lapis: string;
68
109
  granularity: TemporalGranularity;
69
110
  lapisDateField: string;
111
+ includeMutations?: string[];
70
112
  signal?: AbortSignal;
71
113
  }) {
72
114
  const requestedDateRanges = await getDatesInDataset(lapisFilter, lapis, granularity, lapisDateField, signal);
73
115
 
74
116
  if (requestedDateRanges.length === 0) {
75
- return {
76
- content: [],
77
- };
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
+ }
78
128
  }
79
129
 
80
130
  const filter = {
@@ -83,26 +133,39 @@ async function queryOverallMutationData({
83
133
  [`${lapisDateField}To`]: requestedDateRanges[requestedDateRanges.length - 1].lastDay.toString(),
84
134
  };
85
135
 
86
- return fetchAndPrepareSubstitutionsOrDeletions(filter, sequenceType).evaluate(lapis, signal);
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;
87
152
  }
88
153
 
89
154
  export type MutationOverTimeQuery = {
90
155
  lapisFilter: LapisFilter;
156
+ displayMutations?: string[];
91
157
  sequenceType: SequenceType;
92
158
  lapis: string;
93
159
  lapisDateField: string;
94
160
  granularity: TemporalGranularity;
161
+ useNewEndpoint?: boolean;
95
162
  signal?: AbortSignal;
96
163
  };
97
164
 
98
- export async function queryMutationsOverTimeData({
99
- lapisFilter,
100
- sequenceType,
101
- lapis,
102
- lapisDateField,
103
- granularity,
104
- signal,
105
- }: MutationOverTimeQuery) {
165
+ export async function queryMutationsOverTimeData(query: MutationOverTimeQuery) {
166
+ const { lapisFilter, displayMutations, sequenceType, lapis, lapisDateField, granularity, useNewEndpoint, signal } =
167
+ query;
168
+
106
169
  const requestedDateRanges = await getDatesInDataset(lapisFilter, lapis, granularity, lapisDateField, signal);
107
170
 
108
171
  if (requestedDateRanges.length > MAX_NUMBER_OF_GRID_COLUMNS) {
@@ -114,7 +177,26 @@ export async function queryMutationsOverTimeData({
114
177
  );
115
178
  }
116
179
 
117
- const subQueries = requestedDateRanges.map(async (date) => {
180
+ const overallMutationData = queryOverallMutationData({
181
+ lapisFilter,
182
+ sequenceType,
183
+ lapis,
184
+ lapisDateField,
185
+ includeMutations: displayMutations,
186
+ granularity,
187
+ }).then((r) => r.content);
188
+
189
+ return useNewEndpoint === true
190
+ ? queryMutationsOverTimeDataDirectEndpoint(requestedDateRanges, overallMutationData, query)
191
+ : queryMutationsOverTimeDataMultiQuery(requestedDateRanges, overallMutationData, query);
192
+ }
193
+
194
+ async function queryMutationsOverTimeDataMultiQuery(
195
+ allDates: TemporalClass[],
196
+ overallMutationDataPromise: Promise<SubstitutionOrDeletionEntry[]>,
197
+ { lapisFilter, sequenceType, lapis, lapisDateField, signal }: MutationOverTimeQuery,
198
+ ) {
199
+ const subQueries = allDates.map(async (date) => {
118
200
  const dateFrom = date.firstDay.toString();
119
201
  const dateTo = date.lastDay.toString();
120
202
 
@@ -124,8 +206,10 @@ export async function queryMutationsOverTimeData({
124
206
  [`${lapisDateField}To`]: dateTo,
125
207
  };
126
208
 
127
- const data = await fetchAndPrepareSubstitutionsOrDeletions(filter, sequenceType).evaluate(lapis, signal);
128
- const totalCountQuery = await getTotalNumberOfSequencesInDateRange(filter).evaluate(lapis, signal);
209
+ const [data, totalCountQuery] = await Promise.all([
210
+ fetchAndPrepareSubstitutionsOrDeletions(filter, sequenceType).evaluate(lapis, signal),
211
+ getTotalNumberOfSequencesInDateRange(filter).evaluate(lapis, signal),
212
+ ]);
129
213
 
130
214
  return {
131
215
  date,
@@ -135,20 +219,80 @@ export async function queryMutationsOverTimeData({
135
219
  });
136
220
 
137
221
  const data = await Promise.all(subQueries);
222
+ const overallMutationData = await overallMutationDataPromise;
138
223
 
139
- const overallMutationsData = (
140
- await queryOverallMutationData({
141
- lapisFilter,
142
- sequenceType,
143
- lapis,
144
- lapisDateField,
145
- granularity,
146
- })
147
- ).content;
224
+ return {
225
+ mutationOverTimeData: groupByMutation(data, overallMutationData),
226
+ overallMutationData,
227
+ };
228
+ }
229
+
230
+ async function queryMutationsOverTimeDataDirectEndpoint(
231
+ allDates: TemporalClass[],
232
+ overallMutationDataPromise: Promise<SubstitutionOrDeletionEntry[]>,
233
+ { lapisFilter, sequenceType, lapis, lapisDateField, signal }: MutationOverTimeQuery,
234
+ ): Promise<{
235
+ mutationOverTimeData: BaseMutationOverTimeDataMap;
236
+ overallMutationData: SubstitutionOrDeletionEntry[];
237
+ }> {
238
+ const overallMutationData = await overallMutationDataPromise;
239
+ overallMutationData.sort((a, b) => sortSubstitutionsAndDeletions(a.mutation, b.mutation));
240
+ const totalCounts = await Promise.all(
241
+ allDates.map(async (date) => {
242
+ const filter = {
243
+ ...lapisFilter,
244
+ [`${lapisDateField}From`]: date.firstDay.toString(),
245
+ [`${lapisDateField}To`]: date.lastDay.toString(),
246
+ };
247
+
248
+ const totalCountQuery = await getTotalNumberOfSequencesInDateRange(filter).evaluate(lapis, signal);
249
+
250
+ return totalCountQuery.content[0].count;
251
+ }),
252
+ );
253
+
254
+ const includeMutations = overallMutationData.map((value) => value.mutation.code);
255
+ const apiResult = await fetchMutationsOverTime(
256
+ lapis,
257
+ {
258
+ filters: lapisFilter,
259
+ dateRanges: allDates.map((date) => ({
260
+ dateFrom: date.firstDay.toString(),
261
+ dateTo: date.lastDay.toString(),
262
+ })),
263
+ includeMutations,
264
+ dateField: lapisDateField,
265
+ },
266
+ sequenceType,
267
+ signal,
268
+ );
269
+
270
+ const mutationOverTimeData: Map2DContents<Substitution | Deletion, Temporal, MutationOverTimeMutationValue> = {
271
+ keysFirstAxis: new Map(overallMutationData.map((value) => [value.mutation.code, value.mutation])),
272
+ keysSecondAxis: new Map(allDates.map((date) => [date.dateString, date])),
273
+ data: new Map(
274
+ overallMutationData.map((mutation, i) => [
275
+ mutation.mutation.code,
276
+ new Map(
277
+ allDates.map((date, j): [string, MutationOverTimeMutationValue] => [
278
+ date.dateString,
279
+ {
280
+ type: 'value',
281
+ // 'coverage' in the API resp. is the number of seqs. that have a non-ambiguous symbol at position
282
+ // 'count' in the API resp. is the number of seqs with the mutation
283
+ proportion: apiResult.data.data[i][j].count / apiResult.data.data[i][j].coverage,
284
+ count: apiResult.data.data[i][j].count,
285
+ totalCount: totalCounts[j],
286
+ },
287
+ ]),
288
+ ),
289
+ ]),
290
+ ),
291
+ };
148
292
 
149
293
  return {
150
- mutationOverTimeData: groupByMutation(data, overallMutationsData),
151
- overallMutationData: overallMutationsData,
294
+ mutationOverTimeData: new BaseMutationOverTimeDataMap(mutationOverTimeData),
295
+ overallMutationData,
152
296
  };
153
297
  }
154
298
 
@@ -236,7 +380,7 @@ export function serializeTemporal(date: Temporal) {
236
380
  export function groupByMutation(
237
381
  data: MutationOverTimeData[],
238
382
  overallMutationData: (SubstitutionEntry | DeletionEntry)[],
239
- ) {
383
+ ): BaseMutationOverTimeDataMap {
240
384
  const dataArray = new BaseMutationOverTimeDataMap();
241
385
 
242
386
  const allDates = data.map((mutationData) => mutationData.date);