@genspectrum/dashboard-components 0.6.14 → 0.6.15
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 +12 -6
- package/dist/dashboard-components.js +97 -53
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +27 -9
- package/dist/style.css +2 -2
- package/package.json +3 -3
- package/src/index.ts +8 -0
- package/src/lapisApi/lapisApi.ts +15 -7
- package/src/preact/components/color-scale-selector-dropdown.stories.tsx +1 -1
- package/src/preact/components/error-boundary.stories.tsx +21 -4
- package/src/preact/components/error-display.stories.tsx +20 -2
- package/src/preact/components/error-display.tsx +64 -10
- package/src/preact/components/info.stories.tsx +5 -5
- package/src/preact/components/info.tsx +1 -3
- package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +2 -3
- package/src/preact/lineageFilter/lineage-filter.stories.tsx +2 -3
- package/src/preact/locationFilter/fetchAutocompletionList.ts +1 -14
- package/src/preact/locationFilter/location-filter.stories.tsx +2 -3
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +2 -3
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +1 -1
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +1 -1
- package/src/preact/shared/floating-ui/hooks.ts +1 -1
- package/src/preact/textInput/text-input.stories.tsx +2 -3
- package/src/web-components/app.stories.ts +0 -2
- package/src/web-components/errorHandling.mdx +8 -0
- package/src/web-components/input/gs-date-range-selector.stories.ts +2 -3
- package/src/web-components/input/gs-lineage-filter.stories.ts +2 -3
- package/src/web-components/input/gs-location-filter.stories.ts +2 -3
- package/src/web-components/input/gs-mutation-filter.stories.ts +2 -3
- package/src/web-components/input/gs-text-input.stories.ts +2 -3
- package/standalone-bundle/dashboard-components.js +1719 -1686
- package/standalone-bundle/dashboard-components.js.map +1 -1
|
@@ -178,6 +178,12 @@ export declare class DateRangeSelectorComponent extends PreactLitAdapter {
|
|
|
178
178
|
render(): JSX_2.Element;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
declare class ErrorEvent_2 extends Event {
|
|
182
|
+
readonly error: Error;
|
|
183
|
+
constructor(error: Error);
|
|
184
|
+
}
|
|
185
|
+
export { ErrorEvent_2 as ErrorEvent }
|
|
186
|
+
|
|
181
187
|
declare type LapisFilter = Record<string, string | number | null | boolean>;
|
|
182
188
|
|
|
183
189
|
/**
|
|
@@ -948,11 +954,23 @@ export declare class TextInputComponent extends PreactLitAdapter {
|
|
|
948
954
|
render(): JSX_2.Element;
|
|
949
955
|
}
|
|
950
956
|
|
|
957
|
+
export declare class UserFacingError extends Error {
|
|
958
|
+
readonly headline: string;
|
|
959
|
+
constructor(headline: string, message: string);
|
|
960
|
+
}
|
|
961
|
+
|
|
951
962
|
declare type View = 'table';
|
|
952
963
|
|
|
953
964
|
export { }
|
|
954
965
|
|
|
955
966
|
|
|
967
|
+
declare global {
|
|
968
|
+
interface HTMLElementEventMap {
|
|
969
|
+
'gs-error': ErrorEvent;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
|
|
956
974
|
declare global {
|
|
957
975
|
interface HTMLElementTagNameMap {
|
|
958
976
|
'gs-app': App;
|
|
@@ -1021,41 +1039,41 @@ declare global {
|
|
|
1021
1039
|
|
|
1022
1040
|
declare global {
|
|
1023
1041
|
interface HTMLElementTagNameMap {
|
|
1024
|
-
'gs-
|
|
1042
|
+
'gs-location-filter': LocationFilterComponent;
|
|
1025
1043
|
}
|
|
1026
1044
|
interface HTMLElementEventMap {
|
|
1027
|
-
'gs-
|
|
1045
|
+
'gs-location-changed': CustomEvent<Record<string, string>>;
|
|
1028
1046
|
}
|
|
1029
1047
|
}
|
|
1030
1048
|
|
|
1031
1049
|
|
|
1032
1050
|
declare global {
|
|
1033
1051
|
interface HTMLElementTagNameMap {
|
|
1034
|
-
'gs-
|
|
1052
|
+
'gs-text-input': TextInputComponent;
|
|
1035
1053
|
}
|
|
1036
1054
|
interface HTMLElementEventMap {
|
|
1037
|
-
'gs-
|
|
1055
|
+
'gs-text-input-changed': CustomEvent<Record<string, string>>;
|
|
1038
1056
|
}
|
|
1039
1057
|
}
|
|
1040
1058
|
|
|
1041
1059
|
|
|
1042
1060
|
declare global {
|
|
1043
1061
|
interface HTMLElementTagNameMap {
|
|
1044
|
-
'gs-
|
|
1062
|
+
'gs-lineage-filter': LineageFilterComponent;
|
|
1045
1063
|
}
|
|
1046
1064
|
interface HTMLElementEventMap {
|
|
1047
|
-
'gs-
|
|
1048
|
-
'gs-mutation-filter-on-blur': CustomEvent<SelectedMutationFilterStrings>;
|
|
1065
|
+
'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
|
|
1049
1066
|
}
|
|
1050
1067
|
}
|
|
1051
1068
|
|
|
1052
1069
|
|
|
1053
1070
|
declare global {
|
|
1054
1071
|
interface HTMLElementTagNameMap {
|
|
1055
|
-
'gs-
|
|
1072
|
+
'gs-mutation-filter': MutationFilterComponent;
|
|
1056
1073
|
}
|
|
1057
1074
|
interface HTMLElementEventMap {
|
|
1058
|
-
'gs-
|
|
1075
|
+
'gs-mutation-filter-changed': CustomEvent<SelectedMutationFilterStrings>;
|
|
1076
|
+
'gs-mutation-filter-on-blur': CustomEvent<SelectedMutationFilterStrings>;
|
|
1059
1077
|
}
|
|
1060
1078
|
}
|
|
1061
1079
|
|
package/dist/style.css
CHANGED
|
@@ -3372,9 +3372,9 @@ input.tab:checked + .tab-content,
|
|
|
3372
3372
|
--tw-text-opacity: 1;
|
|
3373
3373
|
color: rgb(30 64 175 / var(--tw-text-opacity));
|
|
3374
3374
|
}
|
|
3375
|
-
.hover\:text-gray-
|
|
3375
|
+
.hover\:text-gray-400:hover {
|
|
3376
3376
|
--tw-text-opacity: 1;
|
|
3377
|
-
color: rgb(
|
|
3377
|
+
color: rgb(156 163 175 / var(--tw-text-opacity));
|
|
3378
3378
|
}
|
|
3379
3379
|
.hover\:text-gray-700:hover {
|
|
3380
3380
|
--tw-text-opacity: 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genspectrum/dashboard-components",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.15",
|
|
4
4
|
"description": "GenSpectrum web components for building dashboards",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -93,8 +93,8 @@
|
|
|
93
93
|
"@storybook/web-components-vite": "^8.0.9",
|
|
94
94
|
"@types/node": "^22.0.0",
|
|
95
95
|
"@types/object-hash": "^3.0.6",
|
|
96
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
97
|
-
"@typescript-eslint/parser": "^
|
|
96
|
+
"@typescript-eslint/eslint-plugin": "^8.2.0",
|
|
97
|
+
"@typescript-eslint/parser": "^8.2.0",
|
|
98
98
|
"autoprefixer": "^10.4.19",
|
|
99
99
|
"daisyui": "^4.10.2",
|
|
100
100
|
"depcheck": "^1.4.7",
|
package/src/index.ts
CHANGED
package/src/lapisApi/lapisApi.ts
CHANGED
|
@@ -15,6 +15,7 @@ export class UnknownLapisError extends Error {
|
|
|
15
15
|
constructor(
|
|
16
16
|
message: string,
|
|
17
17
|
public readonly status: number,
|
|
18
|
+
public readonly requestedData: string,
|
|
18
19
|
) {
|
|
19
20
|
super(message);
|
|
20
21
|
this.name = 'UnknownLapisError';
|
|
@@ -26,6 +27,7 @@ export class LapisError extends Error {
|
|
|
26
27
|
message: string,
|
|
27
28
|
public readonly status: number,
|
|
28
29
|
public readonly problemDetail: ProblemDetail,
|
|
30
|
+
public readonly requestedData: string,
|
|
29
31
|
) {
|
|
30
32
|
super(message);
|
|
31
33
|
this.name = 'LapisError';
|
|
@@ -42,7 +44,7 @@ export async function fetchAggregated(lapisUrl: string, body: LapisBaseRequest,
|
|
|
42
44
|
signal,
|
|
43
45
|
});
|
|
44
46
|
|
|
45
|
-
await handleErrors(response);
|
|
47
|
+
await handleErrors(response, 'aggregated data');
|
|
46
48
|
|
|
47
49
|
return aggregatedResponse.parse(await response.json());
|
|
48
50
|
}
|
|
@@ -62,7 +64,7 @@ export async function fetchInsertions(
|
|
|
62
64
|
signal,
|
|
63
65
|
});
|
|
64
66
|
|
|
65
|
-
await handleErrors(response);
|
|
67
|
+
await handleErrors(response, `${sequenceType} insertions`);
|
|
66
68
|
|
|
67
69
|
return insertionsResponse.parse(await response.json());
|
|
68
70
|
}
|
|
@@ -82,7 +84,7 @@ export async function fetchSubstitutionsOrDeletions(
|
|
|
82
84
|
signal,
|
|
83
85
|
});
|
|
84
86
|
|
|
85
|
-
await handleErrors(response);
|
|
87
|
+
await handleErrors(response, `${sequenceType} mutations`);
|
|
86
88
|
|
|
87
89
|
return mutationsResponse.parse(await response.json());
|
|
88
90
|
}
|
|
@@ -96,11 +98,11 @@ export async function fetchReferenceGenome(lapisUrl: string, signal?: AbortSigna
|
|
|
96
98
|
signal,
|
|
97
99
|
});
|
|
98
100
|
|
|
99
|
-
await handleErrors(response);
|
|
101
|
+
await handleErrors(response, 'the reference genomes');
|
|
100
102
|
return referenceGenomeResponse.parse(await response.json());
|
|
101
103
|
}
|
|
102
104
|
|
|
103
|
-
const handleErrors = async (response: Response) => {
|
|
105
|
+
const handleErrors = async (response: Response, requestedData: string) => {
|
|
104
106
|
if (!response.ok) {
|
|
105
107
|
if (response.status >= 400 && response.status < 500) {
|
|
106
108
|
const json = await response.json();
|
|
@@ -111,6 +113,7 @@ const handleErrors = async (response: Response) => {
|
|
|
111
113
|
response.statusText + lapisErrorResult.data.error.detail,
|
|
112
114
|
response.status,
|
|
113
115
|
lapisErrorResult.data.error,
|
|
116
|
+
requestedData,
|
|
114
117
|
);
|
|
115
118
|
}
|
|
116
119
|
|
|
@@ -120,12 +123,17 @@ const handleErrors = async (response: Response) => {
|
|
|
120
123
|
response.statusText + problemDetailResult.data.detail,
|
|
121
124
|
response.status,
|
|
122
125
|
problemDetailResult.data,
|
|
126
|
+
requestedData,
|
|
123
127
|
);
|
|
124
128
|
}
|
|
125
129
|
|
|
126
|
-
throw new UnknownLapisError(
|
|
130
|
+
throw new UnknownLapisError(
|
|
131
|
+
`${response.statusText}: ${JSON.stringify(json)}`,
|
|
132
|
+
response.status,
|
|
133
|
+
requestedData,
|
|
134
|
+
);
|
|
127
135
|
}
|
|
128
|
-
throw new UnknownLapisError(`${response.statusText}: ${response.status}`, response.status);
|
|
136
|
+
throw new UnknownLapisError(`${response.statusText}: ${response.status}`, response.status, requestedData);
|
|
129
137
|
}
|
|
130
138
|
};
|
|
131
139
|
|
|
@@ -14,7 +14,7 @@ const meta: Meta<ColorScaleSelectorDropdownProps> = {
|
|
|
14
14
|
|
|
15
15
|
export default meta;
|
|
16
16
|
|
|
17
|
-
const WrapperWithState: FunctionComponent
|
|
17
|
+
const WrapperWithState: FunctionComponent = () => {
|
|
18
18
|
const [colorScale, setColorScale] = useState<ColorScale>({ min: 0, max: 1, color: 'indigo' });
|
|
19
19
|
|
|
20
20
|
return <ColorScaleSelectorDropdown colorScale={colorScale} setColorScale={setColorScale} />;
|
|
@@ -2,11 +2,14 @@ import { type Meta, type StoryObj } from '@storybook/preact';
|
|
|
2
2
|
import { expect, waitFor, within } from '@storybook/test';
|
|
3
3
|
|
|
4
4
|
import { ErrorBoundary } from './error-boundary';
|
|
5
|
+
import { UserFacingError } from './error-display';
|
|
5
6
|
|
|
6
7
|
const meta: Meta = {
|
|
7
8
|
title: 'Component/Error boundary',
|
|
8
9
|
component: ErrorBoundary,
|
|
9
|
-
parameters: {
|
|
10
|
+
parameters: {
|
|
11
|
+
fetchMock: {},
|
|
12
|
+
},
|
|
10
13
|
argTypes: {
|
|
11
14
|
size: { control: 'object' },
|
|
12
15
|
defaultSize: { control: 'object' },
|
|
@@ -34,7 +37,7 @@ export const ErrorBoundaryWithoutErrorStory: StoryObj = {
|
|
|
34
37
|
export const ErrorBoundaryWithErrorStory: StoryObj = {
|
|
35
38
|
render: (args) => (
|
|
36
39
|
<ErrorBoundary size={args.size}>
|
|
37
|
-
<ContentThatThrowsError />
|
|
40
|
+
<ContentThatThrowsError error={() => new Error('Some error')} />
|
|
38
41
|
</ErrorBoundary>
|
|
39
42
|
),
|
|
40
43
|
play: async ({ canvasElement }) => {
|
|
@@ -45,6 +48,20 @@ export const ErrorBoundaryWithErrorStory: StoryObj = {
|
|
|
45
48
|
},
|
|
46
49
|
};
|
|
47
50
|
|
|
48
|
-
const
|
|
49
|
-
|
|
51
|
+
export const ErrorBoundaryWithUserFacingErrorStory: StoryObj = {
|
|
52
|
+
render: (args) => (
|
|
53
|
+
<ErrorBoundary size={args.size}>
|
|
54
|
+
<ContentThatThrowsError error={() => new UserFacingError('Error Headline', 'Some error')} />
|
|
55
|
+
</ErrorBoundary>
|
|
56
|
+
),
|
|
57
|
+
play: async ({ canvasElement }) => {
|
|
58
|
+
const canvas = within(canvasElement);
|
|
59
|
+
const content = canvas.queryByText('Some content.', { exact: false });
|
|
60
|
+
await waitFor(() => expect(content).not.toBeInTheDocument());
|
|
61
|
+
await waitFor(() => expect(canvas.getByText('Error - Error Headline')).toBeInTheDocument());
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const ContentThatThrowsError = (props: { error: () => Error }) => {
|
|
66
|
+
throw props.error();
|
|
50
67
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
-
import { expect, userEvent, waitFor, within } from '@storybook/test';
|
|
2
|
+
import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
|
|
3
3
|
|
|
4
4
|
import { ErrorDisplay, UserFacingError } from './error-display';
|
|
5
5
|
import { ResizeContainer } from './resize-container';
|
|
@@ -12,7 +12,7 @@ const meta: Meta = {
|
|
|
12
12
|
|
|
13
13
|
export default meta;
|
|
14
14
|
|
|
15
|
-
export const
|
|
15
|
+
export const GenericErrorStory: StoryObj = {
|
|
16
16
|
render: () => (
|
|
17
17
|
<ResizeContainer size={{ height: '600px', width: '100%' }}>
|
|
18
18
|
<ErrorDisplay error={new Error('some message')} />
|
|
@@ -48,3 +48,21 @@ export const UserFacingErrorStory: StoryObj = {
|
|
|
48
48
|
});
|
|
49
49
|
},
|
|
50
50
|
};
|
|
51
|
+
|
|
52
|
+
export const FiresEvent: StoryObj = {
|
|
53
|
+
render: () => (
|
|
54
|
+
<ResizeContainer size={{ height: '600px', width: '100%' }}>
|
|
55
|
+
<ErrorDisplay error={new UserFacingError('Error Title', 'some message')} />
|
|
56
|
+
</ResizeContainer>
|
|
57
|
+
),
|
|
58
|
+
|
|
59
|
+
play: async ({ canvasElement }) => {
|
|
60
|
+
const listenerMock = fn();
|
|
61
|
+
canvasElement.addEventListener('gs-error', listenerMock);
|
|
62
|
+
|
|
63
|
+
await waitFor(() => {
|
|
64
|
+
expect(listenerMock.mock.calls.at(-1)[0].error.name).toStrictEqual('UserFacingError');
|
|
65
|
+
expect(listenerMock.mock.calls.at(-1)[0].error.message).toStrictEqual('some message');
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
};
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { type FunctionComponent } from 'preact';
|
|
2
|
-
import { useRef } from 'preact/hooks';
|
|
2
|
+
import { useEffect, useRef } from 'preact/hooks';
|
|
3
|
+
|
|
4
|
+
import { LapisError, UnknownLapisError } from '../../lapisApi/lapisApi';
|
|
5
|
+
|
|
6
|
+
export const GS_ERROR_EVENT_TYPE = 'gs-error';
|
|
7
|
+
|
|
8
|
+
export class ErrorEvent extends Event {
|
|
9
|
+
constructor(public readonly error: Error) {
|
|
10
|
+
super(GS_ERROR_EVENT_TYPE, {
|
|
11
|
+
bubbles: true,
|
|
12
|
+
composed: true,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
}
|
|
3
16
|
|
|
4
17
|
export class UserFacingError extends Error {
|
|
5
18
|
constructor(
|
|
@@ -14,20 +27,27 @@ export class UserFacingError extends Error {
|
|
|
14
27
|
export const ErrorDisplay: FunctionComponent<{ error: Error }> = ({ error }) => {
|
|
15
28
|
console.error(error);
|
|
16
29
|
|
|
30
|
+
const containerRef = useRef<HTMLInputElement>(null);
|
|
17
31
|
const ref = useRef<HTMLDialogElement>(null);
|
|
18
32
|
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
containerRef.current?.dispatchEvent(new ErrorEvent(error));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const { headline, details } = getDisplayedErrorMessage(error);
|
|
38
|
+
|
|
19
39
|
return (
|
|
20
|
-
<div
|
|
21
|
-
|
|
40
|
+
<div
|
|
41
|
+
ref={containerRef}
|
|
42
|
+
className='h-full w-full rounded-md border-2 border-gray-100 p-2 flex items-center justify-center flex-col'
|
|
43
|
+
>
|
|
44
|
+
<div className='text-red-700 font-bold'>{headline}</div>
|
|
22
45
|
<div>
|
|
23
46
|
Oops! Something went wrong.
|
|
24
|
-
{
|
|
47
|
+
{details !== undefined && (
|
|
25
48
|
<>
|
|
26
49
|
{' '}
|
|
27
|
-
<button
|
|
28
|
-
className='text-sm text-gray-600 hover:text-gray-300'
|
|
29
|
-
onClick={() => ref.current?.showModal()}
|
|
30
|
-
>
|
|
50
|
+
<button className='underline hover:text-gray-400' onClick={() => ref.current?.showModal()}>
|
|
31
51
|
Show details.
|
|
32
52
|
</button>
|
|
33
53
|
<dialog ref={ref} class='modal'>
|
|
@@ -37,8 +57,8 @@ export const ErrorDisplay: FunctionComponent<{ error: Error }> = ({ error }) =>
|
|
|
37
57
|
✕
|
|
38
58
|
</button>
|
|
39
59
|
</form>
|
|
40
|
-
<h1 class='text-lg'>{
|
|
41
|
-
<p class='py-4'>{
|
|
60
|
+
<h1 class='text-lg'>{details.headline}</h1>
|
|
61
|
+
<p class='py-4'>{details.message}</p>
|
|
42
62
|
</div>
|
|
43
63
|
<form method='dialog' class='modal-backdrop'>
|
|
44
64
|
<button>close</button>
|
|
@@ -50,3 +70,37 @@ export const ErrorDisplay: FunctionComponent<{ error: Error }> = ({ error }) =>
|
|
|
50
70
|
</div>
|
|
51
71
|
);
|
|
52
72
|
};
|
|
73
|
+
|
|
74
|
+
function getDisplayedErrorMessage(error: Error) {
|
|
75
|
+
if (error instanceof UserFacingError) {
|
|
76
|
+
return {
|
|
77
|
+
headline: `Error - ${error.headline}`,
|
|
78
|
+
details: {
|
|
79
|
+
headline: error.headline,
|
|
80
|
+
message: error.message,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (error instanceof LapisError) {
|
|
86
|
+
return {
|
|
87
|
+
headline: `Error - Failed fetching ${error.requestedData} from LAPIS`,
|
|
88
|
+
details: {
|
|
89
|
+
headline: `LAPIS request failed: ${error.requestedData} - ${error.problemDetail.status} ${error.problemDetail.title}`,
|
|
90
|
+
message: error.problemDetail.detail ?? error.message,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (error instanceof UnknownLapisError) {
|
|
96
|
+
return {
|
|
97
|
+
headline: `Error - Failed fetching ${error.requestedData} from LAPIS`,
|
|
98
|
+
details: {
|
|
99
|
+
headline: `LAPIS request failed: An unexpected error occurred while fetching ${error.requestedData}`,
|
|
100
|
+
message: error.message,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { headline: 'Error', details: undefined };
|
|
106
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
2
|
import { expect, userEvent, waitFor, within } from '@storybook/test';
|
|
3
3
|
|
|
4
|
-
import Info
|
|
4
|
+
import Info from './info';
|
|
5
5
|
|
|
6
|
-
const meta: Meta
|
|
6
|
+
const meta: Meta = {
|
|
7
7
|
title: 'Component/Info',
|
|
8
8
|
component: Info,
|
|
9
9
|
parameters: { fetchMock: {} },
|
|
@@ -13,7 +13,7 @@ export default meta;
|
|
|
13
13
|
|
|
14
14
|
const tooltipText = 'This is a tooltip which shows some information.';
|
|
15
15
|
|
|
16
|
-
export const InfoStory: StoryObj
|
|
16
|
+
export const InfoStory: StoryObj = {
|
|
17
17
|
render: (args) => (
|
|
18
18
|
<div class='flex justify-center px-4 py-16'>
|
|
19
19
|
<Info {...args}>{tooltipText}</Info>
|
|
@@ -21,7 +21,7 @@ export const InfoStory: StoryObj<InfoProps> = {
|
|
|
21
21
|
),
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
export const OpenInfo: StoryObj
|
|
24
|
+
export const OpenInfo: StoryObj = {
|
|
25
25
|
...InfoStory,
|
|
26
26
|
play: async ({ canvasElement }) => {
|
|
27
27
|
const canvas = within(canvasElement);
|
|
@@ -30,7 +30,7 @@ export const OpenInfo: StoryObj<InfoProps> = {
|
|
|
30
30
|
},
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
export const ShowsAndClosesInfoOnClick: StoryObj
|
|
33
|
+
export const ShowsAndClosesInfoOnClick: StoryObj = {
|
|
34
34
|
...InfoStory,
|
|
35
35
|
play: async ({ canvasElement, step }) => {
|
|
36
36
|
const canvas = within(canvasElement);
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { type FunctionComponent } from 'preact';
|
|
2
2
|
import { useRef } from 'preact/hooks';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const Info: FunctionComponent<InfoProps> = ({ children }) => {
|
|
4
|
+
const Info: FunctionComponent = ({ children }) => {
|
|
7
5
|
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
8
6
|
|
|
9
7
|
const toggleHelp = () => {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { withActions } from '@storybook/addon-actions/decorator';
|
|
2
1
|
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
3
2
|
import { expect, waitFor, within } from '@storybook/test';
|
|
4
3
|
import dayjs from 'dayjs/esm';
|
|
@@ -13,6 +12,7 @@ import {
|
|
|
13
12
|
PRESET_VALUE_LAST_6_MONTHS,
|
|
14
13
|
PRESET_VALUE_LAST_MONTH,
|
|
15
14
|
} from './selectableOptions';
|
|
15
|
+
import { previewHandles } from '../../../.storybook/preview';
|
|
16
16
|
import { LAPIS_URL } from '../../constants';
|
|
17
17
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
18
18
|
|
|
@@ -23,7 +23,7 @@ const meta: Meta<DateRangeSelectorProps<'CustomDateRange'>> = {
|
|
|
23
23
|
component: DateRangeSelector,
|
|
24
24
|
parameters: {
|
|
25
25
|
actions: {
|
|
26
|
-
handles: ['gs-date-range-changed'],
|
|
26
|
+
handles: ['gs-date-range-changed', ...previewHandles],
|
|
27
27
|
},
|
|
28
28
|
fetchMock: {},
|
|
29
29
|
},
|
|
@@ -68,7 +68,6 @@ const meta: Meta<DateRangeSelectorProps<'CustomDateRange'>> = {
|
|
|
68
68
|
initialDateFrom: '',
|
|
69
69
|
initialDateTo: '',
|
|
70
70
|
},
|
|
71
|
-
decorators: [withActions],
|
|
72
71
|
};
|
|
73
72
|
|
|
74
73
|
export default meta;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { withActions } from '@storybook/addon-actions/decorator';
|
|
2
1
|
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
3
2
|
|
|
4
3
|
import { LineageFilter, type LineageFilterProps } from './lineage-filter';
|
|
4
|
+
import { previewHandles } from '../../../.storybook/preview';
|
|
5
5
|
import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
|
|
6
6
|
import aggregatedData from '../../preact/lineageFilter/__mockData__/aggregated.json';
|
|
7
7
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
@@ -11,7 +11,7 @@ const meta: Meta = {
|
|
|
11
11
|
component: LineageFilter,
|
|
12
12
|
parameters: {
|
|
13
13
|
actions: {
|
|
14
|
-
handles: ['gs-lineage-filter-changed'],
|
|
14
|
+
handles: ['gs-lineage-filter-changed', ...previewHandles],
|
|
15
15
|
},
|
|
16
16
|
fetchMock: {
|
|
17
17
|
mocks: [
|
|
@@ -31,7 +31,6 @@ const meta: Meta = {
|
|
|
31
31
|
],
|
|
32
32
|
},
|
|
33
33
|
},
|
|
34
|
-
decorators: [withActions],
|
|
35
34
|
};
|
|
36
35
|
|
|
37
36
|
export default meta;
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { LapisError } from '../../lapisApi/lapisApi';
|
|
2
1
|
import { FetchAggregatedOperator } from '../../operator/FetchAggregatedOperator';
|
|
3
|
-
import { UserFacingError } from '../components/error-display';
|
|
4
2
|
|
|
5
3
|
export async function fetchAutocompletionList(fields: string[], lapis: string, signal?: AbortSignal) {
|
|
6
4
|
const toAncestorInHierarchyOverwriteValues = Array(fields.length - 1)
|
|
@@ -10,18 +8,7 @@ export async function fetchAutocompletionList(fields: string[], lapis: string, s
|
|
|
10
8
|
|
|
11
9
|
const fetchAggregatedOperator = new FetchAggregatedOperator<Record<string, string | null>>({}, fields);
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
data = (await fetchAggregatedOperator.evaluate(lapis, signal)).content;
|
|
16
|
-
} catch (error) {
|
|
17
|
-
if (error instanceof LapisError) {
|
|
18
|
-
throw new UserFacingError(
|
|
19
|
-
`Failed to fetch autocomplete list from LAPIS: ${error.problemDetail.status} ${error.problemDetail.title ?? ''}`,
|
|
20
|
-
error.problemDetail.detail ?? error.message,
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
throw error;
|
|
24
|
-
}
|
|
11
|
+
const data = (await fetchAggregatedOperator.evaluate(lapis, signal)).content;
|
|
25
12
|
|
|
26
13
|
const locationValues = data
|
|
27
14
|
.map((entry) => fields.reduce((acc, field) => ({ ...acc, [field]: entry[field] }), {}))
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { withActions } from '@storybook/addon-actions/decorator';
|
|
2
1
|
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
3
2
|
|
|
4
3
|
import data from './__mockData__/aggregated.json';
|
|
5
4
|
import { LocationFilter, type LocationFilterProps } from './location-filter';
|
|
5
|
+
import { previewHandles } from '../../../.storybook/preview';
|
|
6
6
|
import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
|
|
7
7
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
8
8
|
|
|
@@ -28,7 +28,7 @@ const meta: Meta<LocationFilterProps> = {
|
|
|
28
28
|
],
|
|
29
29
|
},
|
|
30
30
|
actions: {
|
|
31
|
-
handles: ['gs-location-changed'],
|
|
31
|
+
handles: ['gs-location-changed', ...previewHandles],
|
|
32
32
|
},
|
|
33
33
|
},
|
|
34
34
|
args: {
|
|
@@ -59,7 +59,6 @@ const meta: Meta<LocationFilterProps> = {
|
|
|
59
59
|
},
|
|
60
60
|
},
|
|
61
61
|
},
|
|
62
|
-
decorators: [withActions],
|
|
63
62
|
};
|
|
64
63
|
|
|
65
64
|
export default meta;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { withActions } from '@storybook/addon-actions/decorator';
|
|
2
1
|
import { type Meta, type PreactRenderer, type StoryObj } from '@storybook/preact';
|
|
3
2
|
import { expect, fireEvent, fn, userEvent, waitFor, within } from '@storybook/test';
|
|
4
3
|
import { type StepFunction } from '@storybook/types';
|
|
5
4
|
|
|
6
5
|
import { MutationFilter, type MutationFilterProps } from './mutation-filter';
|
|
6
|
+
import { previewHandles } from '../../../.storybook/preview';
|
|
7
7
|
import { LAPIS_URL } from '../../constants';
|
|
8
8
|
import referenceGenome from '../../lapisApi/__mockData__/referenceGenome.json';
|
|
9
9
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
@@ -14,7 +14,7 @@ const meta: Meta<MutationFilterProps> = {
|
|
|
14
14
|
component: MutationFilter,
|
|
15
15
|
parameters: {
|
|
16
16
|
actions: {
|
|
17
|
-
handles: ['gs-mutation-filter-changed', 'gs-mutation-filter-on-blur'],
|
|
17
|
+
handles: ['gs-mutation-filter-changed', 'gs-mutation-filter-on-blur', ...previewHandles],
|
|
18
18
|
},
|
|
19
19
|
fetchMock: {},
|
|
20
20
|
},
|
|
@@ -26,7 +26,6 @@ const meta: Meta<MutationFilterProps> = {
|
|
|
26
26
|
},
|
|
27
27
|
},
|
|
28
28
|
},
|
|
29
|
-
decorators: [withActions],
|
|
30
29
|
};
|
|
31
30
|
|
|
32
31
|
export default meta;
|
|
@@ -69,7 +69,7 @@ export const PrevalenceOverTimeInner: FunctionComponent<PrevalenceOverTimeProps>
|
|
|
69
69
|
lapis,
|
|
70
70
|
lapisDateField,
|
|
71
71
|
),
|
|
72
|
-
[lapis, numeratorFilter, denominatorFilter, granularity, smoothingWindow],
|
|
72
|
+
[lapis, numeratorFilter, denominatorFilter, granularity, smoothingWindow, lapisDateField],
|
|
73
73
|
);
|
|
74
74
|
|
|
75
75
|
if (isLoading) {
|
|
@@ -66,7 +66,7 @@ export const RelativeGrowthAdvantageInner: FunctionComponent<RelativeGrowthAdvan
|
|
|
66
66
|
|
|
67
67
|
const { data, error, isLoading } = useQuery(
|
|
68
68
|
() => queryRelativeGrowthAdvantage(numeratorFilter, denominatorFilter, generationTime, lapis, lapisDateField),
|
|
69
|
-
[lapis, numeratorFilter, denominatorFilter, generationTime, views],
|
|
69
|
+
[lapis, numeratorFilter, denominatorFilter, generationTime, views, lapisDateField],
|
|
70
70
|
);
|
|
71
71
|
|
|
72
72
|
if (isLoading) {
|
|
@@ -9,7 +9,7 @@ export function useFloatingUi(
|
|
|
9
9
|
middleware?: Array<Middleware | null | undefined | false>,
|
|
10
10
|
placement?: Placement,
|
|
11
11
|
) {
|
|
12
|
-
const cleanupRef = useRef<
|
|
12
|
+
const cleanupRef = useRef<() => void | null>(null);
|
|
13
13
|
|
|
14
14
|
useEffect(() => {
|
|
15
15
|
if (!referenceRef.current || !floatingRef.current) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { withActions } from '@storybook/addon-actions/decorator';
|
|
2
1
|
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
3
2
|
import { expect, waitFor, within } from '@storybook/test';
|
|
4
3
|
|
|
5
4
|
import data from './__mockData__/aggregated_hosts.json';
|
|
6
5
|
import { TextInput, type TextInputProps } from './text-input';
|
|
6
|
+
import { previewHandles } from '../../../.storybook/preview';
|
|
7
7
|
import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
|
|
8
8
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
9
9
|
|
|
@@ -12,7 +12,7 @@ const meta: Meta<TextInputProps> = {
|
|
|
12
12
|
component: TextInput,
|
|
13
13
|
parameters: {
|
|
14
14
|
actions: {
|
|
15
|
-
handles: ['gs-text-input-changed'],
|
|
15
|
+
handles: ['gs-text-input-changed', ...previewHandles],
|
|
16
16
|
},
|
|
17
17
|
fetchMock: {
|
|
18
18
|
mocks: [
|
|
@@ -32,7 +32,6 @@ const meta: Meta<TextInputProps> = {
|
|
|
32
32
|
],
|
|
33
33
|
},
|
|
34
34
|
},
|
|
35
|
-
decorators: [withActions],
|
|
36
35
|
argTypes: {
|
|
37
36
|
lapisField: {
|
|
38
37
|
control: {
|