Skip to content

Conversation

@shubham1g5
Copy link
Contributor

@shubham1g5 shubham1g5 commented Aug 13, 2025

Product Description

https://dimagi.atlassian.net/browse/CCCT-1613

  • Hides Connect Screens behind a Acccess Mechanism based on a flag "invited_user" from personal ID server
  • Implements a Refresh Opportunities Button
vidma_recorder_14082025_223355.mp4

Technical Summary

Spec

QA Plan

Part of 2.59

Labels and Review

  • Do we need to enhance the manual QA test coverage ? If yes, the "QA Note" label is set correctly
  • Does the PR introduce any major changes worth communicating ? If yes, the "Release Note" label is set and a "Release Note" is specified in PR description.
  • Risk label is set correctly
  • The set of people pinged as reviewers is appropriate for the level of risk of the change

@coderabbitai
Copy link

coderabbitai bot commented Aug 13, 2025

📝 Walkthrough

Walkthrough

  • Adds hasConnectAccess field to ConnectUserRecord, constructor, getters/setters, and persists it via DB schema V17.
  • Introduces intermediate model ConnectUserRecordV16 and upgrades: 14→15 to V16, then 16→17 to new ConnectUserRecord with hasConnectAccess inferred from existing jobs.
  • Gates Connect UI elements and navigation based on ConnectUserDatabaseUtil.hasConnectAccess(context).
  • Adds ConnectNavHelper.goToConnectJobsListChecked and enforces access checks in navigation.
  • Parses invited_user from server responses into PersonalIdSessionData and persists it in ConnectUserRecord on user creation.
  • FirebaseMessagingUtil.turnOnConnectAccess enables access upon specific push handling.
  • Bumps DB version to 17.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant App
  participant ConnectDB as ConnectUserDatabaseUtil
  participant Nav as ConnectNavHelper

  User->>App: Open Login/Setup screen
  App->>ConnectDB: hasConnectAccess(context)
  ConnectDB-->>App: true/false
  App->>User: Show/enable Connect button if true

  User->>App: Tap Connect
  App->>Nav: goToConnectJobsListChecked(context)
  Nav->>ConnectDB: hasConnectAccess(context)
  alt access allowed
    Nav-->>App: Start ConnectActivity
  else no access
    Nav-->>App: Throw IllegalStateException
  end
Loading
sequenceDiagram
  participant DB as Connect DB
  participant Upgrader as ConnectDatabaseUpgrader
  participant V14 as ConnectUserRecordV14
  participant V16 as ConnectUserRecordV16
  participant New as ConnectUserRecord

  DB->>Upgrader: oldVersion=14..16
  alt 14->15
    Upgrader->>DB: Read V14 records
    Upgrader->>V16: fromV14(old)
    Upgrader->>DB: Write V16 records
  end
  alt 16->17
    Upgrader->>DB: Read V16 records
    Upgrader->>DB: Check ConnectJobRecord presence
    Upgrader->>New: fromV16(old, hasConnectAccess)
    Upgrader->>DB: Write New records (preserve IDs)
  end
Loading
sequenceDiagram
  participant FCM as FirebaseMessagingUtil
  participant ConnectDB as ConnectUserDatabaseUtil

  FCM->>ConnectDB: turnOnConnectAccess(context)
  ConnectDB-->>FCM: Access enabled if user exists
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • OrangeAndGreen
  • Jignesh-dimagi
  • pm-dimagi
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch connectVisibility

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🔭 Outside diff range comments (1)
app/src/org/commcare/activities/CommCareSetupActivity.java (1)

674-680: Use the checked navigation helper to prevent potential crashes.

The click handler currently calls goToConnectJobsList, which throws IllegalStateException if access is absent. While the button is gated by isConnectEnabled, future regressions could enable the button prematurely and cause a crash. Prefer the “checked” helper for safety.

Apply:

-        installFragment.updateConnectButton(isConnectEnabled, v -> {
-            PersonalIdManager.getInstance().unlockConnect(this, success -> {
-                ConnectNavHelper.INSTANCE.goToConnectJobsList(this);
-            });
-        });
+        installFragment.updateConnectButton(isConnectEnabled, v -> {
+            PersonalIdManager.getInstance().unlockConnect(this, success -> {
+                ConnectNavHelper.INSTANCE.goToConnectJobsListChecked(this);
+            });
+        });
🧹 Nitpick comments (13)
app/src/org/commcare/android/database/connect/models/PersonalIdSessionData.kt (1)

24-26: Add invitedUser flag: fix comment typo; confirm default-false semantics

The addition looks good. Two small things:

  • Typo in the comment.
  • Please confirm that defaulting to false is intentional for older server responses that may not include invited_user; i.e., access will be turned on via push/migration.

Apply this diff to fix the comment:

-        var invitedUser: Boolean = false // indicates if user has has been invited to Connect
+        var invitedUser: Boolean = false // indicates if the user has been invited to Connect
app/src/org/commcare/connect/network/connectId/parser/ConfirmBackupCodeResponseParser.java (1)

30-30: Deduplicate JSON key "invited_user" via a shared constant

The same key is used across multiple parsers. Extract a shared constant to avoid typos and simplify future changes.

Consider adding a constant to the shared interface and using it here (and in CompleteProfileResponseParser):

Add in PersonalIdApiResponseParser:

// e.g., in PersonalIdApiResponseParser.java
String JSON_INVITED_USER = "invited_user";

Then change here:

-        sessionData.setInvitedUser(json.optBoolean("invited_user", false));
+        sessionData.setInvitedUser(json.optBoolean(JSON_INVITED_USER, false));

And similarly in CompleteProfileResponseParser:

-        sessionData.setInvitedUser(json.optBoolean("invited_user", false));
+        sessionData.setInvitedUser(json.optBoolean(JSON_INVITED_USER, false));
app/src/org/commcare/utils/FirebaseMessagingUtil.java (1)

194-194: Enabling access on any CCC push: confirm intended behavior; consider adding audit logging

Turning on Connect access upon any CCC push centralizes the “invite” mechanism and seems aligned with the PR goals. Two follow-ups:

  • Confirm it’s acceptable to enable access even if the specific CCC action is unrecognized (default case) or has an invalid payload; the call happens before the switch handling.
  • Consider adding a lightweight log/analytics event when access is enabled via push to aid debugging/audit.
app/src/org/commcare/connect/PersonalIdManager.java (1)

225-225: Post-signin navigation now silently no-ops without access; consider user feedback.

Switching to goToConnectJobsListChecked avoids crashes but provides no UX signal if access is absent. Consider surfacing a brief message so users know why they aren’t navigated.

Example change (if you prefer handling it here instead of in the helper):

-            ConnectNavHelper.INSTANCE.goToConnectJobsListChecked(activity);
+            if (ConnectUserDatabaseUtil.hasConnectAccess(activity)) {
+                ConnectNavHelper.INSTANCE.goToConnectJobsList(activity);
+            } else {
+                // Optional: replace with a dedicated string if available
+                Toast.makeText(activity, activity.getString(R.string.connect_unlock_unavailable), Toast.LENGTH_SHORT).show();
+            }
app/src/org/commcare/navdrawer/BaseDrawerController.kt (1)

135-136: Typo: Rename hasConnecrAccess → hasConnectAccess.

Minor readability nit; keeps naming consistent with the util method.

-            val hasConnecrAccess = ConnectUserDatabaseUtil.hasConnectAccess(activity)
+            val hasConnectAccess = ConnectUserDatabaseUtil.hasConnectAccess(activity)

Also update the reference below (line 143) accordingly:

-                    isEnabled = hasConnecrAccess
+                    isEnabled = hasConnectAccess
app/src/org/commcare/connect/ConnectNavHelper.kt (1)

20-20: Nit: Remove unnecessary semicolon.

Kotlin doesn’t need it here.

-        checkConnectAccess(context);
+        checkConnectAccess(context)
app/src/org/commcare/fragments/personalId/PersonalIdBackupCodeFragment.java (1)

230-236: Parameter alignment issue in ConnectUserRecord constructor

The constructor call has inconsistent spacing between parameters personalIdSessionData.getDemoUser() and personalIdSessionData.getRequiredLock().

Apply this diff to fix the spacing:

-                personalIdSessionData.getDemoUser(),personalIdSessionData.getRequiredLock(),
+                personalIdSessionData.getDemoUser(), personalIdSessionData.getRequiredLock(),
app/src/org/commcare/fragments/personalId/PersonalIdPhotoCaptureFragment.java (1)

133-141: Parameter alignment issue in ConnectUserRecord constructor

The constructor call has inconsistent spacing between parameters personalIdSessionData.getDemoUser() and personalIdSessionData.getRequiredLock().

Apply this diff to fix the spacing:

-                personalIdSessionData.getDemoUser(),personalIdSessionData.getRequiredLock(),
+                personalIdSessionData.getDemoUser(), personalIdSessionData.getRequiredLock(),
app/src/org/commcare/android/database/connect/models/ConnectUserRecord.java (2)

83-84: Add Javadoc for the new hasConnectAccess field

Based on the learning about documenting fields with Javadoc comments, the new hasConnectAccess field should include documentation explaining its purpose and relationship with the Connect feature access control.

Add documentation above the field:

+    /**
+     * Indicates whether the user has access to Connect features.
+     * This flag is controlled by the invited_user status from the server
+     * and can be enabled through push notifications or server responses.
+     */
     @Persisting(value = 16)
     private boolean hasConnectAccess;

95-109: Constructor parameter list is becoming unwieldy

The constructor now has 10 parameters, which makes it difficult to use correctly and prone to errors. Consider using a builder pattern or a parameter object to improve maintainability.

Consider refactoring to use a builder pattern for better maintainability:

public static class Builder {
    private String primaryPhone;
    private String userId;
    private String password;
    private String name;
    private String pin;
    private Date lastPinVerifyDate;
    private String photo;
    private boolean isDemo;
    private String requiredLock = PersonalIdSessionData.PIN;
    private boolean hasConnectAccess;
    
    public Builder withPrimaryPhone(String primaryPhone) {
        this.primaryPhone = primaryPhone;
        return this;
    }
    // ... other builder methods
    
    public ConnectUserRecord build() {
        ConnectUserRecord record = new ConnectUserRecord();
        // ... set all fields
        return record;
    }
}

This would make the code more readable and less error-prone when creating instances.

app/src/org/commcare/android/database/connect/models/ConnectUserRecordV16.java (3)

11-12: Add a brief class-level Javadoc to clarify this is an intermediate, migration-only model.

This helps future maintainers avoid adding business logic or evolving this class beyond its migration purpose.

Apply this diff:

 @Table(ConnectUserRecordV16.STORAGE_KEY)
-public class ConnectUserRecordV16 extends Persisted {
+/**
+ * Intermediate persisted model for DB v16 used only during the 14→15→16 and 16→17 upgrades.
+ * Mirrors the legacy ConnectUserRecord schema to facilitate data migration.
+ * Do not add business logic or evolve this class beyond migration needs.
+ */
+public class ConnectUserRecordV16 extends Persisted {

67-69: Annotate requiredLock with DeviceAuthType for compile-time safety.

Since only PIN or BIOMETRIC_TYPE are valid, use the existing annotation to catch invalid assignments at compile time.

Apply this diff:

-    @Persisting(value = 15)
-    private String requiredLock;
+    @Persisting(value = 15)
+    @PersonalIdSessionData.DeviceAuthType
+    private String requiredLock;

And annotate the getter’s return type:

-    public String getRequiredLock() {
+    public @PersonalIdSessionData.DeviceAuthType String getRequiredLock() {
         return requiredLock;
     }

Also applies to: 141-143


19-69: Sensitive fields are persisted; ensure we never log them.

password and pin are plain strings in the model (expected for compatibility). Please confirm that:

  • We don’t log instances of this class (or override toString()).
  • Any debug logs redact these fields.

If needed, I can propose a minimal toString() override that redacts sensitive fields specifically in this intermediate class.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 49d1bf1 and c0a5176.

📒 Files selected for processing (16)
  • app/src/org/commcare/activities/CommCareSetupActivity.java (2 hunks)
  • app/src/org/commcare/activities/LoginActivityUIController.java (1 hunks)
  • app/src/org/commcare/android/database/connect/models/ConnectUserRecord.java (6 hunks)
  • app/src/org/commcare/android/database/connect/models/ConnectUserRecordV16.java (1 hunks)
  • app/src/org/commcare/android/database/connect/models/PersonalIdSessionData.kt (1 hunks)
  • app/src/org/commcare/connect/ConnectNavHelper.kt (2 hunks)
  • app/src/org/commcare/connect/PersonalIdManager.java (1 hunks)
  • app/src/org/commcare/connect/database/ConnectUserDatabaseUtil.java (1 hunks)
  • app/src/org/commcare/connect/network/connectId/parser/CompleteProfileResponseParser.java (1 hunks)
  • app/src/org/commcare/connect/network/connectId/parser/ConfirmBackupCodeResponseParser.java (1 hunks)
  • app/src/org/commcare/fragments/personalId/PersonalIdBackupCodeFragment.java (1 hunks)
  • app/src/org/commcare/fragments/personalId/PersonalIdPhotoCaptureFragment.java (1 hunks)
  • app/src/org/commcare/models/database/connect/ConnectDatabaseUpgrader.java (4 hunks)
  • app/src/org/commcare/models/database/connect/DatabaseConnectOpenHelper.java (1 hunks)
  • app/src/org/commcare/navdrawer/BaseDrawerController.kt (1 hunks)
  • app/src/org/commcare/utils/FirebaseMessagingUtil.java (2 hunks)
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: shubham1g5
PR: dimagi/commcare-android#0
File: :0-0
Timestamp: 2025-05-08T11:08:18.530Z
Learning: PR #3048 "Phase 4 Connect PR" introduces a substantial feature called "Connect" to the CommCare Android app, which includes messaging, job management, delivery tracking, payment processing, authentication flows, and learning modules. It follows a modern architecture using Navigation Components with three navigation graphs, segregated business logic in Manager classes, and proper database persistence.
Learnt from: pm-dimagi
PR: dimagi/commcare-android#2847
File: app/src/org/commcare/android/database/connect/models/ConnectLinkedAppRecordV8.java:107-107
Timestamp: 2025-01-28T08:41:32.380Z
Learning: When upgrading from ConnectLinkedAppRecordV3 to ConnectLinkedAppRecordV8, the connectIdLinked field is intentionally set to true as part of the database migration strategy since this field was not available from the server in V3 and is introduced in V8.
📚 Learning: 2025-05-08T11:08:18.530Z
Learnt from: shubham1g5
PR: dimagi/commcare-android#0
File: :0-0
Timestamp: 2025-05-08T11:08:18.530Z
Learning: PR #3048 "Phase 4 Connect PR" introduces a substantial feature called "Connect" to the CommCare Android app, which includes messaging, job management, delivery tracking, payment processing, authentication flows, and learning modules. It follows a modern architecture using Navigation Components with three navigation graphs, segregated business logic in Manager classes, and proper database persistence.

Applied to files:

  • app/src/org/commcare/activities/CommCareSetupActivity.java
  • app/src/org/commcare/connect/PersonalIdManager.java
  • app/src/org/commcare/utils/FirebaseMessagingUtil.java
  • app/src/org/commcare/connect/ConnectNavHelper.kt
📚 Learning: 2025-05-22T14:28:35.959Z
Learnt from: OrangeAndGreen
PR: dimagi/commcare-android#3121
File: app/src/org/commcare/activities/CommCareSetupActivity.java:360-364
Timestamp: 2025-05-22T14:28:35.959Z
Learning: In CommCareSetupActivity.java, the call to installFragment.showConnectErrorMessage() after fragment transactions is intentionally unguarded with null checks. This follows the app's design pattern where critical error paths prefer immediate crashes over silent failures, making potential issues immediately visible during development rather than hiding them with defensive programming.

Applied to files:

  • app/src/org/commcare/activities/CommCareSetupActivity.java
📚 Learning: 2025-02-04T15:29:12.993Z
Learnt from: OrangeAndGreen
PR: dimagi/commcare-android#2927
File: app/src/org/commcare/activities/connect/HQUserInviteActivity.java:0-0
Timestamp: 2025-02-04T15:29:12.993Z
Learning: In HQUserInviteActivity, parameters from Intent data (domain, inviteCode, username, etc.) are required and the code is designed to fail immediately if they're missing, rather than adding null checks. This helps catch integration issues early.

Applied to files:

  • app/src/org/commcare/connect/network/connectId/parser/ConfirmBackupCodeResponseParser.java
📚 Learning: 2025-02-19T15:15:01.935Z
Learnt from: pm-dimagi
PR: dimagi/commcare-android#2956
File: app/src/org/commcare/fragments/connectMessaging/ConnectMessageChannelListFragment.java:58-60
Timestamp: 2025-02-19T15:15:01.935Z
Learning: Error handling for message retrieval in ConnectMessageChannelListFragment's retrieveMessages callback is not required as per user preference.

Applied to files:

  • app/src/org/commcare/utils/FirebaseMessagingUtil.java
📚 Learning: 2025-07-29T14:14:07.954Z
Learnt from: OrangeAndGreen
PR: dimagi/commcare-android#3248
File: app/src/org/commcare/adapters/ConnectMessageAdapter.java:0-0
Timestamp: 2025-07-29T14:14:07.954Z
Learning: In the CommCare Android Connect messaging system (ConnectMessageAdapter.java), the team prefers to let getMessage() potentially return null and crash rather than adding defensive null checks. This follows the fail-fast philosophy used throughout the Connect feature to catch programming errors immediately during development.

Applied to files:

  • app/src/org/commcare/utils/FirebaseMessagingUtil.java
📚 Learning: 2025-05-09T10:57:41.073Z
Learnt from: Jignesh-dimagi
PR: dimagi/commcare-android#3093
File: app/res/navigation/nav_graph_connect_messaging.xml:41-45
Timestamp: 2025-05-09T10:57:41.073Z
Learning: In the CommCare Android codebase, the navigation graph for Connect messaging (`nav_graph_connect_messaging.xml`) intentionally uses `channel_id` as the argument name in the connectMessageFragment, despite using `channelId` in other parts of the same navigation graph. This naming difference is by design in the refactored code.

Applied to files:

  • app/src/org/commcare/utils/FirebaseMessagingUtil.java
📚 Learning: 2025-07-29T14:09:49.805Z
Learnt from: OrangeAndGreen
PR: dimagi/commcare-android#3248
File: app/src/org/commcare/fragments/connectMessaging/ConnectMessageFragment.java:0-0
Timestamp: 2025-07-29T14:09:49.805Z
Learning: In the CommCare Android Connect messaging system, message retry logic is implemented at the MessageManager level via MessageManager.sendUnsentMessages(). This method is called automatically when the channel list loads (ConnectMessageChannelListFragment), and it attempts to resend any outgoing messages that are not yet confirmed. Individual message fragments do not need to implement their own retry logic.

Applied to files:

  • app/src/org/commcare/utils/FirebaseMessagingUtil.java
📚 Learning: 2025-01-28T08:41:32.380Z
Learnt from: pm-dimagi
PR: dimagi/commcare-android#2847
File: app/src/org/commcare/android/database/connect/models/ConnectLinkedAppRecordV8.java:107-107
Timestamp: 2025-01-28T08:41:32.380Z
Learning: When upgrading from ConnectLinkedAppRecordV3 to ConnectLinkedAppRecordV8, the connectIdLinked field is intentionally set to true as part of the database migration strategy since this field was not available from the server in V3 and is introduced in V8.

Applied to files:

  • app/src/org/commcare/models/database/connect/DatabaseConnectOpenHelper.java
  • app/src/org/commcare/android/database/connect/models/ConnectUserRecord.java
📚 Learning: 2025-01-21T17:28:09.007Z
Learnt from: OrangeAndGreen
PR: dimagi/commcare-android#2912
File: app/src/org/commcare/android/database/connect/models/ConnectUserRecord.java:66-71
Timestamp: 2025-01-21T17:28:09.007Z
Learning: The ConnectUserRecord class in CommCare Android uses Persisting annotations with sequential indices for field persistence, and MetaField annotations for fields that have corresponding META constants. Fields should be documented with Javadoc comments explaining their purpose, format requirements, and relationships with other fields.

Applied to files:

  • app/src/org/commcare/android/database/connect/models/ConnectUserRecordV16.java
  • app/src/org/commcare/android/database/connect/models/ConnectUserRecord.java
  • app/src/org/commcare/models/database/connect/ConnectDatabaseUpgrader.java
  • app/src/org/commcare/fragments/personalId/PersonalIdPhotoCaptureFragment.java
📚 Learning: 2025-01-23T21:38:17.427Z
Learnt from: pm-dimagi
PR: dimagi/commcare-android#2847
File: app/src/org/commcare/android/database/connect/models/ConnectLinkedAppRecordV3.java:1-68
Timestamp: 2025-01-23T21:38:17.427Z
Learning: The versioned database model classes (e.g., ConnectLinkedAppRecordV3) intentionally maintain their original design patterns, including their security implementation, to preserve backward compatibility.

Applied to files:

  • app/src/org/commcare/android/database/connect/models/ConnectUserRecordV16.java
📚 Learning: 2025-01-26T14:32:09.266Z
Learnt from: pm-dimagi
PR: dimagi/commcare-android#2847
File: app/src/org/commcare/android/database/connect/models/ConnectUserRecordV5.java:72-76
Timestamp: 2025-01-26T14:32:09.266Z
Learning: In ConnectUserRecordV5, initializing lastPasswordDate and connectTokenExpiration to new Date() in the constructor is intentional.

Applied to files:

  • app/src/org/commcare/android/database/connect/models/ConnectUserRecord.java
🧬 Code Graph Analysis (7)
app/src/org/commcare/connect/database/ConnectUserDatabaseUtil.java (1)
app/src/org/commcare/connect/workers/ConnectHeartbeatWorker.kt (1)
  • context (11-38)
app/src/org/commcare/activities/CommCareSetupActivity.java (1)
app/src/org/commcare/connect/database/ConnectUserDatabaseUtil.java (1)
  • ConnectUserDatabaseUtil (10-56)
app/src/org/commcare/connect/PersonalIdManager.java (1)
app/src/org/commcare/connect/ConnectNavHelper.kt (1)
  • goToConnectJobsListChecked (31-35)
app/src/org/commcare/utils/FirebaseMessagingUtil.java (1)
app/src/org/commcare/connect/database/ConnectUserDatabaseUtil.java (1)
  • ConnectUserDatabaseUtil (10-56)
app/src/org/commcare/activities/LoginActivityUIController.java (3)
app/src/org/commcare/navdrawer/BaseDrawerActivity.kt (1)
  • isPersonalIdLoggedIn (21-25)
app/src/org/commcare/connect/PersonalIdManager.java (1)
  • PersonalIdManager (64-622)
app/src/org/commcare/connect/database/ConnectUserDatabaseUtil.java (1)
  • ConnectUserDatabaseUtil (10-56)
app/src/org/commcare/android/database/connect/models/ConnectUserRecordV16.java (1)
app/src/org/commcare/android/database/connect/models/PersonalIdSessionData.kt (1)
  • requiredLock (9-40)
app/src/org/commcare/android/database/connect/models/ConnectUserRecord.java (1)
app/src/org/commcare/android/database/connect/models/PersonalIdSessionData.kt (1)
  • requiredLock (9-40)
🔇 Additional comments (9)
app/src/org/commcare/connect/network/connectId/parser/CompleteProfileResponseParser.java (1)

26-26: Parse invited_user: LGTM

Using optBoolean with a false fallback is appropriate and consistent with the gating strategy described in the PR.

app/src/org/commcare/utils/FirebaseMessagingUtil.java (1)

30-30: Import of ConnectUserDatabaseUtil: LGTM

app/src/org/commcare/activities/CommCareSetupActivity.java (1)

33-33: Import looks correct.

The new import for ConnectUserDatabaseUtil aligns with usage below.

app/src/org/commcare/navdrawer/BaseDrawerController.kt (1)

141-143: Gating OPPORTUNITIES by Connect access is appropriate.

Enabling the parent item based on persisted access state is consistent with the rest of the PR’s access-control pattern.

app/src/org/commcare/connect/database/ConnectUserDatabaseUtil.java (1)

44-55: LGTM: Minimal, clear APIs for access check and enablement.

Both hasConnectAccess and turnOnConnectAccess read well and match the storage model introduced by the migration.

app/src/org/commcare/activities/LoginActivityUIController.java (1)

565-576: Good refactoring to improve code organization

The extraction of the welcome message logic into a separate method improves code organization and readability. The access control logic correctly combines both login state and Connect access checks to determine button visibility.

app/src/org/commcare/models/database/connect/ConnectDatabaseUpgrader.java (1)

601-632: Review migrate has_connect_access logic for edge cases

The migration in ConnectDatabaseUpgrader.java:upgradeSixteenSeventeen calculates hasConnectAccess solely by checking for existing ConnectJobRecord entries. No special handling is present for demo/test or invited users, and we found no documentation on an invited_user flag.

Please confirm that this aligns with product requirements for:

  • Users whose job records have been deleted
  • Demo/test users created via DemoUserBuilder
  • Any invited-user onboarding flows

Locations for review:

  • app/src/org/commcare/models/database/connect/ConnectDatabaseUpgrader.java (lines 601–632)
  • app/src/org/commcare/models/database/user/DemoUserBuilder.java
  • app/unit-tests/src/org/commcare/android/tests/DemoUserRestoreTest.java
app/src/org/commcare/android/database/connect/models/ConnectUserRecord.java (1)

199-216: Verify invited_user mapping and migration logic for hasConnectAccess

It looks like fromV16 correctly uses the hasConnectAccess parameter, but no references to the server’s invited_user flag were found in the codebase. Please manually verify that:

  • The JSON parser/model for the auth or user endpoint (e.g. ConnectAuthResponse, PersonalIdServerParser) reads the invited_user field.
  • That invited_user is passed into the ConnectUserRecordV17 (or equivalent) constructor and eventually into hasConnectAccess.
  • Your database migration tests cover both invited and non-invited user scenarios to ensure new and migrated users receive the correct hasConnectAccess value.
app/src/org/commcare/android/database/connect/models/ConnectUserRecordV16.java (1)

70-77: Confirm defaults won’t be used for new records outside migrations.

Setting verifySecondaryPhoneByDate and connectTokenExpiration to new Date() in the constructor is fine for migration scaffolding, but if this constructor is ever used for non-migration instantiation, these defaults could be semantically incorrect.

Would you like me to scan the PR branch to verify that ConnectUserRecordV16 is only used by the upgrader path and never for creating new users?

Comment on lines +79 to +95
public static ConnectUserRecordV16 fromV14(ConnectUserRecordV14 oldRecord) {
ConnectUserRecordV16 newRecord = new ConnectUserRecordV16();
newRecord.userId = oldRecord.getUserId();
newRecord.password = oldRecord.getPassword();
newRecord.name = oldRecord.getName();
newRecord.primaryPhone = oldRecord.getPrimaryPhone();
newRecord.alternatePhone = "";
newRecord.registrationPhase = oldRecord.getRegistrationPhase();
newRecord.lastPasswordDate = oldRecord.getLastPasswordDate();
newRecord.connectToken = oldRecord.getConnectToken();
newRecord.connectTokenExpiration = oldRecord.getConnectTokenExpiration();
newRecord.secondaryPhoneVerified = true;
newRecord.photo = oldRecord.getPhoto();
newRecord.isDemo = oldRecord.isDemo();
newRecord.requiredLock = PersonalIdSessionData.PIN;
return newRecord;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Migration currently loses secondary phone verification state and deadline. Preserve values from V14 instead of forcing defaults.

Forcing secondaryPhoneVerified = true and leaving verifySecondaryPhoneByDate at constructor default overwrites existing user data and can change behavior. Even if deprecated, preserving fidelity during migration avoids unintended side effects.

Apply this diff:

     public static ConnectUserRecordV16 fromV14(ConnectUserRecordV14 oldRecord) {
         ConnectUserRecordV16 newRecord = new ConnectUserRecordV16();
         newRecord.userId = oldRecord.getUserId();
         newRecord.password = oldRecord.getPassword();
         newRecord.name = oldRecord.getName();
         newRecord.primaryPhone = oldRecord.getPrimaryPhone();
         newRecord.alternatePhone = "";
         newRecord.registrationPhase = oldRecord.getRegistrationPhase();
         newRecord.lastPasswordDate = oldRecord.getLastPasswordDate();
         newRecord.connectToken = oldRecord.getConnectToken();
         newRecord.connectTokenExpiration = oldRecord.getConnectTokenExpiration();
-        newRecord.secondaryPhoneVerified = true;
+        // Preserve legacy state for deprecated fields to avoid behavior changes during migration
+        newRecord.secondaryPhoneVerified = oldRecord.isSecondaryPhoneVerified();
+        newRecord.verifySecondaryPhoneByDate = oldRecord.getVerifySecondaryPhoneByDate();
         newRecord.photo = oldRecord.getPhoto();
         newRecord.isDemo = oldRecord.isDemo();
         newRecord.requiredLock = PersonalIdSessionData.PIN;
         return newRecord;
     }

If the intent is to discard these fields entirely in v17+, consider explicitly nulling them (and marking them nullable) or removing them during the v16→v17 transformation instead of mutating values here.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static ConnectUserRecordV16 fromV14(ConnectUserRecordV14 oldRecord) {
ConnectUserRecordV16 newRecord = new ConnectUserRecordV16();
newRecord.userId = oldRecord.getUserId();
newRecord.password = oldRecord.getPassword();
newRecord.name = oldRecord.getName();
newRecord.primaryPhone = oldRecord.getPrimaryPhone();
newRecord.alternatePhone = "";
newRecord.registrationPhase = oldRecord.getRegistrationPhase();
newRecord.lastPasswordDate = oldRecord.getLastPasswordDate();
newRecord.connectToken = oldRecord.getConnectToken();
newRecord.connectTokenExpiration = oldRecord.getConnectTokenExpiration();
newRecord.secondaryPhoneVerified = true;
newRecord.photo = oldRecord.getPhoto();
newRecord.isDemo = oldRecord.isDemo();
newRecord.requiredLock = PersonalIdSessionData.PIN;
return newRecord;
}
public static ConnectUserRecordV16 fromV14(ConnectUserRecordV14 oldRecord) {
ConnectUserRecordV16 newRecord = new ConnectUserRecordV16();
newRecord.userId = oldRecord.getUserId();
newRecord.password = oldRecord.getPassword();
newRecord.name = oldRecord.getName();
newRecord.primaryPhone = oldRecord.getPrimaryPhone();
newRecord.alternatePhone = "";
newRecord.registrationPhase = oldRecord.getRegistrationPhase();
newRecord.lastPasswordDate = oldRecord.getLastPasswordDate();
newRecord.connectToken = oldRecord.getConnectToken();
newRecord.connectTokenExpiration = oldRecord.getConnectTokenExpiration();
// Preserve legacy state for deprecated fields to avoid behavior changes during migration
newRecord.secondaryPhoneVerified = oldRecord.isSecondaryPhoneVerified();
newRecord.verifySecondaryPhoneByDate = oldRecord.getVerifySecondaryPhoneByDate();
newRecord.photo = oldRecord.getPhoto();
newRecord.isDemo = oldRecord.isDemo();
newRecord.requiredLock = PersonalIdSessionData.PIN;
return newRecord;
}
🤖 Prompt for AI Agents
In
app/src/org/commcare/android/database/connect/models/ConnectUserRecordV16.java
around lines 79 to 95, the migration from V14 currently forces
secondaryPhoneVerified = true and leaves verifySecondaryPhoneByDate at the
constructor default, which overwrites existing V14 state; instead copy the
secondary phone verification fields from the old record (e.g.,
newRecord.secondaryPhoneVerified = oldRecord.isSecondaryPhoneVerified();
newRecord.verifySecondaryPhoneByDate =
oldRecord.getVerifySecondaryPhoneByDate()) so the original verification flag and
deadline are preserved during migration; if the V14 getters differ, call the
appropriate getters or add them, and only default/null them here if the explicit
intent is to discard these fields (in which case make that behavior explicit and
documented).

Comment on lines +25 to +62
private fun checkConnectAccess(context: Context) {
if (!ConnectUserDatabaseUtil.hasConnectAccess(context)) {
throw IllegalStateException("Cannot navigate to Connect Jobs List without access")
}
}

fun goToConnectJobsListChecked(context: Context) {
if (ConnectUserDatabaseUtil.hasConnectAccess(context)) {
goToConnectJobsList(context)
}
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Throwing on missing access can crash legacy call sites; verify and consider a softer failure mode.

goToConnectJobsList and goToActiveInfoForJob now throw IllegalStateException via checkConnectAccess. If any legacy caller didn’t update to the checked variant or pre-gate, the app will crash.

Two-part recommendation:

  • Verify all call sites are either pre-gated or now use goToConnectJobsListChecked.
  • Consider returning early with logging (or using the checked variant exclusively) instead of throwing, to make navigation failures non-fatal in production.

Run to find call sites that may need updating:

If you prefer a non-throwing helper, here’s a diff to soften the behavior:

 private fun checkConnectAccess(context: Context) {
-    if (!ConnectUserDatabaseUtil.hasConnectAccess(context)) {
-        throw IllegalStateException("Cannot navigate to Connect Jobs List without access")
-    }
+    if (!ConnectUserDatabaseUtil.hasConnectAccess(context)) {
+        // Non-fatal guardrail; callers using the unchecked method won't crash.
+        // Consider logging here if you have a logger available.
+        return
+    }
 }

Optionally, extend the pattern to messaging to keep all Connect surfaces uniformly gated (outside the changed range):

// Additional change outside the selected lines
fun goToMessagingChecked(context: Context) {
    if (ConnectUserDatabaseUtil.hasConnectAccess(context)) {
        goToMessaging(context)
    }
}

Also applies to: 37-39


🏁 Script executed:

#!/bin/bash
# Find all direct calls to goToConnectJobsList and goToActiveInfoForJob
# Expectation: Calls either happen behind ConnectUserDatabaseUtil.hasConnectAccess checks
# or use goToConnectJobsListChecked.

set -e

echo "Kotlin call sites:"
rg -n "ConnectNavHelper\.(goToConnectJobsList|goToActiveInfoForJob)\(" -S -g '!**/build/**'

echo
echo "Java call sites:"
rg -n "ConnectNavHelper\.INSTANCE\.(goToConnectJobsList|goToActiveInfoForJob)\(" -S -g '!**/build/**'

Length of output: 149


🏁 Script executed:

#!/bin/bash
# Find all direct calls to goToConnectJobsList and goToActiveInfoForJob,
# but don't exit on no matches so we get both Kotlin and Java results.

echo "Kotlin call sites:"
rg -n "ConnectNavHelper\.(goToConnectJobsList|goToActiveInfoForJob)\(" -S -g '!**/build/**' || true

echo
echo "Java call sites:"
rg -n "ConnectNavHelper\.INSTANCE\.(goToConnectJobsList|goToActiveInfoForJob)\(" -S -g '!**/build/**' || true

Length of output: 834


🏁 Script executed:

#!/bin/bash
# Show context around each Java call site to see if they're already gated.

echo "CommCareSetupActivity.java context:"
rg -n -C3 "goToConnectJobsList" app/src/org/commcare/activities/CommCareSetupActivity.java

echo
echo "LoginActivity.java context:"
rg -n -C3 "goToConnectJobsList" app/src/org/commcare/activities/LoginActivity.java

echo
echo "DispatchActivity.java context (goToConnectJobsList):"
rg -n -C3 "goToConnectJobsList" app/src/org/commcare/activities/DispatchActivity.java

echo
echo "DispatchActivity.java context (goToActiveInfoForJob):"
rg -n -C3 "goToActiveInfoForJob" app/src/org/commcare/activities/DispatchActivity.java

Length of output: 2305


Ensure Connect navigation calls are gated or use the “Checked” variants

We found two unguarded calls in DispatchActivity that will throw IllegalStateException at runtime if the user lacks connect access:

  • app/src/org/commcare/activities/DispatchActivity.java:
    • Line 208: ConnectNavHelper.INSTANCE.goToConnectJobsList(this);
    • Line 212: ConnectNavHelper.INSTANCE.goToActiveInfoForJob(this, job, true);

These should be guarded with ConnectUserDatabaseUtil.hasConnectAccess(context) or swapped to the checked helpers:

-    ConnectNavHelper.INSTANCE.goToConnectJobsList(this);
+    if (ConnectUserDatabaseUtil.hasConnectAccess(this)) {
+        ConnectNavHelper.INSTANCE.goToConnectJobsList(this);
+    }

-    ConnectNavHelper.INSTANCE.goToActiveInfoForJob(this, job, true);
+    if (ConnectUserDatabaseUtil.hasConnectAccess(this)) {
+        ConnectNavHelper.INSTANCE.goToActiveInfoForJob(this, job, true);
+    }

Alternatively, call the checked variants:

ConnectNavHelper.INSTANCE.goToConnectJobsListChecked(this);
ConnectNavHelper.INSTANCE.goToActiveInfoForJobChecked(this, job, true);

Note: Other sites (in CommCareSetupActivity and LoginActivity) invoke navigation only after unlockConnect(success → …), so they’re already gated.

🤖 Prompt for AI Agents
In app/src/org/commcare/activities/DispatchActivity.java around lines 208–212,
two unguarded ConnectNavHelper calls will throw when the user lacks connect
access; replace each direct call with either a guard using
ConnectUserDatabaseUtil.hasConnectAccess(context) before invoking the current
method, or call the checked variants instead: use
ConnectNavHelper.INSTANCE.goToConnectJobsListChecked(this) in place of
goToConnectJobsList(this), and use
ConnectNavHelper.INSTANCE.goToActiveInfoForJobChecked(this, job, true) in place
of goToActiveInfoForJob(this, job, true); ensure any added guard uses the
current activity/context reference consistently.

Comment on lines 61 to 63
*/
private static final int CONNECT_DB_VERSION = 16;
private static final int CONNECT_DB_VERSION = 17;

Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

DB bump to 17: verify upgrade path and align changelog

  • Version bump and note look correct. Please ensure ConnectDatabaseUpgrader implements a robust 16→17 migration that adds has_connect_access (and infers it per spec).
  • Changelog jumps from V.14 to V.16. If V.15 existed, consider adding a brief V.15 note for consistency.

Run this script to verify upgrade/migration artifacts exist and reference has_connect_access:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate ConnectDatabaseUpgrader and check for 16->17 logic"
fd -HI ConnectDatabaseUpgrader.java | sed -n '1,200p'
rg -n "onUpgrade|upgrade\\(|17|16|has_connect_access|V17" -A 3 -B 3

echo
echo "Confirm ConnectUserRecord has has_connect_access column mapping"
fd -HI ConnectUserRecord.java
rg -n "has_connect_access|hasConnectAccess" -A 2 -B 2

echo
echo "Check presence of V16 model used for migration"
fd -HI "ConnectUserRecordV16.java"
rg -n "ConnectUserRecordV16|fromV16|fromV14" -A 2 -B 2

Length of output: 307548


To verify the 16→17 migration and confirm has_connect_access is handled correctly, let’s dump the upgrader and related classes and search for relevant patterns:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== 1) Inspect ConnectDatabaseUpgrader ==="
sed -n '1,200p' app/src/org/commcare/models/database/connect/ConnectDatabaseUpgrader.java

echo
echo "=== 2) Look for 16→17 logic in upgrader ==="
grep -nE 'case\s+16|case\s+17|upgrade.*16|upgrade.*17' \
  app/src/org/commcare/models/database/connect/ConnectDatabaseUpgrader.java || echo "no matches"

echo
echo "=== 3) Search for has_connect_access across repo ==="
grep -Rn "has_connect_access" .

echo
echo "=== 4) Check ConnectUserRecord mapping ==="
grep -Rn "has_connect_access" \
  app/src/org/commcare/models/database/connect/ConnectUserRecord.java || echo "no mapping"

echo
echo "=== 5) Verify presence of V16 model ==="
find app/src -type f -name "ConnectUserRecordV16.java" -print
grep -Rn "class ConnectUserRecordV16" app/src || echo "V16 model not found"

Length of output: 9401


Let’s inspect the 16→17 upgrader and the ConnectUserRecord classes to confirm the new column and migration logic:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== 1) Show upgradeSixteenSeventeen in ConnectDatabaseUpgrader ==="
grep -R -n "upgradeSixteenSeventeen" -A20 app/src/org/commcare/models/database/connect/ConnectDatabaseUpgrader.java

echo
echo "=== 2) Verify has_connect_access in current ConnectUserRecord ==="
grep -n "has_connect_access" -A5 -B5 app/src/org/commcare/android/database/connect/models/ConnectUserRecord.java

echo
echo "=== 3) Confirm V16 model lacks this column ==="
grep -n "has_connect_access" -A5 -B5 app/src/org/commcare/android/database/connect/models/ConnectUserRecordV16.java

Length of output: 2667


Let’s locate the actual ConnectUserRecord.java and check how (or if) it maps the new column:

This will confirm whether the new boolean column is declared and migrated in the V16→V17 path.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Locate ConnectUserRecord.java ==="
fd -HI ConnectUserRecord.java

echo
echo "=== Inspect its fields (first 200 lines) ==="
sed -n '1,200p' app/src/org/commcare/android/database/connect/models/ConnectUserRecord.java

echo
echo "=== Look for hasConnectAccess in that file ==="
grep -Rn "hasConnectAccess" app/src/org/commcare/android/database/connect/models/ConnectUserRecord.java || echo "no getter/setter for hasConnectAccess"

echo
echo "=== Confirm V16 has no such field ==="
grep -Rn "hasConnectAccess" app/src/org/commcare/android/database/connect/models/ConnectUserRecordV16.java || echo "V16 unchanged"

Length of output: 6926


Confirm 16→17 migration and fix changelog consistency

  • The upgradeSixteenSeventeen method in ConnectDatabaseUpgrader
    • Reads existing job records and infers hasConnectAccess = jobStorage.getNumRecords() > 0
    • Loads all ConnectUserRecordV16, calls ConnectUserRecord.fromV16(old, hasConnectAccess), and writes the new ConnectUserRecord
  • The ConnectUserRecord class defines
    @Persisting(16) private boolean hasConnectAccess;
    • A fromV16(…, boolean) factory that assigns hasConnectAccess
    • Public hasConnectAccess() getter and setHasConnectAccess(...) setter
  • ConnectUserRecordV16 has no hasConnectAccess field, as expected
  • Changelog in DatabaseConnectOpenHelper.java jumps from V.14 to V.16. Since there is an upgradeFourteenFifteen step, please add a brief V.15 entry (e.g. “V15 – <summary of 14→15 changes>”) for consistency.

@shubham1g5 shubham1g5 marked this pull request as ready for review August 14, 2025 13:23
<string name="nav_drawer_close">Close navigation drawer</string>

<string name="setup_refresh_opportunities_no_jobs">No Opportunities found</string>
<string name="setup_refresh_opportunities_with_jobs">"New opportunities found, click on Opportunities to learn more. "</string>
Copy link
Contributor

Choose a reason for hiding this comment

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

Extra quotes in the string

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@shubham1g5 shubham1g5 merged commit 8f3e668 into commcare_2.59 Aug 15, 2025
2 checks passed
@shubham1g5 shubham1g5 deleted the connectVisibility branch August 15, 2025 02:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants