Skip to content

Conversation

krystophv
Copy link
Member

@krystophv krystophv commented Mar 5, 2025

WEB-3071 initial work for smart-on-fhir launch endpoint

@krystophv
Copy link
Member Author

/deploy dev

@tidebot
Copy link
Collaborator

tidebot commented Mar 6, 2025

krystophv updated values.yaml file in dev1

@tidebot
Copy link
Collaborator

tidebot commented Mar 6, 2025

krystophv updated flux policies file in dev1

@tidebot
Copy link
Collaborator

tidebot commented Mar 6, 2025

krystophv deployed blip WEB-3071-smart-on-fhir-launch branch to dev1 namespace

krystophv and others added 6 commits April 11, 2025 15:53
…fhir-launch

* origin/develop: (50 commits)
  Update pendoMiddleware.js
  WEB-2965 bump viz
  WEB-3418 bump viz
  WEB-3418 adjust comment for redirect url
  WEB-3418 bump viz
  WEB-3418 improve comments
  WEB-3418 always have date available in basics
  WEB-3418 fix test for oauth connection param
  WEB-3418 bump viz
  WEB-3418 test fix for Dexcom logo disappearing
  WEB-3418 use toast message
  WEB-3418 add button for redirect back home
  WEB-3418 fix failing tests
  WEB-3418 rename justConnectedDataSource param to location?.query?.openDataConnectionsModal || false
  WEB-3418 remove extraneous comments
  WEB-3418 send user to device modal page on callback
  WEB-3418 modify landing callback route with new params
  WEB-3418 revert pop-up not closing
  WEB-3418 comment out Popup.close() for debugging
  WEB-3418 only close provider popup when not mobile
  ...
@krystophv krystophv requested a review from Copilot April 16, 2025 22:45
Copilot

This comment was marked as outdated.

* develop: (185 commits)
  v1.85.0-web-3029-dep-updates.2
  Update dependencies: bump @tidepool/viz and tideline
  WEB-3002 trigger CI
  v1.84.1
  WEB-3002 bump viz
  v1.84.1-rc.4
  v1.84.1-rc.3
  Remove unused 'previousSubmitting' state
  Update tests
  Ensure redirect away from clinic details page after new clinic creation, and add more robust dependency arrays
  Reset clinic completed working state when a new create clinic request is issued in case a user creates multiple in a session
  v1.84.1-rc.2
  v1.84.1-rc.1
  Hide "Back to Tidepool" button for custodial users on oauth connection page
  v1.84.0
  Bump tideline, viz, platform-client
  v1.84.0-rc.37
  Open data connections modal directly when clicking view link for dexcom connection status on TIDE
  Make all tests run again
  Add unique dataSourceJustConnected banner message when import time is indeterminate
  ...
* develop: (37 commits)
  v1.85.0-web-3471-one-button-bolus.1
  Bump viz
  WEB-3454 assign null to combinedPatient if clinicPatient is nullish
  WEB-3454 use combinedPatient to access clinic patient details
  Prevent temporary display of uploader banner while initial data is loading
  Update tests
  Only attempt to fetch clinics for patient if user is viewing own data
  WEB-2367 add test
  WEB-2367 show FilterResetBar if search active
  WEB-2367 hide ClearFilterButtons bar when no filters applied
  v1.85.0-web-3636-remove-disconnected-from-list.1
  Hide disconnected data sources on device settings view
  WEB-2367 update patient count on update/delete
  WEB-2367 fix tests
  WEB-2367 use new API response payload for total count
  WEB-2367 add comments for clarity
  WEB-2367 always use older value instead of using _.max()
  WEB-2367 cache initial value of fetchedPatientCount
  Revert "WEB-2367 update patientCount when fetching new patienst"
  WEB-2367 update patientCount when fetching new patienst
  ...
* develop: (139 commits)
  add skip for time-sensitive tests
  v1.85.2
  FIX: Disable the 1min cgm toggle
  v1.85.2-rc.1
  Disable the 1min cgm toggle
  rename sampleFrequency to sampleInterval
  [RELEASE-1] v1.85.0
  [RELEASE-1] Bump viz, tideline, platform-client
  v1.85.0-rc.15
  bump viz
  v1.85.0-rc.14
  WEB-3595 lock packages to exact version;
  WEB-3501 fix missing return bug
  WEB-3595 - remove duplicate test
  WEB-3595 add clarifying comment
  v1.85.0-rc.13
  Bump viz
  v1.86.0-web-3677-one-five-min-cgm-toggle.1
  Update tests
  v1.85.0-rc.12
  ...
…fhir-launch

* origin/develop:
  Bump viz
  Bump viz
@krystophv krystophv requested a review from Copilot June 11, 2025 17:44
Copilot

This comment was marked as outdated.

…fhir-launch

* origin/develop: (30 commits)
  WEB-2813 fix tests
  v1.87.0-web-3720-re-enable-one-min-cgm.1
  Bump viz
  WEB-3511 fix lint error
  WEB-2813 fix capitalization
  WEB-3511 trigger ci
  WEB-3511 bump viz
  WEB-2813 capitalize access management pills
  Bump viz
  v1.86.0-web-3687-new-dosing-decision-extended-duration-props.1
  Bump tideline and viz
  WEB-3719 tests for metrics bug fix
  WEB-3511 trigger ci
  WEB-3511 bump viz
  Show loading dots when updating cgm sample range
  Re-enable one-min cgm sample interval toggle UI
  Bump viz
  WEB-3719 add additional labels for metrics for column header
  WEB-3511 bump viz and change uses of indigo50 to indigo30
  WEB-3511 update text to meet contrast reqs
  ...
krystophv added 2 commits July 7, 2025 16:46
* develop: (52 commits)
  Remove extra EmptyContentNode that was inadvertently added during merge conflict to develop resolution
  Revert tideline to 1.33.1
  v1.87.0-rc.1
  Bump viz and tideline
  v1.87.0-bump-develop.1
  Bump viz and tideline
  v1.86.1
  Bump viz and tideline
  v1.86.1-rc.6
  Bump viz and tideline
  v1.86.1-rc.4
  Bump viz
  v1.86.1-rc.3
  Bump viz
  v1.86.1-web-3773-re-enable-empty-twiist-bolus-filters.1
  Bump viz
  v1.86.1-rc.2
  v1.86.1-rc.1
  Bump tideline
  Send unique combinations of major/minor/patch semvers to Pendo for currentlyViewedDevices
  ...
@krystophv
Copy link
Member Author

/deploy dev

@tidebot
Copy link
Collaborator

tidebot commented Jul 15, 2025

krystophv updated values.yaml file in dev1

@tidebot
Copy link
Collaborator

tidebot commented Jul 15, 2025

krystophv updated flux policies file in dev1

@tidebot
Copy link
Collaborator

tidebot commented Jul 15, 2025

krystophv deployed blip WEB-3071-smart-on-fhir-launch branch to dev1 namespace

@krystophv
Copy link
Member Author

/deploy dev

@tidebot
Copy link
Collaborator

tidebot commented Jul 16, 2025

krystophv updated values.yaml file in dev1

@tidebot
Copy link
Collaborator

tidebot commented Jul 16, 2025

krystophv updated flux policies file in dev1

@tidebot
Copy link
Collaborator

tidebot commented Jul 16, 2025

krystophv deployed blip WEB-3071-smart-on-fhir-launch branch to dev1 namespace

Copy link

@henry-tp-public henry-tp-public left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

This is a comprehensive and well-structured implementation of Smart-on-FHIR integration. The code follows established patterns and demonstrates good understanding of the existing codebase architecture.

Key Strengths:

  • ✅ Proper use of Redux selectors and state management patterns
  • ✅ Good performance optimizations with useMemo and createSelector
  • ✅ Comprehensive error handling with user-friendly messages
  • ✅ Consistent prop threading throughout the component hierarchy
  • ✅ Well-documented API methods

Main Areas for Improvement:

  1. Security: Add validation/encryption for sensitive parameters in sessionStorage
  2. Code Consistency: Use existing selectors instead of direct state access
  3. Component Responsibility: Reduce direct Redux dependencies in presentational components
  4. PropTypes Consistency: Standardize required/optional prop declarations

Security Priority:

The session storage of sensitive FHIR parameters should be addressed before merging, as this could pose security risks in production environments.

Overall, this is solid work that integrates well with the existing codebase. The suggested improvements are primarily around security hardening and maintaining consistency with established patterns.


let clientConfig;

if (iss && launch) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Concern: Session Storage Risk

Using sessionStorage for sensitive FHIR parameters without encryption poses a security risk. Consider encrypting these sensitive parameters or using more secure storage mechanisms. At minimum, add validation to ensure stored values haven't been tampered with.

// Consider adding validation/encryption
const iss = validateAndDecrypt(sessionStorage.getItem('smart_iss')) || urlParams.get('iss');

@@ -94,6 +96,27 @@ export let Login = withTranslation()(class extends React.Component {
redirectUri += dest;
}

const urlParams = new URLSearchParams(routerState?.location?.search);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security: URL Parameter Validation Needed

Direct URL parameter extraction and storage without validation could be vulnerable to XSS or injection attacks. Add input validation for URL parameters (iss, launch, correlation_id).

// Add validation helpers
const validateFhirIss = (iss) => {
  // Validate iss format and whitelist known domains
  return /^https:\/\/[\w.-]+\/fhir/.test(iss) ? iss : null;
};

const iss = validateFhirIss(urlParams.get('iss'));
const launch = urlParams.get('launch')?.replace(/[^a-zA-Z0-9-_]/, '') || null;

@@ -448,6 +449,7 @@ export function mapStateToProps(state) {
selectedClinicId: state.blip.selectedClinicId,
showingWelcomeMessage: state.blip.showingWelcomeMessage,
user,
isSmartOnFhirMode: state.blip.smartCorrelationId !== null,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Consistency: Use Existing Selector

Using direct state property access with magic strings is inconsistent with the rest of the codebase. Use the existing selectIsSmartOnFhirMode(state) selector for consistency.

// Replace this line:
// isSmartOnFhirMode: state.blip.smartCorrelationId !== null,
// With:
isSmartOnFhirMode: selectIsSmartOnFhirMode(state),

@@ -46,6 +46,7 @@ class Basics extends Component {
updateChartPrefs: PropTypes.func.isRequired,
uploadUrl: PropTypes.string.isRequired,
removeGeneratedPDFS: PropTypes.func.isRequired,
isSmartOnFhirMode: PropTypes.bool.isRequired,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PropTypes Consistency Issue

Inconsistent PropTypes declarations across chart components. Some mark isSmartOnFhirMode as isRequired while others don't for the same prop. Consider standardizing this - if the prop is always passed down, it should consistently be marked as isRequired.

Also appears in: app/components/chart/daily.js, app/components/chart/trends.js, etc.


this.props.setSmartCorrelationId(correlationId);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Complexity: Extract Method

The render method has grown quite complex with multiple nested conditions for Smart-on-FHIR handling. Consider extracting the Smart-on-FHIR URL parameter logic into a separate method like handleSmartOnFhirParams() for better readability and maintainability.

if (state.blip.currentPatientInViewId) {
permsOfLoggedInUser = state.blip.selectedClinicId
return clinicPatient;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good Practice: Selector Performance

✅ Excellent use of createSelector for the new selectIsSmartOnFhirMode selector. This provides proper memoization and follows the established patterns in the codebase.

setIsProcessing(false);
return;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error Handling Enhancement

The error handling covers multiple states but could benefit from retry logic for network failures and clearer user guidance for recoverable errors. Consider adding:

  1. Retry mechanism for API failures
  2. More specific error messages for different failure types
  3. Recovery actions where possible

const t = i18next.t.bind(i18next);

const Footer = ({ version, location, trackMetric }) => {
const inSmartOnFhirMode = useSelector(state => selectIsSmartOnFhirMode(state));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Component Responsibility Concern

The Footer component now depends on Redux state management, which breaks component purity. Consider passing isSmartOnFhirMode as a prop from the parent component instead of using useSelector directly to maintain better separation of concerns.

...wrapperProps,

const { Wrapper, wrapperProps } = React.useMemo(() => {
const Wrapper = keycloakConfig?.url ? ReactKeycloakProvider : React.Fragment;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance Optimization

✅ Good use of useMemo for expensive operations. The component re-rendering logic is well-optimized and follows React best practices.

const [error, setError] = useState(null);

// Hide Zendesk widget in Smart-on-FHIR mode
useEffect(() => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Organization: Extract Utility

The Zendesk hiding logic with retry mechanism could be extracted into a utility function for reusability across the application, especially if other components need to interact with Zendesk in Smart-on-FHIR mode.

// utils/zendesk.js
export const hideZendeskWidget = (maxAttempts = 50) => {
  let attempts = 0;
  let timeoutId;
  
  const checkZendesk = () => {
    if (window.zE) {
      window.zE('webWidget', 'hide');
      return;
    }
    
    attempts++;
    if (attempts < maxAttempts) {
      timeoutId = setTimeout(checkZendesk, 100);
    }
  };
  
  checkZendesk();
  return () => timeoutId && clearTimeout(timeoutId);
};

* develop: (326 commits)
  WEB-3830 bump viz
  WEB-3830 bump viz
  WEB-3830 modify Arg Type comment to permit multiple forms of unit representation
  WEB-3830 use correct id prop for select element label
  WEB-3830 hide fields in private workspace
  WEB-3830 minor visual tweaks
  WEB-3830 remove unneeded variable declaration
  WEB-3830 refactor utils.getBGPrefsForDataProcessing and write tests
  WEB-3830 add tests for displaying diagnosisType
  WEB-3830 migrate tests for NavPatientHeader
  WEB-3830 extract selectElementStyleOverrides to own files
  WEB-3830 rename getBgPrefs back to getBGPrefsForDataProcessing
  WEB-3830 rename to SelectGlycemicRanges and add test
  WEB-3830 fix jest specs
  WEB-3830 add spec for SelectDiabetesType
  WEB-3830 remove toggleBgRange tests
  WEB-3830 fix PatientForm-related tests
  WEB-3830 bump viz & tideline
  WEB-3830 send diagnosisType and glycemicRanges when accepting pt invite
  WEB-3830 hide BgSummaryCell when non-standard range
  ...
* develop: (74 commits)
  Fix broken tests
  Remove unused import and variable declaration
  Rename test-watch npm script
  Ensure button disable prop is a boolean
  Add tests for DataDonationForm
  Optimize slideshow images
  Add safety check to latestRecord in consentRecords reducer when fetching user consents
  Add tests for DataDOnationConsentDialog
  Only store active consent records and always fall back to current date for new consent dates
  Fixing some edge cases that were surfaced during code review
  Handle caregiver name changes without extra useEffect
  Derive currentForm directly from currentConsent state
  Remove unused scrollToBottom state in consent dialog
  Add a test:jest:watch npm script for general local use
  More tests
  Allow use of Set in eslint
  Remove unused schema from first step of data donation form
  Add tests for SildeShow component and dataDonation page
  Add additional tests
  v1.89.0-web-3771-informed-consent-flow-updated.1
  ...
Introduces tracking for SMART on FHIR functionality across the application to better monitor and analyze usage patterns. This includes:

- Adds isSmartOnFhir flag to analytics events when SMART on FHIR data is present
- Updates pendo middleware to include SMART on FHIR properties in visitor data
- Adds tracking for Direct Connect patient lookup success/failure events
- Updates tracking middleware to pass SMART on FHIR context to analytics

The changes ensure that SMART on FHIR usage is properly measured without cluttering analytics with false values, and maintains consistency across different tracking systems.
Introduces clinic membership validation before patient lookup to ensure clinicians are properly configured in Tidepool. Adds new error component for cases where clinicians have no clinics assigned and updates test coverage to include this scenario.

- Adds clinic validation logic that checks if clinician belongs to any clinics before proceeding with patient lookup
- Creates new NoClinicsError component to handle cases where clinicians have no clinic assignments
- Updates Smart-on-FHIR error handling to include clinic validation errors
- Adds comprehensive test coverage for clinic validation scenarios
- Updates existing tests to use React Testing Library and Jest mocks instead of Enzyme and sinon

This change improves the Smart-on-FHIR workflow by ensuring clinicians are properly onboarded before attempting patient lookups, providing better error messages for common configuration issues.
@krystophv krystophv requested a review from Copilot September 4, 2025 02:34
Copilot

This comment was marked as outdated.

Switches all import paths from relative to absolute using the @app alias for better maintainability and cleaner module resolution. This change standardizes the import structure across test files, making the codebase more consistent and easier to navigate.

The updates affect:
- Component import paths in test files
- Mock module paths for Icon and theme components
- Overall project structure alignment with alias configuration

This improves code organization and reduces the risk of broken imports when moving files.
* develop:
  WEB-3965 fix className prop
  WEB-3965 fix double calls and last reviewed error
  WEB-3965 modify clinicianPatients for clickable cards
  WEB-3965 make clinicPatients entire row clickable
- Updates ESLint parser from babel-eslint to @babel/eslint-parser across multiple configuration files
- Adds Jest environment configuration for test files
- Improves Smart-on-FHIR error handling with centralized handleError function and better error tracking
- Updates footer links to use HTTPS protocol
- Removes redundant isSmartOnFhir checks in analytics tracking
- Updates patient error components with proper ARIA attributes for accessibility
- Removes unused babel-eslint dependency and related eslint configurations
- Cleans up test files by removing global eslint comments and improving test setup/teardown
@krystophv krystophv requested a review from Copilot September 9, 2025 03:44
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements initial Smart-on-FHIR integration to enable launching Tidepool from Electronic Health Record (EHR) systems. The implementation focuses on handling the SMART authentication flow and displaying patient data in a specialized mode.

  • Smart-on-FHIR authentication flow with correlation ID tracking
  • Patient lookup functionality using MRN and date of birth from SMART tokens
  • Specialized UI mode that hides certain features when in Smart-on-FHIR context

Reviewed Changes

Copilot reviewed 77 out of 79 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
app/pages/smartonfhir/ New Smart-on-FHIR page and error components for patient matching scenarios
app/redux/reducers/ New reducers for Smart-on-FHIR data and correlation ID state management
app/redux/actions/ New actions for Smart-on-FHIR authentication and patient fetching
app/keycloak.js Updated to handle Smart-on-FHIR client configuration and token parsing
app/routes.js Added new route and authentication guard for Smart-on-FHIR flow
test/ Comprehensive test coverage for new Smart-on-FHIR functionality

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

const state = getState();
const isSmartOnFhir = !!state?.blip?.smartOnFhirData;
// Only include isSmartOnFhir when it's true to avoid cluttering analytics
return isSmartOnFhir ? { isSmartOnFhir: true } : undefined;
Copy link
Preview

Copilot AI Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The function returns undefined when not in Smart-on-FHIR mode, but the calling code uses spread operators that could handle empty objects better. Consider returning an empty object {} instead of undefined to simplify the calling code and reduce conditional logic.

Suggested change
return isSmartOnFhir ? { isSmartOnFhir: true } : undefined;
return isSmartOnFhir ? { isSmartOnFhir: true } : {};

Copilot uses AI. Check for mistakes.

Comment on lines +68 to +87
let attempts = 0;
const maxAttempts = 50; // 5 seconds max (50 * 100ms)
let timeoutId;

const checkZendesk = () => {
if (windowObj.zE) {
windowObj.zE('webWidget', 'hide');
return;
}

attempts++;
if (attempts < maxAttempts) {
timeoutId = setTimeout(checkZendesk, 100);
}
};

checkZendesk();

return () => {
if (timeoutId) clearTimeout(timeoutId);
Copy link
Preview

Copilot AI Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The Zendesk hiding logic uses a polling approach with hardcoded values. Consider using a more robust approach like a MutationObserver to detect when Zendesk is loaded, or make the polling parameters configurable constants.

Suggested change
let attempts = 0;
const maxAttempts = 50; // 5 seconds max (50 * 100ms)
let timeoutId;
const checkZendesk = () => {
if (windowObj.zE) {
windowObj.zE('webWidget', 'hide');
return;
}
attempts++;
if (attempts < maxAttempts) {
timeoutId = setTimeout(checkZendesk, 100);
}
};
checkZendesk();
return () => {
if (timeoutId) clearTimeout(timeoutId);
let observer;
let zendeskHidden = false;
const hideZendesk = () => {
if (windowObj.zE && !zendeskHidden) {
windowObj.zE('webWidget', 'hide');
zendeskHidden = true;
}
};
// Try to hide immediately in case zE is already available
hideZendesk();
// Observe DOM mutations to detect when Zendesk widget is loaded
observer = new MutationObserver(() => {
hideZendesk();
});
observer.observe(document.body, { childList: true, subtree: true });
return () => {
if (observer) observer.disconnect();

Copilot uses AI. Check for mistakes.

Comment on lines +107 to +115
getClinics(api, loggedInUserId, (err, clinics) => {
if (err) {
handleError(ErrorMessages.ERR_SMARTONFHIR_CLINICIAN_NO_CLINICS, 'Direct Connect Clinics Fetch Failure');
return;
}
if (!clinics || clinics.length === 0) {
handleError(ErrorMessages.ERR_SMARTONFHIR_CLINICIAN_NO_CLINICS, 'Direct Connect Clinician No Clinics');
return;
}
Copy link
Preview

Copilot AI Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Both error conditions use the same error message ERR_SMARTONFHIR_CLINICIAN_NO_CLINICS but have different underlying causes (fetch failure vs. no clinics available). Consider using distinct error messages to help with debugging and user experience.

Copilot uses AI. Check for mistakes.

Comment on lines +110 to +118
if (iss && launch && !correlationId) {
correlationId = crypto.randomUUID();

sessionStorage.setItem('smart_correlation_id', correlationId);
sessionStorage.setItem('smart_iss', iss);
sessionStorage.setItem('smart_launch', launch);

this.props.setSmartCorrelationId(correlationId);
}
Copy link
Preview

Copilot AI Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code uses crypto.randomUUID() without checking browser support. This API is not available in all browsers and could cause runtime errors. Consider adding a fallback or polyfill, or check for API availability before use.

Copilot uses AI. Check for mistakes.

Comment on lines 26 to +30
export const updateKeycloakConfig = (info, store) => {
if (!(isEmpty(info) || isEqual(_keycloakConfig, info))) {
const urlParams = new URLSearchParams(window.location.search);
const iss = sessionStorage.getItem('smart_iss') || urlParams.get('iss');
const launch = sessionStorage.getItem('smart_launch') || urlParams.get('launch');
Copy link
Preview

Copilot AI Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The function accesses both sessionStorage and window.location directly, making it difficult to test and potentially causing issues in non-browser environments. Consider passing these as parameters or using dependency injection for better testability.

Copilot uses AI. Check for mistakes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants