Email Reports from Rally

If you use Rally, you may be familiar with the joys of trying to make it even slightly useful. Here’s my small contribution: this is a script that formats stories for easy copy/paste into update emails, etc. You can select any iteration to get a nicely formatted list like the one below:

The script uses the Rally API and is based on this and this other code sample. The script makes some decisions that can be fairly easily changed in code if needed:

  • includes stories only (no defects)
  • skips over stories with no owner (we sometimes include stories for formatting purposes only)
  • sorts stories by highest story estimate first (with un-estimated stories at the bottom of the list)
To use:
  1. In Rally go to “Reports -> + New Page”
  2. Name it anything you like (for example “Email”) and click “Save & Close”
  3. Click “Start adding apps”
  4. Choose “Custom HTML” and click “Add This App”
  5. Title it anything you like (for example “Email”), paste the below code into the HTML section, and click “Save”


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "">
   <title>User Stories For Sprint Emails</title>
   <meta name="Name" content="User Stories For Sprint Emails" />
   <meta name="Author" content="Boris Masis, Bigstock" />
   <script type="text/javascript" src="/apps/1.26/sdk.js"></script>
   <script type="text/javascript">
    var rallyDataSource = null;
    var iterDropdown = null;
    function showUserStories(results) {
       var story = "";
       var storyEstimated = "";
       var storyNotEstimated = "";
       for (var i=0; i < results.stories.length; i++) {
         story = results.stories[i];
         if (!(story.Owner)) continue; //skip over stories with no owner
         else { 
           story.Owner = story.Owner._refObjectName.split(" ")[0]; //return the first name of the owner only
         if (story.PlanEstimate != null) {
           storyEstimated += story.Name + ' (' + story.Owner + ')' + '<br/>';
         else { // put stories that aren't estimated at the bottom of the list
           storyNotEstimated += story.Name + ' (' + story.Owner + ')' + '<br/>';
       var storiesList = document.getElementById("storiesList");
       storiesList.innerHTML = storyEstimated + storyNotEstimated;
    function onIterationSelected() {
       storiesList.innerHTML = "";
       var queryConfig = {
         type : 'hierarchicalrequirement',
         key : 'stories',
         fetch: 'PlanEstimate,Owner,Name',
         query: '(Iteration.Name = "' + iterDropdown.getSelectedName() + '")',
         order: 'PlanEstimate desc'
      rallyDataSource.findAll(queryConfig, showUserStories);
     function onLoad() {
       rallyDataSource = new'__WORKSPACE_OID__',
       var iterConfig = {};
       iterDropdown = new rally.sdk.ui.IterationDropdown(iterConfig, rallyDataSource);
       iterDropdown.display(document.getElementById("iterationDiv"), onIterationSelected);
   margin-top:25px !important;
   font:13px arial;
#storiesList b{
   <div id="iterationDiv"></div><br />
   <div id="storiesList"></div>