@genspectrum/dashboard-components 0.6.18 → 0.7.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 (118) hide show
  1. package/README.md +5 -12
  2. package/custom-elements.json +22 -22
  3. package/dist/assets/mutationOverTimeWorker-BOCXtKzd.js.map +1 -0
  4. package/dist/dashboard-components.js +301 -302
  5. package/dist/dashboard-components.js.map +1 -1
  6. package/dist/genspectrum-components.d.ts +60 -10
  7. package/dist/style.css +3 -2
  8. package/package.json +13 -4
  9. package/src/index.ts +1 -0
  10. package/src/operator/FetchInsertionsOperator.ts +2 -2
  11. package/src/operator/FetchSubstitutionsOrDeletionsOperator.ts +3 -3
  12. package/src/preact/dateRangeSelector/computeInitialValues.spec.ts +53 -38
  13. package/src/preact/dateRangeSelector/computeInitialValues.ts +17 -23
  14. package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +46 -32
  15. package/src/preact/dateRangeSelector/date-range-selector.tsx +24 -26
  16. package/src/preact/dateRangeSelector/dateRangeOption.ts +65 -0
  17. package/src/preact/dateRangeSelector/selectableOptions.ts +17 -66
  18. package/src/preact/mutationComparison/fetchMutationData.spec.ts +3 -3
  19. package/src/preact/mutationComparison/getMutationComparisonTableData.spec.ts +11 -11
  20. package/src/preact/mutationComparison/getMutationComparisonTableData.ts +4 -4
  21. package/src/preact/mutationComparison/mutation-comparison-table.tsx +2 -2
  22. package/src/preact/mutationFilter/mutation-filter.tsx +27 -18
  23. package/src/preact/mutationFilter/parseAndValidateMutation.ts +4 -4
  24. package/src/preact/mutationFilter/parseMutation.spec.ts +17 -17
  25. package/src/preact/mutations/getInsertionsTableData.spec.ts +3 -3
  26. package/src/preact/mutations/getMutationsGridData.spec.ts +9 -9
  27. package/src/preact/mutations/getMutationsTableData.spec.ts +7 -7
  28. package/src/preact/mutations/mutations-insertions-table.tsx +3 -3
  29. package/src/preact/mutations/mutations-table.tsx +3 -3
  30. package/src/preact/mutationsOverTime/MutationOverTimeData.ts +20 -0
  31. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutationsByDay.ts +45686 -0
  32. package/src/preact/mutationsOverTime/__mockData__/byWeek.ts +58989 -0
  33. package/src/preact/mutationsOverTime/__mockData__/defaultMockData.ts +103991 -0
  34. package/src/preact/mutationsOverTime/__mockData__/mockConversion.ts +54 -0
  35. package/src/preact/mutationsOverTime/__mockData__/showsMessageWhenTooManyMutations.ts +63690 -0
  36. package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +177 -161
  37. package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +17 -59
  38. package/src/preact/mutationsOverTime/mutationOverTimeWorker.mock.ts +27 -0
  39. package/src/preact/mutationsOverTime/mutationOverTimeWorker.ts +29 -0
  40. package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +13 -14
  41. package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +9 -334
  42. package/src/preact/mutationsOverTime/mutations-over-time.tsx +59 -54
  43. package/src/preact/numberSequencesOverTime/getNumberOfSequencesOverTimeTableData.ts +3 -3
  44. package/src/preact/prevalenceOverTime/getPrevalenceOverTimeTableData.spec.ts +5 -5
  45. package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +1 -1
  46. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +2 -2
  47. package/src/preact/shared/sort/sortInsertions.spec.ts +11 -11
  48. package/src/preact/shared/sort/sortInsertions.ts +2 -2
  49. package/src/preact/shared/sort/sortSubstitutionsAndDeletions.spec.ts +13 -13
  50. package/src/preact/shared/sort/sortSubstitutionsAndDeletions.ts +7 -4
  51. package/src/preact/webWorkers/useWebWorker.ts +51 -0
  52. package/src/preact/webWorkers/workerFunction.ts +14 -0
  53. package/src/query/queryAggregatedDataOverTime.ts +3 -3
  54. package/src/query/queryMutationsOverTime.spec.ts +272 -51
  55. package/src/query/queryMutationsOverTime.ts +114 -47
  56. package/src/query/queryPrevalenceOverTime.ts +2 -2
  57. package/src/query/queryRelativeGrowthAdvantage.ts +3 -3
  58. package/src/types.ts +25 -5
  59. package/src/utils/map2d.spec.ts +79 -12
  60. package/src/utils/map2d.ts +25 -5
  61. package/src/utils/mutations.spec.ts +20 -20
  62. package/src/utils/mutations.ts +80 -17
  63. package/src/utils/sort.ts +5 -2
  64. package/src/utils/temporal.spec.ts +27 -24
  65. package/src/utils/{temporal.ts → temporalClass.ts} +170 -72
  66. package/src/utils/temporalTestHelpers.ts +3 -3
  67. package/src/web-components/input/gs-date-range-selector.stories.ts +16 -28
  68. package/src/web-components/input/gs-date-range-selector.tsx +17 -32
  69. package/src/web-components/introduction.mdx +46 -0
  70. package/src/web-components/visualization/gs-mutations-over-time.stories.ts +6 -699
  71. package/src/web-components/visualization/gs-mutations-over-time.tsx +2 -2
  72. package/standalone-bundle/dashboard-components.js +12011 -12778
  73. package/standalone-bundle/dashboard-components.js.map +1 -1
  74. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_01.json +0 -13
  75. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_02.json +0 -13
  76. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_03.json +0 -13
  77. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_04.json +0 -13
  78. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_05.json +0 -13
  79. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_06.json +0 -13
  80. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_07.json +0 -13
  81. package/src/preact/mutationsOverTime/__mockData__/aggregated_20_01_2024.json +0 -13
  82. package/src/preact/mutationsOverTime/__mockData__/aggregated_21_01_2024.json +0 -13
  83. package/src/preact/mutationsOverTime/__mockData__/aggregated_22_01_2024.json +0 -13
  84. package/src/preact/mutationsOverTime/__mockData__/aggregated_23_01_2024.json +0 -13
  85. package/src/preact/mutationsOverTime/__mockData__/aggregated_24_01_2024.json +0 -13
  86. package/src/preact/mutationsOverTime/__mockData__/aggregated_25_01_2024.json +0 -13
  87. package/src/preact/mutationsOverTime/__mockData__/aggregated_26_01_2024.json +0 -13
  88. package/src/preact/mutationsOverTime/__mockData__/aggregated_byDay.json +0 -38
  89. package/src/preact/mutationsOverTime/__mockData__/aggregated_byWeek.json +0 -122
  90. package/src/preact/mutationsOverTime/__mockData__/aggregated_date.json +0 -642
  91. package/src/preact/mutationsOverTime/__mockData__/aggregated_tooManyMutations.json +0 -1470
  92. package/src/preact/mutationsOverTime/__mockData__/aggregated_tooManyMutations_total.json +0 -13
  93. package/src/preact/mutationsOverTime/__mockData__/aggregated_week3_2024.json +0 -13
  94. package/src/preact/mutationsOverTime/__mockData__/aggregated_week4_2024.json +0 -13
  95. package/src/preact/mutationsOverTime/__mockData__/aggregated_week5_2024.json +0 -13
  96. package/src/preact/mutationsOverTime/__mockData__/aggregated_week6_2024.json +0 -13
  97. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_20_01_2024.json +0 -6778
  98. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_21_01_2024.json +0 -7129
  99. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_22_01_2024.json +0 -4681
  100. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_23_01_2024.json +0 -10738
  101. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_24_01_2024.json +0 -11710
  102. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_25_01_2024.json +0 -11557
  103. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_26_01_2024.json +0 -8596
  104. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_byDayOverall.json +0 -4726
  105. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_01.json +0 -1747
  106. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_02.json +0 -1774
  107. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_03.json +0 -1819
  108. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_04.json +0 -1864
  109. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_05.json +0 -1927
  110. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_06.json +0 -1864
  111. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_07.json +0 -9
  112. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_byMonthOverall.json +0 -11143
  113. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_byWeekOverall.json +0 -9154
  114. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_tooManyMutations.json +0 -16453
  115. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_week3_2024.json +0 -8812
  116. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_week4_2024.json +0 -9730
  117. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_week5_2024.json +0 -9865
  118. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_week6_2024.json +0 -11314
@@ -99,6 +99,53 @@ export declare class App extends LitElement {
99
99
  createRenderRoot(): this;
100
100
  }
101
101
 
102
+ /**
103
+ * A date range option that can be used in the `gs-date-range-selector` component.
104
+ */
105
+ export declare type DateRangeOption = {
106
+ /** The label of the date range option that will be shown to the user */
107
+ label: string;
108
+ /**
109
+ * The start date of the date range in the format `YYYY-MM-DD`.
110
+ * If not set, the date range selector will default to the `earliestDate` property.
111
+ */
112
+ dateFrom?: string;
113
+ /**
114
+ * The end date of the date range in the format `YYYY-MM-DD`.
115
+ * If not set, the date range selector will default to the current date.
116
+ */
117
+ dateTo?: string;
118
+ };
119
+
120
+ /**
121
+ * Presets for the `gs-date-range-selector` component that can be used as `dateRangeOptions`.
122
+ */
123
+ export declare const dateRangeOptionPresets: {
124
+ last2Weeks: {
125
+ label: string;
126
+ dateFrom: string | undefined;
127
+ };
128
+ lastMonth: {
129
+ label: string;
130
+ dateFrom: string | undefined;
131
+ };
132
+ last2Months: {
133
+ label: string;
134
+ dateFrom: string | undefined;
135
+ };
136
+ last3Months: {
137
+ label: string;
138
+ dateFrom: string | undefined;
139
+ };
140
+ last6Months: {
141
+ label: string;
142
+ dateFrom: string | undefined;
143
+ };
144
+ allTimes: {
145
+ label: string;
146
+ };
147
+ };
148
+
102
149
  /**
103
150
  * ## Context
104
151
  * This component is a group of input fields designed to specify date range filters
@@ -130,15 +177,16 @@ export declare class App extends LitElement {
130
177
  */
131
178
  export declare class DateRangeSelectorComponent extends PreactLitAdapter {
132
179
  /**
133
- * An array of custom options that the select field should provide,
134
- * in addition to the predefined options.
180
+ * An array of date range options that the select field should provide.
135
181
  * The `label` will be shown to the user, and it will be available as `initialValue`.
136
182
  * The dates must be in the format `YYYY-MM-DD`.
183
+ *
184
+ * If dateFrom or dateTo is not set, the component will default to the `earliestDate` or the current date.
137
185
  */
138
- customSelectOptions: {
186
+ dateRangeOptions: {
139
187
  label: string;
140
- dateFrom: string;
141
- dateTo: string;
188
+ dateFrom?: string;
189
+ dateTo?: string;
142
190
  }[];
143
191
  /**
144
192
  * The `dateFrom` value to use in the `allTimes` preset in the format `YYYY-MM-DD`.
@@ -146,13 +194,15 @@ export declare class DateRangeSelectorComponent extends PreactLitAdapter {
146
194
  earliestDate: string;
147
195
  /**
148
196
  * The initial value to use for this date range selector.
149
- * Must be a valid label from the preset labels or a `label` given in the `customSelectOptions`.
197
+ * Must be a valid label from the `dateRangeOptions`.
150
198
  *
151
- * If the value is invalid, the component will default to `'last6Months'`.
199
+ * If the value is not set, the component will default to the range `earliestDate` until today.
152
200
  *
153
201
  * It will be overwritten if `initialDateFrom` or `initialDateTo` is set.
202
+ *
203
+ * We provide some options in `dateRangeOptionPresets` for convenience.
154
204
  */
155
- initialValue: 'custom' | 'allTimes' | 'last2Weeks' | 'lastMonth' | 'last2Months' | 'last3Months' | 'last6Months' | string;
205
+ initialValue: string | undefined;
156
206
  /**
157
207
  * A date string in the format `YYYY-MM-DD`.
158
208
  * If set, the date range selector will be initialized with the given date (overwriting `initialValue` to `custom`).
@@ -1181,7 +1231,7 @@ declare global {
1181
1231
 
1182
1232
  declare global {
1183
1233
  interface HTMLElementTagNameMap {
1184
- 'gs-mutations-over-time-component': MutationsOverTimeComponent;
1234
+ 'gs-mutations-over-time': MutationsOverTimeComponent;
1185
1235
  }
1186
1236
  }
1187
1237
 
@@ -1189,7 +1239,7 @@ declare global {
1189
1239
  declare global {
1190
1240
  namespace JSX {
1191
1241
  interface IntrinsicElements {
1192
- 'gs-mutations-over-time-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1242
+ 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1193
1243
  }
1194
1244
  }
1195
1245
  }
package/dist/style.css CHANGED
@@ -482,7 +482,7 @@ input[type="range"] {
482
482
  --tw-contain-paint: ;
483
483
  --tw-contain-style: ;
484
484
  }/*
485
- ! tailwindcss v3.4.13 | MIT License | https://tailwindcss.com
485
+ ! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com
486
486
  *//*
487
487
  1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
488
488
  2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
@@ -873,7 +873,7 @@ video {
873
873
  }
874
874
 
875
875
  /* Make elements with the HTML hidden attribute stay hidden by default */
876
- [hidden] {
876
+ [hidden]:where(:not([hidden="until-found"])) {
877
877
  display: none;
878
878
  }
879
879
 
@@ -2707,6 +2707,7 @@ input.tab:checked + .tab-content,
2707
2707
  }
2708
2708
  .join.join-horizontal > :where(*:not(:first-child)):is(.btn) {
2709
2709
  margin-inline-start: calc(var(--border-btn) * -1);
2710
+ margin-top: 0px;
2710
2711
  }
2711
2712
  .modal-top :where(.modal-box) {
2712
2713
  width: 100%;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genspectrum/dashboard-components",
3
- "version": "0.6.18",
3
+ "version": "0.7.0",
4
4
  "description": "GenSpectrum web components for building dashboards",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -10,6 +10,17 @@
10
10
  "publishConfig": {
11
11
  "access": "public"
12
12
  },
13
+ "imports": {
14
+ "#mutationOverTime": {
15
+ "storybook": "./src/preact/mutationsOverTime/mutationOverTimeWorker.mock.ts",
16
+ "default": "./src/preact/mutationsOverTime/mutationOverTimeWorker.ts"
17
+ },
18
+ "#*": [
19
+ "./*",
20
+ "./*.ts",
21
+ "./*.tsx"
22
+ ]
23
+ },
13
24
  "exports": {
14
25
  ".": {
15
26
  "import": "./dist/dashboard-components.js",
@@ -69,7 +80,6 @@
69
80
  "flatpickr": "^4.6.13",
70
81
  "gridjs": "^6.2.0",
71
82
  "lit": "^3.1.3",
72
- "object-hash": "^3.0.0",
73
83
  "preact": "^10.20.1",
74
84
  "zod": "^3.23.0"
75
85
  },
@@ -92,7 +102,6 @@
92
102
  "@storybook/web-components": "^8.0.9",
93
103
  "@storybook/web-components-vite": "^8.0.9",
94
104
  "@types/node": "^22.0.0",
95
- "@types/object-hash": "^3.0.6",
96
105
  "@typescript-eslint/eslint-plugin": "^8.2.0",
97
106
  "@typescript-eslint/parser": "^8.2.0",
98
107
  "autoprefixer": "^10.4.19",
@@ -102,7 +111,7 @@
102
111
  "eslint-config-preact": "^1.3.0",
103
112
  "eslint-plugin-import": "^2.29.1",
104
113
  "eslint-plugin-jest": "^28.2.0",
105
- "eslint-plugin-storybook": "^0.8.0",
114
+ "eslint-plugin-storybook": "^0.10.1",
106
115
  "http-server": "^14.1.1",
107
116
  "lit-analyzer": "^2.0.3",
108
117
  "msw": "^2.2.14",
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from './web-components';
2
2
 
3
3
  export { type ErrorEvent, UserFacingError } from './preact/components/error-display';
4
+ export { type DateRangeOption, dateRangeOptionPresets } from './preact/dateRangeSelector/dateRangeOption';
4
5
 
5
6
  declare global {
6
7
  interface HTMLElementEventMap {
@@ -2,7 +2,7 @@ import { type Dataset } from './Dataset';
2
2
  import { type Operator } from './Operator';
3
3
  import { fetchInsertions } from '../lapisApi/lapisApi';
4
4
  import { type InsertionEntry, type LapisFilter, type SequenceType } from '../types';
5
- import { Insertion } from '../utils/mutations';
5
+ import { InsertionClass } from '../utils/mutations';
6
6
 
7
7
  export class FetchInsertionsOperator implements Operator<InsertionEntry> {
8
8
  constructor(
@@ -15,7 +15,7 @@ export class FetchInsertionsOperator implements Operator<InsertionEntry> {
15
15
 
16
16
  const content: InsertionEntry[] = insertions.map(({ count, insertedSymbols, sequenceName, position }) => ({
17
17
  type: 'insertion',
18
- mutation: new Insertion(sequenceName ?? undefined, position, insertedSymbols),
18
+ mutation: new InsertionClass(sequenceName ?? undefined, position, insertedSymbols),
19
19
  count,
20
20
  }));
21
21
 
@@ -2,7 +2,7 @@ import { type Dataset } from './Dataset';
2
2
  import { type Operator } from './Operator';
3
3
  import { fetchSubstitutionsOrDeletions } from '../lapisApi/lapisApi';
4
4
  import { type LapisFilter, type SequenceType, type SubstitutionOrDeletionEntry } from '../types';
5
- import { Deletion, Substitution } from '../utils/mutations';
5
+ import { DeletionClass, SubstitutionClass } from '../utils/mutations';
6
6
 
7
7
  export class FetchSubstitutionsOrDeletionsOperator implements Operator<SubstitutionOrDeletionEntry> {
8
8
  constructor(
@@ -19,14 +19,14 @@ export class FetchSubstitutionsOrDeletionsOperator implements Operator<Substitut
19
19
  if (mutationTo === '-') {
20
20
  return {
21
21
  type: 'deletion',
22
- mutation: new Deletion(sequenceName ?? undefined, mutationFrom, position),
22
+ mutation: new DeletionClass(sequenceName ?? undefined, mutationFrom, position),
23
23
  count,
24
24
  proportion,
25
25
  };
26
26
  }
27
27
  return {
28
28
  type: 'substitution',
29
- mutation: new Substitution(sequenceName ?? undefined, mutationFrom, mutationTo, position),
29
+ mutation: new SubstitutionClass(sequenceName ?? undefined, mutationFrom, mutationTo, position),
30
30
  count,
31
31
  proportion,
32
32
  };
@@ -1,48 +1,75 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
 
3
3
  import { computeInitialValues } from './computeInitialValues';
4
- import { PRESET_VALUE_CUSTOM, PRESET_VALUE_LAST_3_MONTHS, PRESET_VALUE_LAST_6_MONTHS } from './selectableOptions';
5
4
 
6
5
  const today = new Date();
7
6
  const earliestDate = '1900-01-01';
8
7
 
8
+ const fromOption = 'fromOption';
9
+ const toOption = 'toOption';
10
+ const fromToOption = 'fromToOption';
11
+ const dateFromOptionValue = '2010-06-30';
12
+ const dateToOptionValue = '2020-01-01';
13
+
14
+ const dateRangeOptions = [
15
+ { label: fromOption, dateFrom: dateFromOptionValue },
16
+ { label: toOption, dateTo: dateToOptionValue },
17
+ { label: fromToOption, dateFrom: dateFromOptionValue, dateTo: dateToOptionValue },
18
+ ];
19
+
9
20
  describe('computeInitialValues', () => {
10
21
  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, []);
22
+ const result = computeInitialValues(fromToOption, undefined, undefined, earliestDate, dateRangeOptions);
12
23
 
13
- const expectedFrom = new Date();
14
- expectedFrom.setMonth(today.getMonth() - 3);
24
+ expect(result.initialSelectedDateRange).toEqual(fromToOption);
25
+ expectDateMatches(result.initialSelectedDateFrom, new Date(dateFromOptionValue));
26
+ expectDateMatches(result.initialSelectedDateTo, new Date(dateToOptionValue));
27
+ });
15
28
 
16
- expect(result.initialSelectedDateRange).toEqual(PRESET_VALUE_LAST_3_MONTHS);
17
- expectDateMatches(result.initialSelectedDateFrom, expectedFrom);
29
+ it('should use today as "dateTo" if it is unset in selected option', () => {
30
+ const result = computeInitialValues(fromOption, undefined, undefined, earliestDate, dateRangeOptions);
31
+
32
+ expect(result.initialSelectedDateRange).toEqual(fromOption);
33
+ expectDateMatches(result.initialSelectedDateFrom, new Date(dateFromOptionValue));
18
34
  expectDateMatches(result.initialSelectedDateTo, today);
19
35
  });
20
36
 
21
- it('should fall back to default when initial value is unknown', () => {
22
- const result = computeInitialValues('not a known value', undefined, undefined, earliestDate, []);
37
+ it('should use earliest date as "dateFrom" if it is unset in selected option', () => {
38
+ const result = computeInitialValues(toOption, undefined, undefined, earliestDate, dateRangeOptions);
23
39
 
24
- const expectedFrom = new Date();
25
- expectedFrom.setMonth(today.getMonth() - 6);
40
+ expect(result.initialSelectedDateRange).toEqual(toOption);
41
+ expectDateMatches(result.initialSelectedDateFrom, new Date(earliestDate));
42
+ expectDateMatches(result.initialSelectedDateTo, new Date(dateToOptionValue));
43
+ });
26
44
 
27
- expect(result.initialSelectedDateRange).toEqual(PRESET_VALUE_LAST_6_MONTHS);
28
- expectDateMatches(result.initialSelectedDateFrom, expectedFrom);
45
+ it('should fall back to full range if initial value is not set', () => {
46
+ const result = computeInitialValues(undefined, undefined, undefined, earliestDate, dateRangeOptions);
47
+
48
+ expect(result.initialSelectedDateRange).toBeUndefined();
49
+ expectDateMatches(result.initialSelectedDateFrom, new Date(earliestDate));
29
50
  expectDateMatches(result.initialSelectedDateTo, today);
30
51
  });
31
52
 
53
+ it('should fall back to default when initial value is unknown', () => {
54
+ expect(() => computeInitialValues('not a known value', undefined, undefined, earliestDate, [])).toThrowError(
55
+ /Invalid initialValue "not a known value", It must be one of/,
56
+ );
57
+ });
58
+
32
59
  it('should overwrite initial value if initial "from" is set', () => {
33
60
  const initialDateFrom = '2020-01-01';
34
- const result = computeInitialValues(PRESET_VALUE_LAST_3_MONTHS, initialDateFrom, undefined, earliestDate, []);
61
+ const result = computeInitialValues(fromOption, initialDateFrom, undefined, earliestDate, dateRangeOptions);
35
62
 
36
- expect(result.initialSelectedDateRange).toEqual(PRESET_VALUE_CUSTOM);
63
+ expect(result.initialSelectedDateRange).toBeUndefined();
37
64
  expectDateMatches(result.initialSelectedDateFrom, new Date(initialDateFrom));
38
65
  expectDateMatches(result.initialSelectedDateTo, today);
39
66
  });
40
67
 
41
68
  it('should overwrite initial value if initial "to" is set', () => {
42
69
  const initialDateTo = '2020-01-01';
43
- const result = computeInitialValues(PRESET_VALUE_LAST_3_MONTHS, undefined, initialDateTo, earliestDate, []);
70
+ const result = computeInitialValues(fromOption, undefined, initialDateTo, earliestDate, dateRangeOptions);
44
71
 
45
- expect(result.initialSelectedDateRange).toEqual(PRESET_VALUE_CUSTOM);
72
+ expect(result.initialSelectedDateRange).toBeUndefined();
46
73
  expectDateMatches(result.initialSelectedDateFrom, new Date(earliestDate));
47
74
  expectDateMatches(result.initialSelectedDateTo, new Date(initialDateTo));
48
75
  });
@@ -50,15 +77,9 @@ describe('computeInitialValues', () => {
50
77
  it('should overwrite initial value if initial "to" and "from" are set', () => {
51
78
  const initialDateFrom = '2020-01-01';
52
79
  const initialDateTo = '2022-01-01';
53
- const result = computeInitialValues(
54
- PRESET_VALUE_LAST_3_MONTHS,
55
- initialDateFrom,
56
- initialDateTo,
57
- earliestDate,
58
- [],
59
- );
80
+ const result = computeInitialValues(fromOption, initialDateFrom, initialDateTo, earliestDate, dateRangeOptions);
60
81
 
61
- expect(result.initialSelectedDateRange).toEqual(PRESET_VALUE_CUSTOM);
82
+ expect(result.initialSelectedDateRange).toBeUndefined();
62
83
  expectDateMatches(result.initialSelectedDateFrom, new Date(initialDateFrom));
63
84
  expectDateMatches(result.initialSelectedDateTo, new Date(initialDateTo));
64
85
  });
@@ -66,29 +87,23 @@ describe('computeInitialValues', () => {
66
87
  it('should set initial "to" to "from" if "from" is after "to"', () => {
67
88
  const initialDateFrom = '2020-01-01';
68
89
  const initialDateTo = '1900-01-01';
69
- const result = computeInitialValues(
70
- PRESET_VALUE_LAST_3_MONTHS,
71
- initialDateFrom,
72
- initialDateTo,
73
- earliestDate,
74
- [],
75
- );
90
+ const result = computeInitialValues(undefined, initialDateFrom, initialDateTo, earliestDate, dateRangeOptions);
76
91
 
77
- expect(result.initialSelectedDateRange).toEqual(PRESET_VALUE_CUSTOM);
92
+ expect(result.initialSelectedDateRange).toBeUndefined();
78
93
  expectDateMatches(result.initialSelectedDateFrom, new Date(initialDateFrom));
79
94
  expectDateMatches(result.initialSelectedDateTo, new Date(initialDateFrom));
80
95
  });
81
96
 
82
97
  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');
98
+ expect(() => computeInitialValues(undefined, 'not a date', undefined, earliestDate, [])).toThrowError(
99
+ 'Invalid initialDateFrom',
100
+ );
86
101
  });
87
102
 
88
103
  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');
104
+ expect(() => computeInitialValues(undefined, undefined, 'not a date', earliestDate, [])).toThrowError(
105
+ 'Invalid initialDateTo',
106
+ );
92
107
  });
93
108
 
94
109
  function expectDateMatches(actual: Date, expected: Date) {
@@ -1,36 +1,30 @@
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';
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<CustomLabel extends string>(
12
- initialValue: PresetOptionValues | CustomLabel | undefined,
5
+ export function computeInitialValues(
6
+ initialValue: string | undefined,
13
7
  initialDateFrom: string | undefined,
14
8
  initialDateTo: string | undefined,
15
9
  earliestDate: string,
16
- customSelectOptions: CustomSelectOption<CustomLabel>[],
10
+ dateRangeOptions: DateRangeOption[],
17
11
  ): {
18
- initialSelectedDateRange: CustomLabel | PresetOptionValues;
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(customSelectOptions);
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
- const { dateFrom, dateTo } = getDatesForSelectorValue(
30
- initialSelectedDateRange,
31
- customSelectOptions,
32
- earliestDate,
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: PRESET_VALUE_CUSTOM,
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<'CustomDateRange'>> = {
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
- customSelectOptions: {
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
- customSelectOptions: [{ label: 'CustomDateRange', dateFrom: '2021-01-01', dateTo: '2021-12-31' }],
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: PRESET_VALUE_LAST_3_MONTHS,
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<'CustomDateRange'>> = {
66
+ export const Primary: StoryObj<DateRangeSelectorProps> = {
76
67
  render: (args) => (
77
68
  <LapisUrlContext.Provider value={LAPIS_URL}>
78
69
  <DateRangeSelector
79
- customSelectOptions={args.customSelectOptions}
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<'CustomDateRange'>> = {
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<'CustomDateRange'>> = {
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(PRESET_VALUE_CUSTOM);
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<'CustomDateRange'>> = {
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(PRESET_VALUE_CUSTOM);
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 HandlesInvalidInitialDateFrom: StoryObj<DateRangeSelectorProps<'CustomDateRange'>> = {
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,