@genspectrum/dashboard-components 0.8.1 → 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.
@@ -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
- * Too much data might make the browser unresponsive.
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.
@@ -1029,10 +1029,7 @@ declare global {
1029
1029
 
1030
1030
  declare global {
1031
1031
  interface HTMLElementTagNameMap {
1032
- 'gs-location-filter': LocationFilterComponent;
1033
- }
1034
- interface HTMLElementEventMap {
1035
- 'gs-location-changed': CustomEvent<Record<string, string>>;
1032
+ 'gs-mutation-comparison-component': MutationComparisonComponent;
1036
1033
  }
1037
1034
  }
1038
1035
 
@@ -1040,7 +1037,7 @@ declare global {
1040
1037
  declare global {
1041
1038
  namespace JSX {
1042
1039
  interface IntrinsicElements {
1043
- 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1040
+ 'gs-mutation-comparison-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1044
1041
  }
1045
1042
  }
1046
1043
  }
@@ -1048,10 +1045,7 @@ declare global {
1048
1045
 
1049
1046
  declare global {
1050
1047
  interface HTMLElementTagNameMap {
1051
- 'gs-date-range-selector': DateRangeSelectorComponent;
1052
- }
1053
- interface HTMLElementEventMap {
1054
- 'gs-date-range-changed': CustomEvent<Record<string, string>>;
1048
+ 'gs-mutations-component': MutationsComponent;
1055
1049
  }
1056
1050
  }
1057
1051
 
@@ -1059,7 +1053,7 @@ declare global {
1059
1053
  declare global {
1060
1054
  namespace JSX {
1061
1055
  interface IntrinsicElements {
1062
- 'gs-date-range-selector': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1056
+ 'gs-mutations-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1063
1057
  }
1064
1058
  }
1065
1059
  }
@@ -1067,10 +1061,7 @@ declare global {
1067
1061
 
1068
1062
  declare global {
1069
1063
  interface HTMLElementTagNameMap {
1070
- 'gs-text-input': TextInputComponent;
1071
- }
1072
- interface HTMLElementEventMap {
1073
- 'gs-text-input-changed': CustomEvent<Record<string, string>>;
1064
+ 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
1074
1065
  }
1075
1066
  }
1076
1067
 
@@ -1078,7 +1069,7 @@ declare global {
1078
1069
  declare global {
1079
1070
  namespace JSX {
1080
1071
  interface IntrinsicElements {
1081
- 'gs-text-input': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1072
+ 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1082
1073
  }
1083
1074
  }
1084
1075
  }
@@ -1086,10 +1077,7 @@ declare global {
1086
1077
 
1087
1078
  declare global {
1088
1079
  interface HTMLElementTagNameMap {
1089
- 'gs-mutation-filter': MutationFilterComponent;
1090
- }
1091
- interface HTMLElementEventMap {
1092
- 'gs-mutation-filter-changed': CustomEvent<SelectedMutationFilterStrings>;
1080
+ 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
1093
1081
  }
1094
1082
  }
1095
1083
 
@@ -1097,7 +1085,7 @@ declare global {
1097
1085
  declare global {
1098
1086
  namespace JSX {
1099
1087
  interface IntrinsicElements {
1100
- 'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1088
+ 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1101
1089
  }
1102
1090
  }
1103
1091
  }
@@ -1105,10 +1093,7 @@ declare global {
1105
1093
 
1106
1094
  declare global {
1107
1095
  interface HTMLElementTagNameMap {
1108
- 'gs-lineage-filter': LineageFilterComponent;
1109
- }
1110
- interface HTMLElementEventMap {
1111
- 'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
1096
+ 'gs-aggregate-component': AggregateComponent;
1112
1097
  }
1113
1098
  }
1114
1099
 
@@ -1116,7 +1101,7 @@ declare global {
1116
1101
  declare global {
1117
1102
  namespace JSX {
1118
1103
  interface IntrinsicElements {
1119
- 'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1104
+ 'gs-aggregate-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1120
1105
  }
1121
1106
  }
1122
1107
  }
@@ -1124,7 +1109,7 @@ declare global {
1124
1109
 
1125
1110
  declare global {
1126
1111
  interface HTMLElementTagNameMap {
1127
- 'gs-mutation-comparison-component': MutationComparisonComponent;
1112
+ 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1128
1113
  }
1129
1114
  }
1130
1115
 
@@ -1132,7 +1117,7 @@ declare global {
1132
1117
  declare global {
1133
1118
  namespace JSX {
1134
1119
  interface IntrinsicElements {
1135
- 'gs-mutation-comparison-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1120
+ 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1136
1121
  }
1137
1122
  }
1138
1123
  }
@@ -1140,7 +1125,7 @@ declare global {
1140
1125
 
1141
1126
  declare global {
1142
1127
  interface HTMLElementTagNameMap {
1143
- 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
1128
+ 'gs-mutations-over-time': MutationsOverTimeComponent;
1144
1129
  }
1145
1130
  }
1146
1131
 
@@ -1148,7 +1133,7 @@ declare global {
1148
1133
  declare global {
1149
1134
  namespace JSX {
1150
1135
  interface IntrinsicElements {
1151
- 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1136
+ 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1152
1137
  }
1153
1138
  }
1154
1139
  }
@@ -1156,7 +1141,10 @@ declare global {
1156
1141
 
1157
1142
  declare global {
1158
1143
  interface HTMLElementTagNameMap {
1159
- 'gs-mutations-component': MutationsComponent;
1144
+ 'gs-date-range-selector': DateRangeSelectorComponent;
1145
+ }
1146
+ interface HTMLElementEventMap {
1147
+ 'gs-date-range-changed': CustomEvent<Record<string, string>>;
1160
1148
  }
1161
1149
  }
1162
1150
 
@@ -1164,7 +1152,7 @@ declare global {
1164
1152
  declare global {
1165
1153
  namespace JSX {
1166
1154
  interface IntrinsicElements {
1167
- 'gs-mutations-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1155
+ 'gs-date-range-selector': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1168
1156
  }
1169
1157
  }
1170
1158
  }
@@ -1172,7 +1160,10 @@ declare global {
1172
1160
 
1173
1161
  declare global {
1174
1162
  interface HTMLElementTagNameMap {
1175
- 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
1163
+ 'gs-location-filter': LocationFilterComponent;
1164
+ }
1165
+ interface HTMLElementEventMap {
1166
+ 'gs-location-changed': CustomEvent<Record<string, string>>;
1176
1167
  }
1177
1168
  }
1178
1169
 
@@ -1180,7 +1171,7 @@ declare global {
1180
1171
  declare global {
1181
1172
  namespace JSX {
1182
1173
  interface IntrinsicElements {
1183
- 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1174
+ 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1184
1175
  }
1185
1176
  }
1186
1177
  }
@@ -1188,7 +1179,10 @@ declare global {
1188
1179
 
1189
1180
  declare global {
1190
1181
  interface HTMLElementTagNameMap {
1191
- 'gs-aggregate-component': AggregateComponent;
1182
+ 'gs-text-input': TextInputComponent;
1183
+ }
1184
+ interface HTMLElementEventMap {
1185
+ 'gs-text-input-changed': CustomEvent<Record<string, string>>;
1192
1186
  }
1193
1187
  }
1194
1188
 
@@ -1196,7 +1190,7 @@ declare global {
1196
1190
  declare global {
1197
1191
  namespace JSX {
1198
1192
  interface IntrinsicElements {
1199
- 'gs-aggregate-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1193
+ 'gs-text-input': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1200
1194
  }
1201
1195
  }
1202
1196
  }
@@ -1204,7 +1198,10 @@ declare global {
1204
1198
 
1205
1199
  declare global {
1206
1200
  interface HTMLElementTagNameMap {
1207
- 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1201
+ 'gs-mutation-filter': MutationFilterComponent;
1202
+ }
1203
+ interface HTMLElementEventMap {
1204
+ 'gs-mutation-filter-changed': CustomEvent<SelectedMutationFilterStrings>;
1208
1205
  }
1209
1206
  }
1210
1207
 
@@ -1212,7 +1209,7 @@ declare global {
1212
1209
  declare global {
1213
1210
  namespace JSX {
1214
1211
  interface IntrinsicElements {
1215
- 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1212
+ 'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1216
1213
  }
1217
1214
  }
1218
1215
  }
@@ -1220,7 +1217,10 @@ declare global {
1220
1217
 
1221
1218
  declare global {
1222
1219
  interface HTMLElementTagNameMap {
1223
- 'gs-mutations-over-time': MutationsOverTimeComponent;
1220
+ 'gs-lineage-filter': LineageFilterComponent;
1221
+ }
1222
+ interface HTMLElementEventMap {
1223
+ 'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
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-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1231
+ 'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1232
1232
  }
1233
1233
  }
1234
1234
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genspectrum/dashboard-components",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "GenSpectrum web components for building dashboards",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -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={{
@@ -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
+ };
@@ -87,7 +87,7 @@ export const MutationsOverTimeInner: FunctionComponent<MutationsOverTimeProps> =
87
87
  throw error;
88
88
  }
89
89
 
90
- if (data === null || data === undefined) {
90
+ if (data === null || data === undefined || data.overallMutationData.length === 0) {
91
91
  return <NoDataDisplay />;
92
92
  }
93
93
 
@@ -231,8 +231,14 @@ const MutationsOverTimeInfo: FunctionComponent<MutationsOverTimeInfoProps> = ({
231
231
  const lapis = useContext(LapisUrlContext);
232
232
  return (
233
233
  <Info>
234
- <InfoHeadline1>Info for mutations over time</InfoHeadline1>
235
- <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>
236
242
  <InfoComponentCode componentName='mutations-over-time' params={originalComponentProps} lapisUrl={lapis} />
237
243
  </Info>
238
244
  );
@@ -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.