Skip to content

Commit

Permalink
feat(website): Split mutations by gene (for aa) and segment (for nucs…
Browse files Browse the repository at this point in the history
…), fix "show more" bug (#1811)

* Create a `segmentedMutations` object which maps a segment to a list of all mutations on that segment and replace `substitutionsList()` with `substitutionsMap()` to return it.

* Remove the `show-more` button when there are under MAX_INITIAL_NUMBER_BADGES number of mutations.

* Add `sr-only` to the sequenceName portion of the `mutationbadge` object - this allows the mutations to be searchable with an appended gene name (and will be visible when the amino acid mutations are copied) but it will not be shown on the page.
  • Loading branch information
anna-parker authored May 8, 2024
1 parent 3ccf09a commit 9106d31
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 58 deletions.
2 changes: 1 addition & 1 deletion deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def handle_helm():
]

if args.for_e2e or args.dev:
parameters += ['-f', 'kubernetes/loculus/values_e2e_and_dev.yaml']
parameters += ['-f', HELM_CHART_DIR / 'values_e2e_and_dev.yaml']
if args.sha:
parameters += ['--set', f"sha={args.sha[:7]}"]

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';

import { DataUseTermsHistoryModal } from './DataUseTermsHistoryModal';
import { SubstitutionsContainer } from './MutationBadge';
import { SubstitutionsContainers } from './MutationBadge';
import { type TableDataEntry } from './types.ts';
import { type DataUseTermsHistoryEntry } from '../../types/backend.ts';

Expand All @@ -21,7 +21,7 @@ const CustomDisplayComponent: React.FC<Props> = ({ data, dataUseTermsHistory })
(customDisplay.value === undefined ? (
<span className='italic'>N/A</span>
) : (
<SubstitutionsContainer values={customDisplay.value} />
<SubstitutionsContainers values={customDisplay.value} />
))}
{customDisplay?.type === 'link' && customDisplay.url !== undefined && (
<a
Expand Down
49 changes: 32 additions & 17 deletions website/src/components/SequenceDetailsPage/MutationBadge.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { type FC, type ReactElement, useMemo, useState } from 'react';

import type { SegmentedMutations } from '../../types/config';
import type { MutationProportionCount } from '../../types/lapis';

export type SubProps = {
Expand All @@ -9,6 +10,10 @@ export type SubProps = {
sequenceName: string | null;
};

export type Props = {
values: MutationProportionCount[];
};

export const SubBadge: FC<SubProps> = ({ position, mutationTo, mutationFrom, sequenceName }) => {
return (
<li key={position} className='inline-block'>
Expand Down Expand Up @@ -69,7 +74,16 @@ export function getColor(code: string): string {

const MAX_INITIAL_NUMBER_BADGES = 20;

export const SubstitutionsContainer = ({ values }: { values: MutationProportionCount[] }) => {
export const SubstitutionsContainers = ({ values }: { values: SegmentedMutations[] }) => {
return values.map(({ segment, mutations }) => (
<div key={segment}>
<h2 className='py-1 my-1 font-semibold border-b'>{segment}</h2>
<SubstitutionsContainer values={mutations} />
</div>
));
};

export const SubstitutionsContainer: FC<Props> = ({ values }) => {
const [showMore, setShowMore] = useState(false);

const { alwaysVisible, initiallyHidden } = useMemo(() => {
Expand Down Expand Up @@ -97,23 +111,24 @@ export const SubstitutionsContainer = ({ values }: { values: MutationProportionC
return (
<div>
{alwaysVisible}
{initiallyHidden.length > 0 && showMore ? (
<>
{initiallyHidden}
<button onClick={() => setShowMore(false)} className='underline'>
Show less
{initiallyHidden.length > 0 &&
(showMore ? (
<>
{initiallyHidden}
<button onClick={() => setShowMore(false)} className='underline'>
Show less
</button>
</>
) : (
<button
onClick={() => {
setShowMore(true);
}}
className='underline'
>
Show more
</button>
</>
) : (
<button
onClick={() => {
setShowMore(true);
}}
className='underline'
>
Show more
</button>
)}
))}
</div>
);
};
74 changes: 42 additions & 32 deletions website/src/components/SequenceDetailsPage/getTableData.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,22 +150,27 @@ describe('getTableData', () => {
type: 'badge',
value: [
{
count: 0,
mutation: 'T10A',
mutationFrom: 'T',
mutationTo: 'A',
position: 10,
proportion: 0,
sequenceName: null,
},
{
count: 0,
mutation: 'C30G',
mutationFrom: 'C',
mutationTo: 'G',
position: 30,
proportion: 0,
sequenceName: null,
segment: '',
mutations: [
{
count: 0,
mutation: 'T10A',
mutationFrom: 'T',
mutationTo: 'A',
position: 10,
proportion: 0,
sequenceName: null,
},
{
count: 0,
mutation: 'C30G',
mutationFrom: 'C',
mutationTo: 'G',
position: 30,
proportion: 0,
sequenceName: null,
},
],
},
],
},
Expand All @@ -187,22 +192,27 @@ describe('getTableData', () => {
type: 'badge',
value: [
{
count: 0,
mutation: 'gene1:N10Y',
mutationFrom: 'N',
mutationTo: 'Y',
position: 10,
proportion: 0,
sequenceName: 'gene1',
},
{
count: 0,
mutation: 'gene1:T30N',
mutationFrom: 'T',
mutationTo: 'N',
position: 30,
proportion: 0,
sequenceName: 'gene1',
segment: 'gene1',
mutations: [
{
count: 0,
mutation: 'gene1:N10Y',
mutationFrom: 'N',
mutationTo: 'Y',
position: 10,
proportion: 0,
sequenceName: 'gene1',
},
{
count: 0,
mutation: 'gene1:T30N',
mutationFrom: 'T',
mutationTo: 'N',
position: 30,
proportion: 0,
sequenceName: 'gene1',
},
],
},
],
},
Expand Down
28 changes: 23 additions & 5 deletions website/src/components/SequenceDetailsPage/getTableData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { err, Result } from 'neverthrow';
import type { TableDataEntry } from './types.js';
import { type LapisClient } from '../../services/lapisClient.ts';
import type { ProblemDetail } from '../../types/backend.ts';
import type { Metadata, Schema } from '../../types/config.ts';
import type { Metadata, Schema, SegmentedMutations } from '../../types/config.ts';
import {
type Details,
type DetailsResponse,
Expand Down Expand Up @@ -93,7 +93,7 @@ function mutationDetails(
name: 'nucleotideSubstitutions',
value: '',
header: 'Nucleotide mutations',
customDisplay: { type: 'badge', value: substitutionsList(nucleotideMutations) },
customDisplay: { type: 'badge', value: substitutionsMap(nucleotideMutations) },
type: { kind: 'mutation' },
},
{
Expand All @@ -115,7 +115,7 @@ function mutationDetails(
name: 'aminoAcidSubstitutions',
value: '',
header: 'Amino acid mutations',
customDisplay: { type: 'badge', value: substitutionsList(aminoAcidMutations) },
customDisplay: { type: 'badge', value: substitutionsMap(aminoAcidMutations) },
type: { kind: 'mutation' },
},
{
Expand Down Expand Up @@ -184,8 +184,26 @@ function mapValueToDisplayedValue(value: undefined | null | string | number | bo
return value;
}

function substitutionsList(mutationData: MutationProportionCount[]) {
return mutationData.filter((m) => m.mutationTo !== '-');
export function substitutionsMap(mutationData: MutationProportionCount[]): SegmentedMutations[] {
const result: SegmentedMutations[] = [];
const substitutionData = mutationData.filter((m) => m.mutationTo !== '-');

const segmentMutationsMap = new Map<string, MutationProportionCount[]>();
for (const entry of substitutionData) {
let sequenceName = '';
if (entry.sequenceName !== null) {
sequenceName = entry.sequenceName;
}
if (!segmentMutationsMap.has(sequenceName)) {
segmentMutationsMap.set(sequenceName, []);
}
segmentMutationsMap.get(sequenceName)!.push(entry);
}
for (const [segment, mutations] of segmentMutationsMap.entries()) {
result.push({ segment, mutations });
}

return result;
}

function deletionsToCommaSeparatedString(mutationData: MutationProportionCount[]) {
Expand Down
8 changes: 7 additions & 1 deletion website/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@ const metadataPossibleTypes = z.enum([
'authors',
] as const);

export const segmentedMutations = z.object({
segment: z.string(),
mutations: z.array(mutationProportionCount),
});

export const customDisplay = z.object({
type: z.string(),
url: z.string().optional(),
value: z.array(mutationProportionCount).optional(),
value: z.array(segmentedMutations).optional(),
});

export const metadata = z.object({
Expand All @@ -43,6 +48,7 @@ export type InputField = z.infer<typeof inputField>;
export type CustomDisplay = z.infer<typeof customDisplay>;
export type Metadata = z.infer<typeof metadata>;
export type MetadataType = z.infer<typeof metadataPossibleTypes>;
export type SegmentedMutations = z.infer<typeof segmentedMutations>;

export type MetadataFilter = Metadata & {
filterValue: string;
Expand Down

0 comments on commit 9106d31

Please sign in to comment.