Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove insert by keyfield #1610

Merged
merged 18 commits into from
Jan 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,6 @@ $RECYCLE.BIN/
.gitignore
package-lock.json
jsconfig.json
.lwc/jsconfig.json
.sf/config.json
.pmdCache
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* Get Index from Key - Flow Action
*
* Eric Smith - 11/26/24 - v1.0
*
* This class is a Collection Utility designed to find the index of a record in a collection of records based on the value of a field
*
* It takes for input a record collection, the API name of a field and the target value of that field.
*
* It will then loop through the collection and compare the field value to the target value. If the field value matches the target value,
* the index of that record is returned. If a matching record is not found, an index value of -1 will be returned.
*
* The primary use case for this action is to find the index of a record based on the value of a key field so it can be used in
* other collecion actions that require an index value such as Add or Insert Record or Remove Recod from Collection.
*
* Release Notes:
* v1.0.0 - 11/26/24 - Initial Version
* v1.0.1 - 11/27/24 - Allow Field API value to be null
*
**/

global inherited sharing class GetIndexFromKey {

// Attributes passed in from the Flow
global class Requests {

@InvocableVariable(label='Input Collection' required=true)
global List<SObject> inputCollection;

@InvocableVariable(label='Field API Name')
global String fieldAPIName;

@InvocableVariable(label='Field API Value')
global String fieldValue;

}

// Attributes passed back to the Flow
global class Results {

@InvocableVariable(label='Index')
global Integer index;

}

// Standard Exception Handling
global class InvocableActionException extends Exception {}

// Expose this Action to the Flow
@InvocableMethod(label='Get Index from Keyfield Value [USF Collection Processor]' category='Util' iconName='resource:CollectionProcessorsSVG:colproc')
global static List<Results> getIndexFromKey(List<Requests> requestList) {

// Prepare the response to send back to the Flow
Results response = new Results();
List<Results> responseWrapper = new List<Results>();

// Bulkify proccessing of multiple requests
for (Requests curRequest : requestList) {

// Get Input Value(s)
List<SObject> inputCollection = curRequest.inputCollection;
String fieldAPIName = curRequest.fieldAPIName;
Object fieldValue = curRequest.fieldValue;

// Process input attributes
if (fieldAPIName == '' || fieldAPIName == null) {
fieldAPIName = 'Id';
}

// Define working variables
Integer index = -1;
Integer counter = 0;

// Start processing
for (SObject record: inputCollection) {
if (record.get(fieldAPIName)?.toString() == fieldValue) {
index = counter;
break;
}
counter++;
}

// Set Output Values
response.index = counter;
responseWrapper.add(response);
}

// Return values back to the Flow
return responseWrapper;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/**
* Upsert Record by Key - Flow Action
*
* Eric Smith - 11/26/24 - v1.0
*
* This class is a Collection Utility designed to upsert a record into a collection of records based on the value of a field in the source record.
*
* It takes for input a record collection, a single record and the API name of a key field.
*
* If it finds a matching record in the collection based on the value of the key field, it will replace that record in the collection.
* If a matching record is not found,the record will be added to the collection unless the option to skip the insert is set to True.
*
* The updated record colllection is returned by the action along with a noMatchFound flag which will be true if no matching record was found.
*
* The primary use case for this action is to update a record collection in a flow prior to saving the changes to the database.
*
* Release Notes:
* v1.0.0 - 11/26/24 - Initial Version
* v1.0.1 - 11/27/24 - Handle replacing last record in the collection
*
**/

global inherited sharing class UpsertRecordByKey {

// Attributes passed in from the Flow
global class Requests {

@InvocableVariable(label='Original record collection' required=true)
global List<SObject> inputCollection;

@InvocableVariable(label='New or replacement record')
global SObject inputRecord;

@InvocableVariable(label='API name of keyfield (Default = Id)')
global String fieldAPIName;

@InvocableVariable(label='Do you want to skip insert if no match is found? (Default = False)')
global Boolean skipInsertIfNoMatchFound;

}

// Attributes passed back to the Flow
global class Results {

@InvocableVariable(Label='Collection with upserted record')
global List<SObject> outputCollection;

@InvocableVariable(Label='No matching record was found')
global Boolean noMatchFound;

}

// Standard Exception Handling
global class InvocableActionException extends Exception {}

// Expose this Action to the Flow
@InvocableMethod(label='Upsert Record by Key [USF Collection Processor]' category='Util' iconName='resource:CollectionProcessorsSVG:colproc')
global static List <Results> upsertRecordByKey(List<Requests> requestList) {

// Prepare the response to send back to the Flow
Results response = new Results();
List<Results> responseWrapper = new List<Results>();

// Bulkify proccessing of multiple requests
for (Requests curRequest : requestList) {

// Get Input Value(s)
List<SObject> inputCollection = curRequest.inputCollection;
SObject inputRecord = curRequest.inputRecord;
String fieldAPIName = curRequest.fieldAPIName;
Boolean skipInsertIfNoMatchFound = curRequest.skipInsertIfNoMatchFound;

// Process input attributes
if (fieldAPIName == '' || fieldAPIName == null) {
fieldAPIName = 'Id';
}
if (skipInsertIfNoMatchFound == null) {
skipInsertIfNoMatchFound = false;
}
String fieldValue = inputRecord?.get(fieldAPIName)?.toString();

// Set initial values
getIndexResults idx = new getIndexResults();

// Start processing
idx = getIndexFromKey(inputCollection, fieldAPIName, fieldValue);
Integer index = idx.index;

response.noMatchFound = true;
if (inputCollection != null && inputRecord != null) {
response.noMatchFound = false;
if (index == -1 || index == null || index >= inputCollection.size()) {
response.noMatchFound = true;
if (!skipInsertIfNoMatchFound) {
inputCollection.add(inputRecord);
}
} else {
inputCollection.remove(index);
if (index == inputCollection.size()) { // Removed last record
inputCollection.add(inputRecord);
} else {
inputCollection.add(index, inputRecord);
}
}
}

// Set Output Values
response.outputCollection = inputCollection.clone();

responseWrapper.add(response);

}

// Return values back to the Flow
return responseWrapper;
}

public class getIndexResults {
Integer index;
}

public static getIndexResults getIndexFromKey(List<SObject> inputCollection, String fieldAPIName, String fieldValue) {

Invocable.Action indexAction = Invocable.Action.createCustomAction('apex', 'GetIndexFromKey');

indexAction.setInvocationParameter('inputCollection', inputCollection);
indexAction.setInvocationParameter('fieldAPIName', fieldAPIName);
indexAction.setInvocationParameter('fieldValue', fieldValue);

List<Invocable.Action.Result> results = indexAction.invoke();

getIndexResults gir = new getIndexResults();

gir.index = -1;
if (results.size() > 0 && results[0].isSuccess()) {
gir.index = Integer.valueOf(results[0].getOutputParameters().get('index'));
}

return gir;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* Upsert Record by Key Test - Apex Tests
*
* Eric Smith - 11/26/24 - v1.0
*
* This will test both GetIndexFromKey.cls and UpsertRecordByKey.cls
*
**/

@isTest
public with sharing class UpsertRecordByKeyTest {

@isTest
static void upsertTest() {

Account acct = new Account(Name='Test Account1', AccountNumber='1');
insert acct;
List<Account> accts = new List<Account>();
accts.add(acct);
acct = new Account(Name='Test Account2', AccountNumber='2');
insert acct;
Id acct2id = acct.Id;
accts.add(acct);
acct = new Account(Name='Test Account3', AccountNumber='3');
insert acct;
accts.add(acct);

UpsertRecordByKey.Requests testUpsert = new UpsertRecordByKey.Requests();
List<UpsertRecordByKey.Requests> testUpsertList = new List<UpsertRecordByKey.Requests>();

// Test match with field API name provided
Account upd_acct1 = new Account(Name='Test Account1', AccountNumber='11');
testUpsert.inputCollection = accts;
testUpsert.inputRecord = upd_acct1;
testUpsert.fieldAPIName = 'Name';
testUpsertList.add(testUpsert);
List<UpsertRecordByKey.Results> testResultList = UpsertRecordByKey.upsertRecordByKey(testUpsertList);
Assert.areEqual('Test Account1', testResultList[0].outputCollection[0].get('Name'));
Assert.areEqual('11', testResultList[0].outputCollection[0].get('AccountNumber'));
Assert.areEqual(3, testResultList[0].outputCollection.size());
testUpsertList.clear();

// Test match of Id field by default
Account upd_acct2 = accts[1];
upd_acct2.AccountNumber='22';
testUpsert.inputCollection = accts;
testUpsert.inputRecord = upd_acct2;
testUpsert.fieldAPIName = '';
testUpsertList.add(testUpsert);
testResultList = UpsertRecordByKey.upsertRecordByKey(testUpsertList);
Assert.areEqual(acct2Id, testResultList[0].outputCollection[1].Id);
Assert.areEqual('Test Account2', testResultList[0].outputCollection[1].get('Name'));
Assert.areEqual('22', testResultList[0].outputCollection[1].get('AccountNumber'));
Assert.areEqual(3, testResultList[0].outputCollection.size());
Assert.isFalse(testResultList[0].noMatchFound);
testUpsertList.clear();

// Test upsert last record in collection
Account upd_acct3 = accts[2];
upd_acct3.AccountNumber='33';
testUpsert.inputCollection = accts;
testUpsert.inputRecord = upd_acct3;
testUpsert.fieldAPIName = 'Name';
testUpsertList.add(testUpsert);
testResultList = UpsertRecordByKey.upsertRecordByKey(testUpsertList);
Assert.areEqual('Test Account3', testResultList[0].outputCollection[2].get('Name'));
Assert.areEqual('33', testResultList[0].outputCollection[2].get('AccountNumber'));
Assert.areEqual(3, testResultList[0].outputCollection.size());
Assert.isFalse(testResultList[0].noMatchFound);
testUpsertList.clear();

// Test skip add new record when no match
Account acct4 = new Account(Name='Test Account4', AccountNumber='4');
insert acct4;
testUpsert.inputCollection = accts;
testUpsert.inputRecord = acct4;
testUpsert.fieldAPIName = 'Name';
testUpsert.skipInsertIfNoMatchFound = true;
testUpsertList.add(testUpsert);
testResultList = UpsertRecordByKey.upsertRecordByKey(testUpsertList);
Assert.areEqual(3, testResultList[0].outputCollection.size());
Assert.IsTrue(testResultList[0].noMatchFound);
testUpsertList.clear();

// Test add new record when no match
testUpsert.inputCollection = accts;
testUpsert.inputRecord = acct4;
testUpsert.fieldAPIName = 'Name';
testUpsert.skipInsertIfNoMatchFound = false;
testUpsertList.add(testUpsert);
testResultList = UpsertRecordByKey.upsertRecordByKey(testUpsertList);
Assert.areEqual(4, testResultList[0].outputCollection.size());
Assert.IsTrue(testResultList[0].noMatchFound);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<status>Active</status>
</ApexClass>
Loading