Tool to Delete Components from Production

Tool to Delete Components from Production

It is known that we cannot delete components like Apex Classes, Triggers, etc. manually from production. The common approach to delete these components are through Force.com IDE or Force.com Migration tool. In this article we design a tool to delete these components. We build a Visualforce page which creates two XML files named package.xml and destructiveChanges.xml, later compress them and deploy.

To start with include JSZip as a static resource. Now lets create a Visualforce page as below,

Screen

How this works ?

The field “Component Type” contain elements like ApexClass, ApexPage, ApexTrigger, etc and in the field “Component” we have to enter the Object Name of the component to be deleted, click on the button “Add” and the component is added to the wrapper list. When confirmed click on the button “Delete”, a javascript function is triggered to add the wrapper list elements to a destructive changes XML string, package XML string is also initialised, both these strings are converted into files and zipped using JSZip. After this an apex Deploy method is invoked from the Visualforce page javascript, we are using MetadataService Class to make the deployment call.

Please use the below code for the Visualforce page and note that in the controller in the method createService we have to update service endpoint with the URL of the organisation. Also add the organisation url in the Remote site settings.

Controller:

[sourcecode language=”java” collapse=”true” title=”DeleteComponentsClass.apxc”]
public class DeleteComponentsClass {

// Initializing elements
public String selectedType{get;set;}
public String componentName{get;set;}
public List<DeleteComponentsWrpClass> componentItems{get;set;}

// Getting picklist elements
public static List<SelectOption> getDataTypes(){

List<String> itemTypes = new List<String>{‘ActionLinkGroupTemplate’, ‘AnalyticSnapshot’, ‘ApexClass’, ‘ApexComponent’, ‘ApexPage’,
‘ApexTrigger’, ‘AppMenu’, ‘ApprovalProcess’, ‘AssignmentRules’, ‘AuraDefinitionBundle’, ‘AuthProvider’, ‘AutoResponseRules’, ‘BusinessProcess’,
‘CallCenter’, ‘ChannelLayout’, ‘Community’, ‘CompactLayout’, ‘CorsWhitelistOrigin’, ‘CustomApplication’, ‘CustomField’, ‘CustomMetadata’,
‘CustomObject’, ‘CustomObjectTranslation’, ‘CustomPageWebLink’, ‘CustomPermission’, ‘CustomSite’, ‘CustomTab’, ‘Dashboard’, ‘Document’,
‘EmailTemplate’, ‘EscalationRules’, ‘ExternalDataSource’, ‘FieldSet’, ‘FlexiPage’, ‘FlowDefinition’, ‘Group’, ‘HomePageComponent’, ‘HomePageLayout’,
‘Layout’, ‘Letterhead’, ‘ListView’, ‘MatchingRule’, ‘NamedCredential’, ‘PermissionSet’, ‘PlatformCachePartition’, ‘Portal’, ‘PostTemplate’,
‘Profile’, ‘Queue’, ‘QuickAction’, ‘RecordType’, ‘RemoteSiteSetting’, ‘Report’, ‘ReportType’, ‘Role’, ‘Scontrol’, ‘Settings’, ‘SharingCriteriaRule’,
‘SharingOwnerRule’, ‘SharingReason’, ‘SharingRules’, ‘SharingSet’, ‘SiteDotCom’, ‘StaticResource’, ‘UserPermissions’, ‘ValidationRule’,
‘WebLink’, ‘Workflow’, ‘WorkflowAlert’, ‘WorkflowFieldUpdate’, ‘WorkflowOutboundMessage’, ‘WorkflowRule’, ‘WorkflowTask’};

List<SelectOption> customTypes = new List<SelectOption>();
customTypes.add(new SelectOption(”,’–None–‘));
for(String item : itemTypes){
customTypes.add(new SelectOption(item,item));
}

return customTypes;
}

// Adding Items to the List
public void add(){

List<String> splitItems = componentName.split(‘;’);

for(String Item : splitItems){
DeleteComponentsWrpClass compItem = new DeleteComponentsWrpClass();
compItem.compName = Item;
compItem.compType = selectedType;
if(componentItems == null){
componentItems = new List<DeleteComponentsWrpClass>();
}
componentItems.add(compItem);
}
componentName = null;

}

// Deploying zip file
@RemoteAction
public static String getDeployContent(String zipData){

MetadataService.MetadataPort service = createService();

MetadataService.DeployOptions deployOptions = new MetadataService.DeployOptions();
deployOptions.allowMissingFiles = false;
deployOptions.autoUpdatePackage = false;
deployOptions.checkOnly = false;
deployOptions.ignoreWarnings = false;
deployOptions.performRetrieve = false;
deployOptions.purgeOnDelete = false;
deployOptions.rollbackOnError = true;
deployOptions.testLevel = ‘NoTestRun’;
deployOptions.singlePackage = true;

MetadataService.AsyncResult AsyncResult = service.deploy(zipData, DeployOptions);

return (‘Success’);
}

// Cancel button action
public PageReference cancel(){

PageReference pageRef = new PageReference(‘/home/home.jsp’);
pageRef.setRedirect(true);
return pageRef;

}

// Delete button action
public PageReference deleteItems(){

Long startTime = System.now().getTime();
Integer delay = 5000;

do {
// Do nothing
}while (System.now().getTime() – startTime < delay);

PageReference pageRef = new PageReference(‘/changemgmt/monitorDeployment.apexp’);
pageRef.setRedirect(true);
return pageRef;

}

// Creating service
private static MetadataService.MetadataPort createService() {

MetadataService.MetadataPort service = new MetadataService.MetadataPort();
service.SessionHeader = new MetadataService.SessionHeader_element();
service.endpoint_x = ‘https://coyg-dev-ed.my.salesforce.com/services/Soap/m/35.0’;
service.timeout_x = 120000;
service.SessionHeader.sessionId = UserInfo.getSessionId();
return service;

}

// Wrapper Class
public class DeleteComponentsWrpClass {

public String compType{get;set;}
public String compName{get;set;}

public DeleteComponentsWrpClass(){

}
}

}
[/sourcecode]

Visualforce page:

[sourcecode language=”html” collapse=”true” title=”DeleteComponents.vfp”]
<apex:page id=”page” controller=”DeleteComponentsClass”>

<apex:includeScript value=”{!$Resource.jsZip}”/>

<script type=”text/javascript”>
var compMap = {};

function contentToMap(key,value) {
compMap[key] = compMap[key] || [];
compMap[key].push(value);
}

function getRemoteContent(){

var keys = Object.keys(compMap);

if(keys.length > 0){
var packageXml = ‘<?xml version=”1.0″ encoding=”UTF-8″?>’ +
‘<Package xmlns=”http://soap.sforce.com/2006/04/metadata”>’ +
‘<version>35.0</version>’ +
‘</Package>’ ;

var destructiveChanges = ‘<?xml version=”1.0″ encoding=”UTF-8″?>’ +
‘<Package xmlns=”http://soap.sforce.com/2006/04/metadata”>’ ;

for(var i=0;i<keys.length;i++){

destructiveChanges = destructiveChanges + ‘<types>’;

if(keys[i] == ‘Dashboard’ || keys[i] == ‘Report’ || keys[i] == ‘EmailTemplate’ ||keys[i] == ‘Document’){

for(var j=0;j<compMap[keys[i]].length;j++) {
var items = compMap[keys[i]][j].split(‘/’);
if((destructiveChanges.indexOf(‘<members>’ + items[0] + ‘</members>’) > -1) && (items[0] != ‘unfiled$public’)) {
destructiveChanges = destructiveChanges + ‘<members>’+items[0]+'</members>’;
}

destructiveChanges = destructiveChanges + ‘<members>’+compMap[keys[i]][j]+'</members>’;
}

} else {

for(var j=0;j<compMap[keys[i]].length;j++) {
destructiveChanges = destructiveChanges + ‘<members>’+compMap[keys[i]][j]+'</members>’;
}

}

destructiveChanges = destructiveChanges + ‘<name>’+keys[i]+'</name>’;
destructiveChanges = destructiveChanges + ‘</types>’;

}

destructiveChanges = destructiveChanges + ‘</Package>’;

zipFile = new JSZip();
zipFile.file(‘package.xml’,packageXml);
zipFile.file(‘destructiveChanges.xml’,destructiveChanges);
zipFile.generateAsync({type:”base64″}).then(function(data) {
Visualforce.remoting.Manager.invokeAction(
‘{!$RemoteAction.DeleteComponentsClass.getDeployContent}’,
data,

function(result, event){
if (event.status) {

} else {
alert(“Error”);
}
});
});
} else {
alert(‘Add Components’);
}
}

</script>

<apex:form id=”frm” >

<apex:pageBlock id=”pblck1″ title=”Delete Components”>

<apex:pageBlockSection id=”pbSectn1″ columns=”4″ showHeader=”false”>
<apex:outputLabel value=”Component Type” for=”picklistID” />
<apex:selectList id=”picklistID” value=”{!SelectedType}” title=”Component Type” multiselect=”false” size=”1″>
<apex:selectOptions value=”{!DataTypes}” />
</apex:selectList>
</apex:pageBlockSection>

<apex:pageBlockSection id=”pbSectn2″ columns=”4″ showHeader=”false”>
<apex:outputLabel value=”Component” for=”nameID” />
<apex:inputText id=”componentName” value=”{!ComponentName}” />
</apex:pageBlockSection>

<apex:pageBlockButtons location=”bottom”>
<apex:commandButton action=”{!add}” value=” Add “> </apex:commandButton>
<apex:commandButton onclick=”getRemoteContent()” action=”{!deleteItems}” value=” Delete “> </apex:commandButton>
<apex:commandButton action=”{!cancel}” value=” Cancel “></apex:commandButton>
</apex:pageBlockButtons>

</apex:pageBlock>

<apex:pageBlock id=”pblck2″ >
<table border=’1′>
<tr style=”background:#3498db;color:white”>
<th>Component Name</th>
<th>Component Type</th>
</tr>
<apex:repeat id=”itemId” value=”{!componentItems}” var=”item”>
<script>
contentToMap(‘{!item.compType}’,'{!item.compName}’);
</script>
<tr>
<td>{!item.compName}</td>
<td>{!item.compType}</td>
</tr>
</apex:repeat></table>
</apex:pageBlock>

<apex:pageBlock >

<apex:pageBlockSection columns=”1″>
<span style=”font-style:Italic”>
Note: Component is the Component Object Name. For Folder components like Dashboard, Document, EmailTemplate and Report, the Folder Name is added as prefix to the Component. For example my Dashboard folder is demoFolder and Dashboard is demoComponent, then the Component name is demoFolder/demoComponent.
You can add more than one Component Name separated by semicolon(‘;’).
</span>
</apex:pageBlockSection>

</apex:pageBlock>

</apex:form>

</apex:page>
[/sourcecode]

Let’s test this:

I have an Apex class “HelloWorld” and Apex Trigger “HelloTrigger”. (I am using the developer org not the production).

Screen

Screen

Add these components in the tool.

Screen

Click on the delete button we are redirected to the Deployment status page displaying record showing deployment succeeded.

Screen

We can also check in the Apex classes and Apex Triggers setup that the components are deleted.

Screen

Screen

This tool can also be used to delete other components like Apex pages, Dashboards, Documents, Reports, Workflows, etc,. The components Dashboard, Document, EmailTemplate and Report are folder components, so we have to mention they folder name in order to delete. For example, my dashboard is demoDashboard and its in folder demoFolder. Then the component name will be demoFolder/demoDashboard. We can also add multiple component names of same type separated by semicolon (;).

Related links:

View complete code on GitHub

Install Unmanaged package

Happy Coding!

Leave a Comment

Your email address will not be published. Required fields are marked *

Recent Posts

Salesforce CRM Insights
From Data to Action: Tableau Agent’s Role in Shaping Salesforce CRM Insights
future-of-ai-powered-business-solutions
Dreamforce 2024: Unveiling the Future of AI-Powered Business Solutions
gitex 2024 worlds largest tech show
GITEX 2024 |  World's Largest Tech Show - ABSYZ
inclusive practices in women leadership equitable future
Inclusive Practices in Women’s Leadership: Equitable Future
salesforce dreamfest 2024 the highlights of dreamforce 2024
Dreamfest 2024: The highlights of Dreamforce 2024
Document

How can i help you? close button

powered     by   ABSYZ
Scroll to Top