Chatter and the CLM
I love Chatter. I love the way that it is a game-changer. I think that Chatter is a powerful tool. But as Peter Parker's Uncle Ben said, "With great power comes great responsibility," and with the possibility of great rewards from Chatter comes great risks.
First I'll list my conclusions, just so there's no misunderstanding. Then I'll explain some of the possible pitfalls that Chatter presents.
- Chatter is amazing. It lets users share information in ways never seen before.
- Chatter should be enabled in an org as an all-or-nothing switch - as it is now. Allowing it to be disabled for some users goes against its core purpose.
- As a single-opt-in system, the risk of Chatter-Spam is huge, but responsibility falls to each user to self-police, and probably to each company to educate its users in appropriate chatting.
Let's compare Chatter, Facebook, and Twitter in terms of what is necessary for a conversation between two people to appear in my activity stream.
Facebook is a double-double opt-in system. To see a wall post from one person to another in my stream, I must be friends with both of them, and each friendship-connection is a double opt-in, meaning that one person requests and the other approves.
Twitter is a double-single opt-in system. To see one person's reply to another, I must be following both of them. But unless one of them decides to block me, the default is that I will see the entire exchange in my twitter feed.
Chatter
Chatter is a single-single opt-in system. This means that if I post something to anyone in the org, everyone following that person will see. Here's an example: Let's assume that every user at salesforce.com follows Marc Benioff. One user posts a photo of his son's graduation to Marc. Everyone following Marc will see it.
Sounds innocuous, right? Probably. But if a user keeps posting silly things to Marc, and enough people see it, other users could become annoyed.
Take it down a level, and imagine that everyone on a sales team follows each other. A junior AE keeps sending silly stuff to the strongest seller in the group, and all the sales people have to see it. This could clog their streams.
This is called a CLM: A Career-Limiting Move. As great power requires great responsibility, we must ask who should shoulder this responsibility.
- The company: Training for users and a quick eye to bring inappropriate chatters into line.
- Individuals: Just be careful. This is the Facebook public-posting dilemma. Don't forget that you have no control with whom you are connected, so anyone who wants to follow you will see anything you post, and anyone who follows someone you post to will see it as well.
The solution is NOT to turn off Chatter for certain people - Chatter is about the free-flow of data across an org, organized into forms that make it useful information. By putting information at the fingertips of every user, productivity will be increased.
Blocking people is also not a good solution.
Chatter Groups (Safe Harbor Statement!) will help, but will attenuate, not completely remove, the chances that this will happen.
Salesforce CRM should be a "sticky" app - that is, it should provide services in one place so that users see it as their central point of information. The best way to ensure the free-flow of information is to prevent blocking of certain users.
Sophisticated DateTime “Formula Fields” with Apex and Field-Level Security
What do you do when you want to calculate a formula-like field but a regular formula won't work?
Salesforce CRM's formulas handle dates very well. If you want to enter a date value and have formula fields display, for instance, mydate__c + 21 days, that's simple. Just use mydate__c + 21.
Side note: If you try going the long way around and use DATE( YEAR( mydate__c ), MONTH( mydate__c ), DAY( mydate__c ) + 21 ) and mydate__c = 09/17/2010, Salesforce returns #Error! because there's no date 09/38/2010. Similarly, adding three months to a date like 1/31/2010 will also give an error. More about this in a future post.
DateTime fields are like Date fields, but they include... wait for it... a time component (and can be created in the running user's local time zone or in GMT).
Here's a use-case for a DateTime formula field:
A photography studio schedules photo shoots, and different packages include different durations. Similarly, we could use a hair salon which offers different services, each with a different duration, a dentist... you get the idea.
Requirements:
- Enter a DateTime for an appointment start time (
starttime__c) - Enter a duration (though in a production system, I'd include a value on the
Product2sObject, we'll just enter a value here) (minutes__c) - Display a read-only DateTime field with the end time (
endtime__c) - The end time must be read-only to all users, like any formula field
Here's what won't work:
- A formula field won't work because there are no MINUTE(), HOUR(), SECOND() formula functions
- Workflow won't work because it depends on formulas to fill new values for date/datetime fields
That leaves Apex. First, the configuration:
- Create DateTime field
starttime__c - Create DateTime field
endtime__c - Set
endtime__cfield-level security to Read-Only for all profiles - Create Number (18,0) field
minutes__c - Create a trigger on the sobject
Here's the trigger:
trigger timeTrigger on TestObject__c (before insert, before update) {
for (TestObject__c t : Trigger.New){
if(t.StartTime__c != null && t.minutes__c != null){
datetime myDateT = t.StartTime__c;
double d = t.minutes__c;
Integer shootmins = d.intValue();
if(mydateT != null && shootmins != null)
t.EndTime__c = myDateT.addminutes(integer.valueof(shootmins));
}
}
}
Regular readers will note that I do usually split triggers into a trigger and a class, but I've not done so here purely for the sake of brevity.
Here's the test code:
public without sharing class shootTimesTriggerTest {
private static testMethod void ShootCalculateEndTime_PositiveTestCases() {
TestObject__c to;
TestObject__c l;
test.starttest();
l = new TestObject__c (name = 'test');
datetime myDateTime = datetime.newInstance(2008, 12, 1, 12, 30, 2);
l.StartTime__c = myDateTime;
l.minutes__c = 90;
insert l;
to = [SELECT id, EndTime__c FROM TestObject__c WHERE id = :l.id];
datetime newDateTime = datetime.newInstance(2008, 12, 1, 14, 0, 2);
system.assertequals(to.EndTime__c, newDateTime);
l.minutes__c = 45;
update l;
to = [SELECT id, EndTime__c FROM TestObject__c WHERE id = :l.id];
newDateTime = datetime.newInstance(2008, 12, 1, 13, 15, 2);
system.assertequals(to.EndTime__c, newDateTime);
test.stoptest();
}
private static testMethod void OppCalculateEndTime_NegativeTestCases() {
test.starttest();
TestObject__c l = new TestObject__c (name = 'test');
l.minutes__c = null;
insert l;
system.assertequals(l.EndTime__c, null);
test.stoptest();
}
}
A few points about how this works:
- Triggers run in System mode, so they don't respect field-level security. Thus, we can set a field to read-only for all profiles, and the EndTime__c field will still be updated.
- The test code runs in System mode as well, avoiding any potential problems if the field were set to invisible to a profile and we used System.RunAs() to test for various profiles.
- Although I'm not a fan of using SOQL queries this often, I used these in the interest of saving time. Keep in mind that if you had quite a few queries in your regular code, adding these two might put you over the limit, so use queries sparingly!
- This is the only way I know of to add minutes to a DateTime.
Did I miss anything? Please let me know in the comments.
Get Documents and Attachments out of Salesforce
As Content will be included in all Salesforce licenses (for completeness, I'll add 'to some degree') with the Spring '10 release, orgs will be faced with the daunting prospect of getting their documents and attachments out of Salesforce and into Content.
I had this problem when Content was first released and I was asked to be one of the first SysAds to use it. At the time, we used Solution 1 (below), but since then, other products have been released to help with this.
Why is it even an issue?
- Surely we can download each file? Yes, but who wants to?
- Can't we do a Data Export and then upload those to Content? Yes, but all the files are renamed with their 15-character Ids, making renaming them all-but-impossible.
salesforce.com and DreamFactory to the rescue!
Solution 1
Summary: Use a script to rename all exported files. A (wonderful!) salesforce.com employee, Nick Marcantonio, wrote a Perl script to perform the transformation. Here it is, in all its glory:
# Nick Marcantonio
# nmarcantonio at salesforce.com
# 08/07
$file = 'Attachment.csv';
open (F, $file) || die ("Could not open $file!");
$line = <F>; #read first line which is nothing but column headers
while ($line = <F>)
{
($id,$name) = split ',', $line;
chomp($id);
$id =~ s/\"//g;
chomp($name);
$name =~ s/\"//g;
#print "$id : $name\n";
$result = rename($id, $name);
#print "$result\n";
}
close (F);
The instructions:
If you've done a data export you've noticed that all attachments are placed in the Attachments subfolder and named with their salesforce ID, not the actual file name or extension. One must then consult the Attachment.csv file included in the data export to find the name associated with the ID and rename the file. Attached to this solution is a Perl script that will rename all of the exported attachments to their proper names. Please follow these steps to run this: 1. Perform a data export and unzip the resulting zip file 2. Launch the data loader and export from the Attachments table ONLY the Id and Name column. This file must be named Attachment.csv. 3. Install ActivePerl. This will allow perl scripts to be run on a Windows machine. ActivePerl is available here (http://www.activestate.com/activeperl). 4. Copy the Attachment.csv file and the attached AttachmentParser.pl file to the Attachments subdirectory of the data export. 5. Double-click on AttachmentParser.pl. All of the files named with their salesforce IDs will be renamed with their proper names and file extensions. (This solution will work for documents as well. Follow the same procedure and be sure to name the extract from the Documents table Attachment.csv)
Note: This will not preserve folders, as far as I know. You may be able to recreate this by exporting the Folder table and doing some work on that, as the Document table does include a FolderId column.
A heartfelt thank-you to Nick Marcantonio for his help!
Solution 2
Install DreamFactory's FREE DreamTeam Document Management application from the AppExchange to drag-and-drop your Documents to your desktop.
This doesn't work with Attachments, though, so you may need to use another method for them.
Please let us know how it goes - good luck and enjoy Content!
Filtered Lookups, Validation Rules, and Order of Execution
Reading the cheatsheet for Filtered Lookup (beta), I noticed an interesting line:
Lookup filters function similarly to validation rules when you save a record. That is, actions that cause related records to save, such as changes to a roll-up summary fields, also trigger the lookup filters on the related record and block the save.
The implications for this are massive. Let's explore two examples:
Example 1: Filter as Validation Rule from Parent Record
- We create a lookup on a Child object to Parent.
- We filter the lookup to EXCLUDE Parent.Status = 'Closed' (Parent.Status is only Open or Closed.)
- We can edit the Child records as long as the Parent Status is not Closed.
- When Parent.Status is changed to Closed, existing related Child records are not affected...
- BUT if we attempt to edit a Child when the Parent is Closed, Force.com will throw an error (which we can customize) beause that the Lookup is invalid.
- (and clearly we cannot add new Child records either)
Conclusion: Thus, Filtered Lookups act much like Validation Rules. A quick experiment shows that Filtered Lookup errors actually fire before Validation Rules.
Example 2: Filter as Validation Rule on Roll-Up Summary (from Child Record) - what the line above was referencing
- Use the above example, but change the lookup to a master-detail relationship
- Create a Roll-Up Summary field to count all child records
- Prevent saving more than 10 child records for one parent record
Here, we have triggered a filter error without touching a parent record, yet we throw an error based on a value on the parent record.
This second example is significant because we could already prevent more than 10 child records from saving, but doing so required a Roll-Up Summary field on the parent object AND a Validation Rule on the child object. Now we can replace the Validation Rule with the Lookup Filter, though we still need the Roll-Up Summary field. Whether or not this simplifies things is definitely up for debate...
Conclusion
This is a very powerful feature! Thanks to salesforce.com for rolling it out, even in beta form.
Real world example: The above example would be great for Time Sheet Entry and Time Sheet Header objects, as they would create, in effect, a validation rule on the Header record preventing editing of any child records. Awesome!
For further reading, check Salesforce Help's Lookup Filters examples.
Preparing a New Org
With the impending arrival of the Winter 2010 (aka 162 or Winter'10) edition of Salesforce CRM, as with every other release, comes a prerelease org. (You can get one at https://www.salesforce.com/form/trial/prerelease_winter10.jsp.)
Every time one encounters a fresh org, there are maintenance tasks to perform. I usually go through an org (whether a Developer Edition org or a Prerelease version) and do the same tasks, generally in no particular order. This time, however, I wrote down what I did as I did it. Looking at the list, it's hardly in any "best practices" order at all - it's just how I did it.
There's no need to follow every step, and it is not a complete list of all possibilities, but this should give you some idea of the possibilities and available tweaks: (*** indicates some of the new features in WInter '10)
- Save login with 1Password/Roboform
- Reset (Set) Security Token
- Administration Setup | Security Controls
- Session time 8 hrs
- Passwords never expire
- Create Record Types (and Business Processes) for Lead, Opportunity, Case
- (Campaigns were not enabled in this prerelease org) - would have configured them here, similarly
- Activities section: Calendar link on sidebar
- Download latest versions of Connect for Outlook, Office Edition
- Opportunities:
- Enable Similar Opportunities
- Enable Opportunity Teams
- Create Account Master Record Type
- Enable Account Teams
- Create Contact Master Record Type
- Note: Asked to add to page layout. Not asked for Opportunities.
- Enable Case Teams
- Enable Public Solutions
- Solutions:
- Enable Solution Browsing
- Enable Solution HTML
- Could have created a Solution Process & Record Type
- Did not enable multilingual solutions
- Enable Self-Service
- Enable Web-to-Case
- Create default Owner, etc (auto prompted)
- Enable PRM and Partner Portal (though have no licenses)
- Salesforce to Salesforce
- Enabled S2S
- Set up S2S Connection Finder ***
- Added fields to page layout - Kept read-only for all profiles except System Administrator
- Enable Public & Private Tags
- Enable Console for all Profiles
- Search Settings - Enable Enhanced Lookup & Auto-Complete
- User Interface
- Separate loading of related lists
- Spell Checker on Tasks & Events
- Collapsible Sidebar
- Custom Sidebar on all Pages
- Enhanced Profile Management ***
- Set myself as default Workflow User
- Looked at Develop | Custom Settings ***
- Created a Default Queue and added myself
- Set all Sharing Rules to Private
- Update Home Page to the way I like it
- Order of wide section (top down): Calendar, Tasks, Items to Approve, Dashboard
- No changes to narrow section
Other things that may be possible in other orgs:
- Enable Customer Portal
- Customize Campaigns
- Set up Sites
Again, this is not meant to be a complete list. Also, it is not intended to be a how-to; for more information you may search the Help link at the top of every org page, check Salesforce Community, or Developer Force.
Happy configuring!


