Tweet |
Exporting Folder Metadata with the Salesforce CLI
Introduction
In my earlier post, Exporting Metadata with the Salesforce CLI, I detailed how to replicate the Force.com CLI export command using the Salesforce CLI. One area that neither of these handle is metadata inside folders, so reports, dashboards and earl templates. Since then I’ve figured out how to do this, so the latest version of the CLIScripts Github repo has the code to figure out which folders are present, and includes the contents of each of these in the export.
Identifying the Folders
This turned out to be a lot easier than I expected - I can simply execute a SOQL query on the Folder sobject type and process the results:
let query="Select Id, Name, DeveloperName, Type, NamespacePrefix from Folder where DeveloperName!=null"; let foldersJSON=child_process.execFileSync('sfdx', ['force:data:soql:query', '-q', query, '-u', this.options.sfdxUser, '--json']);
Note that I’m not entirely sure what it means when a folder has a DeveloperName of null - I suspect it indicates a system folder, but as the folders I was interested in appeared, I didn’t look into this any further.
I then created a JavaScript object containing a nested object for each folder type:
this.foldersByType={'Dashboard':{}, 'Report':{}, 'Email':{}}
and then parsed the resulting JSON, adding each result into the appropriate folder type object as a property named as the folder Id. The property contains another nested object wrapping the folder name and an array where I will store each entry from the the folder:
var foldersForType=this.foldersByType[folder.Type]; if (foldersForType) { if (!foldersForType[folder.Id]) { foldersForType[folder.Id]={'Name': folder.DeveloperName, 'members': []}; } }
Retrieving the Folder Contents
Once I have all of the folders stored in my complex object structure, I can query the metadata for each folder type - the dashboards in this instance - and build out my structure modelling all the folders and their contents:
let query="Select Id, DeveloperName, FolderId from Dashboard"; let dashboardsJSON=child_process.execFileSync('sfdx', ['force:data:soql:query', '-q', query, '-u', this.options.sfdxUser, '--json']);
I then iterate the results and add these to the members for the specific folder:
var foldersForDashboards=this.foldersByType['Dashboard']; var folderForThisDashboard=foldersForDashboards[dashboard.FolderId]; if (folderForThisDashboard) { folderForThisDashboard.members.push(dashboard); }
Adding to the Manifest
As covered in the previous post on this topic, once I’ve identified the metadata required, I have to add it to the manifest file - package.xml.
I already had a method to add details of a metadata type to the package, so I extended that to include a switch statement to change the processing for those items that have folders. Using dashboards as the example again, I iterate all the folders and their contents, adding the appropriate entry for each:
case 'Dashboard': var dbFolders=this.foldersByType['Dashboard']; for (var folderId in dbFolders) { if (dbFolders.hasOwnProperty(folderId)) { var folder=dbFolders[folderId]; this.addPackageMember(folder.Name); for (var dbIdx=0; dbIdx<folder.members.length; dbIdx++) { var dashboard=folder.members[dbIdx]; this.addPackageMember(folder.Name + '/' + dashboard.DeveloperName); } } } break;
In the case of our BrightMedia appcelerator, the package.xml ends up looking something like this:
<types> <members>BG_Dashboard</members> <members>BG_Dashboard/BrightMedia</members> <members>BG_Dashboard/BrightMedia_digital_dashboard</members> <members>Best_Practice_Service_Dashboards</members> <members>Best_Practice_Service_Dashboards/Service_KPIs</members> <members>Sales_Marketing_Dashboards</members> <members>Sales_Marketing_Dashboards/Sales_Manager_Dashboard</members> <members>Sales_Marketing_Dashboards/Salesperson_Dashboard</members> <name>Dashboard</name> </types>
Exporting the Metadata
One thing to note is that the export is slowed down a bit as there are now six new round trips to the server - three for each of the folder types, and three more to retrieve the metadata for each type of folder. Exporting the metadata using the command:
node index.js export -u <username> -d output
creates a new output folder containing the zipped metadata. Unzipping this shows that the dashboard metadata has been retrieved as expected:
> ls -lR dashboards
total 24
drwxr-xr-x 5 kbowden staff 160 21 Jul 15:29 BG_Dashboard
-rw-r--r-- 1 kbowden staff 154 21 Jul 14:27 BG_Dashboard-meta.xml
drwxr-xr-x 5 kbowden staff 160 21 Jul 15:29 Best_Practice_Service_Dashboards
-rw-r--r-- 1 kbowden staff 174 21 Jul 14:27 Best_Practice_Service_Dashboards-meta.xml
drwxr-xr-x 6 kbowden staff 192 21 Jul 15:29 Sales_Marketing_Dashboards
-rw-r--r-- 1 kbowden staff 180 21 Jul 14:27 Sales_Marketing_Dashboards-meta.xml
dashboards//BG_Dashboard:
total 48
-rw-r--r-- 1 kbowden staff 2868 21 Jul 14:27 BrightMedia.dashboard
-rw-r--r-- 1 kbowden staff 6917 21 Jul 14:27 BrightMedia_digital_dashboard.dashboard
dashboards//Best_Practice_Service_Dashboards:
total 88
-rw-r--r-- 1 kbowden staff 8677 21 Jul 14:27 Service_KPIs.dashboard
dashboards//Sales_Marketing_Dashboards:
total 96
-rw-r--r-- 1 kbowden staff 10317 21 Jul 14:27 Sales_Manager_Dashboard.dashboard
-rw-r--r-- 1 kbowden staff 8046 21 Jul 14:27 Salesperson_Dashboard.dashboard
One more thing
I also fixed the export of sharing rules, so rather than specifying SharingRules as the metadata type, it specifies the three subtypes of sharing rule (SharingCriteriaRule, SharingOwnerRule, SharingTerritoryRule) required to actually export them!
Related Posts
- Exporting Metadata with the Salesforce CLI
- SFDX and the Metadata API Part 4 - VS Code Integration
- SFDX and the Metadata API Part 3 - Destructive Changes
- SFDX and the Metadata API Part 2 - Scripting
- SFDX and the Metadata API
- Force CLI Part 5 - Node Wrapper
- Force CLI Part 4 - Deploying Metadata
- Force CLI Part 3 - Accessing Data
- Force CLI Part 2 - Extracting Metadata
- Force CLI Part 1 - Getting Started