@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.
Files changed (35) hide show
  1. package/custom-elements.json +1 -1
  2. package/dist/assets/mutationOverTimeWorker-kjUXkRmn.js.map +1 -0
  3. package/dist/dashboard-components.js +142 -70
  4. package/dist/dashboard-components.js.map +1 -1
  5. package/dist/genspectrum-components.d.ts +2 -2
  6. package/dist/style.css +36 -0
  7. package/package.json +1 -1
  8. package/src/lapisApi/lapisApi.ts +59 -34
  9. package/src/preact/aggregatedData/aggregate.stories.tsx +35 -0
  10. package/src/preact/aggregatedData/aggregate.tsx +1 -2
  11. package/src/preact/components/error-boundary.tsx +9 -4
  12. package/src/preact/components/error-display.stories.tsx +23 -3
  13. package/src/preact/components/error-display.tsx +37 -25
  14. package/src/preact/dateRangeSelector/date-range-selector.tsx +1 -1
  15. package/src/preact/lineageFilter/lineage-filter.tsx +2 -3
  16. package/src/preact/locationFilter/location-filter.tsx +2 -3
  17. package/src/preact/mutationComparison/mutation-comparison.tsx +1 -2
  18. package/src/preact/mutationFilter/mutation-filter.tsx +1 -1
  19. package/src/preact/mutations/mutations.tsx +1 -2
  20. package/src/preact/mutationsOverTime/__mockData__/noDataWhenNoMutationsAreInFilter.ts +22 -0
  21. package/src/preact/mutationsOverTime/__mockData__/noDataWhenThereAreNoDatesInFilter.ts +22 -0
  22. package/src/preact/mutationsOverTime/mutationOverTimeWorker.mock.ts +2 -0
  23. package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +3 -0
  24. package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +71 -1
  25. package/src/preact/mutationsOverTime/mutations-over-time.tsx +10 -5
  26. package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +1 -2
  27. package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +1 -2
  28. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +1 -2
  29. package/src/preact/textInput/text-input.tsx +2 -3
  30. package/src/query/queryMutationsOverTime.spec.ts +30 -0
  31. package/src/query/queryMutationsOverTime.ts +7 -0
  32. package/src/web-components/visualization/gs-mutations-over-time.tsx +2 -2
  33. package/standalone-bundle/dashboard-components.js +4194 -4091
  34. package/standalone-bundle/dashboard-components.js.map +1 -1
  35. 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
- return <ErrorDisplay error={error} />;
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>Info for mutations over time</InfoHeadline1>
236
- <InfoParagraph>TODO: https://github.com/GenSpectrum/dashboard-components/issues/441</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
- return <ErrorDisplay error={error} />;
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
- return <ErrorDisplay error={error} />;
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
- return <ErrorDisplay error={error} />;
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
- return <ErrorDisplay error={error} />;
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
- * Too much data might make the browser unresponsive.
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.