Using Salesforce History Data

There is build-in functionality in Salesforce for tracking history on specific fields, which can save you development efforts.

Per each object you can enable the tracking history feature, and select the fields you would like track. In each page layout you may add the history related list, that will show all changes for this record. All this feature is build-in and can be done with setup only.

I had once issue, with main object that have several telated childs objects. When user view the history for the object he doesn't want to dive into each child record, but  rather to see all the changes (main object + childs) in one place.
Therefore I develop kind of history page which show all changes together.
At first this page was design for the specific custom object. Lately, with some modification it was amended to work completely generic. Means, it can work on any custom object.
Its show only changes for custom objects, as SF objects sometimes have different names for their history table.

The relevant components are visualforce page and controller, usage is by the URL:
/apex/HistoryPage?ids=<recordID>

The URL should get as parameter: ids. The Id of the record you want to view its history.

First should select the objects (master/child) to view their changes, press 'Get Changes' and view the result.

I created for example custom object: "Parent Object", and 2 child custom object.








Controller Code:


public class vf_HistoryPage {  

 public Id objID {get; set;}        //main object id (should get as URL parameter)   
 public String objName {get; set;}     //main object name (should get as URL parameter)   

 public List<HistoryChange> historyChangeLst {get; set;}     //list with all changes   

 public list<String> childObjLst {get; set;}                       //list of all child objects   
 public map<String, Boolean> obj_showBol_map {get; set;}               //for each object if need to show its changes   
 public map<String, String> objLabel_API_map {get; set;}               //map from Label to API   

 private map<String, String> objAPI_objLabel=new map<String, String>();     //for each object its label name   
 private map<String, String> objAPI_fieldName_map=new map<String, String>();   //for each object the field name of his parent   

 private map<String, Set<String>> tableFieldAdd_Map=new Map<String, Set<String>>();   //used to prevent from same change return twice   

 public vf_HistoryPage() {  

  //record ID should be as parameters in the URL   
  objID=ApexPages.currentPage().getParameters().get('ids');   
  objName=objID.getsobjecttype().getDescribe().getName();   

  //DescribeResult of the main object   
  Schema.DescribeSObjectResult objectResult = Schema.getGlobalDescribe().get(objName).getDescribe();   

  //intialize maps   
  objLabel_API_map=new map<String, String>();   
  childObjLst=new list<String>{objectResult.getLabel()};   
  obj_showBol_map=new map<String, Boolean>();   

  //adding the main object   
  obj_showBol_map.put(objName, true);   
  objAPI_objLabel.put(objName, objectResult.getLabel());   
  objLabel_API_map.put(objectResult.getLabel(), objName);   
  objAPI_fieldName_map.put(objName, 'id');   

  for(Schema.ChildRelationship child : objectResult.getChildRelationships()) {  

   Schema.DescribeSObjectResult objRes=child.getChildSObject().getDescribe();   

   if(objRes.isCustom()) {  
    childObjLst.add(objRes.getLabel());   

    obj_showBol_map.put(objRes.getName(), true);   
    objAPI_objLabel.put(objRes.getName(), objRes.getLabel());   
    objLabel_API_map.put(objRes.getLabel(), objRes.getName());   
    objAPI_fieldName_map.put(objRes.getName(), child.getField().getDescribe().getName());   
   }   
  }   
  historyChangeLst=new List<HistoryChange>();   
 }   

 //Get all the changes   
 public PageReference getChanges() {  

  //clear list of changes   
  historyChangeLst.clear();   

  for(String childObjName : obj_showBol_map.KeySet()) {  
   if(obj_showBol_map.get(childObjName)) {  

    //reset this list for the specific object   
    tableFieldAdd_Map.put(childObjName, new Set<String>());   
 
    //collect all the childs IDs   
    Set<ID> childObjIDSet=new Set<ID>();   

    for(sObject childObj : Database.query('SELECT id FROM ' + childObjName + ' WHERE ' + objAPI_fieldName_map.get(childObjName) + ' =\'' + objID +'\'')) {   
     childObjIDSet.add(childObj.id);   
    }  

    //add all the object changes to list of all changes      
    if(!childObjIDSet.isEmpty()) {  
     addAllChanges(childObjIDSet,    
      objAPI_objLabel.get(childObjName),   
      childObjName,   
      childObjName.endsWith('__c') ? childObjName.replace('__c', '__History') : childObjName + 'History');   
    }  
   }   
  }   
  historyChangeLst.sort();   

  return ApexPages.currentPage();   
 }   

 public void addAllChanges(   
  Set<Id> idLst,   
  String objectName,   
  String tableName,   
  String tableHistoryName) {  

  String idConcatenateLst='';   
  String dynamicSQLHistory;   

  for(String objID : idLst) {  
   idConcatenateLst+= idConcatenateLst!='' ? ', \''+objID+'\'' : '\''+objID+'\'';   
  }  

  dynamicSQLHistory='SELECT id, oldvalue, parentid, newvalue, field, createdDate, CreatedBy.LastName, CreatedBy.FirstName';    
  dynamicSQLHistory+=' FROM ' + tableHistoryName + ' WHERE parentId IN ( ' + idConcatenateLst + ')';   
  dynamicSQLHistory+= ' order by createdDate';   

  try {   
   List<sObject> objLst=Database.query(dynamicSQLHistory);   
   sObject userObj;   
   String firstName, lastName;   

   for(sObject histObj : objLst) {  

    userObj=histObj.getSObject('CreatedBy');   
    firstName=(String)(userObj.get('FirstName'));   
    lastName=(String)(userObj.get('LastName'));   

    addChange(tableName,   
     (String)histObj.get('field'),   
     histObj.get('oldvalue'),   
     histObj.get('newValue'),   
     (DateTime)histObj.get('createdDate'),   
     (firstName==null ? '' : (firstName + ' '))+ (lastName==null ? '' : lastName),   
     objectName,   
     (String)histObj.get('parentid'));   
   }   
  }   
  catch(System.QueryException sqe) {  
   System.debug('##err: ' + sqe);   //possible error: object not support history   
   System.debug('##DYNAMIC SQL: ' + dynamicSQLHistory);   
  }   
  catch(Exception e) {  
   ApexPages.addMessage(new ApexPages.Message(ApexPages.severity.ERROR, ' Error with ' + tableName + '. ' + e));   
  }   
 }   

 //add change   
 private void addChange(   
  String table_Str,   
  String field_Str,   
  Object oldValue_Obj,   
  Object newValue_Obj,   
  DateTime createDate,   
  String user,   
  String objectName,   
  String objID) {  

  String fieldLabel_Str, name_Str='';   
  String result_Str='';   
  Boolean creation=false;   

  if(!tableFieldAdd_Map.get(table_Str).contains(field_Str+createDate+objID)) {  

   tableFieldAdd_Map.get(table_Str).add(field_Str+createDate+objID);   
   if(field_Str=='created')   {   
    result_Str='Record created';   
    creation=true;   
   }   
   else   {   
    if(oldValue_Obj != null && newValue_Obj != null) {  
     result_Str+=' changed from ' + (oldValue_Obj==null ? '' : oldValue_Obj) + ' to ' + (newValue_Obj==null ? '' : newValue_Obj);   
    }  
    else if(oldValue_Obj == null && newValue_Obj != null) {  
     result_Str+=' added ' + (newValue_Obj==null ? '' : newValue_Obj);   
    }  
    else if(oldValue_Obj != null && newValue_Obj == null) {  
     result_Str+=' deleted ' + (oldValue_Obj==null ? '' : oldValue_Obj);   
    }  
    else {  
     result_Str+=' Field has been changed';   //for long text field SF doesn't store the values   
    }  
   }   

   if(result_Str!=null && result_Str.trim() !='') {  
    historyChangeLst.add(new HistoryChange(result_Str, user, createDate, objectName, objID, table_Str, field_Str=='RecordType' ? 'RecordTypeID' : field_Str, creation));   
   }  
  }   
 }   

 //Object HistoryChange - represent history record   
 public class HistoryChange implements Comparable {  

  public String changeStr {get; set;}   
  public DateTime changeDate {get; set;}   
  public String userStr {get; set;}   
  public String objectName {get; set;}   
  public String objectID {get; set;}   
  public String objectAPI {get; set;}   
  public String fieldAPI {get; set;}   
  public Boolean creationChange {get; set;}   

  public HistoryChange(String pChange, String pUser, DateTime pChangeDate, String pObjectName, String pObjectID,   
   String pObjectAPI, String pFieldAPI, Boolean pCreationChange) {  
   changeStr=pChange;   
   changeDate=pChangeDate;   
   userStr=pUser;   
   objectName=pObjectName;   
   objectID=pObjectID;   
   objectAPI=pObjectAPI;   
   fieldAPI=pFieldAPI;   
   creationChange=pCreationChange;   
  }   

  public Integer compareTo(Object historyRec) {   
   HistoryChange compareToHC = (HistoryChange)historyRec;   
   if(changeDate!=compareToHC.changeDate) {  
    return changeDate >= compareToHC.changeDate ? 0 : 1;   
   }  
   else {  
    return creationChange ? 1 : 0;   
   }  
  }   
 }   
}  


Visualforce Page:

<apex:page controller="vf_HistoryPage">   
 <apex:pageMessages />   
 <apex:form id="frm">   
  <apex:pageBlock mode="edit" id="mainblock" title="Object Selection">   
   <apex:pageBlockButtons id="buttons" location="bottom">   
    <apex:commandButton value="Get Changes" action="{!getChanges}"/>   
   </apex:pageBlockButtons>   
   <apex:pageBlockSection id="filters" columns="1" collapsible="true">   
    <apex:repeat value="{!childObjLst}" var="obj">   
     <apex:pageBlockSectionItem >   
      <apex:outputText value="{!obj}"/>   
      <apex:inputCheckbox value="{!obj_showBol_map[objLabel_API_map[obj]]}"/>   
     </apex:pageBlockSectionItem>   
    </apex:repeat>   
   </apex:pageBlockSection>   
  </apex:pageBlock>   
  
  <apex:pageBlock mode="edit" id="results">   
   <apex:pageBlockTable value="{!historyChangeLst}" var="change">   
    <apex:column headerValue="Date">   
     <apex:outputText value="{0,date,dd'/'MM'/'yyyy HH:mm:ss}">   
      <apex:param value="{!change.changeDate}" id="datefromid"/>   
     </apex:outputText>   
    </apex:column>   
    <apex:column headerValue="User">   
     <apex:outputText value="{!change.userStr}"/>   
    </apex:column>   
    <apex:column headerValue="Object">   
     <apex:outputLink target="_blank" value="/{!change.objectID}" id="eds"> {!change.objectName}</apex:outputLink>   
    </apex:column>   
    <apex:column headerValue="Change">   
     <apex:outputPanel rendered="{!NOT change.creationChange}">   
      {!$ObjectType[change.objectAPI].fields[change.fieldAPI].Label}   
     </apex:outputPanel>   
     {!change.changeStr}   
    </apex:column>   
   </apex:pageBlockTable>   
  </apex:pageBlock>   
 </apex:form>   
</apex:page>  

No comments:

Post a Comment

Retire of Permission on Profiles

If you are working as a Salesforce admin/developer you've probably heard somewhere that Salesforce is planning to make a significant cha...