Skip to content

Commit

Permalink
fix(components): gs-prevalence-over-time: validate attributes
Browse files Browse the repository at this point in the history
Resolves: #576
  • Loading branch information
JonasKellerer committed Dec 10, 2024
1 parent 66d75fe commit daa2a6a
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import numeratorFilterNoData from './__mockData__/numeratorFilterNoData.json';
import numeratorOneDataset from './__mockData__/numeratorFilterOneDataset.json';
import { PrevalenceOverTime, type PrevalenceOverTimeProps } from './prevalence-over-time';
import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
import { expectInvalidAttributesErrorMessage } from '../shared/stories/expectInvalidAttributesErrorMessage';

export default {
title: 'Visualization/PrevalenceOverTime',
Expand Down Expand Up @@ -256,3 +257,16 @@ export const ShowsNoDataBanner: StoryObj<PrevalenceOverTimeProps> = {
});
},
};

export const WithNoLapisDateField: StoryObj<PrevalenceOverTimeProps> = {
...OneVariant,
args: {
...OneVariant.args,
lapisDateField: '',
},
play: async ({ canvasElement, step }) => {
step('expect error message', async () => {
await expectInvalidAttributesErrorMessage(canvasElement, 'String must contain at least 1 character(s)');
});
},
};
47 changes: 28 additions & 19 deletions components/src/preact/prevalenceOverTime/prevalence-over-time.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { type FunctionComponent } from 'preact';
import { useContext, useEffect, useState } from 'preact/hooks';
import z from 'zod';

import { getPrevalenceOverTimeTableData } from './getPrevalenceOverTimeTableData';
import PrevalenceOverTimeBarChart from './prevalence-over-time-bar-chart';
import PrevalenceOverTimeBubbleChart from './prevalence-over-time-bubble-chart';
import PrevalenceOverTimeLineChart from './prevalence-over-time-line-chart';
import PrevalenceOverTimeTable from './prevalence-over-time-table';
import { type PrevalenceOverTimeData, queryPrevalenceOverTime } from '../../query/queryPrevalenceOverTime';
import { type LapisFilter, type NamedLapisFilter, type TemporalGranularity } from '../../types';
import { lapisFilterSchema, namedLapisFilterSchema, temporalGranularitySchema, views } from '../../types';
import { LapisUrlContext } from '../LapisUrlContext';
import { ConfidenceIntervalSelector } from '../components/confidence-interval-selector';
import { CsvDownloadButton } from '../components/csv-download-button';
Expand All @@ -19,34 +20,42 @@ import { NoDataDisplay } from '../components/no-data-display';
import { ResizeContainer } from '../components/resize-container';
import { ScalingSelector } from '../components/scaling-selector';
import Tabs from '../components/tabs';
import { type ConfidenceIntervalMethod } from '../shared/charts/confideceInterval';
import { type AxisMax } from '../shared/charts/getYAxisMax';
import { type ConfidenceIntervalMethod, confidenceIntervalMethodSchema } from '../shared/charts/confideceInterval';
import { axisMaxSchema } from '../shared/charts/getYAxisMax';
import { type ScaleType } from '../shared/charts/getYAxisScale';
import { useQuery } from '../useQuery';

export type View = 'bar' | 'line' | 'bubble' | 'table';
const viewSchema = z.union([
z.literal(views.table),
z.literal(views.bar),
z.literal(views.line),
z.literal(views.bubble),
]);
export type View = z.infer<typeof viewSchema>;

export interface PrevalenceOverTimeProps {
width: string;
height: string;
numeratorFilter: NamedLapisFilter | NamedLapisFilter[];
denominatorFilter: LapisFilter;
granularity: TemporalGranularity;
smoothingWindow: number;
views: View[];
confidenceIntervalMethods: ConfidenceIntervalMethod[];
lapisDateField: string;
pageSize: boolean | number;
yAxisMaxLinear: AxisMax;
yAxisMaxLogarithmic: AxisMax;
}
const prevalenceOverTimePropsSchema = z.object({
width: z.string(),
height: z.string(),
numeratorFilter: z.union([namedLapisFilterSchema, z.array(namedLapisFilterSchema)]),
denominatorFilter: lapisFilterSchema,
granularity: temporalGranularitySchema,
smoothingWindow: z.number(),
views: z.array(viewSchema),
confidenceIntervalMethods: z.array(confidenceIntervalMethodSchema),
lapisDateField: z.string().min(1),
pageSize: z.union([z.boolean(), z.number()]),
yAxisMaxLinear: axisMaxSchema,
yAxisMaxLogarithmic: axisMaxSchema,
});

export type PrevalenceOverTimeProps = z.infer<typeof prevalenceOverTimePropsSchema>;

export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = (componentProps) => {
const { width, height } = componentProps;
const size = { height, width };

return (
<ErrorBoundary size={size}>
<ErrorBoundary size={size} schema={prevalenceOverTimePropsSchema} componentProps={componentProps}>
<ResizeContainer size={size}>
<PrevalenceOverTimeInner {...componentProps} />
</ResizeContainer>
Expand Down
18 changes: 10 additions & 8 deletions components/src/preact/shared/charts/confideceInterval.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// calculateWilsonInterval calculates the Wilson score interval for 95% confidence.
//
// This function is based on https://github.com/erikfox/wilson-interval, but without
// high precision math.
//
// observed - number of observed positive outcomes
// sample - number of experiments or size of the sample
import z from 'zod';

/*
* calculateWilsonInterval calculates the Wilson score interval for 95% confidence.
*This function is based on https://github.com/erikfox/wilson-interval, but without high precision math.
* observed - number of observed positive outcomes
* sample - number of experiments or size of the sample
*/
export function wilson95PercentConfidenceInterval(observed: number, sample: number) {
const p = observed / sample;
const n = sample;
Expand All @@ -31,4 +32,5 @@ export const confidenceIntervalDataLabel = (
return `${label}${value.toFixed(3)} (${lowerLimit?.toFixed(3)} - ${upperLimit?.toFixed(3)})`;
};

export type ConfidenceIntervalMethod = 'wilson' | 'none';
export const confidenceIntervalMethodSchema = z.union([z.literal('wilson'), z.literal('none')]);
export type ConfidenceIntervalMethod = z.infer<typeof confidenceIntervalMethodSchema>;
5 changes: 4 additions & 1 deletion components/src/preact/shared/charts/getYAxisMax.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import z from 'zod';

export interface YAxisMaxConfig {
linear?: AxisMax;
logarithmic?: AxisMax;
}

export type AxisMax = 'maxInData' | 'limitTo1' | number;
export const axisMaxSchema = z.union([z.literal('maxInData'), z.literal('limitTo1'), z.number()]);
export type AxisMax = z.infer<typeof axisMaxSchema>;

export const getYAxisMax = (maxInData: number, axisMax?: AxisMax) => {
if (!axisMax) {
Expand Down
1 change: 1 addition & 0 deletions components/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const views = {
insertions: 'insertions',
bar: 'bar',
line: 'line',
bubble: 'bubble',
} as const;

export const mutationComparisonViewSchema = z.union([z.literal(views.table), z.literal(views.venn)]);
Expand Down
4 changes: 4 additions & 0 deletions components/src/utilEntrypoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ export {
} from './types';

export { type SelectedMutationFilterStrings } from './preact/mutationFilter/mutation-filter';

export { type ConfidenceIntervalMethod } from './preact/shared/charts/confideceInterval';

export { type AxisMax, type YAxisMaxConfig } from './preact/shared/charts/getYAxisMax';
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ export class PrevalenceOverTimeComponent extends PreactLitAdapterWithGridJsStyle
// prettier-ignore
// The multiline union type must not start with `|` because it looks weird in the Storybook docs
/**
* Required.
* Either a LAPIS filter or an array of LAPIS filters to calculate the prevalence for.
*
* The `lapisFilter` will be sent as is to LAPIS to select the data.
Expand All @@ -57,7 +56,7 @@ export class PrevalenceOverTimeComponent extends PreactLitAdapterWithGridJsStyle
* It should be human-readable.
*
*/
@property({type: Object})
@property({ type: Object })
numeratorFilter:
{
lapisFilter: Record<string, string | number | null | boolean>;
Expand All @@ -66,11 +65,9 @@ export class PrevalenceOverTimeComponent extends PreactLitAdapterWithGridJsStyle
| {
lapisFilter: Record<string, string | number | null | boolean>;
displayName: string;
}[] = {displayName: '', lapisFilter: {}};
}[] = { displayName: '', lapisFilter: {} };

/**
* Required.
*
* The LAPIS filter, to select the data of the reference.
* It must be a valid LAPIS filter object.
*/
Expand Down Expand Up @@ -133,7 +130,7 @@ export class PrevalenceOverTimeComponent extends PreactLitAdapterWithGridJsStyle
* Must be a field of type `date` in LAPIS.
*/
@property({ type: String })
lapisDateField: string = 'date';
lapisDateField: string = '';

/**
* The maximum number of rows to display in the table view.
Expand Down

0 comments on commit daa2a6a

Please sign in to comment.