Skip to content

Commit 7a54fe6

Browse files
committed
feat(website): add count to the referenceSelector
1 parent cebd17c commit 7a54fe6

File tree

3 files changed

+153
-71
lines changed

3 files changed

+153
-71
lines changed

website/src/components/SearchPage/ReferenceSelector.spec.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ describe('ReferenceSelector', () => {
3737
it('renders nothing in single reference case', () => {
3838
const { container } = render(
3939
<ReferenceSelector
40+
lapisSearchParameters={{}}
41+
lapisUrl='https://example.com/lapis'
4042
filterSchema={filterSchema}
4143
referenceGenomesInfo={SINGLE_SEG_SINGLE_REF_REFERENCEGENOMES}
4244
referenceIdentifierField={referenceIdentifierField}
@@ -52,6 +54,8 @@ describe('ReferenceSelector', () => {
5254
const setSelected = vi.fn();
5355
render(
5456
<ReferenceSelector
57+
lapisSearchParameters={{}}
58+
lapisUrl='https://example.com/lapis'
5559
filterSchema={filterSchema}
5660
referenceGenomesInfo={SINGLE_SEG_MULTI_REF_REFERENCEGENOMES}
5761
referenceIdentifierField={referenceIdentifierField}
@@ -69,6 +73,8 @@ describe('ReferenceSelector', () => {
6973
const setSelected = vi.fn();
7074
render(
7175
<ReferenceSelector
76+
lapisSearchParameters={{}}
77+
lapisUrl='https://example.com/lapis'
7278
filterSchema={multiSegmentFilterSchema}
7379
referenceGenomesInfo={MULTI_SEG_MULTI_REF_REFERENCEGENOMES}
7480
referenceIdentifierField={referenceIdentifierField}
@@ -86,6 +92,8 @@ describe('ReferenceSelector', () => {
8692
const setSelected = vi.fn();
8793
render(
8894
<ReferenceSelector
95+
lapisSearchParameters={{}}
96+
lapisUrl='https://example.com/lapis'
8997
filterSchema={filterSchema}
9098
referenceGenomesInfo={SINGLE_SEG_MULTI_REF_REFERENCEGENOMES}
9199
referenceIdentifierField={referenceIdentifierField}
@@ -102,6 +110,8 @@ describe('ReferenceSelector', () => {
102110
const setSelected = vi.fn();
103111
render(
104112
<ReferenceSelector
113+
lapisSearchParameters={{}}
114+
lapisUrl='https://example.com/lapis'
105115
filterSchema={filterSchema}
106116
referenceGenomesInfo={SINGLE_SEG_MULTI_REF_REFERENCEGENOMES}
107117
referenceIdentifierField={referenceIdentifierField}

website/src/components/SearchPage/ReferenceSelector.tsx

Lines changed: 141 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,116 @@
11
import { type FC, useId, useMemo } from 'react';
22

3+
import type { LapisSearchParameters } from './DownloadDialog/SequenceFilters.tsx';
4+
import { createOptionsProviderHook, type OptionsProvider } from './fields/AutoCompleteOptions.ts';
35
import { type ReferenceGenomesInfo } from '../../types/referencesGenomes.ts';
6+
import { formatNumberWithDefaultLocale } from '../../utils/formatNumber.tsx';
47
import { getReferenceIdentifier } from '../../utils/referenceSelection.ts';
58
import type { MetadataFilterSchema } from '../../utils/search.ts';
6-
import {
7-
getSegmentNames,
8-
segmentsWithMultipleReferences,
9-
type SegmentReferenceSelections,
10-
} from '../../utils/sequenceTypeHelpers.ts';
9+
import { segmentsWithMultipleReferences, type SegmentReferenceSelections } from '../../utils/sequenceTypeHelpers.ts';
1110
import { Button } from '../common/Button.tsx';
1211
import { Select } from '../common/Select.tsx';
1312
import MaterialSymbolsClose from '~icons/material-symbols/close';
1413

14+
type SegmentReferenceSelectorProps = {
15+
selectId: string;
16+
label: string | undefined;
17+
value: string | null | undefined;
18+
onChange: (next: string) => void;
19+
onClear: () => void;
20+
optionsProvider: OptionsProvider;
21+
};
22+
23+
const SegmentReferenceSelector: FC<SegmentReferenceSelectorProps> = ({
24+
selectId,
25+
label,
26+
value,
27+
onChange,
28+
onClear,
29+
optionsProvider,
30+
}) => {
31+
const hook = createOptionsProviderHook(optionsProvider);
32+
const { options, isPending, error, load } = hook();
33+
34+
return (
35+
<div className='bg-gray-50 border border-gray-300 rounded-md p-3 mb-3'>
36+
<label className='block text-xs font-semibold text-gray-700 mb-1' htmlFor={selectId}>
37+
{label}
38+
</label>
39+
40+
<div className='relative'>
41+
<Select
42+
id={selectId}
43+
value={value ?? ''}
44+
onChange={(e) => onChange(e.target.value)}
45+
onFocus={() => {
46+
load();
47+
}}
48+
className='w-full px-2 py-1.5 rounded border border-gray-300 text-sm bg-white focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-200'
49+
>
50+
<option value='' disabled>
51+
Select {formatLabel(label ?? '')}...
52+
</option>
53+
54+
{isPending && (
55+
<option value='' disabled>
56+
Loading...
57+
</option>
58+
)}
59+
60+
{error && !isPending && (
61+
<option value='' disabled>
62+
Failed to load options
63+
</option>
64+
)}
65+
66+
{!isPending &&
67+
!error &&
68+
options.map((opt) => {
69+
const display =
70+
opt.count !== undefined
71+
? `${opt.option} (${formatNumberWithDefaultLocale(opt.count)})`
72+
: opt.option;
73+
74+
return (
75+
<option key={opt.value} value={opt.value}>
76+
{display}
77+
</option>
78+
);
79+
})}
80+
</Select>
81+
82+
{value != null && (
83+
<Button
84+
className='absolute top-2 right-6 flex items-center pr-2 h-5 bg-white rounded-sm'
85+
onClick={onClear}
86+
aria-label={`Clear ${label ?? ''}`}
87+
type='button'
88+
>
89+
<MaterialSymbolsClose className='w-5 h-5 text-gray-400' />
90+
</Button>
91+
)}
92+
</div>
93+
94+
<p className='text-xs text-gray-600 mt-2'>
95+
Select a {formatLabel(label ?? '')} to enable mutation search and download of aligned sequences
96+
</p>
97+
</div>
98+
);
99+
};
100+
15101
type ReferenceSelectorProps = {
102+
lapisSearchParameters: LapisSearchParameters;
103+
lapisUrl: string;
16104
filterSchema: MetadataFilterSchema;
17105
referenceGenomesInfo: ReferenceGenomesInfo;
18106
referenceIdentifierField: string;
19-
setSelectedReferences: (newValues: SegmentReferenceSelections) => void;
20107
selectedReferences: SegmentReferenceSelections;
108+
setSelectedReferences: (newValues: SegmentReferenceSelections) => void;
21109
};
22110

23-
/**
24-
* In the multi pathogen case, this is a prominent selector at the top to choose the reference.
25-
* Choosing a value here is required e.g. to enable mutation search and download of aligned sequences.
26-
*
27-
* Does nothing in the single pathogen case.
28-
*/
29111
export const ReferenceSelector: FC<ReferenceSelectorProps> = ({
112+
lapisSearchParameters,
113+
lapisUrl,
30114
filterSchema,
31115
referenceGenomesInfo,
32116
referenceIdentifierField,
@@ -35,20 +119,25 @@ export const ReferenceSelector: FC<ReferenceSelectorProps> = ({
35119
}) => {
36120
const baseSelectId = useId();
37121

38-
const segments = getSegmentNames(referenceGenomesInfo);
39-
40-
if (segmentsWithMultipleReferences(referenceGenomesInfo).length === 0) {
41-
return null;
42-
}
43-
44-
const labelsBySegment = useMemo(() => {
45-
return segments.reduce<Record<string, string | undefined>>((acc, segmentName) => {
122+
const multiRefSegments = segmentsWithMultipleReferences(referenceGenomesInfo);
123+
if (multiRefSegments.length === 0) return null;
124+
const identifierBySegment = useMemo(() => {
125+
return multiRefSegments.reduce<Record<string, string | undefined>>((acc, segmentName) => {
46126
const identifier = getReferenceIdentifier(
47127
referenceIdentifierField,
48128
segmentName,
49129
referenceGenomesInfo.isMultiSegmented,
50130
);
51131

132+
acc[segmentName] = identifier;
133+
134+
return acc;
135+
}, {});
136+
}, [filterSchema, referenceIdentifierField, referenceGenomesInfo]);
137+
const labelsBySegment = useMemo(() => {
138+
return multiRefSegments.reduce<Record<string, string | undefined>>((acc, segmentName) => {
139+
const identifier = identifierBySegment[segmentName];
140+
52141
acc[segmentName] = identifier ? filterSchema.filterNameToLabelMap()[identifier] : undefined;
53142

54143
return acc;
@@ -57,60 +146,41 @@ export const ReferenceSelector: FC<ReferenceSelectorProps> = ({
57146

58147
return (
59148
<>
60-
{segmentsWithMultipleReferences(referenceGenomesInfo).map((segment) => {
149+
{multiRefSegments.map((segment) => {
61150
const selectId = `${baseSelectId}-${segment}`;
151+
const identifier = identifierBySegment[segment];
152+
const label = labelsBySegment[segment];
62153

63-
return (
64-
<div key={segment} className='bg-gray-50 border border-gray-300 rounded-md p-3 mb-3'>
65-
<label className='block text-xs font-semibold text-gray-700 mb-1' htmlFor={selectId}>
66-
{labelsBySegment[segment]}
67-
</label>
68-
69-
<div className='relative'>
70-
<Select
71-
id={selectId}
72-
value={selectedReferences[segment] ?? ''}
73-
onChange={(e) =>
74-
setSelectedReferences({
75-
...selectedReferences,
76-
[segment]: e.target.value,
77-
})
78-
}
79-
className='w-full px-2 py-1.5 rounded border border-gray-300 text-sm bg-white focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-200'
80-
>
81-
<option value='' disabled>
82-
Select {formatLabel(labelsBySegment[segment] ?? '')}...
83-
</option>
154+
const optionsProvider = useMemo(
155+
() => ({
156+
type: 'generic' as const,
157+
lapisUrl,
158+
lapisSearchParameters,
159+
fieldName: identifier!,
160+
}),
161+
[lapisUrl, lapisSearchParameters, identifier],
162+
);
84163

85-
{Object.keys(referenceGenomesInfo.segmentReferenceGenomes[segment]).map((reference) => (
86-
<option key={reference} value={reference}>
87-
{reference}
88-
</option>
89-
))}
90-
</Select>
91-
92-
{selectedReferences[segment] != null && (
93-
<Button
94-
className='absolute top-2 right-6 flex items-center pr-2 h-5 bg-white rounded-sm'
95-
onClick={() =>
96-
setSelectedReferences({
97-
...selectedReferences,
98-
[segment]: null,
99-
})
100-
}
101-
aria-label={`Clear ${labelsBySegment[segment] ?? ''}`}
102-
type='button'
103-
>
104-
<MaterialSymbolsClose className='w-5 h-5 text-gray-400' />
105-
</Button>
106-
)}
107-
</div>
108-
109-
<p className='text-xs text-gray-600 mt-2'>
110-
Select a {formatLabel(labelsBySegment[segment] ?? '')} to enable mutation search and
111-
download of aligned sequences
112-
</p>
113-
</div>
164+
return (
165+
<SegmentReferenceSelector
166+
key={segment}
167+
label={label}
168+
selectId={selectId}
169+
value={selectedReferences[segment]}
170+
onChange={(e) =>
171+
setSelectedReferences({
172+
...selectedReferences,
173+
[segment]: e,
174+
})
175+
}
176+
onClear={() =>
177+
setSelectedReferences({
178+
...selectedReferences,
179+
[segment]: null,
180+
})
181+
}
182+
optionsProvider={optionsProvider}
183+
/>
114184
);
115185
})}
116186
</>

website/src/components/SearchPage/SearchForm.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ export const SearchForm = ({
186186
selectedReferences !== undefined &&
187187
setSelectedReferences !== undefined && (
188188
<ReferenceSelector
189+
lapisSearchParameters={lapisSearchParameters}
190+
lapisUrl={lapisUrl}
189191
filterSchema={filterSchema}
190192
referenceGenomesInfo={referenceGenomesInfo}
191193
referenceIdentifierField={referenceIdentifierField}

0 commit comments

Comments
 (0)