diff --git a/apps/smart-forms-app/public/config.json b/apps/smart-forms-app/public/config.json index 6e664f82a..6c063a838 100644 --- a/apps/smart-forms-app/public/config.json +++ b/apps/smart-forms-app/public/config.json @@ -3,5 +3,6 @@ "formsServerUrl": "https://smartforms.csiro.au/api/fhir", "defaultClientId": "a57d90e3-5f69-4b92-aa2e-2992180863c1", "launchScopes": "launch openid fhirUser online_access patient/AllergyIntolerance.cs patient/Condition.cs patient/Encounter.r patient/Immunization.cs patient/Medication.r patient/MedicationStatement.cs patient/Observation.cs patient/Patient.r patient/QuestionnaireResponse.crus user/Practitioner.r launch/questionnaire?role=http://ns.electronichealth.net.au/smart/role/new", - "registeredClientIdsUrl": "https://smartforms.csiro.au/smart-config/config.json" + "registeredClientIdsUrl": "https://smartforms.csiro.au/smart-config/config.json", + "showDeveloperMessages": true } diff --git a/apps/smart-forms-app/src/features/configChecker/utils/config.ts b/apps/smart-forms-app/src/features/configChecker/utils/config.ts index 671a384c4..e5ad4d320 100644 --- a/apps/smart-forms-app/src/features/configChecker/utils/config.ts +++ b/apps/smart-forms-app/src/features/configChecker/utils/config.ts @@ -38,6 +38,11 @@ export interface ConfigFile { // "https://example.com/fhir": "6cc9bccb-3ae2-40d7-9660-22c99534520b" // } registeredClientIdsUrl: string | null; + + // Controls whether developer-focused messages are shown. + // Set to false for clinical/production deployments to hide technical messages. + // Defaults to true if not specified (shows messages for backward compatibility). + showDeveloperMessages?: boolean; } export interface AppConfig extends ConfigFile { @@ -53,6 +58,7 @@ export const FALLBACK_CONFIG: AppConfig = { launchScopes: 'launch openid fhirUser online_access patient/AllergyIntolerance.cs patient/Condition.cs patient/Encounter.r patient/Immunization.cs patient/Medication.r patient/MedicationStatement.cs patient/Observation.cs patient/Patient.r patient/QuestionnaireResponse.crus user/Practitioner.r launch/questionnaire?role=http://ns.electronichealth.net.au/smart/role/new', registeredClientIdsUrl: null, + showDeveloperMessages: true, registeredClientIds: null }; diff --git a/apps/smart-forms-app/src/features/prepopulate/hooks/usePopulate.tsx b/apps/smart-forms-app/src/features/prepopulate/hooks/usePopulate.tsx index 34062942f..d4cf0118b 100644 --- a/apps/smart-forms-app/src/features/prepopulate/hooks/usePopulate.tsx +++ b/apps/smart-forms-app/src/features/prepopulate/hooks/usePopulate.tsx @@ -15,9 +15,10 @@ * limitations under the License. */ -import { useState } from 'react'; +import { useState, useContext } from 'react'; import CloseSnackbar from '../../../components/Snackbar/CloseSnackbar.tsx'; import { useSnackbar } from 'notistack'; +import { ConfigContext } from '../../configChecker/contexts/ConfigContext.tsx'; import { buildForm, useQuestionnaireResponseStore, @@ -44,6 +45,7 @@ function usePopulate(spinner: RendererSpinner, onStopSpinner: () => void): void const [isPopulated, setIsPopulated] = useState(false); const { enqueueSnackbar } = useSnackbar(); + const { config } = useContext(ConfigContext); // Do not run population if spinner purpose is "repopulate" if (status !== 'prepopulate') { @@ -127,10 +129,14 @@ function usePopulate(spinner: RendererSpinner, onStopSpinner: () => void): void onStopSpinner(); if (issues) { - enqueueSnackbar( - 'Form partially populated, there might be pre-population issues. View console for details.', - { action: } - ); + // Only show the snackbar message if developer messages are enabled + if (config.showDeveloperMessages ?? true) { + enqueueSnackbar( + 'Form partially populated, there might be pre-population issues. View console for details.', + { action: } + ); + } + // Always log to console - clinicians won't see this, but developers need it console.warn(issues); return; } diff --git a/apps/smart-forms-app/src/features/prepopulate/test/usePopulate.test.tsx b/apps/smart-forms-app/src/features/prepopulate/test/usePopulate.test.tsx index eeb6c7375..fa0b7d699 100644 --- a/apps/smart-forms-app/src/features/prepopulate/test/usePopulate.test.tsx +++ b/apps/smart-forms-app/src/features/prepopulate/test/usePopulate.test.tsx @@ -45,6 +45,24 @@ jest.mock('../../../components/Snackbar/CloseSnackbar', () => { }; }); +// Mock ConfigContext +const mockConfigContext = { + config: { + showDeveloperMessages: true + }, + configLoading: false, + configValid: true, + configError: null, + configErrorType: null, + currentClientId: 'test-client-id', + onSetCurrentClientId: jest.fn() +}; + +jest.mock('react', () => ({ + ...jest.requireActual('react'), + useContext: jest.fn(() => mockConfigContext) +})); + // Create mock store functions that will be used in the module mock const mockSourceQuestionnaire = jest.fn(); const mockSourceResponse = jest.fn(); @@ -373,7 +391,7 @@ describe('usePopulate', () => { }); }); - it('should handle successful population with issues', async () => { + it('should handle successful population with issues (developer messages enabled)', async () => { const issues = { resourceType: 'OperationOutcome' as const, issue: [ @@ -408,13 +426,67 @@ describe('usePopulate', () => { additionalContext: undefined }); expect(mockOnStopSpinner).toHaveBeenCalled(); + // Snackbar should be shown when developer messages are enabled expect(mockEnqueueSnackbar).toHaveBeenCalledWith( 'Form partially populated, there might be pre-population issues. View console for details.', { action: expect.anything() } ); + // Console warning should always be shown + expect(consoleSpy).toHaveBeenCalledWith(issues); + + consoleSpy.mockRestore(); + }); + + it('should handle successful population with issues (developer messages disabled)', async () => { + // Temporarily set showDeveloperMessages to false + const originalShowDeveloperMessages = mockConfigContext.config.showDeveloperMessages; + mockConfigContext.config.showDeveloperMessages = false; + + const issues = { + resourceType: 'OperationOutcome' as const, + issue: [ + { + severity: 'warning' as const, + code: 'incomplete' as const, + diagnostics: 'Some fields could not be populated' + } + ] + }; + const spinner = createSpinner(true, 'prepopulate'); + + mockPopulateQuestionnaire.mockResolvedValue({ + populateSuccess: true, + populateResult: { + populatedResponse: mockResponse, + issues + } + }); + + const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(); + + renderHook(() => usePopulate(spinner, mockOnStopSpinner)); + + // Wait for async operations + await new Promise((resolve) => setTimeout(resolve, 10)); + + expect(mockBuildForm).toHaveBeenCalledWith({ + questionnaire: mockQuestionnaire, + questionnaireResponse: mockResponse, + terminologyServerUrl: 'https://test-terminology-server.com', + additionalContext: undefined + }); + expect(mockOnStopSpinner).toHaveBeenCalled(); + // Snackbar should NOT be shown when developer messages are disabled + expect(mockEnqueueSnackbar).not.toHaveBeenCalledWith( + 'Form partially populated, there might be pre-population issues. View console for details.', + { action: expect.anything() } + ); + // Console warning should still be shown (developers can still see it) expect(consoleSpy).toHaveBeenCalledWith(issues); consoleSpy.mockRestore(); + // Restore original value + mockConfigContext.config.showDeveloperMessages = originalShowDeveloperMessages; }); });