diff --git a/CHANGELOG.md b/CHANGELOG.md index ed109df2..6dda6548 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ and this project adheres to - 📝(docs) improve README and add documentation hub #1870 - ♿️(frontend) restore focus to triggers after closing menus and modals #1863 +### Fixed + +- 🐛(frontend) analytic feature flags problem #1953 + ## [v4.6.0] - 2026-03-03 ### Added diff --git a/src/frontend/apps/impress/src/libs/Analytics.tsx b/src/frontend/apps/impress/src/libs/Analytics.tsx index 02c996a3..972b24b0 100644 --- a/src/frontend/apps/impress/src/libs/Analytics.tsx +++ b/src/frontend/apps/impress/src/libs/Analytics.tsx @@ -56,16 +56,18 @@ export class Analytics { /** * Check if a feature flag is activated * - * Feature flags are activated if at least one analytic is activated - * because we don't want to hide feature if the user does not - * use analytics (AB testing, etc) + * A feature flag is considered active only if ALL analytics agree it is. + * This ensures that if one analytic explicitly disables a flag, + * it takes precedence over analytics that do not manage flags. + * If no analytics are registered, default to true so features are not hidden + * when analytics are not configured. */ public static isFeatureFlagActivated(flagName: string): boolean { if (!Analytics.analytics.length) { return true; } - return Analytics.analytics.some((analytic) => + return Analytics.analytics.every((analytic) => analytic.isFeatureFlagActivated(flagName), ); } diff --git a/src/frontend/apps/impress/src/libs/__tests__/Analytics.spec.tsx b/src/frontend/apps/impress/src/libs/__tests__/Analytics.spec.tsx new file mode 100644 index 00000000..b0677fb2 --- /dev/null +++ b/src/frontend/apps/impress/src/libs/__tests__/Analytics.spec.tsx @@ -0,0 +1,95 @@ +import { render, screen } from '@testing-library/react'; +import React, { Fragment } from 'react'; + +import { AbstractAnalytic, Analytics, useAnalytics } from '@/libs'; +import { AppWrapper } from '@/tests/utils'; + +class TestAnalytic1 extends AbstractAnalytic { + public constructor() { + super(); + } + + public Provider() { + return ; + } + + public trackEvent() {} + + public isFeatureFlagActivated(flagName: string): boolean { + if (flagName === 'test-flag') { + return false; + } + + if (flagName === 'test-flag2') { + return true; + } + + return true; + } +} + +class TestAnalytic2 extends AbstractAnalytic { + public constructor() { + super(); + } + + public Provider() { + return ; + } + + public trackEvent() {} + + public isFeatureFlagActivated(): boolean { + return true; + } +} + +const TestComponent = ({ flag }: { flag: string }) => { + const { isFeatureFlagActivated } = useAnalytics(); + + return ( +
+ {isFeatureFlagActivated(flag) ? ( + Feature is enabled + ) : ( + Feature is not enabled + )} +
+ ); +}; + +describe('Analytics feature flag', () => { + beforeEach(() => { + Analytics.clearAnalytics(); + }); + + test('renders feature when feature flag is not existing', async () => { + new TestAnalytic1(); + new TestAnalytic2(); + + render(, { + wrapper: AppWrapper, + }); + expect(await screen.findByText('Feature is enabled')).toBeInTheDocument(); + }); + + test('renders feature when feature flag is not enabled', async () => { + new TestAnalytic1(); + new TestAnalytic2(); + + render(, { + wrapper: AppWrapper, + }); + expect(screen.getByText('Feature is not enabled')).toBeInTheDocument(); + }); + + test('renders feature when feature flag is enabled', async () => { + new TestAnalytic1(); + new TestAnalytic2(); + + render(, { + wrapper: AppWrapper, + }); + expect(screen.getByText('Feature is enabled')).toBeInTheDocument(); + }); +});