Skip to content

Instantly share code, notes, and snippets.

@ferentchak
Created January 16, 2013 20:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ferentchak/4550712 to your computer and use it in GitHub Desktop.
Save ferentchak/4550712 to your computer and use it in GitHub Desktop.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- Copyright (c) 2010 Rally Software Development Corp. All rights reserved -->
<html>
<head>
<title>Planning Board</title>
<meta name="Name" content="App: Planning Board"/>
<meta name="Version" content="2011.02.24"/>
<meta name="Vendor" content="Rally Software Labs"/>
<script type="text/javascript" src="/apps/1.23/sdk.js"></script>
<script type="text/javascript">
/**
Copyright (c) 2011 Rally Software Development Corp. All rights reserved
*/
var PlanningBoardCardRenderer = function(column, item) {
rally.sdk.ui.cardboard.BasicCardRenderer.call(this, column, item);
var that = this;
this.renderCard = function() {
var card = document.createElement("div");
dojo.addClass(card, "card");
dojo.addClass(card, rally.sdk.util.Ref.getTypeFromRef(item._ref));
var header = document.createElement("div");
dojo.addClass(header, "cardHeader");
dojo.addClass(header, "dojoDndHandle");
card.appendChild(header);
var idDiv = document.createElement("div");
var link = new rally.sdk.ui.basic.Link({item: item});
link.display(idDiv);
dojo.addClass(idDiv, "leftCardHeader");
header.appendChild(idDiv);
if(item.PlanEstimate) {
idDiv.appendChild(document.createTextNode(" (" + item.PlanEstimate + ")"));
}
var ownerImg = document.createElement("img");
dojo.addClass(ownerImg, "cardOwner");
var ownerName = document.createElement("div");
dojo.addClass(ownerName, "cardOwnerName");
if (item.Owner !== null) {
ownerImg.setAttribute("src", rally.sdk.util.Ref.getUserImage(item.Owner._ref));
ownerName.appendChild(document.createTextNode(item.Owner._refObjectName));
}
else {
ownerImg.setAttribute("src", rally.sdk.loader.launcherPath + "/../images/profile-mark-18.png");
ownerName.appendChild(document.createTextNode("No Owner"));
}
header.appendChild(ownerImg);
header.appendChild(ownerName);
var textDiv = document.createElement("div");
dojo.addClass(textDiv, "cardContent");
textDiv.innerHTML = item.Name;
card.appendChild(textDiv);
return card;
};
this.renderDndCard = function() {
var avatarCard = that.renderCard();
dojo.addClass(avatarCard, "avatar");
return avatarCard;
};
};
/**
Copyright (c) 2011 Rally Software Development Corp. All rights reserved
*/
var PlanningBoardColumnRenderer = function(board, value, options) {
rally.sdk.ui.cardboard.BasicColumnRenderer.call(this, board, value, options);
var that = this;
var dndContainer;
var points = 0;
var columnDiv;
var pointCapacity = Infinity;
var resourcesDisplay;
this.render = function() {
columnDiv = document.createElement("div");
dojo.addClass(columnDiv, "column");
var columnHeader = document.createElement("div");
dojo.addClass(columnHeader, "columnHeader");
var columnHeaderText = document.createElement("span");
columnHeader.appendChild(columnHeaderText);
columnHeaderText.appendChild(document.createTextNode(options.displayValue));
if (options && options.resources) {
pointCapacity = options.resources;
resourcesDisplay = document.createElement("div");
dojo.addClass(resourcesDisplay, "resourcesDisplay");
setCapacityText();
columnHeader.appendChild(resourcesDisplay);
}
columnDiv.appendChild(columnHeader);
if (options.startDate) {
var tooltip = new rally.sdk.ui.basic.Tooltip({
message: rally.sdk.util.DateTime.formatDate(options.startDate, "MM/dd/yyyy") + " - " +
rally.sdk.util.DateTime.formatDate(options.endDate, "MM/dd/yyyy"),
position: "above"
});
tooltip.display(columnHeaderText);
}
dndContainer = document.createElement("div");
dojo.addClass(dndContainer, "columnContent");
columnDiv.appendChild(dndContainer);
return columnDiv;
};
this.getDndContainer = function() {
return dndContainer;
};
this.getColumnNode = function() {
return columnDiv;
};
function setCapacityText() {
if (options && options.resources) {
resourcesDisplay.innerHTML = getCapacityText(points, pointCapacity);
}
if(options && options.startDate &&
rally.sdk.util.DateTime.fromIsoFormatString(options.startDate) < new Date() &&
options.endDate && rally.sdk.util.DateTime.fromIsoFormatString(options.endDate) > new Date()) {
resourcesDisplay.innerHTML += " *Current";
}
}
function getCapacityText(points, pointCapacity) {
var pointDisplay = parseInt(points) === parseFloat(points) ? points : Number(points).toFixed(2);
return "(" + pointDisplay + "/" + (pointCapacity === Infinity ?
'<span class="infinitySymbol">&#8734;</span>)' : (pointCapacity + ")"));
}
this.addNoDropClass = function(nodes) {
if (pointCapacity === Infinity) {
return false;
}
return pointCapacity <= points;
};
this.cardRemoved = function(card) {
points = points - card.PlanEstimate;
setCapacityText();
if (pointCapacity >= points) {
dojo.removeClass(columnDiv, "overCapacity");
}
};
this.cardAdded = function(card) {
points = points + card.PlanEstimate;
setCapacityText();
if (pointCapacity < points) {
dojo.addClass(columnDiv, "overCapacity");
}
};
this.getDndContainer = function() {
return dndContainer;
};
};
function PlanningBoard() {
var checkBoxes;
var columnStates;
var cardboard;
var dropdown;
var rallyDataSource;
var projectIterationMap = {};
var MAX_COLUMNS = 12;
var MAX_CARDS = Infinity;
var releaseMode;
var that = this;
this._createLayout = function(element) {
var headerDiv = document.createElement("div");
element.appendChild(headerDiv);
var dropdownContainerDiv = document.createElement("div");
dojo.addClass(dropdownContainerDiv, "dropdownContainer");
headerDiv.appendChild(dropdownContainerDiv);
var dropdownDiv = document.createElement("span");
dropdownDiv.id = "dropdown";
dropdownContainerDiv.appendChild(dropdownDiv);
var checkBoxContainerDiv = document.createElement("div");
dojo.addClass(checkBoxContainerDiv, "typeFilterContainer");
headerDiv.appendChild(checkBoxContainerDiv);
var showSpan = document.createElement("span");
showSpan.appendChild(document.createTextNode("Show:"));
checkBoxContainerDiv.appendChild(showSpan);
var userStoriesSpan = document.createElement("span");
userStoriesSpan.id = "userStories";
checkBoxContainerDiv.appendChild(userStoriesSpan);
var defectsSpan = document.createElement("span");
defectsSpan.id = "defects";
checkBoxContainerDiv.appendChild(defectsSpan);
var defectSuitesSpan = document.createElement("span");
defectSuitesSpan.id = "defectSuites";
checkBoxContainerDiv.appendChild(defectSuitesSpan);
//Create checkboxes
checkBoxes = [];
var userStoriesCheckBox = new rally.sdk.ui.basic.CheckBox({
showLabel: true,
label: "User Stories",
labelPosition: "after",
value: "HierarchicalRequirement",
checked: true
});
checkBoxes.push(userStoriesCheckBox);
userStoriesCheckBox.display("userStories");
var defectsCheckBox = new rally.sdk.ui.basic.CheckBox({
showLabel: true,
label: "Defects",
labelPosition: "after",
value: "Defect"
});
checkBoxes.push(defectsCheckBox);
defectsCheckBox.display("defects");
var defectSuitesCheckBox = new rally.sdk.ui.basic.CheckBox({
showLabel: true,
label: "Defect Suites",
labelPosition: "after",
value: "DefectSuite"
});
checkBoxes.push(defectSuitesCheckBox);
defectSuitesCheckBox.display("defectSuites");
//Wire up events
dojo.forEach(checkBoxes, function(checkBox) {
checkBox.addEventListener("onChange", that._refreshBoard);
});
var planningBoardDiv = document.createElement("div");
planningBoardDiv.id = "planningBoard";
dojo.addClass(planningBoardDiv, "planningBoard");
element.appendChild(planningBoardDiv);
releaseMode = false;
var togglePlanningMode = {
key: "togglePlanningMode",
label: "Toggle Backlog Mode",
onClick: function() {
releaseMode = !releaseMode;
that._refreshBoard();
}
};
rally.sdk.ui.AppHeader.addPageTool(togglePlanningMode);
};
this._getSelectedTypes = function() {
var types = [];
//Build types based on checkbox selections
dojo.forEach(checkBoxes, function(checkBox) {
if (checkBox.getChecked()) {
types.push(checkBox.getValue());
}
});
return types;
};
this._getItems = function(callback) {
var queries = [];
var backlogQueries = [];
dojo.forEach(that._getSelectedTypes(), function(type) {
queries.push({
key:type,
type: type,
fetch: "Name,FormattedID,Owner,ObjectID,Rank,PlanEstimate,Iteration",
query: rally.sdk.util.Query.and(['Iteration.StartDate >= "' + dropdown.getSelectedStart() + '"',
'Iteration.StartDate <= "' + dropdown.getSelectedEnd() + '"']),
order: "Rank"
});
backlogQueries.push({
key:type + "backlog",
type: type,
fetch: "Name,FormattedID,Owner,ObjectID,Rank,PlanEstimate,Iteration,Children",
query: new rally.sdk.util.Query('Iteration = "null"').and(releaseMode ? dropdown.getQueryFromSelected() : 'Release = "null"'),
order: "Rank"
});
});
var items = [];
var outstandingQueries = 2;
function concatResults(queryArray, results) {
rally.forEach(queryArray, function(query) {
if (results[query.key]) {
items = items.concat(results[query.key]);
}
});
outstandingQueries--;
if(outstandingQueries === 0) {
rally.forEach(items, function(item) {
item._iterationName = item.Iteration ? item.Iteration.Name : null;
});
//Filter out epics
items = dojo.filter(items, function(item) {
return !(item.Children && item.Children.length > 0);
});
callback(items);
}
}
rallyDataSource.find(backlogQueries, function(results) { concatResults(backlogQueries, results); });
rallyDataSource.findAll(queries, function(results) { concatResults(queries, results); });
};
this._getColumns = function(columnCallback) {
columnStates = {};
projectIterationMap = {};
var iterationQuery = {
key:"iterations",
type: "Iteration",
fetch: "Name,Project,StartDate,EndDate,Resources",
order: "EndDate",
query: rally.sdk.util.Query.and(['StartDate >= "' + dropdown.getSelectedStart() + '"',
'StartDate <= "' + dropdown.getSelectedEnd() + '"'])
};
rallyDataSource.findAll(iterationQuery, function(results) {
columnStates = {"null":{
displayValue: releaseMode ? "Release Backlog" : "Backlog",
resources:Infinity
}};
dojo.forEach(results.iterations, function(iteration) {
// Create a map between project and the iterations its contains by iteration name.
var projectRef = rally.sdk.util.Ref.getRelativeRef(iteration.Project);
projectIterationMap[projectRef] = projectIterationMap[projectRef] || {};
projectIterationMap[projectRef][iteration.Name] = rally.sdk.util.Ref.getRelativeRef(iteration);
// Builds list of unique iteration names and rolls up capacity
if (columnStates[iteration.Name]) {
columnStates[iteration.Name].resources += iteration.Resources;
}
else {
columnStates[iteration.Name] = {displayValue:iteration.Name,
resources:iteration.Resources || 0,
startDate: iteration.StartDate,
endDate: iteration.EndDate
};
}
});
columnCallback(columnStates);
});
};
this._postProcessColumnChange = function(obj, args) {
var projectRef = rally.sdk.util.Ref.getRelativeRef(args.originalItem.Project);
var iterationName = args.fieldsToUpdate[args.attribute];
if (iterationName === "null") {
args.fieldsToUpdate.Iteration = "null";
}
else if (projectIterationMap[projectRef][iterationName]) {
args.fieldsToUpdate.Iteration = projectIterationMap[projectRef][iterationName];
}
else {
rally.forEach(projectIterationMap, function(value, key) {
if (value[iterationName]) {
args.fieldsToUpdate.Iteration = value[iterationName];
}
});
}
delete args.fieldsToUpdate[args.attribute];
};
this._refreshBoard = function() {
var cardboardConfig = {
types: [],
attribute: "_iterationName",
sortAscending: true,
order: "Rank",
columns:that._getColumns,
columnRenderer:PlanningBoardColumnRenderer,
cardRenderer:PlanningBoardCardRenderer,
maxColumnsPerBoard: MAX_COLUMNS,
maxCardsPerColumn: MAX_CARDS,
items:that._getItems
};
cardboardConfig.types = that._getSelectedTypes();
if (cardboard) {
cardboard.destroy();
}
cardboard = new rally.sdk.ui.CardBoard(cardboardConfig, rallyDataSource);
cardboard.addEventListener(cardboard.getValidEvents().preUpdate, that._postProcessColumnChange);
cardboard.display("planningBoard");
};
this.display = function(element) {
rally.sdk.ui.AppHeader.showPageTools(true);
//Build app layout
this._createLayout(element);
rallyDataSource = new rally.sdk.data.RallyDataSource('__WORKSPACE_OID__',
'__PROJECT_OID__',
'__PROJECT_SCOPING_UP__',
'__PROJECT_SCOPING_DOWN__');
rallyDataSource.setServer("preview.rallydev.com");
var dropdownConfig = {
showLabel: true, query: '(State != "Accepted")'
};
dropdown = new rally.sdk.ui.ReleaseDropdown(dropdownConfig, rallyDataSource);
dropdown.addEventListener("onChange", that._refreshBoard);
dropdown.addEventListener("onLoad", that._refreshBoard);
dropdown.display("dropdown");
};
}
</script>
<style type="text/css">
.dropdownContainer {
float: left;
}
.typeFilterContainer {
float: right;
margin-right: 30px;
}
.planningEstimate{
display:inline;
}
.planningBoard {
clear: both;
}
.overCapacity .columnHeader {
color: #FC5350;
}
.overCapacity .columnContent {
background-color: #F9E2E1;
}
.cardboard .columnHeader {
height: 35px;
}
.cardboard .columnHeader.current {
color: #000000;
font-weight:bold;
}
.cardboard .resourcesDisplay {
font-size: 12px;
line-height: 20px;
}
.cardboard .infinitySymbol {
font-size: 16px;
position: relative;
top: 2px;
}
</style>
<script type="text/javascript">
function onLoad() {
var planningBoard = new PlanningBoard();
planningBoard.display(dojo.body());
}
rally.addOnLoad(onLoad);
</script>
</head>
<body>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment