Skip to content

Commit

Permalink
feat(website): Add a Mutations Search Info popup (#2232)
Browse files Browse the repository at this point in the history
* Add link to general search docs

* Add mutation search specific info in a pop up next to the mutation search field.
---------

Co-authored-by: Theo Sanderson <[email protected]>
Co-authored-by: Loculus bot <[email protected]>
  • Loading branch information
3 people authored Jul 4, 2024
1 parent 98a471c commit d8fc8d0
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 19 deletions.
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.

0 comments on commit d8fc8d0

Please sign in to comment.