Skip navigation

Category Archives: spring12

When I hear the words “Reports” and “Managed Packages” in the same sentence, I involuntarily let out a grunt of displeasure. Ask any seasoned ISV, and I guarantee you that the same sour taste fills their mouths. Why? Well, here’s the classic problem: An ISV includes some Reports in their managed package. Now, a common trick for making Reports “dynamic” is to leave one of the Filter Criteria blank and then have its value passed in through query string parameters using the “pv<n>” syntax, where n is the parameter you’d like to populate. For example, in this report of Enrollments at a given School, parameter 2 (0-indexed) is left BLANK:

Then, if we load up this page with query string parameter “pv2” set to the name of a School, like so:

the value that we passed in will be dynamically inserted into the 2nd Filter Criterion, and we’ll have a report on Enrollments at Lincoln High School:

This is awesome, right? Quick, let’s throw a Custom Link on the Account Detail Page called “Enrollments” that links to this report, but passing in the Id of the Report! Yeah, yeah, yeah! I love this!

Hold your horses, partner.

This is where the ISV’s hang their heads in sadness… sorry son, it just ain’t that easy.

What’s the matter, grandpa? Come on, this is child’s play!

Not quite.

Notice where we said that we’d be passing in the ID of the Report. Hard-coded. For an ISV, ID’s are the ultimate  taboo. Why? Well, sure, you can package up the Report and Custom Link. But as soon as you install the package into a customer’s org, the Id of the Report has CHANGED — and the link is BROKEN. It’s a situation that the typical one-org Admin will never have to face, but, well, welcome to the world of the ISV.

Isn’t there one of those handy Global Variables which lets you grab the Name or DeveloperName of a Report?

Nope, sorry partner.

So, what DO you do?

Well, you write a ‘ViewReport’ Visualforce page that takes in the API name of a Report — which does NOT change across all the orgs that a package is installed into — and uses this API name to find the ID of the Report and send you to it. What does this look like?

The Visualforce is simple — one line, to be exact:


<apex:page controller="ViewReportController" action="{!redirect}"/>

The Apex Controller is a little more interesting. Here’s the meat, with test code included that achieves 100% coverage (so you can start using it right away!!!):


public class ViewReportController {

    // Controller for ViewReport.page,
    // which redirects the user to a Salesforce Report
    // whose name is passed in as a Query String parameter

    // We expect to be handed 1-2 parameters:
    // r: the DEVELOPER name of the Report you want to view
    // ns: a salesforce namespace prefix (optional)
    public PageReference redirect() {
        // Get all page parameters
        Map<String,String> params = ApexPages.currentPage().getParameters();

        String ns = params.get('ns'); // NamespacePrefix
        String dn = params.get('dn'); // DeveloperName

        List<Report> reports;

        // If a Namespace is provided,
        // then find the report with the specified DeveloperName
        // in the provided Namespace
        // (otherwise, we might find a report in the wrong namespace)
        if (ns != null) {
            reports = [select Id from Report
                  where NamespacePrefix = :ns
                  and DeveloperName = :dn limit 1];
        } else {
            reports = [select Id from Report where DeveloperName = :dn limit 1];
        }

        PageReference pgRef;

        // If we found a Report, go view it
        if (reports != null && !reports.isEmpty()) {
            pgRef = new PageReference('/' + reports[0].Id);
            // Add back in all of the parameters we were passed in,
            // MINUS the ones we already used: ns, dn
            params.remove('ns');
            params.remove('dn');
            pgRef.getParameters().putAll(params);
        } else {
            // We couldn't find the Report,
            // so send the User to the Reports tab
            pgRef = new PageReference('/'
                + Report.SObjectType.getDescribe().getKeyPrefix()
                + '/o'
            );
        }

        // Navigate to the page we've decided on
        pgRef.setRedirect(true);
        return pgRef;

    }

    ////////////////////
    // UNIT TESTS
    ////////////////////

    // We MUST be able to see real Reports for this to work,
    // because we can't insert test Reports.
    // Therefore, in Spring 12, we must use the SeeAllData annotation
    @isTest(SeeAllData=true)
    private static void Test_Controller() {
        // For this example, we assume that there is
        // at least one Report in our org WITH a namespace

        // Get a report to work with
        List<Report> reports = [
            select Id, DeveloperName, NamespacePrefix
            from Report
            where NamespacePrefix != null
            limit 1
        ];

        // Assuming that we have reports...
        if (!reports.isEmpty()) {
            // Get the first one in our list
            Report r = reports[0];

            //
            // CASE 1: Passing in both namespace, developername,
            // and a parameter value
            //

            // Load up our Visualforce Page
            PageReference p = System.Page.ViewReport;
            p.getParameters().put('ns',r.NamespacePrefix);
            p.getParameters().put('dn',r.DeveloperName);
            p.getParameters().put('pv0','llamas');
            p.getParameters().put('pv2','alpacas');
            Test.setCurrentPage(p);

            // Load up our Controller
            ViewReportController ctl = new ViewReportController();

            // Manually call the redirect() action,
            // and store the page that we are returned
            PageReference ret = ctl.redirect();

            // We should be sent to the View page for our Report
            System.assert(ret.getURL().contains('/'+r.Id));
            // Also, make sure that our Filter Criterion values
            // got passed along
            System.assert(ret.getURL().contains('pv0=llamas'));
            System.assert(ret.getURL().contains('pv2=alpacas'));

            //
            // CASE 2: Passing in both just developername
            //

            // Load up our Visualforce Page
            p = System.Page.ViewReport;
            p.getParameters().put('dn',r.DeveloperName);
            Test.setCurrentPage(p);

            // Load up our Controller
            ctl = new ViewReportController();

            // Manually call the redirect() action,
            // and store the page that we are returned
            ret = ctl.redirect();

            // We should be sent to the View page for our Report
            System.assert(ret.getURL().contains('/'+r.Id));

            //
            // CASE 3: Passing in a nonexistent Report name
            //

            // Load up our Visualforce Page
            p = System.Page.ViewReport;
            p.getParameters().put('dn','BlahBLahBlahBlahBlahBlah');
            Test.setCurrentPage(p);

            // Load up our Controller
            ctl = new ViewReportController();

            // Manually call the redirect() action,
            // and store the page that we are returned
            ret = ctl.redirect();

            // We should be sent to the Reports tab
            System.assert(ret.getURL().contains(
                '/'+Report.SObjectType.getDescribe().getKeyPrefix()+'/o'
            ));

        }

    }

}

And here’s an example of using this code in a Custom Link:

Voila! A ISV-caliber way to link to any Report in any Managed Package, and it won’t break in an org where the package is installed!

The basic flow of the Apex is pretty simple: the redirect method gets called immediately upon page load, and it returns a page reference to redirect the user to. So all that Apex needs to do for us is find the Report with the provided API name / DeveloperName (and optionally in a specific namespace), and send us to /<Id>, where <Id> is the Id of that Report. Pretty straightforward. Just a few interesting points:

  1. We ‘tack-on’ to the resultant page reference any OTHER page parameters that the user passed along, so we can pass in any number of dynamic Filter Criteria parameters using the pv<n> syntax.
  2. You may be wondering — wait, you can QUERY on the Report object? Yep! Reports are technically SObjects, so you can query for them, but they fall under the mysterious category called “Setup” objects which, among other peculiar quirks (Google “MIXED_DML_OPERATION” for one of the more annoying ones), only selectively obey CRUD and only expose some of their fields to SOQL. Fortunately, for our purposes, the Id, Name, DeveloperName, and NamespacePrefix fields are all included in this short list. Actually, fire up SOQLXplorer — you might be inspired by some of the other fields that are exposed.
  3. Namespacing of Reports — Reports included in Managed Packages don’t have to have a globally unique name — they only have to be unique within their Namespace. Therefore, when querying for Reports, it’s best to query for a report within a particular namespace.
  4. If the Report is not found — in our example, we send the User to the Reports tab. You might want to do something different.

There’s nothing like a fresh Salesforce release to reassure SF consultants who, gainfully distracted by quotidian customer requests, have had their impetus to innovate temporarily quenched. Translation — I love working on a platform that innovates faster than I can.

Before Richard Vanhook jumps out of his skin (check out his excellent idea posted over a year ago), let me assure you that Salesforce has NOT added a much needed Apex API for Custom Labels / Translations. However, with Spring 12, it HAS finally enabled us to use Visualforce to dynamically render and use Custom Labels — and associated Translations — when the Label name is NOT known beforehand.

How does this make Custom Labels / Translation Workbench more useful? There are several thoughts that come to mind, but I’ll just describe one use case:

Rebranding the UI — At Skoodat, our trademark is design-oriented products, so we’ve done some heavy-duty overhauling of the standard SFDC UI, and have built a custom markup language to dynamically drive the content of our pages. Having the ability to dynamically insert text that automatically gets translated into other languages is a huge win, and the triumvirate of Salesforce’s Translation Workbench, built-in multi-language architecture, and Custom Labels, really makes the implementation of this fairly painless.

HOWEVER, for an ISV Partner interested in rendering pages dynamically based on custom metadata, this whole architecture is inaccessible, because there’s no way to access the value of a Label or Translation through Apex by name, i.e. there is no equivalent of loosely-typed System.Label.get('MyLabelName') … only the strongly-typed System.Label.MyLabelName .

So, how is this magic achieved? Well, with Spring 12, there will actually be 2 ways to achieve it:

  1. Dynamic Visualforce Bindings (which have now been extended to Global Variables)
  2. Dynamic Visualforce Components (which are finally Generally Available [i.e. not Pilot, not Beta] — and thus Packageable!!!

For this example, I first enabled Translation Workbench, and added one language: Spanish. I then created 2 Custom Labels: ‘Release’ and ‘WelcomeMessage’

Custom Labels

and then created an Apex controller called CustomLabelsController:


public class CustomLabelsController {

    public Component.Apex.OutputText output { public get; private set; }
    public String labelName { public get; private set; }

    public CustomLabelsController() {

        // Get the name of the Custom Label to display
        // from a Query String parameter
        Map params = ApexPages.currentPage().getParameters();
        labelName = params.get('label');

        // Instantiate a new Dynamic Visualforce Component,
        // and set its value attribute to a dynamic expression
        output = new Component.Apex.OutputText();
        output.expressions.value = '{!$Label.' + labelName + '}';
    }
}

This controller reads in a query string parameter called ‘label’ and assigns it to a page property (which we will retrieve from Visualforce using a Dynamic Binding), and then instantiates a new Dynamic OutputText component, setting the value property to the VF expression needed to retrieve a Custom Label.

I then created the following Page (called ‘CustomLabels’) which uses this controller:


<apex:page controller="CustomLabelsController">

    <apex:pageBlock title="Using Dynamic VF BINDINGS">
        <apex:outputText value="{!$Label[labelName]}"/>
    </apex:pageBlock>

    <apex:pageBlock title="Using Dynamic VF COMPONENTS">
        <apex:dynamicComponent componentValue="{!output}"/>
    </apex:pageBlock>

</apex:page>

In this page, I first use Dynamic VF Bindings to access the $Label Global Variable, which, as of Spring 12, can now be used with Dynamic Bindings to retrieve an arbitrary label name. I then display the Dynamic Component.

When I navigate to ‘/apex/CustomLabels?label=WelcomeMessage’, I am greeted in our language:

Where this becomes incredibly useful is when we start translating our Labels. I translated my labels into Spanish:

Spanish translation of Welcome Message

and then changed my user language to Spanish. Returning to my page, I was greeted by the following:

For  those of you who joined the Dynamic VF Components Pilot, this functionality technically has existed since Summer 11. In my mind, however, this is really a feature that would most appeal to ISV’s, so the inability to use it in Managed Packages sidelined its appeal until Spring 12.