Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(website): search reactoring, fixes, and new features #1890

Merged
merged 84 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
d6c5cf5
wip v broken
theosanderson May 13, 2024
4aae2c3
wip
theosanderson May 13, 2024
094b39e
update
theosanderson May 13, 2024
8d4e2b9
up
theosanderson May 13, 2024
e4dea0d
update
theosanderson May 14, 2024
6a58b5d
update
theosanderson May 14, 2024
559dd52
updage
theosanderson May 14, 2024
a32574f
up
theosanderson May 14, 2024
4c88574
visibility progress
theosanderson May 15, 2024
0495e79
search kinda functional
theosanderson May 15, 2024
851781f
wip pagination
theosanderson May 15, 2024
9dfb8c4
autocomplete working better
theosanderson May 15, 2024
74767da
fix
theosanderson May 15, 2024
f1fabbf
update
theosanderson May 15, 2024
38f3e0d
date wip
theosanderson May 15, 2024
ae2c9e7
up
theosanderson May 15, 2024
b36cc25
better
theosanderson May 15, 2024
6c6afb9
fixup
theosanderson May 15, 2024
2e03769
format
theosanderson May 15, 2024
40f84e4
update
theosanderson May 15, 2024
da424e4
format
theosanderson May 15, 2024
16693c2
Merge branch 'main' into search-refactor
theosanderson May 15, 2024
a3fdb35
up
theosanderson May 15, 2024
a88eea5
orderby
theosanderson May 17, 2024
93e0375
update
theosanderson May 17, 2024
fb590f6
update
theosanderson May 17, 2024
e7fc09b
update
theosanderson May 17, 2024
8529c73
fix accession field
theosanderson May 18, 2024
1eb9b92
clean a bit
theosanderson May 18, 2024
976afd9
update
theosanderson May 18, 2024
bafa7bc
mutation maybe working
theosanderson May 18, 2024
1deb656
fixup
theosanderson May 18, 2024
752ae51
some cleanup
theosanderson May 18, 2024
89b75a7
fixup
theosanderson May 18, 2024
4a55f00
fix
theosanderson May 19, 2024
8f9a08f
Merge branch 'main' into search-refactor
theosanderson May 19, 2024
1c6641e
fix merge
theosanderson May 19, 2024
4be97df
update
theosanderson May 19, 2024
a0dfe7c
fix
theosanderson May 19, 2024
b487417
update
theosanderson May 19, 2024
b777a64
cleanup
theosanderson May 19, 2024
e9c76ec
clean up
theosanderson May 19, 2024
d0299e6
fix
theosanderson May 19, 2024
2841064
fixes
theosanderson May 19, 2024
53fa229
updates
theosanderson May 19, 2024
dfaff56
up
theosanderson May 19, 2024
b89eb0d
up
theosanderson May 20, 2024
825e9c8
update
theosanderson May 20, 2024
b8596fd
a
theosanderson May 20, 2024
fbd74ce
a
theosanderson May 20, 2024
3f6dc2a
update
theosanderson May 20, 2024
cae208b
Merge branch 'main' into search-refactor
theosanderson May 20, 2024
f7f19b1
update
theosanderson May 20, 2024
7917a98
upd
theosanderson May 20, 2024
194c13b
up
theosanderson May 20, 2024
67bb217
update
theosanderson May 20, 2024
08e92b3
fix a test
theosanderson May 20, 2024
8564ee3
updaste
theosanderson May 20, 2024
89342d6
fix
theosanderson May 20, 2024
3e8f4c5
fix
theosanderson May 20, 2024
4f7a48e
clean
theosanderson May 20, 2024
1cf2fc2
u
theosanderson May 20, 2024
5b2bfa8
fix
theosanderson May 20, 2024
1184aeb
format
theosanderson May 20, 2024
520b574
update
theosanderson May 20, 2024
e26bd5e
update
theosanderson May 20, 2024
3fd1ecd
update
theosanderson May 20, 2024
c2097a1
update
theosanderson May 20, 2024
b867a3d
fix
theosanderson May 20, 2024
614b11c
update
theosanderson May 20, 2024
3b9bddb
format
theosanderson May 20, 2024
1cfa971
fix
theosanderson May 20, 2024
ebcf8f9
Merge branch 'main' into search-refactor
theosanderson May 21, 2024
1d19eb2
update
theosanderson May 21, 2024
1059036
update
theosanderson May 21, 2024
f4afb3b
improve
theosanderson May 21, 2024
1cfcefa
update
theosanderson May 21, 2024
5b09e82
update
theosanderson May 21, 2024
32c64f1
improve typing
theosanderson May 21, 2024
a311bee
update
theosanderson May 21, 2024
45123de
update
theosanderson May 21, 2024
fe07ff5
update
theosanderson May 21, 2024
cff6568
format
theosanderson May 21, 2024
6753a7c
Merge branch 'main' into search-refactor
theosanderson May 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions kubernetes/loculus/templates/_common-metadata.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ fields:
{{- if .initiallyVisible }}
initiallyVisible: {{ .initiallyVisible }}
{{- end }}
{{- if or (or (eq .type "timestamp") (eq .type "date")) ( .rangeSearch) }}
rangeSearch: true
{{- end }}
{{- if .hideOnSequenceDetailsPage }}
hideOnSequenceDetailsPage: {{ .hideOnSequenceDetailsPage }}
{{- end }}
Expand Down
41 changes: 17 additions & 24 deletions website/src/components/SearchPage/CustomizeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,26 @@ const CheckboxField: React.FC<CheckboxFieldProps> = ({ label, checked, onChange,
</div>
);

interface FieldValue {
name: string;
label: string;
isVisible?: boolean;
notSearchable?: boolean;
}

interface CustomizeModalProps {
isCustomizeModalOpen: boolean;
toggleCustomizeModal: () => void;
alwaysPresentFieldNames: string[];
fieldValues: FieldValue[];
handleFieldVisibilityChange: (fieldName: string, isVisible: boolean) => void;
visibilities: Map<string, boolean>;
setAVisibility: (fieldName: string, isVisible: boolean) => void;
nameToLabelMap: Record<string, string>;
}

export const CustomizeModal: React.FC<CustomizeModalProps> = ({
isCustomizeModalOpen,
toggleCustomizeModal,
alwaysPresentFieldNames,
fieldValues,
handleFieldVisibilityChange,
visibilities,
setAVisibility,
nameToLabelMap,
}) => {
return (
<Transition appear show={isCustomizeModalOpen}>
<Dialog as='div' className='fixed inset-0 z-10 overflow-y-auto' onClose={toggleCustomizeModal}>
<Dialog as='div' className='fixed inset-0 z-50 overflow-y-auto' onClose={toggleCustomizeModal}>
<div className='min-h-screen px-4 text-center'>
<Dialog.Overlay className='fixed inset-0 bg-black opacity-30' />

Expand All @@ -66,18 +61,16 @@ export const CustomizeModal: React.FC<CustomizeModalProps> = ({
<CheckboxField key={fieldName} label={fieldName} checked disabled />
))}

{fieldValues
.filter((field) => field.notSearchable !== true)
.map((field) => (
<CheckboxField
key={field.name}
label={field.label}
checked={field.isVisible !== false}
onChange={(e) => {
handleFieldVisibilityChange(field.name, e.target.checked);
}}
/>
))}
{Array.from(visibilities).map(([fieldName, visible]) => (
<CheckboxField
key={fieldName}
label={nameToLabelMap[fieldName]}
checked={visible}
onChange={(e) => {
setAVisibility(fieldName, e.target.checked);
}}
/>
))}
</div>

<div className='mt-6'>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
import type { FC } from 'react';

import type { FilterValue, MutationFilter } from '../../../types/config.ts';
import type { FieldValues } from '../../../types/config';

type ActiveDownloadFiltersProps = {
metadataFilter: FilterValue[];
mutationFilter: MutationFilter;
lapisSearchParameters: Record<string, any>;
hiddenFieldValues: FieldValues;
};

export const ActiveDownloadFilters: FC<ActiveDownloadFiltersProps> = ({ metadataFilter, mutationFilter }) => {
const filterValues: FilterValue[] = metadataFilter.filter((f) => f.filterValue.length > 0);
[
{ name: 'nucleotideMutations', value: mutationFilter.nucleotideMutationQueries },
{ name: 'aminoAcidMutations', value: mutationFilter.aminoAcidMutationQueries },
{ name: 'nucleotideInsertion', value: mutationFilter.nucleotideInsertionQueries },
{ name: 'aminoAcidInsertions', value: mutationFilter.aminoAcidInsertionQueries },
].forEach(({ name, value }) => {
if (value !== undefined && value.length > 0) {
filterValues.push({ name, filterValue: value.join(', ') });
}
});
export const ActiveDownloadFilters: FC<ActiveDownloadFiltersProps> = ({ lapisSearchParameters, hiddenFieldValues }) => {
let filterValues = Object.entries(lapisSearchParameters)
.filter((vals) => vals[1] !== undefined && vals[1] !== '')
.filter(([name, val]) => !(Object.keys(hiddenFieldValues).includes(name) && hiddenFieldValues[name] === val))
.map(([name, filterValue]) => ({ name, filterValue }));

filterValues = filterValues.filter(({ filterValue }) => filterValue.length > 0);

if (filterValues.length === 0) {
return undefined;
return null;
}

return (
Expand All @@ -30,7 +25,7 @@ export const ActiveDownloadFilters: FC<ActiveDownloadFiltersProps> = ({ metadata
<div className='flex flex-row flex-wrap gap-4'>
{filterValues.map(({ name, filterValue }) => (
<div key={name} className='border-black border rounded-full px-2 py-1 text-sm'>
{name}: {filterValue}
{name}: {typeof filterValue === 'object' ? filterValue.join(', ') : filterValue}
</div>
))}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,19 @@ import { type FC, type MouseEventHandler, useMemo } from 'react';

import { type DownloadOption, generateDownloadUrl } from './generateDownloadUrl.ts';
import { approxMaxAcceptableUrlLength } from '../../../routes/routes.ts';
import type { AccessionFilter, FilterValue, MutationFilter } from '../../../types/config.ts';

type DownloadButtonProps = {
downloadOption: DownloadOption | undefined;
lapisUrl: string;
accessionFilter: AccessionFilter;
metadataFilter: FilterValue[];
mutationFilter: MutationFilter;
lapisSearchParameters: Record<string, any>;
disabled?: boolean;
onClick?: () => void;
};

export const DownloadButton: FC<DownloadButtonProps> = ({
downloadOption,
lapisUrl,
accessionFilter,
metadataFilter,
mutationFilter,
lapisSearchParameters,
disabled = false,
onClick,
}) => {
Expand All @@ -37,13 +32,7 @@ export const DownloadButton: FC<DownloadButtonProps> = ({
};
}

const { url, baseUrl, params } = generateDownloadUrl(
accessionFilter,
metadataFilter,
mutationFilter,
downloadOption,
lapisUrl,
);
const { url, baseUrl, params } = generateDownloadUrl(lapisSearchParameters, downloadOption, lapisUrl);
const useGet = url.length <= approxMaxAcceptableUrlLength;
if (useGet) {
return {
Expand All @@ -62,7 +51,7 @@ export const DownloadButton: FC<DownloadButtonProps> = ({
}
},
};
}, [downloadOption, disabled, accessionFilter, metadataFilter, mutationFilter, lapisUrl, onClick]);
}, [downloadOption, disabled, lapisSearchParameters, lapisUrl, onClick]);

return (
<a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import userEvent from '@testing-library/user-event';
import { beforeAll, describe, expect, test, vi } from 'vitest';

import { DownloadDialog } from './DownloadDialog.tsx';
import type { AccessionFilter, FilterValue, MutationFilter } from '../../../types/config.ts';
import type { ReferenceGenomesSequenceNames } from '../../../types/referencesGenomes.ts';

const defaultReferenceGenome: ReferenceGenomesSequenceNames = {
Expand All @@ -13,20 +12,13 @@ const defaultReferenceGenome: ReferenceGenomesSequenceNames = {

const defaultLapisUrl = 'https://lapis';

async function renderDialog(
accessionFilter: AccessionFilter = {},
metadataFilter: FilterValue[] = [],
mutationFilter: MutationFilter = {},
referenceGenomesSequenceNames: ReferenceGenomesSequenceNames = defaultReferenceGenome,
lapisUrl: string = defaultLapisUrl,
) {
async function renderDialog(lapisSearchParameters: any = {}) {
render(
<DownloadDialog
accessionFilter={accessionFilter}
metadataFilter={metadataFilter}
mutationFilter={mutationFilter}
referenceGenomesSequenceNames={referenceGenomesSequenceNames}
lapisUrl={lapisUrl}
lapisSearchParameters={lapisSearchParameters}
referenceGenomesSequenceNames={defaultReferenceGenome}
lapisUrl={defaultLapisUrl}
hiddenFieldValues={{}}
/>,
);

Expand All @@ -49,12 +41,11 @@ describe('DownloadDialog', () => {
});

test('should display active filters if there are some', async () => {
await renderDialog({}, [{ name: 'field1', filterValue: 'value1' }], {
nucleotideMutationQueries: ['A123T', 'G234C'],
});
await renderDialog({ field1: 'value1', nucleotideMutations: 'A123T,G234C' });
expect(screen.queryByText(/Active filters/)).toBeInTheDocument();
expect(screen.queryByText('field1: value1')).toBeInTheDocument();
expect(screen.queryByText(/A123T, G234C/)).toBeInTheDocument();

expect(screen.queryByText(/A123T,G234C/)).toBeInTheDocument();
});

test('should not display active filters if there are none', async () => {
Expand All @@ -77,7 +68,7 @@ describe('DownloadDialog', () => {
});

test('should generate the right download link', async () => {
await renderDialog({ accession: ['accession1', 'accession2'] }, [{ name: 'field1', filterValue: 'value1' }]);
await renderDialog({ accession: ['accession1', 'accession2'], field1: 'value1' });
await checkAgreement();

expect(getDownloadHref()).toBe(
Expand Down
21 changes: 10 additions & 11 deletions website/src/components/SearchPage/DownloadDialog/DownloadDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,21 @@ import { DownloadButton } from './DownloadButton.tsx';
import { DownloadForm } from './DownloadForm.tsx';
import { type DownloadOption } from './generateDownloadUrl.ts';
import { routes } from '../../../routes/routes.ts';
import type { AccessionFilter, FilterValue, MutationFilter } from '../../../types/config.ts';
import type { FieldValues } from '../../../types/config.ts';
import type { ReferenceGenomesSequenceNames } from '../../../types/referencesGenomes.ts';

type DownloadDialogProps = {
accessionFilter: AccessionFilter;
metadataFilter: FilterValue[];
mutationFilter: MutationFilter;
lapisSearchParameters: Record<string, any>;
referenceGenomesSequenceNames: ReferenceGenomesSequenceNames;
lapisUrl: string;
hiddenFieldValues: FieldValues;
};

export const DownloadDialog: FC<DownloadDialogProps> = ({
accessionFilter,
metadataFilter,
mutationFilter,
lapisSearchParameters,
referenceGenomesSequenceNames,
lapisUrl,
hiddenFieldValues,
}) => {
const dialogRef = useRef<HTMLDialogElement>(null);
const [downloadOption, setDownloadOption] = useState<DownloadOption | undefined>();
Expand Down Expand Up @@ -53,7 +51,10 @@ export const DownloadDialog: FC<DownloadDialogProps> = ({

<h3 className='font-bold text-2xl mb-4'>Download</h3>

<ActiveDownloadFilters metadataFilter={metadataFilter} mutationFilter={mutationFilter} />
<ActiveDownloadFilters
lapisSearchParameters={lapisSearchParameters}
hiddenFieldValues={hiddenFieldValues}
/>
<DownloadForm
referenceGenomesSequenceNames={referenceGenomesSequenceNames}
onChange={setDownloadOption}
Expand Down Expand Up @@ -82,9 +83,7 @@ export const DownloadDialog: FC<DownloadDialogProps> = ({
disabled={!agreedToDataUseTerms}
lapisUrl={lapisUrl}
downloadOption={downloadOption}
accessionFilter={accessionFilter}
metadataFilter={metadataFilter}
mutationFilter={mutationFilter}
lapisSearchParameters={lapisSearchParameters}
onClick={closeDialog}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { IS_REVOCATION_FIELD, metadataDefaultDownloadDataFormat, VERSION_STATUS_FIELD } from '../../../settings.ts';
import type { AccessionFilter, FilterValue, MutationFilter } from '../../../types/config.ts';
import { siloVersionStatuses } from '../../../types/lapis.ts';

export type DownloadDataType =
Expand All @@ -18,14 +17,13 @@ export type DownloadOption = {
};

export const generateDownloadUrl = (
accessionFilter: AccessionFilter,
metadataFilter: FilterValue[],
mutationFilter: MutationFilter,
lapisSearchParameters: Record<string, any>,
option: DownloadOption,
lapisUrl: string,
) => {
const baseUrl = `${lapisUrl}${getEndpoint(option.dataType)}`;
const params = new URLSearchParams();

params.set('downloadAsFile', 'true');
if (!option.includeOldData) {
params.set(VERSION_STATUS_FIELD, siloVersionStatuses.latestVersion);
Expand All @@ -40,31 +38,31 @@ export const generateDownloadUrl = (
if (option.compression !== undefined) {
params.set('compression', option.compression);
}
if (accessionFilter.accession !== undefined) {
for (const accession of accessionFilter.accession) {
if (lapisSearchParameters.accession !== undefined) {
for (const accession of lapisSearchParameters.accession) {
params.append('accession', accession);
}
}
for (const { name, filterValue } of metadataFilter) {
if (filterValue.trim().length > 0) {
params.set(name, filterValue);

const mutationKeys = ['nucleotideMutations', 'aminoAcidMutations', 'nucleotideInsertions', 'aminoAcidInsertions'];

for (const [key, value] of Object.entries(lapisSearchParameters)) {
// Skip accession and mutations
if (key === 'accession' || mutationKeys.includes(key)) {
continue;
}
const trimmedValue = value.trim();
if (trimmedValue.length > 0) {
params.set(key, trimmedValue);
}
}
if (mutationFilter.nucleotideMutationQueries !== undefined && mutationFilter.nucleotideMutationQueries.length > 0) {
params.set('nucleotideMutations', mutationFilter.nucleotideMutationQueries.join(','));
}
if (mutationFilter.aminoAcidMutationQueries !== undefined && mutationFilter.aminoAcidMutationQueries.length > 0) {
params.set('aminoAcidMutations', mutationFilter.aminoAcidMutationQueries.join(','));
}
if (
mutationFilter.nucleotideInsertionQueries !== undefined &&
mutationFilter.nucleotideInsertionQueries.length > 0
) {
params.set('nucleotideInsertions', mutationFilter.nucleotideInsertionQueries.join(','));
}
if (mutationFilter.aminoAcidInsertionQueries !== undefined && mutationFilter.aminoAcidInsertionQueries.length > 0) {
params.set('aminoAcidInsertions', mutationFilter.aminoAcidInsertionQueries.join(','));
}

mutationKeys.forEach((key) => {
if (lapisSearchParameters[key] !== undefined) {
params.set(key, lapisSearchParameters[key].join(','));
}
});

return {
url: `${baseUrl}?${params}`,
baseUrl,
Expand Down
Loading
Loading