Skip to content

Commit

Permalink
v0.1.1 - Flow updates (#9)
Browse files Browse the repository at this point in the history
* Reverts mutex due to bulkification issues with Flow, adds more safety for recursive Flow transactions
  • Loading branch information
jamessimone authored Mar 16, 2023
1 parent af57673 commit e98ca44
Show file tree
Hide file tree
Showing 20 changed files with 59 additions and 70 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
node_modules/
yarn.lock
.sfdx/
.sf/
.localdevserver/
debug.log
DEVHUB_SFDX_URL.txt
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

## Deployment

<a href="https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008SjpyAAC">
<a href="https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008fjhFAAQ">
<img alt="Deploy to Salesforce"
src="./media/deploy-package-to-prod.png">
</a>

<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008SjpyAAC">
<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008fjhFAAQ">
<img alt="Deploy to Salesforce Sandbox"
src="./media/deploy-package-to-sandbox.png">
</a>
Expand Down
2 changes: 1 addition & 1 deletion core/classes/AbstractCacheRepo.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>54.0</apiVersion>
<apiVersion>57.0</apiVersion>
<status>Active</status>
</ApexClass>
13 changes: 9 additions & 4 deletions core/classes/FlowRoundRobinAssigner.cls
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,15 @@ global without sharing class FlowRoundRobinAssigner {

@InvocableMethod(category='Round Robin' label='Round robin records')
global static void assign(List<FlowInput> 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<SObject>{ input.recordToRoundRobin };
}
if (input.recordsToRoundRobin.isEmpty() == false) {
SELF.trackAssignedIds(input);
SELF.roundRobin(input);
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion core/classes/FlowRoundRobinAssigner.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>54.0</apiVersion>
<apiVersion>57.0</apiVersion>
<status>Active</status>
</ApexClass>
4 changes: 3 additions & 1 deletion core/classes/FlowRoundRobinAssignerTests.cls
Original file line number Diff line number Diff line change
@@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion core/classes/FlowRoundRobinAssignerTests.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>54.0</apiVersion>
<apiVersion>57.0</apiVersion>
<status>Active</status>
</ApexClass>
2 changes: 1 addition & 1 deletion core/classes/IThreadSafeCacheVisitor.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>54.0</apiVersion>
<apiVersion>57.0</apiVersion>
<status>Active</status>
</ApexClass>
2 changes: 1 addition & 1 deletion core/classes/MockUserAssignmentRepo.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>54.0</apiVersion>
<apiVersion>57.0</apiVersion>
<status>Active</status>
</ApexClass>
2 changes: 1 addition & 1 deletion core/classes/QueryAssigner.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>54.0</apiVersion>
<apiVersion>57.0</apiVersion>
<status>Active</status>
</ApexClass>
2 changes: 1 addition & 1 deletion core/classes/RoundRobinAssigner.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>54.0</apiVersion>
<apiVersion>57.0</apiVersion>
<status>Active</status>
</ApexClass>
2 changes: 1 addition & 1 deletion core/classes/RoundRobinAssignerTests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ private class RoundRobinAssignerTests {

static List<User> createUsersForAssignment(String assignmentType) {
List<User> users = new List<User>();
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)));
}
Expand Down
2 changes: 1 addition & 1 deletion core/classes/RoundRobinAssignerTests.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>54.0</apiVersion>
<apiVersion>57.0</apiVersion>
<status>Active</status>
</ApexClass>
67 changes: 25 additions & 42 deletions core/classes/RoundRobinRepository.cls
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
public without sharing class RoundRobinRepository extends AbstractCacheRepo {
private static Map<String, RoundRobin__c> CACHED_ASSIGNMENTS;

@SuppressWarnings('PMD.ApexCRUDViolation')
public void accept(IThreadSafeCacheVisitor visitor, List<SObject> records) {
RoundRobin__c currentAssignment = this.getCurrentAssignment(visitor.getVisitKey());
visitor.visitRecords(records, currentAssignment);
Expand Down Expand Up @@ -46,56 +47,39 @@ public without sharing class RoundRobinRepository extends AbstractCacheRepo {

@SuppressWarnings('PMD.ApexCRUDViolation')
private Boolean commitUpdatedAssignment(RoundRobin__c assignment) {
Boolean wasCommitSuccessful = true;
Map<String, RoundRobin__c> 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<RoundRobin__c> 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<RoundRobin__c> 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<String, RoundRobin__c> getCachedAssignments() {
Expand All @@ -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)
)
);
}
Expand Down
2 changes: 1 addition & 1 deletion core/classes/RoundRobinRepository.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>54.0</apiVersion>
<apiVersion>57.0</apiVersion>
<status>Active</status>
</ApexClass>
2 changes: 1 addition & 1 deletion core/classes/RoundRobinRepositoryTests.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>54.0</apiVersion>
<apiVersion>57.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>54.0</apiVersion>
<apiVersion>57.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
</value>
</inputParameters>
</actionCalls>
<apiVersion>54.0</apiVersion>
<apiVersion>57.0</apiVersion>
<assignments>
<name>Add_Current_Record_to_Collection</name>
<label>Add Current Record to Collection</label>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
12 changes: 5 additions & 7 deletions sfdx-project.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand All @@ -16,13 +16,11 @@
],
"namespace": "",
"sfdcLoginUrl": "https://login.salesforce.com",
"sourceApiVersion": "54.0",
"sourceApiVersion": "57.0",
"packageAliases": {
"salesforce-round-robin": "0Ho6g000000GnClCAK",
"[email protected]": "04t6g000008SjNvAAK",
"[email protected]": "04t6g000008SjOFAA0",
"[email protected]": "04t6g000008SjREAA0",
"[email protected]": "04t6g000008SjZEAA0",
"[email protected]": "04t6g000008SjpyAAC"
"[email protected]": "04t6g000008SjpyAAC",
"[email protected]": "04t6g000008fjhFAAQ"
}
}

0 comments on commit e98ca44

Please sign in to comment.