From 9106d31955a9d3f96527c8bf36429ff6d87e1004 Mon Sep 17 00:00:00 2001 From: "Anna (Anya) Parker" <50943381+anna-parker@users.noreply.github.com> Date: Wed, 8 May 2024 18:54:58 +0200 Subject: [PATCH] feat(website): Split mutations by gene (for aa) and segment (for nucs), 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. --- deploy.py | 2 +- .../DataTableEntryValue.tsx | 4 +- .../SequenceDetailsPage/MutationBadge.tsx | 49 +++++++----- .../SequenceDetailsPage/getTableData.spec.ts | 74 +++++++++++-------- .../SequenceDetailsPage/getTableData.ts | 28 +++++-- website/src/types/config.ts | 8 +- 6 files changed, 107 insertions(+), 58 deletions(-) diff --git a/deploy.py b/deploy.py index 409b16eed..92e312f26 100755 --- a/deploy.py +++ b/deploy.py @@ -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]}"] diff --git a/website/src/components/SequenceDetailsPage/DataTableEntryValue.tsx b/website/src/components/SequenceDetailsPage/DataTableEntryValue.tsx index 4445080ac..a7bbf7905 100644 --- a/website/src/components/SequenceDetailsPage/DataTableEntryValue.tsx +++ b/website/src/components/SequenceDetailsPage/DataTableEntryValue.tsx @@ -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'; @@ -21,7 +21,7 @@ const CustomDisplayComponent: React.FC = ({ data, dataUseTermsHistory }) (customDisplay.value === undefined ? ( N/A ) : ( - + ))} {customDisplay?.type === 'link' && customDisplay.url !== undefined && ( = ({ position, mutationTo, mutationFrom, sequenceName }) => { return (
  • @@ -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 }) => ( +
    +

    {segment}

    + +
    + )); +}; + +export const SubstitutionsContainer: FC = ({ values }) => { const [showMore, setShowMore] = useState(false); const { alwaysVisible, initiallyHidden } = useMemo(() => { @@ -97,23 +111,24 @@ export const SubstitutionsContainer = ({ values }: { values: MutationProportionC return (
    {alwaysVisible} - {initiallyHidden.length > 0 && showMore ? ( - <> - {initiallyHidden} - + + ) : ( + - - ) : ( - - )} + ))}
    ); }; diff --git a/website/src/components/SequenceDetailsPage/getTableData.spec.ts b/website/src/components/SequenceDetailsPage/getTableData.spec.ts index 134efb25a..3279fc704 100644 --- a/website/src/components/SequenceDetailsPage/getTableData.spec.ts +++ b/website/src/components/SequenceDetailsPage/getTableData.spec.ts @@ -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, + }, + ], }, ], }, @@ -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', + }, + ], }, ], }, diff --git a/website/src/components/SequenceDetailsPage/getTableData.ts b/website/src/components/SequenceDetailsPage/getTableData.ts index d5b10ffe8..4c2d75439 100644 --- a/website/src/components/SequenceDetailsPage/getTableData.ts +++ b/website/src/components/SequenceDetailsPage/getTableData.ts @@ -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, @@ -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' }, }, { @@ -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' }, }, { @@ -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(); + 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[]) { diff --git a/website/src/types/config.ts b/website/src/types/config.ts index f4fef439d..32f73a4ad 100644 --- a/website/src/types/config.ts +++ b/website/src/types/config.ts @@ -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({ @@ -43,6 +48,7 @@ export type InputField = z.infer; export type CustomDisplay = z.infer; export type Metadata = z.infer; export type MetadataType = z.infer; +export type SegmentedMutations = z.infer; export type MetadataFilter = Metadata & { filterValue: string;