@genspectrum/dashboard-components 0.6.19 → 0.7.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/custom-elements.json +18 -18
- package/dist/assets/mutationOverTimeWorker-BOCXtKzd.js.map +1 -0
- package/dist/dashboard-components.js +296 -302
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +98 -48
- package/package.json +1 -3
- package/src/index.ts +1 -0
- package/src/preact/aggregatedData/aggregate.tsx +41 -33
- package/src/preact/dateRangeSelector/computeInitialValues.spec.ts +53 -38
- package/src/preact/dateRangeSelector/computeInitialValues.ts +17 -23
- package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +46 -32
- package/src/preact/dateRangeSelector/date-range-selector.tsx +24 -26
- package/src/preact/dateRangeSelector/dateRangeOption.ts +65 -0
- package/src/preact/dateRangeSelector/selectableOptions.ts +17 -66
- package/src/preact/mutationComparison/mutation-comparison.tsx +32 -34
- package/src/preact/mutations/mutations.tsx +63 -56
- package/src/preact/mutationsOverTime/MutationOverTimeData.ts +20 -0
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +2 -3
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +2 -2
- package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +3 -3
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +40 -43
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +46 -64
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +29 -36
- package/src/query/queryMutationsOverTime.ts +3 -5
- package/src/utils/map2d.spec.ts +52 -13
- package/src/utils/map2d.ts +3 -4
- package/src/web-components/input/gs-date-range-selector.stories.ts +16 -28
- package/src/web-components/input/gs-date-range-selector.tsx +17 -32
- package/standalone-bundle/dashboard-components.js +14322 -15115
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/dist/assets/mutationOverTimeWorker-BdzqDqvO.js.map +0 -1
|
@@ -1,36 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
getDatesForSelectorValue,
|
|
4
|
-
getSelectableOptions,
|
|
5
|
-
PRESET_VALUE_CUSTOM,
|
|
6
|
-
PRESET_VALUE_LAST_6_MONTHS,
|
|
7
|
-
type PresetOptionValues,
|
|
8
|
-
} from './selectableOptions';
|
|
1
|
+
import { type DateRangeOption } from './dateRangeOption';
|
|
2
|
+
import { getDatesForSelectorValue, getSelectableOptions } from './selectableOptions';
|
|
9
3
|
import { UserFacingError } from '../components/error-display';
|
|
10
4
|
|
|
11
|
-
export function computeInitialValues
|
|
12
|
-
initialValue:
|
|
5
|
+
export function computeInitialValues(
|
|
6
|
+
initialValue: string | undefined,
|
|
13
7
|
initialDateFrom: string | undefined,
|
|
14
8
|
initialDateTo: string | undefined,
|
|
15
9
|
earliestDate: string,
|
|
16
|
-
|
|
10
|
+
dateRangeOptions: DateRangeOption[],
|
|
17
11
|
): {
|
|
18
|
-
initialSelectedDateRange:
|
|
12
|
+
initialSelectedDateRange: string | undefined;
|
|
19
13
|
initialSelectedDateFrom: Date;
|
|
20
14
|
initialSelectedDateTo: Date;
|
|
21
15
|
} {
|
|
22
16
|
if (isUndefinedOrEmpty(initialDateFrom) && isUndefinedOrEmpty(initialDateTo)) {
|
|
23
|
-
const selectableOptions = getSelectableOptions(
|
|
24
|
-
const initialSelectedDateRange =
|
|
25
|
-
initialValue !== undefined && selectableOptions.some((option) => option.value === initialValue)
|
|
26
|
-
? initialValue
|
|
27
|
-
: PRESET_VALUE_LAST_6_MONTHS;
|
|
17
|
+
const selectableOptions = getSelectableOptions(dateRangeOptions);
|
|
18
|
+
const initialSelectedDateRange = selectableOptions.find((option) => option.value === initialValue)?.value;
|
|
28
19
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
if (initialValue !== undefined && initialSelectedDateRange === undefined) {
|
|
21
|
+
throw new UserFacingError(
|
|
22
|
+
'Invalid initialValue',
|
|
23
|
+
`Invalid initialValue "${initialValue}", It must be one of ${selectableOptions.map((option) => `'${option.value}'`).join(', ')}`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { dateFrom, dateTo } = getDatesForSelectorValue(initialSelectedDateRange, dateRangeOptions, earliestDate);
|
|
34
28
|
|
|
35
29
|
return {
|
|
36
30
|
initialSelectedDateRange,
|
|
@@ -62,7 +56,7 @@ export function computeInitialValues<CustomLabel extends string>(
|
|
|
62
56
|
}
|
|
63
57
|
|
|
64
58
|
return {
|
|
65
|
-
initialSelectedDateRange:
|
|
59
|
+
initialSelectedDateRange: undefined,
|
|
66
60
|
initialSelectedDateFrom,
|
|
67
61
|
initialSelectedDateTo,
|
|
68
62
|
};
|
|
@@ -1,24 +1,16 @@
|
|
|
1
1
|
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
-
import { expect, waitFor, within } from '@storybook/test';
|
|
2
|
+
import { expect, userEvent, waitFor, within } from '@storybook/test';
|
|
3
3
|
import dayjs from 'dayjs/esm';
|
|
4
4
|
|
|
5
5
|
import { DateRangeSelector, type DateRangeSelectorProps } from './date-range-selector';
|
|
6
|
-
import {
|
|
7
|
-
PRESET_VALUE_ALL_TIMES,
|
|
8
|
-
PRESET_VALUE_CUSTOM,
|
|
9
|
-
PRESET_VALUE_LAST_2_MONTHS,
|
|
10
|
-
PRESET_VALUE_LAST_2_WEEKS,
|
|
11
|
-
PRESET_VALUE_LAST_3_MONTHS,
|
|
12
|
-
PRESET_VALUE_LAST_6_MONTHS,
|
|
13
|
-
PRESET_VALUE_LAST_MONTH,
|
|
14
|
-
} from './selectableOptions';
|
|
15
6
|
import { previewHandles } from '../../../.storybook/preview';
|
|
16
7
|
import { LAPIS_URL } from '../../constants';
|
|
17
8
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
9
|
+
import { dateRangeOptionPresets } from './dateRangeOption';
|
|
18
10
|
|
|
19
11
|
const earliestDate = '1970-01-01';
|
|
20
12
|
|
|
21
|
-
const meta: Meta<DateRangeSelectorProps
|
|
13
|
+
const meta: Meta<DateRangeSelectorProps> = {
|
|
22
14
|
title: 'Input/DateRangeSelector',
|
|
23
15
|
component: DateRangeSelector,
|
|
24
16
|
parameters: {
|
|
@@ -32,18 +24,9 @@ const meta: Meta<DateRangeSelectorProps<'CustomDateRange'>> = {
|
|
|
32
24
|
control: {
|
|
33
25
|
type: 'select',
|
|
34
26
|
},
|
|
35
|
-
options: [
|
|
36
|
-
PRESET_VALUE_CUSTOM,
|
|
37
|
-
PRESET_VALUE_ALL_TIMES,
|
|
38
|
-
PRESET_VALUE_LAST_2_WEEKS,
|
|
39
|
-
PRESET_VALUE_LAST_MONTH,
|
|
40
|
-
PRESET_VALUE_LAST_2_MONTHS,
|
|
41
|
-
PRESET_VALUE_LAST_3_MONTHS,
|
|
42
|
-
PRESET_VALUE_LAST_6_MONTHS,
|
|
43
|
-
'CustomDateRange',
|
|
44
|
-
],
|
|
27
|
+
options: [dateRangeOptionPresets.lastMonth.label, dateRangeOptionPresets.allTimes.label, 'CustomDateRange'],
|
|
45
28
|
},
|
|
46
|
-
|
|
29
|
+
dateRangeOptions: {
|
|
47
30
|
control: {
|
|
48
31
|
type: 'object',
|
|
49
32
|
},
|
|
@@ -60,9 +43,17 @@ const meta: Meta<DateRangeSelectorProps<'CustomDateRange'>> = {
|
|
|
60
43
|
},
|
|
61
44
|
},
|
|
62
45
|
args: {
|
|
63
|
-
|
|
46
|
+
dateRangeOptions: [
|
|
47
|
+
dateRangeOptionPresets.lastMonth,
|
|
48
|
+
dateRangeOptionPresets.allTimes,
|
|
49
|
+
{
|
|
50
|
+
label: 'CustomDateRange',
|
|
51
|
+
dateFrom: '2021-01-01',
|
|
52
|
+
dateTo: '2021-12-31',
|
|
53
|
+
},
|
|
54
|
+
],
|
|
64
55
|
earliestDate,
|
|
65
|
-
initialValue:
|
|
56
|
+
initialValue: dateRangeOptionPresets.lastMonth.label,
|
|
66
57
|
dateColumn: 'aDateColumn',
|
|
67
58
|
width: '100%',
|
|
68
59
|
initialDateFrom: '',
|
|
@@ -72,11 +63,11 @@ const meta: Meta<DateRangeSelectorProps<'CustomDateRange'>> = {
|
|
|
72
63
|
|
|
73
64
|
export default meta;
|
|
74
65
|
|
|
75
|
-
export const Primary: StoryObj<DateRangeSelectorProps
|
|
66
|
+
export const Primary: StoryObj<DateRangeSelectorProps> = {
|
|
76
67
|
render: (args) => (
|
|
77
68
|
<LapisUrlContext.Provider value={LAPIS_URL}>
|
|
78
69
|
<DateRangeSelector
|
|
79
|
-
|
|
70
|
+
dateRangeOptions={args.dateRangeOptions}
|
|
80
71
|
earliestDate={args.earliestDate}
|
|
81
72
|
initialValue={args.initialValue}
|
|
82
73
|
initialDateFrom={args.initialDateFrom}
|
|
@@ -88,7 +79,7 @@ export const Primary: StoryObj<DateRangeSelectorProps<'CustomDateRange'>> = {
|
|
|
88
79
|
),
|
|
89
80
|
};
|
|
90
81
|
|
|
91
|
-
export const SetCorrectInitialValues: StoryObj<DateRangeSelectorProps
|
|
82
|
+
export const SetCorrectInitialValues: StoryObj<DateRangeSelectorProps> = {
|
|
92
83
|
...Primary,
|
|
93
84
|
args: {
|
|
94
85
|
...Primary.args,
|
|
@@ -111,7 +102,7 @@ export const SetCorrectInitialValues: StoryObj<DateRangeSelectorProps<'CustomDat
|
|
|
111
102
|
|
|
112
103
|
const initialDateFrom = '2000-01-01';
|
|
113
104
|
|
|
114
|
-
export const SetCorrectInitialDateFrom: StoryObj<DateRangeSelectorProps
|
|
105
|
+
export const SetCorrectInitialDateFrom: StoryObj<DateRangeSelectorProps> = {
|
|
115
106
|
...Primary,
|
|
116
107
|
args: {
|
|
117
108
|
...Primary.args,
|
|
@@ -125,7 +116,7 @@ export const SetCorrectInitialDateFrom: StoryObj<DateRangeSelectorProps<'CustomD
|
|
|
125
116
|
const selectField = () => canvas.getByRole('combobox');
|
|
126
117
|
|
|
127
118
|
await waitFor(() => {
|
|
128
|
-
expect(selectField()).toHaveValue(
|
|
119
|
+
expect(selectField()).toHaveValue('Custom');
|
|
129
120
|
expect(dateFrom()).toHaveValue(initialDateFrom);
|
|
130
121
|
expect(dateTo()).toHaveValue(dayjs().format('YYYY-MM-DD'));
|
|
131
122
|
});
|
|
@@ -134,7 +125,7 @@ export const SetCorrectInitialDateFrom: StoryObj<DateRangeSelectorProps<'CustomD
|
|
|
134
125
|
|
|
135
126
|
const initialDateTo = '2000-01-01';
|
|
136
127
|
|
|
137
|
-
export const SetCorrectInitialDateTo: StoryObj<DateRangeSelectorProps
|
|
128
|
+
export const SetCorrectInitialDateTo: StoryObj<DateRangeSelectorProps> = {
|
|
138
129
|
...Primary,
|
|
139
130
|
args: {
|
|
140
131
|
...Primary.args,
|
|
@@ -148,14 +139,37 @@ export const SetCorrectInitialDateTo: StoryObj<DateRangeSelectorProps<'CustomDat
|
|
|
148
139
|
const selectField = () => canvas.getByRole('combobox');
|
|
149
140
|
|
|
150
141
|
await waitFor(() => {
|
|
151
|
-
expect(selectField()).toHaveValue(
|
|
142
|
+
expect(selectField()).toHaveValue('Custom');
|
|
152
143
|
expect(dateFrom()).toHaveValue(earliestDate);
|
|
153
144
|
expect(dateTo()).toHaveValue(initialDateTo);
|
|
154
145
|
});
|
|
155
146
|
},
|
|
156
147
|
};
|
|
157
148
|
|
|
158
|
-
export const
|
|
149
|
+
export const ChangingDateSetsOptionToCustom: StoryObj<DateRangeSelectorProps> = {
|
|
150
|
+
...Primary,
|
|
151
|
+
play: async ({ canvasElement }) => {
|
|
152
|
+
const canvas = within(canvasElement);
|
|
153
|
+
|
|
154
|
+
const dateFrom = () => canvas.getByPlaceholderText('Date from');
|
|
155
|
+
const dateTo = () => canvas.getByPlaceholderText('Date to');
|
|
156
|
+
const selectField = () => canvas.getByRole('combobox');
|
|
157
|
+
|
|
158
|
+
await waitFor(() => {
|
|
159
|
+
expect(selectField()).toHaveValue('Last month');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
await userEvent.type(dateFrom(), '{backspace>12}');
|
|
163
|
+
await userEvent.type(dateFrom(), '2000-01-01');
|
|
164
|
+
await userEvent.click(dateTo());
|
|
165
|
+
|
|
166
|
+
await waitFor(() => {
|
|
167
|
+
expect(selectField()).toHaveValue('Custom');
|
|
168
|
+
});
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export const HandlesInvalidInitialDateFrom: StoryObj<DateRangeSelectorProps> = {
|
|
159
173
|
...Primary,
|
|
160
174
|
args: {
|
|
161
175
|
...Primary.args,
|
|
@@ -4,33 +4,28 @@ import { useEffect, useRef, useState } from 'preact/hooks';
|
|
|
4
4
|
|
|
5
5
|
import { computeInitialValues } from './computeInitialValues';
|
|
6
6
|
import { toYYYYMMDD } from './dateConversion';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
getDatesForSelectorValue,
|
|
10
|
-
getSelectableOptions,
|
|
11
|
-
type PresetOptionValues,
|
|
12
|
-
} from './selectableOptions';
|
|
7
|
+
import { type DateRangeOption } from './dateRangeOption';
|
|
8
|
+
import { getDatesForSelectorValue, getSelectableOptions } from './selectableOptions';
|
|
13
9
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
14
10
|
import { Select } from '../components/select';
|
|
15
11
|
import type { ScaleType } from '../shared/charts/getYAxisScale';
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
const customOption = 'Custom';
|
|
14
|
+
|
|
15
|
+
export interface DateRangeSelectorProps extends DateRangeSelectorPropsInner {
|
|
18
16
|
width: string;
|
|
19
17
|
}
|
|
20
18
|
|
|
21
|
-
export interface DateRangeSelectorPropsInner
|
|
22
|
-
|
|
19
|
+
export interface DateRangeSelectorPropsInner {
|
|
20
|
+
dateRangeOptions: DateRangeOption[];
|
|
23
21
|
earliestDate: string;
|
|
24
|
-
initialValue:
|
|
22
|
+
initialValue: string | undefined;
|
|
25
23
|
initialDateFrom: string;
|
|
26
24
|
initialDateTo: string;
|
|
27
25
|
dateColumn: string;
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
export const DateRangeSelector =
|
|
31
|
-
width,
|
|
32
|
-
...innerProps
|
|
33
|
-
}: DateRangeSelectorProps<CustomLabel>) => {
|
|
28
|
+
export const DateRangeSelector = ({ width, ...innerProps }: DateRangeSelectorProps) => {
|
|
34
29
|
const size = { width, height: '3rem' };
|
|
35
30
|
|
|
36
31
|
return (
|
|
@@ -42,20 +37,20 @@ export const DateRangeSelector = <CustomLabel extends string>({
|
|
|
42
37
|
);
|
|
43
38
|
};
|
|
44
39
|
|
|
45
|
-
export const DateRangeSelectorInner =
|
|
46
|
-
|
|
40
|
+
export const DateRangeSelectorInner = ({
|
|
41
|
+
dateRangeOptions,
|
|
47
42
|
earliestDate = '1900-01-01',
|
|
48
43
|
initialValue,
|
|
49
44
|
dateColumn,
|
|
50
45
|
initialDateFrom,
|
|
51
46
|
initialDateTo,
|
|
52
|
-
}: DateRangeSelectorPropsInner
|
|
47
|
+
}: DateRangeSelectorPropsInner) => {
|
|
53
48
|
const initialValues = computeInitialValues(
|
|
54
49
|
initialValue,
|
|
55
50
|
initialDateFrom,
|
|
56
51
|
initialDateTo,
|
|
57
52
|
earliestDate,
|
|
58
|
-
|
|
53
|
+
dateRangeOptions,
|
|
59
54
|
);
|
|
60
55
|
|
|
61
56
|
const fromDatePickerRef = useRef<HTMLInputElement>(null);
|
|
@@ -64,7 +59,7 @@ export const DateRangeSelectorInner = <CustomLabel extends string>({
|
|
|
64
59
|
const [dateFromPicker, setDateFromPicker] = useState<flatpickr.Instance | null>(null);
|
|
65
60
|
const [dateToPicker, setDateToPicker] = useState<flatpickr.Instance | null>(null);
|
|
66
61
|
|
|
67
|
-
const [selectedDateRange, setSelectedDateRange] = useState<
|
|
62
|
+
const [selectedDateRange, setSelectedDateRange] = useState<string | undefined>(
|
|
68
63
|
initialValues.initialSelectedDateRange,
|
|
69
64
|
);
|
|
70
65
|
|
|
@@ -104,10 +99,10 @@ export const DateRangeSelectorInner = <CustomLabel extends string>({
|
|
|
104
99
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
105
100
|
}, [fromDatePickerRef, toDatePickerRef]);
|
|
106
101
|
|
|
107
|
-
const onSelectChange = (value:
|
|
102
|
+
const onSelectChange = (value: string) => {
|
|
108
103
|
setSelectedDateRange(value);
|
|
109
104
|
|
|
110
|
-
const dateRange = getDatesForSelectorValue(value,
|
|
105
|
+
const dateRange = getDatesForSelectorValue(value, dateRangeOptions, earliestDate);
|
|
111
106
|
|
|
112
107
|
dateToPicker?.set('minDate', dateRange.dateFrom);
|
|
113
108
|
dateFromPicker?.set('maxDate', dateRange.dateTo);
|
|
@@ -130,7 +125,7 @@ export const DateRangeSelectorInner = <CustomLabel extends string>({
|
|
|
130
125
|
|
|
131
126
|
selectedDates.dateFrom = dateFromPicker?.selectedDates[0] || new Date();
|
|
132
127
|
dateToPicker?.set('minDate', dateFromPicker?.selectedDates[0]);
|
|
133
|
-
setSelectedDateRange(
|
|
128
|
+
setSelectedDateRange(customOption);
|
|
134
129
|
|
|
135
130
|
submit();
|
|
136
131
|
};
|
|
@@ -142,7 +137,7 @@ export const DateRangeSelectorInner = <CustomLabel extends string>({
|
|
|
142
137
|
|
|
143
138
|
selectedDates.dateTo = dateToPicker?.selectedDates[0] || new Date();
|
|
144
139
|
dateFromPicker?.set('maxDate', dateToPicker?.selectedDates[0]);
|
|
145
|
-
setSelectedDateRange(
|
|
140
|
+
setSelectedDateRange(customOption);
|
|
146
141
|
|
|
147
142
|
submit();
|
|
148
143
|
};
|
|
@@ -168,14 +163,17 @@ export const DateRangeSelectorInner = <CustomLabel extends string>({
|
|
|
168
163
|
return (
|
|
169
164
|
<div class='flex flex-wrap' ref={divRef}>
|
|
170
165
|
<Select
|
|
171
|
-
items={
|
|
172
|
-
|
|
166
|
+
items={[
|
|
167
|
+
...getSelectableOptions(dateRangeOptions),
|
|
168
|
+
{ label: customOption, value: customOption, disabled: true },
|
|
169
|
+
]}
|
|
170
|
+
selected={selectedDateRange ?? customOption}
|
|
173
171
|
selectStyle='select-bordered rounded-none flex-grow min-w-[7.5rem]'
|
|
174
172
|
onChange={(event: Event) => {
|
|
175
173
|
event.preventDefault();
|
|
176
174
|
const select = event.target as HTMLSelectElement;
|
|
177
175
|
const value = select.value as ScaleType;
|
|
178
|
-
onSelectChange(value
|
|
176
|
+
onSelectChange(value);
|
|
179
177
|
}}
|
|
180
178
|
/>
|
|
181
179
|
<div className={'flex flex-wrap flex-grow'}>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { toYYYYMMDD } from './dateConversion';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A date range option that can be used in the `gs-date-range-selector` component.
|
|
5
|
+
*/
|
|
6
|
+
export type DateRangeOption = {
|
|
7
|
+
/** The label of the date range option that will be shown to the user */
|
|
8
|
+
label: string;
|
|
9
|
+
/**
|
|
10
|
+
* The start date of the date range in the format `YYYY-MM-DD`.
|
|
11
|
+
* If not set, the date range selector will default to the `earliestDate` property.
|
|
12
|
+
*/
|
|
13
|
+
dateFrom?: string;
|
|
14
|
+
/**
|
|
15
|
+
* The end date of the date range in the format `YYYY-MM-DD`.
|
|
16
|
+
* If not set, the date range selector will default to the current date.
|
|
17
|
+
*/
|
|
18
|
+
dateTo?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const today = new Date();
|
|
22
|
+
|
|
23
|
+
const twoWeeksAgo = new Date();
|
|
24
|
+
twoWeeksAgo.setDate(today.getDate() - 14);
|
|
25
|
+
|
|
26
|
+
const lastMonth = new Date(today);
|
|
27
|
+
lastMonth.setMonth(today.getMonth() - 1);
|
|
28
|
+
|
|
29
|
+
const last2Months = new Date(today);
|
|
30
|
+
last2Months.setMonth(today.getMonth() - 2);
|
|
31
|
+
|
|
32
|
+
const last3Months = new Date(today);
|
|
33
|
+
last3Months.setMonth(today.getMonth() - 3);
|
|
34
|
+
|
|
35
|
+
const last6Months = new Date(today);
|
|
36
|
+
last6Months.setMonth(today.getMonth() - 6);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Presets for the `gs-date-range-selector` component that can be used as `dateRangeOptions`.
|
|
40
|
+
*/
|
|
41
|
+
export const dateRangeOptionPresets = {
|
|
42
|
+
last2Weeks: {
|
|
43
|
+
label: 'Last 2 weeks',
|
|
44
|
+
dateFrom: toYYYYMMDD(twoWeeksAgo),
|
|
45
|
+
},
|
|
46
|
+
lastMonth: {
|
|
47
|
+
label: 'Last month',
|
|
48
|
+
dateFrom: toYYYYMMDD(lastMonth),
|
|
49
|
+
},
|
|
50
|
+
last2Months: {
|
|
51
|
+
label: 'Last 2 months',
|
|
52
|
+
dateFrom: toYYYYMMDD(last2Months),
|
|
53
|
+
},
|
|
54
|
+
last3Months: {
|
|
55
|
+
label: 'Last 3 months',
|
|
56
|
+
dateFrom: toYYYYMMDD(last3Months),
|
|
57
|
+
},
|
|
58
|
+
last6Months: {
|
|
59
|
+
label: 'Last 6 months',
|
|
60
|
+
dateFrom: toYYYYMMDD(last6Months),
|
|
61
|
+
},
|
|
62
|
+
allTimes: {
|
|
63
|
+
label: 'All times',
|
|
64
|
+
},
|
|
65
|
+
} satisfies Record<string, DateRangeOption>;
|
|
@@ -1,79 +1,30 @@
|
|
|
1
|
-
|
|
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';
|
|
1
|
+
import { type DateRangeOption } from './dateRangeOption';
|
|
8
2
|
|
|
9
|
-
export const
|
|
10
|
-
|
|
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) => {
|
|
3
|
+
export const getSelectableOptions = (dateRangeOptions: DateRangeOption[]) => {
|
|
4
|
+
return dateRangeOptions.map((customSelectOption) => {
|
|
29
5
|
return { label: customSelectOption.label, value: customSelectOption.label };
|
|
30
6
|
});
|
|
31
|
-
|
|
32
|
-
return [...presetOptions, ...customOptions];
|
|
33
7
|
};
|
|
34
8
|
|
|
35
|
-
export const getDatesForSelectorValue =
|
|
36
|
-
|
|
37
|
-
|
|
9
|
+
export const getDatesForSelectorValue = (
|
|
10
|
+
initialSelectedDateRange: string | undefined,
|
|
11
|
+
dateRangeOptions: DateRangeOption[],
|
|
38
12
|
earliestDate: string,
|
|
39
13
|
) => {
|
|
40
14
|
const today = new Date();
|
|
15
|
+
const defaultDates = { dateFrom: new Date(earliestDate), dateTo: today };
|
|
41
16
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return { dateFrom: new Date(customSelectOption.dateFrom), dateTo: new Date(customSelectOption.dateTo) };
|
|
17
|
+
if (initialSelectedDateRange === undefined) {
|
|
18
|
+
return defaultDates;
|
|
45
19
|
}
|
|
46
20
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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 };
|
|
21
|
+
const dateRangeOption = dateRangeOptions.find((option) => option.label === initialSelectedDateRange);
|
|
22
|
+
if (dateRangeOption) {
|
|
23
|
+
return {
|
|
24
|
+
dateFrom: new Date(dateRangeOption.dateFrom ?? earliestDate),
|
|
25
|
+
dateTo: new Date(dateRangeOption.dateTo ?? today),
|
|
26
|
+
};
|
|
78
27
|
}
|
|
28
|
+
|
|
29
|
+
return defaultDates;
|
|
79
30
|
};
|
|
@@ -11,7 +11,7 @@ import { CsvDownloadButton } from '../components/csv-download-button';
|
|
|
11
11
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
12
12
|
import { ErrorDisplay } from '../components/error-display';
|
|
13
13
|
import { Fullscreen } from '../components/fullscreen';
|
|
14
|
-
import Info from '../components/info';
|
|
14
|
+
import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../components/info';
|
|
15
15
|
import { LoadingDisplay } from '../components/loading-display';
|
|
16
16
|
import { type DisplayedMutationType, MutationTypeSelector } from '../components/mutation-type-selector';
|
|
17
17
|
import { NoDataDisplay } from '../components/no-data-display';
|
|
@@ -24,36 +24,30 @@ import { useQuery } from '../useQuery';
|
|
|
24
24
|
|
|
25
25
|
export type View = 'table' | 'venn';
|
|
26
26
|
|
|
27
|
-
export interface MutationComparisonProps
|
|
27
|
+
export interface MutationComparisonProps {
|
|
28
28
|
width: string;
|
|
29
29
|
height: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface MutationComparisonInnerProps {
|
|
33
30
|
lapisFilters: NamedLapisFilter[];
|
|
34
31
|
sequenceType: SequenceType;
|
|
35
32
|
views: View[];
|
|
36
33
|
pageSize: boolean | number;
|
|
37
34
|
}
|
|
38
35
|
|
|
39
|
-
export const MutationComparison: FunctionComponent<MutationComparisonProps> = (
|
|
36
|
+
export const MutationComparison: FunctionComponent<MutationComparisonProps> = (componentProps) => {
|
|
37
|
+
const { width, height } = componentProps;
|
|
40
38
|
const size = { height, width };
|
|
41
39
|
|
|
42
40
|
return (
|
|
43
41
|
<ErrorBoundary size={size}>
|
|
44
42
|
<ResizeContainer size={size}>
|
|
45
|
-
<MutationComparisonInner {...
|
|
43
|
+
<MutationComparisonInner {...componentProps} />
|
|
46
44
|
</ResizeContainer>
|
|
47
45
|
</ErrorBoundary>
|
|
48
46
|
);
|
|
49
47
|
};
|
|
50
48
|
|
|
51
|
-
export const MutationComparisonInner: FunctionComponent<
|
|
52
|
-
lapisFilters,
|
|
53
|
-
sequenceType,
|
|
54
|
-
views,
|
|
55
|
-
pageSize,
|
|
56
|
-
}) => {
|
|
49
|
+
export const MutationComparisonInner: FunctionComponent<MutationComparisonProps> = (componentProps) => {
|
|
50
|
+
const { lapisFilters, sequenceType } = componentProps;
|
|
57
51
|
const lapis = useContext(LapisUrlContext);
|
|
58
52
|
|
|
59
53
|
const { data, error, isLoading } = useQuery(async () => {
|
|
@@ -72,35 +66,21 @@ export const MutationComparisonInner: FunctionComponent<MutationComparisonInnerP
|
|
|
72
66
|
return <NoDataDisplay />;
|
|
73
67
|
}
|
|
74
68
|
|
|
75
|
-
return
|
|
76
|
-
<MutationComparisonTabs
|
|
77
|
-
data={data.mutationData}
|
|
78
|
-
sequenceType={sequenceType}
|
|
79
|
-
views={views}
|
|
80
|
-
pageSize={pageSize}
|
|
81
|
-
/>
|
|
82
|
-
);
|
|
69
|
+
return <MutationComparisonTabs data={data.mutationData} originalComponentProps={componentProps} />;
|
|
83
70
|
};
|
|
84
71
|
|
|
85
72
|
type MutationComparisonTabsProps = {
|
|
86
73
|
data: MutationData[];
|
|
87
|
-
|
|
88
|
-
sequenceType: SequenceType;
|
|
89
|
-
pageSize: boolean | number;
|
|
74
|
+
originalComponentProps: MutationComparisonProps;
|
|
90
75
|
};
|
|
91
76
|
|
|
92
|
-
const MutationComparisonTabs: FunctionComponent<MutationComparisonTabsProps> = ({
|
|
93
|
-
data,
|
|
94
|
-
views,
|
|
95
|
-
sequenceType,
|
|
96
|
-
pageSize,
|
|
97
|
-
}) => {
|
|
77
|
+
const MutationComparisonTabs: FunctionComponent<MutationComparisonTabsProps> = ({ data, originalComponentProps }) => {
|
|
98
78
|
const [proportionInterval, setProportionInterval] = useState({ min: 0.5, max: 1 });
|
|
99
79
|
const [displayedMutationTypes, setDisplayedMutationTypes] = useState<DisplayedMutationType[]>([
|
|
100
80
|
{ label: 'Substitutions', checked: true, type: 'substitution' },
|
|
101
81
|
{ label: 'Deletions', checked: true, type: 'deletion' },
|
|
102
82
|
]);
|
|
103
|
-
const [displayedSegments, setDisplayedSegments] = useDisplayedSegments(sequenceType);
|
|
83
|
+
const [displayedSegments, setDisplayedSegments] = useDisplayedSegments(originalComponentProps.sequenceType);
|
|
104
84
|
|
|
105
85
|
const filteredData = useMemo(
|
|
106
86
|
() => filterMutationData(data, displayedSegments, displayedMutationTypes),
|
|
@@ -116,7 +96,7 @@ const MutationComparisonTabs: FunctionComponent<MutationComparisonTabsProps> = (
|
|
|
116
96
|
<MutationComparisonTable
|
|
117
97
|
data={{ content: filteredData }}
|
|
118
98
|
proportionInterval={proportionInterval}
|
|
119
|
-
pageSize={pageSize}
|
|
99
|
+
pageSize={originalComponentProps.pageSize}
|
|
120
100
|
/>
|
|
121
101
|
),
|
|
122
102
|
};
|
|
@@ -133,7 +113,7 @@ const MutationComparisonTabs: FunctionComponent<MutationComparisonTabsProps> = (
|
|
|
133
113
|
}
|
|
134
114
|
};
|
|
135
115
|
|
|
136
|
-
const tabs = views.map((view) => getTab(view));
|
|
116
|
+
const tabs = originalComponentProps.views.map((view) => getTab(view));
|
|
137
117
|
|
|
138
118
|
return (
|
|
139
119
|
<Tabs
|
|
@@ -147,6 +127,7 @@ const MutationComparisonTabs: FunctionComponent<MutationComparisonTabsProps> = (
|
|
|
147
127
|
filteredData={filteredData}
|
|
148
128
|
proportionInterval={proportionInterval}
|
|
149
129
|
setProportionInterval={setProportionInterval}
|
|
130
|
+
originalComponentProps={originalComponentProps}
|
|
150
131
|
/>
|
|
151
132
|
}
|
|
152
133
|
/>
|
|
@@ -161,6 +142,7 @@ type ToolbarProps = {
|
|
|
161
142
|
filteredData: MutationData[];
|
|
162
143
|
proportionInterval: ProportionInterval;
|
|
163
144
|
setProportionInterval: Dispatch<StateUpdater<ProportionInterval>>;
|
|
145
|
+
originalComponentProps: MutationComparisonProps;
|
|
164
146
|
};
|
|
165
147
|
|
|
166
148
|
const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
@@ -171,6 +153,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
|
171
153
|
filteredData,
|
|
172
154
|
proportionInterval,
|
|
173
155
|
setProportionInterval,
|
|
156
|
+
originalComponentProps,
|
|
174
157
|
}) => {
|
|
175
158
|
return (
|
|
176
159
|
<>
|
|
@@ -189,8 +172,23 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
|
189
172
|
getData={() => getMutationComparisonTableData({ content: filteredData }, proportionInterval)}
|
|
190
173
|
filename='mutation_comparison.csv'
|
|
191
174
|
/>
|
|
192
|
-
<
|
|
175
|
+
<MutationComparisonInfo originalComponentProps={originalComponentProps} />
|
|
193
176
|
<Fullscreen />
|
|
194
177
|
</>
|
|
195
178
|
);
|
|
196
179
|
};
|
|
180
|
+
|
|
181
|
+
type MutationComparisonInfoProps = {
|
|
182
|
+
originalComponentProps: MutationComparisonProps;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const MutationComparisonInfo: FunctionComponent<MutationComparisonInfoProps> = ({ originalComponentProps }) => {
|
|
186
|
+
const lapis = useContext(LapisUrlContext);
|
|
187
|
+
return (
|
|
188
|
+
<Info>
|
|
189
|
+
<InfoHeadline1>Info for mutation comparison</InfoHeadline1>
|
|
190
|
+
<InfoParagraph>TODO: https://github.com/GenSpectrum/dashboard-components/issues/465</InfoParagraph>
|
|
191
|
+
<InfoComponentCode componentName='mutation-comparison' params={originalComponentProps} lapisUrl={lapis} />
|
|
192
|
+
</Info>
|
|
193
|
+
);
|
|
194
|
+
};
|