Skip to content

Instantly share code, notes, and snippets.

@afawcett
Last active August 26, 2017 06:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save afawcett/678c4e4a02e7ab7fc84a00ebc0144586 to your computer and use it in GitHub Desktop.
Save afawcett/678c4e4a02e7ab7fc84a00ebc0144586 to your computer and use it in GitHub Desktop.
/**
*
CustomMetadata.Operations
.callback(
// Platform event for deploy status
MetadataDeployment__e.getSObjectType(),
MetadataDeployment__e.DeploymentId__c,
MetadataDeployment__e.Result__c)
.enqueueUpsertRecords(
// Metadata record type
LookupRollupSummary2__mdt.getSObjectType(),
new List<Map<SObjectField, Object>> {
// Metadata record
new Map<SObjectField, Object> {
LookupRollupSummary2__mdt.DeveloperName => 'MyRecord2',
LookupRollupSummary2__mdt.Label => 'My Record',
LookupRollupSummary2__mdt.ParentObject__c => 'Account',
LookupRollupSummary2__mdt.AggregateResultField__c => 'X',
LookupRollupSummary2__mdt.ChildObject__c => 'Opportunity',
LookupRollupSummary2__mdt.FieldToAggregate__c => 'Amount',
LookupRollupSummary2__mdt.RelationshipField__c => 'AccountId'
} } );
LookupRollupSummary2__mdt readRecord =
[select DeveloperName, Label, ParentObject__c, ChildObject__c from LookupRollupSummary2__mdt limit 1];
CustomMetadata.Operations
.callback(
// Platform event for deploy status
MetadataDeployment__e.getSObjectType(),
MetadataDeployment__e.DeploymentId__c,
MetadataDeployment__e.Result__c)
.enqueueUpsertRecords(
new List<SObject> { readRecord } );
**/
public with sharing class CustomMetadata {
public static final Operations Operations = new Operations();
public class Operations {
private SaveResultCallback deployCallback;
public String deployId {get;private set;}
private Operations() { }
/**
* Takes a raw custom metadata record list and deploys it
**/
public Operations enqueueUpsertRecords(List<Metadata.CustomMetadata> records) {
// TODO: Throw an exception if duplicates found
// TODO: Throw exception if the callback has not been set
Metadata.DeployContainer mdContainer = new Metadata.DeployContainer();
for(Metadata.CustomMetadata record : records) {
mdContainer.addMetadata(record);
}
// Caller will use this generated Id to associate the callback/event with this deployment
deployId = deployCallback.deployId;
// Current return Id from platform is "DeployRequest" record, callback Id passed by platform is "AsyncApexJob"?
Platform.metadatEnqueueDeployment(mdContainer, deployCallback);
return this;
}
/**
* Leverages the fact that MDT SObject's can be bound and thus edited on a VF page!
* And the hope that one day we can edit in memory native MDT SObject type fields ;-)
**/
public Operations enqueueUpsertRecords(List<SObject> records) {
// TODO: Throw an exception for empty lists
SObjectType sObjectType = records[0].getSObjectType();
DescribeSObjectResult dsr = sObjectType.getDescribe();
Map<String, SObjectField> fieldTokenByName = dsr.fields.getMap();
List<Map<SObjectField, Object>> sObjectRecords = new List<Map<SObjectField, Object>>();
for(SObject record: records) {
Map<SObjectField, Object> valuesByField = new Map<SObjectField, Object>();
Map<String, Object> populatedByField = record.getPopulatedFieldsAsMap();
for(String fieldName : populatedByField.keySet()) {
if(fieldName == 'Id') {
continue;
}
SObjectField fieldToken = fieldTokenByName.get(fieldName);
Object fieldValue = populatedByField.get(fieldName);
valuesByField.put(fieldToken, fieldValue);
}
sObjectRecords.add(valuesByField);
}
return enqueueUpsertRecords(sObjectType, sObjectRecords);
}
/**
* Deploy custom metadata records described as a field value pair (good for keeping referential integrity)
* @param sobjectType the SObjectType of the MDT object via MyCustomMetadata__c.getSObjectType()
* @param sobjectRecords the list of field value maps representing the records
**/
public Operations enqueueUpsertRecords(SObjectType sobjectType, List<Map<SObjectField, Object>> sObjectRecords) {
List<Metadata.CustomMetadata> records = new List<Metadata.CustomMetadata>();
for (Map<SObjectField, Object> sObjectRecord : sObjectRecords) {
Metadata.CustomMetadata customMetadataRecord = new Metadata.CustomMetadata();
customMetadataRecord.values = new List<Metadata.CustomMetadataValue>();
for(SObjectField field : sObjectRecord.keySet()) {
DescribeFieldResult dsr = field.getDescribe();
Object fieldValue = sObjectRecord.get(field);
if(dsr.getName() == 'DeveloperName') {
customMetadataRecord.fullName =
sobjectType.getDescribe().getName().replace('__mdt', '') + '.' + fieldValue;
} else if(dsr.getName() == 'Label') {
customMetadataRecord.label = (String) fieldValue;
} else {
Metadata.CustomMetadataValue cmv = new Metadata.CustomMetadataValue();
cmv.field = dsr.getName();
cmv.value = fieldValue;
customMetadataRecord.values.add(cmv);
}
}
records.add(customMetadataRecord);
}
return enqueueUpsertRecords(records) ;
}
/**
* Registers a custom callback implementation
**/
public Operations callback(SaveResultCallback saveResultCallback) {
this.deployCallback = saveResultCallback;
return this;
}
/**
* Fires the given Platform Event populating the given message field with a
* JSON serialised representation of SaveRecordResult
**/
public Operations callback(SObjectType eventType, SObjectField deploymentIdField, SObjectField messageField) {
return callback(new PublishEventCallback(eventType, deploymentIdField, messageField));
}
}
/**
* Sends a Platform Event in response to a Metadata Deploy callback
**/
private class PublishEventCallback extends SaveResultCallback {
private SObjectType eventType;
private SObjectField messageField;
private SObjectFIeld deploymentIdField;
public PublishEventCallback(SObjectType eventType, SObjectField deploymentIdField, SObjectField messageField) {
this.eventType = eventType;
this.messageField = messageField;
this.deploymentIdField = deploymentIdField;
}
public override void handleResult(String deployId, List<SaveRecordResult> result) {
// Create event to publish
SObject event = eventType.newSObject();
event.put(deploymentIdField, deployId);
event.put(messageField, JSON.serialize(result, true));
// Call method to publish events
List<Database.SaveResult> results = Platform.eventBusPublish(new List<SObject> { event });
// Inspect publishing result for each event
// TODO: Consider another way to report this, since this is the phantom users options are limited
for (Database.SaveResult sr : results) {
if (sr.isSuccess()) {
System.debug('Successfully published event.');
} else {
for(Database.Error err : sr.getErrors()) {
System.debug('Error returned: ' +
err.getStatusCode() +
err.getMessage());
}
}
}
}
}
/**
* Base class handler for internal and external handlers,
* marshalls internal deploy results into something easier to handle
**/
public abstract class SaveResultCallback implements Metadata.DeployCallback
{
public String deployId {get; private set;}
public SaveResultCallback() {
// Credit: https://success.salesforce.com/ideaView?id=08730000000KgTYAA0
deployId = EncodingUtil.convertToHex(Crypto.generateAesKey(128));
}
public void handleResult(Metadata.DeployResult result, Metadata.DeployCallbackContext context) {
List<SaveRecordResult> saveRecordResults = new List<SaveRecordResult>();
for(Metadata.DeployMessage deployMessage : result.details.componentFailures) {
if(deployMessage.fileName == 'package.xml') {
continue;
}
saveRecordResults.add(convertToSaveResult(deployMessage));
}
for(Metadata.DeployMessage deployMessage : result.details.componentSuccesses) {
if(deployMessage.fileName == 'package.xml') {
continue;
}
saveRecordResults.add(convertToSaveResult(deployMessage));
}
handleResult(deployId, saveRecordResults);
}
private SaveRecordResult convertToSaveResult(Metadata.DeployMessage deployMessage) {
SaveRecordResult srr = new SaveRecordResult();
srr.fullName = deployMessage.fullName;
srr.status = deployMessage.success;
srr.message = deployMessage.problem;
return srr;
}
/**
* Handler for simplified metadata deployment result based around custom metadata records
**/
public abstract void handleResult(String deployId, List<SaveRecordResult> results);
}
public class SaveRecordResult {
@AuraEnabled
// TODO: Maybe resolve to the SObjectType and avoid namespace issues, does this marshal via Aura though?
public String fullName;
@AuraEnabled
public Boolean status;
@AuraEnabled
public String message;
}
public class CustomMetadataException extends Exception {}
/**
* Basic dependency injection impl for platform APIs leveraged by this class
**/
@TestVisible
private static Platform Platform = new RuntimePlatform();
@TestVisible
private abstract class Platform {
public abstract List<Database.SaveResult> eventBusPublish(List<SObject> events);
public abstract Id metadatEnqueueDeployment(Metadata.DeployContainer mdContainer, Metadata.DeployCallback deployCallback);
}
private class RuntimePlatform extends Platform {
public override List<Database.SaveResult> eventBusPublish(List<SObject> events) {
return System.EventBus.publish(events);
}
public override Id metadatEnqueueDeployment(Metadata.DeployContainer mdContainer, Metadata.DeployCallback deployCallback) {
return Metadata.Operations.enqueueDeployment(mdContainer, deployCallback);
}
}
}
@IsTest
private class CustomMetadataTest {
@IsTest
private static void whenUpsertingByFieldMapSendsEvent() {
// Given
MockPlatform mockPlatform = new MockPlatform();
mockPlatform.metadataEnqueueDeploymentResult = new Metadata.DeployResult();
mockPlatform.metadataEnqueueDeploymentResult.details = new Metadata.DeployDetails();
mockPlatform.metadataEnqueueDeploymentResult.details.componentFailures = new List<Metadata.DeployMessage>();
mockPlatform.metadataEnqueueDeploymentResult.details.componentSuccesses = new List<Metadata.DeployMessage>();
Metadata.DeployMessage deploySuccess =
(Metadata.DeployMessage) JSON.deserialize(
'{"fullName":"Account","success":true}', Metadata.DeployMessage.class);
mockPlatform.metadataEnqueueDeploymentResult.details.componentSuccesses.add(deploySuccess);
mockPlatform.eventBusPublishResponse =
(List<Database.SaveResult>) JSON.deserialize(
'[{"id":"e00xx000000001TAAQ","success":true,"errors":[],"warnings":[]}]', List<Database.SaveResult>.class);
CustomMetadata.Platform = mockPlatform;
List<Map<SObjectField, Object>> records =
new List<Map<SObjectField, Object>> {
new Map<SObjectField, Object> {
Account.Description => 'My Record'
} };
// When
String deployId =
CustomMetadata.Operations
// Opportunity SObject emulating Event object
.callback(Opportunity.getSObjectType(), Opportunity.Name, Opportunity.Description)
// Account SObject emulating Custom Metadata object
.enqueueUpsertRecords(Account.getSObjectType(), records)
.deployId;
// Then
System.assertEquals(
'Description', ((Metadata.CustomMetadata) mockPlatform.metadataEnqueueDeploymentContainer.getMetadata()[0]).values[0].field);
System.assertEquals(
'My Record', ((Metadata.CustomMetadata) mockPlatform.metadataEnqueueDeploymentContainer.getMetadata()[0]).values[0].value);
System.assertEquals(
'[{"status":true,"fullName":"Account"}]',
mockPlatform.eventBusPublishEvents[0].get('Description'));
}
@IsTest
private static void whenUpsertingBySObjectSendsEvent() {
// Given
MockPlatform mockPlatform = new MockPlatform();
mockPlatform.metadataEnqueueDeploymentResult = new Metadata.DeployResult();
mockPlatform.metadataEnqueueDeploymentResult.details = new Metadata.DeployDetails();
mockPlatform.metadataEnqueueDeploymentResult.details.componentFailures = new List<Metadata.DeployMessage>();
mockPlatform.metadataEnqueueDeploymentResult.details.componentSuccesses = new List<Metadata.DeployMessage>();
Metadata.DeployMessage deploySuccess =
(Metadata.DeployMessage) JSON.deserialize(
'{"fullName":"Account","success":true}', Metadata.DeployMessage.class);
mockPlatform.metadataEnqueueDeploymentResult.details.componentSuccesses.add(deploySuccess);
mockPlatform.eventBusPublishResponse =
(List<Database.SaveResult>) JSON.deserialize(
'[{"id":"e00xx000000001TAAQ","success":true,"errors":[],"warnings":[]}]', List<Database.SaveResult>.class);
CustomMetadata.Platform = mockPlatform;
List<SObject> records = new List<SObject> { new Account(Description = 'My Record') };
// When
String deployId =
CustomMetadata.Operations
// Opportunity SObject emulating Event object
.callback(Opportunity.getSObjectType(), Opportunity.Name, Opportunity.Description)
// Account SObject emulating Custom Metadata object
.enqueueUpsertRecords(records)
.deployId;
// Then
System.assertEquals(
'Description', ((Metadata.CustomMetadata) mockPlatform.metadataEnqueueDeploymentContainer.getMetadata()[0]).values[0].field);
System.assertEquals(
'My Record', ((Metadata.CustomMetadata) mockPlatform.metadataEnqueueDeploymentContainer.getMetadata()[0]).values[0].value);
System.assertEquals(
'[{"status":true,"fullName":"Account"}]',
mockPlatform.eventBusPublishEvents[0].get('Description'));
}
/**
* Mock platform API behaviours and responses
**/
public class MockPlatform extends CustomMetadata.Platform {
// Mock test inputs and captured responses
public List<Database.SaveResult> eventBusPublishResponse;
public List<SObject> eventBusPublishEvents;
public Metadata.DeployContainer metadataEnqueueDeploymentContainer;
public Metadata.DeployResult metadataEnqueueDeploymentResult;
// Mock System.EventBus.publish
public override List<Database.SaveResult> eventBusPublish(List<SObject> events) {
eventBusPublishEvents = events;
return eventBusPublishResponse;
}
// Mock Metadata.Operations.enqueueDeployment
public override Id metadatEnqueueDeployment(Metadata.DeployContainer mdContainer, Metadata.DeployCallback deployCallback) {
metadataEnqueueDeploymentContainer = mdContainer;
deployCallback.handleResult(metadataEnqueueDeploymentResult, new Metadata.DeployCallbackContext());
return null; // Not used
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment