@genspectrum/dashboard-components 1.14.2 → 1.16.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.
Files changed (36) hide show
  1. package/custom-elements.json +7 -7
  2. package/dist/components.d.ts +68 -67
  3. package/dist/components.js +67 -13
  4. package/dist/components.js.map +1 -1
  5. package/dist/util.d.ts +83 -65
  6. package/package.json +2 -9
  7. package/src/lapisApi/lapisTypes.ts +1 -1
  8. package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +3 -1
  9. package/src/preact/queriesOverTime/queries-over-time-row-label-tooltip.stories.tsx +58 -0
  10. package/src/preact/queriesOverTime/queries-over-time-row-label-tooltip.tsx +24 -0
  11. package/src/preact/queriesOverTime/queries-over-time.stories.tsx +24 -0
  12. package/src/preact/queriesOverTime/queries-over-time.tsx +63 -14
  13. package/src/utilEntrypoint.ts +1 -1
  14. package/src/web-components/gs-app.ts +2 -3
  15. package/src/web-components/input/gs-date-range-filter.tsx +2 -3
  16. package/src/web-components/input/gs-lineage-filter.tsx +2 -3
  17. package/src/web-components/input/gs-location-filter.tsx +2 -3
  18. package/src/web-components/input/gs-mutation-filter.tsx +3 -4
  19. package/src/web-components/input/gs-number-range-filter.tsx +2 -3
  20. package/src/web-components/input/gs-text-filter.tsx +2 -3
  21. package/src/web-components/mutation-annotations-context.ts +3 -1
  22. package/src/web-components/visualization/gs-aggregate.tsx +2 -3
  23. package/src/web-components/visualization/gs-genome-data-viewer.tsx +2 -3
  24. package/src/web-components/visualization/gs-mutation-comparison.tsx +2 -3
  25. package/src/web-components/visualization/gs-mutations-over-time.tsx +2 -3
  26. package/src/web-components/visualization/gs-mutations.tsx +7 -8
  27. package/src/web-components/visualization/gs-number-sequences-over-time.tsx +2 -3
  28. package/src/web-components/visualization/gs-prevalence-over-time.tsx +2 -3
  29. package/src/web-components/visualization/gs-queries-over-time.stories.ts +4 -0
  30. package/src/web-components/visualization/gs-queries-over-time.tsx +5 -4
  31. package/src/web-components/visualization/gs-relative-growth-advantage.tsx +2 -3
  32. package/src/web-components/visualization/gs-sequences-by-location.tsx +2 -3
  33. package/src/web-components/visualization/gs-statistics.tsx +2 -3
  34. package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.tsx +2 -3
  35. package/standalone-bundle/dashboard-components.js +3573 -3527
  36. package/standalone-bundle/dashboard-components.js.map +1 -1
package/dist/util.d.ts CHANGED
@@ -71,6 +71,25 @@ export declare type ConfidenceIntervalMethod = default_2.infer<typeof confidence
71
71
 
72
72
  declare const confidenceIntervalMethodSchema: default_2.ZodUnion<[default_2.ZodLiteral<"wilson">, default_2.ZodLiteral<"none">]>;
73
73
 
74
+ export declare type CountCoverageQuery = default_2.infer<typeof countCoverageQuerySchema>;
75
+
76
+ declare const countCoverageQuerySchema: default_2.ZodObject<{
77
+ displayLabel: default_2.ZodString;
78
+ description: default_2.ZodOptional<default_2.ZodString>;
79
+ countQuery: default_2.ZodString;
80
+ coverageQuery: default_2.ZodString;
81
+ }, "strip", default_2.ZodTypeAny, {
82
+ displayLabel: string;
83
+ countQuery: string;
84
+ coverageQuery: string;
85
+ description?: string | undefined;
86
+ }, {
87
+ displayLabel: string;
88
+ countQuery: string;
89
+ coverageQuery: string;
90
+ description?: string | undefined;
91
+ }>;
92
+
74
93
  export declare type CustomColumn = default_2.infer<typeof customColumnSchema>;
75
94
 
76
95
  declare const customColumnSchema: default_2.ZodObject<{
@@ -753,19 +772,32 @@ declare const queriesOverTimeSchema: default_2.ZodObject<{
753
772
  nucleotideInsertions?: string[] | undefined;
754
773
  aminoAcidInsertions?: string[] | undefined;
755
774
  }>>;
756
- queries: default_2.ZodArray<default_2.ZodObject<{
775
+ queries: default_2.ZodEffects<default_2.ZodArray<default_2.ZodObject<{
757
776
  displayLabel: default_2.ZodString;
777
+ description: default_2.ZodOptional<default_2.ZodString>;
758
778
  countQuery: default_2.ZodString;
759
779
  coverageQuery: default_2.ZodString;
760
780
  }, "strip", default_2.ZodTypeAny, {
761
781
  displayLabel: string;
762
782
  countQuery: string;
763
783
  coverageQuery: string;
784
+ description?: string | undefined;
764
785
  }, {
765
786
  displayLabel: string;
766
787
  countQuery: string;
767
788
  coverageQuery: string;
768
- }>, "many">;
789
+ description?: string | undefined;
790
+ }>, "many">, {
791
+ displayLabel: string;
792
+ countQuery: string;
793
+ coverageQuery: string;
794
+ description?: string | undefined;
795
+ }[], {
796
+ displayLabel: string;
797
+ countQuery: string;
798
+ coverageQuery: string;
799
+ description?: string | undefined;
800
+ }[]>;
769
801
  views: default_2.ZodArray<default_2.ZodLiteral<"grid">, "many">;
770
802
  granularity: default_2.ZodUnion<[default_2.ZodLiteral<"day">, default_2.ZodLiteral<"week">, default_2.ZodLiteral<"month">, default_2.ZodLiteral<"year">]>;
771
803
  lapisDateField: default_2.ZodString;
@@ -804,6 +836,7 @@ declare const queriesOverTimeSchema: default_2.ZodObject<{
804
836
  displayLabel: string;
805
837
  countQuery: string;
806
838
  coverageQuery: string;
839
+ description?: string | undefined;
807
840
  }[];
808
841
  width: string;
809
842
  views: "grid"[];
@@ -831,6 +864,7 @@ declare const queriesOverTimeSchema: default_2.ZodObject<{
831
864
  displayLabel: string;
832
865
  countQuery: string;
833
866
  coverageQuery: string;
867
+ description?: string | undefined;
834
868
  }[];
835
869
  width: string;
836
870
  views: "grid"[];
@@ -853,22 +887,6 @@ export declare type QueriesOverTimeView = default_2.infer<typeof queriesOverTime
853
887
 
854
888
  declare const queriesOverTimeViewSchema: default_2.ZodLiteral<"grid">;
855
889
 
856
- export declare type QueryDefinition = default_2.infer<typeof queryDefinition>;
857
-
858
- declare const queryDefinition: default_2.ZodObject<{
859
- displayLabel: default_2.ZodString;
860
- countQuery: default_2.ZodString;
861
- coverageQuery: default_2.ZodString;
862
- }, "strip", default_2.ZodTypeAny, {
863
- displayLabel: string;
864
- countQuery: string;
865
- coverageQuery: string;
866
- }, {
867
- displayLabel: string;
868
- countQuery: string;
869
- coverageQuery: string;
870
- }>;
871
-
872
890
  export declare type RelativeGrowthAdvantageProps = default_2.infer<typeof relativeGrowthAdvantagePropsSchema>;
873
891
 
874
892
  declare const relativeGrowthAdvantagePropsSchema: default_2.ZodObject<{
@@ -1080,9 +1098,9 @@ declare global {
1080
1098
 
1081
1099
 
1082
1100
  declare global {
1083
- namespace JSX {
1101
+ namespace React.JSX {
1084
1102
  interface IntrinsicElements {
1085
- 'gs-app': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1103
+ 'gs-app': AppComponent;
1086
1104
  }
1087
1105
  }
1088
1106
  }
@@ -1090,15 +1108,15 @@ declare global {
1090
1108
 
1091
1109
  declare global {
1092
1110
  interface HTMLElementTagNameMap {
1093
- 'gs-wastewater-mutations-over-time': WastewaterMutationsOverTimeComponent;
1111
+ 'gs-genome-data-viewer': GenomeDataViewerComponent;
1094
1112
  }
1095
1113
  }
1096
1114
 
1097
1115
 
1098
1116
  declare global {
1099
- namespace JSX {
1117
+ namespace React.JSX {
1100
1118
  interface IntrinsicElements {
1101
- 'gs-wastewater-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1119
+ 'gs-genome-data-viewer': GenomeDataViewerComponent;
1102
1120
  }
1103
1121
  }
1104
1122
  }
@@ -1106,15 +1124,15 @@ declare global {
1106
1124
 
1107
1125
  declare global {
1108
1126
  interface HTMLElementTagNameMap {
1109
- 'gs-genome-data-viewer': GenomeDataViewerComponent;
1127
+ 'gs-mutation-comparison': MutationComparisonComponent;
1110
1128
  }
1111
1129
  }
1112
1130
 
1113
1131
 
1114
1132
  declare global {
1115
- namespace JSX {
1133
+ namespace React.JSX {
1116
1134
  interface IntrinsicElements {
1117
- 'gs-genome-data-viewer': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1135
+ 'gs-mutation-comparison': MutationComparisonComponent;
1118
1136
  }
1119
1137
  }
1120
1138
  }
@@ -1122,15 +1140,15 @@ declare global {
1122
1140
 
1123
1141
  declare global {
1124
1142
  interface HTMLElementTagNameMap {
1125
- 'gs-mutation-comparison': MutationComparisonComponent;
1143
+ 'gs-mutations': MutationsComponent;
1126
1144
  }
1127
1145
  }
1128
1146
 
1129
1147
 
1130
1148
  declare global {
1131
- namespace JSX {
1149
+ namespace React.JSX {
1132
1150
  interface IntrinsicElements {
1133
- 'gs-mutation-comparison': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1151
+ 'gs-mutations': MutationsComponent;
1134
1152
  }
1135
1153
  }
1136
1154
  }
@@ -1138,15 +1156,15 @@ declare global {
1138
1156
 
1139
1157
  declare global {
1140
1158
  interface HTMLElementTagNameMap {
1141
- 'gs-mutations': MutationsComponent;
1159
+ 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
1142
1160
  }
1143
1161
  }
1144
1162
 
1145
1163
 
1146
1164
  declare global {
1147
- namespace JSX {
1165
+ namespace React.JSX {
1148
1166
  interface IntrinsicElements {
1149
- 'gs-mutations': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1167
+ 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
1150
1168
  }
1151
1169
  }
1152
1170
  }
@@ -1160,9 +1178,9 @@ declare global {
1160
1178
 
1161
1179
 
1162
1180
  declare global {
1163
- namespace JSX {
1181
+ namespace React.JSX {
1164
1182
  interface IntrinsicElements {
1165
- 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1183
+ 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
1166
1184
  }
1167
1185
  }
1168
1186
  }
@@ -1170,15 +1188,15 @@ declare global {
1170
1188
 
1171
1189
  declare global {
1172
1190
  interface HTMLElementTagNameMap {
1173
- 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
1191
+ 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1174
1192
  }
1175
1193
  }
1176
1194
 
1177
1195
 
1178
1196
  declare global {
1179
- namespace JSX {
1197
+ namespace React.JSX {
1180
1198
  interface IntrinsicElements {
1181
- 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1199
+ 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1182
1200
  }
1183
1201
  }
1184
1202
  }
@@ -1192,9 +1210,9 @@ declare global {
1192
1210
 
1193
1211
 
1194
1212
  declare global {
1195
- namespace JSX {
1213
+ namespace React.JSX {
1196
1214
  interface IntrinsicElements {
1197
- 'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1215
+ 'gs-aggregate': AggregateComponent;
1198
1216
  }
1199
1217
  }
1200
1218
  }
@@ -1202,15 +1220,15 @@ declare global {
1202
1220
 
1203
1221
  declare global {
1204
1222
  interface HTMLElementTagNameMap {
1205
- 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1223
+ 'gs-mutations-over-time': MutationsOverTimeComponent;
1206
1224
  }
1207
1225
  }
1208
1226
 
1209
1227
 
1210
1228
  declare global {
1211
- namespace JSX {
1229
+ namespace React.JSX {
1212
1230
  interface IntrinsicElements {
1213
- 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1231
+ 'gs-mutations-over-time': MutationsOverTimeComponent;
1214
1232
  }
1215
1233
  }
1216
1234
  }
@@ -1218,15 +1236,15 @@ declare global {
1218
1236
 
1219
1237
  declare global {
1220
1238
  interface HTMLElementTagNameMap {
1221
- 'gs-mutations-over-time': MutationsOverTimeComponent;
1239
+ 'gs-queries-over-time': QueriesOverTimeComponent;
1222
1240
  }
1223
1241
  }
1224
1242
 
1225
1243
 
1226
1244
  declare global {
1227
- namespace JSX {
1245
+ namespace React.JSX {
1228
1246
  interface IntrinsicElements {
1229
- 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1247
+ 'gs-queries-over-time': QueriesOverTimeComponent;
1230
1248
  }
1231
1249
  }
1232
1250
  }
@@ -1234,15 +1252,15 @@ declare global {
1234
1252
 
1235
1253
  declare global {
1236
1254
  interface HTMLElementTagNameMap {
1237
- 'gs-queries-over-time': QueriesOverTimeComponent;
1255
+ 'gs-statistics': StatisticsComponent;
1238
1256
  }
1239
1257
  }
1240
1258
 
1241
1259
 
1242
1260
  declare global {
1243
- namespace JSX {
1261
+ namespace React.JSX {
1244
1262
  interface IntrinsicElements {
1245
- 'gs-queries-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1263
+ 'gs-statistics': StatisticsComponent;
1246
1264
  }
1247
1265
  }
1248
1266
  }
@@ -1256,9 +1274,9 @@ declare global {
1256
1274
 
1257
1275
 
1258
1276
  declare global {
1259
- namespace JSX {
1277
+ namespace React.JSX {
1260
1278
  interface IntrinsicElements {
1261
- 'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1279
+ 'gs-sequences-by-location': SequencesByLocationComponent;
1262
1280
  }
1263
1281
  }
1264
1282
  }
@@ -1266,15 +1284,15 @@ declare global {
1266
1284
 
1267
1285
  declare global {
1268
1286
  interface HTMLElementTagNameMap {
1269
- 'gs-statistics': StatisticsComponent;
1287
+ 'gs-wastewater-mutations-over-time': WastewaterMutationsOverTimeComponent;
1270
1288
  }
1271
1289
  }
1272
1290
 
1273
1291
 
1274
1292
  declare global {
1275
- namespace JSX {
1293
+ namespace React.JSX {
1276
1294
  interface IntrinsicElements {
1277
- 'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1295
+ 'gs-wastewater-mutations-over-time': WastewaterMutationsOverTimeComponent;
1278
1296
  }
1279
1297
  }
1280
1298
  }
@@ -1292,9 +1310,9 @@ declare global {
1292
1310
 
1293
1311
 
1294
1312
  declare global {
1295
- namespace JSX {
1313
+ namespace React.JSX {
1296
1314
  interface IntrinsicElements {
1297
- 'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1315
+ 'gs-date-range-filter': DateRangeFilterComponent;
1298
1316
  }
1299
1317
  }
1300
1318
  }
@@ -1311,9 +1329,9 @@ declare global {
1311
1329
 
1312
1330
 
1313
1331
  declare global {
1314
- namespace JSX {
1332
+ namespace React.JSX {
1315
1333
  interface IntrinsicElements {
1316
- 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1334
+ 'gs-location-filter': LocationFilterComponent;
1317
1335
  }
1318
1336
  }
1319
1337
  }
@@ -1330,9 +1348,9 @@ declare global {
1330
1348
 
1331
1349
 
1332
1350
  declare global {
1333
- namespace JSX {
1351
+ namespace React.JSX {
1334
1352
  interface IntrinsicElements {
1335
- 'gs-text-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1353
+ 'gs-text-filter': TextFilterComponent;
1336
1354
  }
1337
1355
  }
1338
1356
  }
@@ -1349,9 +1367,9 @@ declare global {
1349
1367
 
1350
1368
 
1351
1369
  declare global {
1352
- namespace JSX {
1370
+ namespace React.JSX {
1353
1371
  interface IntrinsicElements {
1354
- 'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1372
+ 'gs-mutation-filter': MutationFilterComponent;
1355
1373
  }
1356
1374
  }
1357
1375
  }
@@ -1369,9 +1387,9 @@ declare global {
1369
1387
 
1370
1388
 
1371
1389
  declare global {
1372
- namespace JSX {
1390
+ namespace React.JSX {
1373
1391
  interface IntrinsicElements {
1374
- 'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1392
+ 'gs-lineage-filter': LineageFilterComponent;
1375
1393
  }
1376
1394
  }
1377
1395
  }
@@ -1389,9 +1407,9 @@ declare global {
1389
1407
 
1390
1408
 
1391
1409
  declare global {
1392
- namespace JSX {
1410
+ namespace React.JSX {
1393
1411
  interface IntrinsicElements {
1394
- 'gs-number-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1412
+ 'gs-number-range-filter': NumberRangeFilterComponent;
1395
1413
  }
1396
1414
  }
1397
1415
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genspectrum/dashboard-components",
3
- "version": "1.14.2",
3
+ "version": "1.16.0",
4
4
  "description": "GenSpectrum web components for building dashboards",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -15,13 +15,6 @@
15
15
  "publishConfig": {
16
16
  "access": "public"
17
17
  },
18
- "imports": {
19
- "#*": [
20
- "./*",
21
- "./*.ts",
22
- "./*.tsx"
23
- ]
24
- },
25
18
  "exports": {
26
19
  "./components": {
27
20
  "types": "./dist/components.d.ts",
@@ -136,7 +129,7 @@
136
129
  "msw": "^2.2.14",
137
130
  "postcss": "^8.4.38",
138
131
  "prettier": "^3.2.5",
139
- "react": "^18.3.1",
132
+ "react": "^19.0.0",
140
133
  "storybook": "^8.0.9",
141
134
  "storybook-addon-fetch-mock": "^2.0.0",
142
135
  "tailwindcss": "^4.0.9",
@@ -68,7 +68,7 @@ export const mutationsOverTimeResponse = makeLapisResponse(
68
68
  export type MutationsOverTimeResponse = z.infer<typeof mutationsOverTimeResponse>;
69
69
 
70
70
  const queryDefinition = z.object({
71
- displayLabel: z.string(),
71
+ displayLabel: z.string().optional(),
72
72
  countQuery: z.string(),
73
73
  coverageQuery: z.string(),
74
74
  });
@@ -8,7 +8,9 @@ import { type useMutationAnnotationsProvider } from '../MutationAnnotationsConte
8
8
  import type { DisplayedMutationType } from '../components/mutation-type-selector';
9
9
  import type { DisplayedSegment } from '../components/segment-selector';
10
10
 
11
- export const displayMutationsSchema = z.array(z.string());
11
+ export const displayMutationsSchema = z.array(z.string(), {
12
+ errorMap: () => ({ message: `invalid display mutations` }),
13
+ });
12
14
  export type DisplayMutations = z.infer<typeof displayMutationsSchema>;
13
15
 
14
16
  export type MutationFilter = {
@@ -0,0 +1,58 @@
1
+ import { type Meta, type StoryObj } from '@storybook/preact';
2
+ import { expect, within } from '@storybook/test';
3
+
4
+ import {
5
+ QueriesOverTimeRowLabelTooltip,
6
+ type QueriesOverTimeRowLabelTooltipProps,
7
+ } from './queries-over-time-row-label-tooltip';
8
+
9
+ const meta: Meta<QueriesOverTimeRowLabelTooltipProps> = {
10
+ title: 'Component/Queries over time row label tooltip',
11
+ component: QueriesOverTimeRowLabelTooltip,
12
+ argTypes: {
13
+ query: { control: 'object' },
14
+ },
15
+ parameters: {
16
+ fetchMock: {},
17
+ },
18
+ };
19
+
20
+ export default meta;
21
+
22
+ export const Default: StoryObj<QueriesOverTimeRowLabelTooltipProps> = {
23
+ render: (args) => <QueriesOverTimeRowLabelTooltip {...args} />,
24
+ args: {
25
+ query: {
26
+ displayLabel: 'Foo variant',
27
+ description: 'These mutations are associated with increased transmissibility.',
28
+ countQuery: 'S:451L & S:452L & S:453L & S:454L & S:455L & S:456L & S:457L & S:458L & S:459L & S:460L',
29
+ coverageQuery: '!S:456N',
30
+ },
31
+ },
32
+ play: async ({ canvasElement }) => {
33
+ const canvas = within(canvasElement);
34
+ await expect(canvas.getByText('Foo variant', { exact: true })).toBeVisible();
35
+ await expect(canvas.getByText('These mutations are associated with increased transmissibility.')).toBeVisible();
36
+ await expect(canvas.getByText('Count query:')).toBeVisible();
37
+ await expect(
38
+ canvas.getByText('S:451L & S:452L & S:453L & S:454L & S:455L & S:456L & S:457L & S:458L & S:459L & S:460L'),
39
+ ).toBeVisible();
40
+ },
41
+ };
42
+
43
+ export const WithoutDescription: StoryObj<QueriesOverTimeRowLabelTooltipProps> = {
44
+ render: (args) => <QueriesOverTimeRowLabelTooltip {...args} />,
45
+ args: {
46
+ query: {
47
+ displayLabel: 'S:R346T',
48
+ countQuery: 'S:346T',
49
+ coverageQuery: '!S:346N',
50
+ },
51
+ },
52
+ play: async ({ canvasElement }) => {
53
+ const canvas = within(canvasElement);
54
+ await expect(canvas.getByText('S:R346T', { exact: true })).toBeVisible();
55
+ await expect(canvas.getByText('Count query:')).toBeVisible();
56
+ await expect(canvas.getByText('S:346T')).toBeVisible();
57
+ },
58
+ };
@@ -0,0 +1,24 @@
1
+ import type { FunctionComponent } from 'preact';
2
+
3
+ import type { CountCoverageQuery } from './queries-over-time';
4
+
5
+ export type QueriesOverTimeRowLabelTooltipProps = {
6
+ query: CountCoverageQuery;
7
+ };
8
+
9
+ export const QueriesOverTimeRowLabelTooltip: FunctionComponent<QueriesOverTimeRowLabelTooltipProps> = ({ query }) => {
10
+ return (
11
+ <div className='flex flex-col gap-2 max-w-xl'>
12
+ <div className='font-bold'>{query.displayLabel}</div>
13
+ {query.description && <div className='text-sm text-gray-700'>{query.description}</div>}
14
+ <div className='text-sm'>
15
+ <span className='text-gray-600'>Count query:</span>
16
+ <div className='p-2 border border-gray-200 rounded bg-gray-50'>
17
+ <pre className='text-xs whitespace-pre-wrap'>
18
+ <code>{query.countQuery}</code>
19
+ </pre>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ );
24
+ };
@@ -466,6 +466,30 @@ export const WithNoLapisDateFieldField: StoryObj<QueriesOverTimeProps> = {
466
466
  },
467
467
  };
468
468
 
469
+ export const WithDuplicateDisplayLabels: StoryObj<QueriesOverTimeProps> = {
470
+ ...Default,
471
+ args: {
472
+ ...Default.args,
473
+ queries: [
474
+ {
475
+ displayLabel: 'S:F456L (single mutation)',
476
+ countQuery: 'S:456L',
477
+ coverageQuery: '!S:456N',
478
+ },
479
+ {
480
+ displayLabel: 'S:F456L (single mutation)',
481
+ countQuery: 'S:346T & S:456L',
482
+ coverageQuery: '!S:346N & !S:456N',
483
+ },
484
+ ],
485
+ },
486
+ play: async ({ canvasElement, step }) => {
487
+ await step('expect error message', async () => {
488
+ await expectInvalidAttributesErrorMessage(canvasElement, 'Display labels must be unique');
489
+ });
490
+ },
491
+ };
492
+
469
493
  async function expectQueryOnPage(canvas: Canvas, query: string) {
470
494
  await waitFor(async () => {
471
495
  const queryOnPage = canvas.getAllByText(query)[0];
@@ -5,6 +5,7 @@ import z from 'zod';
5
5
  import { getFilteredQueryOverTimeData, type QueryFilter } from './getFilteredQueriesOverTimeData';
6
6
  import { QueriesOverTimeFilter } from './queries-over-time-filter';
7
7
  import { QueriesOverTimeGridTooltip } from './queries-over-time-grid-tooltip';
8
+ import { QueriesOverTimeRowLabelTooltip } from './queries-over-time-row-label-tooltip';
8
9
  import { type ProportionValue, getProportion } from '../../query/queryMutationsOverTime';
9
10
  import { queryQueriesOverTimeData } from '../../query/queryQueriesOverTime';
10
11
  import { lapisFilterSchema, temporalGranularitySchema, views } from '../../types';
@@ -20,6 +21,7 @@ import { Fullscreen } from '../components/fullscreen';
20
21
  import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../components/info';
21
22
  import { LoadingDisplay } from '../components/loading-display';
22
23
  import { NoDataDisplay } from '../components/no-data-display';
24
+ import PortalTooltip from '../components/portal-tooltip';
23
25
  import type { ProportionInterval } from '../components/proportion-selector';
24
26
  import { ProportionSelectorDropdown } from '../components/proportion-selector-dropdown';
25
27
  import { ResizeContainer } from '../components/resize-container';
@@ -37,17 +39,28 @@ const meanProportionIntervalSchema = z.object({
37
39
  });
38
40
  export type MeanProportionInterval = z.infer<typeof meanProportionIntervalSchema>;
39
41
 
42
+ const countCoverageQuerySchema = z.object({
43
+ displayLabel: z.string(),
44
+ description: z.string().optional(),
45
+ countQuery: z.string(),
46
+ coverageQuery: z.string(),
47
+ });
48
+ export type CountCoverageQuery = z.infer<typeof countCoverageQuerySchema>;
49
+
40
50
  const queriesOverTimeSchema = z.object({
41
51
  lapisFilter: lapisFilterSchema,
42
52
  queries: z
43
- .array(
44
- z.object({
45
- displayLabel: z.string(),
46
- countQuery: z.string(),
47
- coverageQuery: z.string(),
48
- }),
49
- )
50
- .min(1),
53
+ .array(countCoverageQuerySchema)
54
+ .min(1)
55
+ .superRefine((queries, ctx) => {
56
+ const duplicateDisplayLabels = findDuplicateStrings(queries.map((v) => v.displayLabel));
57
+ if (duplicateDisplayLabels.length > 0) {
58
+ ctx.addIssue({
59
+ code: z.ZodIssueCode.custom,
60
+ message: `Display labels must be unique. Duplicates: ${duplicateDisplayLabels.join(', ')}`,
61
+ });
62
+ }
63
+ }),
51
64
  views: z.array(queriesOverTimeViewSchema),
52
65
  granularity: temporalGranularitySchema,
53
66
  lapisDateField: z.string().min(1),
@@ -136,19 +149,45 @@ const QueriesOverTimeTabs: FunctionComponent<QueriesOverTimeTabsProps> = ({
136
149
  });
137
150
  }, [queryOverTimeData, proportionInterval, hideGaps, queryFilterValue]);
138
151
 
152
+ const queryLookupMap = useMemo(
153
+ () => new Map(originalComponentProps.queries.map((query) => [query.displayLabel, query])),
154
+ [originalComponentProps.queries],
155
+ );
156
+
139
157
  const queryRenderer = useMemo<FeatureRenderer<string>>(
140
158
  () => ({
141
159
  asString: (value: string) => value,
142
- renderRowLabel: (value: string) => (
143
- <div className='text-center'>
144
- <span>{value}</span>
145
- </div>
146
- ),
160
+ renderRowLabel: (value: string) => {
161
+ const queryObject = queryLookupMap.get(value);
162
+
163
+ return (
164
+ <PortalTooltip
165
+ content={
166
+ <QueriesOverTimeRowLabelTooltip
167
+ query={
168
+ queryObject ?? {
169
+ displayLabel: value,
170
+ description: undefined,
171
+ countQuery: '',
172
+ coverageQuery: '',
173
+ }
174
+ }
175
+ />
176
+ }
177
+ position='right'
178
+ portalTarget={tooltipPortalTarget}
179
+ >
180
+ <div className='text-center whitespace-nowrap mr-2'>
181
+ <span>{value}</span>
182
+ </div>
183
+ </PortalTooltip>
184
+ );
185
+ },
147
186
  renderTooltip: (value: string, temporal: Temporal, proportionValue: ProportionValue) => (
148
187
  <QueriesOverTimeGridTooltip query={value} date={temporal} value={proportionValue} />
149
188
  ),
150
189
  }),
151
- [],
190
+ [tooltipPortalTarget, queryLookupMap],
152
191
  );
153
192
 
154
193
  const getTab = (view: QueriesOverTimeView) => {
@@ -302,3 +341,13 @@ function getDownloadData(filteredData: ReturnType<typeof getFilteredQueryOverTim
302
341
  );
303
342
  });
304
343
  }
344
+
345
+ function findDuplicateStrings(items: string[]): string[] {
346
+ const counts = new Map<string, number>();
347
+
348
+ for (const item of items) {
349
+ counts.set(item, (counts.get(item) ?? 0) + 1);
350
+ }
351
+
352
+ return [...counts.entries()].filter(([, count]) => count > 1).map(([key]) => key);
353
+ }
@@ -54,4 +54,4 @@ export {
54
54
  export { type MeanProportionInterval } from './preact/mutationsOverTime/mutations-over-time';
55
55
  export { type CustomColumn } from './preact/components/features-over-time-grid';
56
56
 
57
- export { type QueryDefinition } from './lapisApi/lapisTypes';
57
+ export { type CountCoverageQuery } from './preact/queriesOverTime/queries-over-time';