tag:blogger.com,1999:blog-60376100345600473352024-03-12T23:16:09.135-04:00Carving in the CloudsInsights and lessons learned from designing and implementing cloud solutions on the Salesforce1 Platform. Side adventures into Heroku with Ruby on Rails apps also included.Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comBlogger86125tag:blogger.com,1999:blog-6037610034560047335.post-34550329377044114092017-06-01T19:03:00.002-04:002017-06-01T19:03:50.073-04:00Trigger + Process + Workflow = Recursion Hell?As your Salesforce org matures, chances are you'll find yourself trying to untangle Apex triggers, processes created with Process Builder, and workflow rules. Specifically when field updates are involved, predicting the outcome from <a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_triggers_order_of_execution.htm" target="_blank">the order of operations</a> can be a pita, especially because the documentation still leaves room for questions in cases involving recursion.<br />
<br />
Follow the two rules below for a reduced-pain implementation.<br />
<ul>
<li><b><a href="https://developer.salesforce.com/page/Trigger_Frameworks_and_Apex_Trigger_Best_Practices" target="_blank">One trigger per object</a></b></li>
<li><b>No <a href="https://help.salesforce.com/articleView?id=vpm_admin_flow_type.htm&language=en_US&type=0" target="_blank">autolaunched flows</a></b>. Use Process Builder, but avoid invoking <a href="https://help.salesforce.com/articleView?id=vpm_admin_flow_type.htm&language=en_US&type=0" target="_blank">autolaunched flows</a>. If you've already got a trigger involved, whatever you need to do in an autolaunched flow can be handled in Apex, with the added benefit of Apex tests to cover yourself.</li>
</ul>
<br />
If you're updating fields on a in Process Builder, and you've marked the "Recursion" checkbox, know that every time the record is updated by the process, <span style="font-family: "Courier New",Courier,monospace;">before</span> and <span style="font-family: "Courier New",Courier,monospace;">after</span> trigger events <i>will</i> fire again. This is also true for updates made by a process invoked as an action by another process.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgG9Il99iPXE6H8vonDKuDZdjQyl-YHx-CTr5wJzJs-JMj2_O1y28Qlru9HUj6l2eJiKRJ6h9P8m2y4oHdSoikLqc3nliQ-pH4Pn2UO_6AvZWv57yCOKHWDBc_rJM1N-KsxgebFmpFIfjZ4/s1600/Screen+Shot+2017-06-01+at+6.45.58+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="526" data-original-width="1600" height="131" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgG9Il99iPXE6H8vonDKuDZdjQyl-YHx-CTr5wJzJs-JMj2_O1y28Qlru9HUj6l2eJiKRJ6h9P8m2y4oHdSoikLqc3nliQ-pH4Pn2UO_6AvZWv57yCOKHWDBc_rJM1N-KsxgebFmpFIfjZ4/s400/Screen+Shot+2017-06-01+at+6.45.58+PM.png" width="400" /></a></div>
<br />
So all in all, remember that <a href="https://github.com/martyychang/sf-trigger-workflow/blob/process-builder/src/classes/ApexTruthTrackTriggerWorkflowTest.cls" target="_blank">a single Apex trigger could run 14 (fourteen) times for a single DML operation</a>! If you're mixing triggers with processes
and workflow rules, make very sure your business logic in triggers will
survive recursion.Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-79856513205469483532017-05-01T23:18:00.004-04:002017-05-01T23:18:52.150-04:00getValue() getter method vs. { get; } shorthandSalesforce's <a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_properties.htm" target="_blank"><span style="font-family: "courier new" , "courier" , monospace;">{ get; set; }</span> syntax</a> has been around for a long time and is a time-tested, reliable way to define properties in Apex. But after <a href="https://github.com/martyychang/sf-get-set-demo" target="_blank">testing its usability and limitations</a> in Spring '17, I've decided that explicitly declaring traditional getter and setter methods should be preferred over using the convenient <span style="font-family: "courier new" , "courier" , monospace;">{ get; set; }</span> syntax.<br />
<br />
The primary reason is that the only way to expose a property in a custom Apex class for use with Lightning Components is to use the <span style="font-family: "Courier New",Courier,monospace;"><a href="https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/ref_attr_types_apex.htm" target="_blank">@AuraEnabled</a></span><a href="https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/ref_attr_types_apex.htm" target="_blank"> annotation</a>, and this annotation only works on a traditional getter method such as <span style="font-family: "Courier New",Courier,monospace;">String getName()</span>.<br />
<br />
The secondary reason is that the developer also has the option to either call the getter or access the private field directly from other methods in the class, which is not possible when using <span style="font-family: "Courier New",Courier,monospace;">{ get; set; }</span>.Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-66518677094335573682016-12-14T11:19:00.001-05:002016-12-14T11:19:38.152-05:00caffe.io.load_image Quick FactsQuick facts on the <span style="font-family: Courier New, Courier, monospace;">numpy.ndarray</span> object returned by <span style="font-family: Courier New, Courier, monospace;">caffe.io.load_image</span>.<br />
<br />
<ul>
<li>The array's shape is (<i><b>height</b></i>, <i><b>width</b></i>, 3)</li>
<li>The last shape value of 3 represents three color channels, in <b>RGB</b> order. This is important because OpenCV's <a href="http://docs.opencv.org/2.4/modules/highgui/doc/reading_and_writing_images_and_video.html#Mat imread(const string& filename, int flags)" target="_blank"><span style="font-family: Courier New, Courier, monospace;">imread</span></a> function gives channels in <i>BGR</i> order.</li>
<li>The array has <span style="font-family: Courier New, Courier, monospace;">dtype=float32</span> with values in range 0.0-1.0. Again, this is important because OpenCV's <span style="font-family: Courier New, Courier, monospace;"><a href="http://docs.opencv.org/2.4/modules/highgui/doc/reading_and_writing_images_and_video.html#Mat imread(const string& filename, int flags)" target="_blank">imread</a></span> function gives an array with <span style="font-family: Courier New, Courier, monospace;">dtype=uint8</span> with values in range 0-255.</li>
</ul>
<div>
<br /></div>
<div>
I'm publishing this so I don't have to re-learn this "truth" every time I'm dealing with image loading and conversions.</div>
Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-11607741457538203982016-12-06T10:16:00.000-05:002016-12-06T10:16:11.604-05:00What Code Belongs in an MVC ControllerThe purpose of a controller is to act as a conduit between each user interaction and system response. Typically this involves three steps:<br />
<br />
<ul>
<li><span style="font-family: Courier New, Courier, monospace;">readRequest()</span>. For a web server this means reading the inbound HTTP request, analyzing the headers, taking care of authorization.</li>
<li><span style="font-family: Courier New, Courier, monospace;">doSomething()</span>. Now that the server knows what it's being asked to do, the server can go ahead and do something useful.</li>
<li><span style="font-family: Courier New, Courier, monospace;">writeResponse()</span>. After the server has finished its job or kicked off a long-running process, it should write a response back to the user to let the user know how things went.</li>
</ul>
<div>
<br /></div>
<div>
In a different sense, a controller's action method is just a wrapper for a <i>function</i> that executes actual business logic, a wrapper that translates an HTTP request into function args.</div>
<div>
<br /></div>
<div>
This setup makes sense to me, but what other approaches are there to writing good controllers? Please share your comments.</div>
Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-51679737181318200922016-11-09T13:52:00.001-05:002016-11-09T13:57:00.156-05:00Install Anaconda 2 to /opt/anaconda2By default, Anaconda 4.2 for Python 2 will install itself to the user's home directory on Linux. This is great for local development, but for server-side deployment and testing it's better to install to a shared location.<br />
<br />
The <a href="https://docs.continuum.io/anaconda/install" target="_blank">install docs</a> are pretty vague about how to set this up, saying simply, "Install Anaconda as a user unless root privileges are required." The way I've made this work easily on an Amazon EC2 running Ubuntu 16.04 LTS is as follows.<br />
<br />
<ol>
<li>Download the <a href="https://www.continuum.io/downloads#linux" target="_blank">appropriate installer</a></li>
<li>Install as a superuser with <span style="font-family: Courier New, Courier, monospace;"><b>sudo</b> bash Anaconda2-4.2.0-Linux-x86_64.sh</span></li>
<li>Install to <b>/opt/anaconda2</b> and prepend the install location to PATH in <b>~/.bashrc</b></li>
<li>Change the target directory's group ownership to <b>ubuntu</b> and grant <b>g+w</b> permission for the directory and all its subdirectories</li>
</ol>
<div>
<br /></div>
<div>
In short, something like this will work beautifully, allowing packages to still be installed simply using <span style="font-family: Courier New, Courier, monospace;">conda install</span> or <span style="font-family: Courier New, Courier, monospace;">pip install</span>.</div>
<div>
<br /></div>
<script type="syntaxhighlighter" class="brush: bash"><![CDATA[
# Download the installer
wget https://repo.continuum.io/archive/Anaconda2-4.2.0-Linux-x86_64.sh
# Install to /opt/anaconda2, and
# prepend the installation location to PATH
bash Anaconda2-4.2.0-Linux-x86_64.sh
# Change the installation location's group ownership
# and grant the group write permission to the directory.
# The -r flag for recursive processing is very important here!
sudo chown -R :ubuntu /opt/anaconda2
sudo chmod -R g+w /opt/anaconda2
]]></script>Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-70133218193005864132016-07-27T20:45:00.002-04:002016-07-27T20:45:50.444-04:00caffe.io.load_image vs. cv2.imdecodeInteresting note to self... the following code produces the same results, one using a chain of OpenCV methods and the other using a concise Caffe method.<br />
<br />
<script type="syntaxhighlighter" class="brush: python"><![CDATA[
import caffe # Caffe RC3
import cv2 # OpenCV 3.1.0
# Load an image using OpenCV
file = open('test/grayshirt.jpg', 'r')
cv2_imbuf = np.fromstring(file.read(), np.uint8)
cv2_im = cv2.imdecode(cv2_imbuf, 1)
cv2_im = np.asarray(cv2_im[:, :, [2,1,0]], dtype=np.float32)/255.0
print('cv2_im.shape: {}'.format(cv2_im.shape))
# Load an image using Caffe
caffe_im = caffe.io.load_image('test/grayshirt.jpg')
print('caffe_im.shape: {}'.format(caffe_im.shape))
# Visually compare the results
for x in range(0, caffe_im.shape[0]):
for y in range(0, caffe_im.shape[1]):
print('caffe_im vs. cv2_im|{}|{}'.format(caffe_im[x][y], cv2_im[x][y]))
]]></script>Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-88234432705604789522016-05-12T17:53:00.002-04:002016-05-12T17:53:42.379-04:00Mix Groovy and Java in STS 3.7.3.RELEASETo mix Java and Groovy together in the same Spring Starter project, a few changes can be made to the project's properties and paths. By default, when a Java project is created it only looks for source files and test files in the src/main/java and src/test/java directories.<br />
<br />
<h3>
Add src/main/groovy to Java Build Path</h3>
<br />
<ol>
<li>Create the <b>src/main/groovy</b> directory</li>
<li>Right-click the project in Package Explorer, then click <b>Properties</b></li>
<li>Click <b>Java Build Path</b> in the left sidebar</li>
<li>Click <b>Add Folder...</b> to add src/main/groovy</li>
</ol>
<br />
<br />
<h3>
Add Groovy libraries to classpath</h3>
<br />
<ol>
<li>Right-click the project in Package Explorer</li>
<li>Expand Groovy, then click Add Groovy libraries to classpath</li>
</ol>
<br />
<br />
<h3>
Only include groovy-all</h3>
<br />
At this point, trying to run the Spring Boot app will generate errors that look like this.<br />
<br />
<blockquote class="tr_bq">
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">...<br /><span class="s1">org.apache.catalina.core.ContainerBase </span>: A child container failed during start<br />java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Tomcat].StandardHost[localhost].StandardContext[]]<br />...</span></blockquote>
<br />
<div>
<br /></div>
<div>
The errors can be resolved by removing the extra libraries STS automatically added in the previous step.</div>
<div>
<ol>
<li>Right-click <b>Groovy Libraries</b> in the project</li>
<li>Click <b>Properties</b></li>
<li>Select "<b>No, only include groovy-all</b>" in the first panel that asks, "Should all jars in the groovy-eclipse lib folder be included on the classpath?"</li>
</ol>
</div>
Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-60568465636362208742016-05-09T15:03:00.004-04:002016-05-09T15:08:45.707-04:00Mix Groovy and Java in IntellJ IDEA<div>To mix Java and Groovy together in the same IntelliJ IDEA project, a simple change can be made to the project's <b>.iml</b> file. By default, when the project is created it only looks for source files and test files in the <b>src/main/java</b> directory.</div>
<script type="syntaxhighlighter" class="brush: xml"><![CDATA[
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
]]>
</script>
<div>
By adding two <span style="font-family: "courier new" , "courier" , monospace;"><b>sourceFolder</b></span> elements IntellJ will automatically find and compile .groovy files in the Groovy directory.</div>
<script type="syntaxhighlighter" class="brush: xml"><![CDATA[
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/groovy" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/groovy" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
]]>
</script>Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-44750077178483399882016-05-04T12:36:00.001-04:002016-05-04T12:37:20.608-04:00Go to Assembla TicketHere's a simple JS code that can be converted into a bookmarklet to quickly open an Assembla ticket.<br />
<br />
<script class="brush: js" type="syntaxhighlighter"><![CDATA[
var space = "foo"; // Your space here
var baseUrl = "https://www.assembla.com/spaces/" + space + "/tickets/";
var ticketNum = prompt("Ticket Number");
if (ticketNum) {
window.open(baseUrl + ticketNum);
}
]]></script>Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-89846269491427197022016-04-30T01:39:00.000-04:002016-05-03T13:24:08.330-04:00HTTP/REST API SpecificationsNow that I have the pleasure of designing new APIs to support both B2C and B2B use cases, my first thought is to standardize. In the case of APIs, I believe standards reduce the burden of maintenance and improve the ease of integration.<br />
<br />
To that end, I sought to define guidelines for all operations. These are not new or novel, but I need these to set shared expectations with my team. And we start with a few <b>core principles</b>:<br />
<br />
<ol>
<li>Follow REST conventions for CRUD operations</li>
<li>Use JSON in all request and response bodies (<span style="font-family: Courier New, Courier, monospace;">Content-Type: application/json</span>)...</li>
<li>Except where binary content is involved (<span style="font-family: Courier New, Courier, monospace;">Content-Type: multipart/form-data</span>)</li>
</ol>
<div>
<br /></div>
<h3>
REST conventions</h3>
<div>
<br /></div>
<div>
Striving to KISS:</div>
<div>
<ul>
<li>Prefix all operations with a major version number, starting with "/v1" (ref <a href="https://developer.paypal.com/docs/api/" target="_blank">PayPal</a>, <a href="https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_versions.htm" target="_blank">Salesforce</a>)</li>
<li>Stick with lowercase (ref <a href="https://devcenter.heroku.com/articles/node-best-practices#stick-with-lowercase" target="_blank"><span id="goog_1281461407"></span>Heroku</a>)</li>
<li>Map HTTP methods to CRUD operations (ref <a href="http://www.restapitutorial.com/lessons/httpmethods.html" target="_blank">REST API Tutorial</a>)</li>
<li>Use <a href="http://expressjs.com/en/api.html#app.param" target="_blank">route parameters</a> for CRUD operations (ref <a href="http://expressjs.com/en/api.html#app.param" target="_blank">Express</a>)</li>
<li>Use <a href="https://en.wikipedia.org/wiki/Query_string" target="_blank">query strings</a> only in GET requests</li>
</ul>
<div>
<br /></div>
</div>
<h3>
Error response body</h3>
<div>
<br /></div>
<div>
Success responses will contain appropriate data for the request, but all error response bodies look alike. At least structurally, having only one field.<br />
<br />
<ul>
<li>Array<Error> <span style="font-family: "courier new" , "courier" , monospace;"><b>errors</b></span> - An array of any errors encountered while executing the operation. This field is always present for an error state (any non-200 HTTP status).</li>
</ul>
<br />
<br />
Each <b><span style="font-family: "courier new" , "courier" , monospace;">errors</span></b> element has the following fields:</div>
<div>
<br />
<ul>
<li>int <b><span style="font-family: "courier new" , "courier" , monospace;">code</span></b> - "for programmatic consumption" (ref <a href="https://developers.braintreepayments.com/reference/general/validation-errors/overview/java" target="_blank">Braintree</a>)</li>
<li>String <b><span style="font-family: "courier new" , "courier" , monospace;">message</span></b> - "for human consumption" (ref <a href="https://developers.braintreepayments.com/reference/general/validation-errors/overview/java" target="_blank">Braintree</a>)</li>
<li>String <b><span style="font-family: "courier new" , "courier" , monospace;">component</span></b> - Whatever we're blaming for the error</li>
</ul>
</div>
Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-45485887539078229262016-02-02T05:21:00.002-05:002016-02-02T05:22:33.433-05:00Get Full DateTime Format (GMT) in Apex<div>To get the full DateTime format in GMT such that it's compatible with Apex methods like <a href="https://developer.salesforce.com/docs/atlas.en-us.200.0.apexcode.meta/apexcode/apex_class_System_Json.htm#apex_System_Json_deserialize" target="_blank"><span style="font-family: "courier new" , "courier" , monospace;">JSON.deserialize()</span></a>, the most accurate method is to call <span style="font-family: "courier new" , "courier" , monospace;"><a href="https://developer.salesforce.com/docs/atlas.en-us.200.0.apexcode.meta/apexcode/apex_methods_system_datetime.htm#apex_System_Datetime_formatGmt" target="_blank">DateTime.formatGmt()</a></span>.<br /></div>
<div>
<br /></div>
<script type="syntaxhighlighter" class="brush: java"><![CDATA[DateTime now = DateTime.now();
// VARIABLE_ASSIGNMENT|[1]|now|"2016-02-02T10:06:43.197Z"
String fullNowGmtString = now.formatGmt('yyyy-MM-dd\'T\'HH:mm:ss.S\'Z\'');
System.debug(fullNowGmtString);
// USER_DEBUG|[11]|DEBUG|2016-02-02T10:06:43.197Z]]></script>
<div>
<br /></div>
<div>
For comparison, below are some other alternatives for generating similar String values for a DateTime object.<br /></div>
<div><br /></div>
<script type="syntaxhighlighter" class="brush: java"><![CDATA[DateTime now = DateTime.now();
// VARIABLE_ASSIGNMENT|[1]|now|"2016-02-02T10:06:43.197Z"
System.debug(String.valueOf(now));
// USER_DEBUG|[2]|DEBUG|2016-02-02 05:06:43
DateTime nowGmt = DateTime.newInstance(now.dateGmt(), now.timeGmt());
// VARIABLE_ASSIGNMENT|[4]|nowGmt|"2016-02-02T15:06:43.197Z"
System.debug(String.valueOf(nowGmt));
// USER_DEBUG|[5]|DEBUG|2016-02-02 10:06:43
String lazyNowGmtString = String.valueOf(nowGmt).replace(' ', 'T') + 'Z';
System.debug(lazyNowGmtString);
// USER_DEBUG|[8]|DEBUG|2016-02-02T10:06:43Z]]></script>Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-77452119614068008532016-01-11T15:56:00.001-05:002016-01-11T15:56:22.912-05:006 Reasons You Got the Generic Error PageSometimes in Visualforce, you'll have debug logging enabled for a community or portal user, but loading a page fails with no trace of what went wrong. When you look in the debug log for an explanation, all you see is that Salesforce successfully loaded the Generic Error Page configured for the site.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3MB_VLdaxgr2Q5bARE06JfdgeioBnz8abk0BXp8fhlUImeUititUy4T1TNBMZd2uG-Hw-yGSD9eUEx5RXS0eD9ccSd_DsqN9w0pAcPs5LSHf50bQYDV0q109Hszw0lmKh4YxH1flaCLaW/s1600/generic-error-page.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3MB_VLdaxgr2Q5bARE06JfdgeioBnz8abk0BXp8fhlUImeUititUy4T1TNBMZd2uG-Hw-yGSD9eUEx5RXS0eD9ccSd_DsqN9w0pAcPs5LSHf50bQYDV0q109Hszw0lmKh4YxH1flaCLaW/s1600/generic-error-page.png" /></a></div>
<br />
Before logging a case or asking a fellow developer for help, check for the following 6 common culprits.<br />
<br />
<ul>
<li>Are you missing any static resources, <span style="font-family: Courier New, Courier, monospace;">{!$Resource.__}</span>?</li>
<li>Are you missing any custom labels, <span style="font-family: Courier New, Courier, monospace;">{!$Label.__}</span>?</li>
<li>Are you missing any custom permissions, <span style="font-family: Courier New, Courier, monospace;">{!$Permission.__}</span>?</li>
<li>Are you missing any page references, <span style="font-family: Courier New, Courier, monospace;">{!$Page.__}</span>?</li>
<li>Are you missing any custom controller/extension <i>properties</i> bound on the page? This can be a problem when deploying packages among sandbox orgs.</li>
<li>Are you missing any custom controller/extension <i>actions</i> bound on the page?</li>
</ul>
<div>
<br /></div>
<div>
What I've also discovered, interestingly enough, is that for some reason using MavensMate v6.0.0 with Sublime Text 3, saving a Visualforce page or component <i>skips</i> the validation for <a href="https://developer.salesforce.com/docs/atlas.en-us.198.0.pages.meta/pages/pages_variables_global.htm" target="_blank">global variables</a> used in Visualforce.</div>
Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-70561926981566362352015-11-18T19:32:00.000-05:002015-11-18T19:33:49.980-05:00Why Consultants Should Learn to Type<b>$100,000,000,000</b>.<br />
<br />
How's that for an answer to the title question? If you're wondering where that came from, the answer's simple. Let's conservatively estimate the size of the consulting industry to be $200 billion<sup><a href="http://www.consultancy.uk/consulting-industry/global-consulting-market" target="_blank">[1]</a><a href="https://www.plunkettresearch.com/trends-analysis/consulting-management-business-market/" target="_blank">[2]</a></sup>. Without adding more people, what would happen if the productivity of people already in the industry increases by 50%? That 50% would amount to $100 billion, and <i>that's </i>the reason I believe all consultants should learn how to type.<br />
<br />
Okay, lest my alma mater revokes my MBA, I'll admit right now my posit is a gross exaggeration based on unrealistic assumptions. So I'm off... but by how much?<br />
<br />
<h3>
50% is bogus. Or is it?</h3>
<div>
<br /></div>
<div>
"The average person types between 38 and 40 words per minute... However, professional typists type ... upwards of 65 to 75 WPM."<sup><a href="http://smallbusiness.chron.com/good-typing-speed-per-minute-71789.html" target="_blank">[3]</a></sup> The difference between the professional typist and the average person is simply that the professional typist <i>learned to type</i>. And my adjusted typing speed is 87 WPM, even with a wrist injury.<br />
<br />
<blockquote class="twitter-tweet" lang="en">
<div dir="ltr" lang="en">
Nbd, just <a href="https://twitter.com/martyychang">@martyychang</a> typing on two keyboards at once <a href="https://twitter.com/hashtag/how?src=hash">#how</a> <a href="https://twitter.com/hashtag/what?src=hash">#what</a> <a href="https://twitter.com/hashtag/nowristnoproblem?src=hash">#nowristnoproblem</a> <a href="https://t.co/rkHLXBTJkB">pic.twitter.com/rkHLXBTJkB</a></div>
— Julien Ouellet (@trailsforce) <a href="https://twitter.com/trailsforce/status/658723746795098112">October 26, 2015</a></blockquote>
<script async="" charset="utf-8" src="//platform.twitter.com/widgets.js"></script>
<br />
<div>
<br />
#yawn So what? So if you're not typing <i>at least </i>at the pace of a professional typist, consider yourself handicapped. Not in a derogatory way, simply such that you have <i>much </i>untapped potential.<br />
<br />
<h3>
Try an analogy</h3>
<br />
Take human speech as an example. Here are two clips below from two consultants pitching their firms to win a project. Which one would you hire?<br />
<br />
<span style="font-size: x-small;">Consultant A</span><br />
<iframe frameborder="no" height="166" scrolling="no" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/233699180&color=ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false" width="100%"></iframe>
<br />
<br />
<span style="font-size: x-small;">Consultant B</span><br />
<iframe frameborder="no" height="166" scrolling="no" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/233698611&color=ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false" width="100%"></iframe>
<br />
<br />
You've probably figured out that it's the same voice recording, with the modified clip slowed to 67% of the original. But isn't the difference obvious? If you wouldn't hire the second guy because he spoke so slowly, why should you settle for the guy who types that way?<br />
<br />
Keep in mind that <b>typing is <i>not </i>rocket science</b>. All it takes is scheduled time and practice.<br />
<div>
<br /></div>
</div>
<h3>
Imagine if</h3>
<div>
<br /></div>
<div>
... John the business analyst suddenly produces 50% more:</div>
<div>
<ul>
<li>Detailed notes from meetings</li>
<li>Thorough functional requirements</li>
<li>Accurate and comprehensive documentation</li>
</ul>
<div>
<br /></div>
</div>
<div>
... Jane the app developer suddenly produces 50% more:</div>
<div>
<ul>
<li>Code</li>
<li>Code comments</li>
<li>Regression test automation</li>
</ul>
<div>
<br /></div>
</div>
<div>
... Jill the project manager suddenly produces 50% more:</div>
<div>
<ul>
<li>Personalized stakeholder communications</li>
<li>Risk reports and mitigation strategies</li>
<li>Next phase project proposals</li>
</ul>
<div>
<br /></div>
</div>
<div>
... <i>everyone</i> shared and collaborated 50% more on enterprise platforms! (<a href="http://www.salesforce.com/chatter/overview/" target="_blank">Chatter</a>, anyone?)<br />
<br />
The value gained from a 50% increase in typing speed is not proportional. I believe <i>it's exponential</i>. People who have speech impediments tend to speak less. You can guess that people who have typing impediments will avoid mediums where typing is the means to communicate.<br />
<br />
And if your consultants, your organization doesn't care to address people's typing skills, aren't you leaving money on the table from your investments in <i>typing-</i>based platforms like SharePoint? Salesforce? Confluence?<br />
<br />
<h3>
Closing thoughts</h3>
<br />
I believe we owe it to ourselves, to our companies, to our economies, to learn this simple skill that catalyzes collaboration and value creation in the digital age. And if you're a company, teaching your people to type is a one-time investment that will pay lifetime dividends.</div>
</div>
Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-64998061550876902762015-10-31T23:19:00.001-04:002015-10-31T23:19:52.137-04:00Field Update + Update Records + Apex Trigger = ?While the "Triggers and Order of Execution" page in the Winter '16 <i><a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/" target="_blank">Force.com Apex Code Developer's Guide</a></i> gives good information about the high-level order of operations, developers reading it are still unclear about how complex interactions unfold across objects that use workflow rules, Apex triggers, <i>and </i>Process Builder ("PB") processes.<br />
<br />
Let's take a relatively simple set of automation applied to a single object:<br />
<br />
<ul>
<li>A workflow rule with a field update</li>
<li>A recursion-enabled process that updates the record which starts the process</li>
<li>An Apex trigger</li>
</ul>
<div>
<br /></div>
<div>
When a transaction is processed, how many times do each of the above automation components execute <i>in that single transaction</i>? The answers below may surprise you/</div>
<div>
<br /></div>
<div>
<table class="modern">
<thead>
<tr><th>Component Type</th><th>Num Executions</th></tr>
</thead>
<tbody>
<tr><td>Workflow Rule</td><td>1</td></tr>
<tr><td>Process</td><td>6</td></tr>
<tr><td>Trigger</td><td>8</td></tr>
</tbody>
</table>
</div>
<div>
<br /></div>
<div>
The exact step order in which the components were executed is illustrated below.</div>
<div>
<br /></div>
<div>
<table class="modern">
<thead>
<tr><th>Workflow Rule</th><th>Process</th><th>Trigger</th></tr>
</thead>
<tbody>
<tr><td></td><td></td><td>1</td></tr>
<tr><td>2</td><td></td><td></td></tr>
<tr><td></td><td></td><td>3</td></tr>
<tr><td></td><td>4</td><td></td></tr>
<tr><td></td><td></td><td>5</td></tr>
<tr><td></td><td>6</td><td></td></tr>
<tr><td></td><td></td><td>7</td></tr>
<tr><td></td><td>8</td><td></td></tr>
<tr><td></td><td></td><td>9</td></tr>
<tr><td></td><td>10</td><td></td></tr>
<tr><td></td><td></td><td>11</td></tr>
<tr><td></td><td>12</td><td></td></tr>
<tr><td></td><td></td><td>13</td></tr>
<tr><td></td><td>14</td><td></td></tr>
<tr><td></td><td></td><td>15</td></tr>
</tbody>
</table>
</div>
<div>
<br /></div>
<div>
The takeaway should be that developers ought to be very careful when adding automation to an environment that uses "all of the above", meaning workflow rules, processes, <i>and </i>triggers.</div>
Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-42087238104298129572015-09-04T12:44:00.000-04:002015-09-04T12:44:14.280-04:00Where's My Flow/Process in the Change Set?If you've tried deploying flows or processes in a change set, especially in a large change set, you may have experienced some disorientation trying to find that in the Change Set detail page. For quick reference, the table below shows you what you should be looking for when examining an open change set vs. a closed change set.<br />
<br />
<table class="modern">
<thead>
<tr>
<th>Component Type</th>
<th>Ordered By (in Open Change Set)</th>
<th>Ordered By (in Closed Change Set)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Flows</td>
<td>Unique Name</td>
<td>Flow Name</td>
</tr>
<tr>
<td>Processes</td>
<td>API Name</td>
<td>Process Name</td>
</tr>
</tbody>
</table>Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-26274197041285978192015-08-31T23:19:00.001-04:002015-08-31T23:19:56.265-04:00What's the Real Risk with Enabling Divisions?<div>
In Salesforce, <a href="https://help.salesforce.com/HTViewHelpDoc?id=admin_division.htm&language=en_US" target="_blank">divisions</a> are a means to improve query and search performance by partitioning data into logical buckets called "divisions". However, like with the <a href="https://help.salesforce.com/HTViewHelpDoc?id=account_person.htm" target="_blank">Person Accounts</a> feature, many admins may hesitate and think twice about enabling divisions due to this warning: "<b><span style="color: red;">Enabling divisions is irreversible. </span></b>After implementing divisions, you cannot revert back to a state that does not have division fields."</div>
<div>
<br /></div>
<div>
"Irreversible", huh? Well... what's the <i>real </i>risk with enabling divisions? <b>I think there is <i>no significant risk</i>.</b></div>
<div>
<br /></div>
<div>
According to <a href="https://help.salesforce.com/HTViewHelpDoc?id=admin_division.htm&language=en_US" target="_blank">Salesforce Help</a> (Summer '15), enabling divisions <a href="https://help.salesforce.com/HTViewHelpDoc?id=admin_division.htm&language=en_US" target="_blank">may affect (or not) nine key areas</a>. But it seems like the effects can be easily negated or suppressed.</div>
<div>
<br /></div>
<table class="modern">
<thead>
<tr><th>Area</th>
<th>Reversal Strategy</th>
</tr>
</thead>
<tbody>
<tr><td>Search</td><td>Revoke the "Affected by Divisions" permission.<!-- Description --></td></tr>
<tr><td>List views</td><td>Revoke the "Affected by Divisions" permission.<!-- Description --></td></tr>
<tr><td>Chatter</td><td>Not supported (i.e., affected).<!-- Description --></td></tr>
<tr><td>Reports</td><td>Revoke the "Affected by Divisions" permission.</td></tr>
<tr><td>Viewing records and related lists</td><td>Not affected.<!-- Description --></td></tr>
<tr><td>Creating new records</td><td>Set to the global division.<!-- Description --></td></tr>
<tr><td>Editing records</td><td>Set to the global division.<!-- Description --></td></tr>
<tr><td>Custom objects</td><td>Set to the global division.<!-- Description --></td></tr>
<tr><td>Relationships</td><td>Set to the global division.<!-- Description --></td></tr>
</tbody>
</table>
<div>
<br /></div>
<div>
In short, if you want to give divisions a try, talk to a few people about enabling the feature. If the foremost argument <i>against </i>enabling divisions is simply that it's irreversible, go ahead and just enable it anyway (in a full sandbox first). If it doesn't work for you, you can always revoke the Affected by Divisions permission from all users.</div>
Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-52806385690843844922015-08-25T12:54:00.002-04:002015-08-25T12:54:37.195-04:00Salesforce API VersionsIn Salesforce, did you know that API version 34.0 corresponds to Summer '15? Or that API version 17.0 means Winter '10? If not, this <a href="https://docs.google.com/spreadsheets/d/1i7ieOHnOvDRkvcN43JK0Z45oD9YlcPSyx4Rm5Onarlk/edit?usp=sharing" target="_blank">simple table</a> of API versions and release names may be useful to you.<div>
<br /></div>
<div>
<table border="1" cellpadding="0" cellspacing="0" dir="ltr" style="border-collapse: collapse; border: 1px solid #ccc; font-family: arial,sans,sans-serif; font-size: 13px; table-layout: fixed;"><colgroup><col width="100"></col><col width="100"></col></colgroup><tbody>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,2,"API Version"]" style="font-weight: bold; padding: 2px 3px 2px 3px; vertical-align: bottom;">API Version</td><td data-sheets-value="[null,2,"Release"]" style="font-weight: bold; padding: 2px 3px 2px 3px; vertical-align: bottom;">Release</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,34]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">34.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Summer '15"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Summer '15</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,33]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">33.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Spring '15"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Spring '15</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,32]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">32.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Winter '15"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Winter '15</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,31]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">31.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Summer '14"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Summer '14</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,30]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">30.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Spring '14"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Spring '14</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,29]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">29.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Winter '14"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Winter '14</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,28]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">28.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Summer '13"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Summer '13</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,27]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">27.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Spring '13"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Spring '13</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,26]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">26.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Winter '13"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Winter '13</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,25]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">25.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Summer '12"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Summer '12</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,24]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">24.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Spring '12"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Spring '12</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,23]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">23.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Winter '12"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Winter '12</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,22]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">22.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Summer '11"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Summer '11</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,21]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">21.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Spring '11"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Spring '11</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,20]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">20.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Winter '11"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Winter '11</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,19]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">19.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Summer '10"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Summer '10</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,18]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">18.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Spring '10"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Spring '10</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,17]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">17.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Winter '10"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Winter '10</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,16]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">16.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Summer '09"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Summer '09</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,15]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">15.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Spring '09"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Spring '09</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,14]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">14.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Winter '09"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Winter '09</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,13]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">13.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Summer '08"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Summer '08</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,12]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">12.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Spring '08"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Spring '08</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,11]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">11.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Winter '08"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Winter '08</td></tr>
<tr style="height: 21px;"><td data-sheets-numberformat="[null,2,"0.0",1]" data-sheets-value="[null,3,null,10]" style="padding: 2px 3px 2px 3px; text-align: right; vertical-align: bottom;">10.0</td><td data-sheets-formula="=R[0]C[1]&" '"&RIGHT(R[0]C[2],2)" data-sheets-value="[null,2,"Summer '07"]" style="padding: 2px 3px 2px 3px; vertical-align: bottom;">Summer '07</td></tr>
</tbody></table>
</div>
Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-13292706537117576912015-08-14T02:17:00.003-04:002015-08-14T02:17:58.005-04:006 Salesforce Target Attributes Every PowerCenter Session Should SetWhen using Informatica PowerCenter to perform ETL jobs that process large data volumes (think <i>millions </i>of records), there are at least six attributes that should be set for each session. And even when loading smaller data volumes, these attributes may still be worth setting to improve performance. <br />
<br />
<h3>
1. Max batch size = 10000</h3>
<br />
The assumption here is that the session will use the Bulk API, which is the fastest way to load data in Salesforce. Period. As of Summer '15, the maximum batch size for a Bulk API job is still 10,000 records. Let's take advantage of this.<br />
<br />
<h3>
2. Set fields to NULL = checked</h3>
<br />
With data loads, by default it's best to assume that a blank field means that there is no data from the source system to feed this field. In this case, whatever is currently in the field would be considered invalid, to be overwritten with a blank value during the feed.<br />
<br />
<h3>
3. Use SFDC Bulk API = checked</h3>
<br />
Self-explanatory.<br />
<br />
<h3>
4. Monitor Bulk Job Until All Batches Processed = checked</h3>
<br />
When chaining tasks inside a worklet or workflow, monitoring the bulk job until all batches are processed helps to ensure that a dependent task will start only after the predecessor task truly completes. Otherwise, not only would you increase the risk of encountering locking errors, you run the risk of the next task running in the context of stale data.<br />
<br />
<h3>
5. Enable field truncation attribute = <i>unchecked</i></h3>
<br />
This is equivalent to the <b>Allow field truncation </b>setting in Salesforce Data Loader. Unfortunately still, as of Summer '15, using the Bulk API prevents us from using this automatic truncation option. So you should be aware that truncating values must be done by other means during the transformation, not the load!<br />
<br />
<h3>
6.Enable hard deletes for BULK API = checked</h3>
<br />
Why not? This significantly improves the performance of mass delete operations, by skipping the Recycle Bin and erasing the record immediately. Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-62868142841773257382015-06-30T09:37:00.001-04:002015-06-30T09:48:20.495-04:00Salesforce Change Set AcceleratorsOkay, so this post isn't really about <a href="https://en.wikipedia.org/wiki/Web_accelerator" target="_blank">web accelerators</a> in the purest sense of the definition. But if you're frustrated with the experience of navigating and managing change sets in the UI, here are a few quick bookmarklets you can add to your browser to ease the pain.<br />
<br />
To "install" a bookmarklet, simply drag the bookmarklet on to your browser's bookmarks bar. Or, in Internet Explorer, right-click the bookmarklet and click <b>Add to favorites...</b><br />
<br />
<i>Last Tested Date: June 30, 2015 (Summer '15)</i><br />
<br />
<h3>
Change Set: Next</h3>
<br />
<a class="bookmarklet" href="javascript:(function()%20{var%20nextLink%20=%20document.getElementById("outboundChangeSetDetailPage:outboundChangeSetDetailPageBody:outboundChangeSetDetailPageBody:detail_form:outboundCs_componentsBlock:OutboundChangeSetComponentBlockSection:componentsPageNavigator:pageNavigatorComponent:nextPageLink");nextLink.click();})();">CS > Next</a><br />
<br />
Ever notice that sometimes when you click Next to scroll through the pages of 25 components in a change set, the size of the table shifts and displaces the Next link? This simple bookmarklet clicks the Next link for you, without you having to move your mouse cursor.<br />
<br />
<h3>
Change Set: Previous</h3>
<br />
<a class="bookmarklet" href="javascript:(function()%20{var%20previousLink%20=%20document.getElementById("outboundChangeSetDetailPage:outboundChangeSetDetailPageBody:outboundChangeSetDetailPageBody:detail_form:outboundCs_componentsBlock:OutboundChangeSetComponentBlockSection:componentsPageNavigator:pageNavigatorComponent:previousPageLink");previousLink.click();})();">CS > Previous</a><br />
<br />
Same as the Change Set: Next bookmarklet, but for the Previous link.<br />
<br />
<h3>
Add to Change Set: more (10,000 records per list page)</h3>
<br />
<a class="bookmarklet" href="javascript:(function()%20{var%20moreArrowImg%20=%20document.getElementsByClassName("moreArrow")[0];var%20moreLink%20=%20moreArrowImg.parentNode;moreLink.href%20=%20moreLink.href.replace(/rowsperpage=[0-9]+/,%20"rowsperpage=10000");moreLink.click();})();">AtCS > more (10,000)</a><br />
<br />
When adding components to a change set, especially for something like Custom Fields, you may have noticed two frustrating problems. First, clicking through multiple pages to find the record you want is a pain. Second, multiplying the pain is the fact that what you select on one page is lost when you switch to a different page. This bookmarklet sort of solves the problem by upping the size of the list to 10,000 records, which usually is enough to allow you to select and add all components of the same type at once.<br />
<br />
<h3>
Other tips </h3>
<br />
If you're planning to create a large change set as you build out your solution over multiple days or weeks, bookmarking the change set's detail page in your sandbox org should be a quick win.Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-38995650191548765722015-05-31T23:31:00.002-04:002015-05-31T23:31:54.244-04:00Why Roll-Up Summary Requires Master-DetailThere's an idea that is 7 years old on the Success Community titled, "<a href="https://success.salesforce.com/ideaView?id=08730000000BrqsAAC" target="_blank">Eliminate Need for Master-Detail Relationship for Roll-ups</a>", and users have voted the idea up to over 25,000 points.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhj36KnmPnNrgDN-zTzkeZdcAmCg-TJzsM7vf1X8TQzc0_RLXiVzsGwjaJrEfRX5mD866f2Se8e6GVCu9qeAmlb7e42sd7y0Umg_1gBdO11Yr7g6XguCRxtX-aBudsfhsv_i5h8_kft4oDd/s1600/eliminate-need-for-master-detail.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="91" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhj36KnmPnNrgDN-zTzkeZdcAmCg-TJzsM7vf1X8TQzc0_RLXiVzsGwjaJrEfRX5mD866f2Se8e6GVCu9qeAmlb7e42sd7y0Umg_1gBdO11Yr7g6XguCRxtX-aBudsfhsv_i5h8_kft4oDd/s400/eliminate-need-for-master-detail.png" width="400" /></a></div>
<br />
I won't lie. I am most likely one of the 2,500 users who voted in favor of the idea, who thought <i>I can't believe a no-brainer like this is still an idea and not a GA feature!</i><br />
<br />
But today, while watching <i><a href="https://www.youtube.com/watch?v=JEKZ2OjwzS8&index=7&list=PL6747B4DAE356E17C" target="_blank">Who Sees What: Record Access Via Sharing Rules</a></i> I suddenly realized that the reason for the delay could actually be ridiculously simple: <b>Implementing this feature would violate the security design of the Salesforce1 Platform. </b>How? By exposing information that would otherwise be hidden when OWD is set to "Private".<br />
<br />
<h3>
Scenario: VIP bank accounts</h3>
<br />
Let's say you're a system administrator for a fictitious financial institution called SaaS Bank. At SaaS Bank, there are everyday customers, and then there are <i>VIP </i>customers. VIP customers at SaaS Bank are high-profile individuals of great importance or great wealth, and a few notable VIPs include Barack Obama, Warren Buffett and Marc Benioff.<br />
<br />
Understandably, VIPs get the white glove treatment. Their relationships are discretely managed by handful of bankers in the Private Bank department within SaaS Bank. These bankers are known as Private Bankers, and their number one priority is protecting their client's sensitive data, namely the clients' bank accounts and balances.<br />
<br />
Data on customers' bank accounts are stored in a custom object labeled Bank Account, and all bank accounts serviced by SaaS Bank are tracked in this object.<br />
<br />
<b>The security requirement: </b>Everyone at SaaS Bank should be able to see that a VIP is indeed a customer, but only Private Bankers (and their trusted colleagues) should be able to see the bank accounts held by a VIP.<br />
<br />
The simple solution would be to make the Bank Account custom object private using OWD. And to relate Bank Account records back to a customer (i.e., a standard Account record), the object has a Lookup(Account) field, <i>not a Master-Detail(Account) </i>field.<br />
<br />
<b>The tricky requirement: </b>All users want to see aggregate balance data for their customers.<br />
<br />
So, if you <i>could </i>create a Roll-Up Summary field on the Account object that sums all balances for a customer, you would violate the private sharing model for the Bank Account object. A Roll-Up Summary field holds a value that is calculated based on all pertinent records.<br />
<br />
In a private sharing model, how would... how <i>could </i>the Roll-Up Summary field hold a value that simultaneously shows 0 to a regular banker, the total balance to a Private Banker and something in between to other bankers with whom a Private Banker has manually shared records? The answer is <i>it can't</i>.<br />
<br />
Objects with Master-Detail fields inherit the record access controls on parent objects. In this case you can present aggregate information on parent records via Roll-Up Summary fields, because if you have access to the parent record you also have access to all child records. But when you use a Lookup field instead because you need different record access for child records, well...<br />
<br />
<h3>
Okay, I get it, and I still need a workaround</h3>
<br />
I think there are legitimate reasons why an organization would want aggregate data to be automatically calculated and displayed through a Lookup relationship. There are two ways to work around this:<br />
<ul>
<li>Ignore the built-in security constraint and leverage Apex running in <a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_keywords_sharing.htm" target="_blank">system context</a> to perform the roll-up. You can even use an off-the-shelf solution the free <a href="https://github.com/afawcett/declarative-lookup-rollup-summaries" target="_blank">Declarative Lookup Rollup Summaries</a> app or the paid <a href="https://github.com/afawcett/declarative-lookup-rollup-summaries" target="_blank">Rollup Helper</a> app.</li>
<li>Use a custom visual element (e.g., Visualforce page) to display a contextual roll-up, taking into account the current user's access to child records. This would leverage the <a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_keywords_sharing.htm" target="_blank"><span style="font-family: "Courier New",Courier,monospace;">with sharing</span></a> keyword to accurately display different values to different users.</li>
</ul>
<br />
In the end, it would be nice of either of the above options were made into native features. And I'm guessing that the first option, the convenient and <i>intentional </i>deviation from an established security model, is what the 2,500 supporters of <a href="https://success.salesforce.com/ideaView?id=08730000000BrqsAAC" target="_blank">that 7-year-old idea</a> want.Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-74530360702565455792015-05-31T02:02:00.000-04:002015-05-31T02:07:12.256-04:00Who Sees What: Record Access Via Roles (Corrected)I'm admittedly a bit disappointed in the <a href="https://www.youtube.com/watch?v=5KLTcu02nfY&index=5&list=PL6747B4DAE356E17C" target="_blank"><i>Who Sees What: Record Access Via Roles</i></a> video. When I first read about the <i>Who Sees What</i> series, I thought it was awesome that salesforce.com decided to produce visual aids on the topic of security. As an alternative to the <a href="https://help.salesforce.com/help/pdfs/en/salesforce_security_impl_guide.pdf" target="_blank"><i>Security Implementation Guide</i></a>, which boasts over 100 pages of official documentation on Salesforce security, I expected the videos to demystify the flexible and nuanced security controls available to system administrators.<br />
<br />
However, after watching the <a href="https://www.youtube.com/watch?v=5KLTcu02nfY&index=5&list=PL6747B4DAE356E17C" target="_blank"><i>Record Access Via Roles</i></a> video, I feel that almost 70% of the content within is either misleading or simply inaccurate.<br />
<br />
<b>Note: </b>I spent significant time staging test cases in an org before deciding to write this blog post, so <i>please </i>let me know if any part of <i>my writing </i>is technically wrong.<br />
<br />
<h3>
2:10 Three ways to open up access? Not quite...</h3>
<br />
In the AW Computing scenario, the presenter says that access to private Opportunity records can be opened up in one of three ways, quoted below:<br />
<ul>
<li>"No access. In essence, this maintains the org-wide default of private. Users in this role would not be able to see opportunities that they do not own." </li>
<li>"View only. Users in a role can view opportunities regardless of ownership."</li>
<li>"View and edit. Users in a role can view and edit opportunities regardless of ownership."</li>
</ul>
<br />
This <i>sounds </i>good in concept, but as the video progresses to the demo portion to show how the three ways are actually implemented, the problem becomes clear. <span style="color: #660000;"><b>The presenter is actually misconstruing and wrongly explaining the Opportunity Access options.</b></span><br />
<br />
In reality, the implicit access granted through the role hierarchy automatically solves the requirement presented in the video, and the Opportunity Access options are completely irrelevant to the hypothetical situation.<br />
<br />
<h3>
A more accurate explanation</h3>
<br />
Let's assume that the role hierarchy is set up as implied by the visual at 1:40 in the video.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZlRN9MNc57844V1uWfXRHMoZWd_Htgro_O3OHoTUwR1en5AiY3aKELAekBo4CRlIY4te1yQRZKfFnfxBmFHueL_R5KXyppen1b8jLVnR0AF8nOQuwcBZl9VEy0ReQ6Y_Nk3aO1vzON5Ey/s1600/aw-computing-role-hierarchy.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="179" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZlRN9MNc57844V1uWfXRHMoZWd_Htgro_O3OHoTUwR1en5AiY3aKELAekBo4CRlIY4te1yQRZKfFnfxBmFHueL_R5KXyppen1b8jLVnR0AF8nOQuwcBZl9VEy0ReQ6Y_Nk3aO1vzON5Ey/s320/aw-computing-role-hierarchy.png" width="320" /></a></div>
<br />
Alan can see and edit whatever opportunities Karen and Phil can see and edit. The two reasons are that Alan is above Karen and Phil in the role hierarchy, and that OWD for the Opportunity object is configured to <a href="https://help.salesforce.com/apex/HTViewHelpDoc?id=security_controlling_access_using_hierarchies.htm" target="_blank"><i>grant access using hierarchies</i></a> (which as of Spring '15 you still cannot disable for standard objects). <b>There are <i>no more granular controls </i>for records owned within a subordinate chain.</b> If Karen can see a record, Alan can see that record. If Karen can edit a record, Alan can edit that record. Access via subordinates in the hierarchy is that simple.<br />
<br />
So what do the Opportunity Access options do? Simply put, the options do exactly what the Role Edit page says they do.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiwwXzEO-cYGriJURcjYj6x5D9SoFLy0xJkXVh-tJXoTraXVljCVTj36E7xKkvrmEEIb0lXCgV61x2bynh38CT_gAfwrXn_z6sTMQ8GYDMP25XjKJXTIDbe8BrnCYWgRIcb-KzIINho52w/s1600/opportunity-access.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="64" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiwwXzEO-cYGriJURcjYj6x5D9SoFLy0xJkXVh-tJXoTraXVljCVTj36E7xKkvrmEEIb0lXCgV61x2bynh38CT_gAfwrXn_z6sTMQ8GYDMP25XjKJXTIDbe8BrnCYWgRIcb-KzIINho52w/s320/opportunity-access.png" width="320" /></a></div>
<br />
Opportunity Access options have nothing to do with roles and subordinates. The selected option comes into play in situations, such as ones involving account teams, where a user from one branch in the role hiearchy owns an account, but a user from a different branch owns an opportunity for that account.<br />
<br />
<h3>
Try it yourself</h3>
<br />
Admittedly this will be really difficult if you don't have access to a sandbox org or a Partner Enterprise Edition org, but here's the idea.<br />
<br />
Your role hierarchy looks something like the following:<br />
<ul>
<li>CEO</li>
<ul>
<li>SVP Products</li>
<ul>
<li>Product Sales Manager </li>
</ul>
<li>SVP Services</li>
<ul>
<li>Services Sales Manager</li>
</ul>
</ul>
</ul>
<br />
Configure <b>Opportunity Access </b>for all roles in the hierarchy so that "users in this role cannot access opportunities that they do not own that are associated with accounts that they do own."<br />
<br />
Set OWD for Opportunity to "Private", then do the following:<br />
<ol>
<li>Log in as a Product Sales Manager</li>
<li>Create an account</li>
<li>Create an opportunity</li>
<li>Log in as a Services Sales Manager</li>
<li>Verify that you cannot see the opportunity</li>
<li>Create a new opportunity on the account owned by the Product Sales Manager</li>
<li>Log in as the Product Sales Manager</li>
<li>Verify that you cannot see the new opportunity created by the Services Sales Manager</li>
<li>Log in as the administrator</li>
<li>Change the Opportunity Access for the Product Sales Manager role so that "users in this role can <b>view </b>all opportunities associated with accounts that they own, regardless of who owns the opportunities."</li>
<li>Log in as the Product Sales Manager</li>
<li>Verify that <i>you can now see the new opportunity </i>created by the Services Sales Manager</li>
<li>Verify that <i>you you cannot edit that opportunity</i></li>
</ol>
Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-46728636591750704612015-05-30T14:50:00.001-04:002015-05-30T14:50:44.541-04:00The Apex Ten Commandments (in Writing)For anyone (like me) who couldn't find the slides to <i><a href="https://www.youtube.com/watch?v=D7mqMYliy3A" target="_blank">The Apex Ten Commandments</a></i> recording referenced on the <a href="https://developer.salesforce.com/page/Architect_Core_Resources" target="_blank">Architect Core Resources</a> page, here's the written list:<br />
<br />
<ol>
<li>Thou shalt not put queries in for loops</li>
<li>Thou shalt not put DML in for loops</li>
<li>Thou shalt have a happy balance between clicks & code</li>
<li>Thou shalt only put one trigger per object</li>
<li>Thou shalt not put code in triggers other than calling methods and managing execution order</li>
<li>Thou shalt utilize maps for queries wherever possible</li>
<li>Thou shalt make use of relationships to reduce queries wherever possible</li>
<li>Thou shalt aim for 100% test coverage</li>
<li>Thou shalt write meaningful and useful tests</li>
<li>Thou shalt limit future calls and use asynchronous code where possible</li>
</ol>
<div>
<br /></div>
<div>
And I just have a couple of comments to add for color.</div>
<div>
<br /></div>
<h3>
Comments on #7</h3>
<div>
<br /></div>
<div>
I haven't tested this hypothesis yet, but... does this commandment still hold with large data volumes? Especially in the context of batch Apex? One certainty is that executing a single query like this is convenient for the developer. But when the query would return thousands of records that reference a small set of parent records, perhaps at larger data volumes a more efficient approach would be to split the query and leverage commandment #6 instead.</div>
<div>
<br /></div>
<h3>
Comments on #10</h3>
<div>
<br /></div>
<div>
The <a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_annotation_future.htm" target="_blank"><span style="font-family: Courier New, Courier, monospace;">future</span> annotation</a> is slowly become obsolete with the introduction of the <a href="https://developer.salesforce.com/releases/release/Winter15/Submit+and+Monitor+Jobs+for+Asynchronous+Execution+with+the+Queueable+Interface" target="_blank">Queuable interface in Winter '15</a>, although general guidelines for designing asynchronous automation still hold true.</div>
Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-83653906207593247652015-05-30T00:04:00.000-04:002015-05-30T00:04:18.681-04:00Salesforce SSO in 5 BulletsFor my own edification, I want to summarize single sign-on options with Salesforce as succinctly as possible.<br />
<br />
<h3>
Using non-Salesforce credentials to get into Salesforce</h3>
<br />
This scenario can be simplified like this: A user already has a username + password combination stored in another system. The user wants to log into Salesforce using that existing username and password, instead of maintaining a separate username and password that's used only to log into Salesforce.<br />
<br />
To achieve this, Salesforce allows:<br />
<ul>
<li><a href="https://help.salesforce.com/apex/HTViewHelpDoc?id=sso_delauthentication_configuring.htm&language=en_US" target="_blank">Delegated Authentication</a></li>
<li><a href="https://help.salesforce.com/apex/HTViewHelpDoc?id=sso_saml.htm&language=en_US" target="_blank">Federated Authentication</a> using SAML 2.0</li>
<li>External authentication providers: Facebook, Google, Janrain, LinkedIn, Microsoft Access Control Service, another Salesforce org, Twitter, any service provider who implements the <a href="http://openid.net/connect/faq/" target="_blank">OpenID Connect </a>protocol</li>
</ul>
<br />
<h3>
Using Salesforce credentials to get into another app</h3>
<br />
This scenario can be simplified like this: A user is already logged into Salesforce. The user wants to launch another app without having to authenticate again. Instead, the other app should recognize the user and respond accordingly, based on the the user's Salesforce session.<br />
<br />
To facilitate this, Salesforce offers:<br />
<ul>
<li><a href="https://help.salesforce.com/HTViewHelpDoc?id=identity_provider_enable.htm&language=en_US" target="_blank">SAML 2.0 Identity Provider</a></li>
<li><a href="https://developer.salesforce.com/page/Single_Sign-On_for_Desktop_and_Mobile_Applications_using_SAML_and_OAuth" target="_blank">OAuth 2.0 Authorization Server</a></li>
</ul>
<br />
<h3>
Closing thoughts</h3>
<br />
A company can mix the two approaches above, so that Salesforce becomes an intermediate link in a chain that allows access to a third-party app using credentials maintained in a non-Salesforce system. Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-9362774815347681462015-05-28T00:09:00.001-04:002015-05-28T00:09:21.456-04:003 Integration Practices Missing from White PaperI just skimmed through the <i><a href="https://developer.salesforce.com/page/Integration_Patterns_and_Practices" target="_blank">Integration Patterns and Practices</a> </i>white paper, which seems like a great primer on some time-tested integration approaches. However, two GA features plus a pseudo-integration option seem to be notably absent from the document.<br />
<br />
<ul>
<li><a href="https://developer.salesforce.com/page/Force.com_Canvas" target="_blank">Force.com Canvas</a></li>
<li><a href="https://help.salesforce.com/apex/HTViewHelpDoc?id=platform_connect_about.htm&language=en_US" target="_blank">Lightning Connect</a></li>
<li><a href="https://help.salesforce.com/apex/HTViewHelpDoc?id=customize_functions_a_h.htm&language=en_US#HYPERLINK" target="_blank">HYPERLINK</a> function in a formula field</li>
</ul>
<div>
<br /></div>
<div>
Am I forgetting any other options? Please let me know!</div>
<div>
<br /></div>
<h3>
Force.com Canvas</h3>
<div>
<br /></div>
<div>
This is a <a href="http://www.salesforce.com/us/developer/docs/platform_connect/canvas_framework.pdf" target="_blank">well documented feature</a> for which I'll summarize the key capabilities as of Summer '15:</div>
<div>
<ul>
<li>Authentication via signed request or OAuth 2.0</li>
<li>Canvas app in Visualforce via <a href="https://www.salesforce.com/us/developer/docs/platform_connect/Content/canvas_app_vf_component_ref.htm" target="_blank"><span style="font-family: Courier New, Courier, monospace;">apex:canvasApp</span></a> component</li>
<li>Canvas app in the Publisher as a custom action</li>
<li>Canvas app in the Chatter feed as feed items</li>
<li>Canvas in the Salesforce1 app via the navigation menu</li>
</ul>
<div>
<br /></div>
</div>
<h3>
Lightning Connect</h3>
<div>
<br /></div>
<div>
Instead of feeding data back and forth with integration jobs or real-time callouts, Lightning Connect offers a speedy alternative for surfacing external data in Salesforce, using the OData protocol. Simple scenario: Data stored in an on-premise database table can be exposed with a few clicks as an object in Salesforce, that looks and feels to end users like any other standard or custom Salesforce object. No code required!</div>
<div>
<br /></div>
<div>
Furthermore, Summer '15 added some really cool features to Lightning Connect, such as a native Salesforce Connector and the ability to access government and health data backed by Socrata Open Data Portalâ„¢. But in my opinion Lightning Connect will become absolutely, ridiculously amazing once <i>write </i>capabilities (<a href="http://www.slideshare.net/developerforce/access-external-data-in-realtime-with-lightning-connect" target="_blank">still in Pilot</a>) become GA, along with support for Process Builder, validation rules and Apex triggers.</div>
<div>
<br /></div>
<h3>
HYPERLINK function in a formula field</h3>
<div>
<br /></div>
<div>
Why do I even bother mentioning this? I think simply that the cheapest, crudest means of "integrating" two systems should not be overlooked as an option. Time is money, and if an external system supports deep linking or can process redirects to specific records, using a formula field to dynamically present a clickable URL to a user can be a really quick win.</div>
Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.comtag:blogger.com,1999:blog-6037610034560047335.post-91640618702437773772015-05-25T21:46:00.001-04:002015-05-25T21:52:15.849-04:00Force.com Query Optimizer FAQ (cont'd)The webinar, <a href="http://wiki.developerforce.com/page/Webinar:_Inside_the_Force.com_Query_Optimizer_(2013-Apr)" target="_blank">Inside the Force.com Query Optimizer</a> delivered in April, 2013 is still featured as a relevant resource on the <a href="https://developer.salesforce.com/page/Architect_Core_Resources" target="_blank">Architect Core Resources</a> page. While the webinar explains many key considerations for designing queries, many questions linger. Let's take a closer look at these open questions, and let me know if you have any of your own to add!<br />
<br />
<h3>
Why does query optimization affect me as an admin? I don't write code.</h3>
<br />
If I were a betting man, I would bet that under the hood, SOQL queries, reports, list views and related lists on detail pages all tap into the same query execution framework. So, if you manage page layouts, reports and/or list views, you should care about the Force.com Query Optimizer.<br />
<br />
<h3>
What is a selective query?</h3>
<br />
A <b>selective query </b>is a query that leverages indexes in filters to avoid full table scans and to reduce the number of records in your result set below the selectivity threshold.<br />
<br />
<h3>
Stupid question, but what is an index?</h3>
<br />
This is a great question. Simply put, an <b>index </b>is a field-based mechanism by which a query can execute significantly faster, compared to execution without the index. Salesforce technical architects don't need to know how an index actually works behind the scenes, much like office workers don't need to know how a Keurig machine makes coffee at the press of a button.<br />
<br />
A technical architect probably just needs to know that fields are either indexed or not indexed, and that indexed fields should be used in query filters. The technical architect should probably also know off-hand what <a href="https://help.salesforce.com/help/pdfs/en/salesforce_query_search_optimization_developer_cheatsheet.pdf" target="_blank">standard indexes</a> are available, and that custom indexes have lower selectivity thresholds and can only be <a href="https://www.salesforce.com/us/developer/docs/apexcode/Content/langCon_apex_SOQL_VLSQ.htm" target="_blank">created by Salesforce Support</a>.<br />
<br />
Anyone hardcore enough to dig into the Oracle database-level index machinery may want to check out the <a href="http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-830-database-systems-fall-2010/" target="_blank">Database Systems</a> course, offered gratis through MIT OpenCourseWare.<br />
<br />
<h3>
What's considered a standard index?</h3>
<div>
<br /></div>
<div>
Great question! I wasn't able to find concrete documentation on this question, so you tell me (<a href="https://twitter.com/martyychang/status/602967154393108480?refsrc=email&s=11" target="_blank">on Twitter</a>)! I think an official answer would be a welcome addition to the "<a href="https://help.salesforce.com/apex/HTViewSolution?id=000181277&language=en_U" target="_blank">Force.com Query Optimizer FAQ</a>" article. In the meantime, the list of <a href="https://help.salesforce.com/help/pdfs/en/salesforce_query_search_optimization_developer_cheatsheet.pdf" target="_blank">indexed standard fields</a> can be considered to all be standard indexes.</div>
<br />
<h3>
What is the selectivity threshold? And why should I care?</h3>
<br />
The <b>selectivity threshold </b>is the maximum number of records that can be returned in a result set, without disqualifying the index-based optimization option for a query. See the <i><a href="https://help.salesforce.com/help/pdfs/en/salesforce_query_search_optimization_developer_cheatsheet.pdf" target="_blank">Query & Search Optimization Cheat Sheet</a></i> for the exact calculations used with standard indexes and custom indexes.<br />
<br />
<h3>
Is there a difference between using nested queries vs. separate queries?</h3>
<br />
This was the first question asked on the <a href="http://wiki.developerforce.com/page/Webinar:_Inside_the_Force.com_Query_Optimizer_(2013-Apr)" target="_blank">Inside the Force.com Query Optimizer</a> webinar during Q&A, "Is using nested queries good practice?" But I don't think the answer fully addressed the question. My personal guess (which needs to be validated in an org that actually contains large data volumes) is that <b>nested queries have negligible impact on the execution time of a query</b>, as long as the queries are not constructed in a way that uses the <span style="font-family: Courier New, Courier, monospace;">NOT </span>operator.<br />
<br />
The basis for my conjecture is a best guess that nested queries are executed sequentially, following an order of operations that allows the result set from one query to be used in another query. How true is this? I suppose I'll need to test all of the following query structures with selective filters applied:<br />
<script class="brush: sql" type="syntaxhighlighter"><![CDATA[
-- Select child records in parent object query
SELECT Id, (SELECT Id FROM Experiments__r)
FROM Hypothesis__c
]]></script>
<script class="brush: sql" type="syntaxhighlighter"><![CDATA[
-- Filter parent object query using child records
SELECT Id
FROM Hypothesis__c
WHERE Id IN (SELECT Hypothesis__c FROM Experiment__c)
]]></script>
<script class="brush: sql" type="syntaxhighlighter"><![CDATA[
-- Select parent fields in child object query
SELECT Id, Hypothesis__r.Status__c
FROM Experiment__c
]]></script>
More importantly, however, it's worth noting that in some instances executing separate queries in Apex is unfeasible, and <b>the only viable alternative is to use nested queries</b>. I could be wrong (and please correct me if I am), but one such situation could be an Apex method that needs to iterate through a result set using a <span style="font-family: Courier New, Courier, monospace;">for </span>loop, processing child records for each record returned.Anonymoushttp://www.blogger.com/profile/12785984085837601535noreply@blogger.com