Metadata mit Apex: RelatedList und QuickAction einem Layout hinzufügen

Mit Summer '17 lassen sich Layouts und Custom Metadata Objekte mittels Apex updaten. Ich hab das mal ausprobiert, hier ein Tutorial

1. Ziel

Auf dem Standard Opportunity Layout eine QuickAction und eine RelatedList hinzufügen. Beides aus einem Managed Package.

2. Wissenswertes

  • Apex stupst ein Deployment an
  • Wie alle anderen Deployments auch, laufen Apex Deployments asynchron
  • Das hat zur Folge, daß zwei Klassen notwendig sind, um Apex Metadata Deployment einzurichten.
  1. Eine Callback Klasse - die wird mit dem Ergebnis des Deployments aufgerufen
  2. Die Klasse, in der die Metadaten geschrieben/verändert werden
  • Bei den meisten Fragen hilft nicht der Apex Developer Guide, sondern der für Metadaten

3. Struktur eines Deployments via Apex

Ganz oben der Callback als inner Class. Der Callback wird im ApexLog in der Developer Console unter 'N/A' zu erkennen sein. Darauf ein Pseudo Retrieve und Pseudo Deploy.

public class DeploymentExample {
    public class deployCallback implements Metadata.DeployCallback {
        public void handleResult(Metadata.DeployResult result, Metadata.DeployCallbackContext context) {                
            if (result.status == Metadata.DeployStatus.Succeeded) {
                system.debug('deploy succeeded');
            } else {
                system.debug('ERROR: ' +result);
                system.debug('ERROR: ' +context);
            }
        }
    }
    //retrieve components 
    List<Metadata.Metadata> mdapiComponents = Metadata.Operations.retrieve(Metadata.MetadataType.X, listNamesToRetrieve);
    //... change concrete components ...
    //...
    
    //do deployment
     Metadata.DeployContainer container = new Metadata.DeployContainer();
     container.addMetadata(component);
        
     deployCallback callback = new deployCallback();
     Metadata.Operations.enqueueDeployment(container, callback);
}

4. Retrieve Standard Opportunity Layout

Wir bereiten damit auch gleich alles vor, um das Layout zu verändern.

//the metadata component that will be edited
Metadata.layout layout =  new Metadata.layout();

//retrieve Opp Layout
List<String> layoutsToRetrieve = new List<String>{'Opportunity-Opportunity Layout'};
List<Metadata.Metadata> mdapiComponents = Metadata.Operations.retrieve(Metadata.MetadataType.Layout, layoutsToRetrieve);

layout = (Metadata.layout)mdapiComponents.get(0);

5. Related List und QuickAction hinzufügen, Deployen

Metadaten sind komplex, entsprechend viele Apex-Zeilen werden hier gebraucht. Ein solider Hintergrund als Administrator zwischen Classic, Lightning und Salesforce1 hilft ungemein, sich zurechtzufinden. Ein Retrieve der Metadaten vor und nach manuellem Verändern des Layouts sei auch wärmstens empfohlen.

// Access appropriate Section on Layout for QuickActions
Metadata.PlatformActionList listActions = layout.PlatformActionList;

//in a clean, vanilla org the ActionList will not be present. if so, we create a new one
if (listActions == null) {
  listActions = new Metadata.PlatformActionList();
  listActions.actionListContext = Metadata.PlatformActionListContextEnum.Record;
  listActions.PlatformActionListItems = new List<Metadata.PlatformActionListItem>();
 layout.PlatformActionList = listActions;
}

// for convenience we add the standard edit action        
Metadata.PlatformActionListItem newActionEdit = new Metadata.PlatformActionListItem();
newActionEdit.actionName = 'Edit';
newActionEdit.actionType = Metadata.PlatformActionTypeEnum.StandardButton;
newActionEdit.sortOrder = 0;

//and our custom lightning quick action
Metadata.PlatformActionListItem newActionCustom = new Metadata.PlatformActionListItem();
newActionCustom.actionName = 'Opportunity.myNamespace__myQuickActionAPIName';
newActionCustom.actionType = Metadata.PlatformActionTypeEnum.QuickAction;
newActionCustom.sortOrder = 1;

// add the components to action list and thereby also to layout metadata
listActions.PlatformActionListItems.add(newActionCustom);
listActions.PlatformActionListItems.add(newActionEdit);

//create a new related list 
Metadata.RelatedListItem newList = new Metadata.RelatedListItem();
        
newList.customButtons = new List<String>();
newList.excludeButtons = new List<String>();
        
newList.fields = new List<String>{'NAME','myNamespace__CustomField1__c'}; //...    

//that was a tricky one :)       
newList.relatedList = 'myNamespace__myCustomObject__c.myNamespace__myCustomLookUpFieldToOpportunity__c';

// add to layout related lists and thereby to layout
layout.relatedLists.add(newList);

//do deployment
Metadata.DeployContainer container = new Metadata.DeployContainer();

//only the layout as a whole needs to be added
container.addMetadata(layout);
        
deployCallback callback = new deployCallback();
Metadata.Operations.enqueueDeployment(container, callback);