From 77986307276620f139574ca2c39a9a219de4abef Mon Sep 17 00:00:00 2001 From: Chris Lei Date: Tue, 23 Jun 2026 13:39:38 -0700 Subject: [PATCH 1/4] Define enableAuditLogs feature flag We can configure LaunchDarkly to target this flag based on workspace context. --- src/shared/hooks/useFeatureFlags.const.ts | 1 + src/shared/types/featureFlags.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/shared/hooks/useFeatureFlags.const.ts b/src/shared/hooks/useFeatureFlags.const.ts index ca2ec7e012..f5b9d3752f 100644 --- a/src/shared/hooks/useFeatureFlags.const.ts +++ b/src/shared/hooks/useFeatureFlags.const.ts @@ -22,6 +22,7 @@ export const FeatureFlagDefaults: FeatureFlags = { enableEmaExtraFiles: false, enableMfa: true, enableAdminAnnouncementBanner: false, + enableAuditLogs: false, }; export const PROHIBITED_PII_KEYS = ['firstName', 'lastName', 'email']; diff --git a/src/shared/types/featureFlags.ts b/src/shared/types/featureFlags.ts index 69d7056c2b..b1c1159f34 100644 --- a/src/shared/types/featureFlags.ts +++ b/src/shared/types/featureFlags.ts @@ -25,4 +25,5 @@ export type FeatureFlags = Partial<{ enableEmaExtraFiles: boolean; enableMfa: boolean; enableAdminAnnouncementBanner: boolean; + enableAuditLogs: boolean; }>; From 689c46e4216f6de652410e866be512fafe4f7e9f Mon Sep 17 00:00:00 2001 From: Chris Lei Date: Tue, 23 Jun 2026 14:11:24 -0700 Subject: [PATCH 2/4] Gate audit logs export behind enableAuditLogs --- .../HeaderOptions/HeaderOptions.tsx | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/src/modules/Dashboard/components/HeaderOptions/HeaderOptions.tsx b/src/modules/Dashboard/components/HeaderOptions/HeaderOptions.tsx index 001da65e13..50c6f8a7ce 100644 --- a/src/modules/Dashboard/components/HeaderOptions/HeaderOptions.tsx +++ b/src/modules/Dashboard/components/HeaderOptions/HeaderOptions.tsx @@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'; import { page } from 'resources'; import { Menu, Svg } from 'shared/components'; +import { useFeatureFlags } from 'shared/hooks'; import { ExportDataSetting } from 'shared/features/AppletSettings'; import { StyledFlexTopCenter, variables } from 'shared/styles'; import { @@ -23,6 +24,7 @@ export const HeaderOptions = () => { const [isAuditLogsExportOpen, setIsAuditLogsExportOpen] = useState(false); const { t } = useTranslation('app'); + const { featureFlags } = useFeatureFlags(); const { appletId } = useParams(); const isSettingsSelected = location.pathname.includes('settings'); const workspaceRoles = workspaces.useRolesData(); @@ -65,20 +67,27 @@ export const HeaderOptions = () => { const canAccessData = checkIfCanAccessData(roles); const canEdit = checkIfCanEdit(roles); - const getExportActions = () => [ - { - icon: , - action: handleOpenResponseData, - title: t('dataExport.responseData.menuCaption'), - 'data-testid': 'header-option-response-data-button', - }, - { - icon: , - action: handleOpenAuditLogs, - title: t('dataExport.auditLogs.menuCaption'), - 'data-testid': 'header-option-audit-logs-button', - }, - ]; + const getExportActions = () => { + const actions = [ + { + icon: , + action: handleOpenResponseData, + title: t('dataExport.responseData.menuCaption'), + 'data-testid': 'header-option-response-data-button', + }, + ]; + + if (featureFlags.enableAuditLogs) { + actions.push({ + icon: , + action: handleOpenAuditLogs, + title: t('dataExport.auditLogs.menuCaption'), + 'data-testid': 'header-option-audit-logs-button', + }); + } + + return actions; + }; return canEdit || canAccessData ? ( @@ -117,12 +126,14 @@ export const HeaderOptions = () => { onExportSettingsClose={handleCloseResponseData} data-testid={'response-data-export'} /> - setIsAuditLogsExportOpen(false)} - /> + {featureFlags.enableAuditLogs && ( + setIsAuditLogsExportOpen(false)} + /> + )} ) : null; }; From 65a1fac98d6c1fcf36177f448d39efa9272626f1 Mon Sep 17 00:00:00 2001 From: Chris Lei Date: Wed, 24 Jun 2026 09:34:34 -0700 Subject: [PATCH 3/4] Test enableAuditLogs --- .../HeaderOptions/HeaderOptions.test.tsx | 24 ++++++++++++++++++- src/setupTests.ts | 1 + 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/modules/Dashboard/components/HeaderOptions/HeaderOptions.test.tsx b/src/modules/Dashboard/components/HeaderOptions/HeaderOptions.test.tsx index 612fdfba91..f417a07039 100644 --- a/src/modules/Dashboard/components/HeaderOptions/HeaderOptions.test.tsx +++ b/src/modules/Dashboard/components/HeaderOptions/HeaderOptions.test.tsx @@ -4,6 +4,7 @@ import { vi } from 'vitest'; import { renderWithProviders } from 'shared/utils/renderWithProviders'; import { mockedApplet, mockedCurrentWorkspace } from 'shared/mock'; import { Roles } from 'shared/consts'; +import { useFeatureFlags } from 'shared/hooks'; import { initialStateData } from 'shared/state'; import { Mixpanel, MixpanelEventType, MixpanelProps } from 'shared/utils'; @@ -91,7 +92,8 @@ describe('HeaderOptions', () => { ); }); }); -describe('should show or hide header buttons depending on role', () => { + +describe('HeaderOptions show or hide depending on role', () => { test.each` canEdit | canAccessData | role | description ${true} | ${true} | ${Roles.Manager} | ${'header for Manager'} @@ -117,3 +119,23 @@ describe('should show or hide header buttons depending on role', () => { : expect(exportButtons.length).toBe(0); }); }); + +describe('HeaderOptions show or hide depending on feature flag', () => { + beforeEach(() => { + vi.mocked(useFeatureFlags).mockReturnValue({ + featureFlags: { enableAuditLogs: false }, + resetLDContext: vi.fn(), + }); + renderWithProviders(, { preloadedState: getPreloadedState() }); + }); + + afterEach(() => { + vi.mocked(useFeatureFlags).mockReset(); + }); + + test('header for enableAuditLogs off', () => { + fireEvent.click(screen.getByTestId('header-option-export-button')); + expect(screen.queryByTestId('header-option-response-data-button')).toBeInTheDocument(); + expect(screen.queryByTestId('header-option-audit-logs-button')).not.toBeInTheDocument(); + }); +}); diff --git a/src/setupTests.ts b/src/setupTests.ts index 515d18d493..1cb443af9d 100644 --- a/src/setupTests.ts +++ b/src/setupTests.ts @@ -81,6 +81,7 @@ vi.mock('shared/hooks/useFeatureFlags', () => ({ enableParticipantMultiInformant: true, enableMfa: false, enableAdminAnnouncementBanner: false, + enableAuditLogs: true, }, resetLDContext: vi.fn(), })), From 9bddcaca5822ba2edded546d8168435e212a7103 Mon Sep 17 00:00:00 2001 From: Chris Lei Date: Wed, 24 Jun 2026 10:27:03 -0700 Subject: [PATCH 4/4] Cap Vitest forks to avoid timeout on heavy tests --- vitest.config.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/vitest.config.ts b/vitest.config.ts index 62be1c0453..0fc2589ed4 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,3 +1,4 @@ +import { availableParallelism } from 'os'; import { defineConfig } from 'vitest/config'; import react from '@vitejs/plugin-react'; import { resolve } from 'path'; @@ -15,6 +16,13 @@ export default defineConfig({ environment: 'jsdom', setupFiles: ['./src/setupTests.ts', 'dotenv/config'], globalSetup: './src/vitest.global-setup.ts', + poolOptions: { + forks: { + // cap forks below core count to avoid timeout from resource overload on heavy tests + maxForks: Math.ceil(availableParallelism() / 2), + minForks: 1, + }, + }, coverage: { reporter: ['text', 'json', 'html'], exclude: ['node_modules/', 'src/setupTests.ts'],