@genspectrum/dashboard-components 0.7.2 → 0.8.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/custom-elements.json +1 -24
- package/dist/assets/mutationOverTimeWorker-ICjqmm9j.js.map +1 -0
- package/dist/dashboard-components.js +143 -90
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +39 -50
- package/dist/style.css +36 -0
- package/package.json +1 -1
- package/src/lapisApi/lapisApi.ts +59 -34
- package/src/preact/aggregatedData/aggregate.stories.tsx +35 -0
- package/src/preact/aggregatedData/aggregate.tsx +1 -2
- package/src/preact/components/error-boundary.tsx +9 -4
- package/src/preact/components/error-display.stories.tsx +23 -3
- package/src/preact/components/error-display.tsx +37 -25
- package/src/preact/dateRangeSelector/date-range-selector.tsx +1 -1
- package/src/preact/lineageFilter/lineage-filter.tsx +2 -3
- package/src/preact/locationFilter/location-filter.tsx +2 -3
- package/src/preact/mutationComparison/mutation-comparison.tsx +1 -2
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +5 -34
- package/src/preact/mutationFilter/mutation-filter.tsx +1 -14
- package/src/preact/mutations/mutations.tsx +1 -2
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +11 -6
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +1 -2
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +1 -2
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +1 -2
- package/src/preact/textInput/text-input.tsx +2 -3
- package/src/preact/webWorkers/useWebWorker.ts +4 -8
- package/src/web-components/input/gs-mutation-filter.stories.ts +1 -27
- package/src/web-components/input/gs-mutation-filter.tsx +0 -11
- package/standalone-bundle/dashboard-components.js +4152 -4068
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/dist/assets/mutationOverTimeWorker-BOCXtKzd.js.map +0 -1
|
@@ -450,16 +450,6 @@ export declare class MutationComparisonComponent extends PreactLitAdapterWithGri
|
|
|
450
450
|
* Fired when:
|
|
451
451
|
* - The user has submitted a valid mutation or insertion
|
|
452
452
|
* - The user has removed a mutation or insertion
|
|
453
|
-
*
|
|
454
|
-
* @fires {CustomEvent<{
|
|
455
|
-
* nucleotideMutations: string[],
|
|
456
|
-
* aminoAcidMutations: string[],
|
|
457
|
-
* nucleotideInsertions: string[],
|
|
458
|
-
* aminoAcidInsertions: string[]
|
|
459
|
-
* }>} gs-mutation-filter-on-blur
|
|
460
|
-
* Fired when:
|
|
461
|
-
* - the mutation filter has lost focus
|
|
462
|
-
* Contains the selected mutations in the format
|
|
463
453
|
*/
|
|
464
454
|
export declare class MutationFilterComponent extends PreactLitAdapter {
|
|
465
455
|
/**
|
|
@@ -1039,7 +1029,10 @@ declare global {
|
|
|
1039
1029
|
|
|
1040
1030
|
declare global {
|
|
1041
1031
|
interface HTMLElementTagNameMap {
|
|
1042
|
-
'gs-
|
|
1032
|
+
'gs-location-filter': LocationFilterComponent;
|
|
1033
|
+
}
|
|
1034
|
+
interface HTMLElementEventMap {
|
|
1035
|
+
'gs-location-changed': CustomEvent<Record<string, string>>;
|
|
1043
1036
|
}
|
|
1044
1037
|
}
|
|
1045
1038
|
|
|
@@ -1047,7 +1040,7 @@ declare global {
|
|
|
1047
1040
|
declare global {
|
|
1048
1041
|
namespace JSX {
|
|
1049
1042
|
interface IntrinsicElements {
|
|
1050
|
-
'gs-
|
|
1043
|
+
'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1051
1044
|
}
|
|
1052
1045
|
}
|
|
1053
1046
|
}
|
|
@@ -1055,7 +1048,10 @@ declare global {
|
|
|
1055
1048
|
|
|
1056
1049
|
declare global {
|
|
1057
1050
|
interface HTMLElementTagNameMap {
|
|
1058
|
-
'gs-
|
|
1051
|
+
'gs-date-range-selector': DateRangeSelectorComponent;
|
|
1052
|
+
}
|
|
1053
|
+
interface HTMLElementEventMap {
|
|
1054
|
+
'gs-date-range-changed': CustomEvent<Record<string, string>>;
|
|
1059
1055
|
}
|
|
1060
1056
|
}
|
|
1061
1057
|
|
|
@@ -1063,7 +1059,7 @@ declare global {
|
|
|
1063
1059
|
declare global {
|
|
1064
1060
|
namespace JSX {
|
|
1065
1061
|
interface IntrinsicElements {
|
|
1066
|
-
'gs-
|
|
1062
|
+
'gs-date-range-selector': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1067
1063
|
}
|
|
1068
1064
|
}
|
|
1069
1065
|
}
|
|
@@ -1071,7 +1067,10 @@ declare global {
|
|
|
1071
1067
|
|
|
1072
1068
|
declare global {
|
|
1073
1069
|
interface HTMLElementTagNameMap {
|
|
1074
|
-
'gs-
|
|
1070
|
+
'gs-text-input': TextInputComponent;
|
|
1071
|
+
}
|
|
1072
|
+
interface HTMLElementEventMap {
|
|
1073
|
+
'gs-text-input-changed': CustomEvent<Record<string, string>>;
|
|
1075
1074
|
}
|
|
1076
1075
|
}
|
|
1077
1076
|
|
|
@@ -1079,7 +1078,7 @@ declare global {
|
|
|
1079
1078
|
declare global {
|
|
1080
1079
|
namespace JSX {
|
|
1081
1080
|
interface IntrinsicElements {
|
|
1082
|
-
'gs-
|
|
1081
|
+
'gs-text-input': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1083
1082
|
}
|
|
1084
1083
|
}
|
|
1085
1084
|
}
|
|
@@ -1087,7 +1086,10 @@ declare global {
|
|
|
1087
1086
|
|
|
1088
1087
|
declare global {
|
|
1089
1088
|
interface HTMLElementTagNameMap {
|
|
1090
|
-
'gs-
|
|
1089
|
+
'gs-mutation-filter': MutationFilterComponent;
|
|
1090
|
+
}
|
|
1091
|
+
interface HTMLElementEventMap {
|
|
1092
|
+
'gs-mutation-filter-changed': CustomEvent<SelectedMutationFilterStrings>;
|
|
1091
1093
|
}
|
|
1092
1094
|
}
|
|
1093
1095
|
|
|
@@ -1095,7 +1097,7 @@ declare global {
|
|
|
1095
1097
|
declare global {
|
|
1096
1098
|
namespace JSX {
|
|
1097
1099
|
interface IntrinsicElements {
|
|
1098
|
-
'gs-
|
|
1100
|
+
'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1099
1101
|
}
|
|
1100
1102
|
}
|
|
1101
1103
|
}
|
|
@@ -1103,7 +1105,10 @@ declare global {
|
|
|
1103
1105
|
|
|
1104
1106
|
declare global {
|
|
1105
1107
|
interface HTMLElementTagNameMap {
|
|
1106
|
-
'gs-
|
|
1108
|
+
'gs-lineage-filter': LineageFilterComponent;
|
|
1109
|
+
}
|
|
1110
|
+
interface HTMLElementEventMap {
|
|
1111
|
+
'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
|
|
1107
1112
|
}
|
|
1108
1113
|
}
|
|
1109
1114
|
|
|
@@ -1111,7 +1116,7 @@ declare global {
|
|
|
1111
1116
|
declare global {
|
|
1112
1117
|
namespace JSX {
|
|
1113
1118
|
interface IntrinsicElements {
|
|
1114
|
-
'gs-
|
|
1119
|
+
'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1115
1120
|
}
|
|
1116
1121
|
}
|
|
1117
1122
|
}
|
|
@@ -1119,7 +1124,7 @@ declare global {
|
|
|
1119
1124
|
|
|
1120
1125
|
declare global {
|
|
1121
1126
|
interface HTMLElementTagNameMap {
|
|
1122
|
-
'gs-
|
|
1127
|
+
'gs-mutation-comparison-component': MutationComparisonComponent;
|
|
1123
1128
|
}
|
|
1124
1129
|
}
|
|
1125
1130
|
|
|
@@ -1127,7 +1132,7 @@ declare global {
|
|
|
1127
1132
|
declare global {
|
|
1128
1133
|
namespace JSX {
|
|
1129
1134
|
interface IntrinsicElements {
|
|
1130
|
-
'gs-
|
|
1135
|
+
'gs-mutation-comparison-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1131
1136
|
}
|
|
1132
1137
|
}
|
|
1133
1138
|
}
|
|
@@ -1135,7 +1140,7 @@ declare global {
|
|
|
1135
1140
|
|
|
1136
1141
|
declare global {
|
|
1137
1142
|
interface HTMLElementTagNameMap {
|
|
1138
|
-
'gs-
|
|
1143
|
+
'gs-prevalence-over-time': PrevalenceOverTimeComponent;
|
|
1139
1144
|
}
|
|
1140
1145
|
}
|
|
1141
1146
|
|
|
@@ -1143,7 +1148,7 @@ declare global {
|
|
|
1143
1148
|
declare global {
|
|
1144
1149
|
namespace JSX {
|
|
1145
1150
|
interface IntrinsicElements {
|
|
1146
|
-
'gs-
|
|
1151
|
+
'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1147
1152
|
}
|
|
1148
1153
|
}
|
|
1149
1154
|
}
|
|
@@ -1151,10 +1156,7 @@ declare global {
|
|
|
1151
1156
|
|
|
1152
1157
|
declare global {
|
|
1153
1158
|
interface HTMLElementTagNameMap {
|
|
1154
|
-
'gs-
|
|
1155
|
-
}
|
|
1156
|
-
interface HTMLElementEventMap {
|
|
1157
|
-
'gs-date-range-changed': CustomEvent<Record<string, string>>;
|
|
1159
|
+
'gs-mutations-component': MutationsComponent;
|
|
1158
1160
|
}
|
|
1159
1161
|
}
|
|
1160
1162
|
|
|
@@ -1162,7 +1164,7 @@ declare global {
|
|
|
1162
1164
|
declare global {
|
|
1163
1165
|
namespace JSX {
|
|
1164
1166
|
interface IntrinsicElements {
|
|
1165
|
-
'gs-
|
|
1167
|
+
'gs-mutations-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1166
1168
|
}
|
|
1167
1169
|
}
|
|
1168
1170
|
}
|
|
@@ -1170,10 +1172,7 @@ declare global {
|
|
|
1170
1172
|
|
|
1171
1173
|
declare global {
|
|
1172
1174
|
interface HTMLElementTagNameMap {
|
|
1173
|
-
'gs-
|
|
1174
|
-
}
|
|
1175
|
-
interface HTMLElementEventMap {
|
|
1176
|
-
'gs-location-changed': CustomEvent<Record<string, string>>;
|
|
1175
|
+
'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
|
|
1177
1176
|
}
|
|
1178
1177
|
}
|
|
1179
1178
|
|
|
@@ -1181,7 +1180,7 @@ declare global {
|
|
|
1181
1180
|
declare global {
|
|
1182
1181
|
namespace JSX {
|
|
1183
1182
|
interface IntrinsicElements {
|
|
1184
|
-
'gs-
|
|
1183
|
+
'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1185
1184
|
}
|
|
1186
1185
|
}
|
|
1187
1186
|
}
|
|
@@ -1189,10 +1188,7 @@ declare global {
|
|
|
1189
1188
|
|
|
1190
1189
|
declare global {
|
|
1191
1190
|
interface HTMLElementTagNameMap {
|
|
1192
|
-
'gs-
|
|
1193
|
-
}
|
|
1194
|
-
interface HTMLElementEventMap {
|
|
1195
|
-
'gs-text-input-changed': CustomEvent<Record<string, string>>;
|
|
1191
|
+
'gs-aggregate-component': AggregateComponent;
|
|
1196
1192
|
}
|
|
1197
1193
|
}
|
|
1198
1194
|
|
|
@@ -1200,7 +1196,7 @@ declare global {
|
|
|
1200
1196
|
declare global {
|
|
1201
1197
|
namespace JSX {
|
|
1202
1198
|
interface IntrinsicElements {
|
|
1203
|
-
'gs-
|
|
1199
|
+
'gs-aggregate-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1204
1200
|
}
|
|
1205
1201
|
}
|
|
1206
1202
|
}
|
|
@@ -1208,11 +1204,7 @@ declare global {
|
|
|
1208
1204
|
|
|
1209
1205
|
declare global {
|
|
1210
1206
|
interface HTMLElementTagNameMap {
|
|
1211
|
-
'gs-
|
|
1212
|
-
}
|
|
1213
|
-
interface HTMLElementEventMap {
|
|
1214
|
-
'gs-mutation-filter-changed': CustomEvent<SelectedMutationFilterStrings>;
|
|
1215
|
-
'gs-mutation-filter-on-blur': CustomEvent<SelectedMutationFilterStrings>;
|
|
1207
|
+
'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
|
|
1216
1208
|
}
|
|
1217
1209
|
}
|
|
1218
1210
|
|
|
@@ -1220,7 +1212,7 @@ declare global {
|
|
|
1220
1212
|
declare global {
|
|
1221
1213
|
namespace JSX {
|
|
1222
1214
|
interface IntrinsicElements {
|
|
1223
|
-
'gs-
|
|
1215
|
+
'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1224
1216
|
}
|
|
1225
1217
|
}
|
|
1226
1218
|
}
|
|
@@ -1228,10 +1220,7 @@ declare global {
|
|
|
1228
1220
|
|
|
1229
1221
|
declare global {
|
|
1230
1222
|
interface HTMLElementTagNameMap {
|
|
1231
|
-
'gs-
|
|
1232
|
-
}
|
|
1233
|
-
interface HTMLElementEventMap {
|
|
1234
|
-
'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
|
|
1223
|
+
'gs-mutations-over-time': MutationsOverTimeComponent;
|
|
1235
1224
|
}
|
|
1236
1225
|
}
|
|
1237
1226
|
|
|
@@ -1239,7 +1228,7 @@ declare global {
|
|
|
1239
1228
|
declare global {
|
|
1240
1229
|
namespace JSX {
|
|
1241
1230
|
interface IntrinsicElements {
|
|
1242
|
-
'gs-
|
|
1231
|
+
'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1243
1232
|
}
|
|
1244
1233
|
}
|
|
1245
1234
|
}
|
package/dist/style.css
CHANGED
|
@@ -2525,6 +2525,36 @@ input.tab:checked + .tab-content,
|
|
|
2525
2525
|
--togglehandleborder: 0 0 0 3px var(--fallback-bc,oklch(var(--bc)/1)) inset,
|
|
2526
2526
|
var(--handleoffsetcalculator) 0 0 3px var(--fallback-bc,oklch(var(--bc)/1)) inset;
|
|
2527
2527
|
}
|
|
2528
|
+
.artboard.phone-1.horizontal,
|
|
2529
|
+
.artboard.phone-1.artboard-horizontal {
|
|
2530
|
+
width: 568px;
|
|
2531
|
+
height: 320px;
|
|
2532
|
+
}
|
|
2533
|
+
.artboard.phone-2.horizontal,
|
|
2534
|
+
.artboard.phone-2.artboard-horizontal {
|
|
2535
|
+
width: 667px;
|
|
2536
|
+
height: 375px;
|
|
2537
|
+
}
|
|
2538
|
+
.artboard.phone-3.horizontal,
|
|
2539
|
+
.artboard.phone-3.artboard-horizontal {
|
|
2540
|
+
width: 736px;
|
|
2541
|
+
height: 414px;
|
|
2542
|
+
}
|
|
2543
|
+
.artboard.phone-4.horizontal,
|
|
2544
|
+
.artboard.phone-4.artboard-horizontal {
|
|
2545
|
+
width: 812px;
|
|
2546
|
+
height: 375px;
|
|
2547
|
+
}
|
|
2548
|
+
.artboard.phone-5.horizontal,
|
|
2549
|
+
.artboard.phone-5.artboard-horizontal {
|
|
2550
|
+
width: 896px;
|
|
2551
|
+
height: 414px;
|
|
2552
|
+
}
|
|
2553
|
+
.artboard.phone-6.horizontal,
|
|
2554
|
+
.artboard.phone-6.artboard-horizontal {
|
|
2555
|
+
width: 1024px;
|
|
2556
|
+
height: 320px;
|
|
2557
|
+
}
|
|
2528
2558
|
.btm-nav-xs > *:where(.active) {
|
|
2529
2559
|
border-top-width: 1px;
|
|
2530
2560
|
}
|
|
@@ -2937,6 +2967,9 @@ input.tab:checked + .tab-content,
|
|
|
2937
2967
|
.m-2 {
|
|
2938
2968
|
margin: 0.5rem;
|
|
2939
2969
|
}
|
|
2970
|
+
.m-4 {
|
|
2971
|
+
margin: 1rem;
|
|
2972
|
+
}
|
|
2940
2973
|
.mx-1 {
|
|
2941
2974
|
margin-left: 0.25rem;
|
|
2942
2975
|
margin-right: 0.25rem;
|
|
@@ -3320,6 +3353,9 @@ input.tab:checked + .tab-content,
|
|
|
3320
3353
|
.mdi--fullscreen-exit {
|
|
3321
3354
|
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='black' d='M14 14h5v2h-3v3h-2zm-9 0h5v5H8v-3H5zm3-9h2v5H5V8h3zm11 3v2h-5V5h2v3z'/%3E%3C/svg%3E");
|
|
3322
3355
|
}
|
|
3356
|
+
.mdi--reload {
|
|
3357
|
+
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='black' d='M2 12a9 9 0 0 0 9 9c2.39 0 4.68-.94 6.4-2.6l-1.5-1.5A6.7 6.7 0 0 1 11 19c-6.24 0-9.36-7.54-4.95-11.95S18 5.77 18 12h-3l4 4h.1l3.9-4h-3a9 9 0 0 0-18 0'/%3E%3C/svg%3E");
|
|
3358
|
+
}
|
|
3323
3359
|
@media (min-width: 640px) {
|
|
3324
3360
|
|
|
3325
3361
|
.sm\:modal-middle {
|
package/package.json
CHANGED
package/src/lapisApi/lapisApi.ts
CHANGED
|
@@ -35,16 +35,18 @@ export class LapisError extends Error {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export async function fetchAggregated(lapisUrl: string, body: LapisBaseRequest, signal?: AbortSignal) {
|
|
38
|
-
const response = await
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
const response = await callLapis(
|
|
39
|
+
aggregatedEndpoint(lapisUrl),
|
|
40
|
+
{
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: {
|
|
43
|
+
'Content-Type': 'application/json',
|
|
44
|
+
},
|
|
45
|
+
body: JSON.stringify(body),
|
|
46
|
+
signal,
|
|
42
47
|
},
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
await handleErrors(response, 'aggregated data');
|
|
48
|
+
'aggregated data',
|
|
49
|
+
);
|
|
48
50
|
|
|
49
51
|
return aggregatedResponse.parse(await response.json());
|
|
50
52
|
}
|
|
@@ -55,16 +57,18 @@ export async function fetchInsertions(
|
|
|
55
57
|
sequenceType: SequenceType,
|
|
56
58
|
signal?: AbortSignal,
|
|
57
59
|
) {
|
|
58
|
-
const response = await
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
const response = await callLapis(
|
|
61
|
+
insertionsEndpoint(lapisUrl, sequenceType),
|
|
62
|
+
{
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: {
|
|
65
|
+
'Content-Type': 'application/json',
|
|
66
|
+
},
|
|
67
|
+
body: JSON.stringify(body),
|
|
68
|
+
signal,
|
|
62
69
|
},
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
await handleErrors(response, `${sequenceType} insertions`);
|
|
70
|
+
`${sequenceType} insertions`,
|
|
71
|
+
);
|
|
68
72
|
|
|
69
73
|
return insertionsResponse.parse(await response.json());
|
|
70
74
|
}
|
|
@@ -75,33 +79,54 @@ export async function fetchSubstitutionsOrDeletions(
|
|
|
75
79
|
sequenceType: SequenceType,
|
|
76
80
|
signal?: AbortSignal,
|
|
77
81
|
) {
|
|
78
|
-
const response = await
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
const response = await callLapis(
|
|
83
|
+
substitutionsOrDeletionsEndpoint(lapisUrl, sequenceType),
|
|
84
|
+
{
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: {
|
|
87
|
+
'Content-Type': 'application/json',
|
|
88
|
+
},
|
|
89
|
+
body: JSON.stringify(body),
|
|
90
|
+
signal,
|
|
82
91
|
},
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
await handleErrors(response, `${sequenceType} mutations`);
|
|
92
|
+
`${sequenceType} mutations`,
|
|
93
|
+
);
|
|
88
94
|
|
|
89
95
|
return mutationsResponse.parse(await response.json());
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
export async function fetchReferenceGenome(lapisUrl: string, signal?: AbortSignal) {
|
|
93
|
-
const response = await
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
const response = await callLapis(
|
|
100
|
+
referenceGenomeEndpoint(lapisUrl),
|
|
101
|
+
{
|
|
102
|
+
method: 'GET',
|
|
103
|
+
headers: {
|
|
104
|
+
'Content-Type': 'application/json',
|
|
105
|
+
},
|
|
106
|
+
signal,
|
|
97
107
|
},
|
|
98
|
-
|
|
99
|
-
|
|
108
|
+
'the reference genomes',
|
|
109
|
+
);
|
|
100
110
|
|
|
101
|
-
await handleErrors(response, 'the reference genomes');
|
|
102
111
|
return referenceGenomeResponse.parse(await response.json());
|
|
103
112
|
}
|
|
104
113
|
|
|
114
|
+
async function callLapis(
|
|
115
|
+
input: Parameters<typeof fetch>[0],
|
|
116
|
+
init: Parameters<typeof fetch>[1],
|
|
117
|
+
requestedDataName: string,
|
|
118
|
+
) {
|
|
119
|
+
try {
|
|
120
|
+
const response = await fetch(input, init);
|
|
121
|
+
|
|
122
|
+
await handleErrors(response, requestedDataName);
|
|
123
|
+
return response;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
const message = error instanceof Error ? error.message : `${error}`;
|
|
126
|
+
throw new UnknownLapisError(`Failed to connect to LAPIS: ${message}`, 500, requestedDataName);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
105
130
|
const handleErrors = async (response: Response, requestedData: string) => {
|
|
106
131
|
if (!response.ok) {
|
|
107
132
|
if (response.status >= 400 && response.status < 500) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
+
import { expect, waitFor, within } from '@storybook/test';
|
|
2
3
|
|
|
3
4
|
import aggregatedData from './__mockData__/aggregated.json';
|
|
4
5
|
import { Aggregate, type AggregateProps } from './aggregate';
|
|
@@ -59,3 +60,37 @@ export const Default: StoryObj<AggregateProps> = {
|
|
|
59
60
|
pageSize: 10,
|
|
60
61
|
},
|
|
61
62
|
};
|
|
63
|
+
|
|
64
|
+
export const FailsLoadingData: StoryObj<AggregateProps> = {
|
|
65
|
+
...Default,
|
|
66
|
+
parameters: {
|
|
67
|
+
fetchMock: {
|
|
68
|
+
mocks: [
|
|
69
|
+
{
|
|
70
|
+
matcher: {
|
|
71
|
+
name: 'aggregatedData',
|
|
72
|
+
url: AGGREGATED_ENDPOINT,
|
|
73
|
+
},
|
|
74
|
+
response: {
|
|
75
|
+
status: 400,
|
|
76
|
+
body: {
|
|
77
|
+
error: {
|
|
78
|
+
title: 'Bad Request',
|
|
79
|
+
detail: 'Test error',
|
|
80
|
+
status: 400,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
play: async ({ canvasElement }) => {
|
|
89
|
+
const canvas = within(canvasElement);
|
|
90
|
+
|
|
91
|
+
await waitFor(async () => {
|
|
92
|
+
await expect(canvas.getByText('Error - Failed fetching aggregated data from LAPIS')).toBeInTheDocument();
|
|
93
|
+
await expect(canvas.getByRole('button', { name: 'Try again' })).toBeInTheDocument();
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
};
|
|
@@ -7,7 +7,6 @@ import { type LapisFilter } from '../../types';
|
|
|
7
7
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
8
8
|
import { CsvDownloadButton } from '../components/csv-download-button';
|
|
9
9
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
10
|
-
import { ErrorDisplay } from '../components/error-display';
|
|
11
10
|
import { Fullscreen } from '../components/fullscreen';
|
|
12
11
|
import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../components/info';
|
|
13
12
|
import { LoadingDisplay } from '../components/loading-display';
|
|
@@ -56,7 +55,7 @@ export const AggregateInner: FunctionComponent<AggregateProps> = (componentProps
|
|
|
56
55
|
}
|
|
57
56
|
|
|
58
57
|
if (error !== null) {
|
|
59
|
-
|
|
58
|
+
throw error;
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
if (data === null) {
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import type { FunctionComponent } from 'preact';
|
|
2
2
|
import { useErrorBoundary } from 'preact/hooks';
|
|
3
3
|
|
|
4
|
-
import { ErrorDisplay } from './error-display';
|
|
4
|
+
import { ErrorDisplay, type ErrorDisplayProps } from './error-display';
|
|
5
5
|
import { ResizeContainer, type Size } from './resize-container';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
type ErrorBoundaryProps = {
|
|
8
|
+
size: Size;
|
|
9
|
+
layout?: ErrorDisplayProps['layout'];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const ErrorBoundary: FunctionComponent<ErrorBoundaryProps> = ({ size, layout, children }) => {
|
|
13
|
+
const [internalError, resetError] = useErrorBoundary();
|
|
9
14
|
|
|
10
15
|
if (internalError) {
|
|
11
16
|
return (
|
|
12
17
|
<ResizeContainer size={size}>
|
|
13
|
-
<ErrorDisplay error={internalError} />
|
|
18
|
+
<ErrorDisplay error={internalError} resetError={resetError} layout={layout} />
|
|
14
19
|
</ResizeContainer>
|
|
15
20
|
);
|
|
16
21
|
}
|
|
@@ -15,7 +15,7 @@ export default meta;
|
|
|
15
15
|
export const GenericErrorStory: StoryObj = {
|
|
16
16
|
render: () => (
|
|
17
17
|
<ResizeContainer size={{ height: '600px', width: '100%' }}>
|
|
18
|
-
<ErrorDisplay error={new Error('some message')} />
|
|
18
|
+
<ErrorDisplay error={new Error('some message')} resetError={() => {}} />
|
|
19
19
|
</ResizeContainer>
|
|
20
20
|
),
|
|
21
21
|
|
|
@@ -30,7 +30,7 @@ export const GenericErrorStory: StoryObj = {
|
|
|
30
30
|
export const UserFacingErrorStory: StoryObj = {
|
|
31
31
|
render: () => (
|
|
32
32
|
<ResizeContainer size={{ height: '600px', width: '100%' }}>
|
|
33
|
-
<ErrorDisplay error={new UserFacingError('Error Title', 'some message')} />
|
|
33
|
+
<ErrorDisplay error={new UserFacingError('Error Title', 'some message')} resetError={() => {}} />
|
|
34
34
|
</ResizeContainer>
|
|
35
35
|
),
|
|
36
36
|
|
|
@@ -52,7 +52,7 @@ export const UserFacingErrorStory: StoryObj = {
|
|
|
52
52
|
export const FiresEvent: StoryObj = {
|
|
53
53
|
render: () => (
|
|
54
54
|
<ResizeContainer size={{ height: '600px', width: '100%' }}>
|
|
55
|
-
<ErrorDisplay error={new UserFacingError('Error Title', 'some message')} />
|
|
55
|
+
<ErrorDisplay error={new UserFacingError('Error Title', 'some message')} resetError={() => {}} />
|
|
56
56
|
</ResizeContainer>
|
|
57
57
|
),
|
|
58
58
|
|
|
@@ -66,3 +66,23 @@ export const FiresEvent: StoryObj = {
|
|
|
66
66
|
});
|
|
67
67
|
},
|
|
68
68
|
};
|
|
69
|
+
|
|
70
|
+
const resetErrorMock = fn();
|
|
71
|
+
|
|
72
|
+
export const TriggersResetErrorOnReloadButton: StoryObj = {
|
|
73
|
+
render: () => (
|
|
74
|
+
<ResizeContainer size={{ height: '600px', width: '100%' }}>
|
|
75
|
+
<ErrorDisplay error={new UserFacingError('Error Title', 'some message')} resetError={resetErrorMock} />
|
|
76
|
+
</ResizeContainer>
|
|
77
|
+
),
|
|
78
|
+
|
|
79
|
+
play: async ({ canvasElement }) => {
|
|
80
|
+
const canvas = within(canvasElement);
|
|
81
|
+
|
|
82
|
+
await userEvent.click(canvas.getByText('Try again'));
|
|
83
|
+
|
|
84
|
+
await waitFor(() => {
|
|
85
|
+
expect(resetErrorMock).toHaveBeenCalled();
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
};
|
|
@@ -24,7 +24,13 @@ export class UserFacingError extends Error {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export
|
|
27
|
+
export type ErrorDisplayProps = {
|
|
28
|
+
error: Error;
|
|
29
|
+
resetError: () => void;
|
|
30
|
+
layout?: 'horizontal' | 'vertical';
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const ErrorDisplay: FunctionComponent<ErrorDisplayProps> = ({ error, resetError, layout }) => {
|
|
28
34
|
// eslint-disable-next-line no-console -- Currently we use the following statement for our error handling
|
|
29
35
|
console.error(error);
|
|
30
36
|
|
|
@@ -40,34 +46,40 @@ export const ErrorDisplay: FunctionComponent<{ error: Error }> = ({ error }) =>
|
|
|
40
46
|
return (
|
|
41
47
|
<div
|
|
42
48
|
ref={containerRef}
|
|
43
|
-
className=
|
|
49
|
+
className={`h-full w-full rounded-md border-2 border-gray-100 p-2 flex items-center justify-center ${layout === 'horizontal' ? 'flex-row' : 'flex-col'}`}
|
|
44
50
|
>
|
|
45
|
-
<div className='text-red-700 font-bold'>{headline}</div>
|
|
46
51
|
<div>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
<div className='text-red-700 font-bold'>{headline}</div>
|
|
53
|
+
<div>
|
|
54
|
+
Oops! Something went wrong.
|
|
55
|
+
{details !== undefined && (
|
|
56
|
+
<>
|
|
57
|
+
{' '}
|
|
58
|
+
<button className='underline hover:text-gray-400' onClick={() => ref.current?.showModal()}>
|
|
59
|
+
Show details.
|
|
60
|
+
</button>
|
|
61
|
+
<dialog ref={ref} class='modal'>
|
|
62
|
+
<div class='modal-box'>
|
|
63
|
+
<form method='dialog'>
|
|
64
|
+
<button className='btn btn-sm btn-circle btn-ghost absolute right-2 top-2'>
|
|
65
|
+
✕
|
|
66
|
+
</button>
|
|
67
|
+
</form>
|
|
68
|
+
<h1 class='text-lg'>{details.headline}</h1>
|
|
69
|
+
<p class='py-4'>{details.message}</p>
|
|
70
|
+
</div>
|
|
71
|
+
<form method='dialog' class='modal-backdrop'>
|
|
72
|
+
<button>close</button>
|
|
60
73
|
</form>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
<button>close</button>
|
|
66
|
-
</form>
|
|
67
|
-
</dialog>
|
|
68
|
-
</>
|
|
69
|
-
)}
|
|
74
|
+
</dialog>
|
|
75
|
+
</>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
70
78
|
</div>
|
|
79
|
+
<button onClick={resetError} className='btn btn-sm flex items-center m-4'>
|
|
80
|
+
<span className='iconify mdi--reload text-lg' />
|
|
81
|
+
Try again
|
|
82
|
+
</button>
|
|
71
83
|
</div>
|
|
72
84
|
);
|
|
73
85
|
};
|
|
@@ -29,7 +29,7 @@ export const DateRangeSelector = ({ width, ...innerProps }: DateRangeSelectorPro
|
|
|
29
29
|
const size = { width, height: '3rem' };
|
|
30
30
|
|
|
31
31
|
return (
|
|
32
|
-
<ErrorBoundary size={size}>
|
|
32
|
+
<ErrorBoundary size={size} layout='horizontal'>
|
|
33
33
|
<div style={{ width }}>
|
|
34
34
|
<DateRangeSelectorInner {...innerProps} />
|
|
35
35
|
</div>
|