diff --git a/packages/app/package.json b/packages/app/package.json index 95805e8acf..4a1a0a4b9f 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -139,7 +139,7 @@ "bootstrap": "exit 0", "export": "next export", "dev": "yarn workspace @corona-dashboard/icons build && yarn workspace @corona-dashboard/common build && run-p dev:common dev:next dev:lokalize", - "dev:next": "node next-server.js", + "dev:next": "cross-env NODE_OPTIONS='--inspect' node next-server.js", "dev:lokalize": "chokidar \"./src/locale/nl_export.json\" -c \"yarn workspace @corona-dashboard/cms lokalize:generate-types\"", "dev:common": "yarn workspace @corona-dashboard/common build:watch", "build": "cross-env NEXT_TELEMETRY_DISABLED=1 && next build", diff --git a/packages/app/schema/archived_nl/__index.json b/packages/app/schema/archived_nl/__index.json index 7322cb6794..03e8b825f0 100644 --- a/packages/app/schema/archived_nl/__index.json +++ b/packages/app/schema/archived_nl/__index.json @@ -42,6 +42,7 @@ "vaccine_vaccinated_or_support_archived_20230411", "vaccine_delivery_per_supplier_archived_20211101", "vaccine_stock_archived_20211024", + "variants_archived_20231101", "tested_ggd_archived_20230321", "tested_overall_archived_20230331", "tested_per_age_group_archived_20230331", @@ -186,6 +187,9 @@ "vaccine_stock_archived_20211024": { "$ref": "vaccine_stock.json" }, + "variants_archived_20231101": { + "$ref": "variants.json" + }, "repeating_shot_administered_20220713": { "$ref": "repeating_shot_administered.json" }, diff --git a/packages/app/schema/archived_nl/variants.json b/packages/app/schema/archived_nl/variants.json new file mode 100644 index 0000000000..84369cd74a --- /dev/null +++ b/packages/app/schema/archived_nl/variants.json @@ -0,0 +1,86 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "archived_nl_variants", + "required": ["values"], + "additionalProperties": false, + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/definitions/variant" + } + } + }, + "definitions": { + "variant": { + "type": "object", + "title": "archived_nl_variants_variant", + "additionalProperties": false, + "required": ["variant_code", "values", "last_value"], + "properties": { + "variant_code": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "$ref": "#/definitions/value" + } + }, + "last_value": { + "$ref": "#/definitions/value" + } + } + }, + "value": { + "type": "object", + "title": "archived_nl_variants_variant_value", + "additionalProperties": false, + "required": [ + "order", + "occurrence", + "percentage", + "sample_size", + "date_start_unix", + "date_end_unix", + "date_of_report_unix", + "date_of_insertion_unix", + "label_nl", + "label_en" + ], + "properties": { + "order": { + "type": "integer" + }, + "occurrence": { + "type": "integer" + }, + "percentage": { + "type": "number" + }, + "sample_size": { + "type": "integer" + }, + "date_start_unix": { + "type": "integer" + }, + "date_end_unix": { + "type": "integer" + }, + "date_of_insertion_unix": { + "type": "integer" + }, + "date_of_report_unix": { + "type": "integer" + }, + "label_nl": { + "type": "string" + }, + "label_en": { + "type": "string" + } + } + } + } +} diff --git a/packages/app/schema/nl/__index.json b/packages/app/schema/nl/__index.json index a8ed83c1e1..86c6e693f1 100644 --- a/packages/app/schema/nl/__index.json +++ b/packages/app/schema/nl/__index.json @@ -20,7 +20,8 @@ "infectionradar_symptoms_trend_per_age_group_weekly", "sewer", "vaccine_campaigns", - "vaccine_administered_last_timeframe" + "vaccine_administered_last_timeframe", + "variants" ], "additionalProperties": false, "properties": { diff --git a/packages/app/src/components/kpi/bordered-kpi-section.tsx b/packages/app/src/components/kpi/bordered-kpi-section.tsx index a5f0d5a9a4..3a254c3162 100644 --- a/packages/app/src/components/kpi/bordered-kpi-section.tsx +++ b/packages/app/src/components/kpi/bordered-kpi-section.tsx @@ -9,10 +9,11 @@ import { KpiContent } from './components/kpi-content'; import { BorderedKpiSectionProps } from './types'; import { Markdown } from '../markdown'; -export const BorderedKpiSection = ({ title, description, source, dateOrRange, tilesData }: BorderedKpiSectionProps) => { +export const BorderedKpiSection = ({ title, description, source, dateOrRange, tilesData, disclaimer }: BorderedKpiSectionProps) => { const metadata: MetadataProps = { date: dateOrRange, source: source, + disclaimer: disclaimer, }; return ( diff --git a/packages/app/src/components/kpi/types.ts b/packages/app/src/components/kpi/types.ts index 8f68774cb0..986f11d303 100644 --- a/packages/app/src/components/kpi/types.ts +++ b/packages/app/src/components/kpi/types.ts @@ -29,6 +29,7 @@ export interface BorderedKpiSectionProps { }; tilesData: [TileData, TileData]; title: string; + disclaimer?: string; } type BarType = { diff --git a/packages/app/src/components/metadata.tsx b/packages/app/src/components/metadata.tsx index 7717c3d9a4..0df2b1ecee 100644 --- a/packages/app/src/components/metadata.tsx +++ b/packages/app/src/components/metadata.tsx @@ -5,6 +5,7 @@ import { space } from '~/style/theme'; import { replaceVariablesInText } from '~/utils/replace-variables-in-text'; import { Box } from './base'; import { InlineText, Text } from './typography'; +import { Markdown } from '~/components/markdown'; type source = { text: string; @@ -25,9 +26,10 @@ export interface MetadataProps extends MarginBottomProps { isTileFooter?: boolean; datumsText?: string; intervalCount?: string; + disclaimer?: string; } -export function Metadata({ date, source, obtainedAt, isTileFooter, datumsText, marginBottom, dataSources, intervalCount }: MetadataProps) { +export function Metadata({ date, source, obtainedAt, isTileFooter, datumsText, marginBottom, dataSources, intervalCount, disclaimer }: MetadataProps) { const { commonTexts, formatDateFromSeconds } = useIntl(); const dateString = @@ -77,6 +79,11 @@ export function Metadata({ date, source, obtainedAt, isTileFooter, datumsText, m }) ) : ( <> + {disclaimer && ( + + + + )} {dateString} {obtainedAt && ` ${replaceVariablesInText(commonTexts.common.metadata.obtained, { diff --git a/packages/app/src/components/stacked-chart/stacked-chart.tsx b/packages/app/src/components/stacked-chart/stacked-chart.tsx index 18141ff0df..cbb52b4407 100644 --- a/packages/app/src/components/stacked-chart/stacked-chart.tsx +++ b/packages/app/src/components/stacked-chart/stacked-chart.tsx @@ -65,6 +65,7 @@ type StackedChartProps = { config: Config[]; valueAnnotation?: string; initialWidth?: number; + disableLegend?: boolean; expectedLabel?: string; formatTooltip?: TooltipFormatter; isPercentage?: boolean; @@ -105,6 +106,7 @@ export function StackedChart(props: StackedChartProp config, initialWidth = 840, isPercentage, + disableLegend, expectedLabel, formatTickValue: formatYTickValue, formatTooltip, @@ -461,9 +463,11 @@ export function StackedChart(props: StackedChartProp )} - - - + {!disableLegend && legendItems && ( + + + + )} ); } diff --git a/packages/app/src/domain/vaccine/vaccine-campaigns-tile/vaccine-campaigns-tile.tsx b/packages/app/src/domain/vaccine/vaccine-campaigns-tile/vaccine-campaigns-tile.tsx index b07689951a..31b96bf566 100644 --- a/packages/app/src/domain/vaccine/vaccine-campaigns-tile/vaccine-campaigns-tile.tsx +++ b/packages/app/src/domain/vaccine/vaccine-campaigns-tile/vaccine-campaigns-tile.tsx @@ -1,16 +1,11 @@ import { useBreakpoints } from '~/utils/use-breakpoints'; -import { ChartTile, Markdown, MetadataProps } from '~/components'; -import { Text } from '~/components/typography'; +import { ChartTile, MetadataProps } from '~/components'; import { NarrowVaccineCampaignTable } from './components/narrow-vaccine-campaign-table'; import { WideVaccineCampaignTable } from './components/wide-vaccine-campaign-table'; import { VaccineCampaign, VaccineCampaignDescriptions, VaccineCampaignHeaders, VaccineCampaignOptions } from './types'; -import { Box } from '~/components/base'; -import { space } from '~/style/theme'; - interface VaccineCampaignsTileProps { title: string; description: string; - descriptionFooter: string; metadata: MetadataProps; headers: VaccineCampaignHeaders; campaigns: VaccineCampaign[]; @@ -18,7 +13,7 @@ interface VaccineCampaignsTileProps { campaignOptions?: VaccineCampaignOptions; } -export const VaccineCampaignsTile = ({ title, headers, campaigns, campaignDescriptions, description, descriptionFooter, metadata, campaignOptions }: VaccineCampaignsTileProps) => { +export const VaccineCampaignsTile = ({ title, headers, campaigns, campaignDescriptions, description, metadata, campaignOptions }: VaccineCampaignsTileProps) => { const breakpoints = useBreakpoints(); // Display only the campaigns that are not hidden in the campaignOptions prop @@ -36,11 +31,6 @@ export const VaccineCampaignsTile = ({ title, headers, campaigns, campaignDescri ) : ( )} - - - - - ); diff --git a/packages/app/src/domain/variants/static-props/get-variant-chart-data.ts b/packages/app/src/domain/variants/data-selection/get-archived-variant-chart-data.ts similarity index 79% rename from packages/app/src/domain/variants/static-props/get-variant-chart-data.ts rename to packages/app/src/domain/variants/data-selection/get-archived-variant-chart-data.ts index 9943c5ef49..586fe5bd97 100644 --- a/packages/app/src/domain/variants/static-props/get-variant-chart-data.ts +++ b/packages/app/src/domain/variants/data-selection/get-archived-variant-chart-data.ts @@ -1,16 +1,9 @@ -import { NlVariants } from '@corona-dashboard/common'; +import { ArchivedNlVariants } from '@corona-dashboard/common'; import { isDefined } from 'ts-is-present'; - -export type VariantCode = string; - -export type VariantChartValue = { - date_start_unix: number; - date_end_unix: number; - is_reliable: boolean; -} & Record; +import { VariantChartValue } from '~/domain/variants/data-selection/types'; const EMPTY_VALUES = { - variantChart: null, + archivedVariantChart: null, dates: { date_of_report_unix: 0, date_start_unix: 0, @@ -22,7 +15,7 @@ const EMPTY_VALUES = { * Returns values for variant timeseries chart * @param variants */ -export function getVariantChartData(variants: NlVariants | undefined) { +export function getArchivedVariantChartData(variants: ArchivedNlVariants | undefined) { if (!isDefined(variants) || !isDefined(variants.values)) { return EMPTY_VALUES; } @@ -51,7 +44,7 @@ export function getVariantChartData(variants: NlVariants | undefined) { }); return { - variantChart: values, + archivedVariantChart: values, dates: { date_of_report_unix: firstVariantInList.last_value.date_of_report_unix, date_start_unix: firstVariantInList.last_value.date_start_unix, diff --git a/packages/app/src/domain/variants/data-selection/get-variant-bar-chart-data.ts b/packages/app/src/domain/variants/data-selection/get-variant-bar-chart-data.ts new file mode 100644 index 0000000000..04f31e1605 --- /dev/null +++ b/packages/app/src/domain/variants/data-selection/get-variant-bar-chart-data.ts @@ -0,0 +1,54 @@ +import { NlVariants } from '@corona-dashboard/common'; +import { isDefined } from 'ts-is-present'; +import { VariantChartValue } from '~/domain/variants/data-selection/types'; + +const EMPTY_VALUES = { + variantChart: null, + dates: { + date_of_report_unix: 0, + date_start_unix: 0, + date_end_unix: 0, + }, +} as const; + +/** + * Returns values for variant timeseries chart + * @param variants + */ +export function getVariantBarChartData(variants: NlVariants) { + if (!isDefined(variants) || !isDefined(variants.values)) { + return EMPTY_VALUES; + } + + const sortedVariants = variants.values.sort((a, b) => b.last_value.order - a.last_value.order); + + const firstVariantInList = sortedVariants.shift(); + + if (!isDefined(firstVariantInList)) { + return EMPTY_VALUES; + } + + const values = firstVariantInList.values.map((value, index) => { + const item = { + is_reliable: true, + date_start_unix: value.date_start_unix, + date_end_unix: value.date_end_unix, + [`${firstVariantInList.variant_code}_occurrence`]: value.occurrence, + } as VariantChartValue; + + sortedVariants.forEach((variant) => { + (item as unknown as Record)[`${variant.variant_code}_occurrence`] = variant.values[index].occurrence; + }); + + return item; + }); + + return { + variantChart: values, + dates: { + date_of_report_unix: firstVariantInList.last_value.date_of_report_unix, + date_start_unix: firstVariantInList.last_value.date_start_unix, + date_end_unix: firstVariantInList.last_value.date_end_unix, + }, + } as const; +} diff --git a/packages/app/src/domain/variants/static-props/get-variant-order-colors.ts b/packages/app/src/domain/variants/data-selection/get-variant-order-colors.ts similarity index 88% rename from packages/app/src/domain/variants/static-props/get-variant-order-colors.ts rename to packages/app/src/domain/variants/data-selection/get-variant-order-colors.ts index b100539ff3..42c0fd5b15 100644 --- a/packages/app/src/domain/variants/static-props/get-variant-order-colors.ts +++ b/packages/app/src/domain/variants/data-selection/get-variant-order-colors.ts @@ -1,11 +1,6 @@ import { NlVariants, colors } from '@corona-dashboard/common'; import { isDefined } from 'ts-is-present'; -import { VariantCode } from './'; - -export type ColorMatch = { - variant: VariantCode; - color: string; -}; +import { ColorMatch, VariantCode } from '~/domain/variants/data-selection/types'; const getColorForVariant = (variantCode: VariantCode, index: number): string => { if (variantCode === 'other_variants') return colors.gray5; diff --git a/packages/app/src/domain/variants/static-props/get-variant-table-data.ts b/packages/app/src/domain/variants/data-selection/get-variant-table-data.ts similarity index 83% rename from packages/app/src/domain/variants/static-props/get-variant-table-data.ts rename to packages/app/src/domain/variants/data-selection/get-variant-table-data.ts index de5947f6ed..cacdbfac67 100644 --- a/packages/app/src/domain/variants/static-props/get-variant-table-data.ts +++ b/packages/app/src/domain/variants/data-selection/get-variant-table-data.ts @@ -1,18 +1,7 @@ -import { colors, NlNamedDifference, NlVariants, NlVariantsVariant, NamedDifferenceDecimal } from '@corona-dashboard/common'; +import { colors, NlNamedDifference, NlVariants, NlVariantsVariant } from '@corona-dashboard/common'; import { first } from 'lodash'; import { isDefined } from 'ts-is-present'; -import { ColorMatch } from './get-variant-order-colors'; -import { VariantCode } from '../static-props'; - -export type VariantRow = { - variantCode: VariantCode; - order: number; - percentage: number | null; - difference?: NamedDifferenceDecimal | null; - color: string; -}; - -export type VariantTableData = ReturnType; +import { ColorMatch, VariantRow } from '~/domain/variants/data-selection/types'; /** * Return values to populate the variants table diff --git a/packages/app/src/domain/variants/data-selection/index.ts b/packages/app/src/domain/variants/data-selection/index.ts new file mode 100644 index 0000000000..7589614493 --- /dev/null +++ b/packages/app/src/domain/variants/data-selection/index.ts @@ -0,0 +1,4 @@ +export * from './get-variant-bar-chart-data'; +export * from './get-archived-variant-chart-data'; +export * from './get-variant-order-colors'; +export * from './get-variant-table-data'; diff --git a/packages/app/src/domain/variants/data-selection/types.ts b/packages/app/src/domain/variants/data-selection/types.ts new file mode 100644 index 0000000000..682bbc6651 --- /dev/null +++ b/packages/app/src/domain/variants/data-selection/types.ts @@ -0,0 +1,40 @@ +import { SiteText } from '~/locale'; +import { NamedDifferenceDecimal, TimestampedValue } from '@corona-dashboard/common'; +import { getVariantTableData } from '~/domain/variants/data-selection/get-variant-table-data'; + +export type VariantCode = string; + +export type ColorMatch = { + variant: VariantCode; + color: string; +}; + +export type VariantTableData = ReturnType; + +export type VariantChartValue = { + date_start_unix: number; + date_end_unix: number; + is_reliable: boolean; +} & Record; + +export type VariantRow = { + variantCode: VariantCode; + order: number; + percentage: number | null; + difference?: NamedDifferenceDecimal | null; + color: string; +}; + +export type VariantDynamicLabels = Record; + +export type VariantsOverTimeGraphText = SiteText['pages']['variants_page']['nl']['varianten_over_tijd_grafiek']; + +export type VariantsStackedAreaTileText = { + variantCodes: VariantDynamicLabels; +} & SiteText['pages']['variants_page']['nl']['varianten_over_tijd_grafiek']; + +export type StackedBarConfig = { + metricProperty: keyof T; + label: string; + color: string; +}; diff --git a/packages/app/src/domain/variants/index.ts b/packages/app/src/domain/variants/index.ts new file mode 100644 index 0000000000..270d708310 --- /dev/null +++ b/packages/app/src/domain/variants/index.ts @@ -0,0 +1,4 @@ +export * from './variants-stacked-bar-chart-tile'; +export * from './variants-stacked-area-tile'; +export * from './variants-table-tile'; +export * from './data-selection'; diff --git a/packages/app/src/domain/variants/logic/use-bar-config.ts b/packages/app/src/domain/variants/logic/use-bar-config.ts new file mode 100644 index 0000000000..9de1371762 --- /dev/null +++ b/packages/app/src/domain/variants/logic/use-bar-config.ts @@ -0,0 +1,69 @@ +import { ColorMatch, VariantChartValue, StackedBarConfig, VariantDynamicLabels, VariantsOverTimeGraphText } from '~/domain/variants/data-selection/types'; +import { useMemo } from 'react'; +import { getValuesInTimeframe, TimeframeOption } from '@corona-dashboard/common'; +import { isPresent } from 'ts-is-present'; + +const extractVariantNamesFromValues = (values: VariantChartValue[]) => { + return values + .flatMap((variantChartValue) => Object.keys(variantChartValue)) + .filter((keyName, index, array) => array.indexOf(keyName) === index) + .filter((keyName) => keyName.endsWith('_occurrence')); +}; + +export const useBarConfig = ( + values: VariantChartValue[], + selectedOptions: (keyof VariantChartValue)[], + variantLabels: VariantDynamicLabels, + tooltipLabels: VariantsOverTimeGraphText, + colors: ColorMatch[], + timeframe: TimeframeOption, + today: Date +) => { + return useMemo(() => { + const valuesInTimeframe: VariantChartValue[] = getValuesInTimeframe(values, timeframe, today); + + const activeVariantsInTimeframeValues: VariantChartValue[] = valuesInTimeframe.map((val) => { + return Object.fromEntries( + Object.entries(val).filter(([key, value]) => (key.includes('occurrence') ? value !== 0 && isPresent(value) && !isNaN(Number(value)) : value)) + ) as VariantChartValue; + }); + + const activeVariantsInTimeframeNames: string[] = extractVariantNamesFromValues(activeVariantsInTimeframeValues); + + const listOfVariantCodes: string[] = extractVariantNamesFromValues(valuesInTimeframe) + .filter((keyName) => activeVariantsInTimeframeNames.includes(keyName)) + .reverse(); + + const barChartConfig: StackedBarConfig[] = []; + + listOfVariantCodes.forEach((variantKey) => { + const variantCodeName = variantKey.split('_').slice(0, -1).join('_'); + + const variantMetricPropertyName = variantCodeName.concat('_occurrence'); + + const variantDynamicLabel = variantLabels[variantCodeName]; + + const color = colors.find((variantColors) => variantColors.variant === variantCodeName)?.color; + + if (variantDynamicLabel) { + const barChartConfigEntry = { + metricProperty: variantMetricPropertyName, + color: color, + label: variantDynamicLabel, + shape: 'gapped-area', + }; + + barChartConfig.push(barChartConfigEntry as StackedBarConfig); + } + }); + + const selectOptions: StackedBarConfig[] = [...barChartConfig]; + + if (selectedOptions.length > 0) { + const selection = barChartConfig.filter((selectedConfig) => selectedOptions.includes(selectedConfig.metricProperty)); + return [selection, selectOptions]; + } else { + return [barChartConfig, selectOptions]; + } + }, [values, tooltipLabels.tooltip_labels.other_percentage, variantLabels, colors, selectedOptions, timeframe, today]); +}; diff --git a/packages/app/src/domain/variants/logic/use-series-config.ts b/packages/app/src/domain/variants/logic/use-series-config.ts new file mode 100644 index 0000000000..e0bd1c1e39 --- /dev/null +++ b/packages/app/src/domain/variants/logic/use-series-config.ts @@ -0,0 +1,60 @@ +import { ColorMatch, VariantChartValue, VariantsStackedAreaTileText } from '~/domain/variants/data-selection/types'; +import { useMemo } from 'react'; +import { GappedAreaSeriesDefinition } from '~/components/time-series-chart/logic'; + +/** + * Create a configuration with appropriate mnemonic label (e.g. "Alpha", "Delta", "Omikron", etc.) and colour for all variants + * present in data. + * @param text + * @param values + * @param colors + */ +export const useSeriesConfig = ( + text: VariantsStackedAreaTileText, + values: VariantChartValue[], + colors: ColorMatch[] +): readonly [GappedAreaSeriesDefinition[], GappedAreaSeriesDefinition[]] => { + return useMemo(() => { + const baseVariantsFiltered = values + .flatMap((x) => Object.keys(x)) // Get all key names + .filter((x, index, array) => array.indexOf(x) === index) // De-dupe keys + .filter((x) => x.endsWith('_percentage')) // Filter out any keys that don't end in '_percentage' + .reverse(); // Reverse to be in alphabetical order + + /* Enrich config with dynamic data / locale */ + const seriesConfig: GappedAreaSeriesDefinition[] = []; + + baseVariantsFiltered.forEach((variantKey) => { + // Remove _percentage from variant key name + const variantCodeFragments = variantKey.split('_'); + variantCodeFragments.pop(); + const variantCode = variantCodeFragments.join('_'); + + // Match mnenonic variant name in lokalize to code-based variant name + const variantDynamicLabel = text.variantCodes[variantCode] + ' '; // THIS IS NECESSARY TO DIFFERENTIATE STATE BETWEEN THE TWO INTERACTIVE LEGENDS ON THE PAGE; + + // Match appropriate variant color + const color = colors.find((variantColors) => variantColors.variant === variantCode)?.color; + + // Create a variant label configuration and push into array + if (variantDynamicLabel) { + const variantConfig = { + type: 'gapped-area', + metricProperty: variantKey as keyof VariantChartValue, + color, + label: variantDynamicLabel, + strokeWidth: 2, + fillOpacity: 0.2, + shape: 'gapped-area', + mixBlendMode: 'multiply', + }; + + seriesConfig.push(variantConfig as GappedAreaSeriesDefinition); + } + }); + + const selectOptions: GappedAreaSeriesDefinition[] = [...seriesConfig]; + + return [seriesConfig, selectOptions] as const; + }, [values, text.tooltip_labels.other_percentage, text.variantCodes, colors]); +}; diff --git a/packages/app/src/domain/variants/variants-stacked-area-tile/logic/use-unreliable-data-annotations.ts b/packages/app/src/domain/variants/logic/use-unreliable-data-annotations.ts similarity index 78% rename from packages/app/src/domain/variants/variants-stacked-area-tile/logic/use-unreliable-data-annotations.ts rename to packages/app/src/domain/variants/logic/use-unreliable-data-annotations.ts index b2e36614ad..603e6c4949 100644 --- a/packages/app/src/domain/variants/variants-stacked-area-tile/logic/use-unreliable-data-annotations.ts +++ b/packages/app/src/domain/variants/logic/use-unreliable-data-annotations.ts @@ -4,19 +4,14 @@ import { useMemo } from 'react'; import { isDefined } from 'ts-is-present'; import { TimespanAnnotationConfig } from '~/components/time-series-chart/logic'; -export function useUnreliableDataAnnotations( - values: (DateSpanValue & { is_reliable: boolean })[], - label: string -) { +export function useUnreliableDataAnnotations(values: (DateSpanValue & { is_reliable: boolean })[], label: string) { return useMemo( () => values .reduce( (acc, x) => { if (!x.is_reliable) { - const annotation = - last(acc) ?? - ({ label, fill: 'dotted' } as TimespanAnnotationConfig); + const annotation = last(acc) ?? ({ label, fill: 'dotted' } as TimespanAnnotationConfig); if (!isDefined(annotation.start)) { annotation.start = x.date_start_unix; annotation.end = x.date_end_unix; diff --git a/packages/app/src/domain/variants/static-props/index.ts b/packages/app/src/domain/variants/static-props/index.ts deleted file mode 100644 index c455be43bc..0000000000 --- a/packages/app/src/domain/variants/static-props/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './get-variant-chart-data'; -export * from './get-variant-order-colors'; -export * from './get-variant-table-data'; diff --git a/packages/app/src/domain/variants/variants-stacked-area-tile/variants-stacked-area-tile.tsx b/packages/app/src/domain/variants/variants-stacked-area-tile.tsx similarity index 71% rename from packages/app/src/domain/variants/variants-stacked-area-tile/variants-stacked-area-tile.tsx rename to packages/app/src/domain/variants/variants-stacked-area-tile.tsx index 753222a673..fbc223b6c2 100644 --- a/packages/app/src/domain/variants/variants-stacked-area-tile/variants-stacked-area-tile.tsx +++ b/packages/app/src/domain/variants/variants-stacked-area-tile.tsx @@ -9,17 +9,11 @@ import { MetadataProps } from '~/components/metadata'; import { TimeSeriesChart } from '~/components/time-series-chart'; import { TooltipSeriesList } from '~/components/time-series-chart/components/tooltip/tooltip-series-list'; import { GappedAreaSeriesDefinition } from '~/components/time-series-chart/logic'; -import { VariantChartValue } from '~/domain/variants/static-props'; -import { SiteText } from '~/locale'; import { useList } from '~/utils/use-list'; -import { ColorMatch } from '~/domain/variants/static-props'; -import { useUnreliableDataAnnotations } from './logic/use-unreliable-data-annotations'; import { space } from '~/style/theme'; -import { VariantDynamicLabels } from '../variants-table-tile/types'; - -type VariantsStackedAreaTileText = { - variantCodes: VariantDynamicLabels; -} & SiteText['pages']['variants_page']['nl']['varianten_over_tijd_grafiek']; +import { useUnreliableDataAnnotations } from './logic/use-unreliable-data-annotations'; +import { ColorMatch, VariantChartValue, VariantsStackedAreaTileText } from '~/domain/variants/data-selection/types'; +import { useSeriesConfig } from '~/domain/variants/logic/use-series-config'; const alwaysEnabled: (keyof VariantChartValue)[] = []; @@ -122,44 +116,3 @@ const useFilteredSeriesConfig = (seriesConfig: GappedAreaSeriesDefinition compareList.includes(item.metricProperty) || compareList.length === alwaysEnabled.length); }, [seriesConfig, compareList]); }; - -const useSeriesConfig = (text: VariantsStackedAreaTileText, values: VariantChartValue[], variantColors: ColorMatch[]) => { - return useMemo(() => { - const baseVariantsFiltered = values - .flatMap((x) => Object.keys(x)) - .filter((x, index, array) => array.indexOf(x) === index) // de-dupe - .filter((x) => x.endsWith('_percentage')) - .reverse(); // Reverse to be in an alphabetical order - - /* Enrich config with dynamic data / locale */ - const seriesConfig: GappedAreaSeriesDefinition[] = []; - baseVariantsFiltered.forEach((variantKey) => { - const variantCodeFragments = variantKey.split('_'); - variantCodeFragments.pop(); - const variantCode = variantCodeFragments.join('_'); - - const variantDynamicLabel = text.variantCodes[variantCode]; - - const color = variantColors.find((variantColors) => variantColors.variant === variantCode)?.color; - - if (variantDynamicLabel) { - const newConfig = { - type: 'gapped-area', - metricProperty: variantKey as keyof VariantChartValue, - color, - label: variantDynamicLabel, - strokeWidth: 2, - fillOpacity: 0.2, - shape: 'gapped-area', - mixBlendMode: 'multiply', - }; - - seriesConfig.push(newConfig as GappedAreaSeriesDefinition); - } - }); - - const selectOptions = [...seriesConfig]; - - return [seriesConfig, selectOptions] as const; - }, [values, text.tooltip_labels.other_percentage, text.variantCodes, variantColors]); -}; diff --git a/packages/app/src/domain/variants/variants-stacked-area-tile/index.ts b/packages/app/src/domain/variants/variants-stacked-area-tile/index.ts deleted file mode 100644 index efc8c20500..0000000000 --- a/packages/app/src/domain/variants/variants-stacked-area-tile/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { VariantsStackedAreaTile } from './variants-stacked-area-tile'; diff --git a/packages/app/src/domain/variants/variants-stacked-bar-chart-tile.tsx b/packages/app/src/domain/variants/variants-stacked-bar-chart-tile.tsx new file mode 100644 index 0000000000..f124b7c2fc --- /dev/null +++ b/packages/app/src/domain/variants/variants-stacked-bar-chart-tile.tsx @@ -0,0 +1,108 @@ +import { ChartTile, MetadataProps } from '~/components'; +import { Spacer } from '~/components/base'; +import { TimeframeOption, TimeframeOptionsList } from '@corona-dashboard/common'; +import { useState } from 'react'; +import { ColorMatch, StackedBarConfig, VariantChartValue, VariantDynamicLabels, VariantsOverTimeGraphText } from '~/domain/variants/data-selection/types'; +import { StackedBarTooltipData, StackedChart } from '~/components/stacked-chart'; +import { useBarConfig } from '~/domain/variants/logic/use-bar-config'; +import { InteractiveLegend } from '~/components/interactive-legend'; +import { useList } from '~/utils/use-list'; +import { TooltipData } from '~/components/time-series-chart/components'; +import { isDefined, isPresent } from 'ts-is-present'; +import { TooltipSeriesList } from '~/components/time-series-chart/components/tooltip/tooltip-series-list'; +import { space } from '~/style/theme'; +import { useCurrentDate } from '~/utils/current-date-context'; + +interface VariantsStackedBarChartTileProps { + title: string; + description: string; + helpText: string; + values: VariantChartValue[]; + tooltipLabels: VariantsOverTimeGraphText; + variantLabels: VariantDynamicLabels; + variantColors: ColorMatch[]; + metadata: MetadataProps; +} + +const alwaysEnabled: (keyof VariantChartValue)[] = []; + +/** + * Check if the key metricProperty exists + * @param config + */ +const hasMetricProperty = (config: any): config is { metricProperty: string } => { + return 'metricProperty' in config; +}; + +/** + * Only variants that have a greater occurrence than 0 must be shown in the tooltip, except when the user narrows down + * the total amount of visible variants by selecting one or more from the legend + * @param context - Tooltip data context + * @param selectionOptions - Currently selected variants + */ +const reorderAndFilter = (context: TooltipData, selectionOptions: StackedBarConfig[]) => { + const metricAmount = context.config.length; + const totalMetricAmount = selectionOptions.length; + const hasSelectedMetrics = metricAmount !== totalMetricAmount; // Check whether the user has selected any variants from the interactive legend. + + /* Filter out any variants that have an occcurrence value of 0 */ + const filteredValues = Object.fromEntries( + Object.entries(context.value).filter(([key, value]) => (key.includes('occurrence') ? value !== 0 && isPresent(value) && !isNaN(Number(value)) : value)) + ) as VariantChartValue; + + /* Rebuild tooltip data context with filtered values */ + const reorderContext = { + ...context, + config: [...context.config.filter((value) => !hasMetricProperty(value) || filteredValues[value.metricProperty] || hasSelectedMetrics)].filter(isDefined), + value: !hasSelectedMetrics ? filteredValues : context.value, + }; + + return reorderContext as TooltipData; +}; + +/** + * Variant bar chart component + * @param title - Graph title + * @param description - Graph description text + * @param helpText - Explainer text above the interactive legend + * @param values - Data + * @param variantLabels - Mnemonic names for variants + * @param variantColors - Colors for variants + * @param metadata - Metadata block + * @constructor + */ +export const VariantsStackedBarChartTile = ({ title, description, helpText, tooltipLabels, values, variantLabels, variantColors, metadata }: VariantsStackedBarChartTileProps) => { + const today = useCurrentDate(); + + const { list, toggle, clear } = useList(alwaysEnabled); + + const [variantTimeFrame, setVariantTimeFrame] = useState(TimeframeOption.THREE_MONTHS); + + const [barChartConfig, selectionOptions] = useBarConfig(values, list, variantLabels, tooltipLabels, variantColors, variantTimeFrame, today); + + const hasTwoColumns = list.length === 0 || list.length > 4; + + return ( + + + + } + /> + + ); +}; diff --git a/packages/app/src/domain/variants/variants-table-tile/variants-table-tile.tsx b/packages/app/src/domain/variants/variants-table-tile.tsx similarity index 60% rename from packages/app/src/domain/variants/variants-table-tile/variants-table-tile.tsx rename to packages/app/src/domain/variants/variants-table-tile.tsx index df622a463c..bd2eb9f760 100644 --- a/packages/app/src/domain/variants/variants-table-tile/variants-table-tile.tsx +++ b/packages/app/src/domain/variants/variants-table-tile.tsx @@ -8,24 +8,19 @@ import { FullscreenChartTile } from '~/components/fullscreen-chart-tile'; import { Markdown } from '~/components/markdown'; import { MetadataProps } from '~/components/metadata'; import { Heading } from '~/components/typography'; -import { VariantRow } from '~/domain/variants/static-props'; import { useIntl } from '~/intl'; -import { space } from '~/style/theme'; +import { fontSizes, space } from '~/style/theme'; import { replaceVariablesInText } from '~/utils/replace-variables-in-text'; -import { VariantsTable } from './components/variants-table'; -import { TableText } from './types'; +import { VariantsTable } from './variants-table-tile/components/variants-table'; +import { TableText } from './variants-table-tile/types'; +import { Tile } from '~/components'; +import { VariantRow } from '~/domain/variants/data-selection/types'; -export function VariantsTableTile({ - text, - noDataMessage = '', - source, - data, - dates, - children = null, -}: { +interface VariantsTableTileProps { text: TableText; noDataMessage?: ReactNode; data?: VariantRow[] | null; + sampleThresholdPassed: boolean; source: { download: string; href: string; @@ -37,7 +32,9 @@ export function VariantsTableTile({ date_of_report_unix: number; }; children?: ReactNode; -}) { +} + +export function VariantsTableTile({ text, noDataMessage = '', sampleThresholdPassed, source, data, dates, children = null }: VariantsTableTileProps) { if (!isPresent(data) || !isPresent(dates)) { return ( @@ -56,21 +53,16 @@ export function VariantsTableTile({ } return ( - + {children} ); } -function VariantsTableTileWithData({ - text, - source, - data, - dates, - children = null, -}: { +interface VariantsTableTileWithDataProps { text: TableText; data: VariantRow[]; + sampleThresholdPassed: boolean; source: { download: string; href: string; @@ -82,7 +74,9 @@ function VariantsTableTileWithData({ date_of_report_unix: number; }; children?: ReactNode; -}) { +} + +function VariantsTableTileWithData({ text, sampleThresholdPassed, source, data, dates, children = null }: VariantsTableTileWithDataProps) { const { formatDateSpan } = useIntl(); const metadata: MetadataProps = { @@ -99,12 +93,25 @@ function VariantsTableTileWithData({ }); return ( - - {children} - - - - + <> + {sampleThresholdPassed ? ( + + {children} + + + + + ) : ( + + + {text.titel} + + + + + + )} + ); } diff --git a/packages/app/src/domain/variants/variants-table-tile/components/narrow-variants-table.tsx b/packages/app/src/domain/variants/variants-table-tile/components/narrow-variants-table.tsx index 46591cdbbc..5dd8740668 100644 --- a/packages/app/src/domain/variants/variants-table-tile/components/narrow-variants-table.tsx +++ b/packages/app/src/domain/variants/variants-table-tile/components/narrow-variants-table.tsx @@ -4,7 +4,6 @@ import styled from 'styled-components'; import { isPresent } from 'ts-is-present'; import { Box } from '~/components/base'; import { InlineText } from '~/components/typography'; -import { VariantRow } from '~/domain/variants/static-props'; import { useIntl } from '~/intl'; import { space } from '~/style/theme'; import { getMaximumNumberOfDecimals } from '~/utils/get-maximum-number-of-decimals'; @@ -12,6 +11,7 @@ import { useCollapsible } from '~/utils/use-collapsible'; import { Cell, HeaderCell, PercentageBarWithNumber, StyledTable, VariantDifference, VariantNameCell } from '.'; import { TableText } from '../types'; import { NoPercentageData } from './no-percentage-data'; +import { VariantRow } from '~/domain/variants/data-selection/types'; interface NarrowVariantsTableProps { rows: VariantRow[]; diff --git a/packages/app/src/domain/variants/variants-table-tile/components/variant-difference.tsx b/packages/app/src/domain/variants/variants-table-tile/components/variant-difference.tsx index 3124720e76..c8e5d50314 100644 --- a/packages/app/src/domain/variants/variants-table-tile/components/variant-difference.tsx +++ b/packages/app/src/domain/variants/variants-table-tile/components/variant-difference.tsx @@ -33,7 +33,7 @@ export const VariantDifference = ({ value, text, isWideTable }: VariantDifferenc { condition: value?.difference > 0, renderingValue: ( - + {formatPercentage(value.difference, options)} {text.verschil.meer} @@ -42,7 +42,7 @@ export const VariantDifference = ({ value, text, isWideTable }: VariantDifferenc { condition: value?.difference < 0, renderingValue: ( - + {formatPercentage(-value.difference, options)} {text.verschil.minder} diff --git a/packages/app/src/domain/variants/variants-table-tile/components/variant-name-cell.tsx b/packages/app/src/domain/variants/variants-table-tile/components/variant-name-cell.tsx index 59567e7993..a58ef0a04b 100644 --- a/packages/app/src/domain/variants/variants-table-tile/components/variant-name-cell.tsx +++ b/packages/app/src/domain/variants/variants-table-tile/components/variant-name-cell.tsx @@ -1,7 +1,7 @@ import { BoldText } from '~/components/typography'; import { Cell } from '.'; import { TableText } from '../types'; -import { VariantCode } from '../../static-props'; +import { VariantCode } from '~/domain/variants/data-selection/types'; type VariantNameCellProps = { variantCode: VariantCode; diff --git a/packages/app/src/domain/variants/variants-table-tile/components/variants-table.tsx b/packages/app/src/domain/variants/variants-table-tile/components/variants-table.tsx index 79143c1368..2f1560a73d 100644 --- a/packages/app/src/domain/variants/variants-table-tile/components/variants-table.tsx +++ b/packages/app/src/domain/variants/variants-table-tile/components/variants-table.tsx @@ -1,8 +1,8 @@ -import { VariantRow } from '~/domain/variants/static-props'; import { useBreakpoints } from '~/utils/use-breakpoints'; import { TableText } from '../types'; import { NarrowVariantsTable } from './narrow-variants-table'; import { WideVariantsTable } from './wide-variants-table'; +import { VariantRow } from '~/domain/variants/data-selection/types'; type VariantsTableProps = { rows: VariantRow[]; @@ -12,13 +12,5 @@ type VariantsTableProps = { export function VariantsTable({ rows, text }: VariantsTableProps) { const breakpoints = useBreakpoints(); - return ( - <> - {breakpoints.sm ? ( - - ) : ( - - )} - - ); + return <>{breakpoints.sm ? : }; } diff --git a/packages/app/src/domain/variants/variants-table-tile/components/wide-variants-table.tsx b/packages/app/src/domain/variants/variants-table-tile/components/wide-variants-table.tsx index 2f53067af4..c9fb40f998 100644 --- a/packages/app/src/domain/variants/variants-table-tile/components/wide-variants-table.tsx +++ b/packages/app/src/domain/variants/variants-table-tile/components/wide-variants-table.tsx @@ -2,12 +2,12 @@ import { DifferenceDecimal } from '@corona-dashboard/common'; import { useMemo } from 'react'; import { isPresent } from 'ts-is-present'; import { Box } from '~/components/base'; -import { VariantRow } from '~/domain/variants/static-props'; import { useIntl } from '~/intl'; import { getMaximumNumberOfDecimals } from '~/utils/get-maximum-number-of-decimals'; import { Cell, HeaderCell, PercentageBarWithNumber, StyledTable, VariantDifference, VariantNameCell } from '.'; import { TableText } from '../types'; import { NoPercentageData } from './no-percentage-data'; +import { VariantRow } from '~/domain/variants/data-selection/types'; const columnKeys = ['variant_titel', 'percentage', 'vorige_meting'] as const; diff --git a/packages/app/src/domain/variants/variants-table-tile/index.ts b/packages/app/src/domain/variants/variants-table-tile/index.ts deleted file mode 100644 index 292e200747..0000000000 --- a/packages/app/src/domain/variants/variants-table-tile/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './variants-table-tile'; diff --git a/packages/app/src/domain/variants/variants-table-tile/types.ts b/packages/app/src/domain/variants/variants-table-tile/types.ts index 3f19cd5577..9132720e62 100644 --- a/packages/app/src/domain/variants/variants-table-tile/types.ts +++ b/packages/app/src/domain/variants/variants-table-tile/types.ts @@ -1,4 +1,4 @@ -export type VariantDynamicLabels = Record; +import { VariantDynamicLabels } from '~/domain/variants/data-selection/types'; export type TableText = { anderen_tooltip: string; diff --git a/packages/app/src/pages/landelijk/vaccinaties.tsx b/packages/app/src/pages/landelijk/vaccinaties.tsx index 9716386f8f..aebe45e4e1 100644 --- a/packages/app/src/pages/landelijk/vaccinaties.tsx +++ b/packages/app/src/pages/landelijk/vaccinaties.tsx @@ -352,7 +352,6 @@ function VaccinationPage(props: StaticProps) { ) { datumsText: textNl.dates, date: archivedData.vaccine_campaigns_archived_20231004.date_unix, source: textNl.vaccine_campaigns.bronnen.rivm, + disclaimer: textNl.vaccine_campaigns.description_footer, }} /> @@ -427,7 +427,6 @@ function VaccinationPage(props: StaticProps) { ) { datumsText: textNl.dates, date: archivedData.vaccine_campaigns_archived_20220908.date_unix, source: textNl.vaccine_campaigns.bronnen.rivm, + disclaimer: textNl.vaccine_campaigns.description_footer, }} /> diff --git a/packages/app/src/pages/landelijk/varianten.tsx b/packages/app/src/pages/landelijk/varianten.tsx index 888702005c..0e569c9728 100644 --- a/packages/app/src/pages/landelijk/varianten.tsx +++ b/packages/app/src/pages/landelijk/varianten.tsx @@ -7,19 +7,20 @@ import { PageInformationBlock } from '~/components/page-information-block'; import { TileList } from '~/components/tile-list'; import { Layout } from '~/domain/layout/layout'; import { NlLayout } from '~/domain/layout/nl-layout'; -import { getVariantChartData, getVariantOrderColors, getVariantTableData } from '~/domain/variants/static-props'; -import { VariantsStackedAreaTile } from '~/domain/variants/variants-stacked-area-tile'; -import { VariantsTableTile } from '~/domain/variants/variants-table-tile'; -import { VariantDynamicLabels } from '~/domain/variants/variants-table-tile/types'; import { useIntl } from '~/intl'; import { Languages, SiteText } from '~/locale'; import { getArticleParts, getDataExplainedParts, getFaqParts, getLinkParts, getPagePartsQuery } from '~/queries/get-page-parts-query'; import { StaticProps, createGetStaticProps } from '~/static-props/create-get-static-props'; -import { createGetContent, getLastGeneratedDate, getLokalizeTexts, selectNlData } from '~/static-props/get-data'; +import { createGetContent, getLastGeneratedDate, getLokalizeTexts, selectArchivedNlData, selectNlData } from '~/static-props/get-data'; import { ArticleParts, LinkParts, PagePartQueryResult } from '~/types/cms'; import { useDynamicLokalizeTexts } from '~/utils/cms/use-dynamic-lokalize-texts'; import { getLastInsertionDateOfPage } from '~/utils/get-last-insertion-date-of-page'; import { getPageInformationHeaderContent } from '~/utils/get-page-information-header-content'; +import { BorderedKpiSection } from '~/components/kpi/bordered-kpi-section'; +import { useState } from 'react'; +import { getArchivedVariantChartData, getVariantBarChartData, getVariantOrderColors, getVariantTableData } from '~/domain/variants/data-selection'; +import { VariantsStackedAreaTile, VariantsStackedBarChartTile, VariantsTableTile } from '~/domain/variants'; +import { VariantDynamicLabels } from '~/domain/variants/data-selection/types'; const pageMetrics = ['variants', 'named_difference']; @@ -36,16 +37,22 @@ export const getStaticProps = createGetStaticProps( getLastGeneratedDate, () => { const data = selectNlData('variants', 'named_difference')(); + const archivedData = selectArchivedNlData('variants_archived_20231101')(); const { selectedNlData: { variants }, } = data; + const { + selectedArchivedNlData: { variants_archived_20231101 }, + } = archivedData; + const variantColors = getVariantOrderColors(variants); return { ...getVariantTableData(variants, data.selectedNlData.named_difference, variantColors), - ...getVariantChartData(variants), + ...getVariantBarChartData(variants), + ...getArchivedVariantChartData(variants_archived_20231101), variantColors, }; }, @@ -64,11 +71,13 @@ export const getStaticProps = createGetStaticProps( ); export default function CovidVariantenPage(props: StaticProps) { - const { pageText, selectedNlData: data, lastGenerated, content, variantTable, variantChart, variantColors, dates } = props; + const { pageText, selectedNlData: data, lastGenerated, content, variantTable, variantChart, archivedVariantChart, variantColors, dates } = props; const { commonTexts, locale } = useIntl(); const { metadataTexts, textNl } = useDynamicLokalizeTexts(pageText, selectLokalizeTexts); + const [isArchivedContentShown, setIsArchivedContentShown] = useState(false); + const metadata = { ...metadataTexts, title: textNl.metadata.title, @@ -77,8 +86,17 @@ export default function CovidVariantenPage(props: StaticProps namedDifferenceEntry.variant_code !== 'other_variants').length; + const totalVariants = data.variants + ? data.variants!.values.reduce((accumulator, currentVariant) => (currentVariant.last_value.occurrence > 0 ? 1 + accumulator : accumulator), 0) + : NaN; + + const sampleThresholdPassed = data.variants ? data.variants!.values[0].last_value.sample_size > 100 : false; + const variantLabels: VariantDynamicLabels = {}; + const variantenTableDescription = sampleThresholdPassed ? textNl.varianten_omschrijving : textNl.varianten_tabel.omschrijving_te_weinig_samples; + data.variants?.values.forEach((variant) => { variantLabels[`${variant.variant_code}`] = locale === 'nl' ? variant.values[0].label_nl : variant.values[0].label_en; }); @@ -109,13 +127,37 @@ export default function CovidVariantenPage(props: StaticProps + + {variantChart && variantLabels && ( - )} + + setIsArchivedContentShown(!isArchivedContentShown)} + /> + + {isArchivedContentShown && ( + <> + {archivedVariantChart && variantLabels && ( + + )} + + )} diff --git a/packages/cms/src/lokalize/key-mutations.csv b/packages/cms/src/lokalize/key-mutations.csv index 4f77c57842..d6e70b0f18 100644 --- a/packages/cms/src/lokalize/key-mutations.csv +++ b/packages/cms/src/lokalize/key-mutations.csv @@ -1,3 +1,15 @@ timestamp,action,key,document_id,move_to 2023-10-16T15:02:56.856Z,add,pages.vaccinations_page.nl.vaccine_campaigns.campaigns.autumn_round_corona_vaccination_2022_description,tWOr2ZtVUADiKBNT0r4Txl,__ 2023-10-16T15:02:56.857Z,delete,pages.vaccinations_page.nl.vaccine_campaigns.campaigns.repeat_vaccination_against_corona_description,DNO5adeD4mWNc516AbctlW,__ +2023-10-20T09:31:18.217Z,add,pages.variants_page.nl.section_archived.title,093FZrW2Ae4fRiIdZYDcnH,__ +2023-10-20T09:31:19.500Z,add,pages.variants_page.nl.section_archived.description,hT5k3RDQ7JafeiQP6wRLNE,__ +2023-10-20T09:31:20.726Z,add,pages.variants_page.nl.varianten_barchart.titel,093FZrW2Ae4fRiIdZYDd0v,__ +2023-10-20T09:31:21.806Z,add,pages.variants_page.nl.varianten_barchart.description,ZkwHqMQjnsmR1ekP50NJ35,__ +2023-10-20T09:31:22.794Z,add,pages.variants_page.nl.kpi_amount_of_samples.kpi_tile_title,ZkwHqMQjnsmR1ekP50NJ65,__ +2023-10-20T09:31:23.837Z,add,pages.variants_page.nl.kpi_amount_of_samples.kpi_tile_description,093FZrW2Ae4fRiIdZYDdEZ,__ +2023-10-20T09:31:24.874Z,add,pages.variants_page.nl.kpi_amount_of_samples.tile_total_samples.title,ZkwHqMQjnsmR1ekP50NJ95,__ +2023-10-20T09:31:25.881Z,add,pages.variants_page.nl.kpi_amount_of_samples.tile_total_samples.description,hT5k3RDQ7JafeiQP6wRLY1,__ +2023-10-20T09:31:26.938Z,add,pages.variants_page.nl.kpi_amount_of_samples.tile_total_variants.title,pmfpKotscgjR5sJqbeu52i,__ +2023-10-20T09:31:28.284Z,add,pages.variants_page.nl.kpi_amount_of_samples.tile_total_variants.description,hT5k3RDQ7JafeiQP6wRLio,__ +2023-10-20T09:31:29.643Z,add,pages.variants_page.nl.kpi_amount_of_samples.disclaimer,w5vHLm19hF0S5wj1e5Ryjx,__ +2023-10-20T09:31:30.622Z,add,pages.variants_page.nl.varianten_tabel.omschrijving_te_weinig_samples,w5vHLm19hF0S5wj1e5RyoG,__ diff --git a/packages/cms/src/studio/data/data-structure.ts b/packages/cms/src/studio/data/data-structure.ts index 3c2c8b2b04..9a2ffa395b 100644 --- a/packages/cms/src/studio/data/data-structure.ts +++ b/packages/cms/src/studio/data/data-structure.ts @@ -261,6 +261,7 @@ export const dataStructure = { 'janssen_not_available', 'janssen_total', ], + variants_archived_20231101: ['variant_code', 'values', 'last_value'], repeating_shot_administered_20220713: ['ggd_administered_total'], corona_melder_app_warning_archived_20220421: ['count'], corona_melder_app_download_archived_20220421: ['count'], diff --git a/packages/common/src/data-sorting.ts b/packages/common/src/data-sorting.ts index 6e167d259a..7198de5eb5 100644 --- a/packages/common/src/data-sorting.ts +++ b/packages/common/src/data-sorting.ts @@ -1,5 +1,5 @@ import { isDefined } from 'ts-is-present'; -import { GmSewerPerInstallationValue, NlVariantsVariantValue } from './types'; +import { ArchivedNlVariantsVariantValue, GmSewerPerInstallationValue, NlVariantsVariantValue } from './types'; export type UnknownObject = Record; @@ -84,40 +84,49 @@ export function sortTimeSeriesInDataInPlace(data: T, { setDatesToMiddleOfDay * The variants data is structured similarly to sewer_per_installation as * shown above. @TODO unify/clean up validation of both. */ - if (isDefined((data as UnknownObject).variants)) { - const nestedSeries = (data as UnknownObject).variants as VariantsData; + if (isDefined((data as UnknownObject).variants) || isDefined((data as UnknownObject).variants_archived_20231101)) { + let nestedSeries; - if (!nestedSeries.values) { - /** - * It can happen that we get incomplete json data and assuming that values - * exists here might crash the app - */ - console.error('variants.values does not exist'); - return; + if (isDefined((data as UnknownObject).variants)) { + nestedSeries = (data as UnknownObject).variants as VariantsData; + } + if (isDefined((data as UnknownObject).variants_archived_20231101)) { + nestedSeries = (data as UnknownObject).variants_archived_20231101 as VariantsData; } - nestedSeries.values = nestedSeries.values.map((x, index) => { - if (!x.values) { + if (nestedSeries) { + if (!nestedSeries.values) { /** * It can happen that we get incomplete json data and assuming that values * exists here might crash the app */ - console.error(`variants.nestedSeries.values[${index}].values does not exist`); - return x; + console.error('variants.values does not exist'); + return; } - x.values = sortTimeSeriesValues(x.values) as NlVariantsVariantValue[]; + nestedSeries.values = nestedSeries.values.map((x, index) => { + if (!x.values) { + /** + * It can happen that we get incomplete json data and assuming that values + * exists here might crash the app + */ + console.error(`variants.nestedSeries.values[${index}].values does not exist`); + return x; + } - if (setDatesToMiddleOfDay) { - x.values = x.values.map(setValueDatesToMiddleOfDay); + x.values = sortTimeSeriesValues(x.values) as ArchivedNlVariantsVariantValue[]; - if (x.last_value) { - x.last_value = setValueDatesToMiddleOfDay(x.last_value); + if (setDatesToMiddleOfDay) { + x.values = x.values.map(setValueDatesToMiddleOfDay); + + if (x.last_value) { + x.last_value = setValueDatesToMiddleOfDay(x.last_value); + } } - } - return x; - }); + return x; + }); + } } } diff --git a/packages/common/src/types/data.ts b/packages/common/src/types/data.ts index bda448e74c..1a02f50e78 100644 --- a/packages/common/src/types/data.ts +++ b/packages/common/src/types/data.ts @@ -231,6 +231,7 @@ export interface ArchivedNl { vaccine_coverage_per_age_group_estimated_fully_vaccinated_archived_20231004: NlVaccineCoveragePerAgeGroupEstimatedFullyVaccinatedValue; vaccine_delivery_per_supplier_archived_20211101: ArchivedNlVaccineDeliveryPerSupplier; vaccine_stock_archived_20211024: ArchivedNlVaccineStock; + variants_archived_20231101: ArchivedNlVariants; repeating_shot_administered_20220713: ArchivedNlRepeatingShotAdministered; corona_melder_app_warning_archived_20220421: ArchivedNlCoronaMelderAppWarning; corona_melder_app_download_archived_20220421: ArchivedNlCoronaMelderAppDownload; @@ -833,6 +834,26 @@ export interface ArchivedNlVaccineStockValue { date_of_insertion_unix: number; date_unix: number; } +export interface ArchivedNlVariants { + values: ArchivedNlVariantsVariant[]; +} +export interface ArchivedNlVariantsVariant { + variant_code: string; + values: ArchivedNlVariantsVariantValue[]; + last_value: ArchivedNlVariantsVariantValue; +} +export interface ArchivedNlVariantsVariantValue { + order: number; + occurrence: number; + percentage: number; + sample_size: number; + date_start_unix: number; + date_end_unix: number; + date_of_insertion_unix: number; + date_of_report_unix: number; + label_nl: string; + label_en: string; +} export interface ArchivedNlRepeatingShotAdministered { values: ArchivedNlRepeatingShotAdministeredValue[]; last_value: ArchivedNlRepeatingShotAdministeredValue; @@ -1031,7 +1052,7 @@ export interface Nl { deceased_cbs: NlDeceasedCbs; vaccine_administered_last_timeframe: NlVaccineAdministeredLastTimeframe; vaccine_campaigns: NlVaccineCampaign; - variants?: NlVariants; + variants: NlVariants; self_test_overall: NlSelfTestOverall; infectionradar_symptoms_trend_per_age_group_weekly: NlInfectionradarSymptomsTrendPerAgeGroupWeekly; }