Thursday, May 19, 2016

Trailhead - Custom Metadata Types

When Custom Metadata Types were first introduced my first reaction was - What is the difference between Custom Settings and Custom Metadata Types?. Why would you use one over the other?

There is now a convenient new Trailhead module that helps answer this question with gif animations in the first unit.

The key point from these gifs are the configuration records that represent the actual configuration. If you are currently using custom settings or custom objects to hold configuration for your org, it's well worth exploring how Custom Metadata Types can help with deployments.

Other useful areas of Custom Metadata Types you can explore in the module:

  • You can use the Custom Metadata Loader to bulk load up to 200 custom metadata records from a CSV.
  • How to configure new Custom Metadata Types and the corresponding records.
  • How custom metadata records are accessed in testing contexts.
  • How to control access to the Custom Metadata Types and fields
  • How to include both the Custom Metadata Type and the corresponding records in a package.
  • How to convert from list custom settings to custom metadata types.
  • There is also some wisdom hidden away in the challenge questions (although it may not be the answer Trailhead is looking for):

    See also:

    Thursday, May 12, 2016

    Summer '16 new Apex Method - Get a Map of Populated SObject Fields

    I've just found my new favorite Apex method in the Summer '16 Release notes - Get a Map of Populated SObject Fields

    // In Summer ’16, we’ve introduced a new method on the SObject class that returns a map of populated field names and their corresponding values:
    Map<String, Object> getPopulatedFieldsAsMap()
    

    Where is this immediately helpful? Maybe this error message looks familiar:

    System.SObjectException: SObject row was retrieved via SOQL without querying the requested field: Account.Name

    In the simplest case it comes from something like the following:

    List accs = [Select Id from Account];
    System.debug(accs[0].Name);
    

    The Accounts were queried for just the Id field, and then the code immediately tries to access the Name field of an Account, which wasn't queried. The SOQL query needs to be expanded to something like: Select Id, Name from Account. In an ideal world fixing this would be as simple as back tracking a few lines of code to find where the Account was queried and then adding the missing fields. In practice the "back tracking" may not be all that simple. From when the object was first queried to when it reached the code that needs a specific field to be populated it could have traversed several methods and classes. Possibly even gone through a namespace change or passed off to an unknown class that implements an interface via Type.forName() and Type.newInstance().

    With the new Summer '16 sObject method we can do some explicit code checks to see if the random Account instance we've been passed has the expected fields populated.

    List<account> accs = [Select Id from Account];
    System.assert(accs[0].getPopulatedFieldsAsMap().ContainsKey('Name'), 'The Account should be queried with the Name field');
    System.debug(accs[0].Name);
    

    This will also work for sObjects that haven't been inserted yet. You can see which fields have been populated.

    Contact con = new Contact();
    con.firstname = 'John';
    System.assert(con.getPopulatedFieldsAsMap().ContainsKey('LastName'), 'The Contact should have a LastName');
    

    In practice you would likely store the Map that comes out of getPopulatedFieldsAsMap() in a variable and do numerous checks on it.

    Having an assertion fail isn't the most elegant solution. Not much better than the existing SObjectException. So how do you handle a missing field?

    You could go back to the originating SOQL query and add the missing field. However, as mentioned above, you may have little or no control of that query. Instead, you could build up a dynamic SOQL query that will pull just the identified missing fields for all the sObjects affected. Then use the sObject.put method to merge the results back into the original sObjects.

    Contact con = [Select Id, FirstName from Contact limit 1]; // Whoops, forgot to query the LastName!
    
    List<string> requiredFields = new List<string> {'FirstName', 'LastName'};
    
    string dynamicQuery = 'Select Id';
    boolean queryRequired = false;
    Map<String, Object> fieldsToValue = con.getPopulatedFieldsAsMap();
    for(string requiredField : requiredFields) {
        if(!fieldsToValue.containsKey(requiredField)) {
            dynamicQuery += ', '+ requiredField;
            queryRequired = true;
        }
    }
    
    if(queryRequired) {
        // Exercise for the reader, better bulkificaiton support for dealing with multiple sObjects
        dynamicQuery += ' From Contact where Id in (\''+con.Id+'\')';
        for(Contact mergingContact : Database.query(dynamicQuery)) {
            // TODO: Matchup queried Contacts with base contacts on Id (or an external Id)
            Map<String, Object> mergingFieldsToValue = mergingContact.getPopulatedFieldsAsMap();
            for(string fieldName : mergingFieldsToValue.keySet()) {
                con.put(fieldName, mergingFieldsToValue.get(fieldName));
            }        
        }
    }
    
    System.debug(con); //12:55:50:007 USER_DEBUG [12]|DEBUG|Contact:{FirstName=John, LastName=Doe}
    

    Things that would make it even more useful:

    • Being able to "depopulate" a field on an sObject. E.g. you've got an Account instance, but only want send specific fields in for a DML update. Currently you would need to create a new sObject and set just the fields you wanted. If you could depopulate them instead you could use the original instance without the risk of updating fields that should be unchanged.
    • A built in way to retrieve additional fields into a collection of sObjects. My sample implementation above should work in theory, but it would be great to provide a collection of sObjects and the names of the additional fields you need populated and have Apex do the rest.
          List<Contact> someListOfContactsWithoutFirstName = //...
          List<string> additionalFields = new List<string> {'FirstName'};
          List<Contact> awesomeListOfContactsNowWithFirstName = Database.queryFields(someListOfContactsWithoutFirstName, additionalFields);
      

    Thursday, April 7, 2016

    The Search for Astro

    Update: Finding Astro just became a whole lot harder as the module no longer appears to be available.


    In what has become become the hallmark of Trailhead emerges a rather cockamamy module to locate the lost Astro mascot. It may seem like an excuse for the creators to make odd videos and run around in the woods blair witch style, and maybe it was considering it went live on the 1st of April, but you might actually learn something along the way (and maybe win a prize).

    The key to finding Astro will be in decoding the clues left in the trails. Expect to watch the videos of noir interrogations, Tanooki cosplay, goats, ... and then jump out to the indicated modules to find additional clues to complete the code. What code is this you ask? The first unit provides more details, but it looks something like this:

    Don't worry if you never finished reading the Cryptonomicon. Or if you don't know the difference between a one-time pad and using the Apex Crypto class with AES256.

    Instead, download the zip from the first part of the module that includes an Excel file you can use to complete the challenge. Alternatively, I've created an equivalent Google Sheet for decoding the clues.

    Other things you might learn in the module:

    • How long it takes to get Cloudy the Goat groomed for a World Tour event.
    • Who sleeps on a pillow stuffed with Astro's hair.
    • "uploading data you get from a random dog you meet in the woods is NOT a Salesforce best practice"
    • Items that Cloudy the Goat has been using for mastication
    • #PancakeHands

    So, hit the trail. Decrypt the note. And bring Astro home!


    See also:

    Wednesday, March 30, 2016

    Salesforce Force.com IDE superpowers uncovered

    Disclaimer: I've been informed by Salesforce that this an exceptional case for functionality is still being refined and will likely be exposed broadly in a future API version (#SafeHarbour). Although perhaps not in this exact form. As with all undocumented API features they could disappear at any time in a future release.

    Borrowing the Warranty phrasing from Scott Hanselman:
    Of course, this is just some dude's blog. Depending on undocumented API functionality is a recipe for losing your job and a messy divorce. Salesforce are likely to make changes to the existing functionality between major releases. As they don't know you are using this functionality they won't tell you. There's no warranty, express or implied. I don't know you and I don't how how you got here. Stop calling. Jimmy no live here, you no call back! [My current landline phone number used to belong to a Thai takeaway shop - True Story]

    My blog disclaimer also applies.


    In putting together an answer to a Salesforce StackExchange question I came across something odd with the Force.com IDE source code. The question needed a way to find details about installed packages.

    I knew from my keyprefix list that InstalledPackageVersion existed. It wasn't, however, exposed via SOQL to the Partner API or Tooling API. So why then when I was Googling around does it show up in the source code for the Force.com IDE in a SOQL query?:

        // P A C K A G E S
        DEVELOPMENT_PACKAGES("SELECT Id, Name, Description, IsManaged FROM DevelopmentPackageVersion"),
        INSTALL_PACKAGES("SELECT Id, Name, Description, IsManaged, VersionName FROM InstalledPackageVersion"),
    

    What makes the Force.com IDE so special that it can run SOQL queries that other API users can't?

    The answer lies in the SOAP API CallOptions.client header. The docs say this is "A string that identifies a client.". I've used it before in the past after passing the app exchange security review to access the Partner API in professional edition orgs. It turns out this is also the key to accessing the hidden abilities of the Force.com IDE. Again from the source code for the Force.com IDE:

        //value is critical for Eclipse-only API support
        private final String clientIdName = "apex_eclipse";
    

    This is later combined with the API version to create the callOptions header.

    So what if we use the same string when we establish our Session using the Partner API and then on subsequent calls?

    The FuseIT SFDC Explorer supports setting the Client Id on the login New Connection screen and on a saved connection string.

       <add name="ForceCom IDE Login" 
         connectionString="G4S:user id=user@example.com;password=shhSecret;environment=Production;Client=apex_eclipse/v36.0"/>
    

    The raw SOAP POST request:

    <?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <soap:Header>
        <CallOptions xmlns="urn:partner.soap.sforce.com">
          <client>apex_eclipse/v36.0</client>
        </CallOptions>
        <SessionHeader xmlns="urn:partner.soap.sforce.com">
          <sessionId>00D300000000001!AQ0AQDxJ1jtrcX3XNotARealSessionIdThatYouCanUseOAgFtJDkHc.IQ6Wjv6b1rTp7qQlNMOOFcZj6</sessionId>
        </SessionHeader>
      </soap:Header>
      <soap:Body>
        <-- etc... -->
      </soap:Body>
    

    Now we be access to additional sObject Types that were previously inaccessible. So far I've tried:

    • DevelopmentPackageVersion
    • InstalledPackageVersion
    • ApexClassIdentifier
    • ApexClassIdentifierRelationship

    I'll do some more poking around as time permits to see if there are any other hidden treasures.

    Friday, March 4, 2016

    FuseIT SFDC Explorer 3.0.16053.2

    The latest v3.0 release of the FuseIT SFDC Explorer now supports TLS 1.1 and 1.2. This was required as Salesforce will be disabling TLS 1.0 via critical updates available in Spring '16.

    Part of this was to upgrade to the .NET Framework 4.6.1 to get native support for the newer TLS versions. You can get the .NET Framework 4.6.1 installer from https://www.microsoft.com/en-us/download/details.aspx?id=49981. Note that it does need to be 4.6.1, the 4.6 installer won't work.

    Without this version you are likely to get an error like the following when attempting to login:

    UNSUPPORTED_CLIENT: TLS 1.0 has been disabled in this organization. Please use TLS 1.1 or higher when connecting to Salesforce using https.

    Other notable changes:

    • Update ApexLog parsing to allow for Spring '16 date formats
    • Wsdl2Apex: Add basic WSDL schema validation and reporting via error messages. Detect potential WSDL 1.1 structure issues.
    • Wsdl2Apex: Apex variable names can't start with an underscore. Will be prefixed with 'x'

    Thursday, February 18, 2016

    Trailhead - Navigate the Salesforce Advantage

    From time to time your parents or grandparents will inquire about what I do for a living. With the latter I first explain that there is no plugging of wires involved, unless I'm breadboarding an IoT experiment, which leads to further confusion.

    I've also found that starting out with the term "cloud computing" leads to more vague looks. And the name "Salesforce" makes them think I'm in retail. So where to start?

    Trailhead to the rescue!

    The new Navigate the Salesforce Advantage trial can help explain who and what Salesforce is and how businesses use it. All with a nautical theme!

    So what will you learn on this trail?

    The entire trail only takes around an hour and gives you a good primer on all things Salesforce.


    See also, the Spring '16 Release specific module (available for a limited time)

    Thursday, January 7, 2016

    FuseIT SFDC Explorer v2.12 Release - Event Log viewer

    The latest v2.12 release of the FuseIT SFDC Explorer now supports a viewer for the Event Log API. This can be useful if you want to quickly browse the Salesforce Event Log content without having to process the base64 encoded CSV content from the LogFile field of EventLogFile.

    You can also export the CSV to open it in an external tool, such as Excel.

    The LogFileFieldTypes and LogFileFieldNames from the EventLogFile are used to improve the formatting of the log file.

    See also: