Deploying Child Pages with Post Install Apex Script

Figured this should have its own thread. Continued from here: https://community.skuid.com/t/appexchange-package-upload-to-include-custom-themes

Child pages do not update at all via the post install script and are untouched. Manually I can unpack the pages from the static resource.

I am unable to figure out how to get my post install script to auto deploy my child pages. I imagine this has to do with every org having different Ids for pages. The script would need to identify master pages, deploy those first, identify child pages, and link those back to the already deployed master pages. 

Craig,

Which Apex method are you using to unpack those Child Pages in your Post Install script? We may have bug, but we are at least attempting to account for this scenario in 
RefreshPagesFromStaticResource and RefreshSkuidPages. In your Page Pack, do your pages have a value for skuid__MasterPage__r.skuid__UniqueId__c on those Child Pages?

Here’s my full post install script. Note, it should be the same as the sample one here. Where do you want me to check for the skuid__MasterPage__r.skuid__UniqueId__c value? In my development environment or in a org that I’m trying to install the package in? Currently, the child pages do not deploy automatically. If I manually click “unpack from page pack” they deploy correctly with the correct master page assigned.

public without sharing class InstallScript_CheetahBMS implements InstallHandler {
public static final String NAMESPACE_PREFIX = ‘CheetahBMS’;
public static boolean IsRunning {
public get {
if (IsRunning==null) IsRunning = false;
return IsRunning;
}
public set;
}

public void onInstall(InstallContext ctx) {
    IsRunning=true;
    RefreshPagesInModule(NAMESPACE_PREFIX);
    IsRunning=false;
}

public static List<skuid__Page__c> RefreshPagesInModule(String module) {
    
    // See if a StaticResource containing new pages for this module yet exists      
    StaticResource sr = [
        select Body
        from StaticResource 
        where Name = :(module + 'Pages')
        and ((NamespacePrefix = NULL) OR (NamespacePrefix = :module))
        limit 1
    ];
    
    // The new Pages for our module that we will be inserting       
    List<skuid__Page__c> newPages
         = (List<skuid__Page__c>) JSON.deserialize(sr.Body.toString(),List<skuid__Page__c>.class);
    List<Schema.SObjectField> layoutFields = new List<Schema.SObjectField>{
        skuid__Page__c.skuid__Layout__c,
        skuid__Page__c.skuid__Layout2__c,
        skuid__Page__c.skuid__Layout3__c,
        skuid__Page__c.skuid__Layout4__c,
        skuid__Page__c.skuid__Layout5__c
    };
    
    for (skuid__Page__c p : newPages) {
        // Get rid of the Ids so that upsert will proceed           
        p.Id = null;
        // Ensure that unused Layout fields are set to null         
        for (Schema.Sobjectfield f : layoutFields) {
        if (p.get(f)==null) p.put(f,null);
        }
    }
    
    // If we have successfully compiled new Pages for this module,        
   // delete the old ones and replace them with the new.        
   if (newPages != null &amp;&amp; !newPages.isEmpty()) {
    Schema.SObjectField f = skuid__Page__c.skuid__UniqueId__c;
    List<Database.UpsertResult> cr = Database.upsert(newPages,f,false);
    }   
    return newPages;
}

}

I looked at the code in my Page Pack. Here’s what all of my child pages show: 

“skuid__MasterPage__c”:“a04j000000AIaOnAAL”,“skuid__MasterPage__r”:{“attributes”:{“type”:“skuid__Page__c”,“url”:“/services/data/v34.0/sobjects/skuid__Page__c/a04j000000AIaOnAAL”},“Name”:“CheetahNavigation”,“skuid__UniqueId__c”:“CheetahBMS_CheetahNavigation”}

OK, so the Page Pack does have a value for skuid__MasterPage__r.skuid__UniqueId__c, which is good, because that’s the only reliable way to identify the same Page in two orgs. Looking at your Post Install script though, I see the issue: the tutorial is out of date as of Banzai. The gist of it is that version of the script doesn’t account for the Master/Child page functionality because it was written before they existed. After doing the initial upsert of all the Pages in the Page Pack, you have to go through all the Child Pages and associate them with the correct Master Page (which may not even be in the Page Pack). We’ll get the tutorial updated, but that’s the overall strategy.

Hi J.,

we are using postinstall class to do multiple task:

  1. assign permission set
  2. populate custom settings
  3. backup existing reference data
  4. insert new reference data
  5. upsert skuid pages
  6. backup skuid page assignments
  7. insert new page assignments
  8. initiate batch apex (if needed)

I used skuid provided code for upserting pages with little modification. i.e. I created private method to process skuid pages like this:

   &#47;&#47; private method to upsert pages from static resource (JSON)    private static void RefreshPagesInModule(String module) {
        string[] upsertErrList = new List<string>();
        string[] updateList = new List<string>();
        string[] insertList = new List<string>();
        string insertedPages = '';
        string updatedPages = '';
        string failedPages = '';
        cloupra__Batch__c batch = new cloupra__Batch__c();
        string csvString  = '';
        Attachment att;
        string event;
        
        &#47;&#47; See if a StaticResource containing new pages for this module yet exists      
        StaticResource sr = [
            select Body
            from StaticResource 
            where Name = :(module + 'Pages')
            and ((NamespacePrefix = NULL) OR (NamespacePrefix = :module))
            limit 1
        ];
        
        &#47;&#47; The new Pages for our module that we will be inserting       
        List<skuid__Page__c> newPages
        = (List<skuid__Page__c>) JSON&#46;deserialize(sr&#46;Body&#46;toString(),List<skuid__Page__c>&#46;class);
        List<Schema&#46;SObjectField> layoutFields = new List<Schema&#46;SObjectField>{
            skuid__Page__c&#46;skuid__Layout__c,
            skuid__Page__c&#46;skuid__Layout2__c,
            skuid__Page__c&#46;skuid__Layout3__c,
            skuid__Page__c&#46;skuid__Layout4__c,
            skuid__Page__c&#46;skuid__Layout5__c
        };
        
        for (skuid__Page__c p : newPages) {
            &#47;&#47; Get rid of the Ids so that upsert will proceed           
            p&#46;Id = null;
            &#47;&#47; Ensure that unused Layout fields are set to null         
            for (Schema&#46;Sobjectfield f : layoutFields) {
                if (p&#46;get(f)==null) p&#46;put(f,null);
            }
        }
        
        &#47;&#47; If we have successfully compiled new Pages for this module,        
        &#47;&#47; delete the old ones and replace them with the new&#46;        
        if (newPages != null &amp;&amp; !newPages&#46;isEmpty()) {
            Schema&#46;SObjectField f = skuid__Page__c&#46;skuid__UniqueId__c;
            try {
                event = 'upsert';
                List<Database&#46;UpsertResult> cr = Database&#46;upsert(newPages,f,false);
                integer ndx = 0;
                
                for (Database&#46;UpsertResult r : cr) {
                    
                    if (r&#46;isSuccess()) {
                        &#47;&#47; Operation was successful, so get the ID of the record that was processed
                        &#47;&#47;System&#46;debug('Successfully Inserted record: ' + r&#46;getId() + ' - ' + r);
                        
                        if(r&#46;isCreated()) { 
                            insertList&#46;add(newPages[ndx]&#46;Name);
                            insertedPages += newPages[ndx]&#46;Name + '
';
                        }
                        else {
                            updateList&#46;add(newPages[ndx]&#46;Name);
                            updatedPages += newPages[ndx]&#46;Name + '
';
                        }
                    }
                    else {
                        string errMsg = r&#46;getErrors()[0]&#46;getMessage();
                        errMsg += ',' + newPages[ndx]&#46;Name + '
';
                        upsertErrList&#46;add(errMsg);
                    }
                    ndx++; 
                }
            
            }
            catch (exception e) {
                 system&#46;debug(logginglevel&#46;info, 'upsert error--------- ' + string&#46;valueOf(e));
            }       
        }        
        if(!newPages&#46;isEmpty()) {
            batch&#46;cloupra__Status__c = 'Completed';
            batch&#46;cloupra__Description__c = 'Batch_Creted_By_Post_Install_Script - skuid__Page__c Upsert';
            
            if(!upsertErrList&#46;isEmpty()) {
                batch&#46;cloupra__Status__c = 'Completed (With Errors)';
                
                for(string s: upsertErrList) {
                    csvString = csvString + s;
                }
                
                csvString = 'Error Message,Page Name
' + csvString;
            }
                        
            insert batch;
            
            string feedTitle = 'Page Upsert Result:
';
            string msgBody = 'Upsert Result:
';
            msgBody += newPages&#46;size() + ' pages processed 
';
            msgBody += insertList&#46;size() + ' pages inserted
';
            msgBody += updateList&#46;size() + ' pages updated
';
            msgBody += upsertErrList&#46;size() + ' pages failed

';
            msgBody += 'Inserted Pages:
' + insertedPages + '
';
            msgBody += 'Updated Pages:
' + updatedPages + '
';
            msgBody += 'Failed Pages:
' + csvString;
            
            if(!upsertErrList&#46;isEmpty()) {
                att = new Attachment();
                att&#46;Name = 'Page upsert error result';
                att&#46;Description = msgBody;
                att&#46;Body = blob&#46;valueOf(csvString);
                att&#46;ParentId = batch&#46;Id;
                insert att; 
            }
            
            FeedItem post = new FeedItem();
            post&#46;ParentId = batch&#46;Id;
            post&#46;Body = msgBody;
            post&#46;Type = 'TextPost';
            post&#46;Title = feedTitle;
            insert post;           
        }        
    }

And called this method form OnInstall method like this

   global void onInstall(InstallContext context) {       
     IsRunning = true;
        &#47;&#47; if this is an upgrade
        if(context&#46;isUpgrade() || context&#46;isPush()) {
            &#47;&#47; upsert skuid pages
<b><i>RefreshPagesInModule('Module_Name');</i></b>
                
            &#47;&#47; Assign required permission sets   
            &#47;&#47; This check is to avoid mixed-dml-error in test class         
            
            if(Test&#46;isRunningTest()) {
                User adminuser = [select Id from User where Profile&#46;Name = 'System Administrator' and IsActive = true limit 1];
                System&#46;runAs(adminuser){
                    Utils&#46;AssignPermissionSets();
                }
            }
            else {
                Utils&#46;AssignPermissionSets();
            }            
            
            &#47;&#47; Populate Custom Settings fields
            Utils&#46;PopulateCustomSettings();               
        
            &#47;&#47; Take full backup of clients reference data and save to Documents (PractiFI Backup folder)
            Utils&#46;BackupRefData();              
        
            &#47;&#47;Populate Reference data 
            Utils&#46;InsertNewRefData();
            
            &#47;&#47;Update Content Items for process launch
            Utils&#46;UpdateContentItems();              
            
            
            if(context&#46;previousVersion()&#46;compareTo(new Version(5,10)) < 0) {
                &#47;&#47; Update ProcessSteps
                Utils&#46;UpdateProcessSteps();
                
                &#47;&#47; Update Task Rich Description                
                string updateTaskDescriptions = Database&#46;executeBatch(new BatchableJob(new ChangeTaskDescription()), 200);
            }                     
        }
        IsRunning = false;        
    }

Now when I see the tutorial (which presumably addressed master page issue), it’s a class that was extended from skuid’s global class InstallScript.

I was wondering how could I use this new approach to upsert pages without breaking my postinstall script. Or can I get an idea how I can update my existing method RefreshPagesInModule to handle Master Page issue.

Thanks.

Hi J.,

I updated the method RefreshPagesInModule to handle Master and child pages and now seems to work well. Just wanted to know if I’m doing anything wrong in the method:


    // private method to upsert pages from static resource (JSON)    
   private static void RefreshPagesInModule(String module) {
        string[] upsertErrList = new List<string>();
        string[] updateList = new List<string>();
        string[] insertList = new List<string>();
        string insertedPages = '';
        string updatedPages = '';
        string failedPages = '';
        cloupra__Batch__c batch = new cloupra__Batch__c();
        string csvString  = '';
        Attachment att;
        string event;
        
        // See if a StaticResource containing new pages for this module yet exists      
        StaticResource sr = [
            select Body
            from StaticResource 
            where Name = :(module + 'Pages')
            and ((NamespacePrefix = NULL) OR (NamespacePrefix = :module))
            limit 1
        ];
        
        // The new Pages for our module that we will be inserting       
        List<skuid__Page__c> newPages
        = (List<skuid__Page__c>) JSON.deserialize(sr.Body.toString(),List<skuid__Page__c>.class);
        List<Schema.SObjectField> layoutFields = new List<Schema.SObjectField>{
            skuid__Page__c.skuid__Layout__c,
            skuid__Page__c.skuid__Layout2__c,
            skuid__Page__c.skuid__Layout3__c,
            skuid__Page__c.skuid__Layout4__c,
            skuid__Page__c.skuid__Layout5__c
        };        
        
        
        Map<string, string> pageMap = new Map<string, string>();
        
        for (skuid__Page__c p : newPages) {
            if(p.skuid__MasterPage__c != null) {
                // map of unique Id of page to unique id of master page to be used to set master page for child pages in newPages list
                pageMap.put(p.skuid__UniqueId__c, p.skuid__MasterPage__r.skuid__UniqueId__c);
                // Get rid of the master page Ids so that upsert will proceed           
                p.skuid__MasterPage__c = null;
                p.skuid__MasterPage__r = null;
            }
            // Get rid of the Ids so that upsert will proceed           
            p.Id = null;
            // Ensure that unused Layout fields are set to null         
            for (Schema.Sobjectfield f : layoutFields) {
                if (p.get(f)==null) p.put(f,null);
            }
        }
        
        
        // If we have successfully compiled new Pages for this module,        
        // delete the old ones and replace them with the new.        
        if (newPages != null &amp;&amp; !newPages.isEmpty()) {
            Schema.SObjectField f = skuid__Page__c.skuid__UniqueId__c;
            try {
                event = 'upsert';
                List<Database.UpsertResult> cr = Database.upsert(newPages,f,false);
                integer ndx = 0;
                
                for (Database.UpsertResult r : cr) {
                    
                    if (r.isSuccess()) {
                        // Operation was successful, so get the ID of the record that was processed
                        //System.debug('Successfully Inserted record: ' + r.getId() + ' - ' + r);
                        
                        if(r.isCreated()) { 
                            insertList.add(newPages[ndx].Name);
                            insertedPages += newPages[ndx].Name + '
';
                        }
                        else {
                            updateList.add(newPages[ndx].Name);
                            updatedPages += newPages[ndx].Name + '
';
                        }
                    }
                    else {
                        string errMsg = r.getErrors()[0].getMessage();
                        errMsg += ',' + newPages[ndx].Name + '
';
                        upsertErrList.add(errMsg);
                    }
                    ndx++; 
                }            
            }
            catch (exception e) {
                 system.debug(logginglevel.info, 'upsert error--------- ' + string.valueOf(e));
            }       
        }
        
        skuid__Page__c[]  allPages = [select Id, skuid__UniqueId__c, skuid__MasterPage__c from skuid__Page__c where skuid__Module__c = :module];
        Map<string, string> IdUniqueIdMap = new Map<string, string>();
        
        for(skuid__Page__c p: allPages) {
            IdUniqueIdMap.put(p.skuid__UniqueId__c, p.Id);
        }
        skuid__Page__c [] childPages = new List<skuid__Page__c>();
        
        for(skuid__Page__c p: allPages) {
            // if page did not have master page in the pagepack continue
            if(pageMap.get(p.skuid__UniqueId__c) == null) continue;
            p.skuid__MasterPage__c = IdUniqueIdMap.get(pageMap.get(p.skuid__UniqueId__c));
            childPages.add(p);
        }
        
        if(!childPages.isEmpty()) {       
            database.update(childPages, false); 
        }
        
        if(!newPages.isEmpty()) {
            batch.cloupra__Status__c = 'Completed';
            batch.cloupra__Description__c = 'Batch_Creted_By_Post_Install_Script - skuid__Page__c Upsert';
            
            if(!upsertErrList.isEmpty()) {
                batch.cloupra__Status__c = 'Completed (With Errors)';
                
                for(string s: upsertErrList) {
                    csvString = csvString + s;
                }
                
                csvString = 'Error Message,Page Name
' + csvString;
            }
                        
            insert batch;
            
            string feedTitle = 'Page Upsert Result:
';
            string msgBody = 'Upsert Result:
';
            msgBody += newPages.size() + ' pages processed 
';
            msgBody += insertList.size() + ' pages inserted
';
            msgBody += updateList.size() + ' pages updated
';
            msgBody += upsertErrList.size() + ' pages failed

';
            msgBody += 'Inserted Pages:
' + insertedPages + '
';
            msgBody += 'Updated Pages:
' + updatedPages + '
';
            msgBody += 'Failed Pages:
' + csvString;
            
            if(!upsertErrList.isEmpty()) {
                att = new Attachment();
                att.Name = 'Page upsert error result';
                att.Description = msgBody;
                att.Body = blob.valueOf(csvString);
                att.ParentId = batch.Id;
                insert att; 
            }
            
            FeedItem post = new FeedItem();
            post.ParentId = batch.Id;
            post.Body = msgBody;
            post.Type = 'TextPost';
            post.Title = feedTitle;
            insert post;           
        }     
    } 

Any Advice would be much appreciated. Thanks.