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

Auto nav #1611

Merged
merged 8 commits into from
Jan 11, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* 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.
*
**/

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' required=true)
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,133 @@
/**
* 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.
*
**/

global inherited sharing class UpsertRecordByKey {

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

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

@InvocableVariable(label='New or replacement record' required=true)
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;

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);
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,82 @@
/**
* 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 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>
12 changes: 12 additions & 0 deletions flow_screen_components/AutoNavigate_Refresh/.forceignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# List files or directories below to ignore them when running force:source:push, force:source:pull, and force:source:status
# More information: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_exclude_source.htm
#

package.xml

# LWC configuration files
**/jsconfig.json
**/.eslintrc.json

# LWC Jest
**/__tests__/**
4 changes: 4 additions & 0 deletions flow_screen_components/AutoNavigate_Refresh/.husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm run precommit
41 changes: 41 additions & 0 deletions flow_screen_components/AutoNavigate_Refresh/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# ers_AutoNavigate_Refresh

Lightning Web Component for Flow Screens: **ers_AutoNavigate_Refresh**

Lightning Flow Builder Name: **Auto Navigate + Console Tab Refresh**

** This component is designed to be used on a FLow Screen when you need to refresh the console tabs and/or **
** automatically navigate to the previous or next screen. The refresh and navigation can be further controlled **
** by making this component conditionally visible so the action(s) will only occur when the conditional **
** visibility is true.**

Additional components packaged with this LWC:

Lightning Web Components: ers_AutoNavigate_RefreshCPE //TODO

**Documentation:** https://unofficialsf.com/TODO/

**Created by:** Eric Smith
**Date:** November 27, 2024

LinkedIn: https://www.linkedin.com/in/ericrsmith2
Salesforce: https://trailblazer.me/id/ericsmith
Blog: https://ericsplayground.wordpress.com/blog/
Twitter: https://twitter.com/esmith35

---

**You must install these components FIRST in order to install and use this component**


---

**Install ers_AutoNavigate_Refresh**
[Version 1.0.0 (Production or Developer)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5G000004fz9nQAA)
[Version 1.0.0 (Sandbox)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5G000004fz9nQAA)

---

# Release Notes

## 11/27/24 - Eric Smith - Version 1.0.0
Loading