Skip to content

CCCT-2054 - UUIDs are replaced for int id for Connect#3535

Open
Jignesh-dimagi wants to merge 16 commits intomasterfrom
jignesh/feat/ccct-2054
Open

CCCT-2054 - UUIDs are replaced for int id for Connect#3535
Jignesh-dimagi wants to merge 16 commits intomasterfrom
jignesh/feat/ccct-2054

Conversation

@Jignesh-dimagi
Copy link
Contributor

@Jignesh-dimagi Jignesh-dimagi commented Feb 11, 2026

Product Description

This PR doesn't have direct UI changes, but it has changes which can affect the UI as data models are changed.

Technical Summary

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

The aim of these changes is that the application should start using the UUID instead of the integer ID. This would apply to:

  • Local DB models

  • API calling

  • Parsing the API response and storing the new UUID

  • Using UUID for fetching any data object and not using Int ID

Concept used for migration:

  • Whenever an application is upgraded from an older version, it will not have a UUID but only an Int ID. The application will default all UUIDs to their corresponding Int IDs.

  • The application will be using these UUIDs only for all the operations, as their default value is set to Int Id.

    • The entire app is still using the Int Id value only but through the UUID field.
  • Now, whenever an application has a new UUID field value for ConnectJobRecord, which is the main record for referencing the Connect opportunities, all the other models are updated for its UUID in the jobUUID field. So now the application has a uniform UUID record for opportunity.

    • The entire app will start using the new UUID field value.
  • The entire app is using the UUID field to set, get and update the data models, as the application has the UUID field value always there, which can be the default Int Id or the new UUID actual value.

  • API calls are made using the UUID field only for the reason as stated in the above point.

Safety Assurance

Safety story

I have tested the application in the following scenarios:

  1. Installing the 2.61 version and downloading the One Learn and Delivery app. Also, sends the push notification

  2. Upgrading the version to these changes, and checking

  • DB migrations are good.

  • CommCare apps are still being used from the left navigation drawer without any issue.

  • Push notifications also working fine

  • Fetching the opportunities, installing the Learn and Delivery app from there and being able to sync the form successfully.

  • Connect opportunity showing correct job status, payments and payment units

  • Sending a new push notification, navigating to the correct page after clicking on it from the device tray. The same is true for push notifications from the notification screen.

QA Plan

QA should upgrade to these version from older and make sure that every thing is working.

Note: Due to some missing UUIDs in the production server, the app is working for the staging server correctly as of now. The web team will upload these changes on 23rd Feb, so it should be a good test on the production server after that.

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
Contributor

coderabbitai bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

This pull request migrates the Connect module from integer-based job and payment unit identifiers to string-based UUID identifiers. Changes span model classes, database schema and migrations, API methods, fragments, and helper utilities. Key modifications include: ConnectActivity now extracts OPPORTUNITY_UUID instead of OPPORTUNITY_ID from intents; database models (ConnectAppRecord, ConnectDeliveryDetails, ConnectJobDeliveryRecord, etc.) expose UUID accessors and update schema versioning from 22 to 23; API methods update parameter types from int jobId to String jobUUID; helper utilities and fragments update job lookups to use jobUUID; push notification handling switches to UUID-based payload keys. A migration helper ConnectJobDeliveryRecordV22 is introduced to support the schema transition.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • PR #3522 — Directly applies the same UUID migration pattern across Connect models, replacing numeric IDs with UUID fields and updating all dependent code paths.
  • PR #3353 — Modifies push notification model and payload to replace numeric opportunity/payment identifiers with string UUIDs.
  • PR #3040 — Updates ConnectJobDeliveryRecord schema and migration logic with delivery-related flags and database version handling.

Suggested reviewers

  • shubham1g5
  • conroy-ricketts
  • OrangeAndGreen
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'UUIDs are replaced for int id for Connect' accurately describes the main change: replacing integer IDs with UUIDs across Connect-related components. It is specific, clear, and directly summarizes the primary objective.
Description check ✅ Passed The PR description covers required sections: Product Description (explains non-UI data model changes), Technical Summary (references CCCT-2054 ticket, describes UUID migration strategy), Safety Assurance (testing scenarios, upgrade verification, and known limitations), and QA Plan. All major sections are present and adequately detailed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jignesh/feat/ccct-2054

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@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: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
app/src/org/commcare/android/database/connect/models/ConnectPaymentUnitRecord.java (1)

29-29: ⚠️ Potential issue | 🟡 Minor

Misleading constant name: META_PAYMENT_UNIT_UUID suggests UUID but contains a string ID.

The constant name and field name (unitUUID) both suggest this is a UUID, but line 94 reveals it's actually String.valueOf(connectPaymentUnitRecordV21.getUnitId()) — a string representation of an integer ID, not a UUID format. Consider renaming META_PAYMENT_UNIT_UUID to META_PAYMENT_UNIT_ID and the field from unitUUID to unitId (or similar) for consistency and clarity, or add a comment explaining that this field stores a stringified integer identifier despite its name.

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

90-91: ⚠️ Potential issue | 🔴 Critical

Line 91 uses the wrong constant to parse paymentUUID.

The paymentUUID field is defined with @MetaField(META_PAYMENT_UUID) (line 66), which maps to the JSON key "payment_uuid". However, line 91 reads from META_PAYMENT_ID ("payment_id") instead. This will cause the UUID field to be populated with the wrong value from the server response. The META_PAYMENT_UUID constant exists specifically for this purpose (line 30) and is consistent with field definitions and server API contract.

🐛 Proposed fix
         payment.paymentId = json.getString("id");
-        payment.paymentUUID = json.getString(META_PAYMENT_ID);
+        payment.paymentUUID = json.getString(META_PAYMENT_UUID);
app/src/org/commcare/android/database/connect/models/ConnectDeliveryDetails.java (1)

10-22: ⚠️ Potential issue | 🟠 Major

Missing getter (and setter) for unitUUID.

The field is assigned in the constructor but never exposed. Every other field in this class has a matching getter/setter pair—this is an inconsistency that leaves unitUUID inaccessible after construction.

Proposed fix
     public void setApprovedPercentage(double approvedPercentage) {
         this.approvedPercentage = approvedPercentage;
     }
+
+    public String getUnitUUID() {
+        return unitUUID;
+    }
+
+    public void setUnitUUID(String unitUUID) {
+        this.unitUUID = unitUUID;
+    }
 }
app/src/org/commcare/android/database/connect/models/ConnectJobDeliveryRecord.java (1)

179-196: ⚠️ Potential issue | 🟠 Major

slugUUID is assigned from oldRecord.getSlug() instead of a UUID value.

Line 193 assigns newRecord.slugUUID = oldRecord.getSlug(), which populates a field documented as "This is payment unit uuid" (line 78) with a human-readable slug string. The V22 model does not have a slugUUID field—only slug. However, assigning the slug (payment unit ID per line 60) to a field named slugUUID that should contain a server-assigned UUID creates semantic confusion and inconsistency with the field's documented purpose.

The fromJson() method (line 105) correctly populates slugUUID from server data (json.getString(META_SLUG_UUID)), which will provide proper UUIDs during normal synchronization. During migration from V22, consider explicitly documenting whether this slug-to-UUID assignment is intentional as a temporary placeholder, or if the field should remain uninitialized/null until the server provides actual UUID values.

🤖 Fix all issues with AI agents
In
`@app/src/org/commcare/android/database/connect/models/ConnectJobDeliveryRecordV22.kt`:
- Around line 70-84: Rename the factory method
ConnectJobDeliveryRecordV22.fromV22 to fromV21 (it currently accepts a
ConnectJobDeliveryRecordV21 and returns ConnectJobDeliveryRecordV22), update its
declaration and all callers to use fromV21 (e.g. the invocation in
ConnectDatabaseUpgrader), and run a compile to fix any remaining references;
keep the implementation identical and only change the method name to reflect it
converts from V21 to V22.

In `@app/src/org/commcare/android/database/connect/models/ConnectJobRecord.java`:
- Around line 399-406: The query is comparing
PushNotificationRecord.META_OPPORTUNITY_ID (a String column) against jobId (an
int), causing a type mismatch; change the query to pass a String representation
of jobId (e.g., String.valueOf(jobId)) when calling
notificationStorage.getRecordsForValues so the WHERE binds use the correct type,
then continue to call pushNotificationRecord.setOpportunityUUID(jobUUID) and
notificationStorage.write(pushNotificationRecord) as before to persist the
migration.
- Around line 205-209: The fromJson(JSONObject) parser in ConnectJobRecord
currently calls migrateOtherModelsForUUID(job.jobId, job.jobUUID), which causes
heavy DB reads/writes during JSON parsing; remove that call from
ConnectJobRecord.fromJson so parsing has no side effects, and instead invoke
migrateOtherModelsForUUID from the storage layer where DB operations belong (for
example inside JobStoreManager.storeOrUpdateJob or the method that persists
ConnectJobRecord). Update callers to ensure migration runs only when
storing/updating a job and add tests to validate parsing without DB access and
migration execution during persistence.
- Around line 345-352: The storage query for ConnectLearnModuleSummaryRecord
uses the wrong constant: replace ConnectAppRecord.META_JOB_ID with
ConnectLearnModuleSummaryRecord.META_JOB_ID in the
moduleStorage.getRecordsForValues call so the query uses the correct
record-class constant (leave the loop that calls
connectLearnModuleSummaryRecord.setJobUUID(jobUUID) and moduleStorage.write(...)
unchanged).
- Around line 305-408: migrateOtherModelsForUUID is being invoked on every
fromJson parse and currently queries/writes 9 tables; to avoid the heavy work,
add an early idempotency check right after fetching connectJobRecords from
connectJobStorage: inspect the matching ConnectJobRecord(s) (from
connectJobRecords) and if any existing record already has getJobUUID() equal to
the incoming jobUUID, return immediately without touching other storages; keep
this check inside migrateOtherModelsForUUID (referencing connectJobStorage,
connectJobRecords and fromJson where it's called) so normal migration runs only
when the stored job UUID differs from the incoming UUID.

In `@app/src/org/commcare/models/database/connect/ConnectDatabaseUpgrader.java`:
- Around line 958-987: The migration in upgradeTwentyTwoTwentyThree (which calls
upgradeConnectJobDeliveryRecordToV23) performs multiple DB operations but lacks
a transaction; wrap the entire body of upgradeConnectJobDeliveryRecordToV23 in a
transaction by calling db.beginTransaction() before altering the
schema/iterating writes, call db.setTransactionSuccessful() after all operations
succeed, and ensure db.endTransaction() runs in a finally block so partial work
is rolled back on failure and the subsequent retry won't re-run a partially
applied migration.

In `@app/src/org/commcare/pn/workermanager/NotificationsSyncWorkerManager.kt`:
- Around line 230-237: The variable name oppotunityUUID is misspelled; rename it
to opportunityUUID in both startLearningSyncWorker and startDeliverySyncWorker,
update all usages (including the check TextUtils.isEmpty(opportunityUUID), the
cccCheckPassed call, and the startWorkRequest tag concatenation that uses
"-$oppotunityUUID"), and ensure any other occurrences in this file are similarly
corrected so references and string interpolation match the new identifier.
🧹 Nitpick comments (6)
app/src/org/commcare/models/connect/ConnectLoginJobListModel.java (1)

214-220: Nit: toString still labels the field as "id=".

Consider updating the label to "uuid=" for consistency with the renamed field.

Proposed fix
         return "AddListItem{" +
                 "name='" + name + '\'' +
-                ", id=" + uuid +
+                ", uuid=" + uuid +
                 ", date=" + date +
app/src/org/commcare/android/database/connect/models/ConnectJobDeliveryRecord.java (1)

103-105: delivery.reason is assigned twice (pre-existing).

Line 103 assigns json.getString(META_REASON) and line 104 immediately overwrites it with optStringSafe(...). The first assignment is dead code. Not introduced by this PR, but it's directly adjacent to the new line 105.

Proposed cleanup
-        delivery.reason = json.getString(META_REASON);
         delivery.reason = JsonExtensions.optStringSafe(json, META_REASON,"");
         delivery.slugUUID = json.getString(META_SLUG_UUID);
app/src/org/commcare/android/database/connect/models/ConnectJobDeliveryRecordV22.kt (1)

40-42: Minor: unitName defaults to "" while other nullable fields default to null.

All other nullable String fields (status, slug, entityId, entityName, reason, jobUUID) default to null, but unitName defaults to "". This inconsistency could cause subtle issues if null-checking logic is used elsewhere.

app/unit-tests/src/org/commcare/android/tests/processing/FormStorageTest.java (1)

417-418: New externalizable classes registered correctly.

Both PushNotificationRecordV21 and ConnectJobDeliveryRecordV22 are added to the history list.

Minor nit: consider adding a version comment (e.g., // Added in 2.XX) above these entries to match the existing pattern used throughout the list.

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

68-68: Version bump to 23 is correct.

Consider adding a V.23 entry to the version comment block above (lines 44-67) to document the addition of slugUUID to ConnectJobDeliveryRecord, following the existing documentation pattern.

app/src/org/commcare/pn/workermanager/NotificationsSyncWorkerManager.kt (1)

4-4: Prefer Kotlin-idiomatic isNullOrEmpty() over TextUtils.isEmpty().

In Kotlin, you can use notificationPayload[OPPORTUNITY_UUID].isNullOrEmpty() instead of importing android.text.TextUtils. This is more idiomatic and avoids the Android framework dependency in a Kotlin file.

@Jignesh-dimagi Jignesh-dimagi changed the title UUIDs are replaced for int id for Connect CCCT-2054 - UUIDs are replaced for int id for Connect Feb 11, 2026
@conroy-ricketts
Copy link
Contributor

@Jignesh-dimagi
Copy link
Contributor Author

Hey @Jignesh-dimagi, I'm seeing a bit of an odd issue with these changes. The opportunities list is failing to load a few of my opportunities:

Here are the links to the failing opportunities if it helps:

Yes @conroy-ricketts. It's not fully working with production server as mentioned in the description of this ticket. You can check with Staging, where it's working fine.

Note: Due to some missing UUIDs in the production server, the app is working for the staging server correctly as of now. The web team will upload these changes on 23rd Feb, so it should be a good test on the production server after that.

@conroy-ricketts
Copy link
Contributor

@Jignesh-dimagi Ah ok I missed that thanks, sounds good!

ConnectJobRecord job = new ConnectJobRecord();

job.jobId = json.getInt(META_JOB_ID);
job.jobId = json.getInt(META_JOB_ID); // This will be eventually removed
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of a comment here, can we add a @deprecated to all the old model fields so that it's obvious to anyone writing code to not use these fields.

Copy link
Contributor Author

@Jignesh-dimagi Jignesh-dimagi Feb 17, 2026

Choose a reason for hiding this comment

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

@shubham1g5 I was thinking even of changing it to json.optInt(META_JOB_ID); so if the server stops sending it in the future, our app can keep running without issue. This will be applicable at some other places too. Your thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 to both of the above ideas.

boolean isExisting = false;
for (ConnectJobRecord existingJob : existingJobs) {
if (existingJob.getJobId() == job.getJobId()) {
if (existingJob.getJobUUID().equals(job.getJobUUID())) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We can't guarantee here that the uuid stored in mobile is stored as the old id or the new uuid and think we need to instead check

if (existingJob.getJobUUID().equals(job.getJobUUID()) || existingJob.getJobUUID().equals(job.getJobIId()) 

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As per the solution adapted here, every object including existing DB should have same value of uuid.

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 I did test the flow before and after; it seems it's working after changes were made as per our offline talk.

* @param jobUUID
*/
private static void migrateOtherModelsForJobUUID(int jobId, String jobUUID) {
if (jobId != -1 && !TextUtils.isEmpty(jobUUID)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

can this condition be false ever besides a bug ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This can be false if server stops sending jobId in future.

Copy link
Contributor

Choose a reason for hiding this comment

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

got it, and what about !TextUtils.isEmpty(jobUUID) ? Can that be empty ever ?

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 It cannot be now, as the server has made the necessary changes. It was at the time when I was developing this code. Not sure if we want to handle it or not.

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 Removed the check.

Copy link
Contributor

@conroy-ricketts conroy-ricketts left a comment

Choose a reason for hiding this comment

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

I don't think I have any additional concerns to add here 👍

OrangeAndGreen
OrangeAndGreen previously approved these changes Feb 17, 2026
Copy link
Contributor

@OrangeAndGreen OrangeAndGreen left a comment

Choose a reason for hiding this comment

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

Left a few things to consider but nothing I would consider blocking. Well done on a pretty big lift for this one!

ConnectJobRecord job = new ConnectJobRecord();

job.jobId = json.getInt(META_JOB_ID);
job.jobId = json.getInt(META_JOB_ID); // This will be eventually removed
Copy link
Contributor

Choose a reason for hiding this comment

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

+1 to both of the above ideas.

* @param jobId
* @param jobUUID
*/
private static void migrateOtherModelsForJobUUID(int jobId, String jobUUID) {
Copy link
Contributor

Choose a reason for hiding this comment

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

A couple thoughts on this method, nothing crucial:

  1. It seems a bit unfortunate to add a bunch of code to this class which is only relevant for a limited time, or at least to a limited scope. I wonder if it would make more sense to put it in something like ConnectDatabaseUpgrader? This would be a deviation from the existing pattern in that class, but seems like it makes more sense there in the long-term so it isn't nearly doubling the size of this class for code that we know will eventually be outdated.

  2. It also seems unfortunate that this code will run every time the jobs API call succeeds and for every received job (running a query for each job to see no migration necessary), when in reality the upgrade process only needs to happen once (or maybe never).
    A couple ideas:
    2a. We could do one check before going into the loop for all jobs, and see if we have a job in storage that needs to be upgraded (i.e. check any job and I think see if jobId==jobUUID). If any job has been upgraded then they all have, and we can skip the additional queries
    2b. We could also employ a simple SharedPref to remember that we did the migration already (including new versions that never needed to do a migration).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@OrangeAndGreen Yeah, this was chosen to make the code less complex to handle UUID vs ID. Now with these migrations, we are sure that values of Job UUID are uniform across the models, which is the main source of fetching any record. This will reduce the ambiguity which may arise due to different values of UUID in different models.

  1. ConnectDatabaseUpgrader is mostly used for managing the structure / schema of the database and not for storing the real-time values. Also, during the V2 version of UUID changes, it is intended to remove all ID-related stuff, in which removing this function is also planned.

  2. Your point is absolutely correct and makes sense. We already have that mechanism in place to not run the migration code multiple times. Once the migrations are done, this code will prevent doing it again. So this is one time operation only.


// migrate ConnectJobRecord
for (ConnectJobRecord connectJobRecord : connectJobRecords) {
connectJobRecord.setJobUUID(jobUUID);
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Given the goal of no longer exposing the PK IDs to users, is there any safety gain in resetting that value here so it doesn't linger in the DB?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure if I understand your question, but its lifecycle is the same as ConnectJobRecord int ID only. It will be removed once the record is no longer linked to the user.

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.

4 participants