feat: update history_overview_viz configuration and validation
- Replaced the `show_area_summaries` option with individual section visibility settings (`show_section_body`, `show_section_nutrition`, `show_section_fitness`, `show_section_recovery`) in the `history_overview_viz` widget configuration. - Implemented migration logic to handle legacy `show_area_summaries` settings, ensuring backward compatibility. - Updated validation logic to enforce visibility requirements for the new section keys. - Enhanced tests to cover new configuration scenarios and validate the migration logic. - Bumped application version to reflect these changes.
This commit is contained in:
parent
97dbb0f80b
commit
725e7ffe4b
|
|
@ -145,10 +145,17 @@ _RECOVERY_HISTORY_VIZ_DEFAULTS: dict[str, Any] = {
|
|||
"show_vitals_extra_trends": False,
|
||||
}
|
||||
|
||||
_HISTORY_OVERVIEW_VIZ_SECTION_KEYS: frozenset[str] = frozenset({
|
||||
"show_section_body",
|
||||
"show_section_nutrition",
|
||||
"show_section_fitness",
|
||||
"show_section_recovery",
|
||||
})
|
||||
|
||||
_HISTORY_OVERVIEW_VIZ_BOOL_KEYS: frozenset[str] = frozenset({
|
||||
"show_confidence_banner",
|
||||
"show_intro_blurb",
|
||||
"show_area_summaries",
|
||||
*_HISTORY_OVERVIEW_VIZ_SECTION_KEYS,
|
||||
"show_correlation_c1_c3",
|
||||
"show_drivers_c4",
|
||||
})
|
||||
|
|
@ -157,7 +164,10 @@ _HISTORY_OVERVIEW_VIZ_DEFAULTS: dict[str, Any] = {
|
|||
"chart_days": 30,
|
||||
"show_confidence_banner": True,
|
||||
"show_intro_blurb": True,
|
||||
"show_area_summaries": True,
|
||||
"show_section_body": True,
|
||||
"show_section_nutrition": True,
|
||||
"show_section_fitness": True,
|
||||
"show_section_recovery": True,
|
||||
"show_correlation_c1_c3": True,
|
||||
"show_drivers_c4": True,
|
||||
}
|
||||
|
|
@ -457,36 +467,52 @@ def _validate_recovery_history_viz_config(raw: dict[str, Any]) -> dict[str, Any]
|
|||
return out
|
||||
|
||||
|
||||
def _migrate_history_overview_viz_raw(raw: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Alt: show_area_summaries → vier show_section_* (nur wo keine expliziten Section-Keys gesetzt)."""
|
||||
r = dict(raw)
|
||||
if "show_area_summaries" not in r:
|
||||
return r
|
||||
leg = r.pop("show_area_summaries")
|
||||
if not isinstance(leg, bool):
|
||||
raise ValueError("history_overview_viz: show_area_summaries muss boolean sein (veraltet — nutze show_section_*)")
|
||||
for k in _HISTORY_OVERVIEW_VIZ_SECTION_KEYS:
|
||||
if k not in r:
|
||||
r[k] = leg
|
||||
return r
|
||||
|
||||
|
||||
def _validate_history_overview_viz_config(raw: dict[str, Any]) -> dict[str, Any]:
|
||||
label = "history_overview_viz"
|
||||
raw_m = _migrate_history_overview_viz_raw(raw)
|
||||
allowed = _HISTORY_OVERVIEW_VIZ_BOOL_KEYS | frozenset({"chart_days"})
|
||||
unknown = set(raw) - allowed
|
||||
unknown = set(raw_m) - allowed
|
||||
if unknown:
|
||||
raise ValueError(f"{label}: unbekannte config-Felder: {sorted(unknown)}")
|
||||
out: dict[str, Any] = dict(_HISTORY_OVERVIEW_VIZ_DEFAULTS)
|
||||
for k in _HISTORY_OVERVIEW_VIZ_BOOL_KEYS:
|
||||
if k not in raw:
|
||||
if k not in raw_m:
|
||||
continue
|
||||
v = raw[k]
|
||||
v = raw_m[k]
|
||||
if not isinstance(v, bool):
|
||||
raise ValueError(f"{label}: {k} muss boolean sein")
|
||||
out[k] = v
|
||||
if "chart_days" in raw:
|
||||
v = _parse_chart_days(raw["chart_days"], label)
|
||||
if "chart_days" in raw_m:
|
||||
v = _parse_chart_days(raw_m["chart_days"], label)
|
||||
if v < 7 or v > 90:
|
||||
raise ValueError(f"{label}: chart_days muss zwischen 7 und 90 liegen")
|
||||
out["chart_days"] = v
|
||||
if not any(
|
||||
has_section = any(out[k] for k in _HISTORY_OVERVIEW_VIZ_SECTION_KEYS)
|
||||
has_other = any(
|
||||
out[k]
|
||||
for k in (
|
||||
"show_confidence_banner",
|
||||
"show_area_summaries",
|
||||
"show_correlation_c1_c3",
|
||||
"show_drivers_c4",
|
||||
)
|
||||
):
|
||||
)
|
||||
if not has_section and not has_other:
|
||||
raise ValueError(
|
||||
f"{label}: mindestens Datenlage-Banner, Bereichs-Kacheln, Lag-Korrelationen (C1–C3) oder Treiber (C4) muss sichtbar sein"
|
||||
f"{label}: mindestens eine Bereichs-Kachel, das Datenlage-Banner, Lag-Korrelationen (C1–C3) oder Treiber (C4) muss sichtbar sein"
|
||||
)
|
||||
return out
|
||||
|
||||
|
|
|
|||
|
|
@ -157,7 +157,10 @@ def test_history_overview_viz_empty_expands_defaults():
|
|||
d = validate_widget_entry_config("history_overview_viz", {})
|
||||
assert d["chart_days"] == 30
|
||||
assert d["show_confidence_banner"] is True
|
||||
assert d["show_area_summaries"] is True
|
||||
assert d["show_section_body"] is True
|
||||
assert d["show_section_nutrition"] is True
|
||||
assert d["show_section_fitness"] is True
|
||||
assert d["show_section_recovery"] is True
|
||||
assert d["show_correlation_c1_c3"] is True
|
||||
assert d["show_drivers_c4"] is True
|
||||
|
||||
|
|
@ -176,13 +179,26 @@ def test_history_overview_viz_requires_visible_block():
|
|||
"history_overview_viz",
|
||||
{
|
||||
"show_confidence_banner": False,
|
||||
"show_area_summaries": False,
|
||||
"show_section_body": False,
|
||||
"show_section_nutrition": False,
|
||||
"show_section_fitness": False,
|
||||
"show_section_recovery": False,
|
||||
"show_correlation_c1_c3": False,
|
||||
"show_drivers_c4": False,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def test_history_overview_viz_legacy_show_area_summaries_maps_sections():
|
||||
d = validate_widget_entry_config(
|
||||
"history_overview_viz",
|
||||
{"show_area_summaries": False, "show_correlation_c1_c3": True},
|
||||
)
|
||||
assert d["show_section_body"] is False
|
||||
assert d["show_section_fitness"] is False
|
||||
assert d["show_correlation_c1_c3"] is True
|
||||
|
||||
|
||||
def test_history_overview_viz_unknown_key():
|
||||
with pytest.raises(ValueError):
|
||||
validate_widget_entry_config("history_overview_viz", {"evil": True})
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ MODULE_VERSIONS = {
|
|||
"importdata": "1.0.0",
|
||||
"membership": "2.1.0",
|
||||
"workflow": "0.7.0", # Part 3: Inline Prompts (reference + inline mode)
|
||||
"app_dashboard": "1.17.0", # history_overview_viz Widget + chart_payloads im Overview-Bundle
|
||||
"app_dashboard": "1.17.1", # history_overview_viz: Bereichs-Kacheln einzeln per show_section_*
|
||||
"csv_import": "0.3.2", # Import-Fehler: enrich_row_error / freundlichere 500-Hinweise
|
||||
"admin_csv_templates": "0.3.0", # POST /validate + Speichern nur bei valid (422 + warnings in Response)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ WIDGET_CATALOG: list[WidgetCatalogEntry] = [
|
|||
{
|
||||
"id": "history_overview_viz",
|
||||
"title": "Verlauf — Gesamtübersicht",
|
||||
"description": "Layer-2b history-overview-viz: konsolidierte Kurzinfos (Körper/Ernährung/Fitness/Erholung) + C1–C4; chart_payloads im Bundle; chart_days 7–90; Blöcke per show_*",
|
||||
"description": "Layer-2b history-overview-viz: Kurzinfos pro Bereich (show_section_body/nutrition/fitness/recovery) + C1–C4; chart_payloads; chart_days 7–90",
|
||||
},
|
||||
{
|
||||
"id": "recovery_charts_panel",
|
||||
|
|
|
|||
|
|
@ -363,6 +363,20 @@ export default function HistoryOverviewVizSection({
|
|||
const vis =
|
||||
visibilityProp != null ? normalizeHistoryOverviewVizConfig(visibilityProp) : HISTORY_OVERVIEW_VIZ_PAGE_FULL
|
||||
|
||||
const sectionTileEnabled = (id) => {
|
||||
if (id === 'body') return vis.show_section_body
|
||||
if (id === 'nutrition') return vis.show_section_nutrition
|
||||
if (id === 'fitness') return vis.show_section_fitness
|
||||
if (id === 'recovery') return vis.show_section_recovery
|
||||
return true
|
||||
}
|
||||
const wantsAnySectionTile =
|
||||
vis.show_section_body ||
|
||||
vis.show_section_nutrition ||
|
||||
vis.show_section_fitness ||
|
||||
vis.show_section_recovery
|
||||
const visibleSections = wantsAnySectionTile ? sections.filter((s) => sectionTileEnabled(s.id)) : []
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!embedded && <SectionHeader title="📊 Gesamtansicht" />}
|
||||
|
|
@ -402,11 +416,11 @@ export default function HistoryOverviewVizSection({
|
|||
</p>
|
||||
)}
|
||||
|
||||
{vis.show_area_summaries && (sections.length === 0 ? (
|
||||
{wantsAnySectionTile && (visibleSections.length === 0 ? (
|
||||
<EmptySection text="Keine Bereichsdaten." />
|
||||
) : (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr', gap: 10 }}>
|
||||
{sections.map((sec) => {
|
||||
{visibleSections.map((sec) => {
|
||||
const tone = overviewSectionTone(sec)
|
||||
const stripe = getStatusColor(tone)
|
||||
const badgeBg = getStatusBg(tone)
|
||||
|
|
|
|||
|
|
@ -3,10 +3,16 @@ import {
|
|||
normalizeHistoryOverviewVizConfig,
|
||||
} from './historyOverviewVizConfig'
|
||||
|
||||
const TOGGLES = [
|
||||
const SECTION_TOGGLES = [
|
||||
{ key: 'show_section_body', label: 'Körper' },
|
||||
{ key: 'show_section_nutrition', label: 'Ernährung' },
|
||||
{ key: 'show_section_fitness', label: 'Fitness' },
|
||||
{ key: 'show_section_recovery', label: 'Erholung' },
|
||||
]
|
||||
|
||||
const OTHER_TOGGLES = [
|
||||
{ key: 'show_confidence_banner', label: 'Banner «Datenlage»' },
|
||||
{ key: 'show_intro_blurb', label: 'Hinweistext (Ernährung / API)' },
|
||||
{ key: 'show_area_summaries', label: 'Kacheln Körper · Ernährung · Fitness · Erholung' },
|
||||
{ key: 'show_correlation_c1_c3', label: 'Lag-Korrelationen C1–C3 (Charts)' },
|
||||
{ key: 'show_drivers_c4', label: 'Einflussfaktoren C4' },
|
||||
]
|
||||
|
|
@ -34,11 +40,20 @@ export default function HistoryOverviewVizConfigEditor({ config, onChange }) {
|
|||
return (
|
||||
<div style={{ marginTop: 10, marginLeft: 28 }}>
|
||||
<div style={{ fontSize: 12, color: 'var(--text2)', marginBottom: 8, lineHeight: 1.5 }}>
|
||||
<strong>Gesamtübersicht (Verlauf-Bundle):</strong> konsolidierte Kurzinfos und Korrelations-Kacheln — wie im Verlauf-Reiter «Gesamt».
|
||||
<strong>Gesamtübersicht (Verlauf-Bundle):</strong> welche Bereichs-Kacheln und weitere Blöcke erscheinen.
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--text2)', marginBottom: 6 }}>Bereiche</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--text2)', marginBottom: 6 }}>Bereichs-Kacheln</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginBottom: 12 }}>
|
||||
{SECTION_TOGGLES.map(({ key, label }) => (
|
||||
<label key={key} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, cursor: 'pointer' }}>
|
||||
<input type="checkbox" checked={merged[key]} onChange={(e) => setBool(key, e.target.checked)} />
|
||||
<span>{label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--text2)', marginBottom: 6 }}>Weitere Bereiche</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
{TOGGLES.map(({ key, label }) => (
|
||||
{OTHER_TOGGLES.map(({ key, label }) => (
|
||||
<label key={key} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, cursor: 'pointer' }}>
|
||||
<input type="checkbox" checked={merged[key]} onChange={(e) => setBool(key, e.target.checked)} />
|
||||
<span>{label}</span>
|
||||
|
|
|
|||
|
|
@ -3,11 +3,21 @@
|
|||
* `visibility === undefined` → Verlauf-Tab: volle Gesamtübersicht (wie bisher).
|
||||
*/
|
||||
|
||||
export const HISTORY_OVERVIEW_VIZ_SECTION_KEYS = [
|
||||
'show_section_body',
|
||||
'show_section_nutrition',
|
||||
'show_section_fitness',
|
||||
'show_section_recovery',
|
||||
]
|
||||
|
||||
export const HISTORY_OVERVIEW_VIZ_PAGE_FULL = {
|
||||
chart_days: 30,
|
||||
show_confidence_banner: true,
|
||||
show_intro_blurb: true,
|
||||
show_area_summaries: true,
|
||||
show_section_body: true,
|
||||
show_section_nutrition: true,
|
||||
show_section_fitness: true,
|
||||
show_section_recovery: true,
|
||||
show_correlation_c1_c3: true,
|
||||
show_drivers_c4: true,
|
||||
}
|
||||
|
|
@ -16,7 +26,10 @@ export const HISTORY_OVERVIEW_VIZ_WIDGET_DEFAULTS = {
|
|||
chart_days: 30,
|
||||
show_confidence_banner: true,
|
||||
show_intro_blurb: true,
|
||||
show_area_summaries: true,
|
||||
show_section_body: true,
|
||||
show_section_nutrition: true,
|
||||
show_section_fitness: true,
|
||||
show_section_recovery: true,
|
||||
show_correlation_c1_c3: true,
|
||||
show_drivers_c4: true,
|
||||
}
|
||||
|
|
@ -24,17 +37,29 @@ export const HISTORY_OVERVIEW_VIZ_WIDGET_DEFAULTS = {
|
|||
const BOOL_KEYS = [
|
||||
'show_confidence_banner',
|
||||
'show_intro_blurb',
|
||||
'show_area_summaries',
|
||||
...HISTORY_OVERVIEW_VIZ_SECTION_KEYS,
|
||||
'show_correlation_c1_c3',
|
||||
'show_drivers_c4',
|
||||
]
|
||||
|
||||
function hasExplicitSectionKeys(raw) {
|
||||
return HISTORY_OVERVIEW_VIZ_SECTION_KEYS.some((k) => Object.prototype.hasOwnProperty.call(raw, k))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>|null|undefined} raw
|
||||
*/
|
||||
export function normalizeHistoryOverviewVizConfig(raw) {
|
||||
const base = { ...HISTORY_OVERVIEW_VIZ_WIDGET_DEFAULTS }
|
||||
if (!raw || typeof raw !== 'object') return base
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(raw, 'show_area_summaries') && !hasExplicitSectionKeys(raw)) {
|
||||
const v = raw.show_area_summaries === true
|
||||
for (const k of HISTORY_OVERVIEW_VIZ_SECTION_KEYS) {
|
||||
base[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
for (const k of BOOL_KEYS) {
|
||||
if (Object.prototype.hasOwnProperty.call(raw, k)) {
|
||||
base[k] = raw[k] === true
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user