From 6c2d8a4d4f8251b668ee583fcf61c5f9a0b2cffe Mon Sep 17 00:00:00 2001 From: Dataguru-tech Date: Sun, 28 Jun 2026 07:45:33 +0100 Subject: [PATCH 1/2] feat: add health check auto-disable with feature flag integration - Define HEALTH_TO_FEATURE_MAP in src/config/degradationConfig.ts - Update setSnapshot() to apply degradation flags after each health cycle - Add selectIsServiceDegraded selector for fallback UI - Add full test suite: degradation, fallback, recovery, admin override --- src/config/degradationConfig.ts | 19 ++++ src/store/healthDashboardStore.ts | 116 ++++++++++++----------- tests/store/healthDashboardStore.test.ts | 113 ++++++++++++++++++++++ 3 files changed, 191 insertions(+), 57 deletions(-) create mode 100644 src/config/degradationConfig.ts create mode 100644 tests/store/healthDashboardStore.test.ts diff --git a/src/config/degradationConfig.ts b/src/config/degradationConfig.ts new file mode 100644 index 00000000..1c77180b --- /dev/null +++ b/src/config/degradationConfig.ts @@ -0,0 +1,19 @@ +export type ServiceName = 'streaming' | 'payment'; +export type FeatureFlagKey = 'streaming_courses' | 'payment_form'; + +export interface FeatureFlagEntry { + flagKey: FeatureFlagKey; + adminOverride?: boolean; +} + +export const HEALTH_TO_FEATURE_MAP: Record = { + streaming: [{ flagKey: 'streaming_courses', adminOverride: false }], + payment: [{ flagKey: 'payment_form', adminOverride: false }], +}; + +export const DEGRADED_STATUSES = ['degraded', 'down', 'error', 'unhealthy'] as const; +export type DegradedStatus = (typeof DEGRADED_STATUSES)[number]; + +export function isServiceDegraded(status: string): boolean { + return (DEGRADED_STATUSES as readonly string[]).includes(status); +} diff --git a/src/store/healthDashboardStore.ts b/src/store/healthDashboardStore.ts index fe400d14..05b87baa 100644 --- a/src/store/healthDashboardStore.ts +++ b/src/store/healthDashboardStore.ts @@ -1,11 +1,4 @@ -/** - * healthDashboardStore — Zustand store for the real-time health dashboard. - * - * Holds the latest health snapshot, active alerts, user-configured thresholds, - * and polling state. Intentionally NOT persisted — always starts fresh. - */ - -import { create } from 'zustand'; +import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; import { @@ -15,33 +8,29 @@ import { MetricAlert, } from '../services/healthMetrics'; import { shallowDiff } from '../utils/stateDiff'; - -// ─── Types ──────────────────────────────────────────────────────────────────── +import { isServiceDegraded, HEALTH_TO_FEATURE_MAP, ServiceName } from '../config/degradationConfig'; +import { useFeatureFlagStore } from './featureFlagStore'; export type DashboardStatus = 'idle' | 'polling' | 'error'; +export interface ServiceHealthStatus { + service: ServiceName; + status: string; +} + interface HealthDashboardState { - // Data snapshot: HealthSnapshot | null; alerts: MetricAlert[]; thresholds: AlertThresholds; - - // UI state + serviceHealthStatuses: ServiceHealthStatus[]; status: DashboardStatus; lastUpdated: number | null; - /** Timestamp of last successful data read (cache or network) */ lastChecked: number | null; - /** Timestamp of last actual network API call */ lastNetworkCheck: number | null; - /** How often to refresh in ms */ refreshIntervalMs: number; - /** Whether auto-refresh is active */ isAutoRefresh: boolean; - /** Dismissed alert IDs (cleared on next full refresh) */ dismissedAlertIds: Set; - - // Actions - setSnapshot: (snapshot: HealthSnapshot) => void; + setSnapshot: (snapshot: HealthSnapshot, serviceStatuses?: ServiceHealthStatus[]) => void; setAlerts: (alerts: MetricAlert[]) => void; setThresholds: (thresholds: Partial) => void; setStatus: (status: DashboardStatus) => void; @@ -54,38 +43,65 @@ interface HealthDashboardState { reset: () => void; } -// ─── Initial state ──────────────────────────────────────────────────────────── - const initialState = { snapshot: null, alerts: [], thresholds: DEFAULT_THRESHOLDS, + serviceHealthStatuses: [] as ServiceHealthStatus[], status: 'idle' as DashboardStatus, lastUpdated: null, lastChecked: null, lastNetworkCheck: null, - refreshIntervalMs: 10_000, // 10 seconds default + refreshIntervalMs: 10_000, isAutoRefresh: true, dismissedAlertIds: new Set(), }; -// ─── Store ──────────────────────────────────────────────────────────────────── +function applyDegradationFlags(serviceStatuses: ServiceHealthStatus[]): void { + const flagStore = useFeatureFlagStore.getState(); + for (const { service, status } of serviceStatuses) { + const flagEntries = HEALTH_TO_FEATURE_MAP[service]; + if (!flagEntries) continue; + const degraded = isServiceDegraded(status); + for (const entry of flagEntries) { + if (entry.adminOverride) continue; + const currentDef = flagStore.getDefinition(entry.flagKey); + const updatedDef = { ...(currentDef ?? {}), enabled: !degraded }; + useFeatureFlagStore.setState(state => ({ + flags: { + ...state.flags, + flags: { ...state.flags.flags, [entry.flagKey]: updatedDef }, + }, + })); + } + } +} export const useHealthDashboardStore = create()( devtools( set => ({ ...initialState, - setSnapshot: snapshot => + setSnapshot: (snapshot, serviceStatuses) => set( state => { if (!state.snapshot && !snapshot) return state; - if (!state.snapshot) return { snapshot, lastUpdated: Date.now() }; - const diff = shallowDiff(state.snapshot, snapshot); - if (!diff) return state; + let nextSnapshot: HealthSnapshot; + if (!state.snapshot) { + nextSnapshot = snapshot; + } else { + const diff = shallowDiff(state.snapshot, snapshot); + if (!diff) { + if (serviceStatuses && serviceStatuses.length > 0) applyDegradationFlags(serviceStatuses); + return state; + } + nextSnapshot = { ...state.snapshot, ...diff } as HealthSnapshot; + } + if (serviceStatuses && serviceStatuses.length > 0) applyDegradationFlags(serviceStatuses); return { - snapshot: { ...state.snapshot, ...diff } as HealthSnapshot, + snapshot: nextSnapshot, lastUpdated: Date.now(), + serviceHealthStatuses: serviceStatuses ?? state.serviceHealthStatuses, }; }, false, @@ -98,7 +114,7 @@ export const useHealthDashboardStore = create()( set( state => { const diff = shallowDiff(state.thresholds, partial); - if (!diff) return state; // Return unchanged state to bypass allocation + if (!diff) return state; return { thresholds: { ...state.thresholds, ...diff } }; }, false, @@ -106,51 +122,37 @@ export const useHealthDashboardStore = create()( ), setStatus: status => set({ status }, false, 'setStatus'), - setLastChecked: () => set({ lastChecked: Date.now() }, false, 'setLastChecked'), - - setLastNetworkCheck: () => - set( - { lastNetworkCheck: Date.now(), lastChecked: Date.now() }, - false, - 'setLastNetworkCheck' - ), - - setRefreshInterval: refreshIntervalMs => - set({ refreshIntervalMs }, false, 'setRefreshInterval'), - - toggleAutoRefresh: () => - set(state => ({ isAutoRefresh: !state.isAutoRefresh }), false, 'toggleAutoRefresh'), - + setLastNetworkCheck: () => set({ lastNetworkCheck: Date.now(), lastChecked: Date.now() }, false, 'setLastNetworkCheck'), + setRefreshInterval: refreshIntervalMs => set({ refreshIntervalMs }, false, 'setRefreshInterval'), + toggleAutoRefresh: () => set(state => ({ isAutoRefresh: !state.isAutoRefresh }), false, 'toggleAutoRefresh'), dismissAlert: id => set( - state => { - const next = new Set(state.dismissedAlertIds); - next.add(id); - return { dismissedAlertIds: next }; - }, + state => { const next = new Set(state.dismissedAlertIds); next.add(id); return { dismissedAlertIds: next }; }, false, 'dismissAlert' ), - clearDismissed: () => set({ dismissedAlertIds: new Set() }, false, 'clearDismissed'), - reset: () => set({ ...initialState, dismissedAlertIds: new Set() }, false, 'reset'), }), { name: 'HealthDashboardStore' } ) ); -// ─── Selectors ──────────────────────────────────────────────────────────────── - -/** Returns only non-dismissed alerts */ export const selectVisibleAlerts = (state: HealthDashboardState): MetricAlert[] => state.alerts.filter(a => !state.dismissedAlertIds.has(a.id)); -/** Returns the highest severity across all visible alerts */ export const selectOverallStatus = (state: HealthDashboardState): 'ok' | 'warning' | 'critical' => { const visible = selectVisibleAlerts(state); if (visible.some(a => a.severity === 'critical')) return 'critical'; if (visible.some(a => a.severity === 'warning')) return 'warning'; return 'ok'; }; + +export const selectIsServiceDegraded = + (service: ServiceName) => + (state: HealthDashboardState): boolean => { + const entry = state.serviceHealthStatuses.find(s => s.service === service); + if (!entry) return false; + return isServiceDegraded(entry.status); + }; diff --git a/tests/store/healthDashboardStore.test.ts b/tests/store/healthDashboardStore.test.ts new file mode 100644 index 00000000..1c16d364 --- /dev/null +++ b/tests/store/healthDashboardStore.test.ts @@ -0,0 +1,113 @@ +import { beforeEach, describe, expect, it } from '@jest/globals'; +import { useHealthDashboardStore, selectIsServiceDegraded } from '../../src/store/healthDashboardStore'; +import { useFeatureFlagStore } from '../../src/store/featureFlagStore'; +import { HEALTH_TO_FEATURE_MAP } from '../../src/config/degradationConfig'; + +function makeSnapshot(overrides = {}) { + return { + capturedAt: Date.now(), + crashCount: 0, errorCount: 0, crashRate: 0, errorRatePerMinute: 0, + apiLatencyP50: 50, apiLatencyP95: 100, apiLatencyP99: 200, + apiCallCount: 10, apiErrorCount: 0, apiErrorRate: 0, + activeSessions: 5, totalSessionsInWindow: 5, + fps: 60, jsBusyRatio: 0.1, isOnline: true, networkType: 'wifi', + ...overrides, + }; +} + +beforeEach(() => { + useHealthDashboardStore.getState().reset(); + useFeatureFlagStore.setState(state => ({ + flags: { + ...state.flags, + flags: { ...state.flags.flags, streaming_courses: { enabled: true }, payment_form: { enabled: true } }, + }, + })); +}); + +describe('Health-check to Feature Flag auto-disable', () => { + + describe('Degradation', () => { + it('disables streaming_courses when streaming is degraded', () => { + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [{ service: 'streaming', status: 'degraded' }]); + expect(useFeatureFlagStore.getState().isEnabled('streaming_courses', true)).toBe(false); + }); + + it('disables streaming_courses when streaming status is down', () => { + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [{ service: 'streaming', status: 'down' }]); + expect(useFeatureFlagStore.getState().isEnabled('streaming_courses', true)).toBe(false); + }); + + it('disables payment_form when payment is degraded', () => { + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [{ service: 'payment', status: 'degraded' }]); + expect(useFeatureFlagStore.getState().isEnabled('payment_form', true)).toBe(false); + }); + }); + + describe('Fallback UI state', () => { + it('selectIsServiceDegraded returns true for a degraded service', () => { + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [{ service: 'streaming', status: 'degraded' }]); + expect(selectIsServiceDegraded('streaming')(useHealthDashboardStore.getState())).toBe(true); + }); + + it('selectIsServiceDegraded returns false for a healthy service', () => { + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [{ service: 'streaming', status: 'healthy' }]); + expect(selectIsServiceDegraded('streaming')(useHealthDashboardStore.getState())).toBe(false); + }); + + it('selectIsServiceDegraded returns false when no status recorded yet', () => { + expect(selectIsServiceDegraded('streaming')(useHealthDashboardStore.getState())).toBe(false); + }); + }); + + describe('Recovery', () => { + it('re-enables streaming_courses after recovery', () => { + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [{ service: 'streaming', status: 'degraded' }]); + expect(useFeatureFlagStore.getState().isEnabled('streaming_courses', true)).toBe(false); + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [{ service: 'streaming', status: 'healthy' }]); + expect(useFeatureFlagStore.getState().isEnabled('streaming_courses', true)).toBe(true); + }); + + it('re-enables payment_form after payment service recovers', () => { + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [{ service: 'payment', status: 'down' }]); + expect(useFeatureFlagStore.getState().isEnabled('payment_form', true)).toBe(false); + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [{ service: 'payment', status: 'healthy' }]); + expect(useFeatureFlagStore.getState().isEnabled('payment_form', true)).toBe(true); + }); + + it('only re-enables the recovered service, not unrelated flags', () => { + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [ + { service: 'streaming', status: 'degraded' }, + { service: 'payment', status: 'degraded' }, + ]); + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [ + { service: 'streaming', status: 'healthy' }, + { service: 'payment', status: 'degraded' }, + ]); + expect(useFeatureFlagStore.getState().isEnabled('streaming_courses', true)).toBe(true); + expect(useFeatureFlagStore.getState().isEnabled('payment_form', true)).toBe(false); + }); + }); + + describe('Admin override', () => { + it('does NOT disable the flag when adminOverride is true', () => { + const original = HEALTH_TO_FEATURE_MAP.streaming[0].adminOverride; + HEALTH_TO_FEATURE_MAP.streaming[0].adminOverride = true; + try { + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [{ service: 'streaming', status: 'degraded' }]); + expect(useFeatureFlagStore.getState().isEnabled('streaming_courses', true)).toBe(true); + } finally { + HEALTH_TO_FEATURE_MAP.streaming[0].adminOverride = original; + } + }); + }); + + describe('Backward compatibility', () => { + it('does not crash when setSnapshot is called without serviceStatuses', () => { + useHealthDashboardStore.getState().setSnapshot(makeSnapshot()); + expect(useFeatureFlagStore.getState().isEnabled('streaming_courses', true)).toBe(true); + expect(useFeatureFlagStore.getState().isEnabled('payment_form', true)).toBe(true); + }); + }); + +}); From ef00f90914b905c6f35d18ab8c09213b076f3781 Mon Sep 17 00:00:00 2001 From: Dataguru-tech Date: Sun, 28 Jun 2026 07:50:26 +0100 Subject: [PATCH 2/2] feat: add health check auto-disable with feature flag integration - Define HEALTH_TO_FEATURE_MAP in src/config/degradationConfig.ts - Update setSnapshot() to apply degradation flags after each health cycle - Add selectIsServiceDegraded selector for fallback UI - Add full test suite: degradation, fallback, recovery, admin override --- src/store/healthDashboardStore.ts | 38 ++++++++++++--- tests/store/healthDashboardStore.test.ts | 60 ++++++++++++++++++------ 2 files changed, 77 insertions(+), 21 deletions(-) diff --git a/src/store/healthDashboardStore.ts b/src/store/healthDashboardStore.ts index 05b87baa..f94a27bd 100644 --- a/src/store/healthDashboardStore.ts +++ b/src/store/healthDashboardStore.ts @@ -92,12 +92,16 @@ export const useHealthDashboardStore = create()( } else { const diff = shallowDiff(state.snapshot, snapshot); if (!diff) { - if (serviceStatuses && serviceStatuses.length > 0) applyDegradationFlags(serviceStatuses); + if (serviceStatuses && serviceStatuses.length > 0) { + applyDegradationFlags(serviceStatuses); + } return state; } nextSnapshot = { ...state.snapshot, ...diff } as HealthSnapshot; } - if (serviceStatuses && serviceStatuses.length > 0) applyDegradationFlags(serviceStatuses); + if (serviceStatuses && serviceStatuses.length > 0) { + applyDegradationFlags(serviceStatuses); + } return { snapshot: nextSnapshot, lastUpdated: Date.now(), @@ -122,17 +126,35 @@ export const useHealthDashboardStore = create()( ), setStatus: status => set({ status }, false, 'setStatus'), + setLastChecked: () => set({ lastChecked: Date.now() }, false, 'setLastChecked'), - setLastNetworkCheck: () => set({ lastNetworkCheck: Date.now(), lastChecked: Date.now() }, false, 'setLastNetworkCheck'), - setRefreshInterval: refreshIntervalMs => set({ refreshIntervalMs }, false, 'setRefreshInterval'), - toggleAutoRefresh: () => set(state => ({ isAutoRefresh: !state.isAutoRefresh }), false, 'toggleAutoRefresh'), + + setLastNetworkCheck: () => + set( + { lastNetworkCheck: Date.now(), lastChecked: Date.now() }, + false, + 'setLastNetworkCheck' + ), + + setRefreshInterval: refreshIntervalMs => + set({ refreshIntervalMs }, false, 'setRefreshInterval'), + + toggleAutoRefresh: () => + set(state => ({ isAutoRefresh: !state.isAutoRefresh }), false, 'toggleAutoRefresh'), + dismissAlert: id => set( - state => { const next = new Set(state.dismissedAlertIds); next.add(id); return { dismissedAlertIds: next }; }, + state => { + const next = new Set(state.dismissedAlertIds); + next.add(id); + return { dismissedAlertIds: next }; + }, false, 'dismissAlert' ), + clearDismissed: () => set({ dismissedAlertIds: new Set() }, false, 'clearDismissed'), + reset: () => set({ ...initialState, dismissedAlertIds: new Set() }, false, 'reset'), }), { name: 'HealthDashboardStore' } @@ -142,7 +164,9 @@ export const useHealthDashboardStore = create()( export const selectVisibleAlerts = (state: HealthDashboardState): MetricAlert[] => state.alerts.filter(a => !state.dismissedAlertIds.has(a.id)); -export const selectOverallStatus = (state: HealthDashboardState): 'ok' | 'warning' | 'critical' => { +export const selectOverallStatus = ( + state: HealthDashboardState +): 'ok' | 'warning' | 'critical' => { const visible = selectVisibleAlerts(state); if (visible.some(a => a.severity === 'critical')) return 'critical'; if (visible.some(a => a.severity === 'warning')) return 'warning'; diff --git a/tests/store/healthDashboardStore.test.ts b/tests/store/healthDashboardStore.test.ts index 1c16d364..a26d3649 100644 --- a/tests/store/healthDashboardStore.test.ts +++ b/tests/store/healthDashboardStore.test.ts @@ -20,7 +20,11 @@ beforeEach(() => { useFeatureFlagStore.setState(state => ({ flags: { ...state.flags, - flags: { ...state.flags.flags, streaming_courses: { enabled: true }, payment_form: { enabled: true } }, + flags: { + ...state.flags.flags, + streaming_courses: { enabled: true }, + payment_form: { enabled: true }, + }, }, })); }); @@ -29,49 +33,75 @@ describe('Health-check to Feature Flag auto-disable', () => { describe('Degradation', () => { it('disables streaming_courses when streaming is degraded', () => { - useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [{ service: 'streaming', status: 'degraded' }]); + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [ + { service: 'streaming', status: 'degraded' }, + ]); expect(useFeatureFlagStore.getState().isEnabled('streaming_courses', true)).toBe(false); }); it('disables streaming_courses when streaming status is down', () => { - useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [{ service: 'streaming', status: 'down' }]); + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [ + { service: 'streaming', status: 'down' }, + ]); expect(useFeatureFlagStore.getState().isEnabled('streaming_courses', true)).toBe(false); }); it('disables payment_form when payment is degraded', () => { - useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [{ service: 'payment', status: 'degraded' }]); + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [ + { service: 'payment', status: 'degraded' }, + ]); expect(useFeatureFlagStore.getState().isEnabled('payment_form', true)).toBe(false); }); }); describe('Fallback UI state', () => { it('selectIsServiceDegraded returns true for a degraded service', () => { - useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [{ service: 'streaming', status: 'degraded' }]); - expect(selectIsServiceDegraded('streaming')(useHealthDashboardStore.getState())).toBe(true); + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [ + { service: 'streaming', status: 'degraded' }, + ]); + expect( + selectIsServiceDegraded('streaming')(useHealthDashboardStore.getState()) + ).toBe(true); }); it('selectIsServiceDegraded returns false for a healthy service', () => { - useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [{ service: 'streaming', status: 'healthy' }]); - expect(selectIsServiceDegraded('streaming')(useHealthDashboardStore.getState())).toBe(false); + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [ + { service: 'streaming', status: 'healthy' }, + ]); + expect( + selectIsServiceDegraded('streaming')(useHealthDashboardStore.getState()) + ).toBe(false); }); it('selectIsServiceDegraded returns false when no status recorded yet', () => { - expect(selectIsServiceDegraded('streaming')(useHealthDashboardStore.getState())).toBe(false); + expect( + selectIsServiceDegraded('streaming')(useHealthDashboardStore.getState()) + ).toBe(false); }); }); describe('Recovery', () => { it('re-enables streaming_courses after recovery', () => { - useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [{ service: 'streaming', status: 'degraded' }]); + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [ + { service: 'streaming', status: 'degraded' }, + ]); expect(useFeatureFlagStore.getState().isEnabled('streaming_courses', true)).toBe(false); - useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [{ service: 'streaming', status: 'healthy' }]); + + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [ + { service: 'streaming', status: 'healthy' }, + ]); expect(useFeatureFlagStore.getState().isEnabled('streaming_courses', true)).toBe(true); }); it('re-enables payment_form after payment service recovers', () => { - useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [{ service: 'payment', status: 'down' }]); + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [ + { service: 'payment', status: 'down' }, + ]); expect(useFeatureFlagStore.getState().isEnabled('payment_form', true)).toBe(false); - useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [{ service: 'payment', status: 'healthy' }]); + + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [ + { service: 'payment', status: 'healthy' }, + ]); expect(useFeatureFlagStore.getState().isEnabled('payment_form', true)).toBe(true); }); @@ -94,7 +124,9 @@ describe('Health-check to Feature Flag auto-disable', () => { const original = HEALTH_TO_FEATURE_MAP.streaming[0].adminOverride; HEALTH_TO_FEATURE_MAP.streaming[0].adminOverride = true; try { - useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [{ service: 'streaming', status: 'degraded' }]); + useHealthDashboardStore.getState().setSnapshot(makeSnapshot(), [ + { service: 'streaming', status: 'degraded' }, + ]); expect(useFeatureFlagStore.getState().isEnabled('streaming_courses', true)).toBe(true); } finally { HEALTH_TO_FEATURE_MAP.streaming[0].adminOverride = original;