-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(website): add download dialog to search page (#847)
- Loading branch information
1 parent
c68096f
commit 09744a6
Showing
8 changed files
with
564 additions
and
2 deletions.
There are no files selected for viewing
39 changes: 39 additions & 0 deletions
39
website/src/components/SearchPage/DownloadDialog/ActiveDownloadFilters.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import type { FC } from 'react'; | ||
|
||
import type { FilterValue, MutationFilter } from '../../../types/config.ts'; | ||
|
||
type ActiveDownloadFiltersProps = { | ||
metadataFilter: FilterValue[]; | ||
mutationFilter: MutationFilter; | ||
}; | ||
|
||
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) { | ||
filterValues.push({ name, filterValue: value.join(', ') }); | ||
} | ||
}); | ||
|
||
if (filterValues.length === 0) { | ||
return undefined; | ||
} | ||
|
||
return ( | ||
<div className='mb-4'> | ||
<h4 className='font-bold mb-2'>Active filters:</h4> | ||
<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} | ||
</div> | ||
))} | ||
</div> | ||
</div> | ||
); | ||
}; |
99 changes: 99 additions & 0 deletions
99
website/src/components/SearchPage/DownloadDialog/DownloadDialog.spec.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { render, screen } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
import { beforeAll, describe, expect, test, vi } from 'vitest'; | ||
|
||
import { DownloadDialog } from './DownloadDialog.tsx'; | ||
import type { FilterValue, MutationFilter } from '../../../types/config.ts'; | ||
import type { ReferenceGenomesSequenceNames } from '../../../types/referencesGenomes.ts'; | ||
|
||
const defaultReferenceGenome: ReferenceGenomesSequenceNames = { | ||
nucleotideSequences: ['main'], | ||
genes: ['gene1', 'gene2'], | ||
}; | ||
|
||
const defaultLapisUrl = 'https://lapis'; | ||
|
||
async function renderDialog( | ||
metadataFilter: FilterValue[] = [], | ||
mutationFilter: MutationFilter = {}, | ||
referenceGenomesSequenceNames: ReferenceGenomesSequenceNames = defaultReferenceGenome, | ||
lapisUrl: string = defaultLapisUrl, | ||
) { | ||
render( | ||
<DownloadDialog | ||
metadataFilter={metadataFilter} | ||
mutationFilter={mutationFilter} | ||
referenceGenomesSequenceNames={referenceGenomesSequenceNames} | ||
lapisUrl={lapisUrl} | ||
/>, | ||
); | ||
|
||
// Open the panel | ||
const button = screen.getByRole('button', { name: 'Download' }); | ||
await userEvent.click(button); | ||
} | ||
|
||
describe('DownloadDialog', () => { | ||
beforeAll(() => { | ||
// Vitest does not seem to support showModal, yet. | ||
// Workaround from https://github.com/jsdom/jsdom/issues/3294#issuecomment-1268330372 | ||
HTMLDialogElement.prototype.showModal = vi.fn(function mock(this: HTMLDialogElement) { | ||
this.open = true; | ||
}); | ||
|
||
HTMLDialogElement.prototype.close = vi.fn(function mock(this: HTMLDialogElement) { | ||
this.open = false; | ||
}); | ||
}); | ||
|
||
test('should display active filters if there are some', async () => { | ||
await renderDialog([{ name: 'field1', filterValue: 'value1' }], { | ||
nucleotideMutationQueries: ['A123T', 'G234C'], | ||
}); | ||
expect(screen.queryByText(/Active filters/)).toBeInTheDocument(); | ||
expect(screen.queryByText('field1: value1')).toBeInTheDocument(); | ||
expect(screen.queryByText(/A123T, G234C/)).toBeInTheDocument(); | ||
}); | ||
|
||
test('should not display active filters if there are none', async () => { | ||
await renderDialog(); | ||
expect(screen.queryByText(/Active filters/)).not.toBeInTheDocument(); | ||
expect(screen.queryByText('field1: value1')).not.toBeInTheDocument(); | ||
expect(screen.queryByText(/A123T, G234C/)).not.toBeInTheDocument(); | ||
}); | ||
|
||
test('should activate download button only after agreeing to the terms', async () => { | ||
await renderDialog(); | ||
|
||
const downloadButton = screen.getByRole('link', { name: 'Download' }); | ||
expect(downloadButton).toHaveClass('btn-disabled'); | ||
expect(getDownloadHref()).not.toMatch(new RegExp(`^${defaultLapisUrl}`)); | ||
|
||
await checkAgreement(); | ||
expect(downloadButton).not.toHaveClass('btn-disabled'); | ||
expect(getDownloadHref()).toMatch(new RegExp(`^${defaultLapisUrl}`)); | ||
}); | ||
|
||
test('should generate the right download link', async () => { | ||
await renderDialog([{ name: 'field1', filterValue: 'value1' }]); | ||
await checkAgreement(); | ||
|
||
expect(getDownloadHref()).toBe( | ||
`${defaultLapisUrl}/sample/details?versionStatus=LATEST_VERSION&isRevocation=false&dataFormat=tsv&field1=value1`, | ||
); | ||
|
||
await userEvent.click(screen.getByLabelText(/Yes, include older versions/)); | ||
await userEvent.click(screen.getByLabelText(/Raw nucleotide sequences/)); | ||
expect(getDownloadHref()).toBe(`${defaultLapisUrl}/sample/unalignedNucleotideSequences?field1=value1`); | ||
}); | ||
}); | ||
|
||
async function checkAgreement() { | ||
const agreementCheckbox = screen.getByLabelText('I agree to the data use terms.'); | ||
await userEvent.click(agreementCheckbox); | ||
} | ||
|
||
function getDownloadHref() { | ||
const downloadButton = screen.getByRole('link', { name: 'Download' }); | ||
return downloadButton.getAttribute('href'); | ||
} |
96 changes: 96 additions & 0 deletions
96
website/src/components/SearchPage/DownloadDialog/DownloadDialog.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import { type FC, useMemo, useRef, useState } from 'react'; | ||
|
||
import { ActiveDownloadFilters } from './ActiveDownloadFilters.tsx'; | ||
import { DownloadForm } from './DownloadForm.tsx'; | ||
import { type DownloadOption, generateDownloadUrl } from './generateDownloadUrl.ts'; | ||
import type { FilterValue, MutationFilter } from '../../../types/config.ts'; | ||
import type { ReferenceGenomesSequenceNames } from '../../../types/referencesGenomes.ts'; | ||
|
||
type DownloadDialogProps = { | ||
metadataFilter: FilterValue[]; | ||
mutationFilter: MutationFilter; | ||
referenceGenomesSequenceNames: ReferenceGenomesSequenceNames; | ||
lapisUrl: string; | ||
}; | ||
|
||
export const DownloadDialog: FC<DownloadDialogProps> = ({ | ||
metadataFilter, | ||
mutationFilter, | ||
referenceGenomesSequenceNames, | ||
lapisUrl, | ||
}) => { | ||
const dialogRef = useRef<HTMLDialogElement>(null); | ||
const [downloadOption, setDownloadOption] = useState<DownloadOption | undefined>(); | ||
const [agreedToDataUseTerms, setAgreedToDataUseTerms] = useState(false); | ||
|
||
const openDialog = () => { | ||
if (dialogRef.current) { | ||
dialogRef.current.showModal(); | ||
} | ||
}; | ||
|
||
const closeDialog = () => { | ||
if (dialogRef.current) { | ||
dialogRef.current.close(); | ||
} | ||
}; | ||
|
||
const downloadUrl = useMemo(() => { | ||
if (downloadOption === undefined || !agreedToDataUseTerms) { | ||
return '#'; | ||
} | ||
return generateDownloadUrl(metadataFilter, mutationFilter, downloadOption, lapisUrl); | ||
}, [downloadOption, lapisUrl, metadataFilter, mutationFilter, agreedToDataUseTerms]); | ||
|
||
return ( | ||
<> | ||
<button className='btn' onClick={openDialog}> | ||
Download | ||
</button> | ||
|
||
<dialog ref={dialogRef} className='modal'> | ||
<div className='modal-box max-w-5xl'> | ||
<form method='dialog'> | ||
<button className='btn btn-sm btn-circle btn-ghost absolute right-2 top-2'>✕</button> | ||
</form> | ||
|
||
<h3 className='font-bold text-2xl mb-4'>Download</h3> | ||
|
||
<ActiveDownloadFilters metadataFilter={metadataFilter} mutationFilter={mutationFilter} /> | ||
<DownloadForm | ||
referenceGenomesSequenceNames={referenceGenomesSequenceNames} | ||
onChange={setDownloadOption} | ||
/> | ||
|
||
<div className='mb-4'> | ||
<label className='label justify-start'> | ||
<input | ||
type='checkbox' | ||
name='data-use-terms-agreement' | ||
className='checkbox mr-2' | ||
checked={agreedToDataUseTerms} | ||
onChange={() => setAgreedToDataUseTerms(!agreedToDataUseTerms)} | ||
/> | ||
<span className='label-text'> | ||
I agree to the {/* TODO(862) */} | ||
<a href='#' className='underline'> | ||
data use terms | ||
</a> | ||
. | ||
</span> | ||
</label> | ||
</div> | ||
|
||
<a | ||
className={`btn loculusGreen ${!agreedToDataUseTerms ? 'btn-disabled' : ''}`} | ||
href={downloadUrl} | ||
target='_blank' | ||
onClick={closeDialog} | ||
> | ||
Download | ||
</a> | ||
</div> | ||
</dialog> | ||
</> | ||
); | ||
}; |
Oops, something went wrong.