@genspectrum/dashboard-components 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/custom-elements.json +453 -67
- package/dist/dashboard-components.js +778 -488
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +277 -50
- package/dist/style.css +132 -139
- package/package.json +9 -5
- package/src/lapisApi/lapisApi.ts +1 -1
- package/src/preact/aggregatedData/__mockData__/aggregated.json +585 -0
- package/src/preact/aggregatedData/aggregate-table.tsx +32 -0
- package/src/preact/aggregatedData/aggregate.stories.tsx +53 -0
- package/src/preact/aggregatedData/aggregate.tsx +102 -0
- package/src/preact/components/ReferenceGenomesAwaiter.tsx +25 -0
- package/src/preact/components/csv-download-button.tsx +8 -2
- package/src/preact/components/headline.tsx +16 -4
- package/src/preact/components/min-max-range-slider.tsx +4 -4
- package/src/preact/components/percent-intput.tsx +2 -3
- package/src/preact/components/resize-container.tsx +23 -0
- package/src/preact/components/table.tsx +1 -0
- package/src/preact/components/tabs.stories.tsx +2 -2
- package/src/preact/components/tabs.tsx +47 -24
- package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +36 -4
- package/src/preact/dateRangeSelector/date-range-selector.tsx +57 -43
- package/src/preact/locationFilter/location-filter.tsx +2 -2
- package/src/preact/mutationComparison/getMutationComparisonTableData.spec.ts +5 -5
- package/src/preact/mutationComparison/getMutationComparisonTableData.ts +45 -10
- package/src/preact/mutationComparison/mutation-comparison-table.tsx +20 -22
- package/src/preact/mutationComparison/mutation-comparison-venn.tsx +6 -3
- package/src/preact/mutationComparison/mutation-comparison.stories.tsx +8 -1
- package/src/preact/mutationComparison/mutation-comparison.tsx +13 -4
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +70 -31
- package/src/preact/mutationFilter/mutation-filter.tsx +62 -14
- package/src/preact/mutations/getInsertionsTableData.spec.ts +6 -4
- package/src/preact/mutations/getInsertionsTableData.ts +1 -1
- package/src/preact/mutations/getMutationsTableData.spec.ts +9 -19
- package/src/preact/mutations/getMutationsTableData.ts +1 -1
- package/src/preact/mutations/mutations-insertions-table.tsx +3 -1
- package/src/preact/mutations/mutations-table.tsx +3 -1
- package/src/preact/mutations/mutations.stories.tsx +8 -1
- package/src/preact/mutations/mutations.tsx +16 -5
- package/src/preact/prevalenceOverTime/prevalence-over-time-bar-chart.tsx +1 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +1 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time-line-chart.tsx +1 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +4 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +17 -9
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +8 -5
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +12 -0
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +13 -8
- package/src/preact/shared/sort/sortInsertions.spec.ts +11 -10
- package/src/preact/shared/sort/sortInsertions.ts +10 -17
- package/src/preact/shared/sort/sortSubstitutionsAndDeletions.spec.ts +19 -10
- package/src/preact/shared/sort/sortSubstitutionsAndDeletions.ts +45 -12
- package/src/preact/textInput/text-input.stories.tsx +22 -1
- package/src/preact/textInput/text-input.tsx +3 -1
- package/src/query/queryAggregateData.spec.ts +32 -0
- package/src/query/queryAggregateData.ts +25 -0
- package/src/utils/typeAssertions.spec.ts +31 -0
- package/src/utils/typeAssertions.ts +16 -0
- package/src/web-components/PreactLitAdapter.tsx +0 -1
- package/src/web-components/app.stories.ts +129 -0
- package/src/web-components/app.ts +27 -6
- package/src/web-components/display/aggregate-component.stories.ts +73 -0
- package/src/web-components/display/aggregate-component.tsx +58 -0
- package/src/web-components/display/index.ts +1 -0
- package/src/web-components/display/mutation-comparison-component.stories.ts +29 -11
- package/src/web-components/display/mutation-comparison-component.tsx +72 -4
- package/src/web-components/display/mutations-component.stories.ts +14 -13
- package/src/web-components/display/mutations-component.tsx +14 -1
- package/src/web-components/display/prevalence-over-time-component.stories.ts +20 -18
- package/src/web-components/display/prevalence-over-time-component.tsx +12 -0
- package/src/web-components/display/relative-growth-advantage-component.stories.ts +11 -10
- package/src/web-components/display/relative-growth-advantage-component.tsx +12 -0
- package/src/web-components/input/date-range-selector-component.stories.ts +35 -8
- package/src/web-components/input/date-range-selector-component.tsx +18 -5
- package/src/web-components/input/location-filter-component.stories.ts +17 -8
- package/src/web-components/input/location-filter-component.tsx +2 -6
- package/src/web-components/input/mutation-filter-component.stories.ts +20 -9
- package/src/web-components/input/mutation-filter-component.tsx +10 -2
- package/src/web-components/input/text-input-component.stories.ts +13 -4
- package/src/web-components/input/text-input-component.tsx +11 -2
- package/src/web-components/input/location-filter.mdx +0 -25
|
@@ -2,6 +2,7 @@ import { type FunctionComponent } from 'preact';
|
|
|
2
2
|
|
|
3
3
|
import { getMutationsTableData } from './getMutationsTableData';
|
|
4
4
|
import { type SubstitutionOrDeletionEntry } from '../../types';
|
|
5
|
+
import { type Deletion, type Substitution } from '../../utils/mutations';
|
|
5
6
|
import type { ProportionInterval } from '../components/proportion-selector';
|
|
6
7
|
import { Table } from '../components/table';
|
|
7
8
|
import { sortSubstitutionsAndDeletions } from '../shared/sort/sortSubstitutionsAndDeletions';
|
|
@@ -18,10 +19,11 @@ const MutationsTable: FunctionComponent<MutationsTableProps> = ({ data, proporti
|
|
|
18
19
|
{
|
|
19
20
|
name: 'Mutation',
|
|
20
21
|
sort: {
|
|
21
|
-
compare: (a:
|
|
22
|
+
compare: (a: Substitution | Deletion, b: Substitution | Deletion) => {
|
|
22
23
|
return sortSubstitutionsAndDeletions(a, b);
|
|
23
24
|
},
|
|
24
25
|
},
|
|
26
|
+
formatter: (cell: Substitution | Deletion) => cell.toString(),
|
|
25
27
|
},
|
|
26
28
|
{
|
|
27
29
|
name: 'Type',
|
|
@@ -22,6 +22,7 @@ const meta: Meta<MutationsProps> = {
|
|
|
22
22
|
options: ['table', 'grid', 'insertions'],
|
|
23
23
|
control: { type: 'check' },
|
|
24
24
|
},
|
|
25
|
+
size: [{ control: 'object' }],
|
|
25
26
|
},
|
|
26
27
|
};
|
|
27
28
|
|
|
@@ -31,7 +32,12 @@ const Template = {
|
|
|
31
32
|
render: (args: MutationsProps) => (
|
|
32
33
|
<LapisUrlContext.Provider value={LAPIS_URL}>
|
|
33
34
|
<ReferenceGenomeContext.Provider value={referenceGenome}>
|
|
34
|
-
<Mutations
|
|
35
|
+
<Mutations
|
|
36
|
+
variant={args.variant}
|
|
37
|
+
sequenceType={args.sequenceType}
|
|
38
|
+
views={args.views}
|
|
39
|
+
size={args.size}
|
|
40
|
+
/>
|
|
35
41
|
</ReferenceGenomeContext.Provider>
|
|
36
42
|
</LapisUrlContext.Provider>
|
|
37
43
|
),
|
|
@@ -43,6 +49,7 @@ export const Default: StoryObj<MutationsProps> = {
|
|
|
43
49
|
variant: { country: 'Switzerland', pangoLineage: 'B.1.1.7', dateTo: '2022-01-01' },
|
|
44
50
|
sequenceType: 'nucleotide',
|
|
45
51
|
views: ['grid', 'table', 'insertions'],
|
|
52
|
+
size: { width: '100%', height: '700px' },
|
|
46
53
|
},
|
|
47
54
|
parameters: {
|
|
48
55
|
fetchMock: {
|
|
@@ -24,6 +24,7 @@ import { type DisplayedMutationType, MutationTypeSelector } from '../components/
|
|
|
24
24
|
import { NoDataDisplay } from '../components/no-data-display';
|
|
25
25
|
import type { ProportionInterval } from '../components/proportion-selector';
|
|
26
26
|
import { ProportionSelectorDropdown } from '../components/proportion-selector-dropdown';
|
|
27
|
+
import { ResizeContainer, type Size } from '../components/resize-container';
|
|
27
28
|
import Tabs from '../components/tabs';
|
|
28
29
|
import { useQuery } from '../useQuery';
|
|
29
30
|
|
|
@@ -33,9 +34,10 @@ export interface MutationsProps {
|
|
|
33
34
|
variant: LapisFilter;
|
|
34
35
|
sequenceType: SequenceType;
|
|
35
36
|
views: View[];
|
|
37
|
+
size?: Size;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
export const Mutations: FunctionComponent<MutationsProps> = ({ variant, sequenceType, views }) => {
|
|
40
|
+
export const Mutations: FunctionComponent<MutationsProps> = ({ variant, sequenceType, views, size }) => {
|
|
39
41
|
const lapis = useContext(LapisUrlContext);
|
|
40
42
|
const { data, error, isLoading } = useQuery(async () => {
|
|
41
43
|
return queryMutationsData(variant, sequenceType, lapis);
|
|
@@ -67,9 +69,11 @@ export const Mutations: FunctionComponent<MutationsProps> = ({ variant, sequence
|
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
return (
|
|
70
|
-
<
|
|
71
|
-
<
|
|
72
|
-
|
|
72
|
+
<ResizeContainer size={size} defaultSize={{ height: '700px', width: '100%' }}>
|
|
73
|
+
<Headline heading={headline}>
|
|
74
|
+
<MutationsTabs mutationsData={data} sequenceType={sequenceType} views={views} />
|
|
75
|
+
</Headline>
|
|
76
|
+
</ResizeContainer>
|
|
73
77
|
);
|
|
74
78
|
};
|
|
75
79
|
|
|
@@ -182,7 +186,14 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
|
182
186
|
{activeTab === 'Insertions' && (
|
|
183
187
|
<CsvDownloadButton
|
|
184
188
|
className='mx-1 btn btn-xs'
|
|
185
|
-
getData={() =>
|
|
189
|
+
getData={() =>
|
|
190
|
+
getInsertionsTableData(filteredData.insertions).map((row) => {
|
|
191
|
+
return {
|
|
192
|
+
insertion: row.insertion.toString(),
|
|
193
|
+
count: row.count,
|
|
194
|
+
};
|
|
195
|
+
})
|
|
196
|
+
}
|
|
186
197
|
filename='insertions.csv'
|
|
187
198
|
/>
|
|
188
199
|
)}
|
|
@@ -29,6 +29,7 @@ export default {
|
|
|
29
29
|
options: ['wilson'],
|
|
30
30
|
control: { type: 'check' },
|
|
31
31
|
},
|
|
32
|
+
size: [{ control: 'object' }],
|
|
32
33
|
},
|
|
33
34
|
};
|
|
34
35
|
|
|
@@ -42,6 +43,7 @@ const Template = {
|
|
|
42
43
|
smoothingWindow={args.smoothingWindow}
|
|
43
44
|
views={args.views}
|
|
44
45
|
confidenceIntervalMethods={args.confidenceIntervalMethods}
|
|
46
|
+
size={args.size}
|
|
45
47
|
/>
|
|
46
48
|
</LapisUrlContext.Provider>
|
|
47
49
|
),
|
|
@@ -59,6 +61,7 @@ export const TwoVariants = {
|
|
|
59
61
|
smoothingWindow: 0,
|
|
60
62
|
views: ['bar', 'line', 'bubble', 'table'],
|
|
61
63
|
confidenceIntervalMethods: ['wilson'],
|
|
64
|
+
size: { width: '100%', height: '700px' },
|
|
62
65
|
},
|
|
63
66
|
parameters: {
|
|
64
67
|
fetchMock: {
|
|
@@ -124,6 +127,7 @@ export const OneVariant = {
|
|
|
124
127
|
smoothingWindow: 7,
|
|
125
128
|
views: ['bar', 'line', 'bubble', 'table'],
|
|
126
129
|
confidenceIntervalMethods: ['wilson'],
|
|
130
|
+
size: { width: '100%', height: '700px' },
|
|
127
131
|
},
|
|
128
132
|
parameters: {
|
|
129
133
|
fetchMock: {
|
|
@@ -16,6 +16,7 @@ import Headline from '../components/headline';
|
|
|
16
16
|
import Info from '../components/info';
|
|
17
17
|
import { LoadingDisplay } from '../components/loading-display';
|
|
18
18
|
import { NoDataDisplay } from '../components/no-data-display';
|
|
19
|
+
import { ResizeContainer, type Size } from '../components/resize-container';
|
|
19
20
|
import { ScalingSelector } from '../components/scaling-selector';
|
|
20
21
|
import Tabs from '../components/tabs';
|
|
21
22
|
import { type ConfidenceIntervalMethod } from '../shared/charts/confideceInterval';
|
|
@@ -31,6 +32,7 @@ export interface PrevalenceOverTimeProps {
|
|
|
31
32
|
smoothingWindow: number;
|
|
32
33
|
views: View[];
|
|
33
34
|
confidenceIntervalMethods: ConfidenceIntervalMethod[];
|
|
35
|
+
size?: Size;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({
|
|
@@ -40,6 +42,7 @@ export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({
|
|
|
40
42
|
smoothingWindow,
|
|
41
43
|
views,
|
|
42
44
|
confidenceIntervalMethods,
|
|
45
|
+
size,
|
|
43
46
|
}) => {
|
|
44
47
|
const lapis = useContext(LapisUrlContext);
|
|
45
48
|
|
|
@@ -75,14 +78,16 @@ export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({
|
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
return (
|
|
78
|
-
<
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
<ResizeContainer size={size} defaultSize={{ height: '600px', width: '100%' }}>
|
|
82
|
+
<Headline heading={headline}>
|
|
83
|
+
<PrevalenceOverTimeTabs
|
|
84
|
+
views={views}
|
|
85
|
+
data={data}
|
|
86
|
+
granularity={granularity}
|
|
87
|
+
confidenceIntervalMethods={confidenceIntervalMethods}
|
|
88
|
+
/>
|
|
89
|
+
</Headline>
|
|
90
|
+
</ResizeContainer>
|
|
86
91
|
);
|
|
87
92
|
};
|
|
88
93
|
|
|
@@ -134,7 +139,10 @@ const PrevalenceOverTimeTabs: FunctionComponent<PrevalenceOverTimeTabsProps> = (
|
|
|
134
139
|
content: <PrevalenceOverTimeBubbleChart data={data} yAxisScaleType={yAxisScaleType} />,
|
|
135
140
|
};
|
|
136
141
|
case 'table':
|
|
137
|
-
return {
|
|
142
|
+
return {
|
|
143
|
+
title: 'Table',
|
|
144
|
+
content: <PrevalenceOverTimeTable data={data} granularity={granularity} />,
|
|
145
|
+
};
|
|
138
146
|
}
|
|
139
147
|
};
|
|
140
148
|
|
|
@@ -37,6 +37,7 @@ const RelativeGrowthAdvantageChart = ({ data, yAxisScaleType }: RelativeGrowthAd
|
|
|
37
37
|
datasets: datasets(data),
|
|
38
38
|
},
|
|
39
39
|
options: {
|
|
40
|
+
maintainAspectRatio: false,
|
|
40
41
|
animation: false,
|
|
41
42
|
scales: {
|
|
42
43
|
y: getYAxisScale(yAxisScaleType),
|
|
@@ -51,13 +52,15 @@ const RelativeGrowthAdvantageChart = ({ data, yAxisScaleType }: RelativeGrowthAd
|
|
|
51
52
|
};
|
|
52
53
|
|
|
53
54
|
return (
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
|
|
55
|
+
<div className='flex flex-col h-full'>
|
|
56
|
+
<div className='flex-1'>
|
|
57
|
+
<GsChart configuration={config} />
|
|
58
|
+
</div>
|
|
59
|
+
<p>
|
|
57
60
|
Advantage: {(data.params.fd.value * 100).toFixed(2)}% ({(data.params.fd.ciLower * 100).toFixed(2)}% -{' '}
|
|
58
61
|
{(data.params.fd.ciUpper * 100).toFixed(2)}%)
|
|
59
|
-
</
|
|
60
|
-
|
|
62
|
+
</p>
|
|
63
|
+
</div>
|
|
61
64
|
);
|
|
62
65
|
};
|
|
63
66
|
|
|
@@ -10,6 +10,16 @@ export default {
|
|
|
10
10
|
parameters: {
|
|
11
11
|
fetchMock: {},
|
|
12
12
|
},
|
|
13
|
+
argTypes: {
|
|
14
|
+
numerator: { control: 'object' },
|
|
15
|
+
denominator: { control: 'object' },
|
|
16
|
+
generationTime: { control: 'number' },
|
|
17
|
+
views: {
|
|
18
|
+
options: ['line'],
|
|
19
|
+
control: { type: 'check' },
|
|
20
|
+
},
|
|
21
|
+
size: [{ control: 'object' }],
|
|
22
|
+
},
|
|
13
23
|
};
|
|
14
24
|
|
|
15
25
|
export const Primary = {
|
|
@@ -20,6 +30,7 @@ export const Primary = {
|
|
|
20
30
|
denominator={args.denominator}
|
|
21
31
|
generationTime={args.generationTime}
|
|
22
32
|
views={args.views}
|
|
33
|
+
size={args.size}
|
|
23
34
|
/>
|
|
24
35
|
</LapisUrlContext.Provider>
|
|
25
36
|
),
|
|
@@ -28,6 +39,7 @@ export const Primary = {
|
|
|
28
39
|
denominator: { country: 'Switzerland', dateFrom: '2020-12-01', dateTo: '2021-03-01' },
|
|
29
40
|
generationTime: 7,
|
|
30
41
|
views: ['line'],
|
|
42
|
+
size: { width: '100%', height: '700px' },
|
|
31
43
|
},
|
|
32
44
|
parameters: {
|
|
33
45
|
fetchMock: {
|
|
@@ -13,6 +13,7 @@ import Headline from '../components/headline';
|
|
|
13
13
|
import Info from '../components/info';
|
|
14
14
|
import { LoadingDisplay } from '../components/loading-display';
|
|
15
15
|
import { NoDataDisplay } from '../components/no-data-display';
|
|
16
|
+
import { ResizeContainer, type Size } from '../components/resize-container';
|
|
16
17
|
import { ScalingSelector } from '../components/scaling-selector';
|
|
17
18
|
import Tabs from '../components/tabs';
|
|
18
19
|
import { type ScaleType } from '../shared/charts/getYAxisScale';
|
|
@@ -25,6 +26,7 @@ export interface RelativeGrowthAdvantageProps {
|
|
|
25
26
|
denominator: LapisFilter;
|
|
26
27
|
generationTime: number;
|
|
27
28
|
views: View[];
|
|
29
|
+
size?: Size;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageProps> = ({
|
|
@@ -32,6 +34,7 @@ export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageP
|
|
|
32
34
|
denominator,
|
|
33
35
|
generationTime,
|
|
34
36
|
views,
|
|
37
|
+
size,
|
|
35
38
|
}) => {
|
|
36
39
|
const lapis = useContext(LapisUrlContext);
|
|
37
40
|
const [yAxisScaleType, setYAxisScaleType] = useState<ScaleType>('linear');
|
|
@@ -67,14 +70,16 @@ export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageP
|
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
return (
|
|
70
|
-
<
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
<ResizeContainer size={size} defaultSize={{ height: '700px', width: '100%' }}>
|
|
74
|
+
<Headline heading={headline}>
|
|
75
|
+
<RelativeGrowthAdvantageTabs
|
|
76
|
+
data={data}
|
|
77
|
+
yAxisScaleType={yAxisScaleType}
|
|
78
|
+
setYAxisScaleType={setYAxisScaleType}
|
|
79
|
+
views={views}
|
|
80
|
+
/>
|
|
81
|
+
</Headline>
|
|
82
|
+
</ResizeContainer>
|
|
78
83
|
);
|
|
79
84
|
};
|
|
80
85
|
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { describe, expect, test } from 'vitest';
|
|
2
2
|
|
|
3
3
|
import { sortInsertions } from './sortInsertions';
|
|
4
|
+
import { Insertion } from '../../../utils/mutations';
|
|
4
5
|
|
|
5
6
|
describe('sortInsertions with no segments', () => {
|
|
6
7
|
test('should sort for positions first', () => {
|
|
7
|
-
const a = '
|
|
8
|
-
const b = '
|
|
8
|
+
const a = new Insertion(undefined, 1, 'A');
|
|
9
|
+
const b = new Insertion(undefined, 2, 'A');
|
|
9
10
|
|
|
10
11
|
expect(sortInsertions(a, b)).toBeLessThan(0);
|
|
11
12
|
expect(sortInsertions(b, a)).toBeGreaterThan(0);
|
|
12
13
|
});
|
|
13
14
|
|
|
14
15
|
test('should sort for symbols second', () => {
|
|
15
|
-
const a = '
|
|
16
|
-
const b = '
|
|
16
|
+
const a = new Insertion(undefined, 1, 'A');
|
|
17
|
+
const b = new Insertion(undefined, 1, 'B');
|
|
17
18
|
|
|
18
19
|
expect(sortInsertions(a, b)).toBeLessThan(0);
|
|
19
20
|
expect(sortInsertions(b, a)).toBeGreaterThan(0);
|
|
@@ -22,24 +23,24 @@ describe('sortInsertions with no segments', () => {
|
|
|
22
23
|
|
|
23
24
|
describe('sortInsertions with segments', () => {
|
|
24
25
|
test('should sort for segments first', () => {
|
|
25
|
-
const a = '
|
|
26
|
-
const b = '
|
|
26
|
+
const a = new Insertion('AA1', 1, 'A');
|
|
27
|
+
const b = new Insertion('BB1', 1, 'A');
|
|
27
28
|
|
|
28
29
|
expect(sortInsertions(a, b)).toBeLessThan(0);
|
|
29
30
|
expect(sortInsertions(b, a)).toBeGreaterThan(0);
|
|
30
31
|
});
|
|
31
32
|
|
|
32
33
|
test('should sort for positions second', () => {
|
|
33
|
-
const a = '
|
|
34
|
-
const b = '
|
|
34
|
+
const a = new Insertion('AA1', 1, 'A');
|
|
35
|
+
const b = new Insertion('AA1', 2, 'A');
|
|
35
36
|
|
|
36
37
|
expect(sortInsertions(a, b)).toBeLessThan(0);
|
|
37
38
|
expect(sortInsertions(b, a)).toBeGreaterThan(0);
|
|
38
39
|
});
|
|
39
40
|
|
|
40
41
|
test('should sort for symbols third', () => {
|
|
41
|
-
const a = '
|
|
42
|
-
const b = '
|
|
42
|
+
const a = new Insertion('AA1', 1, 'A');
|
|
43
|
+
const b = new Insertion('AA1', 1, 'B');
|
|
43
44
|
|
|
44
45
|
expect(sortInsertions(a, b)).toBeLessThan(0);
|
|
45
46
|
expect(sortInsertions(b, a)).toBeGreaterThan(0);
|
|
@@ -1,21 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { comparePositions, compareSegments } from './sortSubstitutionsAndDeletions';
|
|
2
|
+
import { type Insertion } from '../../../utils/mutations';
|
|
2
3
|
|
|
3
|
-
export const sortInsertions = (a:
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
export const sortInsertions = (a: Insertion, b: Insertion) => {
|
|
5
|
+
if (a.segment !== b.segment) {
|
|
6
|
+
return compareSegments(a.segment, b.segment);
|
|
7
|
+
}
|
|
6
8
|
|
|
7
|
-
if (
|
|
8
|
-
|
|
9
|
-
const segmentB = insertionB.segment;
|
|
10
|
-
if (segmentA !== undefined && segmentB !== undefined && segmentA !== segmentB) {
|
|
11
|
-
return segmentA.localeCompare(segmentB);
|
|
12
|
-
}
|
|
13
|
-
const positionA = insertionA.position;
|
|
14
|
-
const positionB = insertionB.position;
|
|
15
|
-
if (positionA !== positionB) {
|
|
16
|
-
return positionA - positionB;
|
|
17
|
-
}
|
|
18
|
-
return insertionA.insertedSymbols.localeCompare(insertionB.insertedSymbols);
|
|
9
|
+
if (a.position !== b.position) {
|
|
10
|
+
return comparePositions(a.position, b.position);
|
|
19
11
|
}
|
|
20
|
-
|
|
12
|
+
|
|
13
|
+
return a.insertedSymbols.localeCompare(b.insertedSymbols);
|
|
21
14
|
};
|
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
import { describe, expect, test } from 'vitest';
|
|
2
2
|
|
|
3
3
|
import { sortSubstitutionsAndDeletions } from './sortSubstitutionsAndDeletions';
|
|
4
|
+
import { Deletion, Substitution } from '../../../utils/mutations';
|
|
4
5
|
|
|
5
6
|
describe('sortSubstitutionsAndDeletions with no segments', () => {
|
|
6
7
|
test('should sort for positions first', () => {
|
|
7
|
-
const a = '
|
|
8
|
-
const b = '
|
|
8
|
+
const a = new Substitution(undefined, 'A', 'B', 123);
|
|
9
|
+
const b = new Substitution(undefined, 'A', 'B', 234);
|
|
9
10
|
|
|
10
11
|
expect(sortSubstitutionsAndDeletions(a, b)).toBeLessThan(0);
|
|
11
12
|
expect(sortSubstitutionsAndDeletions(b, a)).toBeGreaterThan(0);
|
|
12
13
|
});
|
|
13
14
|
|
|
14
15
|
test('should sort for substitutionValue second', () => {
|
|
15
|
-
const a = '
|
|
16
|
-
const b = '
|
|
16
|
+
const a = new Substitution(undefined, 'A', 'A', 123);
|
|
17
|
+
const b = new Substitution(undefined, 'A', 'B', 123);
|
|
18
|
+
|
|
19
|
+
expect(sortSubstitutionsAndDeletions(a, b)).toBeLessThan(0);
|
|
20
|
+
expect(sortSubstitutionsAndDeletions(b, a)).toBeGreaterThan(0);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('should sort for substitutionValue over deletion', () => {
|
|
24
|
+
const a = new Substitution(undefined, 'A', 'A', 123);
|
|
25
|
+
const b = new Deletion(undefined, 'A', 123);
|
|
17
26
|
|
|
18
27
|
expect(sortSubstitutionsAndDeletions(a, b)).toBeLessThan(0);
|
|
19
28
|
expect(sortSubstitutionsAndDeletions(b, a)).toBeGreaterThan(0);
|
|
@@ -22,24 +31,24 @@ describe('sortSubstitutionsAndDeletions with no segments', () => {
|
|
|
22
31
|
|
|
23
32
|
describe('sortSubstitutionsAndDeletions with segments', () => {
|
|
24
33
|
test('should sort for segment first', () => {
|
|
25
|
-
const a = 'AA1
|
|
26
|
-
const b = 'BB1
|
|
34
|
+
const a = new Substitution('AA1', 'A', 'B', 123);
|
|
35
|
+
const b = new Substitution('BB1', 'A', 'B', 234);
|
|
27
36
|
|
|
28
37
|
expect(sortSubstitutionsAndDeletions(a, b)).toBeLessThan(0);
|
|
29
38
|
expect(sortSubstitutionsAndDeletions(b, a)).toBeGreaterThan(0);
|
|
30
39
|
});
|
|
31
40
|
|
|
32
41
|
test('should sort for position second', () => {
|
|
33
|
-
const a = 'AA1
|
|
34
|
-
const b = 'AA1
|
|
42
|
+
const a = new Substitution('AA1', 'A', 'B', 123);
|
|
43
|
+
const b = new Substitution('AA1', 'A', 'B', 234);
|
|
35
44
|
|
|
36
45
|
expect(sortSubstitutionsAndDeletions(a, b)).toBeLessThan(0);
|
|
37
46
|
expect(sortSubstitutionsAndDeletions(b, a)).toBeGreaterThan(0);
|
|
38
47
|
});
|
|
39
48
|
|
|
40
49
|
test('should sort for substitutionValue third', () => {
|
|
41
|
-
const a = 'AA1
|
|
42
|
-
const b = 'AA1
|
|
50
|
+
const a = new Substitution('AA1', 'A', 'A', 123);
|
|
51
|
+
const b = new Substitution('AA1', 'A', 'B', 123);
|
|
43
52
|
|
|
44
53
|
expect(sortSubstitutionsAndDeletions(a, b)).toBeLessThan(0);
|
|
45
54
|
expect(sortSubstitutionsAndDeletions(b, a)).toBeGreaterThan(0);
|
|
@@ -1,17 +1,50 @@
|
|
|
1
|
-
|
|
1
|
+
import { Deletion, type Substitution } from '../../../utils/mutations';
|
|
2
2
|
|
|
3
|
-
export const sortSubstitutionsAndDeletions = (a:
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
export const sortSubstitutionsAndDeletions = (a: Substitution | Deletion, b: Substitution | Deletion) => {
|
|
4
|
+
if (a.segment !== b.segment) {
|
|
5
|
+
compareSegments(a.segment, b.segment);
|
|
6
|
+
}
|
|
6
7
|
|
|
7
|
-
if (
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
if (a.position !== b.position) {
|
|
9
|
+
return comparePositions(a.position, b.position);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const aIsDeletion = a instanceof Deletion;
|
|
13
|
+
const bIsDeletion = b instanceof Deletion;
|
|
14
|
+
|
|
15
|
+
if (aIsDeletion !== bIsDeletion) {
|
|
16
|
+
return aIsDeletion ? 1 : -1;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!aIsDeletion && !bIsDeletion) {
|
|
20
|
+
if (a.substitutionValue !== b.substitutionValue) {
|
|
21
|
+
return compareSubstitutionValues(a.substitutionValue, b.substitutionValue);
|
|
13
22
|
}
|
|
14
|
-
return aMatch[4].localeCompare(bMatch[4]);
|
|
15
23
|
}
|
|
16
|
-
|
|
24
|
+
|
|
25
|
+
return 0;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const compareSegments = (a: string | undefined, b: string | undefined) => {
|
|
29
|
+
if (a === undefined) {
|
|
30
|
+
return -1;
|
|
31
|
+
}
|
|
32
|
+
if (b === undefined) {
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
return a.localeCompare(b);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const comparePositions = (a: number, b: number) => {
|
|
39
|
+
return a - b;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const compareSubstitutionValues = (a: string | undefined, b: string | undefined) => {
|
|
43
|
+
if (a === undefined) {
|
|
44
|
+
return -1;
|
|
45
|
+
}
|
|
46
|
+
if (b === undefined) {
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
return a.localeCompare(b);
|
|
17
50
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { withActions } from '@storybook/addon-actions/decorator';
|
|
2
2
|
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
3
|
+
import { expect, waitFor, within } from '@storybook/test';
|
|
3
4
|
|
|
4
5
|
import data from './__mockData__/aggregated_hosts.json';
|
|
5
6
|
import { TextInput, type TextInputProps } from './text-input';
|
|
@@ -39,7 +40,11 @@ export default meta;
|
|
|
39
40
|
export const Default: StoryObj<TextInputProps> = {
|
|
40
41
|
render: (args) => (
|
|
41
42
|
<LapisUrlContext.Provider value={LAPIS_URL}>
|
|
42
|
-
<TextInput
|
|
43
|
+
<TextInput
|
|
44
|
+
lapisField={args.lapisField}
|
|
45
|
+
placeholderText={args.placeholderText}
|
|
46
|
+
initialValue={args.initialValue}
|
|
47
|
+
/>
|
|
43
48
|
</LapisUrlContext.Provider>
|
|
44
49
|
),
|
|
45
50
|
args: {
|
|
@@ -47,3 +52,19 @@ export const Default: StoryObj<TextInputProps> = {
|
|
|
47
52
|
placeholderText: 'Enter a host name',
|
|
48
53
|
},
|
|
49
54
|
};
|
|
55
|
+
|
|
56
|
+
export const WithInitialValue: StoryObj<TextInputProps> = {
|
|
57
|
+
...Default,
|
|
58
|
+
args: {
|
|
59
|
+
...Default.args,
|
|
60
|
+
initialValue: 'Homo sapiens',
|
|
61
|
+
},
|
|
62
|
+
play: async ({ canvasElement }) => {
|
|
63
|
+
const canvas = within(canvasElement);
|
|
64
|
+
|
|
65
|
+
await waitFor(() => {
|
|
66
|
+
const input = canvas.getByPlaceholderText('Enter a host name', { exact: false });
|
|
67
|
+
expect(input).toHaveValue('Homo sapiens');
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
};
|
|
@@ -11,9 +11,10 @@ import { useQuery } from '../useQuery';
|
|
|
11
11
|
export interface TextInputProps {
|
|
12
12
|
lapisField: string;
|
|
13
13
|
placeholderText?: string;
|
|
14
|
+
initialValue?: string;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export const TextInput: FunctionComponent<TextInputProps> = ({ lapisField, placeholderText }) => {
|
|
17
|
+
export const TextInput: FunctionComponent<TextInputProps> = ({ lapisField, placeholderText, initialValue }) => {
|
|
17
18
|
const lapis = useContext(LapisUrlContext);
|
|
18
19
|
|
|
19
20
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
@@ -62,6 +63,7 @@ export const TextInput: FunctionComponent<TextInputProps> = ({ lapisField, place
|
|
|
62
63
|
onInput={onInput}
|
|
63
64
|
ref={inputRef}
|
|
64
65
|
list={lapisField}
|
|
66
|
+
value={initialValue}
|
|
65
67
|
/>
|
|
66
68
|
<datalist id={lapisField}>
|
|
67
69
|
{data.map((item) => (
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { queryAggregateData } from './queryAggregateData';
|
|
4
|
+
import { DUMMY_LAPIS_URL, lapisRequestMocks } from '../../vitest.setup';
|
|
5
|
+
|
|
6
|
+
describe('queryAggregateData', () => {
|
|
7
|
+
test('should fetch aggregate data', async () => {
|
|
8
|
+
const fields = ['division', 'host'];
|
|
9
|
+
const filter = { country: 'USA' };
|
|
10
|
+
|
|
11
|
+
lapisRequestMocks.aggregated(
|
|
12
|
+
{ fields, ...filter },
|
|
13
|
+
{
|
|
14
|
+
data: [
|
|
15
|
+
{ count: 4, region: 'region1', host: 'host1' },
|
|
16
|
+
{ count: 4, region: 'region1', host: 'host2' },
|
|
17
|
+
{ count: 8, region: 'region2', host: 'host1' },
|
|
18
|
+
{ count: 16, region: 'region2', host: 'host2' },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const result = await queryAggregateData(filter, fields, DUMMY_LAPIS_URL);
|
|
24
|
+
|
|
25
|
+
expect(result).to.deep.equal([
|
|
26
|
+
{ proportion: 0.125, count: 4, region: 'region1', host: 'host1' },
|
|
27
|
+
{ proportion: 0.125, count: 4, region: 'region1', host: 'host2' },
|
|
28
|
+
{ proportion: 0.25, count: 8, region: 'region2', host: 'host1' },
|
|
29
|
+
{ proportion: 0.5, count: 16, region: 'region2', host: 'host2' },
|
|
30
|
+
]);
|
|
31
|
+
});
|
|
32
|
+
});
|