From bf0e31ec9b45236c601bc2921bceb053e76aa1ca Mon Sep 17 00:00:00 2001 From: Theo Sanderson Date: Fri, 20 Sep 2024 15:06:30 +0100 Subject: [PATCH] feat(website): One tab for all protein sequences (#2851) --- .../SequenceContainer.spec.tsx | 4 +- .../SequenceDetailsPage/SequenceViewer.tsx | 2 +- .../SequencesContainer.tsx | 191 +++++++++++++----- .../tests/pages/sequences/accession.spec.ts | 2 +- .../tests/pages/sequences/sequences.page.ts | 19 +- 5 files changed, 157 insertions(+), 61 deletions(-) diff --git a/website/src/components/SequenceDetailsPage/SequenceContainer.spec.tsx b/website/src/components/SequenceDetailsPage/SequenceContainer.spec.tsx index 0d975b336..676891c17 100644 --- a/website/src/components/SequenceDetailsPage/SequenceContainer.spec.tsx +++ b/website/src/components/SequenceDetailsPage/SequenceContainer.spec.tsx @@ -63,7 +63,7 @@ describe('SequencesContainer', () => { click('Load sequences'); - click('Aligned'); + click('Aligned nucleotide sequence'); await waitFor(() => { expect( screen.getByText(singleSegmentSequence, { @@ -72,7 +72,7 @@ describe('SequencesContainer', () => { ).toBeVisible(); }); - click('Sequence'); + click('Nucleotide sequence'); await waitFor(() => { expect( screen.getByText(unalignedSingleSegmentSequence, { diff --git a/website/src/components/SequenceDetailsPage/SequenceViewer.tsx b/website/src/components/SequenceDetailsPage/SequenceViewer.tsx index fb5a15b96..8445ae704 100644 --- a/website/src/components/SequenceDetailsPage/SequenceViewer.tsx +++ b/website/src/components/SequenceDetailsPage/SequenceViewer.tsx @@ -47,7 +47,7 @@ export const SequencesViewer: FC = ({ const header = '>' + data.name + (sequenceType.name === 'main' ? '' : `_${sequenceType.name}`); return ( -
+
); diff --git a/website/src/components/SequenceDetailsPage/SequencesContainer.tsx b/website/src/components/SequenceDetailsPage/SequencesContainer.tsx index 979c371b4..34e8901d2 100644 --- a/website/src/components/SequenceDetailsPage/SequencesContainer.tsx +++ b/website/src/components/SequenceDetailsPage/SequencesContainer.tsx @@ -49,75 +49,118 @@ export const InnerSequencesContainer: FC = ({ } return ( - <> - - - - - + ); }; export const SequencesContainer = withQueryProvider(InnerSequencesContainer); -type NucleotideSequenceTabsProps = { +type SequenceTabsProps = { + organism: string; + accessionVersion: string; + clientConfig: ClientConfig; nucleotideSegmentNames: NucleotideSegmentNames; sequenceType: SequenceType; setType: Dispatch>; + genes: string[]; }; -const SequenceTabs: FC = ({ +const SequenceTabs: FC = ({ + organism, + accessionVersion, + clientConfig, nucleotideSegmentNames, genes, sequenceType, setType, -}) => ( - - - - {genes.map((gene) => ( - setType(geneSequence(gene))} - label={gene} - key={gene} - /> - ))} - -); +}) => { + const [activeTab, setActiveTab] = useState<'unaligned' | 'aligned' | 'gene'>('unaligned'); + + useEffect(() => { + if (isUnalignedSequence(sequenceType)) { + setActiveTab('unaligned'); + } else if (isAlignedSequence(sequenceType)) { + setActiveTab('aligned'); + } else if (isGeneSequence(sequenceType.name, sequenceType)) { + setActiveTab('gene'); + } + }, [sequenceType]); + + return ( + <> + + + + setActiveTab('gene')} + /> + + + {activeTab === 'gene' && } + {activeTab !== 'gene' || isGeneSequence(sequenceType.name, sequenceType) ? ( + + ) : ( +
+ )} +
+ + ); +}; + +type NucleotideSequenceTabsProps = { + nucleotideSegmentNames: NucleotideSegmentNames; + sequenceType: SequenceType; + setType: Dispatch>; + isActive: boolean; + setActiveTab: (tab: 'unaligned' | 'aligned' | 'gene') => void; +}; const UnalignedNucleotideSequenceTabs: FC = ({ nucleotideSegmentNames, sequenceType, setType, + isActive, + setActiveTab, }) => { if (!isMultiSegmented(nucleotideSegmentNames)) { const onlySegment = nucleotideSegmentNames[0]; return ( setType(unalignedSequenceSegment(onlySegment))} - label='Sequence' + isActive={isActive} + onClick={() => { + setType(unalignedSequenceSegment(onlySegment)); + setActiveTab('unaligned'); + }} + label='Nucleotide sequence' /> ); } @@ -127,8 +170,11 @@ const UnalignedNucleotideSequenceTabs: FC = ({ {nucleotideSegmentNames.map((segmentName) => ( setType(unalignedSequenceSegment(segmentName))} + isActive={isActive && isUnalignedSequence(sequenceType) && segmentName === sequenceType.name} + onClick={() => { + setType(unalignedSequenceSegment(segmentName)); + setActiveTab('unaligned'); + }} label={`${segmentName} (unaligned)`} /> ))} @@ -136,15 +182,24 @@ const UnalignedNucleotideSequenceTabs: FC = ({ ); }; -const AlignmentSequenceTabs: FC = ({ nucleotideSegmentNames, sequenceType, setType }) => { +const AlignmentSequenceTabs: FC = ({ + nucleotideSegmentNames, + sequenceType, + setType, + isActive, + setActiveTab, +}) => { if (!isMultiSegmented(nucleotideSegmentNames)) { const onlySegment = nucleotideSegmentNames[0]; return ( setType(alignedSequenceSegment(onlySegment))} - label='Aligned' + isActive={isActive} + onClick={() => { + setType(alignedSequenceSegment(onlySegment)); + setActiveTab('aligned'); + }} + label='Aligned nucleotide sequence' /> ); } @@ -154,8 +209,11 @@ const AlignmentSequenceTabs: FC = ({ nucleotideSegm {nucleotideSegmentNames.map((segmentName) => ( setType(alignedSequenceSegment(segmentName))} + isActive={isActive && isAlignedSequence(sequenceType) && segmentName === sequenceType.name} + onClick={() => { + setType(alignedSequenceSegment(segmentName)); + setActiveTab('aligned'); + }} label={`${segmentName} (aligned)`} /> ))} @@ -163,6 +221,35 @@ const AlignmentSequenceTabs: FC = ({ nucleotideSegm ); }; +type GeneDropdownProps = { + genes: string[]; + sequenceType: SequenceType; + setType: Dispatch>; +}; + +const GeneDropdown: FC = ({ genes, sequenceType, setType }) => { + const selectedGene = isGeneSequence(sequenceType.name, sequenceType) ? sequenceType.name : ''; + + return ( +
+ +
+ ); +}; + function isMultiSegmented(nucleotideSegmentNames: string[]) { return nucleotideSegmentNames.length > 1; } diff --git a/website/tests/pages/sequences/accession.spec.ts b/website/tests/pages/sequences/accession.spec.ts index 4a55836d8..70f815aa6 100644 --- a/website/tests/pages/sequences/accession.spec.ts +++ b/website/tests/pages/sequences/accession.spec.ts @@ -11,7 +11,7 @@ test.describe('The detailed sequence page', () => { await expect(sequencePage.page.getByText(testSequenceEntryData.orf1a)).not.toBeVisible(); await sequencePage.loadSequences(); - await sequencePage.clickORF1aButton(); + await sequencePage.selectORF1a(); await expect(sequencePage.page.getByText(testSequenceEntryData.orf1a, { exact: false })).toBeVisible(); }); diff --git a/website/tests/pages/sequences/sequences.page.ts b/website/tests/pages/sequences/sequences.page.ts index f9d0a6daf..8975b2fc0 100644 --- a/website/tests/pages/sequences/sequences.page.ts +++ b/website/tests/pages/sequences/sequences.page.ts @@ -12,11 +12,13 @@ export class SequencePage { private readonly loadButton: Locator; private readonly allVersions: Locator; - private readonly orf1aButton: Locator; + private readonly specificProteinTab: Locator; + private readonly geneDropdown: Locator; constructor(public readonly page: Page) { this.loadButton = this.page.getByRole('button', { name: 'Load sequences' }); - this.orf1aButton = this.page.getByRole('button', { name: 'ORF1a' }); + this.specificProteinTab = this.page.getByRole('button', { name: 'Aligned amino acid sequences' }); + this.geneDropdown = this.page.locator('select'); this.allVersions = this.page.getByRole('link', { name: `All versions`, }); @@ -40,8 +42,15 @@ export class SequencePage { await this.loadButton.click(); } - public async clickORF1aButton() { - await expect(this.orf1aButton).toBeVisible(); - await this.orf1aButton.click(); + public async selectSpecificProtein(proteinName: string) { + await expect(this.specificProteinTab).toBeVisible(); + await this.specificProteinTab.click(); + + await expect(this.geneDropdown).toBeVisible(); + await this.geneDropdown.selectOption(proteinName); + } + + public async selectORF1a() { + await this.selectSpecificProtein('ORF1a'); } }