@genspectrum/dashboard-components 0.5.4 → 0.5.6

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 (35) hide show
  1. package/custom-elements.json +322 -0
  2. package/dist/dashboard-components.js +436 -263
  3. package/dist/dashboard-components.js.map +1 -1
  4. package/dist/genspectrum-components.d.ts +78 -0
  5. package/package.json +1 -1
  6. package/src/preact/aggregatedData/aggregate.tsx +2 -14
  7. package/src/preact/dateRangeSelector/date-range-selector.tsx +2 -14
  8. package/src/preact/locationFilter/location-filter.tsx +2 -7
  9. package/src/preact/mutationComparison/mutation-comparison.tsx +2 -10
  10. package/src/preact/mutations/mutations.tsx +2 -10
  11. package/src/preact/numberSequencesOverTime/__mockData__/oneVariantEG.json +1702 -0
  12. package/src/preact/numberSequencesOverTime/__mockData__/twoVariantsEG.json +1710 -0
  13. package/src/preact/numberSequencesOverTime/__mockData__/twoVariantsJN1.json +1070 -0
  14. package/src/preact/numberSequencesOverTime/__mockData__/twoVariantsXBB.json +506 -0
  15. package/src/preact/numberSequencesOverTime/getNumberOfSequencesOverTimeTableData.spec.ts +75 -0
  16. package/src/preact/numberSequencesOverTime/getNumberOfSequencesOverTimeTableData.ts +39 -0
  17. package/src/preact/numberSequencesOverTime/number-sequences-over-time-bar-chart.tsx +58 -0
  18. package/src/preact/numberSequencesOverTime/number-sequences-over-time-line-chart.tsx +59 -0
  19. package/src/preact/numberSequencesOverTime/number-sequences-over-time-table.tsx +32 -0
  20. package/src/preact/numberSequencesOverTime/number-sequences-over-time.stories.tsx +133 -0
  21. package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +106 -0
  22. package/src/preact/prevalenceOverTime/prevalence-over-time-bar-chart.tsx +11 -20
  23. package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +2 -20
  24. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +2 -14
  25. package/src/preact/textInput/text-input.tsx +2 -2
  26. package/src/query/queryAggregatedDataOverTime.ts +78 -0
  27. package/src/query/queryNumberOfSequencesOverTime.spec.ts +195 -0
  28. package/src/query/queryNumberOfSequencesOverTime.ts +33 -0
  29. package/src/query/queryPrevalenceOverTime.ts +10 -86
  30. package/src/utils/sort.ts +9 -0
  31. package/src/utils/temporalTestHelpers.ts +9 -0
  32. package/src/utils/utils.ts +7 -0
  33. package/src/web-components/visualization/gs-number-sequences-over-time.stories.ts +243 -0
  34. package/src/web-components/visualization/gs-number-sequences-over-time.tsx +140 -0
  35. package/src/web-components/visualization/index.ts +1 -0
@@ -0,0 +1,195 @@
1
+ import { describe, expect, test } from 'vitest';
2
+
3
+ import { queryNumberOfSequencesOverTime } from './queryNumberOfSequencesOverTime';
4
+ import { DUMMY_LAPIS_URL, lapisRequestMocks } from '../../vitest.setup';
5
+ import { yearMonth, yearMonthDay } from '../utils/temporalTestHelpers';
6
+
7
+ const lapisDateField = 'dateField';
8
+ const lapisFilter = { field1: 'value1', field2: 'value2' };
9
+
10
+ describe('queryNumberOfSequencesOverTime', () => {
11
+ test('should fetch data for a single filter', async () => {
12
+ lapisRequestMocks.aggregated(
13
+ { ...lapisFilter, fields: [lapisDateField] },
14
+ {
15
+ data: [
16
+ { count: 1, [lapisDateField]: '2023-01-01' },
17
+ { count: 2, [lapisDateField]: '2023-01-02' },
18
+ ],
19
+ },
20
+ );
21
+
22
+ const result = await queryNumberOfSequencesOverTime(
23
+ DUMMY_LAPIS_URL,
24
+ { displayName: 'displayName', lapisFilter },
25
+ lapisDateField,
26
+ 'day',
27
+ 0,
28
+ );
29
+
30
+ expect(result).to.deep.equal([
31
+ {
32
+ displayName: 'displayName',
33
+ content: [
34
+ { count: 1, dateRange: yearMonthDay('2023-01-01') },
35
+ { count: 2, dateRange: yearMonthDay('2023-01-02') },
36
+ ],
37
+ },
38
+ ]);
39
+ });
40
+
41
+ test('should fill missing dates with count 0', async () => {
42
+ lapisRequestMocks.aggregated(
43
+ { ...lapisFilter, fields: [lapisDateField] },
44
+ {
45
+ data: [
46
+ { count: 1, [lapisDateField]: '2023-01-01' },
47
+ { count: 2, [lapisDateField]: '2023-01-04' },
48
+ ],
49
+ },
50
+ );
51
+
52
+ const result = await queryNumberOfSequencesOverTime(
53
+ DUMMY_LAPIS_URL,
54
+ { displayName: 'displayName', lapisFilter },
55
+ lapisDateField,
56
+ 'day',
57
+ 0,
58
+ );
59
+
60
+ expect(result).to.deep.equal([
61
+ {
62
+ displayName: 'displayName',
63
+ content: [
64
+ { count: 1, dateRange: yearMonthDay('2023-01-01') },
65
+ { count: 0, dateRange: yearMonthDay('2023-01-02') },
66
+ { count: 0, dateRange: yearMonthDay('2023-01-03') },
67
+ { count: 2, dateRange: yearMonthDay('2023-01-04') },
68
+ ],
69
+ },
70
+ ]);
71
+ });
72
+
73
+ test('should smooth the data', async () => {
74
+ lapisRequestMocks.aggregated(
75
+ { ...lapisFilter, fields: [lapisDateField] },
76
+ {
77
+ data: [
78
+ { count: 3, [lapisDateField]: '2023-01-01' },
79
+ { count: 0, [lapisDateField]: '2023-01-02' },
80
+ { count: 3, [lapisDateField]: '2023-01-03' },
81
+ { count: 0, [lapisDateField]: '2023-01-04' },
82
+ { count: 6, [lapisDateField]: '2023-01-05' },
83
+ { count: 0, [lapisDateField]: '2023-01-06' },
84
+ ],
85
+ },
86
+ );
87
+
88
+ const result = await queryNumberOfSequencesOverTime(
89
+ DUMMY_LAPIS_URL,
90
+ { displayName: 'displayName', lapisFilter },
91
+ lapisDateField,
92
+ 'day',
93
+ 3,
94
+ );
95
+
96
+ expect(result).to.deep.equal([
97
+ {
98
+ displayName: 'displayName',
99
+ content: [
100
+ { count: 2, dateRange: yearMonthDay('2023-01-02') },
101
+ { count: 1, dateRange: yearMonthDay('2023-01-03') },
102
+ { count: 3, dateRange: yearMonthDay('2023-01-04') },
103
+ { count: 2, dateRange: yearMonthDay('2023-01-05') },
104
+ ],
105
+ },
106
+ ]);
107
+ });
108
+
109
+ test('should aggregate by month', async () => {
110
+ lapisRequestMocks.aggregated(
111
+ { ...lapisFilter, fields: [lapisDateField] },
112
+ {
113
+ data: [
114
+ { count: 1, [lapisDateField]: '2023-01-01' },
115
+ { count: 2, [lapisDateField]: '2023-01-02' },
116
+ { count: 3, [lapisDateField]: '2023-02-05' },
117
+ { count: 4, [lapisDateField]: '2023-02-06' },
118
+ { count: 5, [lapisDateField]: '2023-03-06' },
119
+ ],
120
+ },
121
+ );
122
+
123
+ const result = await queryNumberOfSequencesOverTime(
124
+ DUMMY_LAPIS_URL,
125
+ { displayName: 'displayName', lapisFilter },
126
+ lapisDateField,
127
+ 'month',
128
+ 0,
129
+ );
130
+
131
+ expect(result).to.deep.equal([
132
+ {
133
+ displayName: 'displayName',
134
+ content: [
135
+ { count: 3, dateRange: yearMonth('2023-01') },
136
+ { count: 7, dateRange: yearMonth('2023-02') },
137
+ { count: 5, dateRange: yearMonth('2023-03') },
138
+ ],
139
+ },
140
+ ]);
141
+ });
142
+
143
+ test('should fetch data for multiple filters', async () => {
144
+ const lapisFilter1 = { field1: 'value1', field2: 'value2' };
145
+ const lapisFilter2 = { field3: 'value3', field4: 'value4' };
146
+ lapisRequestMocks.multipleAggregated([
147
+ {
148
+ body: { ...lapisFilter1, fields: [lapisDateField] },
149
+ response: {
150
+ data: [
151
+ { count: 1, [lapisDateField]: '2023-01-01' },
152
+ { count: 2, [lapisDateField]: '2023-01-02' },
153
+ ],
154
+ },
155
+ },
156
+ {
157
+ body: { ...lapisFilter2, fields: [lapisDateField] },
158
+ response: {
159
+ data: [
160
+ { count: 3, [lapisDateField]: '2023-01-02' },
161
+ { count: 4, [lapisDateField]: '2023-01-03' },
162
+ ],
163
+ },
164
+ },
165
+ ]);
166
+
167
+ const result = await queryNumberOfSequencesOverTime(
168
+ DUMMY_LAPIS_URL,
169
+ [
170
+ { displayName: 'displayName1', lapisFilter: lapisFilter1 },
171
+ { displayName: 'displayName2', lapisFilter: lapisFilter2 },
172
+ ],
173
+ lapisDateField,
174
+ 'day',
175
+ 0,
176
+ );
177
+
178
+ expect(result).to.deep.equal([
179
+ {
180
+ displayName: 'displayName1',
181
+ content: [
182
+ { count: 1, dateRange: yearMonthDay('2023-01-01') },
183
+ { count: 2, dateRange: yearMonthDay('2023-01-02') },
184
+ ],
185
+ },
186
+ {
187
+ displayName: 'displayName2',
188
+ content: [
189
+ { count: 3, dateRange: yearMonthDay('2023-01-02') },
190
+ { count: 4, dateRange: yearMonthDay('2023-01-03') },
191
+ ],
192
+ },
193
+ ]);
194
+ });
195
+ });
@@ -0,0 +1,33 @@
1
+ import { queryAggregatedDataOverTime } from './queryAggregatedDataOverTime';
2
+ import { type NamedLapisFilter, type TemporalGranularity } from '../types';
3
+ import { sortNullToBeginningThenByDate } from '../utils/sort';
4
+ import { makeArray } from '../utils/utils';
5
+
6
+ export type NumberOfSequencesDatasets = Awaited<ReturnType<typeof queryNumberOfSequencesOverTime>>;
7
+ export type NumberOfSequencesDataset = NumberOfSequencesDatasets[number];
8
+
9
+ export async function queryNumberOfSequencesOverTime(
10
+ lapis: string,
11
+ lapisFilter: NamedLapisFilter | NamedLapisFilter[],
12
+ lapisDateField: string,
13
+ granularity: TemporalGranularity,
14
+ smoothingWindow: number,
15
+ ) {
16
+ const lapisFilters = makeArray(lapisFilter);
17
+
18
+ const queries = lapisFilters.map(async ({ displayName, lapisFilter }) => {
19
+ const { content } = await queryAggregatedDataOverTime(
20
+ lapisFilter,
21
+ granularity,
22
+ smoothingWindow,
23
+ lapisDateField,
24
+ ).evaluate(lapis);
25
+
26
+ return {
27
+ displayName,
28
+ content: content.sort(sortNullToBeginningThenByDate),
29
+ };
30
+ });
31
+
32
+ return Promise.all(queries);
33
+ }
@@ -1,19 +1,8 @@
1
+ import { queryAggregatedDataOverTime } from './queryAggregatedDataOverTime';
1
2
  import { DivisionOperator } from '../operator/DivisionOperator';
2
- import { FetchAggregatedOperator } from '../operator/FetchAggregatedOperator';
3
- import { FillMissingOperator } from '../operator/FillMissingOperator';
4
- import { GroupByAndSumOperator } from '../operator/GroupByAndSumOperator';
5
- import { MapOperator } from '../operator/MapOperator';
6
- import { RenameFieldOperator } from '../operator/RenameFieldOperator';
7
- import { SlidingOperator } from '../operator/SlidingOperator';
8
- import { SortOperator } from '../operator/SortOperator';
9
3
  import { type LapisFilter, type NamedLapisFilter, type TemporalGranularity } from '../types';
10
- import {
11
- compareTemporal,
12
- generateAllInRange,
13
- getMinMaxTemporal,
14
- type Temporal,
15
- TemporalCache,
16
- } from '../utils/temporal';
4
+ import { type Temporal } from '../utils/temporal';
5
+ import { makeArray } from '../utils/utils';
17
6
 
18
7
  export type PrevalenceOverTimeData = PrevalenceOverTimeVariantData[];
19
8
 
@@ -40,10 +29,15 @@ export function queryPrevalenceOverTime(
40
29
  ): Promise<PrevalenceOverTimeData> {
41
30
  const numeratorFilters = makeArray(numeratorFilter);
42
31
 
43
- const denominatorData = fetchAndPrepare(denominatorFilter, granularity, smoothingWindow, lapisDateField);
32
+ const denominatorData = queryAggregatedDataOverTime(
33
+ denominatorFilter,
34
+ granularity,
35
+ smoothingWindow,
36
+ lapisDateField,
37
+ );
44
38
  const subQueries = numeratorFilters.map(async (namedLapisFilter) => {
45
39
  const { displayName, lapisFilter } = namedLapisFilter;
46
- const numeratorData = fetchAndPrepare(lapisFilter, granularity, smoothingWindow, lapisDateField);
40
+ const numeratorData = queryAggregatedDataOverTime(lapisFilter, granularity, smoothingWindow, lapisDateField);
47
41
  const divide = new DivisionOperator(
48
42
  numeratorData,
49
43
  denominatorData,
@@ -61,73 +55,3 @@ export function queryPrevalenceOverTime(
61
55
  });
62
56
  return Promise.all(subQueries);
63
57
  }
64
-
65
- function makeArray<T>(arrayOrSingleItem: T | T[]) {
66
- if (Array.isArray(arrayOrSingleItem)) {
67
- return arrayOrSingleItem;
68
- }
69
- return [arrayOrSingleItem];
70
- }
71
-
72
- function fetchAndPrepare<LapisDateField extends string>(
73
- filter: LapisFilter,
74
- granularity: TemporalGranularity,
75
- smoothingWindow: number,
76
- lapisDateField: LapisDateField,
77
- ) {
78
- const fetchData = new FetchAggregatedOperator<{ [key in LapisDateField]: string | null }>(filter, [lapisDateField]);
79
- const dataWithFixedDateKey = new RenameFieldOperator(fetchData, lapisDateField, 'date');
80
- const mapData = new MapOperator(dataWithFixedDateKey, (d) => mapDateToGranularityRange(d, granularity));
81
- const groupByData = new GroupByAndSumOperator(mapData, 'dateRange', 'count');
82
- const fillData = new FillMissingOperator(
83
- groupByData,
84
- 'dateRange',
85
- getMinMaxTemporal,
86
- generateAllInRange,
87
- (key) => ({ dateRange: key, count: 0 }),
88
- );
89
- const sortData = new SortOperator(fillData, dateRangeCompare);
90
-
91
- return smoothingWindow >= 1 ? new SlidingOperator(sortData, smoothingWindow, averageSmoothing) : sortData;
92
- }
93
-
94
- function mapDateToGranularityRange(d: { date: string | null; count: number }, granularity: TemporalGranularity) {
95
- let dateRange: Temporal | null = null;
96
- if (d.date !== null) {
97
- const date = TemporalCache.getInstance().getYearMonthDay(d.date);
98
- switch (granularity) {
99
- case 'day':
100
- dateRange = date;
101
- break;
102
- case 'week':
103
- dateRange = date.week;
104
- break;
105
- case 'month':
106
- dateRange = date.month;
107
- break;
108
- case 'year':
109
- dateRange = date.year;
110
- break;
111
- }
112
- }
113
- return {
114
- dateRange,
115
- count: d.count,
116
- };
117
- }
118
-
119
- function dateRangeCompare(a: { dateRange: Temporal | null }, b: { dateRange: Temporal | null }) {
120
- if (a.dateRange === null) {
121
- return 1;
122
- }
123
- if (b.dateRange === null) {
124
- return -1;
125
- }
126
- return compareTemporal(a.dateRange, b.dateRange);
127
- }
128
-
129
- function averageSmoothing(slidingWindow: { dateRange: Temporal | null; count: number }[]) {
130
- const average = slidingWindow.reduce((acc, curr) => acc + curr.count, 0) / slidingWindow.length;
131
- const centerIndex = Math.floor(slidingWindow.length / 2);
132
- return { dateRange: slidingWindow[centerIndex].dateRange, count: average };
133
- }
@@ -0,0 +1,9 @@
1
+ import type { Temporal } from './temporal';
2
+
3
+ export function sortNullToBeginningThenByDate(a: { dateRange: Temporal | null }, b: { dateRange: Temporal | null }) {
4
+ return a.dateRange === null
5
+ ? -1
6
+ : b.dateRange === null
7
+ ? 1
8
+ : a.dateRange.toString().localeCompare(b.dateRange.toString());
9
+ }
@@ -0,0 +1,9 @@
1
+ import { TemporalCache, YearMonth, YearMonthDay } from './temporal';
2
+
3
+ export function yearMonthDay(date: string) {
4
+ return YearMonthDay.parse(date, TemporalCache.getInstance());
5
+ }
6
+
7
+ export function yearMonth(date: string) {
8
+ return YearMonth.parse(date, TemporalCache.getInstance());
9
+ }
@@ -36,3 +36,10 @@ function stringifyLapisFilterValue(value: LapisFilter[string]) {
36
36
  return value;
37
37
  }
38
38
  }
39
+
40
+ export function makeArray<T>(arrayOrSingleItem: T | T[]) {
41
+ if (Array.isArray(arrayOrSingleItem)) {
42
+ return arrayOrSingleItem;
43
+ }
44
+ return [arrayOrSingleItem];
45
+ }
@@ -0,0 +1,243 @@
1
+ import { expect, fireEvent, waitFor } from '@storybook/test';
2
+ import type { Meta, StoryObj } from '@storybook/web-components';
3
+ import { html } from 'lit';
4
+
5
+ import '../app';
6
+ import './gs-number-sequences-over-time';
7
+ import { withComponentDocs } from '../../../.storybook/ComponentDocsBlock';
8
+ import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
9
+ import oneVariantEG from '../../preact/numberSequencesOverTime/__mockData__/oneVariantEG.json';
10
+ import twoVariantsEG from '../../preact/numberSequencesOverTime/__mockData__/twoVariantsEG.json';
11
+ import twoVariantsJN1 from '../../preact/numberSequencesOverTime/__mockData__/twoVariantsJN1.json';
12
+ import twoVariantsXBB from '../../preact/numberSequencesOverTime/__mockData__/twoVariantsXBB.json';
13
+ import type { NumberSequencesOverTimeProps } from '../../preact/numberSequencesOverTime/number-sequences-over-time';
14
+ import { withinShadowRoot } from '../withinShadowRoot.story';
15
+
16
+ const codeExample = String.raw`
17
+ <gs-number-sequences-over-time
18
+ lapisFilter='[{ "displayName": "EG", "lapisFilter": { "country": "USA", "pangoLineage": "EG*" }}, { "displayName": "JN.1", "lapisFilter": { "country": "USA", "pangoLineage": "JN.1*" }}]'
19
+ lapisDateField="date"
20
+ views='["bar", "line", "table"]'
21
+ headline="Number of sequences over time"
22
+ width="100%"
23
+ height="700px"
24
+ granularity="month"
25
+ smoothingWindow="0"
26
+ pageSize="10"
27
+ ></gs-number-sequences-over-time>`;
28
+
29
+ const meta: Meta<NumberSequencesOverTimeProps> = {
30
+ title: 'Visualization/Number sequences over time',
31
+ component: 'gs-number-sequences-over-time',
32
+ argTypes: {
33
+ granularity: {
34
+ options: ['day', 'week', 'month', 'year'],
35
+ control: { type: 'radio' },
36
+ },
37
+ views: {
38
+ options: ['bar', 'line', 'table'],
39
+ control: { type: 'check' },
40
+ },
41
+ pageSize: { control: 'object' },
42
+ },
43
+ parameters: withComponentDocs({
44
+ componentDocs: {
45
+ opensShadowDom: true,
46
+ expectsChildren: false,
47
+ codeExample,
48
+ },
49
+ }),
50
+ tags: ['autodocs'],
51
+ };
52
+
53
+ export default meta;
54
+
55
+ const Template: StoryObj<NumberSequencesOverTimeProps> = {
56
+ render: (args) => html`
57
+ <gs-app lapis="${LAPIS_URL}">
58
+ <gs-number-sequences-over-time
59
+ .lapisFilter=${args.lapisFilter}
60
+ .lapisDateField=${args.lapisDateField}
61
+ .views=${args.views}
62
+ .width=${args.width}
63
+ .height=${args.height}
64
+ .headline=${args.headline}
65
+ .granularity=${args.granularity}
66
+ .smoothingWindow=${args.smoothingWindow}
67
+ .pageSize=${args.pageSize}
68
+ ></gs-number-sequences-over-time>
69
+ </gs-app>
70
+ `,
71
+ args: {
72
+ views: ['bar', 'line', 'table'],
73
+ lapisFilter: [
74
+ { displayName: 'EG', lapisFilter: { country: 'USA', pangoLineage: 'EG*', dateFrom: '2022-12-01' } },
75
+ ],
76
+ lapisDateField: 'date',
77
+ width: '100%',
78
+ height: '700px',
79
+ headline: 'Number of sequences over time',
80
+ smoothingWindow: 0,
81
+ granularity: 'month',
82
+ pageSize: 10,
83
+ },
84
+ parameters: {
85
+ fetchMock: {
86
+ mocks: [
87
+ {
88
+ matcher: {
89
+ name: 'aggregated',
90
+ url: AGGREGATED_ENDPOINT,
91
+ body: {
92
+ country: 'USA',
93
+ pangoLineage: 'EG*',
94
+ dateFrom: '2022-12-01',
95
+ fields: ['date'],
96
+ },
97
+ },
98
+ response: {
99
+ status: 200,
100
+ body: oneVariantEG,
101
+ },
102
+ },
103
+ ],
104
+ },
105
+ },
106
+ };
107
+
108
+ export const OneDatasetBarChart: StoryObj<NumberSequencesOverTimeProps> = {
109
+ ...Template,
110
+ play: async ({ canvasElement }) => {
111
+ const canvas = await withinShadowRoot(canvasElement, 'gs-number-sequences-over-time');
112
+
113
+ await waitFor(() => expect(canvas.getByRole('button', { name: 'Bar' })).toBeVisible());
114
+
115
+ await fireEvent.click(canvas.getByRole('button', { name: 'Bar' }));
116
+ },
117
+ };
118
+
119
+ export const OneDatasetLineChart: StoryObj<NumberSequencesOverTimeProps> = {
120
+ ...Template,
121
+ play: async ({ canvasElement }) => {
122
+ const canvas = await withinShadowRoot(canvasElement, 'gs-number-sequences-over-time');
123
+
124
+ await waitFor(() => expect(canvas.getByRole('button', { name: 'Line' })).toBeVisible());
125
+
126
+ await fireEvent.click(canvas.getByRole('button', { name: 'Line' }));
127
+ },
128
+ };
129
+
130
+ export const OneDatasetTable: StoryObj<NumberSequencesOverTimeProps> = {
131
+ ...Template,
132
+ play: async ({ canvasElement }) => {
133
+ const canvas = await withinShadowRoot(canvasElement, 'gs-number-sequences-over-time');
134
+
135
+ await waitFor(() => expect(canvas.getByRole('button', { name: 'Table' })).toBeVisible());
136
+
137
+ await fireEvent.click(canvas.getByRole('button', { name: 'Table' }));
138
+ },
139
+ };
140
+
141
+ export const TwoDatasets: StoryObj<NumberSequencesOverTimeProps> = {
142
+ ...Template,
143
+ args: {
144
+ ...Template.args,
145
+ lapisFilter: [
146
+ {
147
+ displayName: 'EG',
148
+ lapisFilter: { country: 'USA', pangoLineage: 'EG*', dateFrom: '2022-10-01' },
149
+ },
150
+ { displayName: 'JN.1', lapisFilter: { country: 'USA', pangoLineage: 'JN.1*', dateFrom: '2023-01-01' } },
151
+ ],
152
+ },
153
+ parameters: {
154
+ fetchMock: {
155
+ mocks: [
156
+ {
157
+ matcher: {
158
+ name: 'aggregatedEG',
159
+ url: AGGREGATED_ENDPOINT,
160
+ body: {
161
+ country: 'USA',
162
+ pangoLineage: 'EG*',
163
+ fields: ['date'],
164
+ },
165
+ },
166
+ response: {
167
+ status: 200,
168
+ body: twoVariantsEG,
169
+ },
170
+ },
171
+ {
172
+ matcher: {
173
+ name: 'aggregatedJN.1',
174
+ url: AGGREGATED_ENDPOINT,
175
+ body: {
176
+ country: 'USA',
177
+ pangoLineage: 'JN.1*',
178
+ dateFrom: '2023-01-01',
179
+ fields: ['date'],
180
+ },
181
+ },
182
+ response: {
183
+ status: 200,
184
+ body: twoVariantsJN1,
185
+ },
186
+ },
187
+ ],
188
+ },
189
+ },
190
+ };
191
+
192
+ export const TwoDatasetsWithNonOverlappingDates: StoryObj<NumberSequencesOverTimeProps> = {
193
+ ...Template,
194
+ args: {
195
+ ...Template.args,
196
+ lapisFilter: [
197
+ {
198
+ displayName: 'XBB',
199
+ lapisFilter: { country: 'USA', pangoLineage: 'XBB*', dateFrom: '2022-01-01', dateTo: '2022-12-31' },
200
+ },
201
+ { displayName: 'JN.1', lapisFilter: { country: 'USA', pangoLineage: 'JN.1*', dateFrom: '2023-01-01' } },
202
+ ],
203
+ },
204
+ parameters: {
205
+ fetchMock: {
206
+ mocks: [
207
+ {
208
+ matcher: {
209
+ name: 'aggregatedEG',
210
+ url: AGGREGATED_ENDPOINT,
211
+ body: {
212
+ country: 'USA',
213
+ pangoLineage: 'XBB*',
214
+ dateFrom: '2022-01-01',
215
+ dateTo: '2022-12-31',
216
+ fields: ['date'],
217
+ },
218
+ },
219
+ response: {
220
+ status: 200,
221
+ body: twoVariantsXBB,
222
+ },
223
+ },
224
+ {
225
+ matcher: {
226
+ name: 'aggregatedJN.1',
227
+ url: AGGREGATED_ENDPOINT,
228
+ body: {
229
+ country: 'USA',
230
+ pangoLineage: 'JN.1*',
231
+ dateFrom: '2023-01-01',
232
+ fields: ['date'],
233
+ },
234
+ },
235
+ response: {
236
+ status: 200,
237
+ body: twoVariantsJN1,
238
+ },
239
+ },
240
+ ],
241
+ },
242
+ },
243
+ };