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

initial draft of config helper apex script #837

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ node_modules
.DS_Store
.idea

# .sfdx files
.sfdx

hardis-report/
megalinter-reports/
config/user/
138 changes: 138 additions & 0 deletions src/force-app/main/default/classes/ConfigBuilderExport.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
public class ConfigBuilderExport {


static String filter = '';// where LastModifiedDate >= LAST_N_WEEKS:2';
static Map<String, Schema.SObjectField> objectsExtId = new Map<String, Schema.SObjectField>();
static Map<String, Schema.SObjectField> objectsExtIdExcep = new Map<String, Schema.SObjectField>();
//List of object which record to be exported
static List<String> objects = new List<String>{
'SBQQ__QuoteTemplate__c','SBQQ__TemplateContent__c'
};
//Fields which are self/system generated, no need to export/import
static Set<String> blacklistedFields = new Set<String>{'CompletedDateTime','RecurrenceRegeneratedType','RecurrenceMonthOfYear',
'RecurrenceInstance','RecurrenceDayOfMonth','RecurrenceDayOfWeekMask','RecurrenceType','RecurrenceTimeZoneSidKey',
'RecurrenceEndDateOnly','RecurrenceStartDateOnly ','RecurrenceActivityId','ReminderDateTime','ActivityOriginType',
'ArchivedDate','IsArchived','IsClosed','PrioritySortOrder','IsHighPriority','JigsawContactId',
'ReportsToName','EmailBouncedReason','EmailBouncedDate','IsEmailBounced','JigsawContactId',
'Id','IsDeleted','CreatedDate','LastModifiedDate','SystemModstamp','LastActivityDate','LastViewedDate',
'LastReferencedDate','UserRecordAccessId','MasterRecordId','AccountSource','IsCssEnabled',
'CssLastLoginDate','CompareName','PhotoUrl','CompareSite','OwnerAlias','JigSawCompanyId',
'ConnectionReceivedDate','ConnectionSentDate','AccountRollupId','ProductIsArchived', 'OwnerId', 'Id', 'SetupOwnerId'};

//This is to filter out reference/lookup field before export
Static Set<String> unUsedObject = new Set<String>{'dummy__C'};




@AuraEnabled(cacheable=true)
public static String exportConfigData(String filterString){
filterString = String.escapeSingleQuotes(filterString);
Map<String, String> objectsQuery = new Map<String, String>{};
Map<String, Set<String>> objectsFields = new Map<String, Set<String>>{};

String jsonConfigData = '';
try{

Map<String, Schema.SObjectType> schemaMap = Schema.getGlobalDescribe();

//Collecting externalId first for all objects
for(String obj: objects){
Map<String, Schema.SObjectField> fieldMap = schemaMap.get(obj).getDescribe().fields.getMap();

for(Schema.SObjectField sfield : fieldMap.Values())
{
Schema.DescribeFieldResult des = sfield.getDescribe();

if(des.isExternalID() && ){
objectsExtId.put(obj, sfield);

//Adding external Id for object which has exception like multiple ExternalIds
// this can be improved by having input and managed dynamically
if(obj=='Product__c' && des.getName()== 'External_Id__c'){
objectsExtIdExcep.put(obj,sfield);
}
}

}
}

for(String obj: objects){
Map<String, String> lookupFieldToRefField = new Map<String, String>();
Map<String, String> refFieldToObject = new Map<String, String>();
Set<String> objLookupFields = new Set<String>();
Set<String> objLookupRefExtIdFields = new Set<String>();
Set<String> fields = new Set<String>();
//Map<String, Schema.SObjectType> schemaMap = Schema.getGlobalDescribe();
Map<String, Schema.SObjectField> fieldMap = schemaMap.get(obj).getDescribe().fields.getMap();


for(Schema.SObjectField sfield : fieldMap.Values())
{
Schema.DescribeFieldResult des = sfield.getDescribe();
if(!des.isCalculated() && des.isCreateable() )
//&& !des.isDefaultedOnCreate())
{
if(des.getName() == 'RecordTypeId'){
fields.add('RecordType.DeveloperName');
}
else{
fields.add(des.getName());
}
}


if( des.isCustom() && des.getReferenceTo() !=null && des.getRelationshipName() != null){

if(!unUsedObject.contains(des.getReferenceTo()[0].getDescribe().getName())){
lookupFieldToRefField.put(des.getName(), des.getRelationshipName());
refFieldToObject.put(des.getName(), des.getReferenceTo()[0].getDescribe().getName());
}
}

}

//replacing existing key with below info
//once object with multiple externalId are managed dynmically, below piece of code will undergo changes
objectsExtId.put('Product__c',objectsExtIdExcep.get('Product__c'));

for(String lookupField :lookupFieldToRefField.keySet()){

String refExtIdField = lookupFieldToRefField.get(lookupField)+ '.' +
(objectsExtId.get(refFieldToObject.get(lookupField))).getDescribe().getName();
objLookupRefExtIdFields.add(refExtIdField);
}

fields.removeAll(blacklistedFields);
fields.removeAll(lookupFieldToRefField.keyset());
fields.addAll(objLookupRefExtIdFields);
objectsFields.put(obj, fields);
}

for(String obj: objects){

String query='';
query = 'Select '+String.join(new List<String>(objectsFields.get(obj)), ',')+ ' from '+obj +' '+ filterString
+ ' Order by CreatedDate';
objectsQuery.put(obj, query);
}

Map<String, List<sObject>> objRecords = new Map<String, List<sObject>>();
for(String obj : objects){
List<SObject> records = database.query(objectsQuery.get(obj));
if(records != null && records.size()>0 ){
objRecords.put(obj,records);

}

}
jsonConfigData = JSON.serializePretty(objRecords, true);
return jsonConfigData;
}
catch(Exception ex){
system.debug('::ex:'+ex.getMessage()+'::line:'+ex.getLineNumber());
return ex.getMessage();
}

}
}
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>55.0</apiVersion>
<status>Active</status>
</ApexClass>
153 changes: 153 additions & 0 deletions src/force-app/main/default/classes/ConfigBuilderImport.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
public class ConfigBuilderImport {

static Map<String, Schema.SObjectField> objectsExtId = new Map<String, Schema.SObjectField>();

//This might be needed if code is improved, it can be decided by uploaded file itself
static List<String> objects = new List<String>{
'SBQQ__QuoteTemplate__c','SBQQ__TemplateContent__c'
};
Static Set<String> unUsedObject = new Set<String>{'Product__c','BusinessHours'};

public ConfigBuilderImport(){

}

//Accept text file having JSON content
@AuraEnabled(cacheable=false)
public static String importConfigData(String base64){
// system.debug(':::'+base64);
Blob textBlob = EncodingUtil.base64Decode(base64);
String configData = textBlob.toString();
Map<String, Set<String>> objToLookupFields = new Map<String, Set<String>>();
Map<String, String> refFieldToObject = new Map<String, String>();
Map<String, Object> recordsByObj = (Map<String, Object>) JSON.deserializeUntyped(configData);

Map<String, String> objSelfLookUpFields = new Map<String, String>();
for(String obj: objects){
Set<String> fields = new Set<String>();
Map<String, Schema.SObjectType> schemaMap = Schema.getGlobalDescribe();
Map<String, Schema.SObjectField> fieldMap = schemaMap.get(obj).getDescribe().fields.getMap();

Set<String> objLookupFields = new Set<String>();
Set<String> objLookupRefExtIdFields = new Set<String>();

Set<String> lookupFields = new Set<String>();
for(Schema.SObjectField sfield : fieldMap.Values())
{


Schema.DescribeFieldResult des = sfield.getDescribe();

if(des.isExternalID()){
objectsExtId.put(obj, sfield);

}

if( des.isCustom() && des.getReferenceTo() !=null && des.getRelationshipName() != null){

if(!unUsedObject.contains(des.getReferenceTo()[0].getDescribe().getName())){

//lookupFieldToRefField.put(des.getName(), des.getRelationshipName());
lookupFields.add(des.getName());
system.debug('::lookupFields::'+lookupFields);
if(des.getReferenceTo()[0].getDescribe().getName() == obj){
objSelfLookUpFields.put(obj, des.getName());
}
//Removing lookup fields which has relationship to own object
lookupFields.removeAll(objSelfLookUpFields.values());
}

}

}
objToLookupFields.put(obj,lookupFields);
//Adding external Id which has exception like multiple ExternalIds
//Can be improvised by accpeting such objects name as input and dynamically handling below code instead
// objectsExtId.put('Product__c',
// Schema.getGlobalDescribe().get('Product__c').getDescribe().fields.getMap().get('External_Id__c'));
}

Map<String, List<SObject>> objToRecords = new Map<String, List<SObject>>();
Map<String, List<SObject>> objToRecordsFirst = new Map<String, List<SObject>>();

for(String obj :objects){
if(recordsByObj.containsKey(obj)){

List<SObject> sobjRecords = new List<SObject>();
List<SObject> sobjRecordFirstInsert = new List<SObject>();
List<SObject> sobjRecordLaterInsert = new List<SObject>();

List<Object> recordsJson = (List<Object>)recordsByObj.get(obj);
for(Object jsonRec : recordsJson){
jsonRec = removeAttributes((Map<String,Object>)jsonRec, objToLookupFields.get(obj));
String objSer = JSON.serializePretty(jsonRec);
SObject sObj = (SObject)JSON.deserialize(objSer, SObject.class);
sobjRecords.add(sObj);
}
//Order the records in case of self reference lookup
if(objSelfLookUpFields.containsKey(obj))
{
for(SObject sobj: sobjRecords){
if(sobj.get(objSelfLookUpFields.get(obj)) == null){
sobjRecordFirstInsert.add(sobj);
}
}
for(SObject sobj: sobjRecords){
if(sobj.get(objSelfLookUpFields.get(obj)) != null){
sobj.put(objSelfLookUpFields.get(obj), null);
//Removing the lookup field from sobj.
String jsonStr = JSON.serialize(sobj);
Map<String,Object> objJson = (Map<String,Object>)JSON.deserializeUntyped(jsonStr);
objJson.remove(objSelfLookUpFields.get(obj));
String objSer = JSON.serializePretty(objJson);
sobj = (SObject)JSON.deserialize(objSer, SObject.class);
sobjRecordLaterInsert.add(sobj);
}

}
}
else{
sobjRecordLaterInsert.addAll(sobjRecords);
}
//creating 2 collection to upsert separtiely due to dependecy on records
objToRecordsFirst.put(obj,sobjRecordFirstInsert );
objToRecords.put(obj,sobjRecordLaterInsert );


}
}

for(String objName : objects){
if(objToRecords.containsKey(objName)){

//inserting records first which has self lookup empty and are dependent for rest of records
if(objToRecordsFirst.containsKey(objName)){
Database.upsert(objToRecordsFirst.get(objName), objectsExtId.get(objName));
}
Database.upsert(objToRecords.get(objName), objectsExtId.get(objName));
}
}

return 'Config Imported Successfully';
}

//Removes the Key/fields which are not needed for next org
public static Object removeAttributes(Object jsonObj, Set<String> extraKeyToRemove){
Map<String,Object> jsonObjMap = (Map<String,Object>)jsonObj;
for(String key : jsonObjMap.keySet()) {
if(key == 'Id' || key == 'RecordTypeId' || key=='RecordType' || extraKeyToRemove.contains(key)) {
jsonObjMap.remove(key);
} else {
if(jsonObjMap.get(key) instanceof Map<String,Object>) {
removeAttributes((Map<String,Object>)jsonObjMap.get(key), extraKeyToRemove);
}
}
}
return jsonObjMap;
}





}
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>56.0</apiVersion>
<status>Active</status>
</ApexClass>
74 changes: 74 additions & 0 deletions src/force-app/main/default/classes/FileUploaderClass.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
public with sharing class FileUploaderClass {
/*
* @method uploadFile()
* @desc Creates a content version from a given file's base64 and name
*
* @param {String} base64 - base64 string that represents the file
* @param {String} filename - full file name with extension, i.e. 'products.csv'
* @param {String} recordId - Id of the record you want to attach this file to
*
* @return {ContentVersion} - returns the created ContentDocumentLink Id if the
* upload was successful, otherwise returns null
*/
@AuraEnabled
public static String uploadFile(String base64, String filename, String recordId) {
ContentVersion cv = createContentVersion(base64, filename);
ContentDocumentLink cdl = createContentLink(cv.Id, recordId);
if (cv == null || cdl == null) { return null; }
return cdl.Id;
}
/*
* @method createContentVersion() [private]
* @desc Creates a content version from a given file's base64 and name
*
* @param {String} base64 - base64 string that represents the file
* @param {String} filename - full file name with extension, i.e. 'products.csv'
*
* @return {ContentVersion} - returns the newly created ContentVersion, or null
* if there was an error inserting the record
*/
private static ContentVersion createContentVersion(String base64, String filename) {
ContentVersion cv = new ContentVersion();
cv.VersionData = EncodingUtil.base64Decode(base64);
cv.Title = filename;
cv.PathOnClient = filename;
try {
insert cv;
return cv;
} catch(DMLException e) {
System.debug(e);
return null;
}
}

/*
* @method createContentLink() [private]
* @desc Creates a content link for a given ContentVersion and record
*
* @param {String} contentVersionId - Id of the ContentVersion of the file
* @param {String} recordId - Id of the record you want to attach this file to
*
* @return {ContentDocumentLink} - returns the newly created ContentDocumentLink,
* or null if there was an error inserting the record
*/
private static ContentDocumentLink createContentLink(String contentVersionId, String recordId) {
if (contentVersionId == null || recordId == null) { return null; }
ContentDocumentLink cdl = new ContentDocumentLink();
cdl.ContentDocumentId = [
SELECT ContentDocumentId
FROM ContentVersion
WHERE Id =: contentVersionId
].ContentDocumentId;
cdl.LinkedEntityId = recordId;
// ShareType is either 'V', 'C', or 'I'
// V = Viewer, C = Collaborator, I = Inferred
cdl.ShareType = 'V';
try {
insert cdl;
return cdl;
} catch(DMLException e) {
System.debug(e);
return null;
}
}
}
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>55.0</apiVersion>
<status>Active</status>
</ApexClass>
Loading