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

Some suggestions for the Search help feature #2234

Merged
merged 6 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
222 changes: 127 additions & 95 deletions website/src/components/SearchPage/DisplaySearchDocs.tsx
Original file line number Diff line number Diff line change
@@ -1,110 +1,142 @@
import { type FC, useRef } from 'react';
import { Dialog, Transition, DialogPanel, DialogTitle } from '@headlessui/react';
import React, { Fragment } from 'react';

const DisplaySearchDocs: FC = () => {
const dialogRef = useRef<HTMLDialogElement>(null);
import X from '~icons/material-symbols/close';
import MaterialSymbolsHelpOutline from '~icons/material-symbols/help-outline';

const openDialog = () => {
if (dialogRef.current) {
dialogRef.current.showModal();
}
};
const DisplaySearchDocs: React.FC = () => {
const [isOpen, setIsOpen] = React.useState(false);

const closeDialog = () => {
if (dialogRef.current) {
dialogRef.current.close();
}
};
const openDialog = () => setIsOpen(true);
const closeDialog = () => setIsOpen(false);

return (
<>
<button className='outlineButton' onClick={openDialog}>
?
<button onClick={openDialog} className='text-gray-400 hover:text-primary-600 '>
<MaterialSymbolsHelpOutline className='inline-block h-6 w-5' />
</button>
<dialog ref={dialogRef} className='modal'>
<button
className='btn btn-sm btn-circle btn-ghost text-gray-900 absolute right-2 top-2'
onClick={closeDialog}
>
</button>
<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 text-primary-700'>Mutation Search</h3>
<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>
<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='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='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>
<h3 className='font-bold text-2xl mb-4 text-primary-700'>Mutation Search</h3>
<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'>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'>
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'>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'>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'>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 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>
</div>
</dialog>
</Dialog>
</Transition>
</>
);
};
Expand Down
22 changes: 13 additions & 9 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 @@ -60,23 +64,23 @@ 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>
<a href='/docs/how-to/search_sequences_website' className='text-primary-700 underline mb-2'>
How to Search
</a>
<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
Loading