@genspectrum/dashboard-components 0.5.7 → 0.6.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 +194 -128
- package/dist/dashboard-components.js +14 -93
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +24 -28
- package/dist/style.css +0 -4
- package/package.json +1 -1
- package/src/preact/aggregatedData/aggregate.stories.tsx +0 -2
- package/src/preact/aggregatedData/aggregate.tsx +3 -12
- package/src/preact/components/error-boundary.stories.tsx +2 -6
- package/src/preact/components/error-boundary.tsx +2 -5
- package/src/preact/lineageFilter/__mockData__/aggregated.json +14510 -0
- package/src/preact/lineageFilter/fetchLineageAutocompleteList.spec.ts +14 -0
- package/src/preact/lineageFilter/fetchLineageAutocompleteList.ts +9 -0
- package/src/preact/lineageFilter/lineage-filter.stories.tsx +56 -0
- package/src/preact/lineageFilter/lineage-filter.tsx +100 -0
- package/src/preact/mutationComparison/mutation-comparison.stories.tsx +0 -3
- package/src/preact/mutationComparison/mutation-comparison.tsx +3 -12
- package/src/preact/mutations/mutations.stories.tsx +0 -3
- package/src/preact/mutations/mutations.tsx +3 -12
- package/src/preact/numberSequencesOverTime/__mockData__/twoVariantsEG.json +0 -8
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.stories.tsx +0 -2
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +3 -7
- package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +0 -4
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +3 -12
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +0 -3
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +2 -7
- package/src/preact/textInput/text-input.tsx +1 -5
- package/src/web-components/input/gs-lineage-filter.stories.ts +137 -0
- package/src/web-components/input/gs-lineage-filter.tsx +79 -0
- package/src/web-components/visualization/gs-aggregate.stories.ts +0 -4
- package/src/web-components/visualization/gs-aggregate.tsx +0 -7
- package/src/web-components/visualization/gs-mutation-comparison.stories.ts +0 -4
- package/src/web-components/visualization/gs-mutation-comparison.tsx +0 -7
- package/src/web-components/visualization/gs-mutations.stories.ts +0 -4
- package/src/web-components/visualization/gs-mutations.tsx +0 -7
- package/src/web-components/visualization/gs-number-sequences-over-time.stories.ts +1 -3
- package/src/web-components/visualization/gs-number-sequences-over-time.tsx +14 -11
- package/src/web-components/visualization/gs-prevalence-over-time.stories.ts +0 -5
- package/src/web-components/visualization/gs-prevalence-over-time.tsx +0 -7
- package/src/web-components/visualization/gs-relative-growth-advantage.stories.ts +0 -4
- package/src/web-components/visualization/gs-relative-growth-advantage.tsx +0 -7
- package/src/preact/components/headline.stories.tsx +0 -47
- package/src/preact/components/headline.tsx +0 -36
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { fetchLineageAutocompleteList } from './fetchLineageAutocompleteList';
|
|
4
|
+
import { DUMMY_LAPIS_URL, lapisRequestMocks } from '../../../vitest.setup';
|
|
5
|
+
|
|
6
|
+
describe('fetchLineageAutocompleteList', () => {
|
|
7
|
+
test('should add sublineage values', async () => {
|
|
8
|
+
lapisRequestMocks.aggregated({ fields: ['lineageField'] }, { data: [{ lineageField: 'B.1.1.7', count: 1 }] });
|
|
9
|
+
|
|
10
|
+
const result = await fetchLineageAutocompleteList(DUMMY_LAPIS_URL, 'lineageField');
|
|
11
|
+
|
|
12
|
+
expect(result).to.deep.equal(['B.1.1.7', 'B.1.1.7*']);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FetchAggregatedOperator } from '../../operator/FetchAggregatedOperator';
|
|
2
|
+
|
|
3
|
+
export async function fetchLineageAutocompleteList(lapis: string, field: string, signal?: AbortSignal) {
|
|
4
|
+
const fetchAggregatedOperator = new FetchAggregatedOperator<Record<string, string>>({}, [field]);
|
|
5
|
+
|
|
6
|
+
const data = (await fetchAggregatedOperator.evaluate(lapis, signal)).content;
|
|
7
|
+
|
|
8
|
+
return data.flatMap((item) => [item[field], `${item[field]}*`]).sort();
|
|
9
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { withActions } from '@storybook/addon-actions/decorator';
|
|
2
|
+
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
3
|
+
|
|
4
|
+
import { LineageFilter, type LineageFilterProps } from './lineage-filter';
|
|
5
|
+
import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
|
|
6
|
+
import aggregatedData from '../../preact/lineageFilter/__mockData__/aggregated.json';
|
|
7
|
+
import { LapisUrlContext } from '../LapisUrlContext';
|
|
8
|
+
|
|
9
|
+
const meta: Meta = {
|
|
10
|
+
title: 'Input/LineageFilter',
|
|
11
|
+
component: LineageFilter,
|
|
12
|
+
parameters: {
|
|
13
|
+
actions: {
|
|
14
|
+
handles: ['gs-lineage-filter-changed'],
|
|
15
|
+
},
|
|
16
|
+
fetchMock: {
|
|
17
|
+
mocks: [
|
|
18
|
+
{
|
|
19
|
+
matcher: {
|
|
20
|
+
name: 'pangoLineage',
|
|
21
|
+
url: AGGREGATED_ENDPOINT,
|
|
22
|
+
body: {
|
|
23
|
+
fields: ['pangoLineage'],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
response: {
|
|
27
|
+
status: 200,
|
|
28
|
+
body: aggregatedData,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
decorators: [withActions],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default meta;
|
|
38
|
+
|
|
39
|
+
export const Default: StoryObj<LineageFilterProps> = {
|
|
40
|
+
render: (args) => (
|
|
41
|
+
<LapisUrlContext.Provider value={LAPIS_URL}>
|
|
42
|
+
<LineageFilter
|
|
43
|
+
lapisField={args.lapisField}
|
|
44
|
+
placeholderText={args.placeholderText}
|
|
45
|
+
initialValue={args.initialValue}
|
|
46
|
+
width={args.width}
|
|
47
|
+
/>
|
|
48
|
+
</LapisUrlContext.Provider>
|
|
49
|
+
),
|
|
50
|
+
args: {
|
|
51
|
+
lapisField: 'pangoLineage',
|
|
52
|
+
placeholderText: 'Enter lineage',
|
|
53
|
+
initialValue: '',
|
|
54
|
+
width: '100%',
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { type FunctionComponent } from 'preact';
|
|
2
|
+
import { useContext, useRef } from 'preact/hooks';
|
|
3
|
+
|
|
4
|
+
import { fetchLineageAutocompleteList } from './fetchLineageAutocompleteList';
|
|
5
|
+
import { LapisUrlContext } from '../LapisUrlContext';
|
|
6
|
+
import { ErrorBoundary } from '../components/error-boundary';
|
|
7
|
+
import { ErrorDisplay } from '../components/error-display';
|
|
8
|
+
import { LoadingDisplay } from '../components/loading-display';
|
|
9
|
+
import { NoDataDisplay } from '../components/no-data-display';
|
|
10
|
+
import { ResizeContainer } from '../components/resize-container';
|
|
11
|
+
import { useQuery } from '../useQuery';
|
|
12
|
+
|
|
13
|
+
export interface LineageFilterInnerProps {
|
|
14
|
+
lapisField: string;
|
|
15
|
+
placeholderText?: string;
|
|
16
|
+
initialValue?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface LineageFilterProps extends LineageFilterInnerProps {
|
|
20
|
+
width: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const LineageFilter: FunctionComponent<LineageFilterProps> = ({ width, ...innerProps }) => {
|
|
24
|
+
const size = { width, height: '3rem' };
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<ErrorBoundary size={size}>
|
|
28
|
+
<ResizeContainer size={size}>
|
|
29
|
+
<LineageFilterInner {...innerProps} />
|
|
30
|
+
</ResizeContainer>
|
|
31
|
+
</ErrorBoundary>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const LineageFilterInner: FunctionComponent<LineageFilterInnerProps> = ({
|
|
36
|
+
lapisField,
|
|
37
|
+
placeholderText,
|
|
38
|
+
initialValue,
|
|
39
|
+
}) => {
|
|
40
|
+
const lapis = useContext(LapisUrlContext);
|
|
41
|
+
|
|
42
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
43
|
+
|
|
44
|
+
const { data, error, isLoading } = useQuery(
|
|
45
|
+
() => fetchLineageAutocompleteList(lapis, lapisField),
|
|
46
|
+
[lapisField, lapis],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if (isLoading) {
|
|
50
|
+
return <LoadingDisplay />;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (error !== null) {
|
|
54
|
+
return <ErrorDisplay error={error} />;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (data === null) {
|
|
58
|
+
return <NoDataDisplay />;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const onInput = () => {
|
|
62
|
+
const value = inputRef.current?.value === '' ? undefined : inputRef.current?.value;
|
|
63
|
+
|
|
64
|
+
if (isValidValue(value)) {
|
|
65
|
+
inputRef.current?.dispatchEvent(
|
|
66
|
+
new CustomEvent('gs-lineage-filter-changed', {
|
|
67
|
+
detail: { [lapisField]: value },
|
|
68
|
+
bubbles: true,
|
|
69
|
+
composed: true,
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const isValidValue = (value: string | undefined) => {
|
|
76
|
+
if (value === undefined) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return data.includes(value);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<>
|
|
84
|
+
<input
|
|
85
|
+
type='text'
|
|
86
|
+
class='input input-bordered w-full'
|
|
87
|
+
placeholder={placeholderText !== undefined ? placeholderText : lapisField}
|
|
88
|
+
onInput={onInput}
|
|
89
|
+
ref={inputRef}
|
|
90
|
+
list={lapisField}
|
|
91
|
+
value={initialValue}
|
|
92
|
+
/>
|
|
93
|
+
<datalist id={lapisField}>
|
|
94
|
+
{data.map((item) => (
|
|
95
|
+
<option value={item} key={item} />
|
|
96
|
+
))}
|
|
97
|
+
</datalist>
|
|
98
|
+
</>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
@@ -29,7 +29,6 @@ const meta: Meta<MutationComparisonProps> = {
|
|
|
29
29
|
},
|
|
30
30
|
width: { control: 'text' },
|
|
31
31
|
height: { control: 'text' },
|
|
32
|
-
headline: { control: 'text' },
|
|
33
32
|
pageSize: { control: 'object' },
|
|
34
33
|
},
|
|
35
34
|
parameters: {
|
|
@@ -85,7 +84,6 @@ const Template: StoryObj<MutationComparisonProps> = {
|
|
|
85
84
|
views={args.views}
|
|
86
85
|
width={args.width}
|
|
87
86
|
height={args.height}
|
|
88
|
-
headline={args.headline}
|
|
89
87
|
pageSize={args.pageSize}
|
|
90
88
|
/>
|
|
91
89
|
</ReferenceGenomeContext.Provider>
|
|
@@ -115,7 +113,6 @@ export const TwoVariants: StoryObj<MutationComparisonProps> = {
|
|
|
115
113
|
views: ['table', 'venn'],
|
|
116
114
|
width: '100%',
|
|
117
115
|
height: '700px',
|
|
118
|
-
headline: 'Mutation comparison',
|
|
119
116
|
pageSize: 10,
|
|
120
117
|
},
|
|
121
118
|
};
|
|
@@ -11,7 +11,6 @@ import { type DisplayedSegment, SegmentSelector, useDisplayedSegments } from '..
|
|
|
11
11
|
import { CsvDownloadButton } from '../components/csv-download-button';
|
|
12
12
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
13
13
|
import { ErrorDisplay } from '../components/error-display';
|
|
14
|
-
import Headline from '../components/headline';
|
|
15
14
|
import Info from '../components/info';
|
|
16
15
|
import { LoadingDisplay } from '../components/loading-display';
|
|
17
16
|
import { type DisplayedMutationType, MutationTypeSelector } from '../components/mutation-type-selector';
|
|
@@ -27,7 +26,6 @@ export type View = 'table' | 'venn';
|
|
|
27
26
|
export interface MutationComparisonProps extends MutationComparisonInnerProps {
|
|
28
27
|
width: string;
|
|
29
28
|
height: string;
|
|
30
|
-
headline?: string;
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
export interface MutationComparisonInnerProps {
|
|
@@ -37,20 +35,13 @@ export interface MutationComparisonInnerProps {
|
|
|
37
35
|
pageSize: boolean | number;
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
export const MutationComparison: FunctionComponent<MutationComparisonProps> = ({
|
|
41
|
-
width,
|
|
42
|
-
height,
|
|
43
|
-
headline = 'Mutation comparison',
|
|
44
|
-
...innerProps
|
|
45
|
-
}) => {
|
|
38
|
+
export const MutationComparison: FunctionComponent<MutationComparisonProps> = ({ width, height, ...innerProps }) => {
|
|
46
39
|
const size = { height, width };
|
|
47
40
|
|
|
48
41
|
return (
|
|
49
|
-
<ErrorBoundary size={size}
|
|
42
|
+
<ErrorBoundary size={size}>
|
|
50
43
|
<ResizeContainer size={size}>
|
|
51
|
-
<
|
|
52
|
-
<MutationComparisonInner {...innerProps} />
|
|
53
|
-
</Headline>
|
|
44
|
+
<MutationComparisonInner {...innerProps} />
|
|
54
45
|
</ResizeContainer>
|
|
55
46
|
</ErrorBoundary>
|
|
56
47
|
);
|
|
@@ -24,7 +24,6 @@ const meta: Meta<MutationsProps> = {
|
|
|
24
24
|
},
|
|
25
25
|
width: { control: 'text' },
|
|
26
26
|
height: { control: 'text' },
|
|
27
|
-
headline: { control: 'text' },
|
|
28
27
|
pageSize: { control: 'object' },
|
|
29
28
|
},
|
|
30
29
|
};
|
|
@@ -41,7 +40,6 @@ const Template = {
|
|
|
41
40
|
views={args.views}
|
|
42
41
|
width={args.width}
|
|
43
42
|
height={args.height}
|
|
44
|
-
headline={args.headline}
|
|
45
43
|
pageSize={args.pageSize}
|
|
46
44
|
/>
|
|
47
45
|
</ReferenceGenomeContext.Provider>
|
|
@@ -57,7 +55,6 @@ export const Default: StoryObj<MutationsProps> = {
|
|
|
57
55
|
views: ['grid', 'table', 'insertions'],
|
|
58
56
|
width: '100%',
|
|
59
57
|
height: '700px',
|
|
60
|
-
headline: 'Mutations',
|
|
61
58
|
pageSize: 10,
|
|
62
59
|
},
|
|
63
60
|
parameters: {
|
|
@@ -18,7 +18,6 @@ import { type DisplayedSegment, SegmentSelector, useDisplayedSegments } from '..
|
|
|
18
18
|
import { CsvDownloadButton } from '../components/csv-download-button';
|
|
19
19
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
20
20
|
import { ErrorDisplay } from '../components/error-display';
|
|
21
|
-
import Headline from '../components/headline';
|
|
22
21
|
import Info from '../components/info';
|
|
23
22
|
import { LoadingDisplay } from '../components/loading-display';
|
|
24
23
|
import { type DisplayedMutationType, MutationTypeSelector } from '../components/mutation-type-selector';
|
|
@@ -41,23 +40,15 @@ export interface MutationsInnerProps {
|
|
|
41
40
|
export interface MutationsProps extends MutationsInnerProps {
|
|
42
41
|
width: string;
|
|
43
42
|
height: string;
|
|
44
|
-
headline?: string;
|
|
45
43
|
}
|
|
46
44
|
|
|
47
|
-
export const Mutations: FunctionComponent<MutationsProps> = ({
|
|
48
|
-
width,
|
|
49
|
-
height,
|
|
50
|
-
headline = 'Mutations',
|
|
51
|
-
...innerProps
|
|
52
|
-
}) => {
|
|
45
|
+
export const Mutations: FunctionComponent<MutationsProps> = ({ width, height, ...innerProps }) => {
|
|
53
46
|
const size = { height, width };
|
|
54
47
|
|
|
55
48
|
return (
|
|
56
|
-
<ErrorBoundary size={size}
|
|
49
|
+
<ErrorBoundary size={size}>
|
|
57
50
|
<ResizeContainer size={size}>
|
|
58
|
-
<
|
|
59
|
-
<MutationsInner {...innerProps} />
|
|
60
|
-
</Headline>
|
|
51
|
+
<MutationsInner {...innerProps} />
|
|
61
52
|
</ResizeContainer>
|
|
62
53
|
</ErrorBoundary>
|
|
63
54
|
);
|
|
@@ -244,10 +244,6 @@
|
|
|
244
244
|
"count": 416,
|
|
245
245
|
"date": "2023-09-18"
|
|
246
246
|
},
|
|
247
|
-
{
|
|
248
|
-
"count": 208,
|
|
249
|
-
"date": null
|
|
250
|
-
},
|
|
251
247
|
{
|
|
252
248
|
"count": 271,
|
|
253
249
|
"date": "2023-09-15"
|
|
@@ -624,10 +620,6 @@
|
|
|
624
620
|
"count": 313,
|
|
625
621
|
"date": "2023-10-26"
|
|
626
622
|
},
|
|
627
|
-
{
|
|
628
|
-
"count": 1,
|
|
629
|
-
"date": "2022-01-23"
|
|
630
|
-
},
|
|
631
623
|
{
|
|
632
624
|
"count": 91,
|
|
633
625
|
"date": "2023-12-31"
|
|
@@ -35,7 +35,6 @@ const Template: StoryObj<NumberSequencesOverTimeProps> = {
|
|
|
35
35
|
views={args.views}
|
|
36
36
|
width={args.width}
|
|
37
37
|
height={args.height}
|
|
38
|
-
headline={args.headline}
|
|
39
38
|
granularity={args.granularity}
|
|
40
39
|
smoothingWindow={args.smoothingWindow}
|
|
41
40
|
pageSize={args.pageSize}
|
|
@@ -50,7 +49,6 @@ const Template: StoryObj<NumberSequencesOverTimeProps> = {
|
|
|
50
49
|
lapisDateField: 'date',
|
|
51
50
|
width: '100%',
|
|
52
51
|
height: '700px',
|
|
53
|
-
headline: 'Number of sequences over time',
|
|
54
52
|
smoothingWindow: 0,
|
|
55
53
|
granularity: 'month',
|
|
56
54
|
pageSize: 10,
|
|
@@ -13,7 +13,6 @@ import { LapisUrlContext } from '../LapisUrlContext';
|
|
|
13
13
|
import { CsvDownloadButton } from '../components/csv-download-button';
|
|
14
14
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
15
15
|
import { ErrorDisplay } from '../components/error-display';
|
|
16
|
-
import Headline from '../components/headline';
|
|
17
16
|
import Info, { InfoHeadline1, InfoParagraph } from '../components/info';
|
|
18
17
|
import { LoadingDisplay } from '../components/loading-display';
|
|
19
18
|
import { NoDataDisplay } from '../components/no-data-display';
|
|
@@ -28,7 +27,6 @@ type NumberSequencesOverTimeView = 'bar' | 'line' | 'table';
|
|
|
28
27
|
export interface NumberSequencesOverTimeProps extends NumberSequencesOverTimeInnerProps {
|
|
29
28
|
width: string;
|
|
30
29
|
height: string;
|
|
31
|
-
headline: string;
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
interface NumberSequencesOverTimeInnerProps {
|
|
@@ -40,15 +38,13 @@ interface NumberSequencesOverTimeInnerProps {
|
|
|
40
38
|
pageSize: boolean | number;
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
export const NumberSequencesOverTime = ({ width, height,
|
|
41
|
+
export const NumberSequencesOverTime = ({ width, height, ...innerProps }: NumberSequencesOverTimeProps) => {
|
|
44
42
|
const size = { height, width };
|
|
45
43
|
|
|
46
44
|
return (
|
|
47
|
-
<ErrorBoundary size={size}
|
|
45
|
+
<ErrorBoundary size={size}>
|
|
48
46
|
<ResizeContainer size={size}>
|
|
49
|
-
<
|
|
50
|
-
<NumberSequencesOverTimeInner {...innerProps} />
|
|
51
|
-
</Headline>
|
|
47
|
+
<NumberSequencesOverTimeInner {...innerProps} />
|
|
52
48
|
</ResizeContainer>
|
|
53
49
|
</ErrorBoundary>
|
|
54
50
|
);
|
|
@@ -31,7 +31,6 @@ export default {
|
|
|
31
31
|
},
|
|
32
32
|
width: { control: 'text' },
|
|
33
33
|
height: { control: 'text' },
|
|
34
|
-
headline: { control: 'text' },
|
|
35
34
|
pageSize: { control: 'object' },
|
|
36
35
|
yAxisMaxConfig: { control: 'object' },
|
|
37
36
|
},
|
|
@@ -49,7 +48,6 @@ const Template = {
|
|
|
49
48
|
confidenceIntervalMethods={args.confidenceIntervalMethods}
|
|
50
49
|
width={args.width}
|
|
51
50
|
height={args.height}
|
|
52
|
-
headline={args.headline}
|
|
53
51
|
lapisDateField={args.lapisDateField}
|
|
54
52
|
pageSize={args.pageSize}
|
|
55
53
|
yAxisMaxConfig={args.yAxisMaxConfig}
|
|
@@ -72,7 +70,6 @@ export const TwoVariants = {
|
|
|
72
70
|
confidenceIntervalMethods: ['wilson'],
|
|
73
71
|
width: '100%',
|
|
74
72
|
height: '700px',
|
|
75
|
-
headline: 'Prevalence over time',
|
|
76
73
|
lapisDateField: 'date',
|
|
77
74
|
pageSize: 10,
|
|
78
75
|
yAxisMaxConfig: {
|
|
@@ -149,7 +146,6 @@ export const OneVariant = {
|
|
|
149
146
|
confidenceIntervalMethods: ['wilson'],
|
|
150
147
|
width: '100%',
|
|
151
148
|
height: '700px',
|
|
152
|
-
headline: 'Prevalence over time',
|
|
153
149
|
lapisDateField: 'date',
|
|
154
150
|
pageSize: 10,
|
|
155
151
|
yAxisMaxConfig: {
|
|
@@ -13,7 +13,6 @@ import { ConfidenceIntervalSelector } from '../components/confidence-interval-se
|
|
|
13
13
|
import { CsvDownloadButton } from '../components/csv-download-button';
|
|
14
14
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
15
15
|
import { ErrorDisplay } from '../components/error-display';
|
|
16
|
-
import Headline from '../components/headline';
|
|
17
16
|
import Info, { InfoHeadline1, InfoParagraph } from '../components/info';
|
|
18
17
|
import { LoadingDisplay } from '../components/loading-display';
|
|
19
18
|
import { NoDataDisplay } from '../components/no-data-display';
|
|
@@ -30,7 +29,6 @@ export type View = 'bar' | 'line' | 'bubble' | 'table';
|
|
|
30
29
|
export interface PrevalenceOverTimeProps extends PrevalenceOverTimeInnerProps {
|
|
31
30
|
width: string;
|
|
32
31
|
height: string;
|
|
33
|
-
headline?: string;
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
export interface PrevalenceOverTimeInnerProps {
|
|
@@ -45,20 +43,13 @@ export interface PrevalenceOverTimeInnerProps {
|
|
|
45
43
|
yAxisMaxConfig: YAxisMaxConfig;
|
|
46
44
|
}
|
|
47
45
|
|
|
48
|
-
export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({
|
|
49
|
-
width,
|
|
50
|
-
height,
|
|
51
|
-
headline = 'Prevalence over time',
|
|
52
|
-
...innerProps
|
|
53
|
-
}) => {
|
|
46
|
+
export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({ width, height, ...innerProps }) => {
|
|
54
47
|
const size = { height, width };
|
|
55
48
|
|
|
56
49
|
return (
|
|
57
|
-
<ErrorBoundary size={size}
|
|
50
|
+
<ErrorBoundary size={size}>
|
|
58
51
|
<ResizeContainer size={size}>
|
|
59
|
-
<
|
|
60
|
-
<PrevalenceOverTimeInner {...innerProps} />
|
|
61
|
-
</Headline>
|
|
52
|
+
<PrevalenceOverTimeInner {...innerProps} />
|
|
62
53
|
</ResizeContainer>
|
|
63
54
|
</ErrorBoundary>
|
|
64
55
|
);
|
|
@@ -20,7 +20,6 @@ export default {
|
|
|
20
20
|
},
|
|
21
21
|
width: { control: 'text' },
|
|
22
22
|
height: { control: 'text' },
|
|
23
|
-
headline: { control: 'text' },
|
|
24
23
|
yAxisMaxConfig: { control: 'object' },
|
|
25
24
|
},
|
|
26
25
|
};
|
|
@@ -35,7 +34,6 @@ export const Primary = {
|
|
|
35
34
|
views={args.views}
|
|
36
35
|
width={args.width}
|
|
37
36
|
height={args.height}
|
|
38
|
-
headline={args.headline}
|
|
39
37
|
lapisDateField={args.lapisDateField}
|
|
40
38
|
yAxisMaxConfig={args.yAxisMaxConfig}
|
|
41
39
|
/>
|
|
@@ -48,7 +46,6 @@ export const Primary = {
|
|
|
48
46
|
views: ['line'],
|
|
49
47
|
width: '100%',
|
|
50
48
|
height: '700px',
|
|
51
|
-
headline: 'Relative growth advantage',
|
|
52
49
|
lapisDateField: 'date',
|
|
53
50
|
yAxisMaxConfig: {
|
|
54
51
|
linear: 1,
|
|
@@ -10,7 +10,6 @@ import { type LapisFilter } from '../../types';
|
|
|
10
10
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
11
11
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
12
12
|
import { ErrorDisplay } from '../components/error-display';
|
|
13
|
-
import Headline from '../components/headline';
|
|
14
13
|
import Info, { InfoHeadline1, InfoHeadline2, InfoLink, InfoParagraph } from '../components/info';
|
|
15
14
|
import { LoadingDisplay } from '../components/loading-display';
|
|
16
15
|
import { NoDataDisplay } from '../components/no-data-display';
|
|
@@ -26,7 +25,6 @@ export type View = 'line';
|
|
|
26
25
|
export interface RelativeGrowthAdvantageProps extends RelativeGrowthAdvantagePropsInner {
|
|
27
26
|
width: string;
|
|
28
27
|
height: string;
|
|
29
|
-
headline?: string;
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
export interface RelativeGrowthAdvantagePropsInner {
|
|
@@ -41,17 +39,14 @@ export interface RelativeGrowthAdvantagePropsInner {
|
|
|
41
39
|
export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageProps> = ({
|
|
42
40
|
width,
|
|
43
41
|
height,
|
|
44
|
-
headline = 'Relative growth advantage',
|
|
45
42
|
...innerProps
|
|
46
43
|
}) => {
|
|
47
44
|
const size = { height, width };
|
|
48
45
|
|
|
49
46
|
return (
|
|
50
|
-
<ErrorBoundary size={size}
|
|
47
|
+
<ErrorBoundary size={size}>
|
|
51
48
|
<ResizeContainer size={size}>
|
|
52
|
-
<
|
|
53
|
-
<RelativeGrowthAdvantageInner {...innerProps} />
|
|
54
|
-
</Headline>
|
|
49
|
+
<RelativeGrowthAdvantageInner {...innerProps} />
|
|
55
50
|
</ResizeContainer>
|
|
56
51
|
</ErrorBoundary>
|
|
57
52
|
);
|
|
@@ -32,11 +32,7 @@ export const TextInput: FunctionComponent<TextInputProps> = ({ width, ...innerPr
|
|
|
32
32
|
);
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
lapisField,
|
|
37
|
-
placeholderText,
|
|
38
|
-
initialValue,
|
|
39
|
-
}) => {
|
|
35
|
+
const TextInputInner: FunctionComponent<TextInputInnerProps> = ({ lapisField, placeholderText, initialValue }) => {
|
|
40
36
|
const lapis = useContext(LapisUrlContext);
|
|
41
37
|
|
|
42
38
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { withActions } from '@storybook/addon-actions/decorator';
|
|
2
|
+
import { expect, fn, userEvent, waitFor } from '@storybook/test';
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/web-components';
|
|
4
|
+
import { html } from 'lit';
|
|
5
|
+
|
|
6
|
+
import { withComponentDocs } from '../../../.storybook/ComponentDocsBlock';
|
|
7
|
+
import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
|
|
8
|
+
import '../app';
|
|
9
|
+
import './gs-lineage-filter';
|
|
10
|
+
import aggregatedData from '../../preact/lineageFilter/__mockData__/aggregated.json';
|
|
11
|
+
import { type LineageFilterProps } from '../../preact/lineageFilter/lineage-filter';
|
|
12
|
+
import { withinShadowRoot } from '../withinShadowRoot.story';
|
|
13
|
+
|
|
14
|
+
const codeExample = String.raw`
|
|
15
|
+
<gs-lineage-filter
|
|
16
|
+
lapisField="pangoLineage"
|
|
17
|
+
placeholderText="Enter lineage"
|
|
18
|
+
initialValue="B.1.1.7"
|
|
19
|
+
width="50%">
|
|
20
|
+
</gs-lineage-filter>`;
|
|
21
|
+
|
|
22
|
+
const meta: Meta<Required<LineageFilterProps>> = {
|
|
23
|
+
title: 'Input/Lineage filter',
|
|
24
|
+
component: 'gs-lineage-filter',
|
|
25
|
+
parameters: withComponentDocs({
|
|
26
|
+
actions: {
|
|
27
|
+
handles: ['gs-lineage-filter-changed'],
|
|
28
|
+
},
|
|
29
|
+
fetchMock: {
|
|
30
|
+
mocks: [
|
|
31
|
+
{
|
|
32
|
+
matcher: {
|
|
33
|
+
name: 'pangoLineage',
|
|
34
|
+
url: AGGREGATED_ENDPOINT,
|
|
35
|
+
body: {
|
|
36
|
+
fields: ['pangoLineage'],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
response: {
|
|
40
|
+
status: 200,
|
|
41
|
+
body: aggregatedData,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
componentDocs: {
|
|
47
|
+
opensShadowDom: true,
|
|
48
|
+
expectsChildren: false,
|
|
49
|
+
codeExample,
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
52
|
+
decorators: [withActions],
|
|
53
|
+
tags: ['autodocs'],
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default meta;
|
|
57
|
+
|
|
58
|
+
export const Default: StoryObj<Required<LineageFilterProps>> = {
|
|
59
|
+
render: (args) => {
|
|
60
|
+
return html` <gs-app lapis="${LAPIS_URL}">
|
|
61
|
+
<div class="max-w-screen-lg">
|
|
62
|
+
<gs-lineage-filter
|
|
63
|
+
.lapisField=${args.lapisField}
|
|
64
|
+
.placeholderText=${args.placeholderText}
|
|
65
|
+
.initialValue=${args.initialValue}
|
|
66
|
+
.width=${args.width}
|
|
67
|
+
></gs-lineage-filter>
|
|
68
|
+
</div>
|
|
69
|
+
</gs-app>`;
|
|
70
|
+
},
|
|
71
|
+
args: {
|
|
72
|
+
lapisField: 'pangoLineage',
|
|
73
|
+
placeholderText: 'Enter lineage',
|
|
74
|
+
initialValue: 'B.1.1.7',
|
|
75
|
+
width: '100%',
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const FiresEvent: StoryObj<Required<LineageFilterProps>> = {
|
|
80
|
+
...Default,
|
|
81
|
+
play: async ({ canvasElement, step }) => {
|
|
82
|
+
const canvas = await withinShadowRoot(canvasElement, 'gs-lineage-filter');
|
|
83
|
+
|
|
84
|
+
const inputField = () => canvas.getByPlaceholderText('Enter lineage');
|
|
85
|
+
const listenerMock = fn();
|
|
86
|
+
await step('Setup event listener mock', async () => {
|
|
87
|
+
canvasElement.addEventListener('gs-lineage-filter-changed', listenerMock);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
await step('wait until data is loaded', async () => {
|
|
91
|
+
await waitFor(() => {
|
|
92
|
+
return expect(inputField()).toBeEnabled();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await step('Enters an invalid lineage value', async () => {
|
|
97
|
+
await userEvent.type(inputField(), 'notInList');
|
|
98
|
+
await expect(listenerMock).not.toHaveBeenCalled();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
await step('Empty input', async () => {
|
|
102
|
+
await userEvent.type(inputField(), '{backspace>9/}');
|
|
103
|
+
await expect(listenerMock.mock.calls.at(-1)[0].detail).toStrictEqual({
|
|
104
|
+
pangoLineage: undefined,
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
await step('Enter a valid lineage value', async () => {
|
|
109
|
+
await userEvent.type(inputField(), 'B.1.1.7');
|
|
110
|
+
|
|
111
|
+
await expect(listenerMock).toHaveBeenCalledWith(
|
|
112
|
+
expect.objectContaining({
|
|
113
|
+
detail: {
|
|
114
|
+
pangoLineage: 'B.1.1.7',
|
|
115
|
+
},
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
await step('Enter a valid lineage value', async () => {
|
|
121
|
+
await userEvent.type(inputField(), '{backspace>9/}');
|
|
122
|
+
await userEvent.type(inputField(), 'B.1.1.7*');
|
|
123
|
+
|
|
124
|
+
await expect(listenerMock).toHaveBeenCalledWith(
|
|
125
|
+
expect.objectContaining({
|
|
126
|
+
detail: {
|
|
127
|
+
pangoLineage: 'B.1.1.7*',
|
|
128
|
+
},
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
args: {
|
|
134
|
+
...Default.args,
|
|
135
|
+
initialValue: '',
|
|
136
|
+
},
|
|
137
|
+
};
|