@genspectrum/dashboard-components 1.9.0 → 1.9.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/dist/util.d.ts CHANGED
@@ -941,7 +941,7 @@ declare global {
941
941
 
942
942
  declare global {
943
943
  interface HTMLElementTagNameMap {
944
- 'gs-genome-data-viewer': GenomeDataViewerComponent;
944
+ 'gs-wastewater-mutations-over-time': WastewaterMutationsOverTimeComponent;
945
945
  }
946
946
  }
947
947
 
@@ -949,7 +949,7 @@ declare global {
949
949
  declare global {
950
950
  namespace JSX {
951
951
  interface IntrinsicElements {
952
- 'gs-genome-data-viewer': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
952
+ 'gs-wastewater-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
953
953
  }
954
954
  }
955
955
  }
@@ -957,7 +957,7 @@ declare global {
957
957
 
958
958
  declare global {
959
959
  interface HTMLElementTagNameMap {
960
- 'gs-mutation-comparison-component': MutationComparisonComponent;
960
+ 'gs-genome-data-viewer': GenomeDataViewerComponent;
961
961
  }
962
962
  }
963
963
 
@@ -965,7 +965,7 @@ declare global {
965
965
  declare global {
966
966
  namespace JSX {
967
967
  interface IntrinsicElements {
968
- 'gs-mutation-comparison-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
968
+ 'gs-genome-data-viewer': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
969
969
  }
970
970
  }
971
971
  }
@@ -973,7 +973,7 @@ declare global {
973
973
 
974
974
  declare global {
975
975
  interface HTMLElementTagNameMap {
976
- 'gs-mutations': MutationsComponent;
976
+ 'gs-mutation-comparison-component': MutationComparisonComponent;
977
977
  }
978
978
  }
979
979
 
@@ -981,7 +981,7 @@ declare global {
981
981
  declare global {
982
982
  namespace JSX {
983
983
  interface IntrinsicElements {
984
- 'gs-mutations': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
984
+ 'gs-mutation-comparison-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
985
985
  }
986
986
  }
987
987
  }
@@ -989,7 +989,7 @@ declare global {
989
989
 
990
990
  declare global {
991
991
  interface HTMLElementTagNameMap {
992
- 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
992
+ 'gs-mutations': MutationsComponent;
993
993
  }
994
994
  }
995
995
 
@@ -997,7 +997,7 @@ declare global {
997
997
  declare global {
998
998
  namespace JSX {
999
999
  interface IntrinsicElements {
1000
- 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1000
+ 'gs-mutations': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1001
1001
  }
1002
1002
  }
1003
1003
  }
@@ -1005,7 +1005,7 @@ declare global {
1005
1005
 
1006
1006
  declare global {
1007
1007
  interface HTMLElementTagNameMap {
1008
- 'gs-aggregate': AggregateComponent;
1008
+ 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
1009
1009
  }
1010
1010
  }
1011
1011
 
@@ -1013,7 +1013,7 @@ declare global {
1013
1013
  declare global {
1014
1014
  namespace JSX {
1015
1015
  interface IntrinsicElements {
1016
- 'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1016
+ 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1017
1017
  }
1018
1018
  }
1019
1019
  }
@@ -1021,7 +1021,7 @@ declare global {
1021
1021
 
1022
1022
  declare global {
1023
1023
  interface HTMLElementTagNameMap {
1024
- 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1024
+ 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
1025
1025
  }
1026
1026
  }
1027
1027
 
@@ -1029,7 +1029,7 @@ declare global {
1029
1029
  declare global {
1030
1030
  namespace JSX {
1031
1031
  interface IntrinsicElements {
1032
- 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1032
+ 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1033
1033
  }
1034
1034
  }
1035
1035
  }
@@ -1037,7 +1037,7 @@ declare global {
1037
1037
 
1038
1038
  declare global {
1039
1039
  interface HTMLElementTagNameMap {
1040
- 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
1040
+ 'gs-aggregate': AggregateComponent;
1041
1041
  }
1042
1042
  }
1043
1043
 
@@ -1045,7 +1045,7 @@ declare global {
1045
1045
  declare global {
1046
1046
  namespace JSX {
1047
1047
  interface IntrinsicElements {
1048
- 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1048
+ 'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1049
1049
  }
1050
1050
  }
1051
1051
  }
@@ -1053,7 +1053,7 @@ declare global {
1053
1053
 
1054
1054
  declare global {
1055
1055
  interface HTMLElementTagNameMap {
1056
- 'gs-sequences-by-location': SequencesByLocationComponent;
1056
+ 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1057
1057
  }
1058
1058
  }
1059
1059
 
@@ -1061,7 +1061,7 @@ declare global {
1061
1061
  declare global {
1062
1062
  namespace JSX {
1063
1063
  interface IntrinsicElements {
1064
- 'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1064
+ 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1065
1065
  }
1066
1066
  }
1067
1067
  }
@@ -1085,7 +1085,7 @@ declare global {
1085
1085
 
1086
1086
  declare global {
1087
1087
  interface HTMLElementTagNameMap {
1088
- 'gs-statistics': StatisticsComponent;
1088
+ 'gs-sequences-by-location': SequencesByLocationComponent;
1089
1089
  }
1090
1090
  }
1091
1091
 
@@ -1093,7 +1093,7 @@ declare global {
1093
1093
  declare global {
1094
1094
  namespace JSX {
1095
1095
  interface IntrinsicElements {
1096
- 'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1096
+ 'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1097
1097
  }
1098
1098
  }
1099
1099
  }
@@ -1101,11 +1101,7 @@ declare global {
1101
1101
 
1102
1102
  declare global {
1103
1103
  interface HTMLElementTagNameMap {
1104
- 'gs-date-range-filter': DateRangeFilterComponent;
1105
- }
1106
- interface HTMLElementEventMap {
1107
- [gsEventNames.dateRangeFilterChanged]: CustomEvent<Record<string, string>>;
1108
- [gsEventNames.dateRangeOptionChanged]: DateRangeOptionChangedEvent;
1104
+ 'gs-statistics': StatisticsComponent;
1109
1105
  }
1110
1106
  }
1111
1107
 
@@ -1113,7 +1109,7 @@ declare global {
1113
1109
  declare global {
1114
1110
  namespace JSX {
1115
1111
  interface IntrinsicElements {
1116
- 'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1112
+ 'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1117
1113
  }
1118
1114
  }
1119
1115
  }
@@ -1121,7 +1117,10 @@ declare global {
1121
1117
 
1122
1118
  declare global {
1123
1119
  interface HTMLElementTagNameMap {
1124
- 'gs-wastewater-mutations-over-time': WastewaterMutationsOverTimeComponent;
1120
+ 'gs-location-filter': LocationFilterComponent;
1121
+ }
1122
+ interface HTMLElementEventMap {
1123
+ [gsEventNames.locationChanged]: LocationChangedEvent;
1125
1124
  }
1126
1125
  }
1127
1126
 
@@ -1129,7 +1128,7 @@ declare global {
1129
1128
  declare global {
1130
1129
  namespace JSX {
1131
1130
  interface IntrinsicElements {
1132
- 'gs-wastewater-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1131
+ 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1133
1132
  }
1134
1133
  }
1135
1134
  }
@@ -1137,10 +1136,11 @@ declare global {
1137
1136
 
1138
1137
  declare global {
1139
1138
  interface HTMLElementTagNameMap {
1140
- 'gs-location-filter': LocationFilterComponent;
1139
+ 'gs-date-range-filter': DateRangeFilterComponent;
1141
1140
  }
1142
1141
  interface HTMLElementEventMap {
1143
- [gsEventNames.locationChanged]: LocationChangedEvent;
1142
+ [gsEventNames.dateRangeFilterChanged]: CustomEvent<Record<string, string>>;
1143
+ [gsEventNames.dateRangeOptionChanged]: DateRangeOptionChangedEvent;
1144
1144
  }
1145
1145
  }
1146
1146
 
@@ -1148,7 +1148,7 @@ declare global {
1148
1148
  declare global {
1149
1149
  namespace JSX {
1150
1150
  interface IntrinsicElements {
1151
- 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1151
+ 'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1152
1152
  }
1153
1153
  }
1154
1154
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genspectrum/dashboard-components",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
4
4
  "description": "GenSpectrum web components for building dashboards",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -321,4 +321,76 @@ describe('fetchLineageAutocompleteList', () => {
321
321
  },
322
322
  ]);
323
323
  });
324
+
325
+ test('should include prefix aliases that are missing from lineage tree', async () => {
326
+ lapisRequestMocks.aggregated(
327
+ { fields: [lineageField], ...lapisFilter },
328
+ {
329
+ data: [
330
+ {
331
+ [lineageField]: 'BA.3.2.1',
332
+ count: 1,
333
+ },
334
+ {
335
+ [lineageField]: 'BA.3.2.2',
336
+ count: 2,
337
+ },
338
+ ],
339
+ },
340
+ );
341
+
342
+ lapisRequestMocks.lineageDefinition(
343
+ {
344
+ 'B.1.1.529.3.2': {
345
+ aliases: ['BA.3.2'],
346
+ },
347
+ 'BA.3.2.1': {
348
+ parents: ['B.1.1.529.3.2'],
349
+ aliases: ['B.1.1.529.3.2.1'],
350
+ },
351
+ 'BA.3.2.2': {
352
+ parents: ['B.1.1.529.3.2'],
353
+ aliases: ['B.1.1.529.3.2.2'],
354
+ },
355
+ },
356
+ lineageField,
357
+ );
358
+
359
+ const result = await fetchLineageAutocompleteList({
360
+ lapisUrl: DUMMY_LAPIS_URL,
361
+ lapisField: lineageField,
362
+ lapisFilter,
363
+ });
364
+
365
+ expect(result).to.deep.equal([
366
+ {
367
+ lineage: 'B.1.1.529.3.2',
368
+ count: 0,
369
+ },
370
+ {
371
+ lineage: 'B.1.1.529.3.2*',
372
+ count: 3,
373
+ },
374
+ {
375
+ lineage: 'BA.3.2*',
376
+ count: 3, // Same as B.1.1.529.3.2* (includes .3.2 and .3.2.1)
377
+ },
378
+ {
379
+ lineage: 'BA.3.2.1',
380
+ count: 1,
381
+ },
382
+ {
383
+ lineage: 'BA.3.2.1*',
384
+ count: 1,
385
+ },
386
+ {
387
+ lineage: 'BA.3.2.2',
388
+ count: 2,
389
+ },
390
+ {
391
+ lineage: 'BA.3.2.2*',
392
+ count: 2,
393
+ },
394
+ ]);
395
+ });
324
396
  });
@@ -2,6 +2,10 @@ import { fetchLineageDefinition } from '../../lapisApi/lapisApi';
2
2
  import { FetchAggregatedOperator } from '../../operator/FetchAggregatedOperator';
3
3
  import type { LapisFilter } from '../../types';
4
4
 
5
+ /**
6
+ * Generates the autocomplete list for lineage search. It includes lineages with wild cards
7
+ * (i.e. "BA.3.2.1" and "BA.3.2.1*") as well as all prefixes of lineages with an asterisk ("BA.3.2*").
8
+ */
5
9
  export async function fetchLineageAutocompleteList({
6
10
  lapisUrl,
7
11
  lapisField,
@@ -13,31 +17,43 @@ export async function fetchLineageAutocompleteList({
13
17
  lapisFilter?: LapisFilter;
14
18
  signal?: AbortSignal;
15
19
  }): Promise<LineageItem[]> {
16
- const [countsByLineage, lineageTree] = await Promise.all([
20
+ const [countsByLineage, { lineageTree, aliasMapping }] = await Promise.all([
17
21
  getCountsByLineage({
18
22
  lapisUrl,
19
23
  lapisField,
20
24
  lapisFilter,
21
25
  signal,
22
26
  }),
23
- getLineageTree({ lapisUrl, lapisField, signal }),
27
+ getLineageTreeAndAliases({ lapisUrl, lapisField, signal }),
24
28
  ]);
25
29
 
26
- return Array.from(lineageTree.keys())
27
- .sort((a, b) => a.localeCompare(b))
28
- .map((lineage) => {
29
- return [
30
- {
31
- lineage,
32
- count: countsByLineage.get(lineage) ?? 0,
33
- },
34
- {
35
- lineage: `${lineage}*`,
36
- count: getCountsIncludingSublineages(lineage, lineageTree, countsByLineage),
37
- },
38
- ];
39
- })
40
- .flat();
30
+ const prefixToLineage = findMissingPrefixMappings(lineageTree, aliasMapping);
31
+
32
+ // Combine actual lineages with their wildcard versions
33
+ const actualLineageItems = Array.from(lineageTree.keys()).flatMap((lineage) => [
34
+ {
35
+ lineage,
36
+ count: countsByLineage.get(lineage) ?? 0,
37
+ },
38
+ {
39
+ lineage: `${lineage}*`,
40
+ count: getCountsIncludingSublineages(lineage, lineageTree, countsByLineage),
41
+ },
42
+ ]);
43
+
44
+ // Add prefix alias items with wildcard and their counts
45
+ const prefixAliasItems = Array.from(prefixToLineage.entries()).map(([prefix, actualLineage]) => ({
46
+ lineage: `${prefix}*`,
47
+ count: getCountsIncludingSublineages(actualLineage, lineageTree, countsByLineage),
48
+ }));
49
+
50
+ // Combine and sort all items (asterisk before period for same prefix)
51
+ return [...actualLineageItems, ...prefixAliasItems].sort((a, b) => {
52
+ // Replace * with a character that sorts before . in ASCII
53
+ const aKey = a.lineage.replace(/\*/g, ' ');
54
+ const bKey = b.lineage.replace(/\*/g, ' ');
55
+ return aKey.localeCompare(bKey);
56
+ });
41
57
  }
42
58
 
43
59
  export type LineageItem = { lineage: string; count: number };
@@ -61,7 +77,7 @@ async function getCountsByLineage({
61
77
  return new Map<string, number>(countsByLineageArray.map((value) => [value[lapisField], value.count]));
62
78
  }
63
79
 
64
- async function getLineageTree({
80
+ async function getLineageTreeAndAliases({
65
81
  lapisUrl,
66
82
  lapisField,
67
83
  signal,
@@ -73,12 +89,17 @@ async function getLineageTree({
73
89
  const lineageDefinitions = await fetchLineageDefinition({ lapisUrl, lapisField, signal });
74
90
 
75
91
  const lineageTree = new Map<string, { children: string[] }>();
92
+ const aliasMapping = new Map<string, string[]>();
76
93
 
77
94
  Object.entries(lineageDefinitions).forEach(([lineage, definition]) => {
78
95
  if (!lineageTree.has(lineage)) {
79
96
  lineageTree.set(lineage, { children: [] });
80
97
  }
81
98
 
99
+ if (definition.aliases && definition.aliases.length > 0) {
100
+ aliasMapping.set(lineage, definition.aliases);
101
+ }
102
+
82
103
  definition.parents?.forEach((parent) => {
83
104
  const parentChildren = lineageTree.get(parent)?.children;
84
105
 
@@ -88,7 +109,7 @@ async function getLineageTree({
88
109
  });
89
110
  });
90
111
 
91
- return lineageTree;
112
+ return { lineageTree, aliasMapping };
92
113
  }
93
114
 
94
115
  function getCountsIncludingSublineages(
@@ -115,3 +136,44 @@ function getAllDescendants(lineage: string, lineageTree: Map<string, { children:
115
136
 
116
137
  return new Set([...children, ...childrenOfChildren.flatMap((child) => Array.from(child))]);
117
138
  }
139
+
140
+ /**
141
+ * This function finds prefixes (i.e. "BA.3.2" for "BA.3.2.1") that are not in the lineageTree,
142
+ * but do appear as an alias. It returns a reverse mapping for those prefixes, back to a lineage
143
+ * that can be found in the lineageTree (i.e. "BA.3.2" -> "B.1.1.529.3.2").
144
+ */
145
+ function findMissingPrefixMappings(
146
+ lineageTree: Map<string, { children: string[] }>,
147
+ aliasMapping: Map<string, string[]>,
148
+ ): Map<string, string> {
149
+ const lineages = Array.from(lineageTree.keys());
150
+ const lineagesSet = new Set(lineages);
151
+
152
+ // Generate all prefixes for each lineage (e.g., "A.B.1" -> ["A", "A.B", "A.B.1"])
153
+ const allPrefixes = lineages.flatMap((lineage) => {
154
+ const parts = lineage.split('.');
155
+ return parts.map((_, i) => parts.slice(0, i + 1).join('.'));
156
+ });
157
+
158
+ // Find prefixes that are NOT in the actual lineages list
159
+ const missingPrefixes = new Set(allPrefixes.filter((prefix) => !lineagesSet.has(prefix)));
160
+
161
+ // Create reverse alias mapping: alias -> original lineage
162
+ const reverseAliasMapping = new Map<string, string>();
163
+ aliasMapping.forEach((aliases, lineage) => {
164
+ aliases.forEach((alias) => {
165
+ reverseAliasMapping.set(alias, lineage);
166
+ });
167
+ });
168
+
169
+ // Map missing prefixes to their actual lineage names via reverse alias lookup
170
+ const prefixToLineage = new Map<string, string>();
171
+ missingPrefixes.forEach((prefix) => {
172
+ const actualLineage = reverseAliasMapping.get(prefix);
173
+ if (actualLineage) {
174
+ prefixToLineage.set(prefix, actualLineage);
175
+ }
176
+ });
177
+
178
+ return prefixToLineage;
179
+ }