diff --git a/website/src/components/pageStateSelectors/wasap/WasapPageStateSelector.tsx b/website/src/components/pageStateSelectors/wasap/WasapPageStateSelector.tsx index 72518f4cc..5f5c483ff 100644 --- a/website/src/components/pageStateSelectors/wasap/WasapPageStateSelector.tsx +++ b/website/src/components/pageStateSelectors/wasap/WasapPageStateSelector.tsx @@ -32,12 +32,14 @@ import { */ export function WasapPageStateSelector({ config, + resistanceSetNames, pageStateHandler, initialBaseFilterState, initialAnalysisFilterState, setPageState, }: { config: WasapPageConfig; + resistanceSetNames: string[]; pageStateHandler: PageStateHandler; initialBaseFilterState: WasapBaseFilter; initialAnalysisFilterState: WasapAnalysisFilter; @@ -189,7 +191,7 @@ export function WasapPageStateSelector({ ); case 'untracked': diff --git a/website/src/components/pageStateSelectors/wasap/filters/ResistanceMutationsFilter.browser.spec.tsx b/website/src/components/pageStateSelectors/wasap/filters/ResistanceMutationsFilter.browser.spec.tsx index 0c56e860a..f389dda80 100644 --- a/website/src/components/pageStateSelectors/wasap/filters/ResistanceMutationsFilter.browser.spec.tsx +++ b/website/src/components/pageStateSelectors/wasap/filters/ResistanceMutationsFilter.browser.spec.tsx @@ -3,7 +3,6 @@ import { render } from 'vitest-browser-react'; import { ResistanceMutationsFilter } from './ResistanceMutationsFilter'; import { it } from '../../../../../test-extend'; -import { covidResistanceMutations } from '../../../views/wasap/resistanceMutations'; import type { WasapResistanceFilter } from '../../../views/wasap/wasapPageConfig'; describe('ResistanceMutationsFilter', () => { @@ -13,7 +12,7 @@ describe('ResistanceMutationsFilter', () => { resistanceSet: '3CLpro', }; - const resistanceMutationSets = covidResistanceMutations; + const resistanceSetNames = ['3CLpro', 'RdRp', 'Spike']; it('renders with initial resistance set', async () => { const mockSetPageState = vi.fn(); @@ -22,7 +21,7 @@ describe('ResistanceMutationsFilter', () => { , ); @@ -37,7 +36,7 @@ describe('ResistanceMutationsFilter', () => { , ); diff --git a/website/src/components/pageStateSelectors/wasap/filters/ResistanceMutationsFilter.tsx b/website/src/components/pageStateSelectors/wasap/filters/ResistanceMutationsFilter.tsx index 5b1f5f48b..6156a72fe 100644 --- a/website/src/components/pageStateSelectors/wasap/filters/ResistanceMutationsFilter.tsx +++ b/website/src/components/pageStateSelectors/wasap/filters/ResistanceMutationsFilter.tsx @@ -1,15 +1,14 @@ -import type { ResistanceMutationSet } from '../../../views/wasap/resistanceMutations'; import type { WasapResistanceFilter } from '../../../views/wasap/wasapPageConfig'; import { LabeledField } from '../utils/LabeledField'; export function ResistanceMutationsFilter({ pageState, setPageState, - resistanceMutationSets, + resistanceSetNames, }: { pageState: WasapResistanceFilter; setPageState: (newState: WasapResistanceFilter) => void; - resistanceMutationSets: ResistanceMutationSet[]; + resistanceSetNames: string[]; }) { return ( @@ -18,9 +17,9 @@ export function ResistanceMutationsFilter({ value={pageState.resistanceSet} onChange={(e) => setPageState({ ...pageState, resistanceSet: e.target.value })} > - {resistanceMutationSets.map((set) => ( - ))} diff --git a/website/src/components/views/wasap/Wasap.astro b/website/src/components/views/wasap/Wasap.astro index 4535ed13f..9d09ead09 100644 --- a/website/src/components/views/wasap/Wasap.astro +++ b/website/src/components/views/wasap/Wasap.astro @@ -1,16 +1,34 @@ --- import { WasapPage } from './WasapPage'; +import { fetchResistanceData, type ResistanceData } from './resistanceData'; +import { BackendService } from '../../../backendApi/backendService.ts'; +import { isStaging, getBackendHost } from '../../../config.ts'; import BaseLayout from '../../../layouts/base/BaseLayout.astro'; -import { wastewaterOrganismConfigs, type WastewaterOrganismName } from '../../../types/wastewaterConfig'; +import { getInstanceLogger } from '../../../logger.ts'; +import { + wastewaterOrganismConfigs, + wastewaterOrganismStagingConfigs, + type WastewaterOrganismName, +} from '../../../types/wastewaterConfig'; type Props = { wastewaterOrganism: WastewaterOrganismName; }; const { wastewaterOrganism } = Astro.props; -const { name } = wastewaterOrganismConfigs[wastewaterOrganism]; +const config = (isStaging() ? wastewaterOrganismStagingConfigs : wastewaterOrganismConfigs)[wastewaterOrganism]; +const backendService = new BackendService(getBackendHost()); +let resistanceData: ResistanceData = { mutationAnnotations: [], displayMutationsBySet: {} }; + +try { + resistanceData = await fetchResistanceData(config, backendService); +} catch (error) { + getInstanceLogger('WasapPage').error( + `Failed to fetch resistance data for WASAP page (organism: ${wastewaterOrganism}): ${error}`, + ); +} --- - - + + diff --git a/website/src/components/views/wasap/WasapPage.tsx b/website/src/components/views/wasap/WasapPage.tsx index 48373be5f..59de27440 100644 --- a/website/src/components/views/wasap/WasapPage.tsx +++ b/website/src/components/views/wasap/WasapPage.tsx @@ -6,17 +6,14 @@ import { CollectionInfo } from './components/CollectionInfo'; import { NoDataHelperText } from './components/NoDataHelperText'; import { VariantFetchInfo } from './components/VariantFetchInfo'; import { WasapStats } from './components/WasapStats'; -import { toMutationAnnotations } from './resistanceMutations'; +import type { ResistanceData } from './resistanceData'; import { useWasapPageData } from './useWasapPageData'; +import type { WasapPageConfig } from './wasapPageConfig'; import { withQueryProvider } from '../../../backendApi/withQueryProvider'; import { defaultBreadcrumbs } from '../../../layouts/Breadcrumbs.tsx'; import { DataPageLayout } from '../../../layouts/OrganismPage/DataPageLayout.tsx'; import { dataOrigins } from '../../../types/dataOrigins.ts'; -import { - wastewaterBreadcrumb, - wastewaterOrganismConfigs, - type WastewaterOrganismName, -} from '../../../types/wastewaterConfig'; +import { wastewaterBreadcrumb } from '../../../types/wastewaterConfig'; import { Loading } from '../../../util/Loading'; import { WasapPageStateHandler } from '../../../views/pageStateHandlers/WasapPageStateHandler'; import { GsMutationsOverTime } from '../../genspectrum/GsMutationsOverTime'; @@ -25,11 +22,11 @@ import { WasapPageStateSelector } from '../../pageStateSelectors/wasap/WasapPage import { usePageState } from '../usePageState.ts'; export type WasapPageProps = { - wastewaterOrganism: WastewaterOrganismName; + config: WasapPageConfig; + resistanceData: ResistanceData; }; -export const WasapPageInner: FC = ({ wastewaterOrganism }) => { - const config = wastewaterOrganismConfigs[wastewaterOrganism]; +export const WasapPageInner: FC = ({ config, resistanceData }) => { // initialize page state from the URL const pageStateHandler = useMemo(() => new WasapPageStateHandler(config), [config]); @@ -38,8 +35,9 @@ export const WasapPageInner: FC = ({ wastewaterOrganism }) => { setPageState, } = usePageState(pageStateHandler); + const { mutationAnnotations, displayMutationsBySet } = resistanceData; // fetch which mutations should be analyzed - const { data, isPending, isError } = useWasapPageData(config, analysis); + const { data, isPending, isError } = useWasapPageData(config, displayMutationsBySet, analysis); let initialMeanProportionInterval: MeanProportionInterval = { min: 0.0, max: 1.0 }; if (analysis.mode === 'manual' && analysis.mutations === undefined) { @@ -52,16 +50,6 @@ export const WasapPageInner: FC = ({ wastewaterOrganism }) => { ...(base.samplingDate?.dateTo && { samplingDateTo: base.samplingDate.dateTo }), }; - const memoizedMutationAnnotations = useMemo( - () => - config.resistanceAnalysisModeEnabled - ? config.resistanceMutationSets.flatMap((resistanceMutation) => - toMutationAnnotations(resistanceMutation), - ) - : [], - [config], - ); - return ( = ({ wastewaterOrganism }) => { >
@@ -88,6 +76,7 @@ export const WasapPageInner: FC = ({ wastewaterOrganism }) => { initialBaseFilterState={base} initialAnalysisFilterState={analysis} setPageState={setPageState} + resistanceSetNames={Object.keys(displayMutationsBySet)} />
{isError ? ( diff --git a/website/src/components/views/wasap/resistanceData.ts b/website/src/components/views/wasap/resistanceData.ts new file mode 100644 index 000000000..4a7fcc306 --- /dev/null +++ b/website/src/components/views/wasap/resistanceData.ts @@ -0,0 +1,58 @@ +import type { MutationAnnotations } from '@genspectrum/dashboard-components/util'; + +import type { ResistanceMutationCollectionConfig, WasapPageConfig } from './wasapPageConfig'; +import type { BackendService } from '../../../backendApi/backendService'; +import type { Collection } from '../../../types/Collection'; + +export type ResistanceData = { + /** Flat list of mutation annotations for the genome viewer. */ + mutationAnnotations: MutationAnnotations; + /** Map from set name (e.g. "3CLpro") to the genomic amino acid mutations to display. */ + displayMutationsBySet: Record; +}; + +export async function fetchResistanceData( + config: WasapPageConfig, + backendService: BackendService, +): Promise { + if (!config.resistanceAnalysisModeEnabled) { + return { mutationAnnotations: [], displayMutationsBySet: {} }; + } + const collections = await Promise.all( + config.resistanceMutationCollections.map((setConfig) => + backendService.getCollection({ id: String(setConfig.collectionId) }), + ), + ); + return buildResistanceData(config.resistanceMutationCollections, collections); +} + +function buildResistanceData( + setConfigs: ResistanceMutationCollectionConfig[], + collections: Collection[], +): ResistanceData { + const mutationAnnotations: MutationAnnotations = []; + const displayMutationsBySet: Record = {}; + + setConfigs.forEach((setConfig, i) => { + const entries = collections[i].variants.flatMap((variant) => { + if (variant.type !== 'filterObject') return []; + return (variant.filterObject.aminoAcidMutations ?? []).map((aminoAcidMutation) => ({ + displayName: variant.name, + aminoAcidMutation, + })); + }); + + displayMutationsBySet[setConfig.name] = entries.map((e) => e.aminoAcidMutation); + + mutationAnnotations.push( + ...entries.map(({ displayName, aminoAcidMutation }) => ({ + name: displayName, + symbol: setConfig.annotationSymbol, + description: setConfig.description, + aminoAcidMutations: [aminoAcidMutation], + })), + ); + }); + + return { mutationAnnotations, displayMutationsBySet }; +} diff --git a/website/src/components/views/wasap/resistanceMutations.ts b/website/src/components/views/wasap/resistanceMutations.ts deleted file mode 100644 index cb513ef18..000000000 --- a/website/src/components/views/wasap/resistanceMutations.ts +++ /dev/null @@ -1,319 +0,0 @@ -import { type MutationAnnotations } from '@genspectrum/dashboard-components/util'; - -export type ResistanceMutationSet = { - name: string; - annotationSymbol: string; - description: string; - mutations: string[]; - offset: number; -}; - -const fetchSnippet = - 'as per Stanford Coronavirus Antiviral & Resistance database (last updated on 21 August 2024).'; - -export const covidResistanceMutations: ResistanceMutationSet[] = [ - { - name: '3CLpro', - annotationSymbol: 'c', - description: `SARS-CoV-2 3C-like protease (3CLpro, or Mpro for Main protease) inhibitor resistance mutation ${fetchSnippet}`, - mutations: [ - 'ORF1a:T3284I', - 'ORF1a:T3288A', - 'ORF1a:T3288N', - 'ORF1a:T3308I', - 'ORF1a:D3311Y', - 'ORF1a:M3312I', - 'ORF1a:M3312L', - 'ORF1a:M3312T', - 'ORF1a:M3312-', - 'ORF1a:L3313F', - 'ORF1a:G3401S', - 'ORF1a:F3403L', - 'ORF1a:F3403S', - 'ORF1a:N3405D', - 'ORF1a:N3405L', - 'ORF1a:N3405S', - 'ORF1a:G3406S', - 'ORF1a:S3407A', - 'ORF1a:S3407E', - 'ORF1a:S3407L', - 'ORF1a:S3407P', - 'ORF1a:C3423F', - 'ORF1a:M3428R', - 'ORF1a:M3428T', - 'ORF1a:E3429A', - 'ORF1a:E3429G', - 'ORF1a:E3429K', - 'ORF1a:E3429Q', - 'ORF1a:E3429V', - 'ORF1a:L3430F', - 'ORF1a:P3431-', - 'ORF1a:T3432I', - 'ORF1a:H3435L', - 'ORF1a:H3435N', - 'ORF1a:H3435Q', - 'ORF1a:H3435Y', - 'ORF1a:A3436T', - 'ORF1a:A3436V', - 'ORF1a:V3449A', - 'ORF1a:R3451G', - 'ORF1a:R3451S', - 'ORF1a:Q3452I', - 'ORF1a:Q3452K', - 'ORF1a:T3453I', - 'ORF1a:A3454T', - 'ORF1a:A3454V', - 'ORF1a:Q3455A', - 'ORF1a:Q3455C', - 'ORF1a:Q3455D', - 'ORF1a:Q3455E', - 'ORF1a:Q3455F', - 'ORF1a:Q3455G', - 'ORF1a:Q3455H', - 'ORF1a:Q3455I', - 'ORF1a:Q3455K', - 'ORF1a:Q3455L', - 'ORF1a:Q3455N', - 'ORF1a:Q3455P', - 'ORF1a:Q3455R', - 'ORF1a:Q3455S', - 'ORF1a:Q3455T', - 'ORF1a:Q3455V', - 'ORF1a:Q3455W', - 'ORF1a:Q3455Y', - 'ORF1a:A3456P', - 'ORF1a:A3457S', - 'ORF1a:P3515L', - 'ORF1a:V3560A', - 'ORF1a:S3564P', - 'ORF1a:T3567I', - 'ORF1a:F3568L', - ], - offset: -3263, - }, - { - name: 'RdRp', - annotationSymbol: 'r', - description: `SARS-CoV-2 RNA-dependent RNA polymerase (RdRP) inhibitor resistance mutation ${fetchSnippet}`, - mutations: [ - 'ORF1b:V157A', - 'ORF1b:V157L', - 'ORF1b:N189S', - 'ORF1b:R276C', - 'ORF1b:A367V', - 'ORF1b:A440V', - 'ORF1b:F471L', - 'ORF1b:D475Y', - 'ORF1b:A517V', - 'ORF1b:V548L', - 'ORF1b:G662S', - 'ORF1b:S750A', - 'ORF1b:V783I', - 'ORF1b:E787G', - 'ORF1b:C790F', - 'ORF1b:C790R', - 'ORF1b:E793A', - 'ORF1b:E793D', - 'ORF1b:M915R', - ], - offset: 9, - }, - { - name: 'Spike', - annotationSymbol: 's', - description: `SARS-CoV-2 Spike monoclonal antibody (mAb) resistance mutation ${fetchSnippet}`, - mutations: [ - 'S:P337H', - 'S:P337L', - 'S:P337R', - 'S:P337S', - 'S:P337T', - 'S:E340A', - 'S:E340D', - 'S:E340G', - 'S:E340K', - 'S:E340Q', - 'S:E340V', - 'S:T345P', - 'S:R346G', - 'S:R346I', - 'S:R346K', - 'S:R346S', - 'S:R346T', - 'S:K356Q', - 'S:K356T', - 'S:S371F', - 'S:S371L', - 'S:D405E', - 'S:D405N', - 'S:E406D', - 'S:K417E', - 'S:K417H', - 'S:K417I', - 'S:K417M', - 'S:K417N', - 'S:K417R', - 'S:K417S', - 'S:K417T', - 'S:D420A', - 'S:D420N', - 'S:N439K', - 'S:N440D', - 'S:N440E', - 'S:N440I', - 'S:N440K', - 'S:N440R', - 'S:N440T', - 'S:N440Y', - 'S:S443Y', - 'S:K444E', - 'S:K444F', - 'S:K444I', - 'S:K444L', - 'S:K444M', - 'S:K444N', - 'S:K444R', - 'S:K444T', - 'S:V445A', - 'S:V445D', - 'S:V445F', - 'S:V445I', - 'S:V445L', - 'S:G446A', - 'S:G446D', - 'S:G446I', - 'S:G446N', - 'S:G446R', - 'S:G446S', - 'S:G446T', - 'S:G446V', - 'S:G447C', - 'S:G447D', - 'S:G447F', - 'S:G447S', - 'S:G447V', - 'S:N448D', - 'S:N448K', - 'S:N448T', - 'S:N448Y', - 'S:Y449D', - 'S:N450D', - 'S:N450K', - 'S:L452M', - 'S:L452Q', - 'S:L452R', - 'S:L452W', - 'S:Y453F', - 'S:Y453H', - 'S:L455F', - 'S:L455M', - 'S:L455S', - 'S:L455W', - 'S:F456C', - 'S:F456L', - 'S:F456V', - 'S:S459P', - 'S:N460D', - 'S:N460H', - 'S:N460I', - 'S:N460K', - 'S:N460S', - 'S:N460T', - 'S:N460Y', - 'S:A475D', - 'S:A475V', - 'S:G476D', - 'S:G476R', - 'S:G476T', - 'S:V483A', - 'S:E484A', - 'S:E484D', - 'S:E484G', - 'S:E484K', - 'S:E484P', - 'S:E484Q', - 'S:E484R', - 'S:E484S', - 'S:E484T', - 'S:E484V', - 'S:G485D', - 'S:G485R', - 'S:F486D', - 'S:F486I', - 'S:F486L', - 'S:F486N', - 'S:F486P', - 'S:F486S', - 'S:F486T', - 'S:F486V', - 'S:N487D', - 'S:N487H', - 'S:N487S', - 'S:Y489H', - 'S:Y489W', - 'S:F490G', - 'S:F490I', - 'S:F490L', - 'S:F490R', - 'S:F490S', - 'S:F490V', - 'S:F490Y', - 'S:Q493D', - 'S:Q493E', - 'S:Q493H', - 'S:Q493K', - 'S:Q493L', - 'S:Q493R', - 'S:Q493V', - 'S:S494P', - 'S:S494R', - 'S:G496S', - 'S:Q498H', - 'S:P499H', - 'S:P499R', - 'S:P499S', - 'S:P499T', - 'S:N501T', - 'S:N501Y', - 'S:G504C', - 'S:G504D', - 'S:G504I', - 'S:G504L', - 'S:G504N', - 'S:G504R', - 'S:G504V', - 'S:P507A', - 'S:N856K', - 'S:N969K', - 'S:E990A', - 'S:T1009I', - ], - offset: 0, - }, -]; - -/** - * Maps a code like ORF1a:T1234:A to RdPd:T56A, by replacing the gene with the mature name - * and adjusting the position with the given offset. - */ -function matureName(code: string, matureName: string, offset: number): string { - const [_, mutationPart] = code.split(':'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const position = parseInt(/\d+/.exec(mutationPart)![0], 10); - const originalBase = mutationPart[0]; - const newBase = mutationPart[mutationPart.length - 1]; - return `${matureName}:${originalBase}${position + offset}${newBase} `; -} - -/** - * Converts a `ResistanceMutationSet` into a list of `MutationAnnotations`. - * For every individual mutation, a mutation annotation will be generated (hence 1:n mapping). - */ -export function toMutationAnnotations(resistanceMutation: ResistanceMutationSet): MutationAnnotations { - return resistanceMutation.mutations.map((mutation) => ({ - name: matureName(mutation, resistanceMutation.name, resistanceMutation.offset), - symbol: resistanceMutation.annotationSymbol, - description: resistanceMutation.description, - aminoAcidMutations: [mutation], - })); -} diff --git a/website/src/components/views/wasap/useWasapPageData.ts b/website/src/components/views/wasap/useWasapPageData.ts index 2eb629e2d..692196812 100644 --- a/website/src/components/views/wasap/useWasapPageData.ts +++ b/website/src/components/views/wasap/useWasapPageData.ts @@ -22,22 +22,32 @@ import { validateGenomeOnly } from '../../../util/siloExpressionUtils'; /** * Hook that fetches and returns `WasapPageData` for the W-ASAP page, * depending on the analysis mode and analysis mode settings. + * The `resistanceMutationsBySet` data, derived from server-fetched collections, + * is needed because resistance is also a possible analysis mode. */ -export function useWasapPageData(config: WasapPageConfig, analysis: WasapAnalysisFilter) { +export function useWasapPageData( + config: WasapPageConfig, + resistanceMutationsBySet: Record, + analysis: WasapAnalysisFilter, +) { return useQuery({ - queryKey: ['wasap', analysis], - queryFn: () => fetchWasapPageData(config, analysis), + queryKey: ['wasap', analysis, resistanceMutationsBySet], + queryFn: () => fetchWasapPageData(config, resistanceMutationsBySet, analysis), }); } -async function fetchWasapPageData(config: WasapPageConfig, analysis: WasapAnalysisFilter): Promise { +async function fetchWasapPageData( + config: WasapPageConfig, + resistanceMutationsBySet: Record, + analysis: WasapAnalysisFilter, +): Promise { switch (analysis.mode) { case 'manual': return fetchManualModeData(config, analysis); case 'variant': return fetchVariantModeData(config, analysis); case 'resistance': - return fetchResistanceModeData(config, analysis); + return fetchResistanceModeData(resistanceMutationsBySet, analysis); case 'untracked': return fetchUntrackedModeData(config, analysis); case 'collection': @@ -87,14 +97,13 @@ async function fetchVariantModeData( }; } -function fetchResistanceModeData(config: WasapPageConfig, analysis: WasapResistanceFilter): WasapMutationsData { - if (!config.resistanceAnalysisModeEnabled) { - throw Error("Cannot fetch data, 'resistance' mode is not enabled."); - } +function fetchResistanceModeData( + displayMutationsBySet: Record, + analysis: WasapResistanceFilter, +): WasapMutationsData { return { type: 'mutations', - displayMutations: - config.resistanceMutationSets.find((set) => set.name === analysis.resistanceSet)?.mutations ?? [], + displayMutations: displayMutationsBySet[analysis.resistanceSet ?? ''] ?? [], }; } diff --git a/website/src/components/views/wasap/wasapPageConfig.ts b/website/src/components/views/wasap/wasapPageConfig.ts index 65ec8bf2d..b4f9b030c 100644 --- a/website/src/components/views/wasap/wasapPageConfig.ts +++ b/website/src/components/views/wasap/wasapPageConfig.ts @@ -1,7 +1,5 @@ import type { DateRangeOption, SequenceType, TemporalGranularity } from '@genspectrum/dashboard-components/util'; -import type { ResistanceMutationSet } from './resistanceMutations'; - /** * All config settings for a W-ASAP dashboard page. */ @@ -87,7 +85,7 @@ type ResistanceAnalysisModeConfig = } | { resistanceAnalysisModeEnabled: true; - resistanceMutationSets: ResistanceMutationSet[]; + resistanceMutationCollections: ResistanceMutationCollectionConfig[]; filterDefaults: { resistance: WasapResistanceFilter; }; @@ -236,3 +234,13 @@ export type WasapFilter = { base: WasapBaseFilter; analysis: WasapAnalysisFilter; }; + +/** + * Resistance mutations defined in a collection, which is specified via the collection ID. + */ +export type ResistanceMutationCollectionConfig = { + collectionId: number; + name: string; + description: string; + annotationSymbol: string; +}; diff --git a/website/src/types/wastewaterConfig.spec.ts b/website/src/types/wastewaterConfig.spec.ts index e1b58e06b..93d774339 100644 --- a/website/src/types/wastewaterConfig.spec.ts +++ b/website/src/types/wastewaterConfig.spec.ts @@ -5,7 +5,7 @@ import { wastewaterOrganismConfigs } from './wastewaterConfig'; describe.each(Object.entries(wastewaterOrganismConfigs))('wastewaterConfig %s', (_configName, config) => { test('default resistance set name is valid', () => { if (config.resistanceAnalysisModeEnabled) { - const resistanceSetNames = config.resistanceMutationSets.map((s) => s.name); + const resistanceSetNames = config.resistanceMutationCollections.map((s) => s.name); const defaultSetName = config.filterDefaults.resistance.resistanceSet; expect(resistanceSetNames).include(defaultSetName); } diff --git a/website/src/types/wastewaterConfig.ts b/website/src/types/wastewaterConfig.ts index bbca9923a..07dbb835b 100644 --- a/website/src/types/wastewaterConfig.ts +++ b/website/src/types/wastewaterConfig.ts @@ -1,6 +1,6 @@ import type { MutationAnnotation } from '@genspectrum/dashboard-components/util'; -import { covidResistanceMutations } from '../components/views/wasap/resistanceMutations'; +import type { ResistanceMutationCollectionConfig } from '../components/views/wasap/wasapPageConfig'; import { VARIANT_TIME_FRAME, type WasapPageConfig } from '../components/views/wasap/wasapPageConfig'; export const wastewaterOrganisms = { @@ -29,7 +29,29 @@ export const wastewaterOrganismConfigs: RecordStanford Coronavirus Antiviral & Resistance database (last updated on 21 August 2024).', + }, + { + collectionId: 2, + name: 'RdRp', + annotationSymbol: 'r', + description: + 'SARS-CoV-2 RNA-dependent RNA polymerase (RdRP) inhibitor resistance mutation as per Stanford Coronavirus Antiviral & Resistance database (last updated on 21 August 2024).', + }, + { + collectionId: 3, + name: 'Spike', + annotationSymbol: 's', + description: + 'SARS-CoV-2 Spike monoclonal antibody (mAb) resistance mutation as per Stanford Coronavirus Antiviral & Resistance database (last updated on 21 August 2024).', + }, + ] satisfies ResistanceMutationCollectionConfig[], lapisBaseUrl: 'https://lapis.wasap.genspectrum.org/covid', samplingDateField: 'samplingDate', locationNameField: 'locationName', @@ -160,6 +182,26 @@ export const wastewaterOrganismConfigs: Record = { '3CLpro': 1, 'RdRp': 2, 'Spike': 3 }; + return { + ...config, + resistanceMutationCollections: config.resistanceMutationCollections.map((set) => ({ + ...set, + collectionId: stagingIds[set.name] ?? set.collectionId, + })), + }; +} + +export const wastewaterOrganismStagingConfigs: Record = { + ...wastewaterOrganismConfigs, + [wastewaterOrganisms.covid]: withResistanceCollectionOverrides( + wastewaterOrganismConfigs[wastewaterOrganisms.covid], + ), +}; + export const wastewaterConfig = { menuListEntryDecoration: 'decoration-teal', backgroundColor: 'bg-tealMuted', diff --git a/website/src/views/pageStateHandlers/WasapPageStateHandler.spec.ts b/website/src/views/pageStateHandlers/WasapPageStateHandler.spec.ts index 2747ba694..37d580272 100644 --- a/website/src/views/pageStateHandlers/WasapPageStateHandler.spec.ts +++ b/website/src/views/pageStateHandlers/WasapPageStateHandler.spec.ts @@ -1,7 +1,6 @@ import { describe, expect, it } from 'vitest'; import { WasapPageStateHandler } from './WasapPageStateHandler'; -import { covidResistanceMutations } from '../../components/views/wasap/resistanceMutations'; import { VARIANT_TIME_FRAME, type WasapCollectionFilter, @@ -26,7 +25,11 @@ const config: WasapPageConfig = { variantAnalysisModeEnabled: true, resistanceAnalysisModeEnabled: true, untrackedAnalysisModeEnabled: true, - resistanceMutationSets: covidResistanceMutations, + resistanceMutationCollections: [ + { name: '3CLpro', annotationSymbol: 'c', description: '', collectionId: 1 }, + { name: 'RdRp', annotationSymbol: 'r', description: '', collectionId: 2 }, + { name: 'Spike', annotationSymbol: 's', description: '', collectionId: 3 }, + ], lapisBaseUrl: 'https://lapis.wasap.genspectrum.org', samplingDateField: 'samplingDate', locationNameField: 'locationName',