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): Add a Mutations Search Info popup #2232

Merged
merged 8 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
143 changes: 143 additions & 0 deletions website/src/components/SearchPage/DisplaySearchDocs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { Dialog, Transition, DialogPanel, DialogTitle } from '@headlessui/react';
import React, { Fragment } from 'react';

import X from '~icons/material-symbols/close';
import MaterialSymbolsHelpOutline from '~icons/material-symbols/help-outline';

const DisplaySearchDocs: React.FC = () => {
const [isOpen, setIsOpen] = React.useState(false);

const openDialog = () => setIsOpen(true);
const closeDialog = () => setIsOpen(false);

return (
<>
<button onClick={openDialog} className='text-gray-400 hover:text-primary-600 '>
<MaterialSymbolsHelpOutline className='inline-block h-6 w-5' />
</button>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as='div' className='relative z-10' onClose={closeDialog}>
<Transition.Child
as={Fragment}
enter='ease-out duration-300'
enterFrom='opacity-0'
enterTo='opacity-100'
leave='ease-in duration-200'
leaveFrom='opacity-100'
leaveTo='opacity-0'
>
<div className='fixed inset-0 bg-black bg-opacity-25' />
</Transition.Child>

<div className='fixed inset-0 overflow-y-auto'>
<div className='flex min-h-full items-center justify-center p-4 text-center'>
<Transition.Child
as={Fragment}
enter='ease-out duration-300'
enterFrom='opacity-0 scale-95'
enterTo='opacity-100 scale-100'
leave='ease-in duration-200'
leaveFrom='opacity-100 scale-100'
leaveTo='opacity-0 scale-95'
>
<DialogPanel className='w-full max-w-5xl transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all'>
<DialogTitle as='h3' className='font-bold text-2xl mb-4 text-primary-700'>
Mutation Search
</DialogTitle>
<button className='absolute right-2 top-2 p-1' onClick={closeDialog}>
<X className='h-6 w-6' />
</button>
<div className='mb-4'>
<h4 className='font-bold text-l mb-4 text-primary-700'>
Nucleotide Mutations and Insertions
</h4>
<p className='mb-2'>
For a single-segmented organism, nucleotide mutations have the format{' '}
<b>&lt;position&gt;&lt;base&gt;</b> or{' '}
<b>&lt;base_ref&gt;&lt;position&gt;&lt;base&gt;</b>. A <b>&lt;base&gt;</b>{' '}
can be one of the four nucleotides <b>A</b>, <b>T</b>, <b>C</b>, and{' '}
<b>G</b>. It can also be <b>-</b> for deletion and <b>N</b> for unknown. For
example if the reference sequence is <b>A</b> at position <b>23</b> both:{' '}
<b>23T</b> and <b>A23T</b> will yield the same results.
</p>
<p className='mb-2'>
If your organism is multi-segmented you must append the name of the segment
to the start of the mutation, e.g. <b>S:23T</b> and <b>S:A23T</b> for a
mutation in segment <b>S</b>.
</p>
<p className='mb-2'>
Insertions can be searched for in the same manner, they just need to have{' '}
<b>ins_</b> appended to the start of the mutation. Example{' '}
<b>ins_10462:A</b> or if the organism is multi-segmented{' '}
<b>ins_S:10462:A</b>.
</p>
</div>

<div className='mb-4'>
<h4 className='font-bold text-l mb-4 text-primary-700'>
Amino Acid Mutations and Insertions
</h4>
<p className='mb-2'>
An amino acid mutation has the format{' '}
<b>&lt;gene&gt;:&lt;position&gt;&lt;base&gt;</b> or{' '}
<b>&lt;gene&gt;:&lt;base_ref&gt;&lt;position&gt;&lt;base&gt;</b>. A{' '}
<b>&lt;base&gt;</b> can be one of the 20 amino acid codes. It can also be{' '}
<b>-</b> for deletion and <b>X</b> for unknown. Example: <b>E:57Q</b>.
</p>
<p className='mb-2'>
Insertions can be searched for in the same manner, they just need to have{' '}
<b>ins_ </b>
appended to the start of the mutation. Example <b>ins_NS4B:31:N</b>.
</p>
</div>

<div className='mb-4'>
<h4 className='font-bold text-l mb-4 text-primary-700'>Insertion Wildcards</h4>
<p className='mb-2'>
Loculus supports insertion queries that contain wildcards <b>?</b>. For
example <b>ins_S:214:?EP?</b> will match all cases where segment <b>S</b>{' '}
has an insertion of <b>EP</b> between the positions 214 and 215 but also an
insertion of other AAs which include the <b>EP</b>, e.g. the insertion{' '}
<b>EPE</b> will be matched.
</p>
<p className='mb-2'>
You can also use wildcards to match any insertion at a given position. For
example <b>ins_S:214:?:</b> will match any (but at least one) insertion
between the positions 214 and 215.
</p>
</div>

<div className='mb-4'>
<h4 className='font-bold text-l mb-4 text-primary-700'>Multiple Mutations</h4>
<p className='mb-2'>
Multiple mutation filters can be provided by adding one mutation after the
other.
</p>
</div>

<div className='mb-4'>
<h4 className='font-bold text-l mb-4 text-primary-700'>Any Mutation</h4>
<p className='mb-2'>
To filter for any mutation at a given position you can omit the{' '}
<b>&lt;base&gt;</b>.
</p>
</div>
<div className='mb-4'>
<h4 className='font-bold text-l mb-4 text-primary-700'>No Mutation</h4>
<p className='mb-2'>
You can write a <b>.</b> for the <b>&lt;base&gt;</b> to filter for sequences
for which it is confirmed that no mutation occurred, i.e. has the same base
as the reference genome at the specified position.
</p>
</div>
</DialogPanel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</>
);
};

export default DisplaySearchDocs;
19 changes: 13 additions & 6 deletions website/src/components/SearchPage/SearchForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import type { GroupedMetadataFilter, MetadataFilter, FieldValues, SetAFieldValue
import { type ReferenceGenomesSequenceNames } from '../../types/referencesGenomes.ts';
import type { ClientConfig } from '../../types/runtimeConfig.ts';
import { OffCanvasOverlay } from '../OffCanvasOverlay.tsx';
import MaterialSymbolsHelpOutline from '~icons/material-symbols/help-outline';
import MaterialSymbolsResetFocus from '~icons/material-symbols/reset-focus';
import StreamlineWrench from '~icons/streamline/wrench';

const queryClient = new QueryClient();

interface SearchFormProps {
Expand Down Expand Up @@ -61,19 +65,22 @@ export const SearchForm = ({
<div className='shadow-xl rounded-r-lg px-4 pt-4'>
<h2 className='text-lg font-semibold flex-1 md:hidden mb-2'>Search query</h2>
<div className='flex'>
<div className='flex items-center justify-between w-full mb-2 text-primary-700'>
<div className='flex items-center justify-between w-full mb-2 text-primary-700'>
<button className='underline' onClick={toggleCustomizeModal}>
Customize fields
<div className='flex items-center justify-between w-full mb-1 text-primary-700'>
<div className='flex items-center justify-between w-full mb-1 text-primary-700 text-sm'>
<button className='hover:underline' onClick={toggleCustomizeModal}>
<StreamlineWrench className='inline-block' /> Select fields
</button>
<button
className='underline'
className='hover:underline'
onClick={() => {
window.location.href = './';
}}
>
Reset
<MaterialSymbolsResetFocus className='inline-block' /> Reset
</button>
<a href='/docs/how-to/search_sequences_website' target='_blank'>
<MaterialSymbolsHelpOutline className='inline-block' /> Help
</a>
</div>
</div>{' '}
</div>
Expand Down
2 changes: 1 addition & 1 deletion website/src/components/SearchPage/SearchFullUI.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ describe('SearchFullUI', () => {
it('toggle field visibility', async () => {
renderSearchFullUI({});
expect(await screen.findByLabelText('Field 1')).toBeVisible();
const customizeButton = await screen.findByRole('button', { name: 'Customize fields' });
const customizeButton = await screen.findByRole('button', { name: 'Select fields' });
await userEvent.click(customizeButton);
const field1Checkbox = await screen.findByRole('checkbox', { name: 'Field 1' });
expect(field1Checkbox).toBeChecked();
Expand Down
32 changes: 20 additions & 12 deletions website/src/components/SearchPage/fields/MutationField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as React from 'react';

import type { ReferenceGenomesSequenceNames } from '../../../types/referencesGenomes.ts';
import type { BaseType } from '../../../utils/sequenceTypeHelpers.ts';
import DisplaySearchDocs from '../DisplaySearchDocs';

interface MutationFieldProps {
referenceGenomesSequenceNames: ReferenceGenomesSequenceNames;
Expand Down Expand Up @@ -188,9 +189,9 @@ export const MutationField: FC<MutationFieldProps> = ({ referenceGenomesSequence
};

return (
<div className='relative mt-1 mb-2'>
<div className='flex relative mt-1 mb-2 flex-row w-full'>
<Combobox value={selectedOptions} onChange={handleOptionClick}>
<div className='relative mt-1'>
<div className='w-full relative mt-1'>
<div
className={`w-full flex flex-wrap items-center border border-gray-300 bg-white rounded-md shadow-sm text-left cursor-default focus-within:ring-1 focus-within:ring-blue-500 focus-within:border-blue-500 sm:text-sm
${selectedOptions.length === 0 ? '' : 'pt-2 pl-2'}
Expand Down Expand Up @@ -226,19 +227,26 @@ export const MutationField: FC<MutationFieldProps> = ({ referenceGenomesSequence
>
Mutations
</label>
<ComboboxInput
onFocus={() => setHasFocus(true)}
onBlur={() => setHasFocus(false)}
placeholder={hasFocus ? '' : selectedOptions.length === 0 ? 'Mutations' : 'Enter mutation'}
onChange={handleInputChange}
displayValue={(option: MutationQuery) => option.text}
value={inputValue}
id='mutField'
className={`
<div className='justify-between w-full'>
<ComboboxInput
onFocus={() => setHasFocus(true)}
onBlur={() => setHasFocus(false)}
placeholder={
hasFocus ? '' : selectedOptions.length === 0 ? 'Mutations' : 'Enter mutation'
}
onChange={handleInputChange}
displayValue={(option: MutationQuery) => option.text}
value={inputValue}
id='mutField'
className={`
block w-full text-sm text-gray-900 bg-transparent focus:outline-none focus:ring-0
${selectedOptions.length === 0 ? 'border-0 focus:border-0 py-3' : 'border border-gray-300 border-solid m-2 text-sm ml-0'}
`}
/>
/>
<div className='absolute bottom-3 right-1'>
<DisplaySearchDocs />
</div>
</div>
</div>
<Transition
as={Fragment}
Expand Down
3 changes: 3 additions & 0 deletions website/src/pages/docs/how-to/search_sequences_website.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# How to Search

Fill this in with tips for your loculus instance.
Loading