init
This commit is contained in:
@@ -0,0 +1,374 @@
|
||||
public inherited sharing class CreateCaseAutoResponseRule {
|
||||
private static final String SENDER_NAME = 'Columbus McKinnon Support';
|
||||
private static final String SENDER_EMAIL = 'cmcoautoreply@columbusmckinnon.com';
|
||||
private static final Integer MAX_POLLS = 20;
|
||||
|
||||
public class Request {
|
||||
@InvocableVariable(label='Auto Response Rule Id')
|
||||
public Id autoResponseRuleId;
|
||||
|
||||
@InvocableVariable(label='Auto Response Rule Name')
|
||||
public String autoResponseRuleName;
|
||||
|
||||
@InvocableVariable(label='Sort Order')
|
||||
public Integer sortOrder;
|
||||
|
||||
@InvocableVariable(required=true label='Case Origin')
|
||||
public String caseOrigin;
|
||||
|
||||
@InvocableVariable(required=true label='Email Template Id')
|
||||
public Id emailTemplateId;
|
||||
}
|
||||
|
||||
public class Result {
|
||||
@InvocableVariable(label='Async Apex Job Id')
|
||||
public Id asyncJobId;
|
||||
|
||||
@InvocableVariable(label='Status')
|
||||
public String status;
|
||||
|
||||
@InvocableVariable(label='Metadata Full Name')
|
||||
public String metadataFullName;
|
||||
}
|
||||
|
||||
public class Configuration {
|
||||
public Id autoResponseRuleId;
|
||||
public String autoResponseRuleName;
|
||||
public Integer sortOrder;
|
||||
public String caseOrigin;
|
||||
public String templateFullName;
|
||||
}
|
||||
|
||||
@InvocableMethod(
|
||||
label='Create Case Auto-Response Rule Entry'
|
||||
description='Queues a file-based Metadata API deployment that appends an entry to a Case auto-response rule.'
|
||||
category='Queue Configuration'
|
||||
callout=true
|
||||
)
|
||||
public static List<Result> createRuleEntry(List<Request> requests) {
|
||||
MetadataXml.require(
|
||||
requests != null && requests.size() == 1,
|
||||
'Create Case Auto-Response Rule Entry accepts exactly one request per invocation.'
|
||||
);
|
||||
Request input = requests[0];
|
||||
input.autoResponseRuleName = input.autoResponseRuleName == null
|
||||
? null
|
||||
: input.autoResponseRuleName.trim();
|
||||
input.caseOrigin = input.caseOrigin == null
|
||||
? null
|
||||
: input.caseOrigin.trim();
|
||||
MetadataXml.require(
|
||||
input.autoResponseRuleId != null ||
|
||||
!String.isBlank(input.autoResponseRuleName),
|
||||
'Auto Response Rule Id or Auto Response Rule Name is required.'
|
||||
);
|
||||
MetadataXml.require(
|
||||
!String.isBlank(input.caseOrigin),
|
||||
'Case Origin is required.'
|
||||
);
|
||||
MetadataXml.require(
|
||||
input.emailTemplateId != null,
|
||||
'Email Template Id is required.'
|
||||
);
|
||||
MetadataXml.require(
|
||||
input.sortOrder == null || input.sortOrder > 0,
|
||||
'Sort Order must be greater than zero.'
|
||||
);
|
||||
|
||||
List<EmailTemplate> templates = [
|
||||
SELECT Id, DeveloperName, FolderId
|
||||
FROM EmailTemplate
|
||||
WHERE Id = :input.emailTemplateId
|
||||
WITH USER_MODE
|
||||
LIMIT 1
|
||||
];
|
||||
MetadataXml.require(
|
||||
!templates.isEmpty(),
|
||||
'Email Template Id was not found.'
|
||||
);
|
||||
List<Folder> folders = [
|
||||
SELECT Id, DeveloperName
|
||||
FROM Folder
|
||||
WHERE Id = :templates[0].FolderId AND Type = 'Email'
|
||||
WITH USER_MODE
|
||||
LIMIT 1
|
||||
];
|
||||
MetadataXml.require(
|
||||
!folders.isEmpty(),
|
||||
'The email template must be stored in a shared Classic Email Template folder.'
|
||||
);
|
||||
|
||||
Configuration configuration = new Configuration();
|
||||
configuration.autoResponseRuleId = input.autoResponseRuleId;
|
||||
configuration.autoResponseRuleName = String.isBlank(
|
||||
input.autoResponseRuleName
|
||||
)
|
||||
? MetadataApiClient.toolingAutoResponseRuleName(input.autoResponseRuleId)
|
||||
: input.autoResponseRuleName;
|
||||
configuration.sortOrder = input.sortOrder;
|
||||
configuration.caseOrigin = input.caseOrigin;
|
||||
configuration.templateFullName =
|
||||
folders[0].DeveloperName +
|
||||
'/' +
|
||||
templates[0].DeveloperName;
|
||||
|
||||
Id jobId = System.enqueueJob(new StartRetrieveJob(configuration));
|
||||
Result result = new Result();
|
||||
result.asyncJobId = jobId;
|
||||
result.status = 'Queued';
|
||||
result.metadataFullName = 'Case.' + configuration.autoResponseRuleName;
|
||||
return new List<Result>{ result };
|
||||
}
|
||||
|
||||
private class StartRetrieveJob implements Queueable, Database.AllowsCallouts {
|
||||
private Configuration configuration;
|
||||
|
||||
private StartRetrieveJob(Configuration configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
public void execute(QueueableContext context) {
|
||||
String retrieveId = MetadataApiClient.startRetrieve(
|
||||
'Case.' + configuration.autoResponseRuleName,
|
||||
'AutoResponseRule'
|
||||
);
|
||||
System.enqueueJob(new RetrievePollJob(configuration, retrieveId, 0), 1);
|
||||
}
|
||||
}
|
||||
|
||||
private class RetrievePollJob implements Queueable, Database.AllowsCallouts {
|
||||
private Configuration configuration;
|
||||
private String retrieveId;
|
||||
private Integer pollCount;
|
||||
|
||||
private RetrievePollJob(
|
||||
Configuration configuration,
|
||||
String retrieveId,
|
||||
Integer pollCount
|
||||
) {
|
||||
this.configuration = configuration;
|
||||
this.retrieveId = retrieveId;
|
||||
this.pollCount = pollCount;
|
||||
}
|
||||
|
||||
public void execute(QueueableContext context) {
|
||||
MetadataApiClient.RetrieveResult retrieveResult = MetadataApiClient.checkRetrieve(
|
||||
retrieveId
|
||||
);
|
||||
if (!retrieveResult.done) {
|
||||
if (pollCount >= MAX_POLLS) {
|
||||
throw new MetadataApiClient.MetadataApiException(
|
||||
'Timed out waiting for the auto-response rule metadata retrieve.'
|
||||
);
|
||||
}
|
||||
System.enqueueJob(
|
||||
new RetrievePollJob(configuration, retrieveId, pollCount + 1),
|
||||
1
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
retrieveResult.status != 'Succeeded' ||
|
||||
retrieveResult.zipFile == null
|
||||
) {
|
||||
throw new MetadataApiClient.MetadataApiException(
|
||||
'Auto-response rule retrieve failed: ' +
|
||||
(String.isBlank(retrieveResult.errorMessage)
|
||||
? retrieveResult.status
|
||||
: retrieveResult.errorMessage)
|
||||
);
|
||||
}
|
||||
|
||||
Blob deploymentZip = buildDeploymentZip(
|
||||
retrieveResult.zipFile,
|
||||
configuration
|
||||
);
|
||||
String deployId = MetadataApiClient.startDeploy(deploymentZip);
|
||||
System.enqueueJob(new DeployPollJob(deployId, 0), 1);
|
||||
}
|
||||
}
|
||||
|
||||
private class DeployPollJob implements Queueable, Database.AllowsCallouts {
|
||||
private String deployId;
|
||||
private Integer pollCount;
|
||||
|
||||
private DeployPollJob(String deployId, Integer pollCount) {
|
||||
this.deployId = deployId;
|
||||
this.pollCount = pollCount;
|
||||
}
|
||||
|
||||
public void execute(QueueableContext context) {
|
||||
MetadataApiClient.DeployResult deployResult = MetadataApiClient.checkDeploy(
|
||||
deployId
|
||||
);
|
||||
if (!deployResult.done) {
|
||||
if (pollCount >= MAX_POLLS) {
|
||||
throw new MetadataApiClient.MetadataApiException(
|
||||
'Timed out waiting for the auto-response rule metadata deployment.'
|
||||
);
|
||||
}
|
||||
System.enqueueJob(new DeployPollJob(deployId, pollCount + 1), 1);
|
||||
return;
|
||||
}
|
||||
if (!deployResult.success) {
|
||||
throw new MetadataApiClient.MetadataApiException(
|
||||
'Auto-response rule deployment failed: ' +
|
||||
(String.isBlank(deployResult.errorMessage)
|
||||
? deployResult.status
|
||||
: deployResult.errorMessage)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestVisible
|
||||
private static Blob buildDeploymentZip(
|
||||
Blob retrievedZip,
|
||||
Configuration configuration
|
||||
) {
|
||||
Compression.ZipReader reader = new Compression.ZipReader(retrievedZip);
|
||||
Compression.ZipEntry ruleEntry;
|
||||
for (String entryName : reader.getEntriesMap().keySet()) {
|
||||
if (entryName.endsWith('Case.autoResponseRules')) {
|
||||
ruleEntry = reader.getEntriesMap().get(entryName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
MetadataXml.require(
|
||||
ruleEntry != null,
|
||||
'The retrieved Case auto-response rule file is missing.'
|
||||
);
|
||||
String sourceXml = reader.extractEntry(ruleEntry).toString();
|
||||
String updatedXml = appendRuleEntry(sourceXml, configuration);
|
||||
|
||||
String packageXml =
|
||||
'<?xml version="1.0" encoding="UTF-8"?>' +
|
||||
'<Package xmlns="http://soap.sforce.com/2006/04/metadata">' +
|
||||
'<types><members>Case.' +
|
||||
MetadataXml.escape(configuration.autoResponseRuleName) +
|
||||
'</members><name>AutoResponseRule</name></types>' +
|
||||
'<version>' +
|
||||
MetadataApiClient.API_VERSION +
|
||||
'</version></Package>';
|
||||
Compression.ZipWriter writer = new Compression.ZipWriter();
|
||||
writer.addEntry('package.xml', Blob.valueOf(packageXml));
|
||||
writer.addEntry(
|
||||
'autoResponseRules/Case.autoResponseRules',
|
||||
Blob.valueOf(updatedXml)
|
||||
);
|
||||
return writer.getArchive();
|
||||
}
|
||||
|
||||
@TestVisible
|
||||
private static String appendRuleEntry(
|
||||
String sourceXml,
|
||||
Configuration configuration
|
||||
) {
|
||||
Dom.Document document = new Dom.Document();
|
||||
document.load(sourceXml);
|
||||
Dom.XmlNode root = document.getRootElement();
|
||||
Dom.XmlNode targetRule;
|
||||
for (Dom.XmlNode rule : MetadataXml.children(root, 'autoResponseRule')) {
|
||||
if (
|
||||
configuration.autoResponseRuleName == MetadataXml.text(rule, 'fullName')
|
||||
) {
|
||||
targetRule = rule;
|
||||
break;
|
||||
}
|
||||
}
|
||||
MetadataXml.require(
|
||||
targetRule != null,
|
||||
'Auto-response rule "' +
|
||||
configuration.autoResponseRuleName +
|
||||
'" was not found.'
|
||||
);
|
||||
|
||||
List<Dom.XmlNode> existingEntries = MetadataXml.children(
|
||||
targetRule,
|
||||
'ruleEntry'
|
||||
);
|
||||
for (Dom.XmlNode existingEntry : existingEntries) {
|
||||
if (
|
||||
configuration.caseOrigin ==
|
||||
criterionValue(existingEntry, 'Case.Origin') &&
|
||||
configuration.templateFullName ==
|
||||
MetadataXml.text(existingEntry, 'template')
|
||||
) {
|
||||
return sourceXml;
|
||||
}
|
||||
}
|
||||
Integer insertionPosition = configuration.sortOrder == null
|
||||
? existingEntries.size() + 1
|
||||
: Math.min(configuration.sortOrder, existingEntries.size() + 1);
|
||||
|
||||
String newEntry =
|
||||
'<ruleEntry><booleanFilter>1 AND 2 AND 3 AND 4</booleanFilter>' +
|
||||
criterion('Case.Origin', 'equals', configuration.caseOrigin) +
|
||||
criterion('Case.ContactEmail', 'notEqual', '') +
|
||||
criterion('Case.Status', 'equals', 'Active') +
|
||||
criterion('Case.RecordTypeId', 'equals', 'Customer Service') +
|
||||
'<senderEmail>' +
|
||||
MetadataXml.escape(SENDER_EMAIL) +
|
||||
'</senderEmail><senderName>' +
|
||||
MetadataXml.escape(SENDER_NAME) +
|
||||
'</senderName><template>' +
|
||||
MetadataXml.escape(configuration.templateFullName) +
|
||||
'</template></ruleEntry>';
|
||||
|
||||
String targetRuleXml = '<autoResponseRule>';
|
||||
Integer currentEntryPosition = 0;
|
||||
Boolean inserted = false;
|
||||
for (Dom.XmlNode child : targetRule.getChildElements()) {
|
||||
if (child.getName() == 'ruleEntry') {
|
||||
currentEntryPosition++;
|
||||
if (!inserted && currentEntryPosition == insertionPosition) {
|
||||
targetRuleXml += newEntry;
|
||||
inserted = true;
|
||||
}
|
||||
}
|
||||
targetRuleXml += MetadataXml.serialize(child);
|
||||
}
|
||||
if (!inserted) {
|
||||
targetRuleXml += newEntry;
|
||||
}
|
||||
targetRuleXml += '</autoResponseRule>';
|
||||
|
||||
String output =
|
||||
'<?xml version="1.0" encoding="UTF-8"?>' +
|
||||
'<AutoResponseRules xmlns="http://soap.sforce.com/2006/04/metadata">';
|
||||
for (Dom.XmlNode rule : root.getChildElements()) {
|
||||
output += rule == targetRule
|
||||
? targetRuleXml
|
||||
: MetadataXml.serialize(rule);
|
||||
}
|
||||
return output + '</AutoResponseRules>';
|
||||
}
|
||||
|
||||
private static String criterion(
|
||||
String field,
|
||||
String operation,
|
||||
String value
|
||||
) {
|
||||
return '<criteriaItems><field>' +
|
||||
MetadataXml.escape(field) +
|
||||
'</field><operation>' +
|
||||
MetadataXml.escape(operation) +
|
||||
'</operation><value>' +
|
||||
MetadataXml.escape(value) +
|
||||
'</value></criteriaItems>';
|
||||
}
|
||||
|
||||
private static String criterionValue(Dom.XmlNode ruleEntry, String field) {
|
||||
for (
|
||||
Dom.XmlNode criterionNode : MetadataXml.children(
|
||||
ruleEntry,
|
||||
'criteriaItems'
|
||||
)
|
||||
) {
|
||||
if (field == MetadataXml.text(criterionNode, 'field')) {
|
||||
return MetadataXml.text(criterionNode, 'value');
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user