@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
-
import { expect, waitFor } from '@storybook/test';
|
|
2
|
+
import { expect, userEvent, waitFor } from '@storybook/test';
|
|
3
3
|
|
|
4
4
|
import { MutationsOverTime, type MutationsOverTimeProps } from './mutations-over-time';
|
|
5
5
|
import { LAPIS_URL } from '../../constants';
|
|
@@ -85,3 +85,73 @@ export const ShowsMessageWhenTooManyMutations: StoryObj<MutationsOverTimeProps>
|
|
|
85
85
|
});
|
|
86
86
|
},
|
|
87
87
|
};
|
|
88
|
+
|
|
89
|
+
export const ShowsNoDataWhenNoMutationsAreInFilter: StoryObj<MutationsOverTimeProps> = {
|
|
90
|
+
...Template,
|
|
91
|
+
args: {
|
|
92
|
+
lapisFilter: { dateFrom: '1800-01-01', dateTo: '1800-01-02' },
|
|
93
|
+
sequenceType: 'nucleotide',
|
|
94
|
+
views: ['grid'],
|
|
95
|
+
width: '100%',
|
|
96
|
+
height: '700px',
|
|
97
|
+
granularity: 'year',
|
|
98
|
+
lapisDateField: 'date',
|
|
99
|
+
},
|
|
100
|
+
play: async ({ canvas }) => {
|
|
101
|
+
await waitFor(() => expect(canvas.getByText('No data available.', { exact: false })).toBeVisible(), {
|
|
102
|
+
timeout: 10000,
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const ShowsNoDataMessageWhenThereAreNoDatesInFilter: StoryObj<MutationsOverTimeProps> = {
|
|
108
|
+
...Template,
|
|
109
|
+
args: {
|
|
110
|
+
lapisFilter: { dateFrom: '2345-01-01', dateTo: '2020-01-02' },
|
|
111
|
+
sequenceType: 'nucleotide',
|
|
112
|
+
views: ['grid'],
|
|
113
|
+
width: '100%',
|
|
114
|
+
height: '700px',
|
|
115
|
+
granularity: 'year',
|
|
116
|
+
lapisDateField: 'date',
|
|
117
|
+
},
|
|
118
|
+
play: async ({ canvas }) => {
|
|
119
|
+
await waitFor(() => expect(canvas.getByText('No data available.', { exact: false })).toBeVisible(), {
|
|
120
|
+
timeout: 10000,
|
|
121
|
+
});
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export const ShowsNoDataMessageForStrictFilters: StoryObj<MutationsOverTimeProps> = {
|
|
126
|
+
...Template,
|
|
127
|
+
args: {
|
|
128
|
+
lapisFilter: { pangoLineage: 'JN.1*', dateFrom: '2024-01-15', dateTo: '2024-07-10' },
|
|
129
|
+
sequenceType: 'nucleotide',
|
|
130
|
+
views: ['grid'],
|
|
131
|
+
width: '100%',
|
|
132
|
+
height: '700px',
|
|
133
|
+
granularity: 'month',
|
|
134
|
+
lapisDateField: 'date',
|
|
135
|
+
},
|
|
136
|
+
play: async ({ canvas }) => {
|
|
137
|
+
await waitFor(() => expect(canvas.getByText('Grid')).toBeVisible(), { timeout: 10000 });
|
|
138
|
+
|
|
139
|
+
const button = canvas.getByRole('button', { name: 'Mean proportion 5.0% - 90.0%' });
|
|
140
|
+
await userEvent.click(button);
|
|
141
|
+
|
|
142
|
+
const minInput = canvas.getAllByLabelText('%')[0];
|
|
143
|
+
await userEvent.clear(minInput);
|
|
144
|
+
await userEvent.type(minInput, '40');
|
|
145
|
+
|
|
146
|
+
const maxInput = canvas.getAllByLabelText('%')[1];
|
|
147
|
+
await userEvent.clear(maxInput);
|
|
148
|
+
await userEvent.type(maxInput, '41');
|
|
149
|
+
|
|
150
|
+
await waitFor(
|
|
151
|
+
() => expect(canvas.getByText('No data available for your filters.', { exact: false })).toBeVisible(),
|
|
152
|
+
{
|
|
153
|
+
timeout: 10000,
|
|
154
|
+
},
|
|
155
|
+
);
|
|
156
|
+
},
|
|
157
|
+
};
|
|
@@ -21,7 +21,6 @@ import { type ColorScale } from '../components/color-scale-selector';
|
|
|
21
21
|
import { ColorScaleSelectorDropdown } from '../components/color-scale-selector-dropdown';
|
|
22
22
|
import { CsvDownloadButton } from '../components/csv-download-button';
|
|
23
23
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
24
|
-
import { ErrorDisplay } from '../components/error-display';
|
|
25
24
|
import { Fullscreen } from '../components/fullscreen';
|
|
26
25
|
import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../components/info';
|
|
27
26
|
import { LoadingDisplay } from '../components/loading-display';
|
|
@@ -85,10 +84,10 @@ export const MutationsOverTimeInner: FunctionComponent<MutationsOverTimeProps> =
|
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
if (error !== undefined) {
|
|
88
|
-
|
|
87
|
+
throw error;
|
|
89
88
|
}
|
|
90
89
|
|
|
91
|
-
if (data === null || data === undefined) {
|
|
90
|
+
if (data === null || data === undefined || data.overallMutationData.length === 0) {
|
|
92
91
|
return <NoDataDisplay />;
|
|
93
92
|
}
|
|
94
93
|
|
|
@@ -232,8 +231,14 @@ const MutationsOverTimeInfo: FunctionComponent<MutationsOverTimeInfoProps> = ({
|
|
|
232
231
|
const lapis = useContext(LapisUrlContext);
|
|
233
232
|
return (
|
|
234
233
|
<Info>
|
|
235
|
-
<InfoHeadline1>
|
|
236
|
-
<InfoParagraph>
|
|
234
|
+
<InfoHeadline1>Mutations over time</InfoHeadline1>
|
|
235
|
+
<InfoParagraph>
|
|
236
|
+
This presents the proportions of {originalComponentProps.sequenceType} mutations per{' '}
|
|
237
|
+
{originalComponentProps.granularity}. In the toolbar, you can configure which mutations are displayed by
|
|
238
|
+
selecting the mutation type (substitution or deletion), choosing specific segments/genes (if the
|
|
239
|
+
organism has multiple segments/genes), and applying a filter based on the proportion of the mutation's
|
|
240
|
+
occurrence over the entire time range.
|
|
241
|
+
</InfoParagraph>
|
|
237
242
|
<InfoComponentCode componentName='mutations-over-time' params={originalComponentProps} lapisUrl={lapis} />
|
|
238
243
|
</Info>
|
|
239
244
|
);
|
|
@@ -13,7 +13,6 @@ import type { NamedLapisFilter, TemporalGranularity } from '../../types';
|
|
|
13
13
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
14
14
|
import { CsvDownloadButton } from '../components/csv-download-button';
|
|
15
15
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
16
|
-
import { ErrorDisplay } from '../components/error-display';
|
|
17
16
|
import { Fullscreen } from '../components/fullscreen';
|
|
18
17
|
import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../components/info';
|
|
19
18
|
import { LoadingDisplay } from '../components/loading-display';
|
|
@@ -64,7 +63,7 @@ const NumberSequencesOverTimeInner = (componentProps: NumberSequencesOverTimePro
|
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
if (error !== null) {
|
|
67
|
-
|
|
66
|
+
throw error;
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
if (data === null) {
|
|
@@ -12,7 +12,6 @@ import { LapisUrlContext } from '../LapisUrlContext';
|
|
|
12
12
|
import { ConfidenceIntervalSelector } from '../components/confidence-interval-selector';
|
|
13
13
|
import { CsvDownloadButton } from '../components/csv-download-button';
|
|
14
14
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
15
|
-
import { ErrorDisplay } from '../components/error-display';
|
|
16
15
|
import { Fullscreen } from '../components/fullscreen';
|
|
17
16
|
import Info, { InfoComponentCode, InfoHeadline1, InfoHeadline2, InfoParagraph } from '../components/info';
|
|
18
17
|
import { LoadingDisplay } from '../components/loading-display';
|
|
@@ -77,7 +76,7 @@ export const PrevalenceOverTimeInner: FunctionComponent<PrevalenceOverTimeProps>
|
|
|
77
76
|
}
|
|
78
77
|
|
|
79
78
|
if (error !== null) {
|
|
80
|
-
|
|
79
|
+
throw error;
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
if (data === null || data.every((variant) => variant.content.length === 0)) {
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
import { type LapisFilter } from '../../types';
|
|
10
10
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
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, InfoHeadline2, InfoLink, InfoParagraph } from '../components/info';
|
|
15
14
|
import { LoadingDisplay } from '../components/loading-display';
|
|
@@ -63,7 +62,7 @@ export const RelativeGrowthAdvantageInner: FunctionComponent<RelativeGrowthAdvan
|
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
if (error !== null) {
|
|
66
|
-
|
|
65
|
+
throw error;
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
if (data === null) {
|
|
@@ -4,7 +4,6 @@ import { useContext, useRef } from 'preact/hooks';
|
|
|
4
4
|
import { fetchAutocompleteList } from './fetchAutocompleteList';
|
|
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 TextInput: FunctionComponent<TextInputProps> = ({ width, ...innerPr
|
|
|
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
|
<TextInputInner {...innerProps} />
|
|
30
29
|
</ResizeContainer>
|
|
@@ -44,7 +43,7 @@ const TextInputInner: FunctionComponent<TextInputInnerProps> = ({ lapisField, pl
|
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
if (error !== null) {
|
|
47
|
-
|
|
46
|
+
throw error;
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
if (data === null) {
|
|
@@ -719,6 +719,36 @@ describe('queryMutationsOverTime', () => {
|
|
|
719
719
|
expect(dates[1].dateString).toBe('2023-02');
|
|
720
720
|
});
|
|
721
721
|
|
|
722
|
+
it('should return empty data when there are no dates in filter', async () => {
|
|
723
|
+
const lapisFilter = { field1: 'value1', field2: 'value2' };
|
|
724
|
+
const dateField = 'dateField';
|
|
725
|
+
|
|
726
|
+
lapisRequestMocks.multipleAggregated([
|
|
727
|
+
{
|
|
728
|
+
body: { ...lapisFilter, fields: [dateField] },
|
|
729
|
+
response: {
|
|
730
|
+
data: [],
|
|
731
|
+
},
|
|
732
|
+
},
|
|
733
|
+
]);
|
|
734
|
+
|
|
735
|
+
const { mutationOverTimeData } = await queryMutationsOverTimeData({
|
|
736
|
+
lapisFilter,
|
|
737
|
+
sequenceType: 'nucleotide',
|
|
738
|
+
lapis: DUMMY_LAPIS_URL,
|
|
739
|
+
lapisDateField: dateField,
|
|
740
|
+
granularity: 'month',
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
expect(mutationOverTimeData.getAsArray({ count: 0, proportion: 0, totalCount: 0 })).to.deep.equal([]);
|
|
744
|
+
|
|
745
|
+
const sequences = mutationOverTimeData.getFirstAxisKeys();
|
|
746
|
+
expect(sequences.length).toBe(0);
|
|
747
|
+
|
|
748
|
+
const dates = mutationOverTimeData.getSecondAxisKeys();
|
|
749
|
+
expect(dates.length).toBe(0);
|
|
750
|
+
});
|
|
751
|
+
|
|
722
752
|
function getSomeTestMutation(proportion: number, count: number) {
|
|
723
753
|
return {
|
|
724
754
|
mutation: 'sequenceName:A123T',
|
|
@@ -66,6 +66,13 @@ export async function queryOverallMutationData({
|
|
|
66
66
|
signal?: AbortSignal;
|
|
67
67
|
}) {
|
|
68
68
|
const allDates = await getDatesInDataset(lapisFilter, lapis, granularity, lapisDateField, signal);
|
|
69
|
+
|
|
70
|
+
if (allDates.length === 0) {
|
|
71
|
+
return {
|
|
72
|
+
content: [],
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
69
76
|
const filter = {
|
|
70
77
|
...lapisFilter,
|
|
71
78
|
[`${lapisDateField}From`]: allDates[0].firstDay.toString(),
|
|
@@ -18,8 +18,8 @@ import { PreactLitAdapterWithGridJsStyles } from '../PreactLitAdapterWithGridJsS
|
|
|
18
18
|
*
|
|
19
19
|
* The grid view shows the proportion for each mutation over date ranges.
|
|
20
20
|
*
|
|
21
|
-
* The grid limits the number of rows columns for browser performance reasons
|
|
22
|
-
*
|
|
21
|
+
* The grid limits the number of rows and columns for browser performance reasons as
|
|
22
|
+
* too much data might make the browser unresponsive.
|
|
23
23
|
*
|
|
24
24
|
* The number of columns is limited to 200.
|
|
25
25
|
* If this number are exceeded, an error message will be shown.
|