Creating Workflow Rules Using MetadataService.cls

I had trouble creating this particular type of metadata using only apex code once. And I’ll try to explain the process so you don’t have to waste 2 goddamn days of your life trying to figure out why the hell Salesforce is telling you that the field ‘fullName’ is empty.

First of all, in Salesforce the Workflow object isn’t actually a system object, which means you cannot use it on queries (‘SELECT id FROM workflow’ – no, you can’t do that). What is it then? Pure Metadata. It just references some of your objects and fields (which aren’t just metadata).

One thing I strongly recommend when dealing with this kind of thing is using the Migration Tool. It can be really useful to see what you currently have in your organization. Some things do not appear in the UI, and you might think there is not much in there. WorkflowFieldUpdates, for instance, do not appear unless you have a WorkflowRule working with them.

Now to our example:

You will need the MetadataService class, which can be obtained here.

Suppose we need to create a WorkflowRule that has the following criteria:

It has to be linked to a custom object that has different RecordTypes, and we trigger the rule based on two fields of this object, comparing with the current date. It has to be active (so it can work, duh), and set a value in a field for this custom object.

Well that seems pretty easy, right? Right. If you were using the UI. But you are using Apex. So it is still easy, but requires a little more brain work.

<fieldUpdates>
    <fullName>Active_Permission</fullName>
    <description>Actives a permission.</description>
    <field>Active__c</field>
    <literalValue>1</literalValue>
    <name>Active Permission</name>
    <notifyAssignee>false</notifyAssignee>
    <operation>Literal</operation>
    <protected>false</protected>
</fieldUpdates>
<rules>
    <fullName>Manage Active Temporary Permission</fullName>
    <active>true</active>
    <description>Manages when a temporary permission has to be set as Active.</description>
    <formula>IF(RecordType.DeveloperName=&apos;Temporary&apos; &amp;&amp; BeginDate__c > Today(),TRUE,FALSE)</formula>
    <triggerType>onCreateOrTriggeringUpdate</triggerType>
    <workflowTimeTriggers>
        <actions>
            <name>Active_Permission</name>
            <type>FieldUpdate</type>
        </actions>
        <offsetFromField>Permission__c.BeginDate__c</offsetFromField>
        <timeLength>0</timeLength>
        <workflowTimeTriggerUnit>Hours</workflowTimeTriggerUnit>
    </workflowTimeTriggers>
</rules>

The code above was obtained in the Permission__c.workflow file, retrieved directly from the SF organization using the Migration Tool. I’ve simplified this snippet a little, since there are other fieldUpdates and rules in the file.

If you take a good look in this file, you’ll notice that it contains every bit of information we need to recreate it with our MetadataService class.

public void create_ManageInactiveTemporaryPermission()
{
    MetadataService.MetadataPort ms = MetadataJob.createService();
    MetadataService.WorkflowActionReference workflowActionReference = new MetadataService.WorkflowActionReference();
    MetadataService.WorkflowRule workflowRule = new MetadataService.WorkflowRule();
    MetadataService.WorkflowTimeTrigger workflowTimeTrigger = new MetadataService.WorkflowTimeTrigger();
    MetadataService.WorkflowFieldUpdate workflowFieldUpdate = new MetadataService.WorkflowFieldUpdate();

    workflowFieldUpdate.fullName = 'Permission__c.Inactivate_Permission';
    workflowFieldUpdate.description = 'Inactivates a permission.';
    workflowFieldUpdate.field = 'Active__c';
    workflowFieldUpdate.literalValue = '0';
    workflowFieldUpdate.name = 'Inactivate Permission';
    workflowFieldUpdate.notifyAssignee = false;
    workflowFieldUpdate.operation = 'Literal';
    workflowFieldUpdate.protected_x = false;

    workflowActionReference.name = 'Inactivate_Permission';
    workflowActionReference.type_x = 'FieldUpdate';

    workflowTimeTrigger.offsetFromField = 'Permission__c.EndDate__c';
    workflowTimeTrigger.timeLength = '0';
    workflowTimeTrigger.workflowTimeTriggerUnit = 'Hours';
    workflowTimeTrigger.actions = new MetadataService.WorkflowActionReference[]{workflowActionReference};

    workflowRule.fullName = 'Permission__c.ManageInactiveTemporaryPermission';
    workflowRule.active = true;
    workflowRule.description = 'Manages when a temporary permission has to be set as inactive.';
    workflowRule.formula = 'IF(RecordType.DeveloperName=\'Temporary\' && EndDate__c > Today(),TRUE,FALSE)';
    workflowRule.triggerType = 'onCreateOrTriggeringUpdate';
    workflowRule.actions = new MetadataService.WorkflowActionReference[]{workflowActionReference};
    workflowRule.workflowTimeTriggers = new MetadataService.WorkflowTimeTrigger[]{workflowTimeTrigger};

    MetadataService.Metadata[] theMetadata = new MetadataService.Metadata[]{};

    theMetadata.add(workflowFieldUpdate);
    theMetadata.add(workflowRule);

    MetadataService.SaveResult[] results = ms.createMetadata(theMetadata);

    for (MetadataService.SaveResult sr : results)
    {
        MetadataJob.handleSaveResults(sr);
    }
}

Note about the MetadataJob class: this is a class I created to check the results and show the error message in a simpler way (so I don’t need to go to the debug log every freaking time I try to make it work). It just receives a MetadataService.SaveResult object and checks if it is null or if its .success property is true. It also checks if its errors property isn’t empty, and if it isn’t, display the errors to me. In the end, if those two previous conditions aren’t met, it throws an exception telling me that the service failed with an unknown reason.

Getting back to our code above, you can see that I basically copy the metadata into the fields in the Apex code. Some things are to be noticed, though:

At line 9, I have to specify the object’s name before the name of the action I want the rule to do (updating a field is an action, right?). I have to do this because the WorkflowFieldUpdate does not hold anything that it can relate to besides a object in the organization. You’ll notice that I create it before the rule itself (see line 36), and that the rule also have the object name before the rule name, and so does the TimeTrigger.

After creating my action, I’m off to create the rule. For this, I check my XML file. Notice that the WorkflowRule ‘object’ has two fields to reference other things, that are interesting to us: actions and workflowTimeTriggers.

Those two have to be created before the rule, and referenced to it after. So I create the ActionReference first (line 18), then the TimeTrigger (line 21) and finally I create my rule (at line 26) referencing those two in the respective fields.

I haven’t tested this, but since the actions and workflowTimeTriggers properties are lists, I assume you can have multiple actions to a rule, and multiple timeTriggers too.

When I have everything set, I send the metadata to the createMetadata function, that actually does the whole connection and conversion to request Salesforce to create my things in the organization.

If everything goes right, you will have your workflows created after running this code!

You can even delete it afterwards using the deleteMetadata function, which is even simpler. You just pass the name of the metadata and a list of names of objects that have this type). So, for my example, it would be something like this:

theMetadataService.deleteMetadata('WorkflowFieldUpdate', new List<String>{'Permission__c.Active_Permission'})

Be warned though: if you are working in an environment with managed packages, you will have to add the namespace before both names, like:

theMetadataService.deleteMetadata('WorkflowFieldUpdate', new List<String>{'myPkgNamespace__Permission__c.myPkgNamespace__Active_Permission'})

If you don’t do that, Salesforce won’t recognize your metadata, and tell you “hey, I didn’t find anything with this name in here!”.
 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s