@genspectrum/dashboard-components 0.8.0 → 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/dist/assets/mutationOverTimeWorker-ICjqmm9j.js.map +1 -0
- package/dist/dashboard-components.js +131 -67
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +39 -39
- 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.tsx +1 -1
- package/src/preact/mutations/mutations.tsx +1 -2
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +1 -2
- 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/standalone-bundle/dashboard-components.js +4184 -4089
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/dist/assets/mutationOverTimeWorker-BOCXtKzd.js.map +0 -1
|
@@ -1029,7 +1029,10 @@ declare global {
|
|
|
1029
1029
|
|
|
1030
1030
|
declare global {
|
|
1031
1031
|
interface HTMLElementTagNameMap {
|
|
1032
|
-
'gs-
|
|
1032
|
+
'gs-location-filter': LocationFilterComponent;
|
|
1033
|
+
}
|
|
1034
|
+
interface HTMLElementEventMap {
|
|
1035
|
+
'gs-location-changed': CustomEvent<Record<string, string>>;
|
|
1033
1036
|
}
|
|
1034
1037
|
}
|
|
1035
1038
|
|
|
@@ -1037,7 +1040,7 @@ declare global {
|
|
|
1037
1040
|
declare global {
|
|
1038
1041
|
namespace JSX {
|
|
1039
1042
|
interface IntrinsicElements {
|
|
1040
|
-
'gs-
|
|
1043
|
+
'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1041
1044
|
}
|
|
1042
1045
|
}
|
|
1043
1046
|
}
|
|
@@ -1045,7 +1048,10 @@ declare global {
|
|
|
1045
1048
|
|
|
1046
1049
|
declare global {
|
|
1047
1050
|
interface HTMLElementTagNameMap {
|
|
1048
|
-
'gs-
|
|
1051
|
+
'gs-date-range-selector': DateRangeSelectorComponent;
|
|
1052
|
+
}
|
|
1053
|
+
interface HTMLElementEventMap {
|
|
1054
|
+
'gs-date-range-changed': CustomEvent<Record<string, string>>;
|
|
1049
1055
|
}
|
|
1050
1056
|
}
|
|
1051
1057
|
|
|
@@ -1053,7 +1059,7 @@ declare global {
|
|
|
1053
1059
|
declare global {
|
|
1054
1060
|
namespace JSX {
|
|
1055
1061
|
interface IntrinsicElements {
|
|
1056
|
-
'gs-
|
|
1062
|
+
'gs-date-range-selector': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1057
1063
|
}
|
|
1058
1064
|
}
|
|
1059
1065
|
}
|
|
@@ -1061,7 +1067,10 @@ declare global {
|
|
|
1061
1067
|
|
|
1062
1068
|
declare global {
|
|
1063
1069
|
interface HTMLElementTagNameMap {
|
|
1064
|
-
'gs-
|
|
1070
|
+
'gs-text-input': TextInputComponent;
|
|
1071
|
+
}
|
|
1072
|
+
interface HTMLElementEventMap {
|
|
1073
|
+
'gs-text-input-changed': CustomEvent<Record<string, string>>;
|
|
1065
1074
|
}
|
|
1066
1075
|
}
|
|
1067
1076
|
|
|
@@ -1069,7 +1078,7 @@ declare global {
|
|
|
1069
1078
|
declare global {
|
|
1070
1079
|
namespace JSX {
|
|
1071
1080
|
interface IntrinsicElements {
|
|
1072
|
-
'gs-
|
|
1081
|
+
'gs-text-input': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1073
1082
|
}
|
|
1074
1083
|
}
|
|
1075
1084
|
}
|
|
@@ -1077,7 +1086,10 @@ declare global {
|
|
|
1077
1086
|
|
|
1078
1087
|
declare global {
|
|
1079
1088
|
interface HTMLElementTagNameMap {
|
|
1080
|
-
'gs-
|
|
1089
|
+
'gs-mutation-filter': MutationFilterComponent;
|
|
1090
|
+
}
|
|
1091
|
+
interface HTMLElementEventMap {
|
|
1092
|
+
'gs-mutation-filter-changed': CustomEvent<SelectedMutationFilterStrings>;
|
|
1081
1093
|
}
|
|
1082
1094
|
}
|
|
1083
1095
|
|
|
@@ -1085,7 +1097,7 @@ declare global {
|
|
|
1085
1097
|
declare global {
|
|
1086
1098
|
namespace JSX {
|
|
1087
1099
|
interface IntrinsicElements {
|
|
1088
|
-
'gs-
|
|
1100
|
+
'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1089
1101
|
}
|
|
1090
1102
|
}
|
|
1091
1103
|
}
|
|
@@ -1093,7 +1105,10 @@ declare global {
|
|
|
1093
1105
|
|
|
1094
1106
|
declare global {
|
|
1095
1107
|
interface HTMLElementTagNameMap {
|
|
1096
|
-
'gs-
|
|
1108
|
+
'gs-lineage-filter': LineageFilterComponent;
|
|
1109
|
+
}
|
|
1110
|
+
interface HTMLElementEventMap {
|
|
1111
|
+
'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
|
|
1097
1112
|
}
|
|
1098
1113
|
}
|
|
1099
1114
|
|
|
@@ -1101,7 +1116,7 @@ declare global {
|
|
|
1101
1116
|
declare global {
|
|
1102
1117
|
namespace JSX {
|
|
1103
1118
|
interface IntrinsicElements {
|
|
1104
|
-
'gs-
|
|
1119
|
+
'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1105
1120
|
}
|
|
1106
1121
|
}
|
|
1107
1122
|
}
|
|
@@ -1109,7 +1124,7 @@ declare global {
|
|
|
1109
1124
|
|
|
1110
1125
|
declare global {
|
|
1111
1126
|
interface HTMLElementTagNameMap {
|
|
1112
|
-
'gs-
|
|
1127
|
+
'gs-mutation-comparison-component': MutationComparisonComponent;
|
|
1113
1128
|
}
|
|
1114
1129
|
}
|
|
1115
1130
|
|
|
@@ -1117,7 +1132,7 @@ declare global {
|
|
|
1117
1132
|
declare global {
|
|
1118
1133
|
namespace JSX {
|
|
1119
1134
|
interface IntrinsicElements {
|
|
1120
|
-
'gs-
|
|
1135
|
+
'gs-mutation-comparison-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1121
1136
|
}
|
|
1122
1137
|
}
|
|
1123
1138
|
}
|
|
@@ -1125,7 +1140,7 @@ declare global {
|
|
|
1125
1140
|
|
|
1126
1141
|
declare global {
|
|
1127
1142
|
interface HTMLElementTagNameMap {
|
|
1128
|
-
'gs-
|
|
1143
|
+
'gs-prevalence-over-time': PrevalenceOverTimeComponent;
|
|
1129
1144
|
}
|
|
1130
1145
|
}
|
|
1131
1146
|
|
|
@@ -1133,7 +1148,7 @@ declare global {
|
|
|
1133
1148
|
declare global {
|
|
1134
1149
|
namespace JSX {
|
|
1135
1150
|
interface IntrinsicElements {
|
|
1136
|
-
'gs-
|
|
1151
|
+
'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1137
1152
|
}
|
|
1138
1153
|
}
|
|
1139
1154
|
}
|
|
@@ -1141,10 +1156,7 @@ declare global {
|
|
|
1141
1156
|
|
|
1142
1157
|
declare global {
|
|
1143
1158
|
interface HTMLElementTagNameMap {
|
|
1144
|
-
'gs-
|
|
1145
|
-
}
|
|
1146
|
-
interface HTMLElementEventMap {
|
|
1147
|
-
'gs-date-range-changed': CustomEvent<Record<string, string>>;
|
|
1159
|
+
'gs-mutations-component': MutationsComponent;
|
|
1148
1160
|
}
|
|
1149
1161
|
}
|
|
1150
1162
|
|
|
@@ -1152,7 +1164,7 @@ declare global {
|
|
|
1152
1164
|
declare global {
|
|
1153
1165
|
namespace JSX {
|
|
1154
1166
|
interface IntrinsicElements {
|
|
1155
|
-
'gs-
|
|
1167
|
+
'gs-mutations-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1156
1168
|
}
|
|
1157
1169
|
}
|
|
1158
1170
|
}
|
|
@@ -1160,10 +1172,7 @@ declare global {
|
|
|
1160
1172
|
|
|
1161
1173
|
declare global {
|
|
1162
1174
|
interface HTMLElementTagNameMap {
|
|
1163
|
-
'gs-
|
|
1164
|
-
}
|
|
1165
|
-
interface HTMLElementEventMap {
|
|
1166
|
-
'gs-location-changed': CustomEvent<Record<string, string>>;
|
|
1175
|
+
'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
|
|
1167
1176
|
}
|
|
1168
1177
|
}
|
|
1169
1178
|
|
|
@@ -1171,7 +1180,7 @@ declare global {
|
|
|
1171
1180
|
declare global {
|
|
1172
1181
|
namespace JSX {
|
|
1173
1182
|
interface IntrinsicElements {
|
|
1174
|
-
'gs-
|
|
1183
|
+
'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1175
1184
|
}
|
|
1176
1185
|
}
|
|
1177
1186
|
}
|
|
@@ -1179,10 +1188,7 @@ declare global {
|
|
|
1179
1188
|
|
|
1180
1189
|
declare global {
|
|
1181
1190
|
interface HTMLElementTagNameMap {
|
|
1182
|
-
'gs-
|
|
1183
|
-
}
|
|
1184
|
-
interface HTMLElementEventMap {
|
|
1185
|
-
'gs-text-input-changed': CustomEvent<Record<string, string>>;
|
|
1191
|
+
'gs-aggregate-component': AggregateComponent;
|
|
1186
1192
|
}
|
|
1187
1193
|
}
|
|
1188
1194
|
|
|
@@ -1190,7 +1196,7 @@ declare global {
|
|
|
1190
1196
|
declare global {
|
|
1191
1197
|
namespace JSX {
|
|
1192
1198
|
interface IntrinsicElements {
|
|
1193
|
-
'gs-
|
|
1199
|
+
'gs-aggregate-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1194
1200
|
}
|
|
1195
1201
|
}
|
|
1196
1202
|
}
|
|
@@ -1198,10 +1204,7 @@ declare global {
|
|
|
1198
1204
|
|
|
1199
1205
|
declare global {
|
|
1200
1206
|
interface HTMLElementTagNameMap {
|
|
1201
|
-
'gs-
|
|
1202
|
-
}
|
|
1203
|
-
interface HTMLElementEventMap {
|
|
1204
|
-
'gs-mutation-filter-changed': CustomEvent<SelectedMutationFilterStrings>;
|
|
1207
|
+
'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
|
|
1205
1208
|
}
|
|
1206
1209
|
}
|
|
1207
1210
|
|
|
@@ -1209,7 +1212,7 @@ declare global {
|
|
|
1209
1212
|
declare global {
|
|
1210
1213
|
namespace JSX {
|
|
1211
1214
|
interface IntrinsicElements {
|
|
1212
|
-
'gs-
|
|
1215
|
+
'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1213
1216
|
}
|
|
1214
1217
|
}
|
|
1215
1218
|
}
|
|
@@ -1217,10 +1220,7 @@ declare global {
|
|
|
1217
1220
|
|
|
1218
1221
|
declare global {
|
|
1219
1222
|
interface HTMLElementTagNameMap {
|
|
1220
|
-
'gs-
|
|
1221
|
-
}
|
|
1222
|
-
interface HTMLElementEventMap {
|
|
1223
|
-
'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
|
|
1223
|
+
'gs-mutations-over-time': MutationsOverTimeComponent;
|
|
1224
1224
|
}
|
|
1225
1225
|
}
|
|
1226
1226
|
|
|
@@ -1228,7 +1228,7 @@ declare global {
|
|
|
1228
1228
|
declare global {
|
|
1229
1229
|
namespace JSX {
|
|
1230
1230
|
interface IntrinsicElements {
|
|
1231
|
-
'gs-
|
|
1231
|
+
'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1232
1232
|
}
|
|
1233
1233
|
}
|
|
1234
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>
|