Skip to content

Commit

Permalink
feat(website): One tab for all protein sequences (#2851)
Browse files Browse the repository at this point in the history
  • Loading branch information
theosanderson authored Sep 20, 2024
1 parent d38457b commit bf0e31e
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ describe('SequencesContainer', () => {

click('Load sequences');

click('Aligned');
click('Aligned nucleotide sequence');
await waitFor(() => {
expect(
screen.getByText(singleSegmentSequence, {
Expand All @@ -72,7 +72,7 @@ describe('SequencesContainer', () => {
).toBeVisible();
});

click('Sequence');
click('Nucleotide sequence');
await waitFor(() => {
expect(
screen.getByText(unalignedSingleSegmentSequence, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const SequencesViewer: FC<Props> = ({
const header = '>' + data.name + (sequenceType.name === 'main' ? '' : `_${sequenceType.name}`);

return (
<div className='max-h-80 overflow-auto'>
<div className='h-80 overflow-auto'>
<FixedLengthTextViewer text={data.sequence} maxLineLength={LINE_LENGTH} header={header} />
</div>
);
Expand Down
191 changes: 139 additions & 52 deletions website/src/components/SequenceDetailsPage/SequencesContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,75 +49,118 @@ export const InnerSequencesContainer: FC<SequenceContainerProps> = ({
}

return (
<>
<SequenceTabs
nucleotideSegmentNames={nucleotideSegmentNames}
sequenceType={sequenceType}
setType={setSequenceType}
genes={genes}
/>
<BoxWithTabsBox>
<SequencesViewer
organism={organism}
accessionVersion={accessionVersion}
clientConfig={clientConfig}
sequenceType={sequenceType}
isMultiSegmented={isMultiSegmented(nucleotideSegmentNames)}
/>
</BoxWithTabsBox>
</>
<SequenceTabs
organism={organism}
accessionVersion={accessionVersion}
clientConfig={clientConfig}
nucleotideSegmentNames={nucleotideSegmentNames}
sequenceType={sequenceType}
setType={setSequenceType}
genes={genes}
/>
);
};

export const SequencesContainer = withQueryProvider(InnerSequencesContainer);

type NucleotideSequenceTabsProps = {
type SequenceTabsProps = {
organism: string;
accessionVersion: string;
clientConfig: ClientConfig;
nucleotideSegmentNames: NucleotideSegmentNames;
sequenceType: SequenceType;
setType: Dispatch<SetStateAction<SequenceType>>;
genes: string[];
};

const SequenceTabs: FC<NucleotideSequenceTabsProps & { genes: string[] }> = ({
const SequenceTabs: FC<SequenceTabsProps> = ({
organism,
accessionVersion,
clientConfig,
nucleotideSegmentNames,
genes,
sequenceType,
setType,
}) => (
<BoxWithTabsTabBar>
<UnalignedNucleotideSequenceTabs
nucleotideSegmentNames={nucleotideSegmentNames}
sequenceType={sequenceType}
setType={setType}
/>
<AlignmentSequenceTabs
nucleotideSegmentNames={nucleotideSegmentNames}
sequenceType={sequenceType}
setType={setType}
/>
{genes.map((gene) => (
<BoxWithTabsTab
isActive={isGeneSequence(gene, sequenceType)}
onClick={() => setType(geneSequence(gene))}
label={gene}
key={gene}
/>
))}
</BoxWithTabsTabBar>
);
}) => {
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 (
<>
<BoxWithTabsTabBar>
<UnalignedNucleotideSequenceTabs
nucleotideSegmentNames={nucleotideSegmentNames}
sequenceType={sequenceType}
setType={setType}
isActive={activeTab === 'unaligned'}
setActiveTab={setActiveTab}
/>
<AlignmentSequenceTabs
nucleotideSegmentNames={nucleotideSegmentNames}
sequenceType={sequenceType}
setType={setType}
isActive={activeTab === 'aligned'}
setActiveTab={setActiveTab}
/>
<BoxWithTabsTab
isActive={activeTab === 'gene'}
label='Aligned amino acid sequences'
onClick={() => setActiveTab('gene')}
/>
</BoxWithTabsTabBar>
<BoxWithTabsBox>
{activeTab === 'gene' && <GeneDropdown genes={genes} sequenceType={sequenceType} setType={setType} />}
{activeTab !== 'gene' || isGeneSequence(sequenceType.name, sequenceType) ? (
<SequencesViewer
organism={organism}
accessionVersion={accessionVersion}
clientConfig={clientConfig}
sequenceType={sequenceType}
isMultiSegmented={isMultiSegmented(nucleotideSegmentNames)}
/>
) : (
<div className='h-80'></div>
)}
</BoxWithTabsBox>
</>
);
};

type NucleotideSequenceTabsProps = {
nucleotideSegmentNames: NucleotideSegmentNames;
sequenceType: SequenceType;
setType: Dispatch<SetStateAction<SequenceType>>;
isActive: boolean;
setActiveTab: (tab: 'unaligned' | 'aligned' | 'gene') => void;
};

const UnalignedNucleotideSequenceTabs: FC<NucleotideSequenceTabsProps> = ({
nucleotideSegmentNames,
sequenceType,
setType,
isActive,
setActiveTab,
}) => {
if (!isMultiSegmented(nucleotideSegmentNames)) {
const onlySegment = nucleotideSegmentNames[0];
return (
<BoxWithTabsTab
key={onlySegment}
isActive={isUnalignedSequence(sequenceType)}
onClick={() => setType(unalignedSequenceSegment(onlySegment))}
label='Sequence'
isActive={isActive}
onClick={() => {
setType(unalignedSequenceSegment(onlySegment));
setActiveTab('unaligned');
}}
label='Nucleotide sequence'
/>
);
}
Expand All @@ -127,24 +170,36 @@ const UnalignedNucleotideSequenceTabs: FC<NucleotideSequenceTabsProps> = ({
{nucleotideSegmentNames.map((segmentName) => (
<BoxWithTabsTab
key={segmentName}
isActive={isUnalignedSequence(sequenceType) && segmentName === sequenceType.name}
onClick={() => setType(unalignedSequenceSegment(segmentName))}
isActive={isActive && isUnalignedSequence(sequenceType) && segmentName === sequenceType.name}
onClick={() => {
setType(unalignedSequenceSegment(segmentName));
setActiveTab('unaligned');
}}
label={`${segmentName} (unaligned)`}
/>
))}
</>
);
};

const AlignmentSequenceTabs: FC<NucleotideSequenceTabsProps> = ({ nucleotideSegmentNames, sequenceType, setType }) => {
const AlignmentSequenceTabs: FC<NucleotideSequenceTabsProps> = ({
nucleotideSegmentNames,
sequenceType,
setType,
isActive,
setActiveTab,
}) => {
if (!isMultiSegmented(nucleotideSegmentNames)) {
const onlySegment = nucleotideSegmentNames[0];
return (
<BoxWithTabsTab
key={onlySegment}
isActive={isAlignedSequence(sequenceType)}
onClick={() => setType(alignedSequenceSegment(onlySegment))}
label='Aligned'
isActive={isActive}
onClick={() => {
setType(alignedSequenceSegment(onlySegment));
setActiveTab('aligned');
}}
label='Aligned nucleotide sequence'
/>
);
}
Expand All @@ -154,15 +209,47 @@ const AlignmentSequenceTabs: FC<NucleotideSequenceTabsProps> = ({ nucleotideSegm
{nucleotideSegmentNames.map((segmentName) => (
<BoxWithTabsTab
key={segmentName}
isActive={isAlignedSequence(sequenceType) && segmentName === sequenceType.name}
onClick={() => setType(alignedSequenceSegment(segmentName))}
isActive={isActive && isAlignedSequence(sequenceType) && segmentName === sequenceType.name}
onClick={() => {
setType(alignedSequenceSegment(segmentName));
setActiveTab('aligned');
}}
label={`${segmentName} (aligned)`}
/>
))}
</>
);
};

type GeneDropdownProps = {
genes: string[];
sequenceType: SequenceType;
setType: Dispatch<SetStateAction<SequenceType>>;
};

const GeneDropdown: FC<GeneDropdownProps> = ({ genes, sequenceType, setType }) => {
const selectedGene = isGeneSequence(sequenceType.name, sequenceType) ? sequenceType.name : '';

return (
<div className='mb-4'>
<select
className='select select-bordered w-full max-w-xs'
value={selectedGene}
onChange={(e) => setType(geneSequence(e.target.value))}
>
<option value='' disabled>
Select a gene
</option>
{genes.map((gene) => (
<option key={gene} value={gene}>
{gene}
</option>
))}
</select>
</div>
);
};

function isMultiSegmented(nucleotideSegmentNames: string[]) {
return nucleotideSegmentNames.length > 1;
}
2 changes: 1 addition & 1 deletion website/tests/pages/sequences/accession.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Expand Down
19 changes: 14 additions & 5 deletions website/tests/pages/sequences/sequences.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
});
Expand All @@ -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');
}
}

0 comments on commit bf0e31e

Please sign in to comment.