@genspectrum/dashboard-components 0.3.1 → 0.4.0

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 (37) hide show
  1. package/custom-elements.json +86 -29
  2. package/dist/dashboard-components.js +391 -207
  3. package/dist/dashboard-components.js.map +1 -1
  4. package/dist/genspectrum-components.d.ts +37 -12
  5. package/dist/style.css +58 -50
  6. package/package.json +2 -1
  7. package/src/operator/RenameFieldOperator.spec.ts +28 -0
  8. package/src/operator/RenameFieldOperator.ts +19 -0
  9. package/src/preact/aggregatedData/aggregate.tsx +1 -1
  10. package/src/preact/components/info.stories.tsx +8 -8
  11. package/src/preact/components/info.tsx +113 -20
  12. package/src/preact/dateRangeSelector/computeInitialValues.spec.ts +99 -0
  13. package/src/preact/dateRangeSelector/computeInitialValues.ts +73 -0
  14. package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +93 -4
  15. package/src/preact/dateRangeSelector/date-range-selector.tsx +27 -88
  16. package/src/preact/dateRangeSelector/selectableOptions.ts +79 -0
  17. package/src/preact/locationFilter/location-filter.tsx +1 -1
  18. package/src/preact/mutationComparison/mutation-comparison.tsx +1 -1
  19. package/src/preact/mutationFilter/mutation-filter.stories.tsx +3 -6
  20. package/src/preact/mutationFilter/mutation-filter.tsx +48 -54
  21. package/src/preact/mutations/mutations.tsx +1 -1
  22. package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +3 -0
  23. package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +7 -3
  24. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +2 -0
  25. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +6 -2
  26. package/src/query/queryPrevalenceOverTime.ts +13 -7
  27. package/src/query/queryRelativeGrowthAdvantage.ts +11 -7
  28. package/src/web-components/input/gs-date-range-selector.stories.ts +11 -5
  29. package/src/web-components/input/gs-date-range-selector.tsx +22 -5
  30. package/src/web-components/input/gs-location-filter.stories.ts +6 -7
  31. package/src/web-components/input/gs-location-filter.tsx +3 -2
  32. package/src/web-components/input/gs-mutation-filter.stories.ts +1 -8
  33. package/src/web-components/input/gs-mutation-filter.tsx +1 -9
  34. package/src/web-components/visualization/gs-prevalence-over-time.stories.ts +6 -2
  35. package/src/web-components/visualization/gs-prevalence-over-time.tsx +11 -0
  36. package/src/web-components/visualization/gs-relative-growth-advantage.stories.ts +3 -0
  37. package/src/web-components/visualization/gs-relative-growth-advantage.tsx +11 -0
@@ -0,0 +1,99 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { computeInitialValues } from './computeInitialValues';
4
+ import { PRESET_VALUE_CUSTOM, PRESET_VALUE_LAST_3_MONTHS, PRESET_VALUE_LAST_6_MONTHS } from './selectableOptions';
5
+
6
+ const today = new Date();
7
+ const earliestDate = '1900-01-01';
8
+
9
+ describe('computeInitialValues', () => {
10
+ it('should compute for initial value if initial "from" and "to" are unset', () => {
11
+ const result = computeInitialValues(PRESET_VALUE_LAST_3_MONTHS, undefined, undefined, earliestDate, []);
12
+
13
+ const expectedFrom = new Date();
14
+ expectedFrom.setMonth(today.getMonth() - 3);
15
+
16
+ expect(result.initialSelectedDateRange).toEqual(PRESET_VALUE_LAST_3_MONTHS);
17
+ expectDateMatches(result.initialSelectedDateFrom, expectedFrom);
18
+ expectDateMatches(result.initialSelectedDateTo, today);
19
+ });
20
+
21
+ it('should fall back to default when initial value is unknown', () => {
22
+ const result = computeInitialValues('not a known value', undefined, undefined, earliestDate, []);
23
+
24
+ const expectedFrom = new Date();
25
+ expectedFrom.setMonth(today.getMonth() - 6);
26
+
27
+ expect(result.initialSelectedDateRange).toEqual(PRESET_VALUE_LAST_6_MONTHS);
28
+ expectDateMatches(result.initialSelectedDateFrom, expectedFrom);
29
+ expectDateMatches(result.initialSelectedDateTo, today);
30
+ });
31
+
32
+ it('should overwrite initial value if initial "from" is set', () => {
33
+ const initialDateFrom = '2020-01-01';
34
+ const result = computeInitialValues(PRESET_VALUE_LAST_3_MONTHS, initialDateFrom, undefined, earliestDate, []);
35
+
36
+ expect(result.initialSelectedDateRange).toEqual(PRESET_VALUE_CUSTOM);
37
+ expectDateMatches(result.initialSelectedDateFrom, new Date(initialDateFrom));
38
+ expectDateMatches(result.initialSelectedDateTo, today);
39
+ });
40
+
41
+ it('should overwrite initial value if initial "to" is set', () => {
42
+ const initialDateTo = '2020-01-01';
43
+ const result = computeInitialValues(PRESET_VALUE_LAST_3_MONTHS, undefined, initialDateTo, earliestDate, []);
44
+
45
+ expect(result.initialSelectedDateRange).toEqual(PRESET_VALUE_CUSTOM);
46
+ expectDateMatches(result.initialSelectedDateFrom, new Date(earliestDate));
47
+ expectDateMatches(result.initialSelectedDateTo, new Date(initialDateTo));
48
+ });
49
+
50
+ it('should overwrite initial value if initial "to" and "from" are set', () => {
51
+ const initialDateFrom = '2020-01-01';
52
+ const initialDateTo = '2022-01-01';
53
+ const result = computeInitialValues(
54
+ PRESET_VALUE_LAST_3_MONTHS,
55
+ initialDateFrom,
56
+ initialDateTo,
57
+ earliestDate,
58
+ [],
59
+ );
60
+
61
+ expect(result.initialSelectedDateRange).toEqual(PRESET_VALUE_CUSTOM);
62
+ expectDateMatches(result.initialSelectedDateFrom, new Date(initialDateFrom));
63
+ expectDateMatches(result.initialSelectedDateTo, new Date(initialDateTo));
64
+ });
65
+
66
+ it('should set initial "to" to "from" if "from" is after "to"', () => {
67
+ const initialDateFrom = '2020-01-01';
68
+ const initialDateTo = '1900-01-01';
69
+ const result = computeInitialValues(
70
+ PRESET_VALUE_LAST_3_MONTHS,
71
+ initialDateFrom,
72
+ initialDateTo,
73
+ earliestDate,
74
+ [],
75
+ );
76
+
77
+ expect(result.initialSelectedDateRange).toEqual(PRESET_VALUE_CUSTOM);
78
+ expectDateMatches(result.initialSelectedDateFrom, new Date(initialDateFrom));
79
+ expectDateMatches(result.initialSelectedDateTo, new Date(initialDateFrom));
80
+ });
81
+
82
+ it('should throw if initial "from" is not a valid date', () => {
83
+ expect(() =>
84
+ computeInitialValues(PRESET_VALUE_LAST_3_MONTHS, 'not a date', undefined, earliestDate, []),
85
+ ).toThrowError('Invalid initialDateFrom');
86
+ });
87
+
88
+ it('should throw if initial "to" is not a valid date', () => {
89
+ expect(() =>
90
+ computeInitialValues(PRESET_VALUE_LAST_3_MONTHS, undefined, 'not a date', earliestDate, []),
91
+ ).toThrowError('Invalid initialDateTo');
92
+ });
93
+
94
+ function expectDateMatches(actual: Date, expected: Date) {
95
+ expect(actual.getFullYear()).toEqual(expected.getFullYear());
96
+ expect(actual.getMonth()).toEqual(expected.getMonth());
97
+ expect(actual.getDate()).toEqual(expected.getDate());
98
+ }
99
+ });
@@ -0,0 +1,73 @@
1
+ import {
2
+ type CustomSelectOption,
3
+ getDatesForSelectorValue,
4
+ getSelectableOptions,
5
+ PRESET_VALUE_CUSTOM,
6
+ PRESET_VALUE_LAST_6_MONTHS,
7
+ type PresetOptionValues,
8
+ } from './selectableOptions';
9
+ import { UserFacingError } from '../components/error-display';
10
+
11
+ export function computeInitialValues<CustomLabel extends string>(
12
+ initialValue: PresetOptionValues | CustomLabel | undefined,
13
+ initialDateFrom: string | undefined,
14
+ initialDateTo: string | undefined,
15
+ earliestDate: string,
16
+ customSelectOptions: CustomSelectOption<CustomLabel>[],
17
+ ): {
18
+ initialSelectedDateRange: CustomLabel | PresetOptionValues;
19
+ initialSelectedDateFrom: Date;
20
+ initialSelectedDateTo: Date;
21
+ } {
22
+ if (isUndefinedOrEmpty(initialDateFrom) && isUndefinedOrEmpty(initialDateTo)) {
23
+ const selectableOptions = getSelectableOptions(customSelectOptions);
24
+ const initialSelectedDateRange =
25
+ initialValue !== undefined && selectableOptions.some((option) => option.value === initialValue)
26
+ ? initialValue
27
+ : PRESET_VALUE_LAST_6_MONTHS;
28
+
29
+ const { dateFrom, dateTo } = getDatesForSelectorValue(
30
+ initialSelectedDateRange,
31
+ customSelectOptions,
32
+ earliestDate,
33
+ );
34
+
35
+ return {
36
+ initialSelectedDateRange,
37
+ initialSelectedDateFrom: dateFrom,
38
+ initialSelectedDateTo: dateTo,
39
+ };
40
+ }
41
+
42
+ const initialSelectedDateFrom = isUndefinedOrEmpty(initialDateFrom)
43
+ ? new Date(earliestDate)
44
+ : new Date(initialDateFrom);
45
+ let initialSelectedDateTo = isUndefinedOrEmpty(initialDateTo) ? new Date() : new Date(initialDateTo);
46
+
47
+ if (isNaN(initialSelectedDateFrom.getTime())) {
48
+ throw new UserFacingError(
49
+ 'Invalid initialDateFrom',
50
+ `Invalid initialDateFrom "${initialDateFrom}", It must be of the format YYYY-MM-DD`,
51
+ );
52
+ }
53
+ if (isNaN(initialSelectedDateTo.getTime())) {
54
+ throw new UserFacingError(
55
+ 'Invalid initialDateTo',
56
+ `Invalid initialDateTo "${initialDateTo}", It must be of the format YYYY-MM-DD`,
57
+ );
58
+ }
59
+
60
+ if (initialSelectedDateFrom > initialSelectedDateTo) {
61
+ initialSelectedDateTo = initialSelectedDateFrom;
62
+ }
63
+
64
+ return {
65
+ initialSelectedDateRange: PRESET_VALUE_CUSTOM,
66
+ initialSelectedDateFrom,
67
+ initialSelectedDateTo,
68
+ };
69
+ }
70
+
71
+ function isUndefinedOrEmpty(value: string | undefined): value is undefined | '' {
72
+ return value === undefined || value === '';
73
+ }
@@ -1,9 +1,10 @@
1
1
  import { withActions } from '@storybook/addon-actions/decorator';
2
2
  import { type Meta, type StoryObj } from '@storybook/preact';
3
+ import { expect, waitFor, within } from '@storybook/test';
4
+ import dayjs from 'dayjs/esm';
3
5
 
6
+ import { DateRangeSelector, type DateRangeSelectorProps } from './date-range-selector';
4
7
  import {
5
- DateRangeSelector,
6
- type DateRangeSelectorProps,
7
8
  PRESET_VALUE_ALL_TIMES,
8
9
  PRESET_VALUE_CUSTOM,
9
10
  PRESET_VALUE_LAST_2_MONTHS,
@@ -11,10 +12,12 @@ import {
11
12
  PRESET_VALUE_LAST_3_MONTHS,
12
13
  PRESET_VALUE_LAST_6_MONTHS,
13
14
  PRESET_VALUE_LAST_MONTH,
14
- } from './date-range-selector';
15
+ } from './selectableOptions';
15
16
  import { LAPIS_URL } from '../../constants';
16
17
  import { LapisUrlContext } from '../LapisUrlContext';
17
18
 
19
+ const earliestDate = '1970-01-01';
20
+
18
21
  const meta: Meta<DateRangeSelectorProps<'CustomDateRange'>> = {
19
22
  title: 'Input/DateRangeSelector',
20
23
  component: DateRangeSelector,
@@ -58,10 +61,12 @@ const meta: Meta<DateRangeSelectorProps<'CustomDateRange'>> = {
58
61
  },
59
62
  args: {
60
63
  customSelectOptions: [{ label: 'CustomDateRange', dateFrom: '2021-01-01', dateTo: '2021-12-31' }],
61
- earliestDate: '1970-01-01',
64
+ earliestDate,
62
65
  initialValue: PRESET_VALUE_LAST_3_MONTHS,
63
66
  dateColumn: 'aDateColumn',
64
67
  width: '100%',
68
+ initialDateFrom: '',
69
+ initialDateTo: '',
65
70
  },
66
71
  decorators: [withActions],
67
72
  };
@@ -75,9 +80,93 @@ export const Primary: StoryObj<DateRangeSelectorProps<'CustomDateRange'>> = {
75
80
  customSelectOptions={args.customSelectOptions}
76
81
  earliestDate={args.earliestDate}
77
82
  initialValue={args.initialValue}
83
+ initialDateFrom={args.initialDateFrom}
84
+ initialDateTo={args.initialDateTo}
78
85
  width={args.width}
79
86
  dateColumn={args.dateColumn}
80
87
  />
81
88
  </LapisUrlContext.Provider>
82
89
  ),
83
90
  };
91
+
92
+ export const SetCorrectInitialValues: StoryObj<DateRangeSelectorProps<'CustomDateRange'>> = {
93
+ ...Primary,
94
+ args: {
95
+ ...Primary.args,
96
+ initialValue: 'CustomDateRange',
97
+ },
98
+ play: async ({ canvasElement }) => {
99
+ const canvas = within(canvasElement);
100
+
101
+ const dateFrom = () => canvas.getByPlaceholderText('Date from');
102
+ const dateTo = () => canvas.getByPlaceholderText('Date to');
103
+ const selectField = () => canvas.getByRole('combobox');
104
+
105
+ await waitFor(() => {
106
+ expect(selectField()).toHaveValue('CustomDateRange');
107
+ expect(dateFrom()).toHaveValue('2021-01-01');
108
+ expect(dateTo()).toHaveValue('2021-12-31');
109
+ });
110
+ },
111
+ };
112
+
113
+ const initialDateFrom = '2000-01-01';
114
+
115
+ export const SetCorrectInitialDateFrom: StoryObj<DateRangeSelectorProps<'CustomDateRange'>> = {
116
+ ...Primary,
117
+ args: {
118
+ ...Primary.args,
119
+ initialDateFrom,
120
+ },
121
+ play: async ({ canvasElement }) => {
122
+ const canvas = within(canvasElement);
123
+
124
+ const dateFrom = () => canvas.getByPlaceholderText('Date from');
125
+ const dateTo = () => canvas.getByPlaceholderText('Date to');
126
+ const selectField = () => canvas.getByRole('combobox');
127
+
128
+ await waitFor(() => {
129
+ expect(selectField()).toHaveValue(PRESET_VALUE_CUSTOM);
130
+ expect(dateFrom()).toHaveValue(initialDateFrom);
131
+ expect(dateTo()).toHaveValue(dayjs().format('YYYY-MM-DD'));
132
+ });
133
+ },
134
+ };
135
+
136
+ const initialDateTo = '2000-01-01';
137
+
138
+ export const SetCorrectInitialDateTo: StoryObj<DateRangeSelectorProps<'CustomDateRange'>> = {
139
+ ...Primary,
140
+ args: {
141
+ ...Primary.args,
142
+ initialDateTo,
143
+ },
144
+ play: async ({ canvasElement }) => {
145
+ const canvas = within(canvasElement);
146
+
147
+ const dateFrom = () => canvas.getByPlaceholderText('Date from');
148
+ const dateTo = () => canvas.getByPlaceholderText('Date to');
149
+ const selectField = () => canvas.getByRole('combobox');
150
+
151
+ await waitFor(() => {
152
+ expect(selectField()).toHaveValue(PRESET_VALUE_CUSTOM);
153
+ expect(dateFrom()).toHaveValue(earliestDate);
154
+ expect(dateTo()).toHaveValue(initialDateTo);
155
+ });
156
+ },
157
+ };
158
+
159
+ export const HandlesInvalidInitialDateFrom: StoryObj<DateRangeSelectorProps<'CustomDateRange'>> = {
160
+ ...Primary,
161
+ args: {
162
+ ...Primary.args,
163
+ initialDateFrom: 'not a date',
164
+ },
165
+ play: async ({ canvasElement }) => {
166
+ const canvas = within(canvasElement);
167
+
168
+ await waitFor(() => {
169
+ expect(canvas.getByText('Oops! Something went wrong.')).toBeVisible();
170
+ });
171
+ },
172
+ };
@@ -2,14 +2,19 @@ import flatpickr from 'flatpickr';
2
2
  import 'flatpickr/dist/flatpickr.min.css';
3
3
  import { useEffect, useRef, useState } from 'preact/hooks';
4
4
 
5
+ import { computeInitialValues } from './computeInitialValues';
5
6
  import { toYYYYMMDD } from './dateConversion';
7
+ import {
8
+ type CustomSelectOption,
9
+ getDatesForSelectorValue,
10
+ getSelectableOptions,
11
+ type PresetOptionValues,
12
+ } from './selectableOptions';
6
13
  import { ErrorBoundary } from '../components/error-boundary';
7
14
  import { ResizeContainer } from '../components/resize-container';
8
15
  import { Select } from '../components/select';
9
16
  import type { ScaleType } from '../shared/charts/getYAxisScale';
10
17
 
11
- export type CustomSelectOption<CustomLabel extends string> = { label: CustomLabel; dateFrom: string; dateTo: string };
12
-
13
18
  export interface DateRangeSelectorProps<CustomLabel extends string> extends DateRangeSelectorPropsInner<CustomLabel> {
14
19
  width: string;
15
20
  }
@@ -18,35 +23,19 @@ export interface DateRangeSelectorPropsInner<CustomLabel extends string> {
18
23
  customSelectOptions: CustomSelectOption<CustomLabel>[];
19
24
  earliestDate?: string;
20
25
  initialValue?: PresetOptionValues | CustomLabel;
26
+ initialDateFrom?: string;
27
+ initialDateTo?: string;
21
28
  dateColumn: string;
22
29
  }
23
30
 
24
- export const PRESET_VALUE_CUSTOM = 'custom';
25
- export const PRESET_VALUE_ALL_TIMES = 'allTimes';
26
- export const PRESET_VALUE_LAST_2_WEEKS = 'last2Weeks';
27
- export const PRESET_VALUE_LAST_MONTH = 'lastMonth';
28
- export const PRESET_VALUE_LAST_2_MONTHS = 'last2Months';
29
- export const PRESET_VALUE_LAST_3_MONTHS = 'last3Months';
30
- export const PRESET_VALUE_LAST_6_MONTHS = 'last6Months';
31
-
32
- export const presets = {
33
- [PRESET_VALUE_CUSTOM]: { label: 'Custom' },
34
- [PRESET_VALUE_ALL_TIMES]: { label: 'All times' },
35
- [PRESET_VALUE_LAST_2_WEEKS]: { label: 'Last 2 weeks' },
36
- [PRESET_VALUE_LAST_MONTH]: { label: 'Last month' },
37
- [PRESET_VALUE_LAST_2_MONTHS]: { label: 'Last 2 months' },
38
- [PRESET_VALUE_LAST_3_MONTHS]: { label: 'Last 3 months' },
39
- [PRESET_VALUE_LAST_6_MONTHS]: { label: 'Last 6 months' },
40
- };
41
-
42
- export type PresetOptionValues = keyof typeof presets;
43
-
44
31
  export const DateRangeSelector = <CustomLabel extends string>({
45
32
  customSelectOptions,
46
33
  earliestDate = '1900-01-01',
47
34
  initialValue,
48
35
  width,
49
36
  dateColumn,
37
+ initialDateFrom,
38
+ initialDateTo,
50
39
  }: DateRangeSelectorProps<CustomLabel>) => {
51
40
  const size = { width, height: '3rem' };
52
41
 
@@ -58,6 +47,8 @@ export const DateRangeSelector = <CustomLabel extends string>({
58
47
  earliestDate={earliestDate}
59
48
  initialValue={initialValue}
60
49
  dateColumn={dateColumn}
50
+ initialDateFrom={initialDateFrom}
51
+ initialDateTo={initialDateTo}
61
52
  />
62
53
  </ResizeContainer>
63
54
  </ErrorBoundary>
@@ -69,24 +60,30 @@ export const DateRangeSelectorInner = <CustomLabel extends string>({
69
60
  earliestDate = '1900-01-01',
70
61
  initialValue,
71
62
  dateColumn,
63
+ initialDateFrom,
64
+ initialDateTo,
72
65
  }: DateRangeSelectorPropsInner<CustomLabel>) => {
66
+ const initialValues = computeInitialValues(
67
+ initialValue,
68
+ initialDateFrom,
69
+ initialDateTo,
70
+ earliestDate,
71
+ customSelectOptions,
72
+ );
73
+
73
74
  const fromDatePickerRef = useRef<HTMLInputElement>(null);
74
75
  const toDatePickerRef = useRef<HTMLInputElement>(null);
75
76
  const divRef = useRef<HTMLDivElement>(null);
76
77
  const [dateFromPicker, setDateFromPicker] = useState<flatpickr.Instance | null>(null);
77
78
  const [dateToPicker, setDateToPicker] = useState<flatpickr.Instance | null>(null);
78
79
 
79
- const selectableOptions = getSelectableOptions(customSelectOptions);
80
-
81
80
  const [selectedDateRange, setSelectedDateRange] = useState<CustomLabel | PresetOptionValues>(
82
- initialValue !== undefined && selectableOptions.some((option) => option.value === initialValue)
83
- ? initialValue
84
- : PRESET_VALUE_LAST_6_MONTHS,
81
+ initialValues.initialSelectedDateRange,
85
82
  );
86
83
 
87
84
  const [selectedDates, setSelectedDates] = useState<{ dateFrom: Date; dateTo: Date }>({
88
- dateFrom: getDatesForSelectorValue('last6Months', customSelectOptions, earliestDate).dateFrom,
89
- dateTo: getDatesForSelectorValue('last6Months', customSelectOptions, earliestDate).dateTo,
85
+ dateFrom: initialValues.initialSelectedDateFrom,
86
+ dateTo: initialValues.initialSelectedDateTo,
90
87
  });
91
88
 
92
89
  useEffect(() => {
@@ -184,7 +181,7 @@ export const DateRangeSelectorInner = <CustomLabel extends string>({
184
181
  return (
185
182
  <div class='join w-full' ref={divRef}>
186
183
  <Select
187
- items={selectableOptions}
184
+ items={getSelectableOptions(customSelectOptions)}
188
185
  selected={selectedDateRange}
189
186
  selectStyle='select-bordered rounded-none join-item grow'
190
187
  onChange={(event: Event) => {
@@ -213,61 +210,3 @@ export const DateRangeSelectorInner = <CustomLabel extends string>({
213
210
  </div>
214
211
  );
215
212
  };
216
-
217
- const getSelectableOptions = <Label extends string>(customSelectOptions: CustomSelectOption<Label>[]) => {
218
- const presetOptions = Object.entries(presets).map(([key, value]) => {
219
- return { label: value.label, value: key };
220
- });
221
-
222
- const customOptions = customSelectOptions.map((customSelectOption) => {
223
- return { label: customSelectOption.label, value: customSelectOption.label };
224
- });
225
-
226
- return [...presetOptions, ...customOptions];
227
- };
228
-
229
- const getDatesForSelectorValue = <Label extends string>(
230
- selectorValue: string,
231
- customSelectOptions: CustomSelectOption<Label>[],
232
- earliestDate: string,
233
- ) => {
234
- const today = new Date();
235
-
236
- const customSelectOption = customSelectOptions.find((option) => option.label === selectorValue);
237
- if (customSelectOption) {
238
- return { dateFrom: new Date(customSelectOption.dateFrom), dateTo: new Date(customSelectOption.dateTo) };
239
- }
240
-
241
- switch (selectorValue) {
242
- case PRESET_VALUE_LAST_2_WEEKS: {
243
- const twoWeeksAgo = new Date(today);
244
- twoWeeksAgo.setDate(today.getDate() - 14);
245
- return { dateFrom: twoWeeksAgo, dateTo: today };
246
- }
247
- case PRESET_VALUE_LAST_MONTH: {
248
- const lastMonth = new Date(today);
249
- lastMonth.setMonth(today.getMonth() - 1);
250
- return { dateFrom: lastMonth, dateTo: today };
251
- }
252
- case PRESET_VALUE_LAST_2_MONTHS: {
253
- const twoMonthsAgo = new Date(today);
254
- twoMonthsAgo.setMonth(today.getMonth() - 2);
255
- return { dateFrom: twoMonthsAgo, dateTo: today };
256
- }
257
- case PRESET_VALUE_LAST_3_MONTHS: {
258
- const threeMonthsAgo = new Date(today);
259
- threeMonthsAgo.setMonth(today.getMonth() - 3);
260
- return { dateFrom: threeMonthsAgo, dateTo: today };
261
- }
262
- case PRESET_VALUE_LAST_6_MONTHS: {
263
- const sixMonthsAgo = new Date(today);
264
- sixMonthsAgo.setMonth(today.getMonth() - 6);
265
- return { dateFrom: sixMonthsAgo, dateTo: today };
266
- }
267
- case PRESET_VALUE_ALL_TIMES: {
268
- return { dateFrom: new Date(earliestDate), dateTo: today };
269
- }
270
- default:
271
- return { dateFrom: today, dateTo: today };
272
- }
273
- };
@@ -0,0 +1,79 @@
1
+ export const PRESET_VALUE_CUSTOM = 'custom';
2
+ export const PRESET_VALUE_ALL_TIMES = 'allTimes';
3
+ export const PRESET_VALUE_LAST_2_WEEKS = 'last2Weeks';
4
+ export const PRESET_VALUE_LAST_MONTH = 'lastMonth';
5
+ export const PRESET_VALUE_LAST_2_MONTHS = 'last2Months';
6
+ export const PRESET_VALUE_LAST_3_MONTHS = 'last3Months';
7
+ export const PRESET_VALUE_LAST_6_MONTHS = 'last6Months';
8
+
9
+ export const presets = {
10
+ [PRESET_VALUE_CUSTOM]: { label: 'Custom' },
11
+ [PRESET_VALUE_ALL_TIMES]: { label: 'All times' },
12
+ [PRESET_VALUE_LAST_2_WEEKS]: { label: 'Last 2 weeks' },
13
+ [PRESET_VALUE_LAST_MONTH]: { label: 'Last month' },
14
+ [PRESET_VALUE_LAST_2_MONTHS]: { label: 'Last 2 months' },
15
+ [PRESET_VALUE_LAST_3_MONTHS]: { label: 'Last 3 months' },
16
+ [PRESET_VALUE_LAST_6_MONTHS]: { label: 'Last 6 months' },
17
+ };
18
+
19
+ export type PresetOptionValues = keyof typeof presets;
20
+
21
+ export type CustomSelectOption<CustomLabel extends string> = { label: CustomLabel; dateFrom: string; dateTo: string };
22
+
23
+ export const getSelectableOptions = <Label extends string>(customSelectOptions: CustomSelectOption<Label>[]) => {
24
+ const presetOptions = Object.entries(presets).map(([key, value]) => {
25
+ return { label: value.label, value: key };
26
+ });
27
+
28
+ const customOptions = customSelectOptions.map((customSelectOption) => {
29
+ return { label: customSelectOption.label, value: customSelectOption.label };
30
+ });
31
+
32
+ return [...presetOptions, ...customOptions];
33
+ };
34
+
35
+ export const getDatesForSelectorValue = <Label extends string>(
36
+ selectorValue: string,
37
+ customSelectOptions: CustomSelectOption<Label>[],
38
+ earliestDate: string,
39
+ ) => {
40
+ const today = new Date();
41
+
42
+ const customSelectOption = customSelectOptions.find((option) => option.label === selectorValue);
43
+ if (customSelectOption) {
44
+ return { dateFrom: new Date(customSelectOption.dateFrom), dateTo: new Date(customSelectOption.dateTo) };
45
+ }
46
+
47
+ switch (selectorValue) {
48
+ case PRESET_VALUE_LAST_2_WEEKS: {
49
+ const twoWeeksAgo = new Date(today);
50
+ twoWeeksAgo.setDate(today.getDate() - 14);
51
+ return { dateFrom: twoWeeksAgo, dateTo: today };
52
+ }
53
+ case PRESET_VALUE_LAST_MONTH: {
54
+ const lastMonth = new Date(today);
55
+ lastMonth.setMonth(today.getMonth() - 1);
56
+ return { dateFrom: lastMonth, dateTo: today };
57
+ }
58
+ case PRESET_VALUE_LAST_2_MONTHS: {
59
+ const twoMonthsAgo = new Date(today);
60
+ twoMonthsAgo.setMonth(today.getMonth() - 2);
61
+ return { dateFrom: twoMonthsAgo, dateTo: today };
62
+ }
63
+ case PRESET_VALUE_LAST_3_MONTHS: {
64
+ const threeMonthsAgo = new Date(today);
65
+ threeMonthsAgo.setMonth(today.getMonth() - 3);
66
+ return { dateFrom: threeMonthsAgo, dateTo: today };
67
+ }
68
+ case PRESET_VALUE_LAST_6_MONTHS: {
69
+ const sixMonthsAgo = new Date(today);
70
+ sixMonthsAgo.setMonth(today.getMonth() - 6);
71
+ return { dateFrom: sixMonthsAgo, dateTo: today };
72
+ }
73
+ case PRESET_VALUE_ALL_TIMES: {
74
+ return { dateFrom: new Date(earliestDate), dateTo: today };
75
+ }
76
+ default:
77
+ return { dateFrom: today, dateTo: today };
78
+ }
79
+ };
@@ -93,7 +93,7 @@ export const LocationFilterInner = ({ initialValue, fields }: LocationFilterInne
93
93
 
94
94
  const parseLocation = (location: string, fields: string[]) => {
95
95
  const fieldValues = location.split('/').map((part) => part.trim());
96
- return fieldValues.reduce((acc, fieldValue, i) => ({ ...acc, [fields[i]]: fieldValue }), {});
96
+ return fields.reduce((acc, field, i) => ({ ...acc, [field]: fieldValues[i] }), {});
97
97
  };
98
98
 
99
99
  const hasMatchingEntry = (data: Record<string, string>[] | null, eventDetail: Record<string, string>) => {
@@ -183,7 +183,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
183
183
  getData={() => getMutationComparisonTableData({ content: filteredData }, proportionInterval)}
184
184
  filename='mutation_comparison.csv'
185
185
  />
186
- <Info>Info for mutation comparison</Info>
186
+ <Info height={'100px'}>Info for mutation comparison</Info>
187
187
  </div>
188
188
  );
189
189
  };
@@ -20,7 +20,6 @@ const meta: Meta<MutationFilterProps> = {
20
20
  },
21
21
  argTypes: {
22
22
  width: { control: 'text' },
23
- height: { control: 'text' },
24
23
  initialValue: {
25
24
  control: {
26
25
  type: 'object',
@@ -36,13 +35,12 @@ export const Default: StoryObj<MutationFilterProps> = {
36
35
  render: (args) => (
37
36
  <LapisUrlContext.Provider value={LAPIS_URL}>
38
37
  <ReferenceGenomeContext.Provider value={referenceGenome}>
39
- <MutationFilter width={args.width} height={args.height} initialValue={args.initialValue} />
38
+ <MutationFilter width={args.width} initialValue={args.initialValue} />
40
39
  </ReferenceGenomeContext.Provider>
41
40
  </LapisUrlContext.Provider>
42
41
  ),
43
42
  args: {
44
43
  width: '100%',
45
- height: '700px',
46
44
  },
47
45
  };
48
46
 
@@ -106,7 +104,7 @@ export const FiresFilterChangedEvents: StoryObj<MutationFilterProps> = {
106
104
  });
107
105
 
108
106
  await step('Remove the first mutation', async () => {
109
- const firstMutationDeleteButton = canvas.getAllByRole('button')[0];
107
+ const firstMutationDeleteButton = canvas.getAllByRole('button')[1];
110
108
  await waitFor(() => fireEvent.click(firstMutationDeleteButton));
111
109
 
112
110
  await expect(changedListenerMock).toHaveBeenCalledWith(
@@ -153,7 +151,7 @@ export const WithInitialValue: StoryObj<MutationFilterProps> = {
153
151
  render: (args) => (
154
152
  <LapisUrlContext.Provider value={LAPIS_URL}>
155
153
  <ReferenceGenomeContext.Provider value={referenceGenome}>
156
- <MutationFilter initialValue={args.initialValue} width={args.width} height={args.height} />
154
+ <MutationFilter initialValue={args.initialValue} width={args.width} />
157
155
  </ReferenceGenomeContext.Provider>
158
156
  </LapisUrlContext.Provider>
159
157
  ),
@@ -165,7 +163,6 @@ export const WithInitialValue: StoryObj<MutationFilterProps> = {
165
163
  aminoAcidInsertions: ['ins_S:123:AAA'],
166
164
  },
167
165
  width: '100%',
168
- height: '700px',
169
166
  },
170
167
  play: async ({ canvasElement, step }) => {
171
168
  const { canvas, onBlurListenerMock } = await prepare(canvasElement, step);