When Salesforce is life!

Author: Enrico Murru Page 13 of 20

[Salesforce] The Sobject Crusade: ApexClass

Source: ApexClass

The ApexClass is the most beloved object for Salesforce Developers.

It identifies a specific Apex Class, so you can query for Classes runtime.

Note that, even if the describe states that the ApexClass is creatable and updatable, an exception is thrown if you try to insert/update via API a class: use the tooling API or metadata API instead.

Among the fields, you can query for the Body of the class, whether it is valid or not, size in byte without comments.

Here an example:

Select Id, Name, ApiVersion, Body, IsValid, LengthWithoutComments From ApexClass ORDER BY Name

[Salesforce] The Sobject Crusade: Announcement

Source: Announcement

This object represents a Chatter Group Announcement.

To create a new announcement, go to the Chatter tab, select (or create) a group:

Click on More > Announcement and select the body of the announcement and an expiration date:

A section will compare on the Group’s sidebar:

The Announcement object is a detail of the FeedItem object that specifies the Expiration Date of the feed. Given the following query:

select FeedItem.Body, ExpirationDate, Parent.Name from Announcement

To create the object programmatically follow these rules:

//query the Collaboration Grouop
CollaborationGroup group = [Select Id From CollaborationGroup Where Name = 'Awesome Group'];

//creates a feed item; type "AdvancedTextPost" is mandatory
FeedItem item = new FeedItem(Type='AdvancedTextPost', ParentId=group.Id, Body='This is an awesome announcement');
insert item;

//create the announcement
Announcement anc = new announcement(FeedItemId=item.Id, ExpirationDate=System.today().addDays(10));
insert anc;

//upate the group
group.AnnouncementId=anc.Id
update group;

[Salesforce / Lightning Connect] Lightning Connect Custom Adapters and MongoDB (DML support) pt.2

Do you remember the Lightning Connect Custom Adapters and MongoDB post?

Things have changed after that post and finally the platform supports all DML statements: insert, update and delete!

To make the magic work I had to make:

Follow the post instructions to setup up your Heroku app.

The core changes are the support for the methods.

Open the MongoDBDataSrouceProvider.cls class:

override global List getCapabilities() {
 List capabilities = new List();
 capabilities.add(DataSource.Capability.ROW_QUERY);
 capabilities.add(DataSource.Capability.SEARCH);
 //new capabilities added
 capabilities.add(DataSource.Capability.ROW_CREATE);
 capabilities.add(DataSource.Capability.ROW_UPDATE);
 capabilities.add(DataSource.Capability.ROW_DELETE);

 return capabilities;
}

The provider has been added with more capabilities, CREATE, UPDATE and DELETE.

Let’s open the MongoDBDataSourceConnectio.cls class and look at the 2 new methods:

global override List<DataSource.UpsertResult> upsertRows(DataSource.UpsertContext context) {
    List<DataSource.UpsertResult> results = new List<DataSource.UpsertResult>();
    List<Map<String, Object>> rows = context.rows;
    Http h = new Http();
    
    for(Integer i = 0; i < rows.size(); i++){
        Map<String,Object> row = rows[i];
        HttpRequest request = new HttpRequest();
        request.setHeader('Content-Type','application/json');
        request.setTimeout(60000);
        Map<String,Object> invoice = new Map<String,Object>();
        if(String.isBlank((String)row.get('ExternalId'))){
            request.setMethod('POST');
            request.setEndpoint(DB_ENDPOINT_NC);
        }else{
            request.setMethod('PUT');
            request.setEndpoint(DB_ENDPOINT_NC+'/'+row.get('ExternalId'));
        }
        
        invoice.put('accountid', row.get('Account'));
        invoice.put('contractid', row.get('Contract'));
        invoice.put('created', row.get('CreatedDate'));
        invoice.put('amount', row.get('Amount'));
        invoice.put('description', row.get('Description'));
        
        request.setBody(JSON.serialize(invoice));
        
        HttpResponse response = h.send(request);
        
        List<Object> mList = (List<Object>)JSON.deserializeUntyped(response.getBody());
        Map<String, Object> m = (Map<String, Object>)mList[0];
        if (response.getStatusCode() == 200){
            String objId = String.valueOf(m.get('_id'));
            if(String.isBlank(objId)){
                objId = String.valueOf(row.get('ExternalId'));
            }
            results.add(DataSource.UpsertResult.success(objId));
        } 
        else {
            results.add(DataSource.UpsertResult.failure(
                String.valueOf(row.get('ExternalId')), 'The callout resulted in an error: ' + response.getStatusCode()+' - '+response.getBody()));
        }
    }
    return results;
}

global override List<DataSource.DeleteResult> deleteRows(DataSource.DeleteContext context) {
    List<DataSource.DeleteResult> results = new List<DataSource.DeleteResult>();
    Http h = new Http();
    
    for (String externalId : context.externalIds){
        HttpRequest request = new HttpRequest();
        request.setHeader('Content-Type','application/json');
        request.setTimeout(60000);

        request.setMethod('DELETE');
        request.setEndpoint(DB_ENDPOINT_NC+'/'+externalId);
        
        HttpResponse response = h.send(request);
        if (response.getStatusCode() == 200
            || response.getStatusCode() == 201){
            results.add(DataSource.DeleteResult.success(String.valueOf(externalId)));
        } 
        else {
            results.add(DataSource.DeleteResult.failure(
                String.valueOf(externalId), 'The callout resulted in an error: ' + response.getStatusCode()+' - '+response.getBody()));
        }
    }
   return results;
}

WARNING: this code is not optimized for bulk upsert/delete because it makes a callout for every record.

It’s a proove of concept, so I challenge you to bulkify the class!

How can you insert an external object provided by a Lightning Connect adapter?

The Database class has been provided with new methods:

  • deleteAsync
  • insertAsync
  • updateAsync

These methods are used to make the calls to the remote system and actually do the work!

Database.insertAsync(new List<MongoDB_Invoice__x>{
    new MongoDB_Invoice__x(Amount__c=1, Description__c ='Async Test 1'),
    new MongoDB_Invoice__x(Amount__c=2, Description__c ='Async Test 2')
});
Database.deleteAsync([Select Id From MongoDB_Invoice__x Where Description__c = 'Async Test 1']);

Every method has an alternative method that provides a callback class, which allows to make further actions after the records are upserted/deleted.

For instance, the asyncUpdate has an optional second parameter of type Database.AsyncSaveCallback that can be created to execute some logic after a specific record is done (the class is called every time a record is updated).

Every asyncDML method returns a List of Database.DeleteResult or Database.SaveResult that contains a link to the asynchrounous operation that can be retrieved by calling the Database.getAsyncLocator(result) method and passing the value to the Database.getAsyncSaveResult(asyncLocator) or Database.getAsyncDeleteResult(asyncLocator): this way you can get the status of the asynchronous operation.

[Salesforce] The Sobject Crusade: AgentWork

Back to the Sobject Crusade list.

Source: AgentWork

This object represents work item assignment that has been routed to an Agent: this is basically a new type of queue management introduced with Summer 15 called Omni-Channel.

Omni-Channel is a comprehensive customer service solution that lets your call center route any type of incoming work item, including cases, chats, phone calls, or leads—to the most qualified, available agents in your organization.
Omni-Channel integrates seamlessly into the Salesforce console.

This release contains a beta version of Omni-Channel that is production quality but has known limitations (Summer 15).

To enable Omni-Channel go to Customize > Omni-Channel > Settings and flag Enable Omni-Channel.

Now let’s create a Service Channel: this is the source of work items for the Omni-Channel, and can be associated to various standard objects and even custom object.

These objects are taken from queues and sent to the agent’s console in real time.

To create a new Service Channel, click on Customize > Omni-Channel > Service Channels:

The Custom Console Footer Component field is optionally used to open a specific component on the Console when that specific work item is selected.

Next step is creating Routing Configurations, which determine how work items are routed to agents; click on Customize > Omni-Channel > Routing Configurations:

Where:

  • Routing Priority: is the priority of the current Service Channel. Other factors are counted when calculating the priority (available capacity of the agent, priority of the queue that the work item came from, amount of time that the work item has been waiting in the queue, members of the queue who are available to receive new work items from the queue). When an agent gets a work item, the owner of the object is set to the agent.
  • Routing Model: reflects the agent’s behavior
  • Capacity Weight: amount of agent’s capacity (set in the Capacity field of the presence configuration) scaled down when the work item is assigned. You can select a Capacity Weight or a Capacity Percentage, but not both.
  • Capacity Percentage: percent of agent’s capacity scaled down when the work item is assigned

Now you can link your ORG queues to the Routing Configurations.

Click on Setup > Manage Users > Queues:

In the Routing Configuration set the configuration you have set up in the previous step.

Next step is to configure Agent’s presence statuses: this way we can set up how much work every agent can handle.

When you enable the Omni-Channel feature Salesforce adds a Default Presence configuration to be applied to all Agent’s by default.

Go to Customize > Omni-Channel > Presence Configurations and click the New:

You can set:

  • Capacity: maximum Agent’s capacity
  • Automatically Accept Requests: automatically assigns a work item to an available agent
  • Allow Agents to Decline Requests: Agents can decline assignments; if “Automatically Accept Requests” is set to true, agents cannot decline
  • Update Status on Decline: update status when declining, only available if “Allow Agents to Decline Requests” is flagged

Finally one or more channels can be associated with different Presence Statuses, on Setup > Customise > Omni-Channel > Presence Statuses:

To assign Presence Statuses to users go to the User’s Profile on the Enabled Service Presence Status Access and click edit:

This could be done as well using Permission Sets. Take a Case and set the Owner to be the previously created queue (so we can test it). Now configure the Cloud Console App (Setup > Apps > [YOUR_CONSOLE_APP_NAME]) to use the Omni-Channel component:

Now open the Console App (Developer Orgs come with a Sample Console app):

Go online by clicking on the presence status link to see the magic start:

You can change the object’s compact layout (for Case go to Setup > Cases > Compact Layout) to show other informations of the object:

You can integrate with an already configured Live Agent. For more details read the official docs. Click “Accept” and the new AgentWork will be stored in the CRM:

select id,lastmodifieddate,CapacityPercentage,CapacityWeight,Name,OriginalQueue.Name,status,WorkItemId,ServiceChannelId,UserId from agentwork order by lastmodifieddate DESC

It shows all the Work Items assigned to the agents (this is basically the history of plyaing with a Case and looping over assigning to a queue and accepting/declining with the current User as Agent).

[Salesforce] The Sobject Crusade: AllowedEmailDomain

Back to the Sobject Crusade list.

Source: AllowedEmailDomain

To enable this object you need to contact Salesforce.com support: I had no way to enable it quickly so I’m just gonna post what this feature means.

The AllowedEmailDomain object is used to store the allowed email domains for your users.

To configure it go to Setup > Manage Users > : you can add domains (e.g. google.com) or even subdomains (e.g. app.enree.co): from now on every new or updated user’s Email field must match the domain whitelist configure in this section.

Existing users’ emails will still be valid till their update.

This restriction doesn’t apply to external users (portal, Communities or Chatter, or simply every user that has a related Contact/Account).

More info here.

[Salesforce] The Sobject Crusade: AdditionalNumber

Back to the Sobject Crusade list.

Source: AdditionalNumber

This is an optional additional number for a call center, visible on the call center’s phone directory: this is useful for numbers not releated to CRM objects (e.g. queues, conference rooms).

This seems boring, but continue reading to see some more action!

To create a new number, go to Setup > Customize > Call Center > Directory Numbers and click the New button:

The object can be optionally linked to a specific call center: if not set, this will be considered a global value (cross-call center valid).

Upon save you can query this object:

select Id, Name, Phone, Description, CallCenterId from AdditionalNumber

This is awesome, right?

Not at all!

Let’s see how this is included in the overall Call Center configuration.

The Call Center configuration involves the configuration of a Computer Telephony Intgration, aka CTI.

As you can see in the picture above, CTI is an external service that communities with Salesforce: it is responsibile for handling phone (and other means, such as SMS or chat) communications and sending back to the CRM information about the call (e.g. calling number, duration of the call, origin, …).

Here you can find the latest complete guide on CTI configuration.

Salesforce.com fortunately give developers a way to try this feature out without having to have access to real CTI hardware.

First download the CTI Demo Adapter from this link.

Once installed, you’ll see the icon in the lower right corner of your desktop (sorry, MS Windows systems only); take the [installed dir]/DemoAdapter.xml file.

Now click on Setup > Customize > Call Center > Call Centers and click the Import button and import the previous XML file.

Click on Add user button to add new users (your current user at least).

Go to the Home page, you’ll see a new component on the sidebar:

Wait till you see a new popup appearing (if it takes longer that a few seconds, click the button):

If you can’t still see anything, it may be because of this kind of error while loading the CTI “localhost” service (see the CTI Adapter URL field on the Call Center setup page):

This happens because you are on an HTTPs site and trying to load an HTTP (less secure) content inside an iframe (don’t worry it’s not your fault!).

Click on the little shield on the addess bar (I’m using Chrome) and click on “Load Anyway” link on the popup that will appear (and reload the page).

Insert your Salesforce username/password to connect to the CTI:

Now the fake CTI adapter can trigger the following actions (right click on the adapter icon):

All this configuration to show where the AdditionalNumber object lays.

Click on the search button next to the textbox for dialing a number:

Select the number and watch it on the dial box:

Just for fun, let’s take an Account and assign it the “4155551212” phone number:

This means that when the customer calls from this number, the CTI adapter will give Salesforce the calling phone number and redirects the user to the correct Account page. Let’s click on the “Call From 415-555-1212” CTI Adapter action:

To watch Salesforce loading automagically the given Account page:

The sidebar shows additional infos for the Call Log, you can update as needed: this call will be saved in a Task object:

You can customize the Demo Adapter by changing the “demo_menu.xml” file (see more datails in the guide).

[Salesforce] The Sobject Crusade: ActivityHistory

Back to the Sobject Crusade list.

Source: ActivityHistory

The ActivityHistory object is a read-only object that shows all completed Tasks and past Events related to a give object (e.g. Account). It includes activities for all contacts related to the object.

This object is shown in the Activity History related list:

Few limitations apply:

  • The object cannot be queried directly but only queried through inner query (e.g. Select Id, (Select Subject From ActivityHistory) From Account Where Name = ‘ACME bros’)
  • Your main query must reference exactly 1 record
  • Inner query must not have a WHERE clause
  • Inner query must filter a maximum of 500 items

Here is an example query:

SELECT Name, (SELECT ActivityDate, Subject, IsTask FROM ActivityHistories ORDER BY ActivityDate ASC NULLS LAST Limit 500) FROM Account WHERE Name =’ACME Bros’

[Salesforce] The Sobject Crusade: ActionLinkTemplate

Back to the Sobject Crusade list.

Source: ActionLinkTemplate

Action Links has been presented when discussing the ActionLinkGroupTemplate object (see this object for a complete guide on configuring and using Action Links): they are a way to execute custom actions on button click on Chatter feed posts.

By templating an Action Link you define a template action which can be “instantiated”.

The Action Link is defined by:

  • LinkType: this is the kind of action that you want to expose, and it can be:
    • Api: direct call to an API endpoint (with given method, parameters, headsrs)
    • Api Async: like the previous point but the server response is asyncrhonous: this way the serve have to send a response to /connect/action-links/[actionLinkId] endpoint to set the SuccessfulStatus o FailedStatus of the operation
    • Download: triggers a download of a file
    • Ui: redirects to the given URL
  • ActionUrl: endpoint of the action. This could have bindings values, e.g. “https://myapi.com/id={!Bindings.customValue}&userId={!userId}”
  • Headers: headers request values (only for Api and ApiAsync), binding supported. Every header value should be in the form (one per line) “Header-key: value”
  • Method: HTTP method for the request, with the following supported behavior:
    • HttpDelete: HTTP 204 on success, response body is empty
    • HttpGet: HTTP 200 on success
    • HttpHead: HTTP 200 on success, response body is empty
    • HttpPatch: HTTP 200 on success or HTTP 204 if response body is empty
    • HttpPost: HTTP 201 on success or HTTP 204 if response body is empty
    • HttpPut: HTTP 200 on success or HTTP 204 if response body is empty

    “Ui” and “Download” types support only “HttpGet” value.

  • RequestBody: bpdy of the request (only for Api and ApiAsync), binding supported
  • LabelKey: standard set of labels (None if none apply, then use the Label field)
  • Label: custom label associated to the action (Pending, Success and Failed applied to the action action)
  • Position: position of the action in the defined group (0 is the first)
  • IsConfirmationRequired: users is prompted with a confirmation alert box upon click on the action
  • IsGroupDefault: default group action
  • UserVisibility: states who can see the action, that is Creator, Everyone, EveryoneButCreator, Manager (manager of the creator on the Action Link), CustomUser or CustomExcludedUser (using the UserAlias field to link to a specific user)
  • UserAlias: user alias to set the visibility of the action. Supports binding
  • LinkType:
  • LinkType:

You can query this object:

Select Id, ActionLinkGroupTemplate.DeveloperName, LinkType, ActionUrl, Headers, IsConfirmationRequired, Label, Method, RequestBody From ActionLinkTemplate

And get:

[Salesforce] The Sobject Crusade: ActionLinkGroupTemplate

Back to the Sobject Crusade list.

Source: ActionLinkGroupTemplate

Action Links are actions you can do in chatter posts (e.g. API call, document download, UI redirect) by clicking a button on the related chatter post.

Action Links are grouped into Action Link Group, so you can choose which action you want to play:

You may think that these actions comes to configuration and some sort of layout, but it is part configuration and part Chatter API call.

Action Links are grouped into Action Link Groups but this is not the object that will be configured.

In fact we talk about Templates (Action Link Group Template and Action Link Template): we define template actions rather than the actual actions we’ll be executing.

The template defines the actions you want to apply to a Chatter feed: when you want to apply the group of actions to a feed, we’ll make an API call to get the “instance” of the actions to be linked to the feed.

Let’s start creating a Group Template (the object we are inspecting here) clicking on Setup > Create > Action Link Templates:

You can define:

  • Name and DeveloperName
  • Category (position of the action buttons in the feed)
  • Number of execution allowed
  • Hours untile the action expires

Once a group is published it cannot be modified anymore.

Let’s query the object:

select id, DeveloperName, IsPublished, MasterLabel  from ActionLinkGroupTemplate

Let’s define the actions:

With the following main fields (more details here):

  • Type of Action (API, API Async, UI, Download)
  • Endpoint of the action (in this example a simple request bin)
  • HTTP Method
  • HTTP Request Body
  • HTTP headers
  • Position among other buttons
  • Label (you can choose between some presets and a custom label)
  • Visibility

Consider that URL, request body and headers can have standard and custom binding values (you can for example pass the current User ID or a custom binding value).

These are the actions defined:

To use them we need a valid Session ID, a call to the /connect/action-link-group-definitions/ API endpoint and a call to Chatter API to send a feed.

We’ll be doing things manually (we could use Workbench, but we like to get our hands dirty!).

First we get a valid OAuth Session ID.

Click on Setuo > Create > Apps click on New button in the Connected Apps section; we only need to setup the middle section:

to get access to Chatter API.

This is what you get:

This application will be online in 10 minutes at most.

Let’s make a REST call to get a valid token using the OAuth Password Flow (details here ):

POST https://login.salesforce.com/services/oauth2/token
    Headers:
        Content-Type: application/x-www-form-urlencoded
    Body:
        grant_type=password&client_id=[APP_CONSUMER_KEY]&client_secret=[APP_CONSUMER_SECRET]&username=[USERNAME]&password=[PASSWORD+TOKEN]

The response returns the Session ID (athorization token) we’ll use to get a valid Action Group Id from this REST call:

POST https://xxx.salesforce.com/services/data/v34.0/connect/action-link-group-definitions/
    Headers:
        Authorization: Bearer [SESSION_ID]
        Content-Type: application/json
    Body:
        {"templateId":"07g24000000CaTc","templateBindings":[]}

Where templateId is the ID of the Action Link Group Template (you can query for it or get it in the configuration section of the template), and templateBindings is a list of objects (with “key” and “value” fields) for custom bindings.

This call will return an instance of the template:

{
  "actionLinks": [
    {
      "actionUrl": "https://requestb.in/11jyqzq1",
      "createdDate": "2015-08-14T20:41:42.566Z",
      "excludedUserId": null,
      "groupDefault": false,
      "headers": [
        {
          "name": "content-type",
          "value": "application/json"
        }
      ],
      "id": "0An240000004FSDCA2",
      "label": "Salute!",
      "labelKey": "None",
      "method": "HttpPost",
      "modifiedDate": "2015-08-14T20:41:42.566Z",
      "requestBody": "{"message": "Hello!", "from":"{!userId}"}",
      "requiresConfirmation": false,
      "templateId": "07l24000000GmbgAAC",
      "type": "Api",
      "userId": null
    },
    {
      "actionUrl": "https://requestb.in/11jyqzq1",
      "createdDate": "2015-08-14T20:41:42.566Z",
      "excludedUserId": null,
      "groupDefault": false,
      "headers": [
        {
          "name": "Content-Type",
          "value": "application/json"
        }
      ],
      "id": "0An240000004FSECA2",
      "label": "Insult!",
      "labelKey": "None",
      "method": "HttpPost",
      "modifiedDate": "2015-08-14T20:41:42.566Z",
      "requestBody": "{"message":"Bad Word", "from":"{!userId}"}",
      "requiresConfirmation": true,
      "templateId": "07l24000000GmblAAC",
      "type": "Api",
      "userId": null
    }
  ],
  "category": "Primary",
  "createdDate": "2015-08-14T20:41:42.566Z",
  "executionsAllowed": "Once",
  "expirationDate": null,
  "id": "0Ag240000004FSNCA2",
  "modifiedDate": "2015-08-14T20:41:42.566Z",
  "templateId": "07g24000000CaThAAK",
  "url": "/services/data/v34.0/connect/action-link-group-definitions/0Ag240000004FSNCA2"
}

Now we can use the id to post the action with a feed:

POST https://xxx.salesforce.com/services/data/v34.0/chatter/feed-elements
    Headers:
        Authorization: Bearer [SESSION_ID]
        Content-Type: application/json
    Body:
        {
          "body": {
            "messageSegments": [
              {
                "type": "Text",
                "text": "What do you want to say me?"
               }
            ]
            },
          "subjectId": "me",
          "feedElementType": "FeedItem",
          "capabilities": {
            "associatedActions": {
              "actionLinkGroupIds": ["0AgRR0000004CTr0AM"]
            }
          }
        }

Once you click on a button, the action is executed and no more action is possible:

The same sequence can be done using Apex and the ConnectApi library:

// Get the action link group template Id.
ActionLinkGroupTemplate template = [SELECT Id FROM ActionLinkGroupTemplate WHERE DeveloperName='Say_Hello'];


// Create ActionLinkTemplateBindingInput objects from the map elements.
List bindingInputs = new List();
/*
// Add binding name-value pairs to a map: our action does not have custom bindings
Map bindingMap = new Map();
bindingMap.put('aBinding','aValue');
for (String key : bindingMap.keySet()) {
    ConnectApi.ActionLinkTemplateBindingInput bindingInput = new ConnectApi.ActionLinkTemplateBindingInput();
    bindingInput.key = key;
    bindingInput.value = bindingMap.get(key);
    bindingInputs.add(bindingInput);
}
*/

// Set the template Id and template binding values in the action link group definition.
ConnectApi.ActionLinkGroupDefinitionInput actionLinkGroupDefinitionInput = new ConnectApi.ActionLinkGroupDefinitionInput();
actionLinkGroupDefinitionInput.templateId = template.id;
actionLinkGroupDefinitionInput.templateBindings = bindingInputs;

// Instantiate the action link group definition.
ConnectApi.ActionLinkGroupDefinition actionLinkGroupDefinition = 
ConnectApi.ActionLinks.createActionLinkGroupDefinition(Network.getNetworkId(), actionLinkGroupDefinitionInput);

This is what the bin has received:

Imagine you can use this feature for allowing selected users to partecipate to surveys/polls or make them doing actions to monitor their behavior.

Page 13 of 20

Powered by WordPress & Theme by Anders Norén