AddThis Feed Button

As part of the release of Force.com Sites , Salesforce has made available a Free 100-user Force.com Platform account . This makes it very easy for people to construct web-based database apps without infrastructure worries. It’s so simple, that even a Business Analyst can do it!

This is similar to the standard Free Developer Edition account, which offers a 2-user, fully-featured Salesforce.com CRM/SFA account, which is great for developers and evaluators.

The Force.com offering, however, is even more amazing. It’s a fantastic Google-like move, aimed at gathering momentum for their platform offering. The Force.com multi-tenant architecture means that giving the free accounts won’t consume many additional resources, aside from storage and some processing when people are online. But it’s a great way to attract people away from traditional alternatives like in-house database apps built on Microsoft Access or SQL Server.

The Force.com platform includes a database, highly configurable UI, workflow, Java-like customisation language that includes Triggers and web services, field-level security, API, the new publicly-accessible Sites offering and heaps more. It’s everything you need to make web-based custom apps, without the Developer!

I’m already thinking of some apps that I could deploy to the free Force.com platform offering — small systems involving just a few people, that would otherwise require me to get a developer to write a Grails-based app, or reuse some of our existing systems to do things a bit outside their intended design.

Interestingly, it means I’ll be creating a free Force.com org separate from my company’s existing Enterprise license because I don’t want to use my full-priced Enterprise CRM licenses for users who just need access to a simple database app. It would be much better if Salesforce gave me 100 free Platform licenses to add to my existing Enterprise org — less logins, more centralised data. Alas, at $50/user/year (or the current special of $20/user/year if I buy a license for every employee) is too expensive to justify the simple apps. So, free version it is for me!

The Terms & Conditions attached to the Free Force.com account are:

  • 100 Users
  • 10 Custom Objects per user
  • 10MB storage per user (so 1GB in total)
  • Various restrictions are Sites and processing time

However, reading the fine print also reveals that Salesforce do not actually enforce these limits. Rather, it’s up to the customer to ensure, for example, that a particular user profile can only access a maximum of 10 custom objects. They balance this with the right to audit and the requirement to purchase full-version licenses if the limits are violated.

The Bottom Line

  • The Force.com Platform is great for creating custom web-based apps
  • The free Force.com Platform offer is very attractive for small-scale apps
  • This will help publicise Force.com as a viable development platform

Today I had to add 32 Checkbox fields to my Contact object. I could have done this by manually creating each field, but the thought of clicking through over 100 screens didn’t take my fancy. So, I thought I’d take the Force.com Metadata API for a spin.

The last time I played with Metadata, I did it via the command-line Metadata Migration Tool . This time I decided to do it via the IDE.

Fortunately, there’s plenty of information available on this topic, the best of which are:

First I updated my Force.com IDE extensions in Eclipse (always make sure you’re working on the most recent version!). One nice feature I see they’ve added is the ability to select exactly which objects are copied down to the IDE. I simply ticked ‘Contacts’ and it appeared in my Package Explorer.

Then, I just did a Copy & Paste job on the metadata, changing the labels for each field.

<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
    <fields>
        <fullName>BI_Owns_Active_Bamboo__c</fullName>
        <defaultValue>false</defaultValue>
        <label>Entity Owns Active Bamboo</label>
        <type>Checkbox</type>
    </fields>
    <fields>
        <fullName>BI_Owns_Active_Clover__c</fullName>
        <defaultValue>false</defaultValue>
        <label>Entity Owns Active Clover</label>
        <type>Checkbox</type>
    </fields>
etc.

I then hit Save and the fields magically appeared in Salesforce.com — I was even somewhat naughty and did it straight into my Production instance (although best practice would be to do it in the Sandbox and then copy across / deploy the change).

Okay, I’ve still got a bit of work to do if I want to add the Checkboxes to my Page Layout, but at least the laborious part is over — the rest is all Drag & Drop!

The Bottom Line

  • The latest Force.com IDE facility lets you choose Standard Objects without having to play with package.xml
  • Creating lots of fields can be done quickly via the IDE, rather than the normal Force.com UI

Once upon a time, I had to Override the standard New button on my Contact object to set a field value as part of the record created. (Specifically, I wanted to set the Account field to a default value.) I did this by creating a Custom S-Control:

It has to be done as an S-Control because the ‘Override’ function only permits S-Controls and Visualforce pages:

That all went very well.

Cloning

I have since been requested by my users to permit Cloning of a Contact record. Here at Atlassian, we have Contacts with two Record Types:

  • Manually created: Fully editable within Salesforce
  • Imported from our Customer Database: Mostly read-only, loaded via DataLoader

I have no problems with peole cloning ‘Manually created’ Contact records, so I provide the normal Clone button. However, for Contact records ‘Imported from our Customer Database’, I was not willing to permit cloning because:

  • The record type would need to be changed (to ‘Manually created’)
  • The ID field from our Customer Database would need to be cleared
  • Many fields should not actually be cloned (eg Name) because the prime purpose of enabling Clone was to save typing when creating a new Contact from the same company as an existing Contact

In the end, instead of a ‘Clone’ button, I decided to implement a ‘Copy’ button that created a new record but automatically copied selected fields across to the new record:

Two important things to note:

  • Field values are passed in the URL, eg ‘con4′ is the Account field
  • I appended a custom field to the end

The technique of setting fields in the URL is standard in the user community. I don’t know if it’s meant to be officially done, but enough people do it that it is pseudo-official. I get field names (eg con4, con19street) by viewing the Page Source of the Salesforce page and looking for the field of interest. Hopefully they’ll stay the same across change to the Page Layout.

Custom fields, however, are known to produce problems. Rather than having a nice field name (eg conRegion__c), the Page Source uses the ID of the custom field. You can prove this by putting the ID in a URL, such as na1.salesforce.com/00N20000001Bx9v. Unfortunately, this results in an error because the URL doesn’t understand something that starts with two zeros.

By a stroke of luck, I found that I could append the Custom Field to the end of the URL (outside the URLFOR command), which would be correctly added to the generated URL that creates the new object. This has driven people crazy for ages, so I figured it was worth a blog post to help future generations of Salesforce Administrators!

The Bottom Line

  • Add custom fields to a URL by placing the custom field assignment AFTER the actual URLFOR statement
  • It can be easier to create a New record rather than a Clone, especially if you don’t want to copy most fields
Tags: Apex

I came across a thread about Dataload batch size and triggers in the Salesforce Developer Community.

Several people are reporting strange behaviour in their Triggers, such that a batch of 200 records actually calls the Trigger twice, but variables are not reset. Apparently it’s an intended behaviour and not a bug, but it’s not documented.

As a result, I’ve reduced all my batch sizes to 100 to avoid any potential problems. You might want to do the same.

The Bottom Line

  • Beware of Triggers with batches of over 100 records
  • The online Developer Community is great for finding answers to strange situations
Tags: Apex

I had been keeping an eye on our Storage Quota and suddenly noticed that we had passed 100%. We don’t store documents on our Salesforce instance — just object data, most of which is imported from our legacy system. Unfortunately, this had been filling up and we exceeded our 1GB quote (20 users).

First, I used Mass Update Anything (available for both Mac and Windows) with a query to find some old data records. I could then delete those records and re-query. Unfortunately, it only processes about 1000 records at a time.

Then, I saw a Custom Object that I could do without. It was taking 80MB, so I deleted the object. Unfortunately, the 80MB remained allocated — perhaps because the Recycle Bin only empties after 30 days, but I’m not sure. Also, Mass Update Anything didn’t like querying that object, so I was stuck.

Then I remembered a post I had read that morning from MK Partners about using the Salesforce System Log window to mass-update some data. Matt had written some quick Apex that executes in the System Log window. So, I modified it to do a mass delete.

Two points to note:

  • The SELECT statement can only return 1000 records, lest you receive the error “System.QueryException: Inline query has too many rows for direct assignment, use FOR loop
  • The whole Apex block can only use 10,000 query rows (hence the 10x loop)

So, I had to run it a few times to delete all my records. It all worked very well.

Oh, one more point. When a custom object is deleted and brought back to life, it gets renamed (eg my_obj_del__c), so take a look at the object page to find the correct API name.

The Bottom Line

  • Apex can execute in the System Log window
  • Apex governor limits can be mostly avoided
  • It pays to read blogs!
Tags: Apex

I had to deactivate a Trigger in Production recently and had a darn difficult time doing so!

At first, I tried to just make it Inactive. I did this by opening the metadata file within Eclipse, and chaning Active to Inactive. However, my change could not be saved to the server because of a Test that failed. I think it was a test for the Trigger, which was no longer active! In the end, things got so confused that I decided just to delete the Trigger rather than deactivating it. (This would also be less confusing for other people to support, since it might not be obvious that a Trigger is inactive.)

I then tried to delete the Trigger in Production via the Setup screen (normal Salesforce UI). However, in Production, the Del option is not displayed.


Triggers in Production — no option to delete

So, I went into my sandbox (where I do my Trigger development) and deleted the Trigger from there. I also deleted by TestMethod that tests the Trigger.


Triggers in the Sandbox — a Delete option at last!

Finally, I went into Eclipse and did a Refresh from Server. This made the files disappear within Eclipse.

I then deployed to Production, ticking Delete next to the files in my Deployment Plan. (For safety, I always Deselect All when deploying, then sort by Type. Only then do I tick which files to deploy, since choosing the wrong ones could cripple Production.)

The Bottom Line

  • It’s more obvious to delete Triggers than deactivate them
  • Deleting Triggers from Production isn’t straight-forward
  • Delete them from Development/Sandbox first, then deploy the change to Production
November 11, 2008
Tags: people

Bloggers of the world unite!

Fresh from Dreamforce, I just had the pleasure of meeting David Schach, better known for writing the X-Squared on Demand blog .


The Enforcer (left) & David Schach (right)

David showed me some of the stuff he has been doing with Visualforce, which made me quite jealous. Take a look at his recent blog post of his Upcoming Plans to see what’s keeping him excited. (Oh, and thanks for the link to drop.io — very nice!).

The Bottom Line

  • The world’s a small place
Tags: Apex

I’ve been working on a fun problem for Force-Friend Nick (Hi Nick!).

The requirement:

  • When an Opportunity is closed, workflow sends an email to the Accounts people (who don’t have a Salesforce login)
  • The email is to include address details of the Primary Contact associated with the Opportunity

Some relevant information:

  • Contacts are linked to Opportunity objects via the OpportunityContactRole object, which contains an OpportunityId, a ContactId and an IsPrimary flag (plus various other fields)
  • Cross-object formulas can’t span the Opportunity-Contact relationship

The Solution

There are several ways this could be implemented:

  • Move the email generation from workflow to Apex, which would give full access to the object model (but lose the easy user interface)
  • Create a Trigger on OpportunityContactRole to copy information onto the Opportunity (unfortunately, we discovered that you can’t create a Trigger on this object!)
  • Create a Trigger on Opportunity to ‘pull’ the information from the Primary Contact onto the Opportunity record

We decided to go with the Trigger on Opportunity. The information is only wanted when the Opportunity is closed, so it means we could write the Trigger to fire on closure of the Opportunity. It then looks for the Primary Contact (as flagged by the IsPrimary field) and copies it to a Custom Field (Primary_Contact__C) on the Opportunity object, which can then be used by cross-formula functions within workflow.

Here’s the code for the trigger:

trigger SetPrimaryContact on Opportunity (before update) {

   for (Opportunity o : Trigger.new) {
     // Check if the Opportunity just closed
     if (o.IsClosed && !Trigger.oldMap.get(o.id).IsClosed) {
       OpportunityContactRole contactRole =
            [select ContactID from OpportunityContactRole where IsPrimary = true and OpportunityId = :o.id];
       if (contactRole != null) {
         o.Primary_Contact__c = contactRole.ContactID;
       }
     }
   }
 }

Simple result, but took a bit of thinking to create it.

The only funny-looking code is !Trigger.oldMap.get(o.id).IsClosed, which retrieves the old value of the IsClosed field to see that it really changed. I use oldMap because I read somewhere that you should not rely on Trigger.old and Trigger.new being perfectly aligned (that is, for example, Trigger.old[4] and Trigger.new[4] do not necessarily refer to the same fields). However, I can’t find that reference anymore, so feel free to tell me if I’m right or wrong!

The Bottom Line

  • Not every object permits a Trigger
  • Cross-object formulas also have their limitations
  • Sometimes a bit of Apex trickery saves having to buy licenses for extra users!

Budding psychologists out there may be aware of Maslow’s hierarchy of needs, which is part of his 1943 paper A Theory of Human Motivation. Maslow puts human needs into a hierarchy:

Following Maslow’s lead, I wish to propose a Force.com Hierarchy of Skills:

The hierarchy has many omissions and contains much that is apocryphal (or at least wildly inaccurate), but does give an indication of the progression of skills while learning Salesforce.com and the Force.com platform.

Let’s take a look at each component:

Standard Objects & Functionality represent the first level of knowledge, which most users of Salesforce.com discover over time. Interestingly, while I’m the Administrator of my company’s Salesforce.com system, I don’t actually know all of the functionality, such as how to use Activities and Email templates. I can trust the users to figure that out.

When using the Force.com platform, there are no standard objects or functions. (Well, there’s a few like Users, but not the Sales-oriented stuff like Opportunities and Campaigns.)

Administration functions give access to the inner workings of the platform, such as adding Objects and Custom Fields, modifying Page Layouts and managing Security.

Reporting could arguably fit into either of these first two levels — it’s simple enough to give to Users, but is easier to understand and build reports if you know about Custom Fields and Formulas.

Workflow and Data Import build upon the knowledge of objects and fields. I’m talking about using the Data Loader rather than the Import Wizard. In fact, during a visit I once had to the Salesforce.com office in San Francisco, even the developers down-talked the Import Wizard. The Data Loader is very powerful and can do some magic with External IDs, but that needs a good understanding of the lower-level Admin skills.

Workflow similarly builds upon Admin skills, requiring knowledge of how information is updated and linked between Objects. It’s not hard to configure, but can be challenging to get ‘right’ in a system with lots of exceptions and strange uses.

Visualforce & S-Controls begin the concept of on-demand programming. I’ve grouped them together because Visualforce is actually replacing what S-Controls previously enabled. S-Controls have the benefit of using the familiar JavaScript language, but Visualforce is more integrated with the Force.com platform.

Apex represents the, umm, ‘Apex’ of Force.com skills. It provides powerful low-level Triggers and custom logic that can be hard to define in a point-and-click system. However, this power comes at the cost of complexity in learning the language, the development environment and the nuances of Force.com development. Fortunately, it comes with good error-prevention capabilities such as strong type checking, automatic reference checking between Apex code and objects, and good deployment tools.

I’ll admit that I was considering putting Visualforce above Apex, since Visualforce can ‘use’ underlying Apex logic (eg controllers). It’s also newer than Apex and you’re likely to find more Apex-skilled administrators than Visualforce-skilled. Nonetheless, I’m keeping Apex at the top due to its higher complexity and the great pun associated with the word ‘apex’.

I’ve got a few experiments in mind to test the validity of this Hierarchy. I’ll post the results when finished.

The Bottom Line

  • If Maslow had access to Force.com, his Apex would have been different

Today I had to create campaigns to track Trade Show attendees. I’ve written previously about creating campaigns from Webex attendee files, but my Trade Show source files were not as friendly. Each Trade Show organiser gave us lists in different formats — the best one contained complete Name, Address, Company, Title and Email information, while the worst had just First Name and Email (yes, really!).

My requirements were:

  • Load information from disparate source documents
  • Create Contacts, not Leads (we don’t use Leads)
  • Don’t overwrite existing Contact records (existing customers are populated from an External system, with more accurate information than Trade Show visitor data)
  • Add them to a Campaign for tracking purposes

Importing Contacts

The first step was quite easy with the help of the Salesforce.com Data Loader (known as LexiLoader on the Mac). It can import Contact records from a CSV file. The trick here, however, was to use INSERT rather than UPSERT. My system is configured to use Email as an External ID for Contacts, so the import will fail for records where there is an existing Contact with the same Email address. This is good — it lets me create new records only where the Contact already does not exist.

Also as part of the import, I added a column for RecordTypeId. This is because I have two Record Types for my contacts:

  • Manually created contacts (which let users edit most fields) — this is the one I selected, and
  • Automatically created contacts (which come from my external system, and don’t let users edit fields since they are slaved from my external system)

I also added a column for AccountId, which is quite important. Any Contact records that aren’t linked to an Account are only visible to the record creator. In our case, we don’t actually use Accounts (I’ll blog about that in future), so I just linked all the new Contacts to a ‘Default Account’ to make them visible.

Adding Contacts to Campaigns

There’s lots of ways to add Campaign Members to Campaigns, either from Lead/Contact records or from the Campaign record. In this case, I wanted to add them in bulk, so I used the Manage Members button:

To load in bulk, I wanted to use an “Import File” capability, but I didn’t want to load Leads. So, the Add Members - Import File option was no good, since it only loads records as Leads. Instead, I used the Update Status - Import File option which can not only update a status, but also associate a Contact with a Campaign. In other words, it can take existing Contacts and add them to a Campaign — Perfect!

Unfortunately, Import File wants a file that contains the Salesforce Contact ID. This is a problem, since the ID is generated within the Force.com platform and is not in my CSV file. The online hints for that command suggest creating a Report on campaign members, saving as CSV then importing them to change the status. This is not an option for me since some of the people are already in Salesforce.com as existing Contacts and others I’ve just created. So, there’s no identifier that lets me select them all in a Report.

So, here’s a trick… After using DataLoader, two files are created. A success file contains a copy of all records that were successfully loaded, with a new ID column inserted. I can use this file with the Import command to update all the statuses. This takes care of all the Contacts that were created during the original Insert of Contacts. Fantastic!

But what about the Contacts that already existed, and therefore caused an error during the Insert? Well, the ID is actually supplied in the error message (eg duplicate value found: EmailAddr__c duplicates value on record with id: 0032000000Fs7Xp) and can be extracted using a RIGHT(B2,15) type of spreadsheet command. Just put that value in a new column, import the file and you’re done!

(To slightly simplify things, it’s also possible to load just one file — the original CSV file! Since all records now exist, every row will generate an error. You can then just process the error file rather than separately loading the success and error files.)


I’ll admit that during this process I stuffed up — I imported attendees to the wrong campaign. To remove them, I could have deleted the Campaign and started again. Instead, I used a favourite utility called Mass Delete Anything (there’s a Windows and a Mac version). I just created an appropriate SOQL command to identify the offending records, then Mass Deleted them. Very handy, but also very dangerous. Use with care on your Production systems.

The end result of all this… My Campaigns can now report counts of Total Contacts for the Trade Shows. When combined with a Trigger I’ve got that identifies people when they purchase products, I can track the conversion rate for Trade Show attendees, all without having to manually type any Contact information.

The Bottom Line

  • You can’t import Contacts directly into Campaigns, but you can import a list of existing Contacts to link to a Campaign
  • DataLoader saves the day again, especially with the information saved in its success and error output files