Asynchronous Reports with the Analytics API in Apex

The Analytics API in Apex provides methods to run reports synchronously or asynchronously.  This article describes how a report can be run asynchronously from Apex and have its results retrieved and displayed on a Visualforce page.  The code for a Visualforce page and controller that allow a user to run a tabular report asynchronously and view its results is detailed in the article.

The Page

The Visualforce page initially displays a select list of tabular reports from which to choose and then displays the results once the report has finished running.

Visualforce page

The Code

The code for the Visualforce page and controller are as follows. The code is also available on GitHub.

Running the Report

The available tabular reports are a list of Report records retrieved by querying the Report object. The Report object was available prior to the release of the Analytics API.

When the Run Report command button is clicked the runReport() controller method is executed and the reportResults and actionPoller are reRendered.

The Reports.ReportManager class can run reports asynchronously with its four overloaded runReportAsync(…)  methods and synchronously with its corresponding four runReport(…) methods.

  1. runReportAsync(reportId) – The results will include summary level information but will not include details (i.e., rows).
  2. runReportAsync(reportId, includeDetails) – The results will include summary level information and will include details if the includeDetails argument is true.
  3. runReportAsync(reportId, metadata) – Same as runReportAsync(reportId), but applies additional filters specified in the Reports.ReportMetadata metadata argument (see this article for an example that uses filtering).
  4. runReportAsync(reportId, metadata, includeDetails) – Same as runReportAsync(reportId, metadata), but the results will include details if the include details argument is true.

The runReport controller method calls the Reports.ReportManager’s runAsyncReport method with the selected reportId and specifies that the details should be included in the results. The runAsync method returns a Reports.ReportInstance which has an Id and a status.  The Id uniquely identifies the instance of the report for the requested asynchronous run.  It is stored in the instanceId property on the controller so that it can be used to query for the results.

Checking for the Results

The actionPoller continues to poll the checkForReportResults controller method while the report has not yet finished (reportIsRunning == true).

The checkForReportResults method calls Reports.ReportManager.getReportInstance and passes the Id of the Reports.ReportInstance returned form the runAsyncReport method. It calls the processInstance controller method to handle the processing of the instance.

The processInstance method checks the status of the reportInstance to see if it is ‘Running’ or ‘New’ which indicates that it has not finished. The possible status values are documented as:

  • New if the report run was recently triggered through a request.
  • Success if the report ran.
  • Running if the report is being run.
  • Error if the report run failed. The instance of a report run can return an error if, for example, your permission to access the report was removed after you requested the run.

If it is no longer running the Reports.ReportResults are retrieved and assigned to the Reports.ReportResults transient controller instance variable.  It is necessary to first check if the report is no longer running before calling the reportInstance.getReportResults() method. If the getReportResults() method is invoked on a Reports.ReportInstance while it is still running the following error will be generated: “System.NoDataFoundException: The report results aren’t available because the instance has not finished running.”  After the results have been assigned, the polling stops and the results are rendered in a table.

Displaying the Results

The values for the table headers are retrieved from the Reports.ReportExtendedMetadata.

The Reports.ReportMetadata contains the names of the detail columns in order in its List<String> of detail column names returned from its getDetailColumnNames(). Those names are the keys in the Reports.ReportExtendedMetadata‘s Map<String,Reports.DetailColumn> map returned from its getDetailColumnInfo() method. Each Reports.DetailColumn has a label which is the column header displayed on the report.  By iterating over the detailColumns in order and using them as keys, the column headers are displayed in the correct order.

The rows of the report are written out in the body of the table.

The ReportResults.getFactMap() method returns a Map<String, Reports.ReportFact> map.  Reports.ReportFact  is a parent class of Reports.ReportFactWithDetails and when the report is run with includeDetails as true, the returned objects are actually Reports.ReportFactWithDetails.  There is detailed documentation in the analytics section of the Apex Developer’s Guide on decoding the fact map.  In a tabular report the report fact with the key ’T!T’ contains the data values.  The Reports.ReportFactWithDetails that it maps to has a List of Reports.ReportDetailRow representing the rows of the report.  Each Reports.ReportDetailRow has a list of Reports.ReportDataCell representing a cell in the row.  The label of the Reports.ReportDataCell is defined as “the localized display name of the value of a specified cell in the report” and it is what gets written out in the td.

Testing

Although I did not write any unit tests for this code, I did look at the the Test Reports section in the Apex Developer’s Guide.  It is worth noting that the report will always run against existing org data, so the results may contain some existing org data.  It does not matter what the value of the SeeAllData annotation is.  The documentation provides an example that shows how to write a reliable test by adding a filter to the report in the unit test method.

Conclusion

This article presented a way to run reports asynchronously using the Analytics API in Apex and an actionPoller in a Visualforce page.  While this article only touched on tabular reports, much more can be done with other report types as well (e.g., this article).  The Analytics API in Apex documentation in the Apex Developer’s Guide contains excellent documentation that can be used as a reference while developing.

All code for this article is available on GitHub.

10 thoughts on “Asynchronous Reports with the Analytics API in Apex

  1. Peter,

    I am struggling to get the sample controller code to save/compile within my demo org. It breaks on line 7 with an “invalid class” error. A similar break happens with the bubble chart sample. Any thoughts on what I might be doing wrong?

    Thanks,

    Andrew

    1. Hi Andrew. Is your org a Spring ’14 org? If it is Winter ’14 you will not be able to use this code because the Analytics API in Apex is new for Spring ’14. I did all of this work in a pre-release org. You can sign up here if you want to experiment with features in Spring ’14.

  2. Outstanding work on this.

    I do have one question. In the standard report you can drill down on the items (for my testing I was using Opportunities).

    When the report is embedded you cannot drilldown. Am I missing a setting or is this by Salesforce design?

    thanks for your work,

  3. now i can i show my reports on vf page but i want first three columns and last three columns cause am getting all the rows and columns what should i do i removed some columns in my reports although here i get all the records of it how to get the required fields only

    thanks peterknolle ur awesome i dont know anything about this thing but u gave me a life

  4. Hey,

    This is really great.

    Does the 2000 rows limit on Report results still hold when using ‘runAsyncReport’ ?

Leave a Reply

Your email address will not be published. Required fields are marked *