44// ----------------------------------------------------------------------------------------------------//
55
66public inherited sharing class CustomMetadataSaver {
7+ private static final Boolean DEFAULT_SEND_EMAIL_ON_ERROR = false ;
8+ private static final Boolean DEFAULT_SEND_EMAIL_ON_SUCCESS = false ;
79 private static final List <String > DEPLOYMENT_JOB_IDS = new List <String >();
810 private static final Set <String > IGNORED_FIELD_NAMES = getIgnoredFieldNames ();
911
1012 public class FlowInput {
11- @InvocableVariable(required= true label = ' The collection of custom metadata type records to deploy ' )
13+ @InvocableVariable(required= true label = ' Custom Metadata Type Records to Deploy ' )
1214 public List <SObject > customMetadataRecords ;
15+
16+ @InvocableVariable(
17+ required= true
18+ label = ' Send Email Alert if the Deployment Fails'
19+ description = ' This option is only used by the default callback Apex class.'
20+ )
21+ public Boolean sendEmailOnError ;
22+
23+ @InvocableVariable(
24+ required= true
25+ label = ' Send Email Alert if the Deployment Succeeds'
26+ description = ' This option is only used by the default callback Apex class.'
27+ )
28+ public Boolean sendEmailOnSuccess ;
29+
30+ @InvocableVariable(
31+ required= false
32+ label = ' (Optional) Custom Callback Apex Class'
33+ description = ' The name of your Apex class to execute after the deployment completes. When provided, this is used instead of the default callback.'
34+ )
35+ public String customCallbackName ;
1336 }
1437
1538 private CustomMetadataSaver () {
@@ -18,27 +41,49 @@ public inherited sharing class CustomMetadataSaver {
1841
1942 @InvocableMethod(
2043 category= ' Custom Metadata'
21- label = ' Deploy Changes to Custom Metadata Records'
44+ label = ' Deploy Custom Metadata Type Records'
2245 description = ' Deploys changes to the list of custom metadata records'
2346 )
24- public static void deploy (List <FlowInput > inputs ) {
47+ public static List < String > deploy (List <FlowInput > inputs ) {
2548 System .debug (' inputs==' + inputs );
2649
27- List <SObject > consolidatedList = new List <SObject >();
50+ Boolean sendEmailOnError = DEFAULT_SEND_EMAIL_ON_ERROR ;
51+ Boolean sendEmailOnSuccess = DEFAULT_SEND_EMAIL_ON_SUCCESS ;
52+ String customCallbackName ;
53+
54+ // Combine all CMDT records into 1 list so that there is only 1 deployment
55+ // TODO allow this to be controlled via FlowInput - there are uses cases for also having multiple deployments
56+ List <SObject > consolidatedCustomMetadataRecords = new List <SObject >();
2857 for (FlowInput input : inputs ) {
29- consolidatedList .addAll (input .customMetadataRecords );
58+ consolidatedCustomMetadataRecords .addAll (input .customMetadataRecords );
59+ // If any of the inputs confirm that an email should be sent, then send it - otherwise, no email will be sent
60+ if (input .sendEmailOnError == true ) {
61+ sendEmailOnError = input .sendEmailOnError ;
62+ }
63+ // If any of the inputs confirm that an email should be sent, then send it - otherwise, no email will be sent
64+ if (input .sendEmailOnSuccess == true ) {
65+ sendEmailOnSuccess = input .sendEmailOnSuccess ;
66+ }
67+ // Assumption: only a single custom callback will be specified
68+ // If we find a custom callback class name, update the variable
69+ if (String .isNotBlank (input .customCallbackName )) {
70+ customCallbackName = input .customCallbackName ;
71+ }
3072 }
3173
32- System .debug (' consolidatedList==' + consolidatedList );
74+ System .debug (' consolidatedCustomMetadataRecords==' + consolidatedCustomMetadataRecords );
75+
76+ Metadata .DeployCallback callback = getFlowDeployCallback (customCallbackName , sendEmailOnError , sendEmailOnSuccess );
77+ System .debug (' callback==' + callback );
3378
34- deploy (consolidatedList , null ) ;
79+ return new List < String >{ deploy (consolidatedCustomMetadataRecords , callback ) } ;
3580 }
3681
37- public static void deploy (List <SObject > customMetadataRecords ) {
38- deploy (customMetadataRecords , null );
82+ public static String deploy (List <SObject > customMetadataRecords ) {
83+ return deploy (customMetadataRecords , new DefaultDeployCallback () );
3984 }
4085
41- public static void deploy (List <SObject > customMetadataRecords , Metadata.DeployCallback callback ) {
86+ public static String deploy (List <SObject > customMetadataRecords , Metadata.DeployCallback callback ) {
4287 Metadata .DeployContainer deployment = new Metadata .DeployContainer ();
4388
4489 for (SObject customMetadataRecord : customMetadataRecords ) {
@@ -49,6 +94,8 @@ public inherited sharing class CustomMetadataSaver {
4994 String jobId = Test .isRunningTest () ? ' Fake Job ID' : Metadata .Operations .enqueueDeployment (deployment , callback );
5095 DEPLOYMENT_JOB_IDS .add (jobId );
5196 System .debug (LoggingLevel .INFO , ' Deployment Job ID: ' + jobId );
97+
98+ return jobId ;
5299 }
53100
54101 public static List <String > getDeploymentJobIds () {
@@ -93,4 +140,81 @@ public inherited sharing class CustomMetadataSaver {
93140
94141 return customMetadata ;
95142 }
143+
144+ private static Metadata.DeployCallback getFlowDeployCallback (String customCallbackName , Boolean sendEmailOnError , Boolean sendEmailOnSuccess ) {
145+ Metadata .DeployCallback callback ;
146+ if (String .isNotBlank (customCallbackName ) && Type .forName (customCallbackName ) != null ) {
147+ // Dynamically create a new instance of the specified class
148+ // Assumption: specified class uses a parameterless constructor
149+ System .debug (' customCallbackName==' + customCallbackName );
150+ callback = (Metadata .DeployCallback ) Type .forName (customCallbackName ).newInstance ();
151+ } else {
152+ // If no custom callback class is specified, use the default inner class
153+ callback = new DefaultDeployCallback (sendEmailOnError , sendEmailOnSuccess );
154+ }
155+ return callback ;
156+ }
157+
158+ private static void sendEmail (String subject , String textBody ) {
159+ Messaging .SingleEmailMessage singleEmail = new Messaging .SingleEmailMessage ();
160+ singleEmail .setToAddresses (new List <String >{ UserInfo .getUserEmail () });
161+ singleEmail .setSubject (subject );
162+ singleEmail .setPlainTextBody (textBody );
163+
164+ Messaging .sendEmail (new List <Messaging .SingleEmailMessage >{ singleEmail });
165+ }
166+
167+ // Inner class used as the default callback if no other callback is specified
168+ @testVisible
169+ private class DefaultDeployCallback implements Metadata .DeployCallback {
170+ @testVisible
171+ private Boolean success ;
172+
173+ private Boolean sendEmailOnError ;
174+ private Boolean sendEmailOnSuccess ;
175+
176+ @testVisible
177+ private DefaultDeployCallback () {
178+ this (DEFAULT_SEND_EMAIL_ON_ERROR , DEFAULT_SEND_EMAIL_ON_SUCCESS );
179+ }
180+ @testVisible
181+ private DefaultDeployCallback (Boolean sendEmailOnError , Boolean sendEmailOnSuccess ) {
182+ this .sendEmailOnError = sendEmailOnError ;
183+ this .sendEmailOnSuccess = sendEmailOnSuccess ;
184+ }
185+
186+ public void handleResult (Metadata.DeployResult result , Metadata.DeployCallbackContext context ) {
187+ System .debug (' deploy result.success==' + result .success );
188+ System .debug (' deploy result==' + result );
189+ System .debug (' deploy callback context==' + context );
190+
191+ System .debug (' this.sendEmailOnError==' + this .sendEmailOnError );
192+ System .debug (' this.sendEmailOnSuccess==' + this .sendEmailOnSuccess );
193+
194+ this .success = result .success ;
195+
196+ // Build the email pieces
197+ String subject = ' Custom metadata type deployment completed' ;
198+ String textBody = ' Deployment ID {0} completed\n Status: {1}\n {2} total items deployed\n {3} items failed\n Details: {4}' ;
199+ List <Object > textReplacements = new List <Object >{
200+ result .id ,
201+ result .status ,
202+ result .numberComponentsTotal ,
203+ result .numberComponentErrors ,
204+ Json .serializePretty (result .details )
205+ };
206+ textBody = String .format (textBody , textReplacements );
207+
208+ // Send the email
209+ if (result .success == true && this .sendEmailOnSuccess == true ) {
210+ System .debug (' Deployment Succeeded!' );
211+
212+ sendEmail (subject , textBody );
213+ } else if (result .success == false && this .sendEmailOnError == true ) {
214+ System .debug (' Deployment Failed!' );
215+
216+ sendEmail (subject , textBody );
217+ }
218+ }
219+ }
96220}
0 commit comments