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

Feature/metadata deploy #160

Merged
merged 13 commits into from
Feb 7, 2024
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
145 changes: 76 additions & 69 deletions force-app/main/default/classes/Cmdt.cls
Original file line number Diff line number Diff line change
Expand Up @@ -5,90 +5,97 @@ public with sharing class Cmdt {

// Gather all active Bundles and put them into a Map (key: DeveloperName; Bundle)
for( Indicator_Bundle__mdt bundle : [
SELECT Id, DeveloperName, MasterLabel, Label,
Active__c,
Card_Icon__c,
Card_Icon_Background__c,
Card_Icon_Foreground__c,
Card_Text__c,
Card_Title__c,
Description__c,
sObject__c,
sObject__r.QualifiedApiName,
sObject__r.Label
FROM Indicator_Bundle__mdt
SELECT Id, DeveloperName, MasterLabel, Label, QualifiedApiName,
Active__c,
Card_Icon__c,
Card_Icon_Background__c,
Card_Icon_Foreground__c,
Card_Text__c,
Card_Title__c,
Description__c,
sObject__c,
sObject__r.QualifiedApiName,
sObject__r.Label
FROM Indicator_Bundle__mdt
WHERE Delete__c != TRUE
] ) {
setBundle(bundle);
}

// Gather all active Items and put them into a Map (key: Developername; Item)
// Using SOQL because of parent-related values AND Advanced_Field__c is long text.
for( Indicator_Item__mdt item : [
SELECT Id, DeveloperName, MasterLabel,
Active__c,
Advanced_Field__c,
Advanced_Field_Label__c,
Display_Multiple__c,
Empty_Static_Text_Behavior__c,
Field__c, Field__r.QualifiedApiName,
Hover_Text__c,
Icon_Value__c,
Image__c,
Inverse_Hover_Text__c,
Inverse_Icon_Value__c,
Inverse_Image__c,
Inverse_Static_Text__c,
Show_False_or_Blank__c,
sObject__c, sObject__r.QualifiedApiName,
Static_Text__c,
Zero_Behavior__c,
Icon_Background__c,
Icon_Foreground__c,
Inverse_Icon_Background__c,
Inverse_Icon_Foreground__c,
Description__c
FROM Indicator_Item__mdt
// WHERE Active__c = TRUE
SELECT Id, DeveloperName, MasterLabel, Label, QualifiedApiName,
Active__c,
Advanced_Field__c,
Advanced_Field_Label__c,
Display_Multiple__c,
Empty_Static_Text_Behavior__c,
Field__c, Field__r.QualifiedApiName,
Hover_Text__c,
Icon_Value__c,
Image__c,
Inverse_Hover_Text__c,
Inverse_Icon_Value__c,
Inverse_Image__c,
Inverse_Static_Text__c,
Show_False_or_Blank__c,
sObject__c, sObject__r.QualifiedApiName,
Static_Text__c,
Zero_Behavior__c,
Icon_Background__c,
Icon_Foreground__c,
Inverse_Icon_Background__c,
Inverse_Icon_Foreground__c,
Description__c
FROM Indicator_Item__mdt
WHERE Delete__c != TRUE
// WHERE Active__c = TRUE
]){
setItem(item);
}

// Gather all active Bundle Items (Bundle and Item are active) and put them into a Map (key: Bundle DeveloperName; List of BundleItems)
// Using SOQL because of the Order By clause rather than creating a custom sort method.
for( Indicator_Bundle_Item__mdt bundleItem : [
SELECT Indicator_Bundle__c, Indicator_Bundle__r.DeveloperName,
Indicator_Item__c, Indicator_Item__r.DeveloperName,
Order__c
FROM Indicator_Bundle_Item__mdt
// WHERE Indicator_Bundle__r.Active__c = TRUE
// AND Indicator_Item__r.Active__c = TRUE
ORDER BY Order__c
SELECT Id, DeveloperName, QualifiedApiName, MasterLabel, Label,
Indicator_Bundle__c, Indicator_Bundle__r.DeveloperName, Indicator_Bundle__r.QualifiedApiName,
Indicator_Item__c, Indicator_Item__r.DeveloperName, Indicator_Item__r.QualifiedApiName,
Order__c
FROM Indicator_Bundle_Item__mdt
WHERE Delete__c != TRUE
// WHERE Indicator_Bundle__r.Active__c = TRUE
// AND Indicator_Item__r.Active__c = TRUE
ORDER BY Order__c
]){
setBundleItem(bundleItem);
}

// Gather all active Item Extensions and put them into a Map (key: Indicator Item; List of Extensions)
// Using SOQL because of the Order By clause rather than creating a custom sort method.
for(Indicator_Item_Extension__mdt itemExtension : [
SELECT Id, Active__c,
Contains_Text__c,
Text_Operator__c,
Hover_Text__c,
Icon_Value__c,
Image__c,
Indicator_Item__c,
Indicator_Item__r.DeveloperName,
Maximum__c,
Minimum__c,
Priority__c,
Static_Text__c,
Icon_Background__c,
Icon_Foreground__c,
Description__c
FROM Indicator_Item_Extension__mdt
// WHERE Active__c = TRUE
// AND Indicator_Item__r.Active__c = TRUE
ORDER BY Indicator_Item__c, Priority__c DESC, MasterLabel
SELECT Id, DeveloperName, QualifiedApiName, Label, MasterLabel,
Active__c,
Contains_Text__c,
Text_Operator__c,
Hover_Text__c,
Icon_Value__c,
Image__c,
Indicator_Item__c,
Indicator_Item__r.DeveloperName,
Indicator_Item__r.QualifiedApiName,
Maximum__c,
Minimum__c,
Priority__c,
Static_Text__c,
Icon_Background__c,
Icon_Foreground__c,
Description__c
FROM Indicator_Item_Extension__mdt
WHERE Delete__c != TRUE
// WHERE Active__c = TRUE
// AND Indicator_Item__r.Active__c = TRUE
ORDER BY Indicator_Item__c, Priority__c DESC, MasterLabel
]){
setItemExtension(itemExtension);
}
Expand Down Expand Up @@ -143,7 +150,7 @@ public with sharing class Cmdt {
}

public static List<Indicator_Item__mdt> getAllOrphanItems(){

List<Indicator_Item__mdt> orphanItems = new List<Indicator_Item__mdt>();

for(String itemName : itemsByName.keyset()){
Expand All @@ -159,28 +166,28 @@ public with sharing class Cmdt {
if(bundlesByName.containsKey(bundleDevName))
return bundlesByName.get(bundleDevName);
else
return new Indicator_Bundle__mdt();
return new Indicator_Bundle__mdt();
}

public static Indicator_Item__mdt getItem(String itemDevName){
if(itemsByName.containsKey(itemDevName))
return itemsByName.get(itemDevName);
else
return new Indicator_Item__mdt();
return new Indicator_Item__mdt();
}

public static List<Indicator_Bundle_Item__mdt> getBundleItems(String bundleDevName){
if(bundleItemsByBundle.containsKey(bundleDevname))
return bundleItemsByBundle.get(bundleDevName);
else
return new List<Indicator_Bundle_Item__mdt>();
else
return new List<Indicator_Bundle_Item__mdt>();
}

public static List<Indicator_Item_Extension__mdt> getExtensionsForItem(String itemDevName){
if(extensionsByItem.containsKey(itemDevName))
return extensionsByItem.get(itemDevName);
else
return new List<Indicator_Item_Extension__mdt>();
return new List<Indicator_Item_Extension__mdt>();
}

// Static variables used across methods.
Expand Down
23 changes: 23 additions & 0 deletions force-app/main/default/classes/MetadataDeploy.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Created by robertwright on 10/30/23.
*/

public with sharing class MetadataDeploy implements Metadata.DeployCallback {

public String resultMessage;

public void handleResult(Metadata.DeployResult result,Metadata.DeployCallbackContext context) {
if(result.status === Metadata.DeployStatus.Succeeded) {
resultMessage = 'MetaData Deploy Succeeded';
} else resultMessage = 'MetaData Deploy Failed';
if(Test.isRunningTest()) System.debug(resultMessage);
}

public static Id upsertMetaData(Metadata.DeployContainer deployContainer) {
MetadataDeploy callback = new MetadataDeploy();
Id upsertId;
if(!Test.isRunningTest()) upsertId = Metadata.Operations.enqueueDeployment(deployContainer,callback);
return upsertId;
}

}
5 changes: 5 additions & 0 deletions force-app/main/default/classes/MetadataDeploy.cls-meta.xml
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>58.0</apiVersion>
<status>Active</status>
</ApexClass>
10 changes: 10 additions & 0 deletions force-app/main/default/classes/MetadataDeployTests.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Created by robertwright on 10/30/23.
*/

@IsTest
private class MetadataDeployTests {
@IsTest
static void testBehavior() {
}
}
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>58.0</apiVersion>
<status>Active</status>
</ApexClass>
123 changes: 123 additions & 0 deletions force-app/main/default/classes/MetadataUtility.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/**
* Created by robertwright on 10/30/23.
*/

public with sharing class MetadataUtility {

/**Static Params**/
private static final String Label_Field = 'Label';
private static final String QualifiedApiName_Field = 'QualifiedApiName';
private static final Set<DescribeFieldResult> entityRelationshipFields = new Set<DescribeFieldResult>{
Indicator_Bundle__mdt.sObject__c.getDescribe(),
Indicator_Item__mdt.Field__c.getDescribe(),
Indicator_Item__mdt.sObject__c.getDescribe()
};

private static Metadata.DeployContainer deployContainer;
private static DescribeSObjectResult describeSObjectResult;
private static Map<String,Object> populatedFieldsMap;

/**Wrappers**/
public class deploymentWrapper {

@AuraEnabled public Indicator_Bundle__mdt indicatorBundle;
@AuraEnabled public List<Indicator_Bundle_Item__mdt> indicatorBundleItems;
@AuraEnabled public Indicator_Item__mdt indicatorItem;
@AuraEnabled public List<Indicator_Item_Extension__mdt> indicatorItemExtensions;

public deploymentWrapper() {}

}

/**Controller Methods**/
@AuraEnabled
public static Id deployIndicatorBundles(deploymentWrapper items) {
return deployWrapperMetadata(items);
}

/**Metadata Methods**/
private static Id deployWrapperMetadata(deploymentWrapper wrapper) {
deployContainer = new Metadata.DeployContainer(); // Build New Deployment Container
if(wrapper.indicatorBundle != null) processMetadataRecords(new List<Indicator_Bundle__mdt>{wrapper.indicatorBundle},Indicator_Bundle__mdt.getSObjectType());
if(wrapper.indicatorItem != null) processMetadataRecords(new List<Indicator_Item__mdt>{wrapper.indicatorItem},Indicator_Item__mdt.getSObjectType());
if(wrapper.indicatorItemExtensions != null && !wrapper.indicatorItemExtensions.isEmpty()) processMetadataRecords(wrapper.indicatorItemExtensions,Indicator_Item_Extension__mdt.getSObjectType());
if(wrapper.indicatorBundleItems != null && !wrapper.indicatorBundleItems.isEmpty()) processMetadataRecords(wrapper.indicatorBundleItems,Indicator_Bundle_Item__mdt.getSObjectType());
Id deployId = MetadataDeploy.upsertMetaData(deployContainer); // Deploy Deploy Container
return deployId;
}

private static void processMetadataRecords(List<SObject> records,SObjectType sObjectType) {
describeSObjectResult = sObjectType.getDescribe();
for(SObject record : records) buildMetaData(record.getPopulatedFieldsAsMap()); // Build Metadata Maps and Add to Deploy Container
}

private static void buildMetaData(Map<String,Object> metadataFieldValueMap) {
populatedFieldsMap = metadataFieldValueMap;
if(populatedFieldsMap == null) populatedFieldsMap = new Map<String, Object>();

String sObjectName = describeSObjectResult.getName();
String QualifiedApiName = (String) populatedFieldsMap.get(QualifiedApiName_Field);
String fullName = mergeFullName(sObjectName,QualifiedApiName);
String label = (String) populatedFieldsMap.get(Label_Field);

if(String.isBlank(label)) label = 'Unnamed Metadata';
Metadata.CustomMetadata customMetadata = buildCustomMetadata(fullName,label);
deployContainer.addMetadata(customMetadata);
}

private static String mergeFullName(String metadataName, String QualifiedApiName) {
String DeveloperName = (QualifiedApiName.length() > 40) ? QualifiedApiName.substring(0,40) : QualifiedApiName;
return metadataName.replace('__mdt','')+'.'+DeveloperName;
}

private static Metadata.CustomMetadata buildCustomMetadata(String fullName, String recordLabel) {
Metadata.CustomMetadata customMetadata = new Metadata.CustomMetadata();
customMetadata.fullName = fullName;
customMetadata.label = (recordLabel.length() > 40) ? recordLabel.substring(0,40) : recordLabel;

Map<String,Schema.SObjectField> describeFieldMap = describeSObjectResult.fields.getMap();

for(String fieldName : populatedFieldsMap.keySet()) processCustomMetadataFieldValues(customMetadata,describeFieldMap.get(fieldName));

return customMetadata;
}

private static void processCustomMetadataFieldValues(Metadata.CustomMetadata customMetadata,Schema.SObjectField sObjectField) {

if(sObjectField == null) return; /**Return if sObjectField not found**/
DescribeFieldResult fieldResult = sObjectField.getDescribe();
if(!fieldResult.isCustom()) return; /**Return If Standard Field**/

String fieldName = fieldResult.getName();
Boolean isReference = fieldResult.getType() === DisplayType.REFERENCE;
Boolean isEntityRelationshipField = entityRelationshipFields.contains(fieldResult);

Object value = null;

if(isReference || isEntityRelationshipField) {
SObject relatedRecord;
Map<String,Object> relatedRecordPopulatedFieldMap;
String relationshipName = (isEntityRelationshipField) ? fieldName.removeEnd('__c')+'__r' : fieldResult.getRelationshipName(); /**We replace __c with __r to resolve Entity Relationship non reference**/
if(populatedFieldsMap.containsKey(relationshipName)) {
try{
relatedRecord = (SObject) populatedFieldsMap.get(relationshipName);
relatedRecordPopulatedFieldMap = relatedRecord.getPopulatedFieldsAsMap();
} catch(Exception ex) {
System.debug(ex.getMessage());
}
}
if(relatedRecordPopulatedFieldMap != null && relatedRecordPopulatedFieldMap.containsKey(QualifiedApiName_Field)) value = (String) relatedRecordPopulatedFieldMap.get(QualifiedApiName_Field);

} else if(populatedFieldsMap.containsKey(fieldName)) value = populatedFieldsMap.get(fieldName);

if(value == null || value == '' && fieldResult.defaultValue != null) value = fieldResult.defaultValue;
else if(value == null || value == '' && fieldResult.defaultValueFormula != null) value = fieldResult.defaultValueFormula.removeEnd('\'').removeEnd('"').removeStart('\'').removeStart('"');

Metadata.CustomMetadataValue metadataField = new Metadata.CustomMetadataValue();
metadataField.field = fieldName;
metadataField.value = value;

customMetadata.values.add(metadataField);
}

}
5 changes: 5 additions & 0 deletions force-app/main/default/classes/MetadataUtility.cls-meta.xml
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>
Loading
Loading