Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,47 +13,47 @@ import { LabeledField } from './utils/LabeledField';
import { RadioSelect } from './utils/RadioSelect';
import { getCladeLineages } from '../../../lapis/getCladeLineages';
import { Inset } from '../../../styles/Inset';
import { wastewaterConfig } from '../../../types/wastewaterConfig';
import { recentDaysDateRangeOptions } from '../../../util/recentDaysDateRangeOptions';
import { type PageStateHandler } from '../../../views/pageStateHandlers/PageStateHandler';
import {
type WasapFilter,
type WasapAnalysisMode,
defaultManualFilter,
defaultVariantFilter,
defaultResistanceFilter,
defaultUntrackedFilter,
type WasapBaseFilter,
type WasapAnalysisFilter,
} from '../../../views/pageStateHandlers/WasapPageStateHandler';
import { GsTextFilter } from '../../genspectrum/GsTextFilter';
import type {
WasapAnalysisFilter,
WasapAnalysisMode,
WasapBaseFilter,
WasapFilter,
WasapPageConfig,
} from '../../views/wasap/wasapPageConfig';

/**
* The root filter control for the W-ASAP dashboard.
* Uses sub filter components for the different modes, in the 'filters' directory.
*/
export function WasapPageStateSelector({
config,
pageStateHandler,
initialBaseFilterState,
initialAnalysisFilterState,
}: {
config: WasapPageConfig;
pageStateHandler: PageStateHandler<WasapFilter>;
initialBaseFilterState: WasapBaseFilter;
initialAnalysisFilterState: WasapAnalysisFilter;
}) {
const [baseFilterState, setBaseFilterState] = useState(initialBaseFilterState);

const [manualFilter, setManualFilter] = useState(
initialAnalysisFilterState.mode === 'manual' ? initialAnalysisFilterState : defaultManualFilter,
initialAnalysisFilterState.mode === 'manual' ? initialAnalysisFilterState : config.filterDefaults.manual,
);
const [variantFilter, setVariantFilter] = useState(
initialAnalysisFilterState.mode === 'variant' ? initialAnalysisFilterState : defaultVariantFilter,
initialAnalysisFilterState.mode === 'variant' ? initialAnalysisFilterState : config.filterDefaults.variant,
);
const [resistanceFilter, setResistanceFilter] = useState(
initialAnalysisFilterState.mode === 'resistance' ? initialAnalysisFilterState : defaultResistanceFilter,
initialAnalysisFilterState.mode === 'resistance'
? initialAnalysisFilterState
: config.filterDefaults.resistance,
);
const [untrackedFilter, setUntrackedFilter] = useState(
initialAnalysisFilterState.mode === 'untracked' ? initialAnalysisFilterState : defaultUntrackedFilter,
initialAnalysisFilterState.mode === 'untracked' ? initialAnalysisFilterState : config.filterDefaults.untracked,
);

const [selectedAnalysisMode, setSelectedAnalysisMode] = useState(initialAnalysisFilterState.mode);
Expand All @@ -73,12 +73,13 @@ export function WasapPageStateSelector({

// data for the 'untracked' analysis mode - loaded here already so it's available when the mode is selected
const cladeLineageQueryResult = useQuery({
enabled: config.enabledAnalysisModes.includes('untracked'),
queryKey: ['cladeLineages'],
queryFn: () =>
getCladeLineages(
wastewaterConfig.wasap.covSpectrum.lapisBaseUrl,
wastewaterConfig.wasap.covSpectrum.cladeField,
wastewaterConfig.wasap.covSpectrum.lineageField,
config.clinicalLapis.lapisBaseUrl,
config.clinicalLapis.cladeField,
config.clinicalLapis.lineageField,
true,
),
});
Expand All @@ -90,7 +91,7 @@ export function WasapPageStateSelector({
<LabeledField label='Sampling location'>
<GsTextFilter
placeholderText='Sampling location'
lapisField={wastewaterConfig.wasap.locationNameField}
lapisField={config.locationNameField}
lapisFilter={{}}
onInputChange={({ locationName }) => {
setBaseFilterState({ ...baseFilterState, locationName });
Expand All @@ -101,8 +102,8 @@ export function WasapPageStateSelector({

<DynamicDateFilter
label='Sampling date'
lapis={wastewaterConfig.wasap.lapisBaseUrl}
dateFieldName={wastewaterConfig.wasap.samplingDateField}
lapis={config.lapisBaseUrl}
dateFieldName={config.samplingDateField}
generateOptions={recentDaysDateRangeOptions}
value={baseFilterState.samplingDate}
onChange={(newDateRange?) => setBaseFilterState({ ...baseFilterState, samplingDate: newDateRange })}
Expand Down Expand Up @@ -139,10 +140,11 @@ export function WasapPageStateSelector({
setSelectedAnalysisMode(e.target.value as WasapAnalysisMode);
}}
>
<option value='manual'>Manual</option>
<option value='resistance'>Resistance Mutations</option>
<option value='variant'>Variant Explorer</option>
<option value='untracked'>Untracked Mutations</option>
{config.enabledAnalysisModes.map((mode) => (
<option key={mode} value={mode}>
{modeLabel(mode)}
</option>
))}
</select>
<Inset className='p-2'>
{(() => {
Expand All @@ -154,22 +156,25 @@ export function WasapPageStateSelector({
<VariantExplorerFilter
pageState={variantFilter}
setPageState={setVariantFilter}
clinicalSequenceLapisBaseUrl={wastewaterConfig.wasap.covSpectrum.lapisBaseUrl}
clinicalSequenceLapisBaseUrl={config.clinicalLapis.lapisBaseUrl}
clinicalSequenceLapisLineageField={config.clinicalLapis.lineageField}
/>
);
case 'resistance':
return (
<ResistanceMutationsFilter
pageState={resistanceFilter}
setPageState={setResistanceFilter}
resistanceMutationSets={config.resistanceMutationSets}
/>
);
case 'untracked':
return (
<UntrackedFilter
pageState={untrackedFilter}
setPageState={setUntrackedFilter}
clinicalSequenceLapisBaseUrl={wastewaterConfig.wasap.covSpectrum.lapisBaseUrl}
clinicalSequenceLapisBaseUrl={config.clinicalLapis.lapisBaseUrl}
clinicalSequenceLapisLineageField={config.clinicalLapis.lineageField}
cladeLineageQueryResult={cladeLineageQueryResult}
/>
);
Expand All @@ -180,3 +185,16 @@ export function WasapPageStateSelector({
</div>
);
}

function modeLabel(mode: WasapAnalysisMode): string {
switch (mode) {
case 'manual':
return 'Manual';
case 'resistance':
return 'Resistance Mutations';
case 'variant':
return 'Variant Explorer';
case 'untracked':
return 'Untracked Mutations';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { render } from 'vitest-browser-react';
import { ManualAnalysisFilter } from './ManualAnalysisFilter';
import { DUMMY_LAPIS_URL, type LapisRouteMocker } from '../../../../../routeMocker';
import { it } from '../../../../../test-extend';
import type { WasapManualFilter } from '../../../../views/pageStateHandlers/WasapPageStateHandler';
import type { WasapManualFilter } from '../../../views/wasap/wasapPageConfig';

describe('ManualAnalysisFilter', () => {
const defaultPageState: WasapManualFilter = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { mutationType, type MutationType } from '@genspectrum/dashboard-components/util';

import { type WasapManualFilter } from '../../../../views/pageStateHandlers/WasapPageStateHandler';
import { GsMutationFilter } from '../../../genspectrum/GsMutationFilter';
import type { WasapManualFilter } from '../../../views/wasap/wasapPageConfig';
import { SequenceTypeSelector } from '../utils/SequenceTypeSelector';

export function ManualAnalysisFilter({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { render } from 'vitest-browser-react';

import { ResistanceMutationsFilter } from './ResistanceMutationsFilter';
import { it } from '../../../../../test-extend';
import type { WasapResistanceFilter } from '../../../../views/pageStateHandlers/WasapPageStateHandler';
import { wastewaterOrganismConfigs } from '../../../../types/wastewaterConfig';
import type { WasapResistanceFilter } from '../../../views/wasap/wasapPageConfig';

describe('ResistanceMutationsFilter', () => {
const defaultPageState: WasapResistanceFilter = {
Expand All @@ -12,11 +13,17 @@ describe('ResistanceMutationsFilter', () => {
resistanceSet: '3CLpro',
};

const resistanceMutationSets = wastewaterOrganismConfigs.covid.resistanceMutationSets;

it('renders with initial resistance set', async () => {
const mockSetPageState = vi.fn();

const { getByRole } = render(
<ResistanceMutationsFilter pageState={defaultPageState} setPageState={mockSetPageState} />,
<ResistanceMutationsFilter
pageState={defaultPageState}
setPageState={mockSetPageState}
resistanceMutationSets={resistanceMutationSets}
/>,
);

const select = getByRole('combobox');
Expand All @@ -27,7 +34,11 @@ describe('ResistanceMutationsFilter', () => {
const mockSetPageState = vi.fn();

const { getByRole } = render(
<ResistanceMutationsFilter pageState={defaultPageState} setPageState={mockSetPageState} />,
<ResistanceMutationsFilter
pageState={defaultPageState}
setPageState={mockSetPageState}
resistanceMutationSets={resistanceMutationSets}
/>,
);

const select = getByRole('combobox');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { wastewaterConfig } from '../../../../types/wastewaterConfig';
import { type WasapResistanceFilter } from '../../../../views/pageStateHandlers/WasapPageStateHandler';
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,
}: {
pageState: WasapResistanceFilter;
setPageState: (newState: WasapResistanceFilter) => void;
resistanceMutationSets: ResistanceMutationSet[];
}) {
return (
<LabeledField label='Resistance mutation set'>
Expand All @@ -16,9 +18,9 @@ export function ResistanceMutationsFilter({
value={pageState.resistanceSet}
onChange={(e) => setPageState({ ...pageState, resistanceSet: e.target.value })}
>
{wastewaterConfig.wasap.resistanceMutations.map((resistanceMutation) => (
<option key={resistanceMutation.name} value={resistanceMutation.name}>
{resistanceMutation.name}
{resistanceMutationSets.map((set) => (
<option key={set.name} value={set.name}>
{set.name}
</option>
))}
</select>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { render } from 'vitest-browser-react';
import { UntrackedFilter } from './UntrackedFilter';
import type { LapisRouteMocker } from '../../../../../routeMocker';
import { it } from '../../../../../test-extend';
import type { WasapUntrackedFilter } from '../../../../views/pageStateHandlers/WasapPageStateHandler';
import type { WasapUntrackedFilter } from '../../../views/wasap/wasapPageConfig';

const DUMMY_LAPIS_URL_2 = 'http://lapis2.dummy';

Expand All @@ -33,6 +33,7 @@ describe('UntrackedFilter - custom variants textarea', () => {
pageState={defaultPageState}
setPageState={mockSetPageState}
clinicalSequenceLapisBaseUrl={DUMMY_LAPIS_URL_2}
clinicalSequenceLapisLineageField='pangoLineage'
cladeLineageQueryResult={mockCladeLineageQueryResult}
/>,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { type UseQueryResult } from '@tanstack/react-query';

import { Loading } from '../../../../util/Loading';
import {
type ExcludeSetName,
type WasapUntrackedFilter,
} from '../../../../views/pageStateHandlers/WasapPageStateHandler';
import { GsLineageFilter } from '../../../genspectrum/GsLineageFilter';
import type { ExcludeSetName, WasapUntrackedFilter } from '../../../views/wasap/wasapPageConfig';
import { KnownVariantsExclusionInfo } from '../InfoBlocks';
import { LabeledField } from '../utils/LabeledField';
import { SequenceTypeSelector } from '../utils/SequenceTypeSelector';
Expand All @@ -19,13 +16,15 @@ interface UntrackedFilterProps {
* This is _not_ the same as the LAPIS providing the wastewater amplicon sequences.
*/
clinicalSequenceLapisBaseUrl: string;
clinicalSequenceLapisLineageField: string;
}

export function UntrackedFilter({
pageState,
setPageState,
cladeLineageQueryResult: { isPending, isError, data: cladeLineages },
clinicalSequenceLapisBaseUrl,
clinicalSequenceLapisLineageField,
}: UntrackedFilterProps) {
const defaultLineages = cladeLineages ? Object.values(cladeLineages) : [];
defaultLineages.sort();
Expand All @@ -42,11 +41,11 @@ export function UntrackedFilter({
value={pageState.excludeSet}
onChange={(e) => setPageState({ ...pageState, excludeSet: e.target.value as ExcludeSetName })}
>
<option value='nextstrain'>Nextstrain clades</option>
<option value='predefined'>Nextstrain clades</option>
<option value='custom'>custom</option>
</select>
</LabeledField>
{pageState.excludeSet === 'nextstrain' ? (
{pageState.excludeSet === 'predefined' ? (
isPending ? (
<Loading />
) : isError ? (
Expand Down Expand Up @@ -74,7 +73,7 @@ export function UntrackedFilter({
<LabeledField label='Custom variant list'>
<gs-app lapis={clinicalSequenceLapisBaseUrl}>
<GsLineageFilter
lapisField='pangoLineage'
lapisField={clinicalSequenceLapisLineageField}
lapisFilter={{}}
placeholderText='Variant'
value={pageState.excludeVariants}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { render } from 'vitest-browser-react';
import { VariantExplorerFilter } from './VariantExplorerFilter';
import { DUMMY_LAPIS_URL, type LapisRouteMocker } from '../../../../../routeMocker';
import { it } from '../../../../../test-extend';
import type { WasapVariantFilter } from '../../../../views/pageStateHandlers/WasapPageStateHandler';
import type { WasapVariantFilter } from '../../../views/wasap/wasapPageConfig';

const DUMMY_LAPIS_URL_2 = 'http://lapis2.dummy';

Expand All @@ -29,6 +29,7 @@ describe('VariantExplorerFilter', () => {
pageState={defaultPageState}
setPageState={mockSetPageState}
clinicalSequenceLapisBaseUrl={DUMMY_LAPIS_URL_2}
clinicalSequenceLapisLineageField='pangoLineage'
/>
</gs-app>,
);
Expand All @@ -52,6 +53,7 @@ describe('VariantExplorerFilter', () => {
pageState={defaultPageState}
setPageState={mockSetPageState}
clinicalSequenceLapisBaseUrl={DUMMY_LAPIS_URL_2}
clinicalSequenceLapisLineageField='pangoLineage'
/>
</gs-app>,
);
Expand Down Expand Up @@ -82,6 +84,7 @@ describe('VariantExplorerFilter', () => {
pageState={defaultPageState}
setPageState={mockSetPageState}
clinicalSequenceLapisBaseUrl={DUMMY_LAPIS_URL_2}
clinicalSequenceLapisLineageField='pangoLineage'
/>
</gs-app>,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Inset } from '../../../../styles/Inset';
import { type WasapVariantFilter } from '../../../../views/pageStateHandlers/WasapPageStateHandler';
import { GsLineageFilter } from '../../../genspectrum/GsLineageFilter';
import type { WasapVariantFilter } from '../../../views/wasap/wasapPageConfig';
import { SelectorHeadline } from '../../SelectorHeadline';
import { DefineClinicalSignatureInfo } from '../InfoBlocks';
import { LabeledField } from '../utils/LabeledField';
Expand All @@ -15,12 +15,14 @@ interface VariantExplorerFilterProps {
* This is _not_ the same as the LAPIS providing the wastewater amplicon sequences.
*/
clinicalSequenceLapisBaseUrl: string;
clinicalSequenceLapisLineageField: string;
}

export function VariantExplorerFilter({
pageState,
setPageState,
clinicalSequenceLapisBaseUrl,
clinicalSequenceLapisLineageField,
}: VariantExplorerFilterProps) {
return (
<>
Expand All @@ -33,7 +35,7 @@ export function VariantExplorerFilter({
<LabeledField label='Variant'>
<gs-app lapis={clinicalSequenceLapisBaseUrl}>
<GsLineageFilter
lapisField='pangoLineage'
lapisField={clinicalSequenceLapisLineageField}
lapisFilter={{}}
placeholderText='Variant'
value={pageState.variant}
Expand Down
Loading