Skip to content

Commit

Permalink
feat(website): Add link to reference sequence on sequence details page (
Browse files Browse the repository at this point in the history
#2238)

* Add insdc_accession_full to reference genomes in kubernetes configs.

* Put reference sequence info into a pop up button next to header.

* Do not show reference sequence link if there are no reference sequences.

* Add which segment each reference corresponds to.
  • Loading branch information
anna-parker authored Jul 8, 2024
1 parent a704b84 commit 344a2f5
Show file tree
Hide file tree
Showing 13 changed files with 225 additions and 13 deletions.
21 changes: 20 additions & 1 deletion kubernetes/loculus/templates/_common-metadata.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,26 @@ organisms:
{{ $metadata.fields | default list | toYaml | nindent 8 }}
{{- end }}
referenceGenomes:
{{ $instance.referenceGenomes | toYaml | nindent 6 }}
{{ $referenceGenomes:= include "loculus.generateReferenceGenome" $instance.referenceGenomes | fromYaml }}
{{ $referenceGenomes | toYaml |nindent 8}}
{{- end }}
{{- end }}

{{- define "loculus.generateReferenceGenome" }}
nucleotideSequences:
{{ $nucleotideSequences := include "loculus.generateSequences" .nucleotideSequences | fromYaml }}
{{ $nucleotideSequences.fields | toYaml | nindent 8 }}
genes:
{{ $genes := include "loculus.generateSequences" .genes | fromYaml }}
{{ $genes.fields | toYaml | nindent 8 }}
{{- end }}

{{- define "loculus.generateSequences" }}
{{- $sequences := . }}
fields:
{{- range $sequence := $sequences }}
- name: {{ printf "%s" $sequence.name | quote}}
sequence: {{ printf "%s" $sequence.sequence | quote }}
{{- end }}
{{- end }}

Expand Down
4 changes: 3 additions & 1 deletion kubernetes/loculus/templates/lapis-silo-database-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
{{- $importScriptWrapperLines := .Files.Lines "silo_import_wrapper.sh" }}

{{- range $key, $instance := (.Values.organisms | default .Values.defaultOrganisms) }}

{{ $referenceGenomes:= include "loculus.generateReferenceGenome" $instance.referenceGenomes | fromYaml }}
---
apiVersion: v1
kind: ConfigMap
Expand All @@ -21,7 +23,7 @@ data:
referenceGenomeFilename: reference_genomes.json
reference_genomes.json: |
{{ $instance.referenceGenomes | toJson }}
{{ $referenceGenomes | toJson }}
silo_import_job.sh: |
{{ range $importScriptLines }}
Expand Down
7 changes: 7 additions & 0 deletions kubernetes/loculus/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,7 @@ defaultOrganismConfig: &defaultOrganismConfig
nucleotideSequences:
- name: "main"
sequence: "[[URL:https://raw.githubusercontent.com/corneliusroemer/seqs/main/artefacts/ebola-zaire/reference.fasta]]"
insdc_accession_full: NC_002549.1
genes:
- name: NP
sequence: "[[URL:https://raw.githubusercontent.com/corneliusroemer/seqs/main/artefacts/ebola-zaire/NP.fasta]]"
Expand Down Expand Up @@ -816,6 +817,7 @@ defaultOrganisms:
nucleotideSequences:
- name: "main"
sequence: "[[URL:https://raw.githubusercontent.com/corneliusroemer/seqs/main/artefacts/ebola-sudan/reference.fasta]]"
insdc_accession_full: NC_006432.1
genes:
- name: NP
sequence: "[[URL:https://raw.githubusercontent.com/corneliusroemer/seqs/main/artefacts/ebola-sudan/NP.fasta]]"
Expand Down Expand Up @@ -907,6 +909,7 @@ defaultOrganisms:
nucleotideSequences:
- name: "main"
sequence: "[[URL:https://raw.githubusercontent.com/corneliusroemer/seqs/main/artefacts/mpox/reference.fasta]]"
insdc_accession_full: NC_063383.1
genes:
- name: OPG001
sequence: "[[URL:https://raw.githubusercontent.com/corneliusroemer/seqs/main/artefacts/mpox/OPG001.fasta]]"
Expand Down Expand Up @@ -964,6 +967,7 @@ defaultOrganisms:
nucleotideSequences:
- name: main
sequence: "[[URL:https://raw.githubusercontent.com/corneliusroemer/seqs/main/artefacts/west-nile/reference.fasta]]"
insdc_accession_full: NC_009942.1
genes:
- name: 2K
sequence: "[[URL:https://raw.githubusercontent.com/corneliusroemer/seqs/main/artefacts/west-nile/2K.fasta]]"
Expand Down Expand Up @@ -1118,10 +1122,13 @@ defaultOrganisms:
nucleotideSequences:
- name: L
sequence: "[[URL:https://raw.githubusercontent.com/corneliusroemer/seqs/main/artefacts/cchf/reference_L.fasta]]"
insdc_accession_full: NC_005301.3
- name: M
sequence: "[[URL:https://raw.githubusercontent.com/corneliusroemer/seqs/main/artefacts/cchf/reference_M.fasta]]"
insdc_accession_full: NC_005300.2
- name: S
sequence: "[[URL:https://raw.githubusercontent.com/corneliusroemer/seqs/main/artefacts/cchf/reference_S.fasta]]"
insdc_accession_full: NC_005302.1
genes:
- name: RdRp
sequence: "[[URL:https://raw.githubusercontent.com/corneliusroemer/seqs/main/artefacts/cchf/RdRp.fasta]]"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@ import userEvent from '@testing-library/user-event';
import { beforeAll, describe, expect, test, vi } from 'vitest';

import { DownloadDialog } from './DownloadDialog.tsx';
import type { ReferenceGenomesSequenceNames } from '../../../types/referencesGenomes.ts';
import type { ReferenceGenomesSequenceNames, ReferenceAccession } from '../../../types/referencesGenomes.ts';

const defaultAccession: ReferenceAccession = {
name: 'main',
insdc_accession_full: undefined,
};

const defaultReferenceGenome: ReferenceGenomesSequenceNames = {
nucleotideSequences: ['main'],
genes: ['gene1', 'gene2'],
insdc_accession_full: [defaultAccession],
};

const defaultLapisUrl = 'https://lapis';
Expand Down
8 changes: 7 additions & 1 deletion website/src/components/SearchPage/SearchForm.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { describe, expect, it, vi } from 'vitest';
import { SearchForm } from './SearchForm';
import { testConfig, testOrganism } from '../../../vitest.setup.ts';
import type { MetadataFilter } from '../../types/config.ts';
import type { ReferenceGenomesSequenceNames } from '../../types/referencesGenomes.ts';
import type { ReferenceGenomesSequenceNames, ReferenceAccession } from '../../types/referencesGenomes.ts';

global.ResizeObserver = class FakeResizeObserver {
observe() {}
Expand All @@ -35,9 +35,15 @@ const defaultSearchFormFilters: MetadataFilter[] = [
},
];

const defaultAccession: ReferenceAccession = {
name: 'main',
insdc_accession_full: undefined,
};

const defaultReferenceGenomesSequenceNames: ReferenceGenomesSequenceNames = {
nucleotideSequences: ['main'],
genes: ['gene1', 'gene2'],
insdc_accession_full: [defaultAccession],
};

const searchVisibilities = new Map<string, boolean>([
Expand Down
8 changes: 7 additions & 1 deletion website/src/components/SearchPage/SearchFullUI.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { SearchFullUI } from './SearchFullUI';
import { testConfig, testOrganism } from '../../../vitest.setup.ts';
import { lapisClientHooks } from '../../services/serviceHooks.ts';
import type { MetadataFilter, Schema } from '../../types/config.ts';
import type { ReferenceGenomesSequenceNames } from '../../types/referencesGenomes.ts';
import type { ReferenceGenomesSequenceNames, ReferenceAccession } from '../../types/referencesGenomes.ts';

global.ResizeObserver = class FakeResizeObserver {
observe() {}
Expand Down Expand Up @@ -66,9 +66,15 @@ const defaultSearchFormFilters: MetadataFilter[] = [
},
];

const defaultAccession: ReferenceAccession = {
name: 'main',
insdc_accession_full: undefined,
};

const defaultReferenceGenomesSequenceNames: ReferenceGenomesSequenceNames = {
nucleotideSequences: ['main'],
genes: ['gene1', 'gene2'],
insdc_accession_full: [defaultAccession],
};

function renderSearchFullUI({
Expand Down
19 changes: 18 additions & 1 deletion website/src/components/SearchPage/fields/MutationField.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,33 @@ import userEvent from '@testing-library/user-event';
import { describe, expect, test, vi } from 'vitest';

import { MutationField } from './MutationField.tsx';
import type { ReferenceGenomesSequenceNames } from '../../../types/referencesGenomes.ts';
import type { ReferenceGenomesSequenceNames, ReferenceAccession } from '../../../types/referencesGenomes.ts';

const singleAccession: ReferenceAccession = {
name: 'main',
insdc_accession_full: 'accession_main',
};

const singleSegmentedReferenceGenome: ReferenceGenomesSequenceNames = {
nucleotideSequences: ['main'],
genes: ['gene1', 'gene2'],
insdc_accession_full: [singleAccession],
};

const multiAccession1: ReferenceAccession = {
name: 'seg1',
insdc_accession_full: 'accession_seg1',
};

const multiAccession2: ReferenceAccession = {
name: 'seg2',
insdc_accession_full: 'accession_seg2',
};

const multiSegmentedReferenceGenome: ReferenceGenomesSequenceNames = {
nucleotideSequences: ['seg1', 'seg2'],
genes: ['gene1', 'gene2'],
insdc_accession_full: [multiAccession1, multiAccession2],
};

function renderField(
Expand Down
16 changes: 13 additions & 3 deletions website/src/components/SequenceDetailsPage/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@ import React from 'react';

import { AuthorList } from './AuthorList';
import DataTableEntry from './DataTableEntry';
import ReferenceSequenceLinkButton from './ReferenceSequenceLinkButton';
import { type DataTableData } from './getDataTableData';
import { type TableDataEntry } from './types';
import { type DataUseTermsHistoryEntry } from '../../types/backend';

import { type ReferenceAccession } from '../../types/referencesGenomes';
interface Props {
dataTableData: DataTableData;
dataUseTermsHistory: DataUseTermsHistoryEntry[];
reference: ReferenceAccession[];
}

const DataTableComponent: React.FC<Props> = ({ dataTableData, dataUseTermsHistory }) => {
const DataTableComponent: React.FC<Props> = ({ dataTableData, dataUseTermsHistory, reference }) => {
const hasReferenceAccession = reference.filter((item) => item.insdc_accession_full !== undefined).length > 0;

return (
<div>
{dataTableData.topmatter.authors !== undefined && dataTableData.topmatter.authors.length > 0 && (
Expand All @@ -25,7 +29,13 @@ const DataTableComponent: React.FC<Props> = ({ dataTableData, dataUseTermsHistor
>
{dataTableData.table.map(({ header, rows }) => (
<div key={header} className='p-4'>
<h1 className='py-2 text-lg font-semibold border-b'>{header}</h1>
<div className='flex flex-row'>
<h1 className='py-2 text-lg font-semibold border-b mr-2'>{header}</h1>
{hasReferenceAccession &&
(header.indexOf('mutation') >= 0 || header.indexOf('Alignment') >= 0) && (
<ReferenceSequenceLinkButton reference={reference} />
)}
</div>
<div className='mt-4'>
{rows.map((entry: TableDataEntry, index: number) => (
<DataTableEntry key={index} data={entry} dataUseTermsHistory={dataUseTermsHistory} />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { Dialog, Transition, DialogPanel, DialogTitle } from '@headlessui/react';
import React, { Fragment } from 'react';

import { type ReferenceAccession } from '../../types/referencesGenomes';
import X from '~icons/material-symbols/close';
import MaterialSymbolsInfoOutline from '~icons/material-symbols/info-outline';

export const ReferenceLink = ({ accession }: { accession: string }) => {
return (
<a
href={'https://www.ncbi.nlm.nih.gov/nuccore/__value__'.replace('__value__', accession.toString())}
target='_blank'
className='underline hover:text-primary-500'
>
{accession}
</a>
);
};

interface Props {
reference: ReferenceAccession[];
}

const ReferenceSequenceLinkButton: React.FC<Props> = ({ reference }) => {
const [isOpen, setIsOpen] = React.useState(false);

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

const isMultiSegmented = reference.length > 1;

return (
<>
<button onClick={openDialog} className='text-gray-400 hover:text-primary-600 '>
<MaterialSymbolsInfoOutline 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-2xl 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'>
Reference Sequence
</DialogTitle>
<button className='absolute right-2 top-2 p-1' onClick={closeDialog}>
<X className='h-6 w-6' />
</button>
<div className='mt-4'>
{reference.filter((item) => item.insdc_accession_full !== undefined).length >
0 && (
<div>
<div>
Alignment and Mutation metrics use the following INSDC reference
sequence
{reference.length > 1 ? 's: ' : ': '}
</div>
<span>
{reference.map(
(currElement) =>
currElement.insdc_accession_full !== undefined && (
<div className='text-primary-700 ml-5 flex'>
{isMultiSegmented && (
<div className='w-6 text-left mr-2'>
{currElement.name}:
</div>
)}
<ReferenceLink
accession={currElement.insdc_accession_full}
/>
</div>
),
)}
</span>
</div>
)}
</div>
</DialogPanel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</>
);
};

export default ReferenceSequenceLinkButton;
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
DATA_USE_TERMS_FIELD,
} from '../../settings';
import { type DataUseTermsHistoryEntry } from '../../types/backend';
import { getReferenceGenomesSequenceNames } from '../../utils/search';
import { cleanOrganism } from '../Navigation/cleanOrganism';
interface Props {
tableData: TableDataEntry[];
Expand All @@ -40,6 +42,17 @@ const relevantFieldsForRevocationVersions = [
const relevantData = tableData.filter((entry) => relevantFieldsForRevocationVersions.includes(entry.name));
const dataTableData = getDataTableData(relevantData);
const { organism: cleanedOrganism } = cleanOrganism(Astro.params.organism);
if (!cleanedOrganism) {
return {
statusCode: 404,
body: 'Organism not found',
};
}
const referenceGenomeSequenceNames = getReferenceGenomesSequenceNames(cleanedOrganism.key);
const reference = referenceGenomeSequenceNames.insdc_accession_full;
---

<DataTable dataTableData={dataTableData} dataUseTermsHistory={dataUseTermsHistory} />
<DataTable dataTableData={dataTableData} dataUseTermsHistory={dataUseTermsHistory} reference={reference} />
4 changes: 2 additions & 2 deletions website/src/components/SequenceDetailsPage/SequenceDataUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const SequenceDataUI: React.FC<Props> = ({

const genes = referenceGenomeSequenceNames.genes;
const nucleotideSegmentNames = referenceGenomeSequenceNames.nucleotideSequences;
const reference = referenceGenomeSequenceNames.insdc_accession_full;

const loadSequencesAutomatically = schema.loadSequencesAutomatically === true;

Expand All @@ -69,7 +70,7 @@ export const SequenceDataUI: React.FC<Props> = ({
</a>
</ErrorBox>
)}
<DataTable dataTableData={dataTableData} dataUseTermsHistory={dataUseTermsHistory} />
<DataTable dataTableData={dataTableData} dataUseTermsHistory={dataUseTermsHistory} reference={reference} />
<div className='mt-10'>
<SequencesContainer
organism={organism}
Expand All @@ -80,7 +81,6 @@ export const SequenceDataUI: React.FC<Props> = ({
loadSequencesAutomatically={loadSequencesAutomatically}
/>
</div>

{isMyGroup && accessToken !== undefined && (
<div className='mt-5'>
<hr />
Expand Down
Loading

0 comments on commit 344a2f5

Please sign in to comment.