@genspectrum/dashboard-components 1.3.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.
@@ -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 { type Deletion, type Substitution, toSubstitutionOrDeletion } from '../utils/mutations';
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
- return {
78
- content: [],
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
- 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;
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 } = query;
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',