Workflow referencing converted lead error

Today I came across a “strange” error message while developing a trigger.

The requirement is the following: We have a a CustomObject__c record linked to a Lead. When the Lead is converted we need to reparent the CustomObject__c and assign it to the newly created Contact. Sounds like a trivial task right ? We just need an after update trigger on Lead and some logic to update the CustomObject__c record accordingly.

Well I also though so, but then during testing I got this error message:

System.DmlException: Update failed. First exception on row 0 with id a2PL00000001UzSMAU; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, The formula in the “Some Workflow” rule or process is invalid due to the following:<br/>This lead has been converted.

First time I am getting this, and I have absolutely no clue what is going wrong. Let’s check the workflow to find out more.

AND(
ISPICKVAL(Status__c, ‘Email Sent’),
ISNULL(TEXT(Campaign__r.Secondary_Language__c)),
OR
(
TEXT(Contact__r.CommLang__c) = TEXT(Campaign__r.Secondary_Language__c),
TEXT(Lead__r.CommLang__c = TEXT(Campaign__r.Secondary_Language__c),
TEXT(Contact__r.CommLang__c) <> TEXT(Campaign__r.Primary_Language__c)
))

The workflow references the Lead__r relationship and is evaluated during the CustomObject__c update. Apparently Salesforce tries to validate the formula in bold and fails. This seems to happen due to the Lead record being already converted since we are in the Lead After update trigger context.

And now what ? I can’t deactivate the workflow but lets try another path.

Instead of evaluating the Lead__r.CommLang__c lets create a formula field on the CustomObject__c referencing it ! So after creating the formula field:

CustomField

and replacing the above workflow with this version:

AND(
ISPICKVAL(Status__c, ‘Email Sent’),
ISNULL(TEXT(Campaign__r.Secondary_Language__c)),
OR
(
TEXT(Contact__r.CommLang__c) = TEXT(Campaign__r.Secondary_Language__c),
Lead_Communication_Language__c =  TEXT(Campaign__r.Secondary_Language__c),
TEXT(Contact__r.CommLang__c) <> TEXT(Campaign__r.Primary_Language__c)
))

everything works !

It does make sense, but it’s not something you will consider when creating a workflow is it?

Posted in apex, trigger, workflow | Tagged , , , , | 1 Comment

Sync Opportunity Team Members between Opportunities with Apex

A very recent requirement which has dropped on my desk was to sync Opportunity Team Members between related Opportunities based on certain criteria (setting a flag on the parent record).

It is much more complex than it sounds because this requires triggers on Opportunity and on Opportunity Team Member, which is something you can easily guess with a minimum Salesforce experience. One tricky part is that you need trigger stoppers to control the recursive invocation of Opportunity Team Member trigger (in After Insert of Opportunity Team Member for the parent Opportunity you will probably Insert new Opportunity Team Members for the children).

However there is more…

Inserting Opportunity Team Members is not a straight forward operation because you also need to set the access via OpportunityShare as described here http://blog.jeffdouglas.com/2011/03/17/opportunityaccesslevel-not-writable/

and you are almost done.

The problem is that the OpportunityShare records for those with Read/Write are updated after the Opportunity Team Member Insert Trigger Context !! which means that if you query for OpportunityShare records with Edit ( OpportunityAccessLevel='Edit' ) you will get NO results.

Thus you need to use a @future method. Here is an example of the Opportunity Team Member Trigger

trigger OpportunityTeamMemberTrigger on OpportunityTeamMember (after delete, after insert, after update) {

    if (OpportunityTeamMembers.triggerStopperEnabled)
        return;
    
    if (Trigger.isInsert)
    {
        if (Trigger.isAfter)
        {
            OpportunityTeamMembers.registerRelatedOpps(Trigger.new);
        }
    }
     else if (Trigger.isUpdate)
    {
        if (Trigger.isAfter)
        {
            OpportunityTeamMembers.registerRelatedOpps(Trigger.new);
        }
    }

    else if (Trigger.isDelete)
    {
        if (Trigger.isAfter)
        {
            OpportunityTeamMembers.registerRelatedOpps(Trigger.old);
                  
        }
    }
    
    //2014.06.17 At the end of the trigger sync the Confidential Opp OTMs enabling the trigger stopper to prevent recursive execution of the trigger
    OpportunityTeamMembers.triggerStopperEnabled=true;
    OpportunityTeamMembers.syncConfidentialProjOppQuoteOppTeamMembers(); //future
    OpportunityTeamMembers.triggerStopperEnabled=false;
}

So it is a little bit more complex than it sounds ;-)

Posted in apex, Uncategorized | Leave a comment

Google charts and data privacy

Google provides a great javascript library to create really impressive charts for your web application. I have used it a couple of times and I managed to generate the desired chart in very little time.

However, there is always something to consider when using this library. As stated in the Security and Privacy section: “All charts depend on linked JavaScript libraries, and some might send chart data from the browser to another location for preprocessing.” This might be a show stopper for many clients, since data may be sent to external locations. If you want to find out more details about what data is sent you need to go to every chart type and read the Data policy. Since I had to do this in order to evaluate the solution for one of our projects I am summarizing this information in the table below

Chart Type Data Policy
Annotation Chart  All code and data are processed and rendered in the browser. No data is sent to any server.
Area Chart All code and data are processed and rendered in the browser. No data is sent to any server.
Bar Chart All code and data are processed and rendered in the browser. No data is sent to any server.
Bubble Chart All code and data are processed and rendered in the browser. No data is sent to any server.
Calendar Chart All code and data are processed and rendered in the browser. No data is sent to any server.
Candlestick Chart All code and data are processed and rendered in the browser. No data is sent to any server.
Column Chart All code and data are processed and rendered in the browser. No data is sent to any server.
Combo Chart All code and data are processed and rendered in the browser. No data is sent to any server.
Diff Charts NO INFO
Gauge All code and data are processed and rendered in the browser. No data is sent to any server.
Geochart Locations are geocoded by Google Maps. Any data that does not require geocoding is not sent to any server.
Histogram All code and data are processed and rendered in the browser. No data is sent to any server.
Intervals NO INFO
Line Chart All code and data are processed and rendered in the browser. No data is sent to any server.
Map Map are displayed by Google Maps. Please refer to the Google Maps Terms of Service for more information on data policy.
Organizational Chart All code and data are processed and rendered in the browser. No data is sent to any server.
Pie Chart All code and data are processed and rendered in the browser. No data is sent to any server.
Sankey Diagram All code and data are processed and rendered in the browser. No data is sent to any server.
Scatter Chart All code and data are processed and rendered in the browser. No data is sent to any server.
Stepped Area Chart All code and data are processed and rendered in the browser. No data is sent to any server.
Table All code and data are processed and rendered in the browser. No data is sent to any server.
Timeline All code and data are processed and rendered in the browser. No data is sent to any server.
Treemap All code and data are processed and rendered in the browser. No data is sent to any server.
Trendlines NO INFO

So most  of the chartss don’t really send the data to a 3rd party to render. There’s information missing for a couple of types probably because they were still in beta at the time of the evaluation.

Information was updated on 2014.02.12

Posted in Data security, Development, Javascript | Tagged , , | Leave a comment

Redirecting from Salesforce Visualforce pages (Internet Explorer friendly)

A quite common requirement is to click a button and redirect users to different urls based on some business logic. Lets focus on the more complex scenario where a controller is required to determine the target url.

Apparently you need a button setting the Content Source to be a Visualforce page. The interesting point is how to handle the redirection. Assuming you use a String variable in your controller (i.e. strRedirectUrl) you can do this with Javascript using the following code

<apex:page Controller="MyRedirectController">
    <script type="text/javascript">
    if (!{!hasErrors})
    {
        window.top.location = "{!strRedirectUrl}"; 
    } 
    </script>
</apex>

Then the only thing you need to do in the Controller code is to set the proper value for strRedirectUrl.

Looks ok, works most of the times, BUT we have identified strange behaviour in one Salesforce Org.

When using Internet Explorer (IE 9, IE10) the popup window either turns blue and doesn’t redirect (yes we managed to make the Blue screen of death appear in Salesforce !!) or it closes automatically. Even more it worked fine when the URL was a salesforce url.

Any hints why this may happen are more than welcome…

So we needed an alternative approach which is the following

Use the action attribute of the <apex:page> component ! 

The code is also very simple

<apex:page Controller="MyRedirectController" action="{!doRedirect}">
    <apex:messages />
</apex:page>

The only thing you need to do is add a doRedirect method in your apex controller class which either redirects to the target url or returns null in case of an error (consider displaying an error message to inform the user.)

Posted in apex, Browser, Development, Firefox, IE, Javascript, Visualforce | Tagged , , , , , , , | 1 Comment

Emails sent from Salesforce via Apex are not delivered when using Email Relay

This week he had an issue with emails sent from Salesforce. In more details we have implemented a Visualforce page allowing the user to select recipients and send them all an email with some information related to a specific Salesforce record.

This has been implemented with Apex outbound email functionality offered by the platform. I will not go into more details regarding the implementation, since its a very complex one and not of interest for this specific post. The only interesting thing to mention regarding the setup is that there was an email relay server involved.

Problem: Suddenly in one of the Sandboxes, emails were not delivered !

Lets see…

  • Limits ? Maybe the daily limits for emails sent have been hit. How to check ? with debug logs… Nothing there, emails were sent successfully.
  • Now what? Checking the email logs sounds like a good idea ! You can request an Email log via the Setup menu under Monitor -> LogsEmailLogFiles

After downloading the csv file, I realised that the message “[internal] no MXs for this domain could be reached at this time” was reported for most rows (all the outbound ones)

What does this mean ? According to salesforce help (http://help.salesforce.com/HTViewSolution?id=000180767&language=en_US) it was related to the email relay server. That was indeed the problem !

Why does this story worth a post ? Because it’s one of these silent failures which don’t announce themselves and they are not straight forward to trace.

Posted in apex, Development | Tagged , | 1 Comment

Lookups, Activities and Performance

I guess you all know it’s not possible to create a lookup field on an Activity… What do you do when you really need a lookup ?

The easiest way to go is create a Text field for the related object Id and handle the relationship with triggers/Visualforce pages. You may even create a formula field in order to add in on Page layouts and display a link.

BUT…

Text fields are not indexed by default, so you may (will) encounter performance issues and Non-Selective query Exceptions !

SO DON’T FORGET to set the Text field as External Id. 

This way Salesforce will create an Index for this field and save you from trouble

Posted in apex, Development, Visualforce | Tagged , , , | 1 Comment

Salesforce URLFOR redirecting to a Visualforce page which is part of a package

This is a quick one but we struggled a little bit before finding out how to make it work.

Requirement: Redirect to a Visualforce page which is part of a package from a Javascript button.

URLFOR($Page.NAMESPACE__MYPAGE) doesn’t even compile…

but the following works like a charm !

URLFOR(“/apex/NAMESPACE__MYPAGE”);

yes it’s 2 _ in between

Posted in Development, Uncategorized, Visualforce | Tagged , , , , | 1 Comment