@genspectrum/dashboard-components 0.8.5 → 0.9.1
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/README.md +14 -2
- package/custom-elements.json +30 -7
- package/dist/{genspectrum-components.d.ts → components.d.ts} +15 -49
- package/dist/{dashboard-components.js → components.js} +85 -90
- package/dist/components.js.map +1 -0
- package/dist/style.css +2 -2
- package/dist/util.d.ts +301 -0
- package/dist/util.js +6 -0
- package/dist/util.js.map +1 -0
- package/dist/utilEntrypoint-g4DsyhU7.js +61 -0
- package/dist/utilEntrypoint-g4DsyhU7.js.map +1 -0
- package/package.json +10 -5
- package/src/{index.ts → componentsEntrypoint.ts} +0 -1
- package/src/preact/components/no-data-display.tsx +2 -2
- package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +101 -44
- package/src/preact/dateRangeSelector/date-range-selector.tsx +33 -14
- package/src/preact/dateRangeSelector/dateConversion.ts +1 -5
- package/src/preact/dateRangeSelector/dateRangeOption.ts +18 -0
- package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +2 -3
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +91 -0
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +6 -0
- package/src/query/queryRelativeGrowthAdvantage.ts +61 -24
- package/src/standaloneEntrypoint.ts +2 -0
- package/src/utilEntrypoint.ts +6 -0
- package/src/web-components/input/gs-date-range-selector.stories.ts +41 -10
- package/src/web-components/input/gs-date-range-selector.tsx +16 -2
- package/standalone-bundle/assets/mutationOverTimeWorker-MVSt1FVw.js.map +1 -0
- package/standalone-bundle/dashboard-components.js +13234 -16877
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/standalone-bundle/style.css +1 -0
- package/dist/dashboard-components.js.map +0 -1
|
@@ -4,7 +4,7 @@ import { useEffect, useRef, useState } from 'preact/hooks';
|
|
|
4
4
|
|
|
5
5
|
import { computeInitialValues } from './computeInitialValues';
|
|
6
6
|
import { toYYYYMMDD } from './dateConversion';
|
|
7
|
-
import { type DateRangeOption } from './dateRangeOption';
|
|
7
|
+
import { type DateRangeOption, DateRangeOptionChangedEvent, type DateRangeSelectOption } from './dateRangeOption';
|
|
8
8
|
import { getDatesForSelectorValue, getSelectableOptions } from './selectableOptions';
|
|
9
9
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
10
10
|
import { Select } from '../components/select';
|
|
@@ -115,7 +115,8 @@ export const DateRangeSelectorInner = ({
|
|
|
115
115
|
dateTo: dateRange.dateTo,
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
fireFilterChangedEvent();
|
|
119
|
+
fireOptionChangedEvent(value);
|
|
119
120
|
};
|
|
120
121
|
|
|
121
122
|
const onChangeDateFrom = () => {
|
|
@@ -123,11 +124,18 @@ export const DateRangeSelectorInner = ({
|
|
|
123
124
|
return;
|
|
124
125
|
}
|
|
125
126
|
|
|
126
|
-
|
|
127
|
-
|
|
127
|
+
const dateTo = dateToPicker?.selectedDates[0];
|
|
128
|
+
const dateFrom = dateFromPicker?.selectedDates[0];
|
|
129
|
+
|
|
130
|
+
selectedDates.dateFrom = dateFrom || new Date();
|
|
131
|
+
dateToPicker?.set('minDate', dateFrom);
|
|
128
132
|
setSelectedDateRange(customOption);
|
|
129
133
|
|
|
130
|
-
|
|
134
|
+
fireFilterChangedEvent();
|
|
135
|
+
fireOptionChangedEvent({
|
|
136
|
+
dateFrom: dateFrom !== undefined ? toYYYYMMDD(dateFrom) : earliestDate,
|
|
137
|
+
dateTo: toYYYYMMDD(dateTo || new Date())!,
|
|
138
|
+
});
|
|
131
139
|
};
|
|
132
140
|
|
|
133
141
|
const onChangeDateTo = () => {
|
|
@@ -135,24 +143,35 @@ export const DateRangeSelectorInner = ({
|
|
|
135
143
|
return;
|
|
136
144
|
}
|
|
137
145
|
|
|
138
|
-
|
|
139
|
-
dateFromPicker?.
|
|
146
|
+
const dateTo = dateToPicker?.selectedDates[0];
|
|
147
|
+
const dateFrom = dateFromPicker?.selectedDates[0];
|
|
148
|
+
|
|
149
|
+
selectedDates.dateTo = dateTo || new Date();
|
|
150
|
+
dateFromPicker?.set('maxDate', dateTo);
|
|
140
151
|
setSelectedDateRange(customOption);
|
|
141
152
|
|
|
142
|
-
|
|
153
|
+
fireFilterChangedEvent();
|
|
154
|
+
fireOptionChangedEvent({
|
|
155
|
+
dateFrom: dateFrom !== undefined ? toYYYYMMDD(dateFrom) : earliestDate,
|
|
156
|
+
dateTo: toYYYYMMDD(dateTo || new Date())!,
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const fireOptionChangedEvent = (option: DateRangeSelectOption) => {
|
|
161
|
+
divRef.current?.dispatchEvent(new DateRangeOptionChangedEvent(option));
|
|
143
162
|
};
|
|
144
163
|
|
|
145
|
-
const
|
|
146
|
-
const dateFrom =
|
|
147
|
-
const dateTo =
|
|
164
|
+
const fireFilterChangedEvent = () => {
|
|
165
|
+
const dateFrom = dateFromPicker?.selectedDates[0];
|
|
166
|
+
const dateTo = dateToPicker?.selectedDates[0];
|
|
148
167
|
|
|
149
168
|
const detail = {
|
|
150
|
-
...(dateFrom !== undefined && { [`${dateColumn}From`]: dateFrom }),
|
|
151
|
-
...(dateTo !== undefined && { [`${dateColumn}To`]: dateTo }),
|
|
169
|
+
...(dateFrom !== undefined && { [`${dateColumn}From`]: toYYYYMMDD(dateFrom) }),
|
|
170
|
+
...(dateTo !== undefined && { [`${dateColumn}To`]: toYYYYMMDD(dateTo) }),
|
|
152
171
|
};
|
|
153
172
|
|
|
154
173
|
divRef.current?.dispatchEvent(
|
|
155
|
-
new CustomEvent('gs-date-range-changed', {
|
|
174
|
+
new CustomEvent('gs-date-range-filter-changed', {
|
|
156
175
|
detail,
|
|
157
176
|
bubbles: true,
|
|
158
177
|
composed: true,
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
export const toYYYYMMDD = (date
|
|
2
|
-
if (!date) {
|
|
3
|
-
return undefined;
|
|
4
|
-
}
|
|
5
|
-
|
|
1
|
+
export const toYYYYMMDD = (date: Date) => {
|
|
6
2
|
const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: '2-digit', day: '2-digit' };
|
|
7
3
|
return date.toLocaleDateString('en-CA', options);
|
|
8
4
|
};
|
|
@@ -18,6 +18,17 @@ export type DateRangeOption = {
|
|
|
18
18
|
dateTo?: string;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
export type DateRangeSelectOption = string | { dateFrom: string; dateTo: string };
|
|
22
|
+
export class DateRangeOptionChangedEvent extends CustomEvent<DateRangeSelectOption> {
|
|
23
|
+
constructor(detail: DateRangeSelectOption) {
|
|
24
|
+
super('gs-date-range-option-changed', {
|
|
25
|
+
detail,
|
|
26
|
+
bubbles: true,
|
|
27
|
+
composed: true,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
21
32
|
const today = new Date();
|
|
22
33
|
|
|
23
34
|
const twoWeeksAgo = new Date();
|
|
@@ -35,6 +46,9 @@ last3Months.setMonth(today.getMonth() - 3);
|
|
|
35
46
|
const last6Months = new Date(today);
|
|
36
47
|
last6Months.setMonth(today.getMonth() - 6);
|
|
37
48
|
|
|
49
|
+
const lastYear = new Date(today);
|
|
50
|
+
lastYear.setFullYear(today.getFullYear() - 1);
|
|
51
|
+
|
|
38
52
|
/**
|
|
39
53
|
* Presets for the `gs-date-range-selector` component that can be used as `dateRangeOptions`.
|
|
40
54
|
*/
|
|
@@ -59,6 +73,10 @@ export const dateRangeOptionPresets = {
|
|
|
59
73
|
label: 'Last 6 months',
|
|
60
74
|
dateFrom: toYYYYMMDD(last6Months),
|
|
61
75
|
},
|
|
76
|
+
lastYear: {
|
|
77
|
+
label: 'Last year',
|
|
78
|
+
dateFrom: toYYYYMMDD(lastYear),
|
|
79
|
+
},
|
|
62
80
|
allTimes: {
|
|
63
81
|
label: 'All times',
|
|
64
82
|
},
|
|
@@ -43,7 +43,6 @@ const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
|
|
|
43
43
|
gridTemplateRows: `repeat(${shownMutations.length}, 24px)`,
|
|
44
44
|
gridTemplateColumns: `${MUTATION_CELL_WIDTH_REM}rem repeat(${dates.length}, minmax(0.05rem, 1fr))`,
|
|
45
45
|
}}
|
|
46
|
-
className='@container'
|
|
47
46
|
>
|
|
48
47
|
{shownMutations.map((mutation, rowIndex) => {
|
|
49
48
|
return (
|
|
@@ -122,9 +121,9 @@ const ProportionCell: FunctionComponent<{
|
|
|
122
121
|
backgroundColor: getColorWithingScale(value.proportion, colorScale),
|
|
123
122
|
color: getTextColorForScale(value.proportion, colorScale),
|
|
124
123
|
}}
|
|
125
|
-
className={`w-full h-full text-center hover:font-bold text-xs group`}
|
|
124
|
+
className={`w-full h-full text-center hover:font-bold text-xs group @container`}
|
|
126
125
|
>
|
|
127
|
-
<span className='invisible @[
|
|
126
|
+
<span className='invisible @[2rem]:visible'>{formatProportion(value.proportion, 0)}</span>
|
|
128
127
|
</div>
|
|
129
128
|
</Tooltip>
|
|
130
129
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type StoryObj } from '@storybook/preact';
|
|
2
|
+
import { expect, waitFor, within } from '@storybook/test';
|
|
2
3
|
|
|
3
4
|
import denominator from './__mockData__/denominatorFilter.json';
|
|
4
5
|
import numerator from './__mockData__/numeratorFilter.json';
|
|
@@ -99,3 +100,93 @@ export const Primary: StoryObj<RelativeGrowthAdvantageProps> = {
|
|
|
99
100
|
},
|
|
100
101
|
},
|
|
101
102
|
};
|
|
103
|
+
|
|
104
|
+
export const TooFewDataToComputeGrowthAdvantage: StoryObj<RelativeGrowthAdvantageProps> = {
|
|
105
|
+
...Primary,
|
|
106
|
+
args: {
|
|
107
|
+
...Primary.args,
|
|
108
|
+
numeratorFilter: {
|
|
109
|
+
country: 'Switzerland',
|
|
110
|
+
pangoLineage: 'B.1.1.7',
|
|
111
|
+
dateFrom: '2021-02-28',
|
|
112
|
+
dateTo: '2021-03-01',
|
|
113
|
+
},
|
|
114
|
+
denominatorFilter: {
|
|
115
|
+
country: 'Switzerland',
|
|
116
|
+
dateFrom: '2021-02-28',
|
|
117
|
+
dateTo: '2021-03-01',
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
parameters: {
|
|
121
|
+
fetchMock: {
|
|
122
|
+
mocks: [
|
|
123
|
+
{
|
|
124
|
+
matcher: {
|
|
125
|
+
name: 'numeratorFilter',
|
|
126
|
+
url: AGGREGATED_ENDPOINT,
|
|
127
|
+
body: {
|
|
128
|
+
country: 'Switzerland',
|
|
129
|
+
pangoLineage: 'B.1.1.7',
|
|
130
|
+
dateFrom: '2021-02-28',
|
|
131
|
+
dateTo: '2021-03-01',
|
|
132
|
+
fields: ['date'],
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
response: {
|
|
136
|
+
status: 200,
|
|
137
|
+
body: {
|
|
138
|
+
data: [
|
|
139
|
+
{
|
|
140
|
+
date: '2021-02-28',
|
|
141
|
+
count: 5,
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
date: '2021-03-01',
|
|
145
|
+
count: 5,
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
matcher: {
|
|
153
|
+
name: 'denominatorFilter',
|
|
154
|
+
url: AGGREGATED_ENDPOINT,
|
|
155
|
+
body: {
|
|
156
|
+
country: 'Switzerland',
|
|
157
|
+
dateFrom: '2021-02-28',
|
|
158
|
+
dateTo: '2021-03-01',
|
|
159
|
+
fields: ['date'],
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
response: {
|
|
163
|
+
status: 200,
|
|
164
|
+
body: {
|
|
165
|
+
data: [
|
|
166
|
+
{
|
|
167
|
+
date: '2021-02-28',
|
|
168
|
+
count: 5,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
date: '2021-03-01',
|
|
172
|
+
count: 7,
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
play: async ({ canvasElement }) => {
|
|
182
|
+
const canvas = within(canvasElement);
|
|
183
|
+
|
|
184
|
+
await waitFor(() => {
|
|
185
|
+
const notEnoughDataMessage = canvas.queryByText(
|
|
186
|
+
'It was not possible to estimate the relative growth advantage',
|
|
187
|
+
{ exact: false },
|
|
188
|
+
);
|
|
189
|
+
return expect(notEnoughDataMessage).toBeVisible();
|
|
190
|
+
});
|
|
191
|
+
},
|
|
192
|
+
};
|
|
@@ -3,6 +3,7 @@ import { useContext, useState } from 'preact/hooks';
|
|
|
3
3
|
|
|
4
4
|
import RelativeGrowthAdvantageChart from './relative-growth-advantage-chart';
|
|
5
5
|
import {
|
|
6
|
+
NotEnoughDataToComputeFitError,
|
|
6
7
|
queryRelativeGrowthAdvantage,
|
|
7
8
|
type RelativeGrowthAdvantageData,
|
|
8
9
|
} from '../../query/queryRelativeGrowthAdvantage';
|
|
@@ -62,6 +63,11 @@ export const RelativeGrowthAdvantageInner: FunctionComponent<RelativeGrowthAdvan
|
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
if (error !== null) {
|
|
66
|
+
if (error instanceof NotEnoughDataToComputeFitError) {
|
|
67
|
+
return (
|
|
68
|
+
<NoDataDisplay message='It was not possible to estimate the relative growth advantage due to insufficient data in the specified filter.' />
|
|
69
|
+
);
|
|
70
|
+
}
|
|
65
71
|
throw error;
|
|
66
72
|
}
|
|
67
73
|
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { FetchAggregatedOperator } from '../operator/FetchAggregatedOperator';
|
|
2
2
|
import { MapOperator } from '../operator/MapOperator';
|
|
3
3
|
import { RenameFieldOperator } from '../operator/RenameFieldOperator';
|
|
4
|
+
import { UserFacingError } from '../preact/components/error-display';
|
|
4
5
|
import { type LapisFilter } from '../types';
|
|
5
6
|
import { getMinMaxTemporal, TemporalCache, type YearMonthDayClass } from '../utils/temporalClass';
|
|
6
7
|
|
|
7
8
|
export type RelativeGrowthAdvantageData = Awaited<ReturnType<typeof queryRelativeGrowthAdvantage>>;
|
|
8
9
|
|
|
10
|
+
export class NotEnoughDataToComputeFitError extends Error {
|
|
11
|
+
constructor() {
|
|
12
|
+
super('Not enough data to compute computeFit');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
export async function queryRelativeGrowthAdvantage<LapisDateField extends string>(
|
|
10
17
|
numerator: LapisFilter,
|
|
11
18
|
denominator: LapisFilter,
|
|
@@ -54,6 +61,37 @@ export async function queryRelativeGrowthAdvantage<LapisDateField extends string
|
|
|
54
61
|
requestData.k.push(numeratorCounts.get(d.date) ?? 0);
|
|
55
62
|
}
|
|
56
63
|
});
|
|
64
|
+
|
|
65
|
+
const responseData = await computeFit(generationTime, maxDate, minDate, requestData, signal);
|
|
66
|
+
|
|
67
|
+
const transformed = {
|
|
68
|
+
...responseData,
|
|
69
|
+
estimatedProportions: {
|
|
70
|
+
...responseData.estimatedProportions,
|
|
71
|
+
t: responseData.estimatedProportions.t.map((t) => minDate.addDays(t)),
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
const observedProportions = transformed.estimatedProportions.t.map(
|
|
75
|
+
(t) => (numeratorCounts.get(t) ?? 0) / (denominatorCounts.get(t) ?? 0),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
...transformed,
|
|
80
|
+
observedProportions,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function computeFit(
|
|
85
|
+
generationTime: number,
|
|
86
|
+
maxDate: YearMonthDayClass,
|
|
87
|
+
minDate: YearMonthDayClass,
|
|
88
|
+
requestData: {
|
|
89
|
+
t: number[];
|
|
90
|
+
k: number[];
|
|
91
|
+
n: number[];
|
|
92
|
+
},
|
|
93
|
+
signal: AbortSignal | undefined,
|
|
94
|
+
) {
|
|
57
95
|
const requestPayload = {
|
|
58
96
|
config: {
|
|
59
97
|
alpha: 0.95,
|
|
@@ -66,15 +104,29 @@ export async function queryRelativeGrowthAdvantage<LapisDateField extends string
|
|
|
66
104
|
},
|
|
67
105
|
data: requestData,
|
|
68
106
|
};
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
107
|
+
|
|
108
|
+
let response;
|
|
109
|
+
try {
|
|
110
|
+
response = await fetch('https://cov-spectrum.org/api/v2/computed/model/chen2021Fitness', {
|
|
111
|
+
method: 'POST',
|
|
112
|
+
headers: {
|
|
113
|
+
'Content-Type': 'application/json',
|
|
114
|
+
},
|
|
115
|
+
body: JSON.stringify(requestPayload),
|
|
116
|
+
signal,
|
|
117
|
+
});
|
|
118
|
+
} catch {
|
|
119
|
+
throw new UserFacingError(
|
|
120
|
+
'Failed to compute relative growth advantage',
|
|
121
|
+
'Could not connect to the server that computes the relative growth advantage. Please try again later.',
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
throw new NotEnoughDataToComputeFitError();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return (await response.json()) as {
|
|
78
130
|
estimatedAbsoluteNumbers: {
|
|
79
131
|
t: number[];
|
|
80
132
|
variantCases: number[];
|
|
@@ -109,21 +161,6 @@ export async function queryRelativeGrowthAdvantage<LapisDateField extends string
|
|
|
109
161
|
};
|
|
110
162
|
};
|
|
111
163
|
};
|
|
112
|
-
const transformed = {
|
|
113
|
-
...responseData,
|
|
114
|
-
estimatedProportions: {
|
|
115
|
-
...responseData.estimatedProportions,
|
|
116
|
-
t: responseData.estimatedProportions.t.map((t) => minDate.addDays(t)),
|
|
117
|
-
},
|
|
118
|
-
};
|
|
119
|
-
const observedProportions = transformed.estimatedProportions.t.map(
|
|
120
|
-
(t) => (numeratorCounts.get(t) ?? 0) / (denominatorCounts.get(t) ?? 0),
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
return {
|
|
124
|
-
...transformed,
|
|
125
|
-
observedProportions,
|
|
126
|
-
};
|
|
127
164
|
}
|
|
128
165
|
|
|
129
166
|
function toYearMonthDay(d: { date: string | null; count: number }) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expect, waitFor } from '@storybook/test';
|
|
1
|
+
import { expect, fn, userEvent, waitFor, type within } from '@storybook/test';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/web-components';
|
|
3
3
|
import { html } from 'lit';
|
|
4
4
|
|
|
@@ -23,12 +23,14 @@ const codeExample = String.raw`
|
|
|
23
23
|
dateColumn="myDateColumn"
|
|
24
24
|
></gs-date-range-selector>`;
|
|
25
25
|
|
|
26
|
+
const customDateRange = { label: 'CustomDateRange', dateFrom: '2021-01-01', dateTo: '2021-12-31' };
|
|
27
|
+
|
|
26
28
|
const meta: Meta<Required<DateRangeSelectorProps>> = {
|
|
27
29
|
title: 'Input/DateRangeSelector',
|
|
28
30
|
component: 'gs-date-range-selector',
|
|
29
31
|
parameters: withComponentDocs({
|
|
30
32
|
actions: {
|
|
31
|
-
handles: ['gs-date-range-changed', ...previewHandles],
|
|
33
|
+
handles: ['gs-date-range-filter-changed', 'gs-date-range-option-changed', ...previewHandles],
|
|
32
34
|
},
|
|
33
35
|
fetchMock: {},
|
|
34
36
|
componentDocs: {
|
|
@@ -66,7 +68,7 @@ const meta: Meta<Required<DateRangeSelectorProps>> = {
|
|
|
66
68
|
dateRangeOptionPresets.lastMonth,
|
|
67
69
|
dateRangeOptionPresets.last3Months,
|
|
68
70
|
dateRangeOptionPresets.allTimes,
|
|
69
|
-
|
|
71
|
+
customDateRange,
|
|
70
72
|
],
|
|
71
73
|
earliestDate: '1970-01-01',
|
|
72
74
|
initialValue: dateRangeOptionPresets.lastMonth.label,
|
|
@@ -80,7 +82,7 @@ const meta: Meta<Required<DateRangeSelectorProps>> = {
|
|
|
80
82
|
|
|
81
83
|
export default meta;
|
|
82
84
|
|
|
83
|
-
export const
|
|
85
|
+
export const Default: StoryObj<Required<DateRangeSelectorProps>> = {
|
|
84
86
|
render: (args) =>
|
|
85
87
|
html` <gs-app lapis="${LAPIS_URL}">
|
|
86
88
|
<div class="max-w-screen-lg">
|
|
@@ -95,19 +97,48 @@ export const DateRangeSelectorStory: StoryObj<Required<DateRangeSelectorProps>>
|
|
|
95
97
|
></gs-date-range-selector>
|
|
96
98
|
</div>
|
|
97
99
|
</gs-app>`,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const FiresEvents: StoryObj<Required<DateRangeSelectorProps>> = {
|
|
103
|
+
...Default,
|
|
98
104
|
play: async ({ canvasElement, step }) => {
|
|
99
105
|
const canvas = await withinShadowRoot(canvasElement, 'gs-date-range-selector');
|
|
100
|
-
|
|
106
|
+
|
|
107
|
+
const filterChangedListenerMock = fn();
|
|
108
|
+
const optionChangedListenerMock = fn();
|
|
109
|
+
await step('Setup event listener mock', async () => {
|
|
110
|
+
canvasElement.addEventListener('gs-date-range-filter-changed', filterChangedListenerMock);
|
|
111
|
+
canvasElement.addEventListener('gs-date-range-option-changed', optionChangedListenerMock);
|
|
112
|
+
});
|
|
101
113
|
|
|
102
114
|
await step('Expect last 6 months to be selected', async () => {
|
|
103
|
-
await expect(canvas
|
|
115
|
+
await expect(selectField(canvas)).toHaveValue('Last month');
|
|
104
116
|
await waitFor(() => {
|
|
105
|
-
expect(
|
|
117
|
+
expect(dateToPicker(canvas)).toHaveValue(toYYYYMMDD(new Date()));
|
|
106
118
|
});
|
|
107
119
|
});
|
|
108
120
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
121
|
+
await step('Expect event to be fired when selecting a different value', async () => {
|
|
122
|
+
await userEvent.selectOptions(selectField(canvas), 'CustomDateRange');
|
|
123
|
+
await expect(dateToPicker(canvas)).toHaveValue(customDateRange.dateTo);
|
|
124
|
+
|
|
125
|
+
await expect(filterChangedListenerMock).toHaveBeenCalledWith(
|
|
126
|
+
expect.objectContaining({
|
|
127
|
+
detail: {
|
|
128
|
+
aDateColumnFrom: customDateRange.dateFrom,
|
|
129
|
+
aDateColumnTo: customDateRange.dateTo,
|
|
130
|
+
},
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
await expect(optionChangedListenerMock).toHaveBeenCalledWith(
|
|
135
|
+
expect.objectContaining({
|
|
136
|
+
detail: customDateRange.label,
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
|
+
});
|
|
112
140
|
},
|
|
113
141
|
};
|
|
142
|
+
|
|
143
|
+
const dateToPicker = (canvas: ReturnType<typeof within>) => canvas.getByPlaceholderText('Date to');
|
|
144
|
+
const selectField = (canvas: ReturnType<typeof within>) => canvas.getByRole('combobox');
|
|
@@ -2,6 +2,7 @@ import { customElement, property } from 'lit/decorators.js';
|
|
|
2
2
|
import { type DetailedHTMLProps, type HTMLAttributes } from 'react';
|
|
3
3
|
|
|
4
4
|
import { DateRangeSelector, type DateRangeSelectorProps } from '../../preact/dateRangeSelector/date-range-selector';
|
|
5
|
+
import { type DateRangeOptionChangedEvent } from '../../preact/dateRangeSelector/dateRangeOption';
|
|
5
6
|
import { type Equals, type Expect } from '../../utils/typeAssertions';
|
|
6
7
|
import { PreactLitAdapter } from '../PreactLitAdapter';
|
|
7
8
|
|
|
@@ -18,7 +19,7 @@ import { PreactLitAdapter } from '../PreactLitAdapter';
|
|
|
18
19
|
* Setting a value in either of the date pickers will set the select field to "custom",
|
|
19
20
|
* which represents an arbitrary date range.
|
|
20
21
|
*
|
|
21
|
-
* @fires {CustomEvent<{ `${dateColumn}From`: string; `${dateColumn}To`: string; }>} gs-date-range-changed
|
|
22
|
+
* @fires {CustomEvent<{ `${dateColumn}From`: string; `${dateColumn}To`: string; }>} gs-date-range-filter-changed
|
|
22
23
|
* Fired when:
|
|
23
24
|
* - The select field is changed,
|
|
24
25
|
* - A date is selected in either of the date pickers,
|
|
@@ -33,6 +34,18 @@ import { PreactLitAdapter } from '../PreactLitAdapter';
|
|
|
33
34
|
* }
|
|
34
35
|
* ```
|
|
35
36
|
* will be fired.
|
|
37
|
+
*
|
|
38
|
+
* Use this event, when you want to use the filter directly as a LAPIS filter.
|
|
39
|
+
*
|
|
40
|
+
*
|
|
41
|
+
* @fires {CustomEvent<{ string | {dateFrom: string, dateTo: string}}>} gs-date-range-option-changed
|
|
42
|
+
* Fired when:
|
|
43
|
+
* - The select field is changed,
|
|
44
|
+
* - A date is selected in either of the date pickers,
|
|
45
|
+
* - A date was typed into either of the date input fields, and the input field loses focus ("on blur").
|
|
46
|
+
* Contains the selected dateRangeOption or when users select custom values it contains the selected dates.
|
|
47
|
+
*
|
|
48
|
+
* Use this event, when you want to control this component in your JS application.
|
|
36
49
|
*/
|
|
37
50
|
@customElement('gs-date-range-selector')
|
|
38
51
|
export class DateRangeSelectorComponent extends PreactLitAdapter {
|
|
@@ -116,7 +129,8 @@ declare global {
|
|
|
116
129
|
}
|
|
117
130
|
|
|
118
131
|
interface HTMLElementEventMap {
|
|
119
|
-
'gs-date-range-changed': CustomEvent<Record<string, string>>;
|
|
132
|
+
'gs-date-range-filter-changed': CustomEvent<Record<string, string>>;
|
|
133
|
+
'gs-date-range-option-changed': DateRangeOptionChangedEvent;
|
|
120
134
|
}
|
|
121
135
|
}
|
|
122
136
|
|