Monthly Archives: April 2016

Testing Viewstate in Apex testmethods

If you have a controller plus VF page that could be subject to viewstate issues and you want to avoid introducing viewstate issues once you have already tested some released version of your code, how should you go about doing this?

There is (as of V36.0), no PageReference method getViewStateSize() which would be the obvious choice.

So, is there a way to approximate the viewstate and verify it doesn’t grow as more data volume increases?

First Principles: What is Viewstate comprised of?

Viewstate is used to preserve state across HTTP stateless protocol as the page interacts with the controller via GETs and POSTs (at least until a client-side redirect occurs). From the Salesforce Developer Doc, Viewstate is made up of:

  • All non-transient data members in the associated controller (either standard or custom) and the controller extensions.
  • Objects that are reachable from a non-transient data member in a controller or controller extension.
  • The component tree for that page, which represents the page’s component structure and the associated state, which are the values applied to those components.
  • A small amount of data for Visualforce to do housekeeping.

So, from a testmethod point of view, we have access to the first two: non-transient data members and their reachable objects. The component tree will vary in size depending on how many components are rendered – and this typically varies with the number of list elements in apex:pageBlockTables, apex:dataTables, and apex:repeats. The Visualforce housekeeping space consumption is not available to us.

Steps to take
Assuming we have followed the excellent guidelines in Visualforce in Practice, Chapter 13, Visualforce Performance and Best PracticesRapidfire Rendering of Visualforce Pages, what remains is to use the Apex testmethods as a regression suite to early warn you if you have introduced a viewstate regression, at least from the controller side.

Note, there are ways to avoid Viewstate altogether via client-side techniques such as Javascript Remoting but for purposes of this Blog post, let’s assume you have a traditional VF controller + VF markup.

Here’s a coding approach to use in your testmethod:

@isTest private static void testViewState() {
  // Mock n Sobjects
  MyController ctlr = new Controller();  // same principle applies for extensions
  // exercise setters, getters, and ajax action methods that process all n records 
  Integer ctlrSerializedSize = Json.serialize(ctlr);  // get size of controller, serialized

  // mock another n SObjects so we have 2n total
  ctlr = new Controller();  
  // exercise setters, getters, and ajax action methods that process all 2n records 
  System.assertEquals(ctlrSerializedSize,Json.serialize(ctlr),
                      'non transient size of controller should stay flat');
}

Caveats

  1. For Json.serialize to work, you can’t have any non-transient, unserializable variables like System.SelectOption. Use methods rather than getter properties. A list of restrictions can be found in the Apex Developer Doc – JSON Support. Some things may be hard to work around.
  2. The equality assert given in the code sample most likely works only if the same input conditions are used for the test on n records as is used on 2n records.
  3. I’ll say it again, this won’t test the number of VF apex:components generated by 2n versus n — that could blow up your view state. Hopefully you addressed this the first time with your design by following the aforementioned VF in Practice guidelines, Chapter 13.

Email2Case Tips

Herein are a set of tips for some non-obvious things when setting up email2Case

Notifying the default case owner when assignment rules look at some field value in the Case like ‘Product’

Since email2Case is basically just free text, unless you have rather clever parsers and triggers, your assignment rules won’t match on any Product field. The assignment rules are more applicable to web-to-case where you can put a form in front of the case submitter. So, if you want to notify the default case owner, you have two choices:

Choice A – Use a catchall assignment rule at the bottom of the list of rules. Be sure to include an email template.
Case assignment rules

If you choose this option, you can get a customized-by-you email template sent to the case owner (or members of the case owner queue)

Choice B – On Case Settings, use a default Case Owner and check the box ‘Notify Default Case Owner’

Case Settings Default Owner
If you choose this option, then there is no email template you can apply. The default case owner (a queue in this example) will receive this simple email format:

Case Settings notify default owner

Sending workflow/process flow emails to customers who are not Contacts in the Case

When Email2Case sends a Case from email foo@bar.com, unless there is an existing Contact for foo@bar.com, SFDC will not set a Contact on the Case (or, for that matter, an Account). That is, Case.ContactId will be null as will Case.AccountID. If a Case Comment is made by the support agent, and, via a Workflow/Process Flow, you want that comment to go to the only email you have on the Case, namely, Case.SuppliedEmail, the following is observed to be true:

  • If you define an Email Alert on Case and have as recipients Related Contact, Contact Email, and Supplied Email (in my example, the first two fields will be null), then the email alert will be sent to SuppliedEmail.
  • If at a later point in time, the Contact is created and associated to the Case, and then you send another email due to a new Case Comment via the workflow’s Email Alert, will the recipient get three copies of the same email? Fortunately, the answer is NO. SFDC de-dups the recipients.
  • How to personalize the email ‘from’ field while ensuring that replies are sent back to the Email2Case routing address

    Email gets sent from Cases to customers in many places:

    1. New Email button on the Email Messages related list
    2. Workflow/Process Flow Email alerts
    3. Case Auto-Response Rules
    4. Enable Case Comment Notifications to Contacts

    Let’s look at these in turn:

    New Email button on the Email Messages related list

    From SFDC’s point of view, this is creating a Task of type Email with the standard email creation form. The Email will be sent to the Case’s Contact.Email. So, if you haven’t converted some ‘new’ email address to a contact, you won’t be able to use this option. Assuming you have done this, then the from and replyTo values have to be established. By default, the drop down will be the running user’s SFDC email address, such as foo-the-agent@bar.com as will the replyTo address. This is not what you want as while the outbound email will have the Case threadId, the customer’s reply will go to the personal inbox of foo-the-agent and not be automatically associated to the Case.

    To resolve this, have each agent change My Settings | Email | My Email Address so that their Email Name is their name but the reply to address is the Email2Case address. Here’s an example:
    EmailSettings

    If you don’t do this, then the Support Agent needs to remember to choose the org-wide email address used for Email2Case routing.

    Workflow/Process Flow Email alerts

    If the workflow email alert is due to a user action (like adding a Case Comment or updating the Case Status), then I recommend using the Email Alert sender = Current User’s Email Address. This is easy to migrate as the alternative, an org-wide email address will be specific to your sandbox versus prod and cause issues when you deploy. Plus, if your agents can send email directly from the Email Messages related list and you chose the personalized option above, you’ll want workflows based on agent actions to operate the same.

Org Wide Email Address Verification Not Received

On a new project with a new client, I asked the mail server admins to set up an orgwide email address with me and a few others as members:

sandbox-no-reply@foo.com

Once you set up an orgwide email address, SFDC sends a verification request to ensure you are authorized to use this email.

But – no verification message was received; not to me, not to any of the email group’s members.

  • Email deliverability in sandbox was ‘System’. A verification message should qualify as a system message
  • Email deliverability tests worked fine across all IP ranges
  • I could happily receive other SFDC messages such as security token requests

The solution …

  • Client’s email system was Google Apps for Work.
  • The email group did not allow by default email from outside the domain of foo.com
  • Changing the group setting to ‘Public’ was step 1 of the solution and maybe the only step you will need.
  • As I was doing sysad via my company, my email address was in cropredy.com, not foo.com. A further change was needed in the Group definition to make my email address an ‘owner’. This allows mail to be distributed outside of the domain, in this case to @cropredy.com

Opportunity Historical Trend Reporting – Missing Opportunities

Here was a real conundrum. A client asked me to resolve why she (a sysad) couldn’t see any Opportunities in her Opportunity Historical Trending report when all her other Opportunity reports showed all Opportunities without issue.

I did the usual first principles checks:

  1. Was Opportunity Historical Trending even enabled? YES
  2. Were the right fields being tracked for trending? YES
  3. Was there actually Field History Tracking enabled for Opportunities? YES
  4. Were there actually rows in the Opportunity__hd Historical Tracking SObject? YES
  5. Could I reproduce the problem as a System Admin? YES

The usual rule of thumb when something isn’t visible in an Opportunity report is either something about the running user’s role or the sharing model. In this org, the sharing model was public read/write. The running user was a System Administrator who has View ALL Data, but wait …. the system administrator’s role was on a leaf at the end of one of two tree main branches. The sysad’s branch was a sibling to the sales org’s branch. The org had no top level role.

Org Roles

But why would the system administrator be able to see all Opportunities when running a standard Opportunities report if their role was buried deep inside a disconnected-from-the sales-org branch?

The answer lies here in the SFDC Help. I quote (note, the org was not using Territory Management):

If the Organization has the Opportunity Organization-wide Defaults set to “Public Read/Only:”
A Standard Report Type will show all the Opportunities the Running User can see, and that meet the criteria.

A Custom Report Type will only show Opportunities owned by a User with the same Role as or a Role below them in the Hierarchy. In this case if the missing Opportunities are owned by a User with a Role higher in the Hierarchy, the Running User will need to click on that Role in the Hierarchy selector (this can be found in the report detail page under its name).

OK, so this explains why standard reports can show all data that the sysad can see, regardless of where their role sits. But isn’t an Opportunity Historical Trend report a standard report? Doesn’t it come out-of-the-box from Salesforce?

The answer is No, Historical Trend Reports are not standard reports, they are custom report types.
Evidence for this:

So, the verbiage in the SFDC Help re: Custom Report Types applies – the running user’s role matters and since their role in the hierarchy was in a sibling branch to the sales org’s branch, there was not even a way to click the Hierarchy chain to see the data.

Bottom line

For your average SMB org, the out-of-the-box Opportunity reports may never be supplemented with Custom Report Types so it is easy to be lulled into thinking that any report you create for users on Opportunities will at least, let you, the sysad, see all data. But that odd duck – Historical Trending Reports – is treated as a Custom Report Type, and, when used for Opportunities (a common use case), your placement in the role hierarchy matters. In this case, the sysads were not at a top level in the hierarchy (and there was no top level either) so the problem ensued.