<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- Copyright (c) 2012 Rally Software Development Corp. All rights reserved -->
<html>
<head>
  <title>Epic Progress</title>
  <meta name="Name" content="App: Epic Progress" />
  <meta name="Version" content="2012.11.22" />
  <meta name="Vendor" content="Rally Software" />

  <script type ="text/javascript" src="/apps/1.32/sdk.js"></script>
  <script type ="text/javascript">
    var releaseDropdown = null;
    var rallyDataSource = null;
    var busySpinner = null;
    var storyName = {};   // purposeful global, for use in showTooltip
    var acceptedStates = ["Accepted"];   // And user could have one more post-Accepted state
    
    //-----------------------------------------------------------------------------------
    
    function findParentPosX(obj) {
        var curleft = 0;
        if (obj.offsetParent) {
            while (obj.offsetParent) {
                curleft += obj.offsetLeft;
                obj = obj.offsetParent;
            }
        }
        else if (obj.x) {
            curleft += obj.x;
        }
        return curleft;
    }
    
    
    function findParentPosY(obj) {
        var curtop = 0;
        if (obj.offsetParent) {
            while (obj.offsetParent) {
                curtop += obj.offsetTop;
                obj = obj.offsetParent;
            }
        }
        else if (obj.y) {
            curtop += obj.y;
        }
        return curtop;
    }
    
    
    function showTooltip(tooltipId, parentId, posX, posY) {
        // show the tooltip contents in the DIV tooltipId relative to the parentID element
        // at posX, posY from the upper left corner of the parentID element
    
        tt = document.getElementById(tooltipId);
        var tooltipText = storyName[parentId];  // parentId value is the story.FormattedID
        if (tooltipText.length > 80) {
            tooltipText = tooltipText.substring(0, 80) + ' ...';
        }
        tt.innerHTML = tooltipText;
    
        parentElement = document.getElementById(parentId);
        parentElement.style.cursor = "default";
    
        // overcome deficiency in MSIE in setting default tooltip size
        if ((tt.style.top === '' || tt.style.top === 0) &&
                (tt.style.left === '' || tt.style.left === 0)) {
            if (tt.offsetWidth < 200) {
                tt.style.width = 200 + 'px';
            }
            else {
                tt.style.width = tt.offsetWidth + 'px';
            }
            tt.style.width = "260px";
            tt.style.height = tt.offsetHeight + 'px';
        }
        tt.style.height = "32px";
        var ttw = tt.style.width;
        var tth = tt.style.height;
    
        // if tooltip is too wide, shift left to be within parent
        if (posX + tt.offsetWidth > parentElement.offsetWidth) {
            posX = parentElement.offsetWidth - tt.offsetWidth;
        }
        if (posX < 0) {
            posX = 0;
        }
    
        var pposY = findParentPosY(parentElement);
        var pposX = findParentPosX(parentElement);
        y = pposY + posY + parentElement.offsetHeight;  // moves y location above (-) or below (+) parent element (+/- posY)
        x = pposX - parentElement.offsetWidth + posX;   // moves x location left (-) or right (+) by width of parent element (+/- posX)
    
        //x = pposX +  parentElement.offsetWidth -2;    // moves x location to just right of parent element
        //y = pposY + (parentElement.offsetHeight/5);   // moves y location down 1/5 of the height of the parent element
    
        tt.style.top = y + 'px';
        tt.style.left = x + 'px';
    
        tt.style.display = 'block';
        tt.style.visibility = 'visible';
    }
    
    
    function hideTooltip(tooltipId, parentId) {
        parentElement = document.getElementById(parentId);
        parentElement.style.cursor = "default";
        tt = document.getElementById(tooltipId);
        tt.innerHTML = "";
        tt.style.visibility = 'hidden';
        tt.style.display = 'none';
    }
    
    
    //-----------------------------------------------------------------------------------
    
    function clearTable(table) {
        var rowCount = table.rows.length;
    
        if (rowCount > 0) {
            for (var i = (rowCount - 1); i >= 0; i--) {
                table.deleteRow(i);
            }
        }
    }
    
    //-----------------------------------------------------------------------------------
    
    function prepFeedback() {
        table = document.getElementById("efp_table");  // epic family progress table
        clearTable(table);
        tbody = document.getElementById("efpt_body");
        var row = document.createElement("tr");
        var cell = document.createElement("td");
        tbody.appendChild(row);
        row.appendChild(cell);
        cell.setAttribute("id", "feedback");
        cell.setAttribute("style", "border: none;");
        cell.innerHTML = "No User Stories were in iterations defined in the time period for the selected release.";
    }
    
    function getRallyDate(jsDate) {
        var yr = jsDate.getUTCFullYear();
        var mon = ("0" + (jsDate.getUTCMonth() + 1));
        var day = ("0" + jsDate.getUTCDate()).substr(-2);
        var hr = ("0" + jsDate.getUTCHours()).substr(-2);
        var min = ("0" + jsDate.getUTCMinutes()).substr(-2);
        var sec = ("0" + jsDate.getUTCSeconds()).substr(-2);
    
        // YYYY-mm-ddTHH:MM:SS
        return       yr +
                "-" + mon.substr(mon.length - 2, 2) +
                "-" + day.substr(day.length - 2, 2) +
                "T" + hr + ":" + min + ":" + sec;
    }
    
    //-----------------------------------------------------------------------------------
    
    
    function manufactureTableColumnHeaders(table, iterations) {
        /*
         // get the "efpt_tbody" element, add a row with:
         // header cell: blank (for Epics), header cell No Iteration,
         // and a header cell for each iteration in iterations
         */
        tbod = document.getElementById("efpt_body");
        headerRow = document.createElement("tr");
        tbod.appendChild(headerRow);
        epicColumn = document.createElement("th");
        headerRow.appendChild(epicColumn);
        epicColumn.setAttribute("id", "EpicStory");
    
        for (var it = 0; it < iterations.length; it++) {
            iter = iterations[it];
            endDate = iter.EndDate;
            endDate = iter.EndDate !== 'None' ? iter.EndDate.replace(/T.*$/, "") : 'None';
            // create the new column header, add it to the parent DOM element (headerRow),
            // then you can manipulate the innards (attributes, innerHTML)
            colHdr = document.createElement("th");
            headerRow.appendChild(colHdr);
            colHdr.setAttribute("id", "it_" + endDate);
            colHdr.innerHTML = iter.Name;
        }
    }
    
    function storyWasAccepted(story) {
        var result = dojo.filter(acceptedStates,
                function (acc) {
                    if (story.ScheduleState === acc) {
                        return true;
                    }
                }
        );
        return (result.length > 0);
    }
    
    //-----------------------------------------------------------------------------------
    
    function createStoryCard(story, today) {
        var MOUSEOVER = "onmouseover=\"showTooltip('story_tooltip', '_STORY_ID_', 0,-98);\"";
        var MOUSEOUT = "onmouseout=\"hideTooltip('story_tooltip', '_STORY_ID_');\"";
        var TOOLTIP_BEHAVIOR = MOUSEOVER + " " + MOUSEOUT;
        var storyUrl = '__SERVER_URL__/detail/ar/_OID_'.replace('_OID_', story.ObjectID);
    
        var card = '<div class="story_card _SCHED_STATE_" id="_STORY_ID_" _TOOLTIP_> \n' +
                '    <div class="story_text"><a href="_STORY_URL_" target="_new">_BOLD_STORY_ID_</a></div>\n' +
                '    <div class="story_name">_STORY_NAME_</div>                   \n' +
                '    <div class="story_ID">_STORY_OID_</div>                      \n' +
                '    <div class="story_points">_STORY_POINTS_</div>               \n' +
                '    <div class="estimate_bar">_STORY_PROGRESS_</div>             \n' +
                '</div>\n';
    
        var storyEnd = "none";
        var storyStart = "none";
        if (story.Iteration !== null) {
            storyEnd = story.Iteration.EndDate.replace(/T.*$/, "");
            storyStart = story.Iteration.StartDate.replace(/T.*$/, "");
        }
    
        var progressBar = '<div class="progress_bar" style="height: 100%;width: _PROGRESS_PCTG_">&nbsp;</div>';
    
        var progress = 0;
        if (( story.TaskEstimateTotal > 0 ) && ( story.TaskEstimateTotal >= story.TaskRemainingTotal )) {
            progress = ( story.TaskEstimateTotal - story.TaskRemainingTotal ) / story.TaskEstimateTotal;
        }
    
        progress = (progress * 100) + "%";
        if (progress != "0%") {
            progressBar = progressBar.replace('_PROGRESS_PCTG_', progress);
        }
        else {
            progressBar = "";
        }
    
        var points = 'X';   // default
        if (story.PlanEstimate !== null) {
            points = story.PlanEstimate + "";
        }
        var schedState = "on-track";  // prime to happy-path...
        if ((! storyWasAccepted(story) ) && ( storyEnd < today ))
        //if ( ( story.ScheduleState != "Accepted" ) &&  ( storyEnd < today ) )
        {
            schedState = "blocked";
        }
    
        if (story.Blocked) {
            schedState = "blocked";
        }
        else {
            if (storyWasAccepted(story))
            //if ( story.ScheduleState == "Accepted" )
            {
                schedState = "accepted";
            }
            else if (( storyEnd > today ) && ( storyStart < today )) {
                schedState = "on-track";
            }
            else if (storyStart > today) {
                schedState = "future";
            }
        }
    
        var tooltipTrigger = TOOLTIP_BEHAVIOR.replace('_STORY_ID_', story.FormattedID);
        tooltipTrigger = tooltipTrigger.replace('_STORY_ID_', story.FormattedID);  // there's two of them...
        card = card.replace('_TOOLTIP_', tooltipTrigger);
        card = card.replace('_STORY_URL_', storyUrl);
        card = card.replace('_STORY_URL_', storyUrl);
        card = card.replace('_STORY_ID_', story.FormattedID);
        card = card.replace('_BOLD_STORY_ID_', story.FormattedID);
        card = card.replace('_STORY_NAME_', story.Name);
        card = card.replace('_STORY_OID_', story.ObjectID);
        card = card.replace('_STORY_POINTS_', points);
        card = card.replace('_SCHED_STATE_', schedState);
        card = card.replace('_STORY_PROGRESS_', progressBar);
        return card;
    }
    
    //-----------------------------------------------------------------------------------
    
    function showEmptyEpics(table, verbiage) {
        prepFeedback();
        var feedbackCell = document.getElementById("feedback");
        feedbackCell.setAttribute("style", "border: none;");
        feedbackCell.innerHTML = verbiage;
    }
    
    //-----------------------------------------------------------------------------------
    
    function countNoParentStoriesInRelease(nop, iterations) {
        var inRlsCount = 0;
        var iterName = "";
        for (var i = 0; i < iterations.length; i++) {
            iterName = iterations[i];
            inRlsCount += nop[iterName].length;
        }
    
        return inRlsCount;
    }
    
    
    
    function byStoryFormattedID(a, b) {
        var a_num = parseInt(a.substring(1, a.length), 10);
        var b_num = parseInt(b.substring(1, b.length), 10);
        return a_num - b_num;
    }
    
    function itemInContainer(item, container) {
        var present = false;
        var ix = 0;
        for (ix = 0; ix < container.length; ix++) {
            if (container[ix] === item) {
                present = true;
                break;
            }
        }
        return present;
    }
    
    
    function organizeResults(results) {
        // Be aware that the level* items in results are symmetric and parallel
        // in that the record at index 4 in level3 is in fact related to the record
        // at index 4 in level2. For this example, the record in level3 is the parent
        // of the user story in level2.  This makes lining up the parent-child chain
        // incredibly easy, eg level[x] is parent of level[x-1] is parent of level[x-2] ...
        // As we will only ever display the ultimate epic (story with no parent) and
        // a leaf story (a story with no children), all we have to do to is to derive
        // a family chain at an index producing a family_chain Array whose item at
        // index 0 is the leaf story and the last non-null story in the chain is the ultimate
        // epic story.
        var level = [results.level1, results.level2, results.level3, results.level4, results.level5];
    
        // we also have to account for items in level1 that have no parent, these stories
        // get plunked into a separate 'No Parent' category.
    
        var iterations = [
            {Name: 'No Iteration', StartDate: 'None', EndDate: 'None'}
        ];
        var iterDict = {}; // a temp bucket to prevent us from adding dups to iterations based on Iteration.Name
        var iterName = ""; // a temp bucket for the iteration name
        //  'noParent' dict, keyed by Story.Iteration.Name with a list for each key
        var noParent = {'No Iteration' : []};
        var orphans = 0;
        for (var i = 0; i < results.iterations.length; i++) {
            iter = results.iterations[i];
            if (iterDict.hasOwnProperty(iter.Name) !== true) {
                iterDict[iter.Name] = true;
                iterations.push(iter);
                noParent[iter.Name] = [];
            }
        }
        var iterationNames = dojo.map(iterations, function (item) {
            return item.Name;
        });
    
        for (i = 0; i < results.level1.length; i++) // iterate through stories in level1
        {
            story = results.level1[i];
            if (story.Parent === null) {
                if (story.Iteration !== null && story.Iteration !== 'null') {
                    iterName = story.Iteration.Name;
                    if (noParent.hasOwnProperty(iterName)) {
                        noParent[iterName].push(story);
                        orphans += 1;
                    }
                }
                else {
                    noParent['No Iteration'].push(story);
                    orphans += 1;
                }
            }
        }
    
        var epicTracker = {};
        var leaf = null;
        var epic = null;
        var num_leaf_stories = results.level1.length;
        for (var ix = 0; ix < num_leaf_stories; ix++) {
            leaf = level[0][ix];
            // ex -- epic index goes from highest possible level index to 1
            for (var ex = 4; ex > 0; ex--) {
                epic = level[ex][ix];
                if (typeof epic.FormattedID !== 'undefined') {
                    if (typeof epicTracker[epic.FormattedID] === 'undefined') {
                        epicTracker[epic.FormattedID] = {'Name' : epic.Name};
                        for (var k = 0; k < iterations.length; k++) {
                            iterationName = iterations[k].Name;
                            epicTracker[epic.FormattedID][iterationName] = [];
                        }
                    }
                    iterName = leaf.Iteration !== null && leaf.Iteration !== 'null' ? leaf.Iteration.Name : 'No Iteration';
                    if (itemInContainer(iterName, iterationNames)) {
                        epicTracker[epic.FormattedID][iterName].push(leaf);
                        break;
                    }
                }
            }
        }
        epics = [];
        for (var property in epicTracker) {
            if (epicTracker.hasOwnProperty(property)) {
                epics.push(property);  // in this case property is actually the UserStory.FormattedID of an Epic user story
            }
        }
        epics.sort(byStoryFormattedID);
    
        var baked = {'iterations'   : iterations,
            'No Parent'    : noParent,
            'orphans'      : orphans,
            'epicsSequence': epics,
            'epicTracker'  : epicTracker
        };
        return baked;
    }
    
    
    
    //-----------------------------------------------------------------------------------
    
    function showEpicFamilyProgress(results) {
        busySpinner.hide();
        fodder = organizeResults(results);  // dict with keys of 'iterations', 'No Parent', 'epicsSequence', 'epicTracker'
        table = document.getElementById("efp_table");  // epic family progress table
        clearTable(table);
    
        var iterations = dojo.map(fodder.iterations, function (it) {
            return it.Name;
        });
    
        var nop = fodder['No Parent'];
        var nopsInRelease = countNoParentStoriesInRelease(nop, iterations);
        if (nopsInRelease === 0 && fodder.epicsSequence.length === 0 && fodder.orphans === 0) {
            var noEpics = "There are no epic story chains defined for the selected release.";
            showEmptyEpics(table, noEpics);
            return;
        }
        //
        // clear out the table rows (including the header)
        //
        clearTable(table);
    
    
        // The column headers show the iterations that are inside the release
        //table.innerHTML = manufactureTableColumnHeaders(fodder.iterations);
        manufactureTableColumnHeaders(table, fodder.iterations);
        today = getRallyDate(new Date());
    
        // roll thru the fodder['No Parent'] sequence in iteration names order
        // create a table row with info for each iteration name from nop[iteration name]
        var nopCols = dojo.map(iterations, function (iterName) {
            return nop[iterName];
        });
    
        var x, iterName;
        for (x = 0; x < nopCols.length; x++) {
            // get the stories for each iteration
            iterName = iterations[x];
            nopStories = nopCols[x];
        }
        var tbod = document.getElementById("efpt_body");
        var row = document.createElement("tr");
        tbod.appendChild(row);
        var epicCell = document.createElement("td");
        row.appendChild(epicCell);
        epicCell.setAttribute("id", "row1_col1");
        epicCell.setAttribute("class", "storyData");
        epicCell.appendChild(document.createTextNode('No Parent'));
    
        var iterCell, iterCellContent;
        var userStories, iterStories, j;
        for (x = 0; x < nopCols.length; x++) {
            iterName = iterations[x];
            iterCell = document.createElement("td");
            row.appendChild(iterCell);
            iterCell.setAttribute("id", "row1_col" + (x + 2));
            iterCell.setAttribute("class", "storyData");
            iterCell.setAttribute("valign", "top");
            iterStories = [];
            userStories = nopCols[x];
            for (j = 0; j < userStories.length; j++) {
                story = userStories[j];
                storyName[story.FormattedID] = story.Name;
                storyCard = createStoryCard(story, today);  // storyCard is HTML text
                iterStories.push(storyCard);
            }
            iterCellContent = iterStories.join(" ");
            iterCell.innerHTML = iterCellContent;
        }
    
        var leafStories = [];
        for (var i = 0; i < fodder.epicsSequence.length; i++) {
            // before creating a row, first see if there are going to be any cells in which there
            // is going to be at least one card
            var epicInRelease = false;
            epicStory = fodder.epicsSequence[i];
            for (j = 0; j < iterations.length; j++) {
                iterName = iterations[j];
                leafStories = fodder.epicTracker[epicStory][iterName];
                if (leafStories.length > 0) {
                    epicInRelease = true;
                    break;
                }
            }
    
            if (!epicInRelease) {
                var p = 0;
            }
            else {
                row = document.createElement("tr");
                tbod.appendChild(row);
                epicCell = document.createElement("td");
                row.appendChild(epicCell);
    
                // strip any surrounding tag syntax
                epicStoryName = fodder.epicTracker[epicStory].Name;
                var protectedEpicName = epicStoryName.replace(/<\/?[^>]+(>|$)/g, "");
                epicLabel = epicStory + " " + protectedEpicName;
                epicCell.setAttribute("id", "row" + (i + 2) + "_col1");
                epicCell.setAttribute("class", "storyData");
                epicCell.appendChild(document.createTextNode(epicLabel));
    
                var rix = 0;
                var cix = 0;
                var epicColumns = [];
                for (j = 0; j < iterations.length; j++) {
                    iterName = iterations[j];
                    iterStories = [];
                    leafStories = fodder.epicTracker[epicStory][iterName];
                    for (var k = 0; k < leafStories.length; k++) {
                        story = leafStories[k];
                        storyName[story.FormattedID] = story.Name;
                        storyCard = createStoryCard(story, today);
                        iterStories.push(storyCard);
                    }
                    iterCellContent = iterStories.join(" ");
                    iterCell = document.createElement("td");
                    row.appendChild(iterCell);
                    rix = i + 2;
                    cix = j + 2;
                    iterCell.setAttribute("id", "row" + rix + "_col" + cix);
                    iterCell.setAttribute("class", "storyData");
                    iterCell.setAttribute("valign", "top");
                    iterCell.innerHTML = iterCellContent;
                }
            }
        }
    }
    
    //-----------------------------------------------------------------------------------
    
    function onReleaseSelected() {
        // show a 'busy' spinner to show something's going on
        busySpinner.display("efp_table");
        var releaseName = releaseDropdown.getSelectedName();
        var releaseStart = releaseDropdown.getSelectedStart();
        var releaseEnd = releaseDropdown.getSelectedEnd();
    
        var iterationsQuery =
        { key  : 'iterations',
            type : 'iterations',
            fetch: 'Name,StartDate,EndDate',
            query: '( ( EndDate > "' + releaseStart + '" ) AND ( StartDate < "' + releaseEnd + '") )',
            order: 'EndDate'
        };
    
        var level1Stories =
        { key  : 'level1',
            type : 'hierarchicalrequirement',
            fetch: 'FormattedID,Name,ObjectID,Iteration,StartDate,EndDate,' +
                    'PlanEstimate,TaskEstimateTotal,TaskRemainingTotal,' +
                    'ScheduleState,Blocked,Parent',
            query: '( Release.Name contains "' + releaseName + '" )',
            order: 'Rank'
        };
    
        var level2Stories =
        { key: 'level2',
            placeholder: '${level1.parent?fetch=Name,FormattedID,Parent}'
        };
    
        var level3Stories =
        { key: 'level3',
            placeholder: '${level2.parent?fetch=Name,FormattedID,Parent}'
        };
    
        var level4Stories =
        { key: 'level4',
            placeholder: '${level3.parent?fetch=Name,FormattedID,Parent}'
        };
    
        var level5Stories =
        { key: 'level5',
            placeholder: '${level4.parent?fetch=Name,FormattedID,Parent}'
        };
    
        var queryArray = [iterationsQuery, level1Stories, level2Stories, level3Stories,
            level4Stories, level5Stories];
    
        rallyDataSource.findAll(queryArray, showEpicFamilyProgress);
    }
    
    function retrieveScheduleStatesAndProceed(results) {
        var accepted_seen = false;
        for (var state in results) {
            if (results.hasOwnProperty(state)) {
                if (accepted_seen === true) {
                    acceptedStates.push(state);
                }
                else {
                    if (state === 'Accepted') {
                        accepted_seen = true;
                    }
                }
            }
        }
    
        releaseDropdown = new rally.sdk.ui.ReleaseDropdown({}, rallyDataSource);
        releaseDropdown.display("releaseList", onReleaseSelected);
    }

  </script>

  <style type="text/css">
        .story_tooltip {
        position: absolute;
        top: 0;
        left: 0;
        z-index: 2;
        font-family: tahoma, geneva, helvetica, arial, antiqua, sans-serif;
        font-size: 9pt;
        font-weight: normal;
        /* background-color: #D4EAF6;  */
        background: #E8F8FE;
        margin: 2px;
        padding: 1px;
        border: 1px solid gray;
        cursor: default;
        visibility: hidden;
        display: none;
    }
    
    .legend {
        font: 8pt Antiqua, Helvetica, sans-serif;
        border-top: 1px solid gray;
        margin: 2px;
    }
    
    .story_card {
        font-family: tahoma, geneva, helvetica, arial, antiqua, sans-serif;
        font-size: 8pt;
        font-weight: normal;
        border: 1px solid gray;
        margin: 2px;
        float: left;
        width: 52px;
        cursor: default;
    }
    
    .storyData {
        font-family: Helvetica, Tahoma, Geneva, Arial, sans-serif;
        font-weight: normal;
        font-size: 9pt;
        border-right: 2px dotted #BEBEBE;
        border-bottom: 1px dotted #DEDEDE;
        cursor: default;
    }
    
    .story_text {
        padding: 2px;
        font-weight: bold;
        color: black;
    }
    
    .story_name {
        padding: 2px;
        display: none;
        cursor: default;
    }
    
    .story_ID {
        display: none;
        cursor: default;
    }
    
    a:link {
        color: #000;
        text-decoration: none;
    }
    
    a:visited {
        color: #000;
        text-decoration: none;
    }
    
    a:hover {
        color: midnightblue;
        text-decoration: none;
    }
    
    .accepted {
        background-color: #6AB17D;
    }
    
    /* greenish color ...on official Rally color palette */
    .on-track {
        background-color: #5C9ACB;
    }
    
    /* bluish   color ...on official Rally color palette */
    .future {
        background-color: #FFFFFF;
    }
    
    /* white */
    .blocked {
        background-color: #EF3F35;
    }
    
    /* reddish  color ...on official Rally color palette */
    .progress_bar {
        background-color: #6AB17D;
        cursor: default;
    }
    
    .story_points {
        border: 1px solid gray;
        padding: 2px;
        background-color: white;
        float: right;
        cursor: default;
    }
    
    .estimate_bar {
        height: 18px;
        width: 100%;
        clear: both;
        background-color: white;
        border-top: 1px solid gray;
        cursor: default;
    }
    
    #efp_table th {
        font-family: Helvetica, Tahoma, Geneva, Arial, sans-serif;
        font-size: 10pt;
        font-weight: bold;
        border-bottom: 2px solid black;
    }
    
    #efp_table tbody tr {
        border-bottom: 1px solid gray;
    }
    
    /*
    #efp_table tbody td { border-right:  1px solid gray;
                          font-family: Helvetica,Tahoma,Geneva,Arial,sans-serif;
                          font-weight: normal;
                          font-size: 9pt;
                          cursor: default;
                        }
    */
  </style>

  <script type="text/javascript">    
    function onLoad() {
        rally.sdk.ui.AppHeader.setHelpTopic("235");
        rally.sdk.ui.AppHeader.showPageTools(true);

        busySpinner = new rally.sdk.ui.Wait({hideTarget: true});
        rallyDataSource = new rally.sdk.data.RallyDataSource('__WORKSPACE_OID__',
                '__PROJECT_OID__',
                '__PROJECT_SCOPING_UP__',
                '__PROJECT_SCOPING_DOWN__');

        rallyDataSource.getAllowedAttributeValues("hierarchicalrequirement",
                "schedulestate",
                retrieveScheduleStatesAndProceed);
    }

    rally.addOnLoad(onLoad);
  </script>
</head>
<body>
<div id="interface">
    <table>
        <tr>
            <td>
                <div id="releaseList"></div>
                <br>
            </td>
        </tr>
    </table>
</div>
<div id="efp_msg"></div>
<div id="efp_div">
    <table id="efp_table">
        <tbody id="efpt_body"></tbody>
    </table>
</div>
<div id="efp_box"></div>
<div id="legend" class="legend">
    <table class="legend">
        <tr>
            <td>&nbsp;</td>
            <td>&nbsp;</td>
        </tr>
        <tr>
            <td></td>
            <td> The green bar at the bottom of a story card represents the calculation
                of the to-dos on tasks for each story when subtracted from the estimates.
            </td>
        </tr>
        <tr>
            <td>
                <div class="story_card accepted">&nbsp;</div>
            </td>
            <td> Accepted Stories</td>
        </tr>
        <tr>
            <td>
                <div class="story_card on-track">&nbsp;</div>
            </td>
            <td> Stories in Current Iteration</td>
        </tr>
        <tr>
            <td>
                <div class="story_card">&nbsp;</div>
            </td>
            <td> Stories Not Scheduled or Scheduled in the Future</td>
        </tr>
        <tr>
            <td>
                <div class="story_card blocked">&nbsp;</div>
            </td>
            <td> Stories Marked as Blocked or Not Accepted from Past Iterations</td>
        </tr>
    </table>
</div>

<div id="story_tooltip" class="story_tooltip"></div>
</body>
</html>