diff --git a/.gitignore b/.gitignore index 0a4980a..2ddff4e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ node_modules/ yarn.lock .sfdx/ +.sf/ .localdevserver/ debug.log DEVHUB_SFDX_URL.txt diff --git a/README.md b/README.md index 384e4f4..fa479de 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,12 @@ ## Deployment - + Deploy to Salesforce - + Deploy to Salesforce Sandbox diff --git a/core/classes/AbstractCacheRepo.cls-meta.xml b/core/classes/AbstractCacheRepo.cls-meta.xml index e7009f9..45cccbd 100644 --- a/core/classes/AbstractCacheRepo.cls-meta.xml +++ b/core/classes/AbstractCacheRepo.cls-meta.xml @@ -1,5 +1,5 @@ - 54.0 + 57.0 Active \ No newline at end of file diff --git a/core/classes/FlowRoundRobinAssigner.cls b/core/classes/FlowRoundRobinAssigner.cls index f017caf..90c29b4 100644 --- a/core/classes/FlowRoundRobinAssigner.cls +++ b/core/classes/FlowRoundRobinAssigner.cls @@ -26,10 +26,15 @@ global without sharing class FlowRoundRobinAssigner { @InvocableMethod(category='Round Robin' label='Round robin records') global static void assign(List flowInputs) { - for (FlowInput input : flowInputs) { - if (input.recordsToRoundRobin?.isEmpty() == false || input.recordToRoundRobin != null) { - SELF.trackAssignedIds(input); - SELF.roundRobin(input); + if (hasBeenUpdated == false) { + for (FlowInput input : flowInputs) { + if (input.recordsToRoundRobin?.isEmpty() != false && input.recordToRoundRobin != null) { + input.recordsToRoundRobin = new List{ input.recordToRoundRobin }; + } + if (input.recordsToRoundRobin.isEmpty() == false) { + SELF.trackAssignedIds(input); + SELF.roundRobin(input); + } } } } diff --git a/core/classes/FlowRoundRobinAssigner.cls-meta.xml b/core/classes/FlowRoundRobinAssigner.cls-meta.xml index 40d6793..754ecb1 100644 --- a/core/classes/FlowRoundRobinAssigner.cls-meta.xml +++ b/core/classes/FlowRoundRobinAssigner.cls-meta.xml @@ -1,5 +1,5 @@ - 54.0 + 57.0 Active diff --git a/core/classes/FlowRoundRobinAssignerTests.cls b/core/classes/FlowRoundRobinAssignerTests.cls index 6e3691a..3c41b53 100644 --- a/core/classes/FlowRoundRobinAssignerTests.cls +++ b/core/classes/FlowRoundRobinAssignerTests.cls @@ -1,6 +1,8 @@ @IsTest private class FlowRoundRobinAssignerTests { - static final User FAKE_USER = new User(Id = User.SObjectType.getDescribe().getKeyPrefix() + '0'.repeat(12)); + static final User FAKE_USER = new User( + Id = User.SObjectType.getDescribe(SObjectDescribeOptions.DEFERRED).getKeyPrefix() + '0'.repeat(12) + ); @IsTest static void defaultAssignmentToOwnerId() { diff --git a/core/classes/FlowRoundRobinAssignerTests.cls-meta.xml b/core/classes/FlowRoundRobinAssignerTests.cls-meta.xml index 40d6793..754ecb1 100644 --- a/core/classes/FlowRoundRobinAssignerTests.cls-meta.xml +++ b/core/classes/FlowRoundRobinAssignerTests.cls-meta.xml @@ -1,5 +1,5 @@ - 54.0 + 57.0 Active diff --git a/core/classes/IThreadSafeCacheVisitor.cls-meta.xml b/core/classes/IThreadSafeCacheVisitor.cls-meta.xml index e7009f9..45cccbd 100644 --- a/core/classes/IThreadSafeCacheVisitor.cls-meta.xml +++ b/core/classes/IThreadSafeCacheVisitor.cls-meta.xml @@ -1,5 +1,5 @@ - 54.0 + 57.0 Active \ No newline at end of file diff --git a/core/classes/MockUserAssignmentRepo.cls-meta.xml b/core/classes/MockUserAssignmentRepo.cls-meta.xml index 40d6793..754ecb1 100644 --- a/core/classes/MockUserAssignmentRepo.cls-meta.xml +++ b/core/classes/MockUserAssignmentRepo.cls-meta.xml @@ -1,5 +1,5 @@ - 54.0 + 57.0 Active diff --git a/core/classes/QueryAssigner.cls-meta.xml b/core/classes/QueryAssigner.cls-meta.xml index 40d6793..754ecb1 100644 --- a/core/classes/QueryAssigner.cls-meta.xml +++ b/core/classes/QueryAssigner.cls-meta.xml @@ -1,5 +1,5 @@ - 54.0 + 57.0 Active diff --git a/core/classes/RoundRobinAssigner.cls-meta.xml b/core/classes/RoundRobinAssigner.cls-meta.xml index e7009f9..45cccbd 100644 --- a/core/classes/RoundRobinAssigner.cls-meta.xml +++ b/core/classes/RoundRobinAssigner.cls-meta.xml @@ -1,5 +1,5 @@ - 54.0 + 57.0 Active \ No newline at end of file diff --git a/core/classes/RoundRobinAssignerTests.cls b/core/classes/RoundRobinAssignerTests.cls index e4b719e..dc21aeb 100644 --- a/core/classes/RoundRobinAssignerTests.cls +++ b/core/classes/RoundRobinAssignerTests.cls @@ -63,7 +63,7 @@ private class RoundRobinAssignerTests { static List createUsersForAssignment(String assignmentType) { List users = new List(); - String baseIdString = Schema.User.SObjectType.getDescribe().getKeyPrefix() + '0'.repeat(11); + String baseIdString = Schema.User.SObjectType.getDescribe(SObjectDescribeOptions.DEFERRED).getKeyPrefix() + '0'.repeat(11); for (Integer index = 0; index < 2; index++) { users.add(new User(Department = assignmentType, Id = baseIdString + (index + 1))); } diff --git a/core/classes/RoundRobinAssignerTests.cls-meta.xml b/core/classes/RoundRobinAssignerTests.cls-meta.xml index e7009f9..45cccbd 100644 --- a/core/classes/RoundRobinAssignerTests.cls-meta.xml +++ b/core/classes/RoundRobinAssignerTests.cls-meta.xml @@ -1,5 +1,5 @@ - 54.0 + 57.0 Active \ No newline at end of file diff --git a/core/classes/RoundRobinRepository.cls b/core/classes/RoundRobinRepository.cls index 81b5680..75d54e2 100644 --- a/core/classes/RoundRobinRepository.cls +++ b/core/classes/RoundRobinRepository.cls @@ -1,6 +1,7 @@ public without sharing class RoundRobinRepository extends AbstractCacheRepo { private static Map CACHED_ASSIGNMENTS; + @SuppressWarnings('PMD.ApexCRUDViolation') public void accept(IThreadSafeCacheVisitor visitor, List records) { RoundRobin__c currentAssignment = this.getCurrentAssignment(visitor.getVisitKey()); visitor.visitRecords(records, currentAssignment); @@ -46,56 +47,39 @@ public without sharing class RoundRobinRepository extends AbstractCacheRepo { @SuppressWarnings('PMD.ApexCRUDViolation') private Boolean commitUpdatedAssignment(RoundRobin__c assignment) { + Boolean wasCommitSuccessful = true; Map currentCache = this.getCachedAssignments(); if ( currentCache.containsKey(assignment.Name) && currentCache.get(assignment.Name).LastUpdated__c > CACHED_ASSIGNMENTS.get(assignment.Name).LastUpdated__c ) { - return false; - } - assignment.LastUpdated__c = System.now(); - /** - * integration tests with after save Flows have shown something unfortunate: - * though the second (recursive) call to the assigner is spawned in a second transaction - * the RoundRobin__c.getAll() call still doesn't contain the Id of the inserted record (for the times where the assignment - * is being run for the first time). - * That means that we can't just call "upsert", and instead have to do this goofy - * song and dance to ensure the Id is appended correctly - */ - if (assignment.Id == null) { - List existingAssignments = [SELECT Id FROM RoundRobin__c WHERE Name = :assignment.Name]; - if (existingAssignments.isEmpty() == false) { - assignment.Id = existingAssignments[0].Id; - } - } - if (assignment.Id != null) { - try { - /** - * if two separate threads are trying to round robin at the same time, the LastUpdated__c check above - * isn't enough to ensure write-safety, and unfortunately FOR UPDATE is the only mutex Apex offers - * as a write-safe guarantee. One downside (among many) is that FOR UPDATE frequently throws; another is - * that another locking thread can release early - let's protect against both those eventualities - */ - RoundRobin__c lockedAssignment = [SELECT Id, Name, LastUpdated__c FROM RoundRobin__c WHERE Id = :assignment.Id FOR UPDATE]; - if (lockedAssignment.LastUpdated__c >= assignment.LastUpdated__c) { - // lock was released early, but the existing Index__c now almost certainly has stale values in it - // re-round robin to get the now-correct values - return false; + assignment = currentCache.get(assignment.Name); + wasCommitSuccessful = false; + } else { + assignment.LastUpdated__c = System.now(); + /** + * integration tests with after save Flows have shown something unfortunate: + * though the second (recursive) call to the assigner is spawned in a second transaction + * the RoundRobin__c.getAll() still doesn't contain the Id of the inserted record (for the times where the assignment + * is being run for the first time). + * That means that we can't just call "upsert", and instead have to do this goofy + * song and dance to ensure the Id is appended correctly + */ + if (assignment.Id == null) { + List existingAssignments = [SELECT Id FROM RoundRobin__c WHERE Name = :assignment.Name]; + if (existingAssignments.isEmpty() == false) { + assignment.Id = existingAssignments[0].Id; } - lockedAssignment.Index__c = assignment.Index__c; - lockedAssignment.LastUpdated__c = assignment.LastUpdated__c; - update lockedAssignment; - // purely for the map assignment, below - assignment = lockedAssignment; - } catch (DmlException ex) { - return false; } - } else { - insert assignment; + if (assignment.Id != null) { + update assignment; + } else { + insert assignment; + } } CACHED_ASSIGNMENTS.put(assignment.Name, assignment); - return true; + return wasCommitSuccessful; } private Map getCachedAssignments() { @@ -108,8 +92,7 @@ public without sharing class RoundRobinRepository extends AbstractCacheRepo { new RoundRobin__c( Name = assignmentType, // some sentinel value - LastUpdated__c = Datetime.newInstanceGmt(1970, 1, 1), - Index__c = null + LastUpdated__c = Datetime.newInstanceGmt(1970, 1, 1) ) ); } diff --git a/core/classes/RoundRobinRepository.cls-meta.xml b/core/classes/RoundRobinRepository.cls-meta.xml index e7009f9..45cccbd 100644 --- a/core/classes/RoundRobinRepository.cls-meta.xml +++ b/core/classes/RoundRobinRepository.cls-meta.xml @@ -1,5 +1,5 @@ - 54.0 + 57.0 Active \ No newline at end of file diff --git a/core/classes/RoundRobinRepositoryTests.cls-meta.xml b/core/classes/RoundRobinRepositoryTests.cls-meta.xml index e7009f9..45cccbd 100644 --- a/core/classes/RoundRobinRepositoryTests.cls-meta.xml +++ b/core/classes/RoundRobinRepositoryTests.cls-meta.xml @@ -1,5 +1,5 @@ - 54.0 + 57.0 Active \ No newline at end of file diff --git a/integration-tests/classes/RoundRobinFlowIntegrationTests.cls-meta.xml b/integration-tests/classes/RoundRobinFlowIntegrationTests.cls-meta.xml index e7009f9..45cccbd 100644 --- a/integration-tests/classes/RoundRobinFlowIntegrationTests.cls-meta.xml +++ b/integration-tests/classes/RoundRobinFlowIntegrationTests.cls-meta.xml @@ -1,5 +1,5 @@ - 54.0 + 57.0 Active \ No newline at end of file diff --git a/integration-tests/flows/Round_Robin_Assign_Leads.flow-meta.xml b/integration-tests/flows/Round_Robin_Assign_Leads.flow-meta.xml index 623d8e0..73bf624 100644 --- a/integration-tests/flows/Round_Robin_Assign_Leads.flow-meta.xml +++ b/integration-tests/flows/Round_Robin_Assign_Leads.flow-meta.xml @@ -35,7 +35,7 @@ - 54.0 + 57.0 Add_Current_Record_to_Collection diff --git a/package.json b/package.json index 763f66a..e7182bc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salesforce-round-robin", - "version": "0.1.0", + "version": "0.1.1", "description": "Round robin records in Salesforce (SFDC) using Flow or Apex. Performant, fair, fast assignment with configurable user pools", "repository": { "type": "git", diff --git a/sfdx-project.json b/sfdx-project.json index 1556026..0195c0f 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -5,8 +5,8 @@ "definitionFile": "config/project-scratch-def.json", "package": "salesforce-round-robin", "path": "core", - "versionName": "Guaranteed write-safety for dueling transactions", - "versionNumber": "0.1.0.0", + "versionName": "Reverts mutex due to bulkification issues with Flow, adds more safety for recursive Flow transactions", + "versionNumber": "0.1.1.0", "versionDescription": "Invocable & Apex-ready Round Robin Assigner for Salesforce Flow, Process Builder, Apex and more", "releaseNotesUrl": "https://github.com/jamessimone/salesforce-round-robin/releases/latest" }, @@ -16,13 +16,11 @@ ], "namespace": "", "sfdcLoginUrl": "https://login.salesforce.com", - "sourceApiVersion": "54.0", + "sourceApiVersion": "57.0", "packageAliases": { "salesforce-round-robin": "0Ho6g000000GnClCAK", - "salesforce-round-robin@0.0.1-0": "04t6g000008SjNvAAK", - "salesforce-round-robin@0.0.2-0": "04t6g000008SjOFAA0", - "salesforce-round-robin@0.0.3-0": "04t6g000008SjREAA0", "salesforce-round-robin@0.0.4-0": "04t6g000008SjZEAA0", - "salesforce-round-robin@0.1.0-0": "04t6g000008SjpyAAC" + "salesforce-round-robin@0.1.0-0": "04t6g000008SjpyAAC", + "salesforce-round-robin@0.1.1-0": "04t6g000008fjhFAAQ" } } \ No newline at end of file