@genspectrum/dashboard-components 0.8.0 → 0.8.2
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 -1
- package/dist/assets/mutationOverTimeWorker-kjUXkRmn.js.map +1 -0
- package/dist/dashboard-components.js +142 -70
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +2 -2
- 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/__mockData__/noDataWhenNoMutationsAreInFilter.ts +22 -0
- package/src/preact/mutationsOverTime/__mockData__/noDataWhenThereAreNoDatesInFilter.ts +22 -0
- package/src/preact/mutationsOverTime/mutationOverTimeWorker.mock.ts +2 -0
- package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +3 -0
- package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +71 -1
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +10 -5
- 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/query/queryMutationsOverTime.spec.ts +30 -0
- package/src/query/queryMutationsOverTime.ts +7 -0
- package/src/web-components/visualization/gs-mutations-over-time.tsx +2 -2
- package/standalone-bundle/dashboard-components.js +4194 -4091
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/dist/assets/mutationOverTimeWorker-BOCXtKzd.js.map +0 -1
|
@@ -547,8 +547,8 @@ export declare class MutationsComponent extends PreactLitAdapterWithGridJsStyles
|
|
|
547
547
|
*
|
|
548
548
|
* The grid view shows the proportion for each mutation over date ranges.
|
|
549
549
|
*
|
|
550
|
-
* The grid limits the number of rows columns for browser performance reasons
|
|
551
|
-
*
|
|
550
|
+
* The grid limits the number of rows and columns for browser performance reasons as
|
|
551
|
+
* too much data might make the browser unresponsive.
|
|
552
552
|
*
|
|
553
553
|
* The number of columns is limited to 200.
|
|
554
554
|
* If this number are exceeded, an error message will be shown.
|
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>
|
|
@@ -4,7 +4,6 @@ import { useContext, useRef } from 'preact/hooks';
|
|
|
4
4
|
import { fetchLineageAutocompleteList } from './fetchLineageAutocompleteList';
|
|
5
5
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
6
6
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
7
|
-
import { ErrorDisplay } from '../components/error-display';
|
|
8
7
|
import { LoadingDisplay } from '../components/loading-display';
|
|
9
8
|
import { NoDataDisplay } from '../components/no-data-display';
|
|
10
9
|
import { ResizeContainer } from '../components/resize-container';
|
|
@@ -24,7 +23,7 @@ export const LineageFilter: FunctionComponent<LineageFilterProps> = ({ width, ..
|
|
|
24
23
|
const size = { width, height: '3rem' };
|
|
25
24
|
|
|
26
25
|
return (
|
|
27
|
-
<ErrorBoundary size={size}>
|
|
26
|
+
<ErrorBoundary size={size} layout='horizontal'>
|
|
28
27
|
<ResizeContainer size={size}>
|
|
29
28
|
<LineageFilterInner {...innerProps} />
|
|
30
29
|
</ResizeContainer>
|
|
@@ -51,7 +50,7 @@ const LineageFilterInner: FunctionComponent<LineageFilterInnerProps> = ({
|
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
if (error !== null) {
|
|
54
|
-
|
|
53
|
+
throw error;
|
|
55
54
|
}
|
|
56
55
|
|
|
57
56
|
if (data === null) {
|
|
@@ -5,7 +5,6 @@ import { type JSXInternal } from 'preact/src/jsx';
|
|
|
5
5
|
import { fetchAutocompletionList } from './fetchAutocompletionList';
|
|
6
6
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
7
7
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
8
|
-
import { ErrorDisplay } from '../components/error-display';
|
|
9
8
|
import { LoadingDisplay } from '../components/loading-display';
|
|
10
9
|
import { ResizeContainer } from '../components/resize-container';
|
|
11
10
|
import { useQuery } from '../useQuery';
|
|
@@ -24,7 +23,7 @@ export const LocationFilter: FunctionComponent<LocationFilterProps> = ({ width,
|
|
|
24
23
|
const size = { width, height: '3rem' };
|
|
25
24
|
|
|
26
25
|
return (
|
|
27
|
-
<ErrorBoundary size={size}>
|
|
26
|
+
<ErrorBoundary size={size} layout='horizontal'>
|
|
28
27
|
<ResizeContainer size={size}>
|
|
29
28
|
<LocationFilterInner {...innerProps} />
|
|
30
29
|
</ResizeContainer>
|
|
@@ -46,7 +45,7 @@ export const LocationFilterInner = ({ initialValue, fields, placeholderText }: L
|
|
|
46
45
|
return <LoadingDisplay />;
|
|
47
46
|
}
|
|
48
47
|
if (error) {
|
|
49
|
-
|
|
48
|
+
throw error;
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
const onInput = (event: JSXInternal.TargetedInputEvent<HTMLInputElement>) => {
|
|
@@ -9,7 +9,6 @@ import { type NamedLapisFilter, type SequenceType } from '../../types';
|
|
|
9
9
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
10
10
|
import { CsvDownloadButton } from '../components/csv-download-button';
|
|
11
11
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
12
|
-
import { ErrorDisplay } from '../components/error-display';
|
|
13
12
|
import { Fullscreen } from '../components/fullscreen';
|
|
14
13
|
import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../components/info';
|
|
15
14
|
import { LoadingDisplay } from '../components/loading-display';
|
|
@@ -59,7 +58,7 @@ export const MutationComparisonInner: FunctionComponent<MutationComparisonProps>
|
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
if (error !== null) {
|
|
62
|
-
|
|
61
|
+
throw error;
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
if (data === null) {
|
|
@@ -35,7 +35,7 @@ export type SelectedMutationFilterStrings = {
|
|
|
35
35
|
|
|
36
36
|
export const MutationFilter: FunctionComponent<MutationFilterProps> = ({ initialValue, width }) => {
|
|
37
37
|
return (
|
|
38
|
-
<ErrorBoundary size={{ height: '3.375rem', width }}>
|
|
38
|
+
<ErrorBoundary size={{ height: '3.375rem', width }} layout='horizontal'>
|
|
39
39
|
<div style={width}>
|
|
40
40
|
<MutationFilterInner initialValue={initialValue} />
|
|
41
41
|
</div>
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
17
17
|
import { CsvDownloadButton } from '../components/csv-download-button';
|
|
18
18
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
19
|
-
import { ErrorDisplay } from '../components/error-display';
|
|
20
19
|
import { Fullscreen } from '../components/fullscreen';
|
|
21
20
|
import Info, { InfoComponentCode, InfoHeadline1, InfoHeadline2, InfoLink, InfoParagraph } from '../components/info';
|
|
22
21
|
import { LoadingDisplay } from '../components/loading-display';
|
|
@@ -66,7 +65,7 @@ export const MutationsInner: FunctionComponent<MutationsProps> = (componentProps
|
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
if (error !== null) {
|
|
69
|
-
|
|
68
|
+
throw error;
|
|
70
69
|
}
|
|
71
70
|
|
|
72
71
|
if (data === null) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { MutationOverTimeMockData } from './mockConversion';
|
|
2
|
+
|
|
3
|
+
export const noDataWhenNoMutationsAreInFilter: MutationOverTimeMockData = {
|
|
4
|
+
query: {
|
|
5
|
+
lapisFilter: {
|
|
6
|
+
dateFrom: '1800-01-01',
|
|
7
|
+
dateTo: '1800-01-02',
|
|
8
|
+
},
|
|
9
|
+
sequenceType: 'nucleotide',
|
|
10
|
+
granularity: 'year',
|
|
11
|
+
lapisDateField: 'date',
|
|
12
|
+
lapis: 'https://lapis.cov-spectrum.org/open/v2',
|
|
13
|
+
},
|
|
14
|
+
response: {
|
|
15
|
+
overallMutationData: [],
|
|
16
|
+
mutationOverTimeSerializedAsArray: {
|
|
17
|
+
keysFirstAxis: [],
|
|
18
|
+
keysSecondAxis: [],
|
|
19
|
+
data: [],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { MutationOverTimeMockData } from './mockConversion';
|
|
2
|
+
|
|
3
|
+
export const noDataWhenNoMutationsAreInFilter: MutationOverTimeMockData = {
|
|
4
|
+
query: {
|
|
5
|
+
lapisFilter: {
|
|
6
|
+
dateFrom: '2345-01-01',
|
|
7
|
+
dateTo: '2020-01-02',
|
|
8
|
+
},
|
|
9
|
+
sequenceType: 'nucleotide',
|
|
10
|
+
granularity: 'year',
|
|
11
|
+
lapisDateField: 'date',
|
|
12
|
+
lapis: 'https://lapis.cov-spectrum.org/open/v2',
|
|
13
|
+
},
|
|
14
|
+
response: {
|
|
15
|
+
overallMutationData: [],
|
|
16
|
+
mutationOverTimeSerializedAsArray: {
|
|
17
|
+
keysFirstAxis: [],
|
|
18
|
+
keysSecondAxis: [],
|
|
19
|
+
data: [],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -5,6 +5,7 @@ import { workerFunction } from '../webWorkers/workerFunction';
|
|
|
5
5
|
import { byWeek } from './__mockData__/byWeek';
|
|
6
6
|
import { defaultMockData } from './__mockData__/defaultMockData';
|
|
7
7
|
import { getMutationOverTimeMock } from './__mockData__/mockConversion';
|
|
8
|
+
import { noDataWhenNoMutationsAreInFilter } from './__mockData__/noDataWhenNoMutationsAreInFilter';
|
|
8
9
|
import { showsMessageWhenTooManyMutations } from './__mockData__/showsMessageWhenTooManyMutations';
|
|
9
10
|
|
|
10
11
|
const mockQueries: { query: MutationOverTimeQuery; response: MutationOverTimeWorkerResponse }[] = [
|
|
@@ -12,6 +13,7 @@ const mockQueries: { query: MutationOverTimeQuery; response: MutationOverTimeWor
|
|
|
12
13
|
getMutationOverTimeMock(showsMessageWhenTooManyMutations),
|
|
13
14
|
getMutationOverTimeMock(byWeek),
|
|
14
15
|
getMutationOverTimeMock(aminoAcidMutationsByDay),
|
|
16
|
+
getMutationOverTimeMock(noDataWhenNoMutationsAreInFilter),
|
|
15
17
|
];
|
|
16
18
|
|
|
17
19
|
self.onmessage = async function (event: MessageEvent<MutationOverTimeQuery>) {
|
|
@@ -35,6 +35,9 @@ const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
|
|
|
35
35
|
reduce the number of mutations.
|
|
36
36
|
</div>
|
|
37
37
|
)}
|
|
38
|
+
{allMutations.length === 0 && (
|
|
39
|
+
<div className={'flex justify-center'}>No data available for your filters.</div>
|
|
40
|
+
)}
|
|
38
41
|
<div
|
|
39
42
|
ref={gridRef}
|
|
40
43
|
style={{
|