When Salesforce is life!

Author: Enrico Murru Page 16 of 20

[Salesforce / Visualforce] Input Lookup Custom Component

Have you ever needed a VisualForce lookup input field without using a lookup field from another “fake” object?

I did, and now I have an easy to use VisualForce component that does the trick (almost all the trick).

Here is the GitHub repository that contains the component.

The components simply replicates what the standard lookup field does, with 2 important differences:

  • We have to use a custom Apex class to store the ID value (simple data types as String or ID are passed by value rather than reference, so it is impossibile to change the component’s parameter value on the fly, see Bob Buzzard post Updating Attributes in Component Controller)
  • If you change the value of the “name” of the lookup field and this matches more than one record, the form submission is canceled and a picklist with all the values is shown (so the user can choose the correct record)

The first class is the ID value container:

public class IDCarrier {
    public IDCarrier(){}
    public IDCarrier(ID value){
        this.value = value;
    }
    public ID value{get;Set;}
}

In your test controller, TestLookupController:

public class TestLookupController {
    public IDCarrier idValue1{get;set;}
 public IDCarrier idValue2{get;set;}
    public TestLookupController(){
        this.idValue1 = new IDCarrier([Select Id From Account Limit 1].Id);
  this.idValue2 = new IDCarrier();
    }
    public void submit(){
  //...
 }
}

You are creating 2 ID fields, one populated with a valid Account Id, the other without any value.

And now in the test page use the component:

<apex:page controller="TestPageController" >
    <apex:form >
        <apex:pageblock>
            <apex:pageBlockButtons location="bottom">
             <apex:commandButton value="SUBMIT" action="{!submit}" />
            </apex:pageBlockButtons>
         <apex:pageblocksection columns="2">
                <apex:pageBlockSectionItem>
                    <apex:outputLabel>Account lookup</apex:outputLabel>
                 <c:inputLookup sobject="Account" value="{!idValue1}"/>
                </apex:pageBlockSectionItem>
                <apex:pageBlockSectionItem>
                    <apex:outputLabel>Selected Account ID:</apex:outputLabel>
                 <apex:outputText value="{!idValue1.value}"/>
                </apex:pageBlockSectionItem>
                
                <apex:pageBlockSectionItem>
                    <apex:outputLabel>Contact lookup</apex:outputLabel>
                 <c:inputLookup sobject="Contact" value="{!idValue2}"/>
                </apex:pageBlockSectionItem>
                <apex:pageBlockSectionItem>
                    <apex:outputLabel>Selected Contact ID:</apex:outputLabel>
                 <apex:outputText value="{!idValue2.value}"/>
                </apex:pageBlockSectionItem>
            </apex:pageblocksection>
        </apex:pageblock>
    </apex:form>
</apex:page>

The component has only 2 parameters:

  • sobject: this is the Sobject API Name (with full prefix name if needed, mandatory)
  • lookup: this is the value field (of type IDCarrier). This is field is not mandatory (in case it is not set, the submit won’t set any value)

In the test page we have now 2 input lookup custom components, one of type Account and the other of type Contact:

If you click the “search” button the standard lookup search page appears (it is configurable with the standard object’s search layout options).

Once you select a new value for both input lookups and hit the SUBMIT button, the corresponding controller variables idValue1 and idValue2 are correctly updated:

If you type a different name value (without using the lookup window):

Then hit SUBMIT:

The first “%Sale%” Account found is linked to the input lookup component.
If no object is found by the query, the result is simply null (on the IDCarrier.value member).

That’s it!

[Salesforce / Trailhead] Christmas Trailhead Gifts

Have you been a good or a bad boy this year?

It doesn’t matter to TrailheadClaus, so he put new modules under the tree for you!

At first we have a new trail for advanced administrators.

This includes Event Monitoring and Lightning Connect modules and the new Advanced Formulas module.

This module explains advanced features of the formula fields’s synthax, allowing to reach a high level of proficiency on this important feature of the platform, including examples and use cases with a great diversity of scenarios.
Do you wanna the a Formula Expert? This is your chance!

The next module is about callouts in Apex, Apex Integration Services:

What is a Callout? How to make a REST callout? And what about SOAP? How to test them? Follow this module and all your questions will be answered.

On the beginner admin trail an awesome Lightning Chatter Basics module, that guides you through the Chatter Lightning experience:

This is a complete and easy guide on how to use and configure Chatter in Lightning mode.

The Lightning Data Management module helps admins and developers to import CSV data in your CRM using the Lightning experience mode

The Application Lifecycle Management modulo has been edited and improved to better explain application lifecycle management (how to deploy in sandbox, production, version control, …):

Last but not least a trailhead comes with a new awesome project, Build a Battle Station App: create the CRM for your Death Star, so you can manage its resources and report all your supplies!

And if you complete it before December 31st you can win awesome prices!

[Salesforce / Amazon Echo] Integrate Alexa with Salesforce

Among the #swag I got from my colleagues from their trip to DreamForce 15, there was a pearl: Amazon Echo, well known as Alexa.

The first (funny) thing I wanted to do was to make it (her) speak italian, and believe me it was as fun as learning how to use the Alexa Skill Kit: the only language you can use is English of course but you can still make her say strange words that “sound like” italian (after practicing building the library, even you would call it “her”).

As usual, if you are a TL;DR person (btw I’m one of you guys), this is the GitHub repository with all the Apex Code.

If you know how to setup a Connected App and expose e public webservice, you are ready to go (some changes on the code are still necessary, refer to the README.md file).

When developing an Alexa Skill you have two choices:

In this post I’m not going to show you how Alexa’s Request/Response paradigm works, but instead I’ll show you how to use the simple Apex library I’ve developed to help you implement your own skills and make the provided example service run on your Org.

Why Salesforce?

First of all because the Force.com platform is awesome and you can easily create REST services in few steps.

Secondly because you can integrate Alexa with Salesforce services (read this awesome post by Jeff Douglas, he has already developed some cool examples in NodeJS playing with Alexa) using your CRM with just voice commands.

So instead of setting up a middleware between Alexa and Salesforce I thought that it would have been amazing to have the service built directly inside Salesforce!

The recipe

This is what we’ll be using:

  • A new Alexa Skill (defined in the developer portal)
  • A Salesforce Alexa Skill Kit implementation (the library)
  • A REST webservice (public, to whom Alexa will be speaking)
  • A Connected App (to enable OAuth, your service will be using an auth. token)
  • A Salesorce Community (custom domain + Branding + Profiles)
  • An Amazon Echo (don’t you say?!?)

Create a new Alexa Skill

The first thing is to define a new skill.

With your amazon account go to the Amazon Developer site.

Choose the Alexa Skill Kit and create a new skill:

The Invocation name is the name that you have to say when calling this skill (e.g. Alexa, ask alexaforce for my user).

The Endpoint is the URL of the service (it will be your custom Salesforce REST service inside the Community): for now add a fake endpoint, you’ll be configuring it later (after configuring the Community and creating the REST service).

Write down your new Application ID.

By clicking the Next button you are setting the skill’s schema:

We’ll be having 3 simple intents:

  • AlexaForceHelp: this helps the user by saying what you can do with this skill
  • AlexaForceStop: this is used when you don’t want to use the skill anymore (e.g. the AlexaFOrceHelp intent ask for a continuation, by saying “stop” the skill ends)
  • SFUserInfo: Alexa reads the full name of the current user (if he is logged)

The next configuration step sets up the SSL certificate option (this is all handled by Salesforce platform):

Click “Next” again and the Test step allow to do some tests with your service (no service is defined for now, so leave this step for now):

The last step is about branding:

And account linking:

This configuration can only be completed when the Connected app has been created so you can link you Salesforce account to the skill: Alexa will be sending to Salesforce REST service the authorization token got when activating the skill on the Alexa site.

For now get the Redirect URL and write it down somewhere but select Account Linking or Creation to No (we’ll be activating this further).

The skill is half configured: to complete the configuration we need some Salesforce configuration.

The skill is now connected to your developer account, so it is shown in the Skill section of your https://alexa.amazon.com site.

Implement your skill in Apex

First thing to create is a new Skill class by creating a new AlexaSkill object:

AlexaSkill skill = new AlexaSkill();
skill.setApplicationId('amzn1.echo-sdk-ams.app.APP_ID');
skill.addIntent(new AlexaUserInfoIntent());
skill.addIntent(new AlexaForceHelpIntent());
skill.addIntent(new AlexaForceStopIntent());
skill.addOnLaunchIntent(new AlexaForceHelpIntent());
return skill;

The setApplicationId sets the application you got from the first page of configuration of the skill on the Amazon site.

The next lines create the new intents.

Each intent is an extension of the AlexaIntent class:

public class AlexaForceHelpIntent extends AlexaIntent{
    public AlexaForceHelpIntent(){
        super('AlexaForceHelp');
        List<AlexaIntent.Slot> slots = new List<AlexaIntent.Slot>();
        List<String> utterances = new List<String>();
        utterances.add('Help'); 
        utterances.add('What to do');
        utterances.add('What can i do');
        utterances.add('What can you do');
        utterances.add('Help me'); 
        this.setSlots(slots);
        this.setUtterances(utterances);
    }
    
    public override ASkillResponse execute(ASkillRequest request){
        String responseText = 'You can use ask for Salesforce info. For example try asking Alexaforce for "my user".';
        return this.say(responseText, null, null, null, false);
    }
}

The intent have to provide:

  • Its describe informations, such as the slots you will use and the utterances (both are not mandatory but suggested to help the configuration phase of the skill)
  • The implementation of the execute() method that handles the code behind the intent

The AlexaSkill class, among various getters and setters (to get/get utterances, slots and the name) provides an helper method to automatically create a response, the say() method.

You can pass the following parameters say(String text, String cardTitle, String cardContent, String reprompt, Boolean shouldEndSession):

  • text: text to be “said” by Alexa
  • cardTitle: title of the card sent to your alexa app (void for no card)
  • cardContent: content of the card
  • reprompt: reprompt sentence
  • shouldEndSession: this response will or will not close the current session

The AlexaSkill class provides the following methods:

  • getIntentSchema(): provides a JSON rapresentation of the intent’s schema. This will be exposed by the REST service we are going to implement
  • getUtterances(): provides a JSON rapresentation of the intent’s utterances. Exposes by the REST service
  • addOnLauchIntent(): (and remove method) allow to set an intent called when you call your skill without saying any of the utterances (e.g. Alexa ask alexaforce. This may be used to guide the user
  • addIntent(): (and remove method) adds an intent to the skill
  • addDefaultIntent(): (and remove method) this is an intent that is called when Alexa asks for an intent but this has not (yet) been implemented (not mandatory)
  • execute(): this method has 2 implementation, we’ll be using the one returning a String and getting a String as the only parameters: this method simply accepts a request JSON string and output the JSON response (this is the core method of the skill)

This classes use the ASkillRequest and ASkillResponse classes that rapresent the JSON request and response object.

Implement the Apex REST service

Once we have implemented our skills, we can create our new Skill REST service:

@RestResource(urlMapping='/AlexaRestTest/*')
global class AlexaRestTest {
    @HttpGet
    global static void get(){
        AlexaSkillRESTApp.handleGet(createAlexaSkill());
    }
    @HttpPost
    global static void post(){
        AlexaSkillRESTApp.handlePost(createAlexaSkill());
    }
    
    //Creates a new Skill
    private static AlexaSkill createAlexaSkill(){
        AlexaSkill skill = new AlexaSkill();
        skill.setApplicationId('amzn1.echo-sdk-ams.app.APP_ID');
        skill.addIntent(new AlexaUserInfoIntent());
        skill.addIntent(new AlexaForceHelpIntent());
        skill.addIntent(new AlexaForceStopIntent());
        skill.addOnLaunchIntent(new AlexaForceHelpIntent());
        return skill;
    }

}

This REST service uses the AlexaSkillRESTApp helper class that handles the GET and POST methods: this class implements a simple Debug process by registering every request/response couple in the RestLog__c Sobject:

You can disable this behavior changing the following member on the AlexaSkillRESTApp class to return false:

public class AlexaSkillRESTApp {
    
    //Enable debug
    public static BOOLEAN ENABLE_DEBUG{
        get{
            //to be replace with a CS / Custom Metadata
            return true;
        }
    }
    //. . .
}

The GET method supports the parameter schema to which you can pass the values utterances to get the JSON rappresentation to be copied in the skill configufation on the Amazon Developer site or leave it blank to get all the intant definitions.

E.g. https://alexaforce-00-developer-edition.eu5.force.com/alexaforce/services/apexrest/AlexaRestTest?schema=utterances
Outputs:

SFUserInfo get my user info
SFUserInfo get my user
SFUserInfo my user
SFUserInfo user info
AlexaForceHelp Help
AlexaForceHelp What to do
AlexaForceHelp What can i do
AlexaForceHelp What can you do
AlexaForceHelp Help me
AlexForceStop Stop
AlexForceStop Bye
AlexForceStop Bye bye
AlexForceStop Shut up
AlexForceStop Shut the fuck up
AlexForceStop Go to sleep
AlexForceStop sleep

E.g. https://alexaforce-00-developer-edition.eu5.force.com/alexaforce/services/apexrest/AlexaRestTest?schema
Outputs:

{
  "intents" : [ {
    "slots" : [ ],
    "intent" : "SFUserInfo"
  }, {
    "slots" : [ ],
    "intent" : "AlexaForceHelp"
  }, {
    "slots" : [ ],
    "intent" : "AlexForceStop"
  } ]
}

The next step is to configure a public endpoint (es. the one provided in the example) to make this REST service available.

Configure the Community

The Salesforce community will be used to give public access to the REST service we’ll be implementing, to get a custom domain and to allow different profiles to get logged in into the community.

Go to Setup > Communities > Settings and enable Communities and create a custom domain (e.g. alexaforce-00-developer-edition.eu5.force.com).

Now create a new community and activate it:

Add the profiles you want to log into the community:

The community allow branding so you can change default styling (color and logo) with few clicks.

This is not the only valid solution (you could have used a simple Site with custom Visualforce pages).

Make the REST service public

Go to Setup > Developer > Sites and search for the Community site just created:

Click on its name:

And then on Public Access Settings. Search for the Enable Apex Class Access Settings and add the AlexaRestTest class:

This way your service will be accessible outside with the endpoint https://[community_base_url]/[community_endpoint_folder]/services/apexrest/AlexaRestTest: note that the [community_endpoint_folder] is not mandatory when configuring the Community access url.

You have to set this endpoint on the skill’s configuration:

Last step is to configure the account linking.

Implement a connected intent

In the code provided you have an intent that does a simple request that requires a valid session ID:

public class AlexaUserInfoIntent extends AlexaIntent{
    
    public AlexaUserInfoIntent(){
        super('SFUserInfo');
        List<String> utterances = new List<String>();
        utterances.add('get my user info');
        utterances.add('get my user');
        utterances.add('my user');
        utterances.add('user info');
        this.setUtterances(utterances);
    }
    
    public override ASkillResponse execute(ASkillRequest request){
        Http h = new Http();
        HttpRequest req = new HttpRequest();
        //change this with your current community folder name (or leave blank string)
        String communityFolder = '/alexaforce';
        String endpoint = URL.getSalesforceBaseUrl().toExternalForm()+communityFolder+'/services/Soap/c/34.0';
        req.setMethod('POST');
        req.setEndpoint(endpoint);
        req.setHeader('Content-Type', 'text/xml; charset=utf-8');
        req.setHeader('SOAPAction','getUserInfo');
        
        String authToken = request.session.user.accessToken;
        
        String soapBody = '<?xml version="1.0" encoding="utf-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:enterprise.soap.sforce.com">  <soapenv:Header><urn:SessionHeader><urn:sessionId>'
                    +authToken+'</urn:sessionId></urn:SessionHeader></soapenv:Header><soapenv:Body><urn:getUserInfo /></soapenv:Body></soapenv:Envelope>';
        
        req.setBody(soapBody);
        HttpResponse resp = h.send(req);
        Dom.Document doc = resp.getBodyDocument();
        //Retrieve the root element for this document.
        Dom.XMLNode soapenvEnvelope  = doc.getRootElement();
        String SOAPENV_NS = 'http://schemas.xmlsoap.org/soap/envelope/';
        String NS = 'urn:enterprise.soap.sforce.com';
        Dom.XMLNode bodyNode = soapenvEnvelope.getChildElement('Body', SOAPENV_NS);
        Dom.XMLNode userinfoRespNode = bodyNode.getChildElement('getUserInfoResponse', NS);
        Dom.XMLNode resultNode = userinfoRespNode.getChildElement('result', NS);
        Dom.XMLNode fullnameNode = resultNode.getChildElement('userFullName',NS);
        String fullName = fullnameNode.getText();
        String responseText = 'Your user's full name is '+fullName;
        return this.say(responseText, 'Salesforce User Info', responseText, null, true);
    }  
}

The intent’s execute() method makes a call getting the (SOAP) UserInfo info about the current users associated to the authorization token provided with the request received (request.session.user.accessToken value): the response will be Alexa telling “You user’s full name is …”.

Remember to add a Remote Site Setting configuration to your community domain, otherwise the callouts won’t work.

The code is not optimized at all (you have to change the communityFolder variable to fit to your Community configuration).

Create a Connected App

Let’s enable OAuth within the Salesforce Org:

The Callback URL is set with the Redirect URL of the Skill configuration in the Amazon developer site (the one got at the beginning of the article).

The Consumer Key will be filled the Client Id field on the Skill configuration in the Amazon developer site (see the first part of this article)

The last piece of configuration of the Account Linking is the Authorization URL that should be the the Community’s login page like https://alexaforce-00-developer-edition.eu5.force.com/alexaforce/login: unfortunately when linking accounts Amazon’s Alexa Skills page doesn’t sends to the login page the redirect_uri parameter that is mandatory for successfull Salesforce OAuth process completion.

That’s why we need a custom login page that will be a proxy to the real login page: this is the AlexaOAuthStarter Visualforce page.

Open its controller class named AlexaOAuthStarterController:

public class AlexaOAuthStarterController {

    //Load action executed by the visualforce page called by Alexa's linked account attempt
    //ATTENTION: change the "communityName" and "alexaOauthCallbackURL" variables with the 
    //correct values
    public PageReference onLoad(){
        //change this with your current community folder name (or leave blank string)
        String communityFolder = '/alexaforce';
        //redirect uri configured in the Alexa Skill configuration
        String alexaOauthCallbackURL = 'https://pitangui.amazon.com/spa/skill/account-linking-status.html?vendorId=XXXXXXXXX';
        String communityURL = URL.getSalesforceBaseUrl().toExternalForm()+communityFolder;
        PageReference page = new PageReference(communityURL+'/services/oauth2/authorize');
        for(String k : ApexPages.currentPage().getParameters().keyset()){
            page.getParameters().put(k, ApexPages.currentPage().getParameters().get(k));
        }
        //appends the "redirect_uri" that amazon's configuration does not send
        //this class should be configured to change depending on the context (1 visualorce page per skill?)
        page.getParameters().put('redirect_uri',alexaOauthCallbackURL);
        return page;
    }
}

Replace the following variables:

  • communityFolder: replace with your community endpoint folder, if any
  • alexaOauthCallbackURL: the redirect URL got from Alexa’s skill configuration

This page will be your entry point for the authorization process.

Remember to enable this page to all the Community’s profiles.

Let’s finalize the configuration

Go again on the Alexa’s skill configuration page and let’s add the remainig configurations:

Where:

  1. https://[community_base_url]/[community_endpoint_folder]/AlexaOAuthStarter
  2. Consumer Key of the Connected App
  3. The domain of the community ([community_base_url])
  4. The redirect URL you have set up in the Connected App
  5. Add a fake URL for the privacy policy

Connect your skill

Your skill is ready to be used.

Go to the Skill section of your https://alexa.amazon.com site:

And click Enable (Chrome blocks the popup, so pay attention to the warning massages):

After you login allow the Connected App:

And you’ll be redirected to the following page (that you can close):

Now Alexa has your access token to make requests to your Community’s API interface (whether it is via SOAP or REST).

Remember: Salesforce sessions don’t last forever and the Amazon Skill documentation states that you are forced to disable and re-enable the skill if you have to refresh the access token.

Now try to say Alexa ask alexaforce for my user and she’ll magically cry out your name!

You have no excuse to develop some awesome app integrating Alexa with Salesforce.

Enjoy!

[Salesforce / Custom Settings] How and Why

This is a beginner’s guide to Custom Settings, that shows a practical way of using all the features they convey.

For TL;DR people, this is the GitHub repository of the example.

One of the greatest pros of the Force.com platform is the extraordinary quick development phase: even when you are under continous software development, you can change your processes implementation within minutes.

With great features come great responsibilities, that’s why you should give administrators, who may not be developers, the same power to configure the code flow.

Before the introduction of custom settings, the only options were:

  • Hard coding constants on your classes
    • Pros: Simple to handle for developers
    • Cons: You need a deploy every time you need to change a value

  • Creating custom objects where storing your configuration (even if only 1 set of data)
    • Pros: High flessibility
    • Cons: You need to do a query every time you need a configuration value (this could be painfull in large and complex implementations)

The introduction of Custom settings made developer lifes easier.

A Custom Setting is an SObject that is available in every execution context (i.e. within apex classes, triggers, visualforce pages, formulas, workflows, …) without the need of query, so basically you always have an instance of an object with all its fields ready to be used.

Let’s do it

Let’s start with a simple Visual Force page that shows, calling a public API, a random quote, *QuoteOfTheDay.page*:

<apex:page controller="QuoteOfTheDayController" action="{!onLoad}" tabStyle="Idea">
    
    <b><apex:outputText value="{!qod}" escape="false" /></b>
     
    <i>{!author}</i>
    
    <hr />
    
    <i>Quote of the day - <b>{!dt}</b></i>
    
    <br/> 
    
    <apex:pageBlock rendered="{!DEBUG_MODE}">
        <apex:pageBlockSection  title="Callout details" columns="1">
        
            <apex:pageBlockSectionItem>
                <apex:outputLabel value="Endpoint" />
                <apex:outputPanel>{!CALLOUT_ENDPOINT}</apex:outputPanel>
            </apex:pageBlockSectionItem>
            
            <apex:pageBlockSectionItem>
                <apex:outputLabel value="Timeout" />
                <apex:outputPanel>{!CALLOUT_TIMEOUT}</apex:outputPanel>
            </apex:pageBlockSectionItem>
            
            <apex:pageBlockSectionItem>
                <apex:outputLabel value="Error Message" />
                <apex:outputPanel>{!errorMessage}</apex:outputPanel>
            </apex:pageBlockSectionItem>
            
        </apex:pageBlockSection>
        
    </apex:pageBlock>
    
</apex:page>

public with sharing class QuoteOfTheDayController {
    
    public String CALLOUT_ENDPOINT{
        get{
            return ConfigurationManager.CALLOUT_ENDPOINT;
        }
    }
    
    public Integer CALLOUT_TIMEOUT{
        get{
            return ConfigurationManager.CALLOUT_TIMEOUT;
        }
    }
    
    public Boolean DEBUG_MODE{
        get{
            return ConfigurationManager.DEBUG_MODE;
        }
    }
    
    public String DATE_FORMAT{
        get{
            return ConfigurationManager.getDateFormat(UserInfo.getLocale());
        }
    }
    
    //quote of the day
    public String qod{get;Set;}
    //quote of the day author
    public String author{get;Set;}
    //date of request
    public String dt{get;set;}
    //debug message
    public String errorMessage{get;Set;}
    
    //loads the Quote of the Day on load
    public void onLoad(){
        
        //date of callout
        DateTime dateOfCall = System.now();
        
        try{
            Http h = new Http();
            HttpRequest request = new HttpRequest();
            request.setEndpoint(CALLOUT_ENDPOINT);
            request.setTimeout(CALLOUT_TIMEOUT);
            request.setMethod('GET');
            HttpResponse response = h.send(request);
            List<String> result = getQuoteFromResponse(response);
            this.qod = result[0];
            this.author = result[1];
        }catch(Exception e){
            this.qod = 'Something bad happened. Please reload';
            this.errorMessage = e.getMessage();
        }
        this.dt = dateOfCall.format(DATE_FORMAT);
    }
    
    /*
    Parse the "Quote Of The Day" API response (details @ http://quotesondesign.com/api-v4-0/)
    @return - List<String> contains 0 => quote, 1 => author 
    */
    public static List<String> getQuoteFromResponse(HttpResponse response){
        String resp = response.getBody();
        List<Object> jsonResponse = (List<Object>)JSON.deserializeUntyped(resp);
        Map<String,Object> quote = (Map<String,Object>)jsonResponse[0];
        String quoteString = (String)quote.get('content');
        String authorString = (String)quote.get('title');
        return new List<String>{quoteString, authorString};
    }
    
    public class CustomException extends Exception{}
}

On page load, the controller:

  1. Stores date/time of request
  2. Creates an HttpRequest setting its endpoint, timeout, method (GET)
  3. Sends the request and parses the response (getting the quote and the author)
  4. Formats the callout’s date depending of current User’s locale

The DEBUG_MODE allow to see some info about the current callout (for debug porpouses).

On the page controller we have no configuration, because they are written on the following utility class:

public class ConfigurationManager{
    
    //global endpoint
    public static String CALLOUT_ENDPOINT{
        get{
            return 'http://quotesondesign.com/wp-json/posts?filter[orderby]=rand';
        }
    }

    //global timeout in ms
    public static Integer CALLOUT_TIMEOUT{
        get{
            return 60000;
        }
    }

    //user/profile centric debug mode
    public static Boolean DEBUG_MODE{
        get{
            return false;
        }
    }

    //returns a specific date format given the locale code
    public static String getDateFormat(String localeCode){
        if(localeCode == 'it_IT'){
            return 'dd/MM/yyyy';
        }
        return 'MM/dd/yyyy';
    }

}

The result is:

Before running the page remember to add the http://quotesondesign.com endpoint among the trusted endpoints for the ORG, by selecting Setup > Security Controls > Remote Siste Settings.

Now let’s open the page:

You can change the hardcoded configurations on the ConfigurationManager class: let’s set the DEBUG_MODE variable to true:

This example clearly shows that it is hard, for non developers, to alter the configurations.

Here come the Custom Settings: they are Custom Objects that can be used without making any SOQL and are fitted to current User’s context.

Let’s create a new Custom Setting with Setup > Custom Settings > New:

The Hierarchic custom setting is a setting that has a global value for its fields which can be overridden on a per User / User’s Profile basis.

This will be more clear later.

A Custom Setting is just like a Custom SObject, so you can add all fields you want (with some exceptions on the types):

To set a value for the fields, click on the Manage button and then on the **Edit** button:

These are global values for the fields and they will be the only values we will be needing for this custom setting.

Set the same values we have on the ConfigurationManager class.

Let’s create another Custom Setting for the DEBUG_MODE flag:

Let’s set a global value with Manage > Edit:

Then click the New button on the lower section of the “Manage” page and add new values for the “System Administrator” profile:

This means that the Debugging custom setting will have a global value and a value fitted for System Administrator users: every administrator that will execute the page will see the debug section.

Finally let’s add the last custom setting for the date formats, by choosing a List type:

With the following fields:

This time, when creating a new value, we will have a set of values (remember the List type) instead of global/user/profile values, each one identified by a Name field:

For each locale code you can set a specific date format plus a Default value (to be handled manually).

Now that we have create all the settings, we need to link them to the code, so let’s change the *ConfigurationManager* class accordingly:

public class ConfigurationManager{
    
    //global endpoint
    public static String CALLOUT_ENDPOINT{
        get{
            return Endpoints__c.getOrgDefaults().Callout_Endpoint__c;
        }
    }

    //global timeout in ms
    public static Integer CALLOUT_TIMEOUT{
        get{
            return Endpoints__c.getOrgDefaults().Callout_Timeout__c.intValue();
        }
    }

    //user/profile centric debug mode
    public static Boolean DEBUG_MODE{
        get{
            return Debugging__c.getInstance().Callout_Debug_Mode__c;
        }
    }

    //returns a specific date format given the locale code
    public static String getDateFormat(String localeCode){
        Date_Formats__c defaultFormat = Date_Formats__c.getInstance('Default');
        Date_Formats__c localeFormat = Date_Formats__c.getInstance(localeCode);
        if(localeFormat != null) return localeFormat.Format__c;
        return defaultFormat.Format__c;
    }

}

From now on if you need to change the endpoint of the service, debug a specific user or change data format according to user locale, you will not need to change the code but you can train an administrator to do it by him self.

There is only some limitation:

In this brief article you have:

  • Created 2 different Hierarchic Custom Settings
  • Created a List Custom Setting
  • Managed all kind of values (global, profile, list)
  • Recalled their values from Apex controller
  • Been an **awesome** developer

Here are some useful resources:

  • GitHub repository of the article
  • Salesforce Docs: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_customsettings.htm?search_text=custom%20settings
  • Custom settings methods: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_methods_system_custom_settings.htm?search_text=custom%20settings
  • Custom settings limits: https://help.salesforce.com/apex/HTViewHelpDoc?id=cs_limits.htm&language=en_US
  • Live example: https://blog-enreeco-community-developer-edition.eu5.force.com/examples/QuoteOfTheDay

[Salesforce / Lightning] Create your own Lightning Progress Bar

This is a repost from the article I wrote for the Salesforce Developer Blog.

Have you ever wanted a progress bar in your linghtning app?
I have and so I came up with a simple bootstrap library wrap up.

Following the successful Lightning series, I’m going to show you how I’ve made a simple Lightning Component out of the awesome Bootstrap library Bootstrap Progressbar (see details in the project’s home page).

For those who are in a TL;DR mode, here is the Github repository with the full code of the component and the demo app.

Let’s start with the component signature:

<c:ProgressBar 
 name="pb1" 
 valueMin="0"
 valueMax="1000"
 value="500"
 displayText="center"
 usePercentage="true"
 barContainerClass="progress-striped active" 
 barClass="progress-bar-info" 
 transitionDelay="10"
 displayText="center"/>

These are the attributes we can set:

  • name: name of the progress bar. This is used to ease events management
  • valueMin: bar minimum value, defaulted to 0
  • valueMax: bar maximum value, defaulted to 100
  • value: starting progress bar value
  • displayText: position of current value’s text (center or fill), blank for no text
  • usePercentage: shows the a % value text or value/max
  • barContainerClass: class of the container of the progress bar: this gives a style (leave blank for no additional effect, “progress-striped” for a “stripped” decoration and “active” to get a move)
  • barClass: this is to be applied to the bar, following the Bootstrap style (progress-bar-info, progress-bar-danger, progress-bar-warning, progress-bar-success, progress-bar-primary)
  • transitionDelay: delay in ms for the animation

This is an example of what you’ll see (simply use the provided app at https://YOUR_INSTANCE.lightning.force.com/c/ProgressBarApp.app):

The component uses 2 kind of events:

  • ProgressBarActionEvt: this is an event used to command the progress bar
  • ProgressBarMessageEvt: this is an event that is used by the progress bar to alert that the MIN/MAX values has been reached

ProgressBarActionEvt

<aura:event type="APPLICATION" description="Inbound event to guide the progress bar" >
    <aura:attribute name="value" type="Integer" default="" 
                    description="Value of the event"/>
    <aura:attribute name="name" required="true" type="String" 
                    description="Progress bar name"/>
 <aura:attribute name="action" required="true" type="String" 
                    description="Progress bar action"/>
</aura:event>

ProgressBarMessageEvt

<aura:event type="APPLICATION" description="Outboud event to guide the progress bar" >
    <aura:attribute name="value" type="Integer" default="" 
                    description="Value of the event"/>
    <aura:attribute name="name" required="true" type="String" 
                    description="Progress bar name"/>
 <aura:attribute name="message" required="true" type="String" 
                    description="Progress bar message"/>
</aura:event>

Both messages are really similar. Both have a name attribute to understand which progress bar should trap / fire the event, and a value attribute that has the value of the event.
The ProgressBarActionEvt has an action parameter that could have the following values:

  • Increment: increment by the value passed
  • Decrement: decrement by the value passed
  • FullFill: fullfills the progress bar
  • Reset: resets progress bar’s value
  • SetValue: sets a specific value

The app calls these actions in its controller’s button functions:

//...
increment10_p1 : function(component, event, helper) {
 helper.sendMessage("Increment","pb1",10);
},
//...

And in its helper:

({
 sendMessage : function(message,name,value) {
  var incrementEvt = $A.get("e.c:ProgressBarActionEvt");
        incrementEvt.setParams({
            name: name,
            value: value,
            action: message
        });
        incrementEvt.fire();
 }
})

On the other side, when we want to handle a message from the progress bar to the container app, we attach a handler to the event:

<aura:application>
    <aura:handler event="c:ProgressBarMessageEvt" 
                  action="{!c.handleProgressBarEvent}" />
 <!-- ... -->
 <div id="{!globalId+'_msg'}" />
//...
handleProgressBarEvent  : function(component, event, helper){
 var originName = event.getParam("name");
 var message =  event.getParam("message");
 document.getElementById(component.getGlobalId()+'_msg')
   .innerHTML = message+" event for progress bar named '"+originName+"' at "+(new Date()).toLocaleString();
},
//...

So every time a ProgressBarMessageEvt event is fired, the apps catches it and write down some of its info into the given DIV HTML component.

Finally let’s see how the component is constructed.
First we need to load jQuery and Bootstrap libraries and the required stylesheets:

<aura:component >
 <ltng:require scripts="/resource/BootstrapSF1/js/jquery-2.1.1.min.js,
             /resource/BootstrapSF1/bootstrap320/js/bootstrap.min.js,
             /resource/BootstrapProgressbar/bootstrap-progressbar.min.js"
        styles="/resource/BootstrapSF1/bootstrap320/css/bootstrap.min.css,
                /resource/BootstrapProgressbar/bootstrap-progressbar.min.css"
     afterScriptsLoaded="{!c.onInit}"/>
 
 <aura:handler event="c:ProgressBarActionEvt" action="{!c.handleEvent}" />
 <aura:registerEvent name="progressBarMsgEvt" type="c:ProgressBarMessageEvt"/>
 
 <!-- ... -->

And then define the place where the Bootstrap Progressbar will be drawn:

<!-- ... -->

<div class="{!'progress '+v.barContainerClass}">
 <div id="{!globalId+'_progbar'}" class="{!'progress-bar '+v.barClass}" 
   role="progressbar" 
   data-transitiongoal="{!v.value}"
   aria-valuemin="{!v.valueMin}"
   aria-valuemax="{!v.valueMax}"></div>
</div>

<!-- ... -->

This component will be fully configured by the “onInit” function of the component’s controller (that is called once all JS/CSS files are loaded):

//...
onInit : function(component, event, helper) {
        $("[id='"+component.getGlobalId()+"_progbar']")
        .progressbar({
            display_text: 'center',
            transition_delay: component.get('v.transitionDelay'),
            refresh_speed: component.get('v.refreshSpeed'),
            display_text: component.get('v.displayText'),
            use_percentage: component.get('v.usePercentage'),
            done : function(cmp){
                $A.run(function(){
                    if(component.get("v.value") >= component.get("v.valueMax")){
                        var evt = $A.get("e.c:ProgressBarMessageEvt");
                        evt.setParams({
                            name: component.get("v.name"),
                            message: "MaximumReached",
                            value: component.get("v.value")
                        });
                        evt.fire();
                    }
                    if(component.get("v.value") <= component.get("v.valueMin")){
                        var evt = $A.get("e.c:ProgressBarMessageEvt");
                        evt.setParams({
                            name: component.get("v.name"),
                            message: "MinimumReached",
                            value: component.get("v.value")
                        });
                        evt.fire();
                    }
                });
            }
        });
    },
//...

The done handler of the progress bar is used to fire the ProgressBarMessageEvt when the bar reaches min o max values.

handleEvent is the controller’s function that handles an incoming action event:

//...
 handleEvent : function(component, event, helper){
        var targetName = event.getParam("name");
        if(targetName !== component.get("v.name")) return;
        var targetIncrement = event.getParam("value");
        var action = event.getParam("action");
        var value = component.get("v.value");
        if(action === 'Decrement') value -= targetIncrement;
        else if(action === 'Increment') value += targetIncrement;
        else if(action === 'FullFill') value = component.get("v.valueMax");
        else if(action === 'Reset') value = component.get("v.valueMin");
        else if(action === 'SetValue') value = targetIncrement;

        var pb = $("[id='"+component.getGlobalId()+"_progbar']");

        if(value = component.get("v.valueMax")){
            value = component.get("v.valueMax");
        }
        component.set("v.value",value);
        pb.attr("data-transitiongoal",value).progressbar();
        
    },
//...

It simply checks progress bar name’s, action, value and executes the corresponding action.

The last command “redraws” the progress var with the new value, and executed the animation.

This is an example of fully javascript components you can make wrapping up existing javascript plugins.

Enjoy this component and may the Force.com be with you!

[Salesforce / Trailhead / Winter16] The Lightning fever: follow the trailhead!

Excited about the latest Lightning Experience announcement?

Me too but unfortunately it was published when I was on vacations without any internet connection, that’s why I read it days after its release, but I just thought “WOW!”.

with great power comes great responsibility, and the guys at Salesforce.com knows it, so they created some cool Trailhead modules to get you started, whether you are a developer, admin or a user.

Learn about why and how you can migrate to the new Lightning Experience, which are the differences and enhancements that has been made.

Watch the new Setup menu in action, saless tools, reporta and dashboards, customize the User interface and plan your rollout strategy: you can even decide which user will be using the new Lightning experience!

These set of modules are useful to understand user management, reports and dashboard creation, app customization (layouts, objects, fields) all with the new Lightning Experience interface.

If you are an experienced Salesforce.com admin you may find some of these modules way too easy, but I bet you’ll still find some new feature you didn’t know/expect!

Developers! That’s what we (most of us?) are!

These modules help you dig into the Lightning Experience as a developer: how can you use the Lightning Components? Is Visual Force dead (don’t worry, the answer is NO)?
The best feature IMHO? The Lightning Design System, finally a unique set of styles and HTML components to build custom applications with a look and feel that is consistent with Salesforce core features — without reverse engineering Salesforce styles!

…Without reverse engineering Salesforce styles!!!!

Finally some cool hints for users and how to use the new Lightning Experience interface (standard objects, list views appearance, dashboards, reports and feeds).

Next step (as usual)?

[Salesforce / Lightning Connect] Lightning Connect Custom Adapters and MongoDB

This is a repost of the article I published on Salesforce Developer Blog.

Summer ’15 Platform Release brought us the awesome Lightning Connect Custom Adapters: you can connect to external data sources and use them as custom Salesforce Objects writing custom Apex code.

After I read Introducing Lightning Connect Custom Adapters by Lawrence McAlpin, I wanted to get my hands dirty and try to implemented my own version of the adapter thinking of a “pseudo-real” use case.

If you are a TL;DR person, here is the code:

  1. Salesforce classes
  2. heroku-crest MondoDB NodeJS proxy

At first the DataSource namespace documentation has been published here, this way you can understand all the objects / interfaces / methods we’ll be using in this example.

In my use case we have:

  • A MongoDB server which stores several invoices
  • A NodeJS server that proxies Salesforce / MongoDB hosted on Heroku
  • A Custom Adapter implemented in Salesforce

The Data Model

An external system writes invoices records on the MongoDB server, storing data on the invoices table, following the model (you can simulate it by POST requesting on the following Heroku REST service):

{
    "_id": "55abc13dad230b03001d7edf",
    "contractid": "C20150001",
    "accountid": "ACC20150001",
    "created": "2013-10-25T20:15:45.851Z",
    "amount": 1000,
    "description": "invoice #1"
}

Where:

  • contractid: is a custom external id for the Contract object
  • accountid: is a custom external id for the Account object
  • _id: MongoDB id
  • created: is the created date (date/time field)
  • amount: is the amount of the invoice (number)
  • description: is a generic description

MongoBD REST interface

The next step is to give MongoDB a REST interface.
I’ve changed an old Project by Ricard Fredin, adding support for:

  • Basic authentication
  • Environment variables for MongoDB host name and port
  • Added support for $oid, $date and $regex BSON types
  • Support for Heroku Button

This is the updated repository with all the infos of the current changes and original project of the new Heroku Crest project.

This project comes with an Heroku Button:

Deploy

This means you can easily deploy this MongoDB proxy on your personal Heroku account:

You need to set your app name, the host name and the server port.

Let’s populate the DB

The last thing to do is to create at least one database on your MongoDB server with one valid user (with at least read permissions) and one table.

In our example we have a “lighthningconnect” database with an “invoices” table.

To see if your Heroku app works, make a get request:

GET https://[YOUR_HEROKU_APP_NAME].herokuapp.com/lighthningconnect/invoices
   Headers: 
   Authorization: BASIC [BASE64(username:password)]

You can also use POST/PUT/DELETE methods.

To populate your instance, make a post request of this kind:

POST https://[YOUR_HEROKU_APP_NAME].herokuapp.com/lighthningconnect/invoices
   Headers: 
     Authorization: BASIC [BASE64(username:password)]
     Content-Type: application/json

BODY:

{
    "contractid": "C20150001",
    "accountid": "ACC20150001",
    "created": {"$date" : "2014-09-15T20:12:42Z"},
    "amount": 1230,
    "description": "invoice #X"
  }

Once the MongoDB REST interface is set up, we are gonna create a new Salesforce Named Credential: this way we can setup access to the REST server (and MongoDB server) with a simple configuration on the interface, every time we make a callout to this endpoint:

The Lightning Custom Adapter implementation

The whole code of the Salesforce implementation can be found here.

The implementation is similar to the one of Lawrence McAlpin’s post.

The main modification involves the handling of the named credential and MongoDB database and table names.

Open the MongoDBDataSourceConnection.cls file and change the first lines:

private static String NC_NAME = 'MongoDB'; //Name of the Named Credential
   private static String DB_ENDPOINT_NC = 'callout:'+NC_NAME+'/lighthningconnect/invoices'; //database and table name in your own database

If you want to change the mappings, you can do it easily by modifying the following map:

private static Map FIELD_MAP = new Map{
         'Id' => '_id',
            'ExternalId' => '_id',
            'DisplayUrl' => '_id',
            'Account' => 'accountid',
            'Contract' => 'contractid',
            'CreatedDate' => 'created',
            'Amount' => 'amount',
            'Description' => 'description'
    };

And the sync() method:

override global List sync() {
        List tables = new List();        
        List columns = new List();
        
        // Always declare these two fields.
        columns.add(DataSource.Column.text('ExternalId', 255));
        columns.add(DataSource.Column.url('DisplayUrl'));
        
        columns.add(DataSource.Column.text('Description', 255));
        columns.add(DataSource.Column.text('Contract', 255));
        columns.add(DataSource.Column.text('Account', 255));
        columns.add(DataSource.Column.number('Amount', 16, 2));
        columns.add(DataSource.Column.get('CreatedDate','Created Date','', true, true, DataSource.DataType.DATETIME_TYPE, 255,0,null,null));
        
        tables.add(DataSource.Table.get('MongoDB_Invoice', 'ExternalId', columns));
        return tables;
    }

Finally we can analize how the query is really done using the REST proxy we just installed on Heroku:

private List<Map> execQuery(string soqlQuery) {
        List<Map> rows = new List<Map>();
        HttpRequest request = new HttpRequest();
        request.setEndpoint(DB_ENDPOINT_NC+soqlQuery);
        request.setHeader('Content-Type','application/json');
        request.setMethod('GET');
        request.setTimeout(60000);
        Http h = new Http();
        HttpResponse response = h.send(request);
        if(response.getStatusCode() != 200){ 
            return new List<Map>();
        }
        
        List result = (List)JSON.deserializeUntyped(response.getBody());
        for(Object obj : result){
            Map jsonObject = (Map)obj;
            Map row = new Map();
            row.put('Amount',jsonObject.get('amount'));
            row.put('Description',jsonObject.get('description'));
            row.put('Contract',jsonObject.get('contractid'));
            row.put('Account',jsonObject.get('accountid'));
            row.put('CreatedDate',parseDateTime(String.valueOf(jsonObject.get('created'))));
            row.put('ExternalId', jsonObject.get('_id'));
            row.put('DisplayUrl', DB_ENDPOINT+'/'+jsonObject.get('_id'));
            rows.add(row);
        }
        return rows;
    }

Basically we do a GET HTTP callout to our endpoint (the Named Credentials will work as a charm to authenticate user) and parse the result to create the object that will be used to present the query result.

The getSoqlQuery() method has been modified from Lawrence’s implementation, in order to adhere to the Heroku Crest application requirements (a JSON input query): we support all basic filters and result ordering.

Configure the Data Source

Once we have uploaded the two  classes, we can configure the External Data Source:

Click on the Validate and Sync button to get the new External Object named MongoDB_Invoice.

Configure the External Object’s lookups

Next step is to link this External Object to actual Account and Contract records referenced by the “contactid” and “accountid” fields on the “invoices” table.

For this reason we will create two new fields on the Account:

and Contract standard objects:

Then we can change the Contract__c and Account__c fields on the MongoDB_Invoice__x external object to be indirect lookups: follow this article but you have to change the field types of both fields type to be Indirect Lookup instead of External Lookup.

This is what you get after changing field types:

This way each record of type MongoDB_Invoice__x that have a “Contact__c” field equal to Contact.MongoDB_Ext_ID__c will be linked to the Contract as if it were a standard lookup field (the same happens for the Account object).

Use the External Object on your app

Next step, create a new custom tab for the MongoDB_Invoice__x object (I’ve configured the “All” list view to show all fields):

You can sort all columns (here the Created Date field):

Click on an External ID value you get all object info:

Where the account:

And its related lists:

And the contract:

With its related lists:

Query External Objects

Finally there is an example of a complex query:

You can certanly improve this implementation and add better query handling, speaking as a pure developer, it’s been a real funny implementation. Can’t wait to see DML statements to be added as well!

Stay tuned for the next platform releases!

[Salesforce / Apex Formula] Help me testing my Apex Formula Algorithm!

a{color:red !important;}

This is a call to arms!

In the past months I have developed an implementation in Apex of Salesforce Formulas in order to have a way to use a formula result dynamically without the need to have a custom field on an object.

It has been a huge work and a lot has still to be done, but I need your help to create a big testbook of formulas, in order to point out bugs, errors, and wathever is bad in this implementation.

This implementation supports (syntax guide here):

  • Base object fields (e.g. MyField__c == 4)
  • Lookup fields (e.g. Relation__r.Account.Name == ‘ACME’)
  • Constants (e.g. $PI, EE, $TODAY, $NOW, $RANDOM
  • Logical operators in function style or inline (e.g. AND(true,false) ; true && false)
  • IF and CASE flow expressions (e.g. IF(true,1,0) )
  • Various String functions (e.g. LEFT(“String”,2) )
  • Various Math functions (e.g. POW(2,5) )
  • Type conversion functions (e.g. DATE(“2015-05-23”) )
  • DateTime functions (e.g. DATETIME(2015,12,01,0,0,0) ; ADDYEARS($TODAY, 10) )
  • Multiline comments
  • Access to Hierarchy Custom Settings (e.g. $Setup.HierarchyCS__c.Value__c)
  • Access to Hierarchy Custom Settings (e.g. $Setup.ListCS__c[“aName”].Value__c)

You can set different return types:

  • BOOLEAN
  • NUMBER
  • STRING
  • DATE
  • DATETIME

For the NUMBER return type you can also set a Scale attribute.

Once released the method call will be something like:

String formula = 'IF(NumberOfEmployees > 15, NumberOfEmployees, NumberOfEmployees/2)';
String objectId = '001000000123456789AAA';
String objectType = 'Account';
String returnType = 'NUMBER';
String scale = 2;
ApexFormulaResult formulaResult = ApexFormulaParser.evaluate(formula, objectId, objectType, returnType, scale);

Here is the Apex Formula Playroom I’ve built with Salesforce Site.

I’ve create a custom object, TestObject__c, which you can select to try the algorithm, which has all kind of Salesforce fields.

This is the raw interface of the playroom:

  1. List of all custom objects you can use (if you need more tell me!). Click on the name to select it.
  2. These are the details (with the objects field API names) of the selected object
  3. You can also test custom settings as well
  4. You can select a built-in formula or you can set a specific Output Type and Scale
  5. Formula’s body and any suggestion you want to give me, and the formula result as well

Every formula you input and calculate is stored in the Site, so please avoid using personal informations that you don’t want to share!

Feel free to break down my code, I expect a lot of work in error handling, but the awesome Developer Salesforce Community< is here to help, isn’t it?

And remember:

[Salesforce / Apex] Can I find out if the current user has access to a record?

Yes you can!

And this is quite simple!
Query the UserRecordAccess object to have instant access to the following informations about a record and an user:

  • HasDeleteAccess: user has delete access on the record
  • HasEditAccess: user has edit access on the record
  • HasTransferAccess: user has transfer access on the record
  • HasReadAccess: user has read access on the record
  • MaxAccessLevel: picklist that tells the maximum access level on the record (None, Read, Edit, Delete, Transfer, All)
  • HasAllAccess: user can share the record

Here is a simple query (on a Case object):

List<UserRecordAccess> accessToRecord = [SELECT RecordId, MaxAccessLevel FROM UserRecordAccess WHERE UserId = :UserInfo.getUserId()  AND RecordId = '500000000000001AAA'];

On API 29.0 and earlier we have few limitations on SOQL:

  • When the running user is querying a user’s access to a set of records, records that the running user does not have read access to are filtered out of the results.
  • When filtering by UserId and RecordId only, you must use SELECT RecordId and optionally one or more of the access level fields:HasReadAccess, HasEditAccess, HasDeleteAccess, HasTransferAccess, and HasAllAccess. You may include MaxAccessLevel.
  • When filtering by UserId, RecordId, and an access level field, you must use SELECT RecordId only.

Quite simple, it has been added since plaftorm version 24.0 but I discovered it few days ago!

Page 16 of 20

Powered by WordPress & Theme by Anders Norén