5

I'm making extensive use of the Metadata API (We have it implemented in Javascript for an admin page on a managed package). The main features we're using is the automatic creation of buttons (to prevent the need to copy and paste Javascript from documentation, etc). The button creation works just fine, however when it comes time to add it to the searchlayouts for the object, it starts to act strange. I'm adding the button name to the searchLayouts on the custom object (for the list view buttons and and the search results custom buttons.) and it comes back successful. If queried, it shows the button there. Even on the setup screen for the search results, it shows the button, but the button is not shown on the list view itself. The most infuriating part is that it's easily resolved by simply clicking "save" on the list view page. Nothing changes, but it suddenly works! Is there something I'm missing in my metadata call? Code snippet here (we have it somewhat spread out in our library, but this is the gist):

//tableName is the API name of the custom object supplied
//buttonName is the API name for the created button
//listName is whether it's the SearchResultsCustomButtons or the ListViewButtons to maximize code reuse
//callback is the function callback to invoke when it's completed
//resultFail is a failout function for error handling.
this.readMetadata('CustomObject', tableName, function (response) { //This is a function we have for generating the SOAP request and parsing the xml that is returned into an object
            var custObj = response.records;
            if (!custObj.searchLayouts) {
              return;
            }
          if (custObj.searchLayouts[listName] == undefined) { //In case the value is 
            custObj.searchLayouts[listName] = [];
          }
          else if (typeof(custObj.searchLayouts[listName]) === 'string') //This is an idiosyncrasy of our parser, I believe, that converts single entries in a list to a single value.
            custObj.searchLayouts[listName] = [custObj.searchLayouts[listName]];

          var prefix = "";
          if (custObj.webLinks instanceof Array)
          {
             Object.each(custObj.webLinks, function(webLink){
                if (webLink.fullName.indexOf('crmc_Grid') > 0)  //Our namespace is 'crmc_Grid' and we've struggled with handling it between dev orgs and installed orgs for the managed package
                {
                    prefix = webLink.fullName.split('__')[0] + '__';
                }

              });
          }
          else
          {
            if (!custObj.webLinks){//Error handling on issues of "attempting to add a button that isn't created yet
                callback({
                    errors:{
                        statusCode:"INVALID_CROSS_REFERENCE_KEY"
                    }
                });
                return;
            }
            if (custObj.webLinks.fullName.indexOf('crmc_Grid') > 0)
            {
                prefix = custObj.webLinks.fullName.split('__')[0] + '__';
            }
          }
          custObj.searchLayouts[listName].push(prefix+buttonName);//here's where we add it, either with the prefix on managed packages or blank for our dev orgs.
          var minObj = { fullName: custObj.fullName, "xsi:type": custObj["xsi:type"], searchLayouts: custObj.searchLayouts };//I've struggled with updating objects if you have every field present, yet if you don't have some of them, then it'll delete them.
          if (custObj.pluralLabel != undefined)
            {
                for(var i in custObj)
                {
                  if (i != 'webLinks' && i !='fields' && i !='listViews')//These are the little idiosyncrasies for this that are so much fun.  If we include these, the update fails.  If we skip the others, they're cleared out.
                  {    
                      minObj[i] = custObj[i];
                  }
                }
            }
          that.updateMetadata('customObject', custObj.fullName, minObj, function (response) {//Another helper function that just invokes the metadata update
            if (typeof(callback) === "function")
              callback(response);
          }, resultFail);
      });

If anyone else is doing anything similar or has hit roadblocks like this, I'd appreciate any help that can be offered. Thank you.

3
  • 1
    Did you ever solve this? I am seeing the same behaviour as well. In fact it appears to be for any changes to any of the search layout metadata on an object. For some reason the underlying engine doesn't seem to recognise the change and keeps delivering the a cached, outdated, version of the metadata. I'm about to raise a case with SFDC but was wondering if you had done anything similar or solved this yourself. Commented Mar 8, 2015 at 22:02
  • 1
    @SMGoodYear, no, unfortunately we never resolved this. We even considered retiring the feature because of the inconsistent behavior. We also considered further investigating into whether it was possible to force the cache somehow (basically "jiggle the switch"), but we didn't assign any resources. If you get any useful answers from your Case, I'd love to hear them. Best of luck! Commented Mar 8, 2015 at 22:27
  • 1
    I will keep you posted and update this with an answer to the question. I had a look a "jiggling the switch" it looked hard and very hacky at best. Instead I used this last night to work around the issues: jsforce.github.io/document/#metadata-api I had to do a complete retrieve/update xml/deploy cycle to get it to update but at least this way it actually recognises the update. Commented Mar 9, 2015 at 8:26

1 Answer 1

1

This looks like a Salesforce bug. We reproduced as follows:

  1. snapshot org, save search layout using API
  2. snapshot org, re-save search layout using GUI
  3. snapshot org, inspect search layouts

Between snapshots 1. and 2. the expected change is seen:

org metadata snapshot diff 1

Between snapshots 2. and 3. there are NO ancillary metadata changes visible:

org metadata snapshot diff 2

Safe to say this is surely not working as designed. There are no visible side effects from using the GUI versus the API. The issue always occurs when using the synchronous metadata insert/update/upsert operations.

One workaround is to use the ZIP archive asynchronous deploy for the custom object / search layout. Or indeed to "jiggle the switch" from JavaScript or Apex by deploying a sparse skeleton of the custom object over itself, again with ZIP archive asynchronous operation.

Here is one such "switch jiggler" using Zippex and Apex Metadata API:

Blob pkg = Blob.valueOf(''
    + '<?xml version="1.0" encoding="UTF-8"?>'
    + '<Package xmlns="http://soap.sforce.com/2006/04/metadata">'
    + '    <version>41.0</version>'
    + '    <types>'
    + '        <members>*</members>'
    + '        <name>CustomObject</name>'
    + '    </types>'
    + '</Package>'
);

Blob customObject = Blob.valueOf(''
    + '<?xml version="1.0" encoding="UTF-8"?>'
    + '<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">'
    + '    <deploymentStatus>Deployed</deploymentStatus>'
    + '    <sharingModel>ReadWrite</sharingModel>'
    + '    <label>Sneeze</label>'
    + '    <pluralLabel>Sneezes</pluralLabel>'
    + '    <nameField>'
    + '        <label>Sneeze Name</label>'
    + '        <type>Text</type>'
    + '    </nameField>'
    + '</CustomObject>'
);

Zippex zip = new Zippex();
zip.addFile('package.xml', pkg, null);
zip.addFile('objects/Sneeze__c.object', customObject, null);

MetadataClient.DeployOptions options = new MetadataClient.DeployOptions();
options.checkOnly = false;
options.testLevel = 'NoTestRun';

String zipFile = EncodingUtil.base64Encode(zip.getZipArchive());
String sessionId = UserInfo.getSessionId();
MetadataClient client = new MetadataClient(sessionId);
client.deploy(zipFile, options);

(An empty CustomObject element is insufficient. The label and status fields etc must be provided)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .