@genspectrum/dashboard-components 0.13.7 → 0.14.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 +24 -62
- package/dist/{LineageFilterChangedEvent-GedKNGFI.js → LineageFilterChangedEvent-C9dXOxt6.js} +11 -3
- package/dist/LineageFilterChangedEvent-C9dXOxt6.js.map +1 -0
- package/dist/assets/mutationOverTimeWorker-Dxnxrfe0.js.map +1 -1
- package/dist/components.d.ts +40 -50
- package/dist/components.js +112 -91
- package/dist/components.js.map +1 -1
- package/dist/util.d.ts +38 -28
- package/dist/util.js +1 -1
- package/package.json +2 -2
- package/src/lapisApi/lapisApi.ts +1 -1
- package/src/operator/FillMissingOperator.spec.ts +1 -1
- package/src/operator/GroupByAndSumOperator.spec.ts +1 -1
- package/src/operator/GroupByOperator.spec.ts +2 -2
- package/src/operator/MapOperator.spec.ts +1 -1
- package/src/operator/MockOperator.spec.ts +1 -1
- package/src/operator/MockOperator.ts +6 -4
- package/src/operator/SortOperator.spec.ts +1 -1
- package/src/preact/LapisUrlContext.ts +14 -1
- package/src/preact/aggregatedData/aggregate.stories.tsx +4 -4
- package/src/preact/aggregatedData/aggregate.tsx +3 -4
- package/src/preact/components/csv-download-button.stories.tsx +2 -2
- package/src/preact/components/csv-download-button.tsx +1 -1
- package/src/preact/components/error-boundary.stories.tsx +5 -5
- package/src/preact/components/error-boundary.tsx +14 -3
- package/src/preact/components/error-display.stories.tsx +9 -9
- package/src/preact/components/fullscreen.tsx +3 -3
- package/src/preact/components/info.tsx +1 -1
- package/src/preact/components/mutation-type-selector.stories.tsx +1 -1
- package/src/preact/components/table.stories.tsx +3 -3
- package/src/preact/components/table.tsx +1 -1
- package/src/preact/dateRangeSelector/computeInitialValues.spec.ts +34 -20
- package/src/preact/dateRangeSelector/computeInitialValues.ts +25 -21
- package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +107 -46
- package/src/preact/dateRangeSelector/date-range-selector.tsx +31 -22
- package/src/preact/dateRangeSelector/dateRangeOption.ts +11 -1
- package/src/preact/lineageFilter/lineage-filter.stories.tsx +9 -9
- package/src/preact/lineageFilter/lineage-filter.tsx +3 -4
- package/src/preact/locationFilter/fetchAutocompletionList.ts +1 -1
- package/src/preact/locationFilter/location-filter.stories.tsx +9 -9
- package/src/preact/locationFilter/location-filter.tsx +4 -4
- package/src/preact/map/sequences-by-location.stories.tsx +4 -4
- package/src/preact/map/sequences-by-location.tsx +3 -4
- package/src/preact/mutationComparison/mutation-comparison.stories.tsx +3 -3
- package/src/preact/mutationComparison/mutation-comparison.tsx +4 -4
- package/src/preact/mutationFilter/mutation-filter-info.tsx +3 -3
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +7 -7
- package/src/preact/mutations/getMutationsGridData.ts +1 -1
- package/src/preact/mutations/mutations.stories.tsx +3 -3
- package/src/preact/mutations/mutations.tsx +4 -4
- package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +3 -3
- package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +4 -4
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +5 -4
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.stories.tsx +4 -4
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +4 -4
- package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +4 -4
- package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +4 -4
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +4 -4
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +4 -4
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +4 -4
- package/src/preact/shared/floating-ui/hooks.ts +1 -1
- package/src/preact/statistic/statistics.stories.tsx +3 -3
- package/src/preact/statistic/statistics.tsx +2 -3
- package/src/preact/textInput/text-input.stories.tsx +7 -7
- package/src/preact/textInput/text-input.tsx +3 -4
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.stories.tsx +3 -3
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +4 -4
- package/src/utils/map2d.ts +1 -0
- package/src/web-components/PreactLitAdapter.tsx +3 -3
- package/src/web-components/gs-app.stories.ts +7 -7
- package/src/web-components/gs-app.ts +3 -1
- package/src/web-components/input/gs-date-range-selector.stories.ts +10 -17
- package/src/web-components/input/gs-date-range-selector.tsx +15 -38
- package/src/web-components/input/gs-lineage-filter.stories.ts +1 -1
- package/src/web-components/input/gs-location-filter.stories.ts +1 -1
- package/src/web-components/input/gs-mutation-filter.stories.ts +7 -7
- package/src/web-components/input/gs-text-input.stories.ts +3 -3
- package/src/web-components/visualization/gs-aggregate.tsx +2 -2
- package/standalone-bundle/assets/mutationOverTimeWorker-CmSrq4SZ.js.map +1 -1
- package/standalone-bundle/dashboard-components.js +6068 -6055
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/dist/LineageFilterChangedEvent-GedKNGFI.js.map +0 -1
|
@@ -18,8 +18,8 @@ const dateRangeOptions = [
|
|
|
18
18
|
];
|
|
19
19
|
|
|
20
20
|
describe('computeInitialValues', () => {
|
|
21
|
-
it('should compute
|
|
22
|
-
const result = computeInitialValues(fromToOption,
|
|
21
|
+
it('should compute initial value if value is dateRangeOption label', () => {
|
|
22
|
+
const result = computeInitialValues(fromToOption, earliestDate, dateRangeOptions);
|
|
23
23
|
|
|
24
24
|
expect(result.initialSelectedDateRange).toEqual(fromToOption);
|
|
25
25
|
expectDateMatches(result.initialSelectedDateFrom, new Date(dateFromOptionValue));
|
|
@@ -27,7 +27,7 @@ describe('computeInitialValues', () => {
|
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
it('should use today as "dateTo" if it is unset in selected option', () => {
|
|
30
|
-
const result = computeInitialValues(fromOption,
|
|
30
|
+
const result = computeInitialValues(fromOption, earliestDate, dateRangeOptions);
|
|
31
31
|
|
|
32
32
|
expect(result.initialSelectedDateRange).toEqual(fromOption);
|
|
33
33
|
expectDateMatches(result.initialSelectedDateFrom, new Date(dateFromOptionValue));
|
|
@@ -35,7 +35,7 @@ describe('computeInitialValues', () => {
|
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
it('should use earliest date as "dateFrom" if it is unset in selected option', () => {
|
|
38
|
-
const result = computeInitialValues(toOption,
|
|
38
|
+
const result = computeInitialValues(toOption, earliestDate, dateRangeOptions);
|
|
39
39
|
|
|
40
40
|
expect(result.initialSelectedDateRange).toEqual(toOption);
|
|
41
41
|
expectDateMatches(result.initialSelectedDateFrom, new Date(earliestDate));
|
|
@@ -43,7 +43,7 @@ describe('computeInitialValues', () => {
|
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
it('should fall back to full range if initial value is not set', () => {
|
|
46
|
-
const result = computeInitialValues(undefined,
|
|
46
|
+
const result = computeInitialValues(undefined, earliestDate, dateRangeOptions);
|
|
47
47
|
|
|
48
48
|
expect(result.initialSelectedDateRange).toBeUndefined();
|
|
49
49
|
expectDateMatches(result.initialSelectedDateFrom, new Date(earliestDate));
|
|
@@ -51,39 +51,46 @@ describe('computeInitialValues', () => {
|
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
it('should throw when initial value is unknown', () => {
|
|
54
|
-
expect(() =>
|
|
55
|
-
|
|
56
|
-
)
|
|
54
|
+
expect(() => computeInitialValues('not a known value', earliestDate, dateRangeOptions)).toThrowError(
|
|
55
|
+
/Invalid value "not a known value", It must be one of/,
|
|
56
|
+
);
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
it('should throw when initial value is set but no options are provided', () => {
|
|
60
|
-
expect(() => computeInitialValues('not a known value',
|
|
60
|
+
expect(() => computeInitialValues('not a known value', earliestDate, [])).toThrowError(
|
|
61
61
|
/There are no selectable options/,
|
|
62
62
|
);
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
-
it('should
|
|
65
|
+
it('should select from date until today if only dateFrom is given', () => {
|
|
66
66
|
const initialDateFrom = '2020-01-01';
|
|
67
|
-
const result = computeInitialValues(
|
|
67
|
+
const result = computeInitialValues({ dateFrom: initialDateFrom }, earliestDate, dateRangeOptions);
|
|
68
68
|
|
|
69
69
|
expect(result.initialSelectedDateRange).toBeUndefined();
|
|
70
70
|
expectDateMatches(result.initialSelectedDateFrom, new Date(initialDateFrom));
|
|
71
71
|
expectDateMatches(result.initialSelectedDateTo, today);
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
it('should
|
|
74
|
+
it('should select from earliest date until date if only dateTo is given', () => {
|
|
75
75
|
const initialDateTo = '2020-01-01';
|
|
76
|
-
const result = computeInitialValues(
|
|
76
|
+
const result = computeInitialValues({ dateTo: initialDateTo }, earliestDate, dateRangeOptions);
|
|
77
77
|
|
|
78
78
|
expect(result.initialSelectedDateRange).toBeUndefined();
|
|
79
79
|
expectDateMatches(result.initialSelectedDateFrom, new Date(earliestDate));
|
|
80
80
|
expectDateMatches(result.initialSelectedDateTo, new Date(initialDateTo));
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
it('should
|
|
83
|
+
it('should select date range is dateFrom and dateTo are given', () => {
|
|
84
84
|
const initialDateFrom = '2020-01-01';
|
|
85
85
|
const initialDateTo = '2022-01-01';
|
|
86
|
-
const result = computeInitialValues(
|
|
86
|
+
const result = computeInitialValues(
|
|
87
|
+
{
|
|
88
|
+
dateFrom: initialDateFrom,
|
|
89
|
+
dateTo: initialDateTo,
|
|
90
|
+
},
|
|
91
|
+
earliestDate,
|
|
92
|
+
dateRangeOptions,
|
|
93
|
+
);
|
|
87
94
|
|
|
88
95
|
expect(result.initialSelectedDateRange).toBeUndefined();
|
|
89
96
|
expectDateMatches(result.initialSelectedDateFrom, new Date(initialDateFrom));
|
|
@@ -93,7 +100,14 @@ describe('computeInitialValues', () => {
|
|
|
93
100
|
it('should set initial "to" to "from" if "from" is after "to"', () => {
|
|
94
101
|
const initialDateFrom = '2020-01-01';
|
|
95
102
|
const initialDateTo = '1900-01-01';
|
|
96
|
-
const result = computeInitialValues(
|
|
103
|
+
const result = computeInitialValues(
|
|
104
|
+
{
|
|
105
|
+
dateFrom: initialDateFrom,
|
|
106
|
+
dateTo: initialDateTo,
|
|
107
|
+
},
|
|
108
|
+
earliestDate,
|
|
109
|
+
dateRangeOptions,
|
|
110
|
+
);
|
|
97
111
|
|
|
98
112
|
expect(result.initialSelectedDateRange).toBeUndefined();
|
|
99
113
|
expectDateMatches(result.initialSelectedDateFrom, new Date(initialDateFrom));
|
|
@@ -101,14 +115,14 @@ describe('computeInitialValues', () => {
|
|
|
101
115
|
});
|
|
102
116
|
|
|
103
117
|
it('should throw if initial "from" is not a valid date', () => {
|
|
104
|
-
expect(() => computeInitialValues(
|
|
105
|
-
'Invalid
|
|
118
|
+
expect(() => computeInitialValues({ dateFrom: 'not a date' }, earliestDate, [])).toThrowError(
|
|
119
|
+
'Invalid value.dateFrom',
|
|
106
120
|
);
|
|
107
121
|
});
|
|
108
122
|
|
|
109
123
|
it('should throw if initial "to" is not a valid date', () => {
|
|
110
|
-
expect(() => computeInitialValues(
|
|
111
|
-
'Invalid
|
|
124
|
+
expect(() => computeInitialValues({ dateTo: 'not a date' }, earliestDate, [])).toThrowError(
|
|
125
|
+
'Invalid value.dateTo',
|
|
112
126
|
);
|
|
113
127
|
});
|
|
114
128
|
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { type DateRangeOption } from './dateRangeOption';
|
|
1
|
+
import { type DateRangeOption, type DateRangeValue } from './dateRangeOption';
|
|
2
2
|
import { getDatesForSelectorValue, getSelectableOptions } from './selectableOptions';
|
|
3
3
|
import { UserFacingError } from '../components/error-display';
|
|
4
4
|
|
|
5
5
|
export function computeInitialValues(
|
|
6
|
-
|
|
7
|
-
initialDateFrom: string | undefined,
|
|
8
|
-
initialDateTo: string | undefined,
|
|
6
|
+
value: DateRangeValue | undefined,
|
|
9
7
|
earliestDate: string,
|
|
10
8
|
dateRangeOptions: DateRangeOption[],
|
|
11
9
|
): {
|
|
@@ -13,20 +11,26 @@ export function computeInitialValues(
|
|
|
13
11
|
initialSelectedDateFrom: Date;
|
|
14
12
|
initialSelectedDateTo: Date;
|
|
15
13
|
} {
|
|
16
|
-
if (
|
|
14
|
+
if (value === undefined) {
|
|
15
|
+
const { dateFrom, dateTo } = getDatesForSelectorValue(undefined, dateRangeOptions, earliestDate);
|
|
16
|
+
return {
|
|
17
|
+
initialSelectedDateRange: undefined,
|
|
18
|
+
initialSelectedDateFrom: dateFrom,
|
|
19
|
+
initialSelectedDateTo: dateTo,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (typeof value === 'string') {
|
|
17
24
|
const selectableOptions = getSelectableOptions(dateRangeOptions);
|
|
18
|
-
const initialSelectedDateRange = selectableOptions.find((option) => option.value ===
|
|
25
|
+
const initialSelectedDateRange = selectableOptions.find((option) => option.value === value)?.value;
|
|
19
26
|
|
|
20
|
-
if (
|
|
27
|
+
if (initialSelectedDateRange === undefined) {
|
|
21
28
|
if (selectableOptions.length === 0) {
|
|
22
|
-
throw new UserFacingError(
|
|
23
|
-
'Invalid initialValue',
|
|
24
|
-
'There are no selectable options, but initialValue is set.',
|
|
25
|
-
);
|
|
29
|
+
throw new UserFacingError('Invalid value', 'There are no selectable options, but value is set.');
|
|
26
30
|
}
|
|
27
31
|
throw new UserFacingError(
|
|
28
|
-
'Invalid
|
|
29
|
-
`Invalid
|
|
32
|
+
'Invalid value',
|
|
33
|
+
`Invalid value "${value}", It must be one of ${selectableOptions.map((option) => `'${option.value}'`).join(', ')}`,
|
|
30
34
|
);
|
|
31
35
|
}
|
|
32
36
|
|
|
@@ -39,21 +43,21 @@ export function computeInitialValues(
|
|
|
39
43
|
};
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
let initialSelectedDateTo = isUndefinedOrEmpty(
|
|
46
|
+
const { dateFrom, dateTo } = value;
|
|
47
|
+
|
|
48
|
+
const initialSelectedDateFrom = isUndefinedOrEmpty(dateFrom) ? new Date(earliestDate) : new Date(dateFrom);
|
|
49
|
+
let initialSelectedDateTo = isUndefinedOrEmpty(dateTo) ? new Date() : new Date(dateTo);
|
|
46
50
|
|
|
47
51
|
if (isNaN(initialSelectedDateFrom.getTime())) {
|
|
48
52
|
throw new UserFacingError(
|
|
49
|
-
'Invalid
|
|
50
|
-
`Invalid
|
|
53
|
+
'Invalid value.dateFrom',
|
|
54
|
+
`Invalid value.dateFrom "${dateFrom}", It must be of the format YYYY-MM-DD`,
|
|
51
55
|
);
|
|
52
56
|
}
|
|
53
57
|
if (isNaN(initialSelectedDateTo.getTime())) {
|
|
54
58
|
throw new UserFacingError(
|
|
55
|
-
'Invalid
|
|
56
|
-
`Invalid
|
|
59
|
+
'Invalid value.dateTo',
|
|
60
|
+
`Invalid value.dateTo "${dateTo}", It must be of the format YYYY-MM-DD`,
|
|
57
61
|
);
|
|
58
62
|
}
|
|
59
63
|
|
|
@@ -2,11 +2,12 @@ import { type Meta, type PreactRenderer, type StoryObj } from '@storybook/preact
|
|
|
2
2
|
import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
|
|
3
3
|
import type { StepFunction } from '@storybook/types';
|
|
4
4
|
import dayjs from 'dayjs/esm';
|
|
5
|
+
import { useEffect, useRef, useState } from 'preact/hooks';
|
|
5
6
|
|
|
6
7
|
import { DateRangeSelector, type DateRangeSelectorProps } from './date-range-selector';
|
|
7
8
|
import { previewHandles } from '../../../.storybook/preview';
|
|
8
9
|
import { LAPIS_URL } from '../../constants';
|
|
9
|
-
import {
|
|
10
|
+
import { LapisUrlContextProvider } from '../LapisUrlContext';
|
|
10
11
|
import { dateRangeOptionPresets } from './dateRangeOption';
|
|
11
12
|
import { expectInvalidAttributesErrorMessage } from '../shared/stories/expectErrorMessage';
|
|
12
13
|
|
|
@@ -28,11 +29,10 @@ const meta: Meta<DateRangeSelectorProps> = {
|
|
|
28
29
|
fetchMock: {},
|
|
29
30
|
},
|
|
30
31
|
argTypes: {
|
|
31
|
-
|
|
32
|
+
value: {
|
|
32
33
|
control: {
|
|
33
|
-
type: '
|
|
34
|
+
type: 'object',
|
|
34
35
|
},
|
|
35
|
-
options: [dateRangeOptionPresets.lastMonth.label, dateRangeOptionPresets.allTimes.label, 'CustomDateRange'],
|
|
36
36
|
},
|
|
37
37
|
dateRangeOptions: {
|
|
38
38
|
control: {
|
|
@@ -53,21 +53,19 @@ const meta: Meta<DateRangeSelectorProps> = {
|
|
|
53
53
|
args: {
|
|
54
54
|
dateRangeOptions: [dateRangeOptionPresets.lastMonth, dateRangeOptionPresets.allTimes, customDateRange],
|
|
55
55
|
earliestDate,
|
|
56
|
-
|
|
56
|
+
value: undefined,
|
|
57
57
|
lapisDateField: 'aDateColumn',
|
|
58
58
|
width: '100%',
|
|
59
|
-
initialDateFrom: undefined,
|
|
60
|
-
initialDateTo: undefined,
|
|
61
59
|
},
|
|
62
60
|
};
|
|
63
61
|
|
|
64
62
|
export default meta;
|
|
65
63
|
|
|
66
|
-
|
|
64
|
+
const Primary: StoryObj<DateRangeSelectorProps> = {
|
|
67
65
|
render: (args) => (
|
|
68
|
-
<
|
|
66
|
+
<LapisUrlContextProvider value={LAPIS_URL}>
|
|
69
67
|
<DateRangeSelector {...args} />
|
|
70
|
-
</
|
|
68
|
+
</LapisUrlContextProvider>
|
|
71
69
|
),
|
|
72
70
|
};
|
|
73
71
|
|
|
@@ -75,15 +73,15 @@ export const SetCorrectInitialValues: StoryObj<DateRangeSelectorProps> = {
|
|
|
75
73
|
...Primary,
|
|
76
74
|
args: {
|
|
77
75
|
...Primary.args,
|
|
78
|
-
|
|
76
|
+
value: 'CustomDateRange',
|
|
79
77
|
},
|
|
80
78
|
play: async ({ canvasElement }) => {
|
|
81
79
|
const canvas = within(canvasElement);
|
|
82
80
|
|
|
83
|
-
await waitFor(() => {
|
|
84
|
-
expect(selectField(canvas)).toHaveValue('CustomDateRange');
|
|
85
|
-
expect(dateFromPicker(canvas)).toHaveValue('2021-01-01');
|
|
86
|
-
expect(dateToPicker(canvas)).toHaveValue('2021-12-31');
|
|
81
|
+
await waitFor(async () => {
|
|
82
|
+
await expect(selectField(canvas)).toHaveValue('CustomDateRange');
|
|
83
|
+
await expect(dateFromPicker(canvas)).toHaveValue('2021-01-01');
|
|
84
|
+
await expect(dateToPicker(canvas)).toHaveValue('2021-12-31');
|
|
87
85
|
});
|
|
88
86
|
},
|
|
89
87
|
};
|
|
@@ -94,15 +92,15 @@ export const SetCorrectInitialDateFrom: StoryObj<DateRangeSelectorProps> = {
|
|
|
94
92
|
...Primary,
|
|
95
93
|
args: {
|
|
96
94
|
...Primary.args,
|
|
97
|
-
initialDateFrom,
|
|
95
|
+
value: { dateFrom: initialDateFrom },
|
|
98
96
|
},
|
|
99
97
|
play: async ({ canvasElement }) => {
|
|
100
98
|
const canvas = within(canvasElement);
|
|
101
99
|
|
|
102
|
-
await waitFor(() => {
|
|
103
|
-
expect(selectField(canvas)).toHaveValue('Custom');
|
|
104
|
-
expect(dateFromPicker(canvas)).toHaveValue(initialDateFrom);
|
|
105
|
-
expect(dateToPicker(canvas)).toHaveValue(dayjs().format('YYYY-MM-DD'));
|
|
100
|
+
await waitFor(async () => {
|
|
101
|
+
await expect(selectField(canvas)).toHaveValue('Custom');
|
|
102
|
+
await expect(dateFromPicker(canvas)).toHaveValue(initialDateFrom);
|
|
103
|
+
await expect(dateToPicker(canvas)).toHaveValue(dayjs().format('YYYY-MM-DD'));
|
|
106
104
|
});
|
|
107
105
|
},
|
|
108
106
|
};
|
|
@@ -113,35 +111,39 @@ export const SetCorrectInitialDateTo: StoryObj<DateRangeSelectorProps> = {
|
|
|
113
111
|
...Primary,
|
|
114
112
|
args: {
|
|
115
113
|
...Primary.args,
|
|
116
|
-
initialDateTo,
|
|
114
|
+
value: { dateTo: initialDateTo },
|
|
117
115
|
},
|
|
118
116
|
play: async ({ canvasElement }) => {
|
|
119
117
|
const canvas = within(canvasElement);
|
|
120
118
|
|
|
121
|
-
await waitFor(() => {
|
|
122
|
-
expect(selectField(canvas)).toHaveValue('Custom');
|
|
123
|
-
expect(dateFromPicker(canvas)).toHaveValue(earliestDate);
|
|
124
|
-
expect(dateToPicker(canvas)).toHaveValue(initialDateTo);
|
|
119
|
+
await waitFor(async () => {
|
|
120
|
+
await expect(selectField(canvas)).toHaveValue('Custom');
|
|
121
|
+
await expect(dateFromPicker(canvas)).toHaveValue(earliestDate);
|
|
122
|
+
await expect(dateToPicker(canvas)).toHaveValue(initialDateTo);
|
|
125
123
|
});
|
|
126
124
|
},
|
|
127
125
|
};
|
|
128
126
|
|
|
129
127
|
export const ChangingDateSetsOptionToCustom: StoryObj<DateRangeSelectorProps> = {
|
|
130
128
|
...Primary,
|
|
129
|
+
args: {
|
|
130
|
+
...Primary.args,
|
|
131
|
+
value: dateRangeOptionPresets.lastMonth.label,
|
|
132
|
+
},
|
|
131
133
|
play: async ({ canvasElement, step }) => {
|
|
132
134
|
const { canvas, filterChangedListenerMock, optionChangedListenerMock } = await prepare(canvasElement, step);
|
|
133
135
|
|
|
134
|
-
await waitFor(() => {
|
|
135
|
-
expect(selectField(canvas)).toHaveValue('Last month');
|
|
136
|
+
await waitFor(async () => {
|
|
137
|
+
await expect(selectField(canvas)).toHaveValue('Last month');
|
|
136
138
|
});
|
|
137
139
|
|
|
138
|
-
step('Change date to custom value', async () => {
|
|
140
|
+
await step('Change date to custom value', async () => {
|
|
139
141
|
await userEvent.type(dateFromPicker(canvas), '{backspace>12}');
|
|
140
142
|
await userEvent.type(dateFromPicker(canvas), '2000-01-01');
|
|
141
143
|
await userEvent.click(dateToPicker(canvas));
|
|
142
144
|
|
|
143
|
-
await waitFor(() => {
|
|
144
|
-
expect(selectField(canvas)).toHaveValue('Custom');
|
|
145
|
+
await waitFor(async () => {
|
|
146
|
+
await expect(selectField(canvas)).toHaveValue('Custom');
|
|
145
147
|
});
|
|
146
148
|
|
|
147
149
|
await expect(filterChangedListenerMock).toHaveBeenCalledWith(
|
|
@@ -165,20 +167,82 @@ export const ChangingDateSetsOptionToCustom: StoryObj<DateRangeSelectorProps> =
|
|
|
165
167
|
},
|
|
166
168
|
};
|
|
167
169
|
|
|
168
|
-
export const
|
|
170
|
+
export const ChangingTheValueProgrammatically: StoryObj<DateRangeSelectorProps> = {
|
|
169
171
|
...Primary,
|
|
172
|
+
render: (args) => {
|
|
173
|
+
const StatefulWrapper = () => {
|
|
174
|
+
const [value, setValue] = useState('Last month');
|
|
175
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
176
|
+
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
ref.current?.addEventListener('gs-date-range-option-changed', (event) => {
|
|
179
|
+
setValue((event as CustomEvent).detail);
|
|
180
|
+
});
|
|
181
|
+
}, []);
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<div ref={ref}>
|
|
185
|
+
<LapisUrlContextProvider value={LAPIS_URL}>
|
|
186
|
+
<DateRangeSelector {...args} value={value} />
|
|
187
|
+
</LapisUrlContextProvider>
|
|
188
|
+
<button className='btn' onClick={() => setValue(customDateRange.label)}>
|
|
189
|
+
Set to Custom
|
|
190
|
+
</button>
|
|
191
|
+
<button className='btn' onClick={() => setValue(dateRangeOptionPresets.lastMonth.label)}>
|
|
192
|
+
Set to Last month
|
|
193
|
+
</button>
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
return <StatefulWrapper />;
|
|
199
|
+
},
|
|
170
200
|
play: async ({ canvasElement, step }) => {
|
|
171
201
|
const { canvas, filterChangedListenerMock, optionChangedListenerMock } = await prepare(canvasElement, step);
|
|
172
202
|
|
|
173
|
-
await waitFor(() => {
|
|
174
|
-
expect(selectField(canvas)).toHaveValue('Last month');
|
|
203
|
+
await waitFor(async () => {
|
|
204
|
+
await expect(selectField(canvas)).toHaveValue('Last month');
|
|
175
205
|
});
|
|
176
206
|
|
|
177
|
-
step('Change
|
|
178
|
-
await userEvent.
|
|
207
|
+
await step('Change the value of the component programmatically', async () => {
|
|
208
|
+
await userEvent.click(canvas.getByRole('button', { name: 'Set to Custom' }));
|
|
209
|
+
await waitFor(async () => {
|
|
210
|
+
await expect(selectField(canvas)).toHaveValue(customDateRange.label);
|
|
211
|
+
});
|
|
179
212
|
|
|
180
|
-
await
|
|
181
|
-
|
|
213
|
+
await userEvent.click(canvas.getByRole('button', { name: 'Set to Last month' }));
|
|
214
|
+
await waitFor(async () => {
|
|
215
|
+
await expect(selectField(canvas)).toHaveValue('Last month');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
await expect(filterChangedListenerMock).toHaveBeenCalledTimes(0);
|
|
219
|
+
await expect(optionChangedListenerMock).toHaveBeenCalledTimes(0);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
await step('Changing the value from within the component is still possible', async () => {
|
|
223
|
+
await userEvent.selectOptions(selectField(canvas), 'All times');
|
|
224
|
+
await waitFor(async () => {
|
|
225
|
+
await expect(selectField(canvas)).toHaveValue('All times');
|
|
226
|
+
});
|
|
227
|
+
await expect(filterChangedListenerMock).toHaveBeenCalledTimes(1);
|
|
228
|
+
await expect(optionChangedListenerMock).toHaveBeenCalledTimes(1);
|
|
229
|
+
});
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export const ChangingDateOption: StoryObj<DateRangeSelectorProps> = {
|
|
234
|
+
...Primary,
|
|
235
|
+
play: async ({ canvasElement, step }) => {
|
|
236
|
+
const { canvas, filterChangedListenerMock, optionChangedListenerMock } = await prepare(canvasElement, step);
|
|
237
|
+
|
|
238
|
+
await waitFor(async () => {
|
|
239
|
+
await expect(selectField(canvas)).toHaveValue('Custom');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
await step('Change date to custom', async () => {
|
|
243
|
+
await waitFor(async () => {
|
|
244
|
+
await userEvent.selectOptions(selectField(canvas), 'CustomDateRange');
|
|
245
|
+
await expect(selectField(canvas)).toHaveValue('CustomDateRange');
|
|
182
246
|
});
|
|
183
247
|
|
|
184
248
|
await expect(filterChangedListenerMock).toHaveBeenCalledWith(
|
|
@@ -203,13 +267,13 @@ export const HandlesInvalidInitialDateFrom: StoryObj<DateRangeSelectorProps> = {
|
|
|
203
267
|
...Primary,
|
|
204
268
|
args: {
|
|
205
269
|
...Primary.args,
|
|
206
|
-
|
|
270
|
+
value: { dateFrom: 'not a date' },
|
|
207
271
|
},
|
|
208
272
|
play: async ({ canvasElement }) => {
|
|
209
273
|
const canvas = within(canvasElement);
|
|
210
274
|
|
|
211
|
-
await waitFor(() => {
|
|
212
|
-
expect(canvas.getByText('Oops! Something went wrong.')).toBeVisible();
|
|
275
|
+
await waitFor(async () => {
|
|
276
|
+
await expect(canvas.getByText('Oops! Something went wrong.')).toBeVisible();
|
|
213
277
|
});
|
|
214
278
|
},
|
|
215
279
|
};
|
|
@@ -221,7 +285,7 @@ export const WithNoDateColumn: StoryObj<DateRangeSelectorProps> = {
|
|
|
221
285
|
lapisDateField: '',
|
|
222
286
|
},
|
|
223
287
|
play: async ({ canvasElement, step }) => {
|
|
224
|
-
step('expect error message', async () => {
|
|
288
|
+
await step('expect error message', async () => {
|
|
225
289
|
await expectInvalidAttributesErrorMessage(canvasElement, 'String must contain at least 1 character(s)');
|
|
226
290
|
});
|
|
227
291
|
},
|
|
@@ -231,12 +295,9 @@ async function prepare(canvasElement: HTMLElement, step: StepFunction<PreactRend
|
|
|
231
295
|
const canvas = within(canvasElement);
|
|
232
296
|
|
|
233
297
|
const filterChangedListenerMock = fn();
|
|
234
|
-
await step('Setup event listener mock', async () => {
|
|
235
|
-
canvasElement.addEventListener('gs-date-range-filter-changed', filterChangedListenerMock);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
298
|
const optionChangedListenerMock = fn();
|
|
239
|
-
await step('Setup event listener mock',
|
|
299
|
+
await step('Setup event listener mock', () => {
|
|
300
|
+
canvasElement.addEventListener('gs-date-range-filter-changed', filterChangedListenerMock);
|
|
240
301
|
canvasElement.addEventListener('gs-date-range-option-changed', optionChangedListenerMock);
|
|
241
302
|
});
|
|
242
303
|
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import flatpickr from 'flatpickr';
|
|
2
2
|
import 'flatpickr/dist/flatpickr.min.css';
|
|
3
|
-
import { useEffect, useRef, useState } from 'preact/hooks';
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
|
4
4
|
import z from 'zod';
|
|
5
5
|
|
|
6
6
|
import { computeInitialValues } from './computeInitialValues';
|
|
7
7
|
import { toYYYYMMDD } from './dateConversion';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
DateRangeOptionChangedEvent,
|
|
10
|
+
dateRangeOptionSchema,
|
|
11
|
+
type DateRangeSelectOption,
|
|
12
|
+
dateRangeValueSchema,
|
|
13
|
+
} from './dateRangeOption';
|
|
9
14
|
import { getDatesForSelectorValue, getSelectableOptions } from './selectableOptions';
|
|
10
15
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
11
16
|
import { Select } from '../components/select';
|
|
@@ -16,9 +21,7 @@ const customOption = 'Custom';
|
|
|
16
21
|
const dateRangeSelectorInnerPropsSchema = z.object({
|
|
17
22
|
dateRangeOptions: z.array(dateRangeOptionSchema),
|
|
18
23
|
earliestDate: z.string().date(),
|
|
19
|
-
|
|
20
|
-
initialDateFrom: z.string().date().optional(),
|
|
21
|
-
initialDateTo: z.string().date().optional(),
|
|
24
|
+
value: dateRangeValueSchema.optional(),
|
|
22
25
|
lapisDateField: z.string().min(1),
|
|
23
26
|
});
|
|
24
27
|
|
|
@@ -45,17 +48,12 @@ export const DateRangeSelector = (props: DateRangeSelectorProps) => {
|
|
|
45
48
|
export const DateRangeSelectorInner = ({
|
|
46
49
|
dateRangeOptions,
|
|
47
50
|
earliestDate = '1900-01-01',
|
|
48
|
-
|
|
51
|
+
value,
|
|
49
52
|
lapisDateField,
|
|
50
|
-
initialDateFrom,
|
|
51
|
-
initialDateTo,
|
|
52
53
|
}: DateRangeSelectorInnerProps) => {
|
|
53
|
-
const initialValues =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
initialDateTo,
|
|
57
|
-
earliestDate,
|
|
58
|
-
dateRangeOptions,
|
|
54
|
+
const initialValues = useMemo(
|
|
55
|
+
() => computeInitialValues(value, earliestDate, dateRangeOptions),
|
|
56
|
+
[value, earliestDate, dateRangeOptions],
|
|
59
57
|
);
|
|
60
58
|
|
|
61
59
|
const fromDatePickerRef = useRef<HTMLInputElement>(null);
|
|
@@ -74,6 +72,12 @@ export const DateRangeSelectorInner = ({
|
|
|
74
72
|
});
|
|
75
73
|
|
|
76
74
|
useEffect(() => {
|
|
75
|
+
setSelectedDateRange(initialValues.initialSelectedDateRange);
|
|
76
|
+
setSelectedDates({
|
|
77
|
+
dateFrom: initialValues.initialSelectedDateFrom,
|
|
78
|
+
dateTo: initialValues.initialSelectedDateTo,
|
|
79
|
+
});
|
|
80
|
+
|
|
77
81
|
const commonConfig = {
|
|
78
82
|
allowInput: true,
|
|
79
83
|
dateFormat: 'Y-m-d',
|
|
@@ -83,7 +87,7 @@ export const DateRangeSelectorInner = ({
|
|
|
83
87
|
setDateFromPicker(
|
|
84
88
|
flatpickr(fromDatePickerRef.current, {
|
|
85
89
|
...commonConfig,
|
|
86
|
-
defaultDate:
|
|
90
|
+
defaultDate: initialValues.initialSelectedDateFrom,
|
|
87
91
|
}),
|
|
88
92
|
);
|
|
89
93
|
}
|
|
@@ -92,17 +96,22 @@ export const DateRangeSelectorInner = ({
|
|
|
92
96
|
setDateToPicker(
|
|
93
97
|
flatpickr(toDatePickerRef.current, {
|
|
94
98
|
...commonConfig,
|
|
95
|
-
defaultDate:
|
|
99
|
+
defaultDate: initialValues.initialSelectedDateTo,
|
|
96
100
|
}),
|
|
97
101
|
);
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
return () => {
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
setDateFromPicker((prev) => {
|
|
106
|
+
prev?.destroy();
|
|
107
|
+
return null;
|
|
108
|
+
});
|
|
109
|
+
setDateToPicker((prev) => {
|
|
110
|
+
prev?.destroy();
|
|
111
|
+
return null;
|
|
112
|
+
});
|
|
103
113
|
};
|
|
104
|
-
|
|
105
|
-
}, [fromDatePickerRef, toDatePickerRef]);
|
|
114
|
+
}, [fromDatePickerRef, toDatePickerRef, initialValues]);
|
|
106
115
|
|
|
107
116
|
const onSelectChange = (value: string) => {
|
|
108
117
|
setSelectedDateRange(value);
|
|
@@ -139,7 +148,7 @@ export const DateRangeSelectorInner = ({
|
|
|
139
148
|
fireFilterChangedEvent();
|
|
140
149
|
fireOptionChangedEvent({
|
|
141
150
|
dateFrom: dateFrom !== undefined ? toYYYYMMDD(dateFrom) : earliestDate,
|
|
142
|
-
dateTo: toYYYYMMDD(dateTo || new Date())
|
|
151
|
+
dateTo: toYYYYMMDD(dateTo || new Date()),
|
|
143
152
|
});
|
|
144
153
|
};
|
|
145
154
|
|
|
@@ -158,7 +167,7 @@ export const DateRangeSelectorInner = ({
|
|
|
158
167
|
fireFilterChangedEvent();
|
|
159
168
|
fireOptionChangedEvent({
|
|
160
169
|
dateFrom: dateFrom !== undefined ? toYYYYMMDD(dateFrom) : earliestDate,
|
|
161
|
-
dateTo: toYYYYMMDD(dateTo || new Date())
|
|
170
|
+
dateTo: toYYYYMMDD(dateTo || new Date()),
|
|
162
171
|
});
|
|
163
172
|
};
|
|
164
173
|
|
|
@@ -22,7 +22,17 @@ export const dateRangeOptionSchema = z.object({
|
|
|
22
22
|
|
|
23
23
|
export type DateRangeOption = z.infer<typeof dateRangeOptionSchema>;
|
|
24
24
|
|
|
25
|
-
export
|
|
25
|
+
export const dateRangeValueSchema = z.union([
|
|
26
|
+
z.string(),
|
|
27
|
+
z.object({
|
|
28
|
+
dateFrom: z.string().date().optional(),
|
|
29
|
+
dateTo: z.string().date().optional(),
|
|
30
|
+
}),
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
export type DateRangeValue = z.infer<typeof dateRangeValueSchema>;
|
|
34
|
+
|
|
35
|
+
export type DateRangeSelectOption = Required<DateRangeValue>;
|
|
26
36
|
|
|
27
37
|
export class DateRangeOptionChangedEvent extends CustomEvent<DateRangeSelectOption> {
|
|
28
38
|
constructor(detail: DateRangeSelectOption) {
|