text:
<!DOCTYPE html>
<html>
<head>
    <title>Train Performance Dashboard</title>

    <script type="text/javascript" src="/apps/2.0rc3/sdk.js"></script>
    <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.4.js"></script>
    <script type="text/javascript" src="https://code.highcharts.com/highcharts.js"></script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/q.js/1.4.1/q.js"></script>

    <script type="text/javascript">
        Rally.onReady(function () {
                /**
	SUMMARY:
		This extends Rally.app.app. All Intel Apps should extend from this if they want to use the functionality it provides,
		as well as numerous other lib/* files that require you to extend from this.

		If you extend from Intel.lib.IntelRallyApp, you have to call me.configureIntelRallyApp() before anything else in your launch()
		function. After you call me.configureIntelRallyApp, you will be able to access a lot of things, such as:
			- me.ScheduleStates: possible UserStory schedule States
			- me.PortfolioItemTypes: Ordered Array of PortfolioItem Types. e.g.: ['Feature', 'Milestone', 'Product']
			- me.ScrumGroupConfig: the scrum group portfolio location config for the workspace
			- me.HorizontalGroupingConfig: the horizontal scrum groupings by keywords
			- All the most-used Rally models, such as me.Project, me.UserStory, me.PortfolioItem, etc...

	DEPENDENCIES:
		Q promise library

	ISSUES:
		sdk 2.0 does not work with the project function due to a bug in the sdk. use sdk 2.0rc3
	*/
(function(){
	var Ext = window.Ext4 || window.Ext,
		ScrumGroupConfigPrefName = 'intel-portfolio-locations-config', //preference to store portfolio locations config for workspace
		HorizontalGroupingConfigPrefName = 'intel-horizontal-grouping-config'; //preference to store map of keywords in project names to horizontal

	//increase timeouts to 2 minutes since rally can be slow sometimes
	var timeout = 120000;
	Ext.override(Ext.data.proxy.Ajax, { timeout: timeout });
	Ext.override(Ext.data.proxy.JsonP, { timeout: timeout });

	//rally's built-in jsonpproxy does not handle timeouts
	Ext.override(Rally.sdk.data.lookback.JsonPProxy, {
		setException: function(operation, response){
			var error = operation.getError() || {};
			operation.setException(Ext.apply(error, {
				errors:(response || {}).Errors || []
			}));
		}
	});

	Ext.define('Intel.lib.IntelRallyApp', {
		extend: 'Rally.app.App',
		minWidth:910, //when Rally adds its own scrollbar anyways

		projectFields: ['ObjectID', 'Releases', 'Children', 'Parent', 'Name', 'TeamMembers'],
		portfolioItemFields: ['Name', 'ObjectID', 'FormattedID', 'Release','c_TeamCommits', 'c_MoSCoW',
			'c_Risks', 'Project', 'PlannedEndDate', 'Parent', 'Children', 'PortfolioItemType', 'Ordinal',
			'PercentDoneByStoryPlanEstimate'],
		userStoryFields: ['Name', 'ObjectID', 'Release', 'Project', 'PortfolioItem', 'PlannedEndDate', 'ActualEndDate',
			'FormattedID', 'Predecessors', 'Successors', 'c_Dependencies', 'Iteration', 'PlanEstimate'],
		releaseFields: ['Name', 'ObjectID', 'ReleaseDate', 'ReleaseStartDate', 'Project', 'TeamMembers'],

		/********************************************** APP CONFIGURATION **************************************/
		_loadScheduleStates: function(){
			var me=this, deferred = Q.defer();
			Rally.data.ModelFactory.getModel({
				type: 'UserStory',
				success: function(model) {
					model.getField('ScheduleState').getAllowedValueStore().load({
						callback: function(records, operation, success) {
							me.ScheduleStates = _.map(records, function(r){ return r.data.StringValue; });
							deferred.resolve();
						}
					});
				}
			});
			return deferred.promise;
		},
		_loadPortfolioItemStatesForEachType: function(){
			var me=this;
			me.PortfolioItemTypeStates = [];
			return Q.all(_.map(me.PortfolioItemTypes, function(portfolioType, ordinal){
				var store = Ext.create('Rally.data.wsapi.Store', {
					model: 'State',
					autoLoad:false,
					limit:Infinity,
					disableMetaChangeEvent: true,
					fetch:['Name', 'Enabled', 'OrderIndex'],
					filters: [{
						property: 'TypeDef.Name',
						value: portfolioType
					},{
						property: 'Enabled',
						value: true
					}]
				});
				return me.reloadStore(store).then(function(store){
					me.PortfolioItemTypeStates[ordinal] = store.getRange();
				});
			}));
		},
		_loadPortfolioItemTypes: function(){
			/** loads all the portfolioitem names into sorted array [smallest ordinal --> biggest ordinal] */
			var me=this,
				deferred = Q.defer(),
				store = Ext.create('Rally.data.wsapi.Store', {
					model: 'TypeDefinition',
					autoLoad:true,
					limit:Infinity,
					disableMetaChangeEvent: true,
					fetch:['Ordinal', 'Name'],
					filters: [{
						property: 'Parent.Name',
						value: 'Portfolio Item'
					},{
						property: 'Creatable',
						value: true
					}],
					listeners:{
						load:function(portfolioTypeStore){
							me.PortfolioItemTypes = _.map(_.sortBy(_.map(portfolioTypeStore.getRange(),
								function(item){ return {Name: item.data.Name, Ordinal: item.data.Ordinal}; }),
								function(item){ return item.Ordinal; }),
								function(item){ return item.Name; });
							deferred.resolve();
						}
					}
				});
			return deferred.promise;
		},
		_loadModels: function(){
			/** loads models for project, userstories, and all the portfolio items */
			var me=this,
				promises = [],
				models = {
					User: 'User',
					Project: 'Project',
					UserStory: 'HierarchicalRequirement',
					PortfolioItem: 'PortfolioItem'
				};
			_.each(me.PortfolioItemTypes, function(name){ models['PortfolioItem/' + name] = 'PortfolioItem/' + name; });
			_.each(models, function(modelType, modelName){
				var deferred = Q.defer();
				Rally.data.WsapiModelFactory.getModel({
					type:modelType,
					success: function(loadedModel){
						me[modelName] = loadedModel;
						deferred.resolve();
					}
				});
				promises.push(deferred.promise);
			});
			return Q.all(promises);
		},
		_loadScrumGroupConfig: function(){
			/** scrum-groups are groups of scrums that share the same portfolio. The group of scrums may or may not be a train */
			/** me.ScrumGroupConfig is an array of these objects:
				{
					ScrumGroupRootProjectOID: configItem.ScrumGroupRootProjectOID || 0,
					ScrumGroupName: configItem.ScrumGroupName || '',
					ScrumGroupAndPortfolioLocationTheSame: configItem.ScrumGroupAndPortfolioLocationTheSame ? true : false,
					PortfolioProjectOID: configItem.PortfolioProjectOID || 0,
					IsTrain: configItem.IsTrain ? true : false
				}
			*/
			var me=this, deferred = Q.defer();
			Rally.data.PreferenceManager.load({
				workspace: me.getContext().getWorkspace()._ref,
				filterByName: ScrumGroupConfigPrefName,
				success: function(prefs) {
					var configString = prefs[ScrumGroupConfigPrefName], scrumGroupConfig;
					try{ scrumGroupConfig = JSON.parse(configString); }
					catch(e){ scrumGroupConfig = []; }
					me.ScrumGroupConfig = scrumGroupConfig;
					deferred.resolve();
				},
				failure: deferred.reject
			});
			return deferred.promise;
		},
		_loadHorizontalGroupingConfig: function(){
			/** HorizontalGroupingConfig is this:
			{
				enabled: boolean,
				groups: {
					<horizontal1Name>: ['keyword1', 'keyword2'],
					<horizontal2Name>: ['keyword1', 'keyword2'],
					...
				}
			}
			*/
			var me=this, deferred = Q.defer();
			Rally.data.PreferenceManager.load({
				workspace: me.getContext().getWorkspace()._ref,
				filterByName: HorizontalGroupingConfigPrefName,
				success: function(prefs) {
					var configString = prefs[HorizontalGroupingConfigPrefName], horizontalGroupingConfig;
					try{ horizontalGroupingConfig = JSON.parse(configString); }
					catch(e){ horizontalGroupingConfig = {enabled:false, groups:{}}; }
					me.HorizontalGroupingConfig = horizontalGroupingConfig;
					deferred.resolve();
				},
				failure: deferred.reject
			});
			return deferred.promise;
		},
		saveScrumGroupConfig: function(scrumGroupConfig){
			var me=this, s = {}, deferred = Q.defer();
			s[ScrumGroupConfigPrefName] = JSON.stringify(scrumGroupConfig);
			Rally.data.PreferenceManager.update({
				workspace: me.getContext().getWorkspace()._ref,
				filterByName: ScrumGroupConfigPrefName,
				settings: s,
				success: deferred.resolve,
				failure: deferred.reject
			});
			return deferred.promise;
		},
		saveHorizontalGroupingConfig: function(horizontalGroupingConfig){
			var me=this, s = {}, deferred = Q.defer();
			s[HorizontalGroupingConfigPrefName] = JSON.stringify(horizontalGroupingConfig);
			Rally.data.PreferenceManager.update({
				workspace: me.getContext().getWorkspace()._ref,
				filterByName: HorizontalGroupingConfigPrefName,
				settings: s,
				success: deferred.resolve,
				failure: deferred.reject
			});
			return deferred.promise;
		},
		configureIntelRallyApp: function(){
			var me=this;
			me.BaseUrl = Rally.environment.getServer().getBaseUrl(); //is "" when in custom app iframe
			return Q.all([
				me._loadPortfolioItemTypes().then(function(){
					me.userStoryFields.push(me.PortfolioItemTypes[0]);
					return Q.all([
						me._loadModels(),
						me._loadPortfolioItemStatesForEachType()
					]);
				}),
				me._loadScrumGroupConfig(),
				me._loadHorizontalGroupingConfig(),
				me._loadScheduleStates()
			]);
		},

		/**************************** Generic store loading with Q wrapper, returns promise *************************************/
		reloadStore: function(store){
			var deferred = Q.defer();
			store.load({
				callback: function(records, operation, success){
					if(!success) deferred.reject(operation.getError() || 'Could not load data');
					else deferred.resolve(store);
				}
			});
			return deferred.promise;
		},

		/********************************************** LOADING SINGLE MODELS **************************************/
		loadProject: function(oid){
			var me = this, deferred = Q.defer();
			if(!oid) return Q.reject('Invalid arguments: loadProject');
			else if(!me.Project) return Q.reject('IntelRallyApp is not configured!');
			else {
				me.Project.load(oid, {
					fetch: me.projectFields,
					context: {
						workspace: me.getContext().getWorkspace()._ref,
						project: null
					},
					callback: deferred.resolve
				});
				return deferred.promise;
			}
		},
		loadUserStory: function(oid, projectRecord){
			var me = this, deferred = Q.defer();
			if(!oid) return Q.reject('Invalid arguments: loadUserStory');
			else if(!me.UserStory) return Q.reject('IntelRallyApp is not configured!');
			else {
				me.UserStory.load(oid, {
					fetch: me.userStoryFields,
					context: {
						workspace: projectRecord ? null : me.getContext().getWorkspace()._ref,
						project: projectRecord ? projectRecord.data._ref : null
					},
					callback: deferred.resolve
				});
				return deferred.promise;
			}
		},
		loadPortfolioItemByType: function(oid, type){
			var me = this, deferred = Q.defer();
			if(!oid || !type) return Q.reject('Invalid arguments: loadPortfolioItemByType');
			else {
				type = (type.indexOf('PortfolioItem/') === 0) ? type : ('PortfolioItem/' + type);
				me[type].load(oid, {
					fetch: me.portfolioItemFields,
					context: {
						workspace: me.getContext().getWorkspace()._ref,
						project: null
					},
					callback: deferred.resolve
				});
				return deferred.promise;
			}
		},
		loadPortfolioItemByOrdinal: function(oid, ordinal){
			var me = this, deferred = Q.defer(),
				type = me.PortfolioItemTypes[ordinal];
			return me.loadPortfolioItemByType(oid, type);
		},

		/**************************************** ScrumGroup Funcs ***************************************************/
		projectInWhichScrumGroup: function(projectRecord){
			/** returns scrumgroup the projectRecord is in, otherwise null. */
			if(!projectRecord) return Q();
			else {
				var me=this,
					foundScrumGroupConfig = _.find(me.ScrumGroupConfig, function(scrumGroupConfig){
						return scrumGroupConfig.ScrumGroupRootProjectOID == projectRecord.data.ObjectID;
					});
				if(foundScrumGroupConfig) return Q(projectRecord);
				else {
					var parent = projectRecord.data.Parent;
					if(!parent) return Q();
					else {
						return me.loadProject(parent.ObjectID).then(function(parentRecord){
							return me.projectInWhichScrumGroup(parentRecord);
						});
					}
				}
			}
		},
		loadScrumGroupPortfolioProject: function(scrumGroupRootProjectRecord){
			if(!scrumGroupRootProjectRecord) return Q.reject('Invalid arguments: loadScrumGroupPortfolioProject');
			var me=this,
				foundScrumGroupConfig = _.find(me.ScrumGroupConfig, function(scrumGroupConfig){
					return scrumGroupConfig.ScrumGroupRootProjectOID == scrumGroupRootProjectRecord.data.ObjectID;
				});
			if(!foundScrumGroupConfig) return Q.reject('Project ' + scrumGroupRootProjectRecord.data.Name + ' is not a scrum group!');
			if(foundScrumGroupConfig.ScrumGroupAndPortfolioLocationTheSame) return Q(scrumGroupRootProjectRecord);
			else return me.loadProject(foundScrumGroupConfig.PortfolioProjectOID);
		},
		getScrumGroupName: function(scrumGroupRootProjectRecord){
			if(!scrumGroupRootProjectRecord) throw 'Invalid arguments: getScrumGroupName';
			var me=this,
				foundScrumGroupConfig = _.find(me.ScrumGroupConfig, function(scrumGroupConfig){
					return scrumGroupConfig.ScrumGroupRootProjectOID == scrumGroupRootProjectRecord.data.ObjectID;
				});
			if(!foundScrumGroupConfig) throw 'Project ' + scrumGroupRootProjectRecord.data.Name + ' is not a scrum-group!';
			if(foundScrumGroupConfig.ScrumGroupName) return foundScrumGroupConfig.ScrumGroupName;
			else return scrumGroupRootProjectRecord.data.Name;
		},
		loadAllScrumGroups: function(){
			var me=this,
				filter = _.reduce(me.ScrumGroupConfig, function(filter, scrumGroupConfig){
					var newFilter = Ext.create('Rally.data.wsapi.Filter', { property:'ObjectID', value: scrumGroupConfig.ScrumGroupRootProjectOID });
					return filter ? filter.or(newFilter) : newFilter;
				}, null);
			if(!filter) return Q([]);
			else {
				var store = Ext.create('Rally.data.wsapi.Store',{
					model: 'Project',
					limit:Infinity,
					disableMetaChangeEvent: true,
					autoLoad:false,
					fetch: me.projectFields,
					filters:[filter],
					context:{
						workspace: me.getContext().getWorkspace()._ref,
						project:null
					}
				});
				return me.reloadStore(store).then(function(store){ return store.getRange(); });
			}
		},
		getScrumGroupPortfolioObjectIDs: function(){
			return _.map(me.ScrumGroupConfig, function(item){
				return item.ScrumGroupAndPortfolioLocationTheSame ? item.ScrumGroupRootProjectOID : item.PortfolioProjectOID;
			});
		},
		getPortfolioOIDForScrumGroupRootProjectRecord: function(scrumGroupRootProjectRecord){
			var configItem = _.find(this.ScrumGroupConfig, function(item){
				return item.ScrumGroupRootProjectOID === scrumGroupRootProjectRecord.data.ObjectID;
			});
			return configItem.ScrumGroupAndPortfolioLocationTheSame ? configItem.ScrumGroupRootProjectOID : configItem.PortfolioProjectOID;
		},

		/**************************************** UserStory Funcs ************************************************/
		_getUserStoryInReleaseTimeFrameFilter: function(releaseRecord){
			/** only pull look at PortfolioItem Release if US.Release == null */
			var me=this,
				lowestPortfolioItem = me.PortfolioItemTypes[0],
				twoWeeks = 1000*60*60*24*7*2,
				releaseStartPadding = new Date(new Date(releaseRecord.data.ReleaseStartDate)*1 + twoWeeks).toISOString(),
				releaseEndPadding = new Date(new Date(releaseRecord.data.ReleaseDate)*1 - twoWeeks).toISOString();
			return Ext.create('Rally.data.wsapi.Filter', {
				property:'Release.ReleaseStartDate',
				operator: '<',
				value: releaseStartPadding
			}).and(Ext.create('Rally.data.wsapi.Filter', {
				property:'Release.ReleaseDate',
				operator: '>',
				value: releaseEndPadding
			})).or(
				Ext.create('Rally.data.wsapi.Filter', {
					property:'Release.ObjectID',
					value: null
				}).and(
					Ext.create('Rally.data.wsapi.Filter', {
						property: lowestPortfolioItem + '.Release.ReleaseStartDate',
						operator: '<',
						value: releaseStartPadding
					}).and(Ext.create('Rally.data.wsapi.Filter', {
						property: lowestPortfolioItem + '.Release.ReleaseDate',
						operator: '>',
						value: releaseEndPadding
					}))
				)
			);
		},
		loadRandomUserStory: function(projectRecord){
			if(!projectRecord) return Q.reject('Invalid arguments: loadRandomUserStory');
			var me=this,
				store = Ext.create('Rally.data.wsapi.Store',{
					model: 'HierarchicalRequirement',
					limit:1,
					pageSize:1,
					disableMetaChangeEvent: true,
					fetch: false,
					context:{
						workspace: me.getContext().getWorkspace()._ref,
						project: null
					},
					filters:[{
						property:'Project.ObjectID',
						value: projectRecord.data.ObjectID
					}]
				});
			return me.reloadStore(store).then(function(store){ return store.getRange().pop(); });
		},
		loadRandomUserStoryFromReleaseTimeframe: function(projectRecord, releaseRecord){
			if(!projectRecord || !releaseRecord) return Q.reject('Invalid arguments: loadRandomUserStoryFromReleaseTimeframe');
			var me=this,
				store = Ext.create('Rally.data.wsapi.Store',{
					model: 'HierarchicalRequirement',
					limit:5,
					pageSize:5,
					disableMetaChangeEvent: true,
					fetch: me.userStoryFields,
					context:{
						workspace: me.getContext().getWorkspace()._ref,
						project: undefined
					},
					sorters: [{
						property: 'CreationDate',
						direction:'DESC'
					}],
					filters:[
						Ext.create('Rally.data.wsapi.Filter', { property:'Project.ObjectID', value: projectRecord.data.ObjectID }).and(
						me._getUserStoryInReleaseTimeFrameFilter(releaseRecord))
					]
				});
			return me.reloadStore(store).then(function(store){
				var records = store.data.items;
				if(records.length) return Q(records[Math.floor(Math.random()*records.length)]);
				else return Q(undefined);
			});
		},
		loadUserStoryByFID: function(formattedID, projectRecord){
			if(!formattedID || !projectRecord) return Q.reject('Invalid arguments: loadUserStoryByFID');
			var me=this,
				store = Ext.create('Rally.data.wsapi.Store',{
					model: 'HierarchicalRequirement',
					limit:1,
					pageSize:1,
					disableMetaChangeEvent: true,
					fetch: ['Name', 'Project', 'ObjectID', 'FormattedID', 'Predecessors', 'Successors', 'c_Dependencies'],
					context:{
						workspace: me.getContext().getWorkspace()._ref,
						project: undefined
					},
					filters: [{
						property:'FormattedID',
						value:formattedID
					},{
						property:'Project.ObjectID',
						value: projectRecord.data.ObjectID
					}]
				});
			return me.reloadStore(store).then(function(store){
				return Q(store.data.items.pop());
			});
		},

		/**************************************** PortfolioItem Funcs ************************************************/
		loadPortfolioItemsOfType: function(portfolioProject, type){
			if(!portfolioProject || !type) return Q.reject('Invalid arguments: loadPortfolioItemsOfType');
			var me=this,
				store = Ext.create('Rally.data.wsapi.Store',{
					model: 'PortfolioItem/' + type,
					limit:Infinity,
					disableMetaChangeEvent: true,
					remoteSort:false,
					fetch: me.portfolioItemFields,
					context:{
						project: portfolioProject.data._ref,
						projectScopeDown: true,
						projectScopeUp:false
					}
				});
			return me.reloadStore(store);
		},
		loadPortfolioItemsOfTypeInRelease: function(releaseRecord, portfolioProject, type){
			if(!releaseRecord || !portfolioProject || !type) return Q.reject('Invalid arguments: loadPortfolioItemsOfTypeInRelease');
			var me=this,
				store = Ext.create('Rally.data.wsapi.Store', {
					model: 'PortfolioItem/' + type,
					limit:Infinity,
					disableMetaChangeEvent: true,
					remoteSort:false,
					fetch: me.portfolioItemFields,
					filters:[{ property:'Release.Name', value:releaseRecord.data.Name}],
					context:{
						project: portfolioProject.data._ref,
						projectScopeDown: true,
						projectScopeUp:false
					}
				});
			return me.reloadStore(store);
		},
		loadPortfolioItemsOfOrdinal: function(portfolioProject, ordinal){
			if(!portfolioProject || typeof ordinal === 'undefined') return Q.reject('Invalid arguments: loadPortfolioItemsOfOrdinal');
			var me=this, type = me.PortfolioItemTypes[ordinal];
			if(type) return me.loadPortfolioItemsOfType(portfolioProject, type);
			else return Q.reject('Invalid PortfolioItem ordinal');
		},
		portfolioItemTypeToOrdinal: function(type){
			return this.PortfolioItemTypes.indexOf(type);
		},
		getPortfolioItemTypeStateByOrdinal: function(ordinal, stateName){
			return _.find(this.PortfolioItemTypeStates[ordinal], function(state){ return state.data.Name == stateName; });
		},
		getPortfolioItemTypeStateByName: function(portfolioType, stateName){
			return this.getPortfolioItemTypeStateByOrdinal(this.portfolioItemTypeToOrdinal(portfolioType), stateName);
		},
		createBottomPortfolioItemObjectIDToTopPortfolioItemNameMap: function(portfolioItemStores){
			var portfolioItemMap = {};
			_.each(portfolioItemStores[0].getRange(), function(lowPortfolioItemRecord){ //create the portfolioItem mapping
				var ordinal = 0,
					parentPortfolioItemRecord = lowPortfolioItemRecord,
					getParentRecord = function(child, parentList){
						return _.find(parentList, function(parent){
							return child.data.Parent && parent.data.ObjectID == child.data.Parent.ObjectID;
						});
					};
				while(ordinal < (portfolioItemStores.length-1) && parentPortfolioItemRecord){
					parentPortfolioItemRecord = getParentRecord(parentPortfolioItemRecord, portfolioItemStores[ordinal+1].getRange());
					++ordinal;
				}
				if(ordinal === (portfolioItemStores.length-1) && parentPortfolioItemRecord) //has a mapping, so add it
					portfolioItemMap[lowPortfolioItemRecord.data.ObjectID] = parentPortfolioItemRecord.data.Name;
			});
			return portfolioItemMap;
		},

		/********************************************** Project Funcs ********************************************/
		/****************************** THESE DO NOT WORK WITH sdk 2.0. USE SDK 2.0rc3 *******************/
		_storeItemsToProjTree: function(projects){
			var me=this, projTree = {};
			for(var i=0, len=projects.length; i<len; ++i){
				var project = projects[i],
					thisRef = project.data.ObjectID,
					parentRef = project.data.Parent ? project.data.Parent.ObjectID : undefined;
				if(!projTree[thisRef]) projTree[thisRef] = {};
				projTree[thisRef].ProjectRecord = project;
				if(parentRef){
					if(!projTree[parentRef]) projTree[parentRef] = {};
					projTree[parentRef][thisRef] = projTree[thisRef];
				}
			}
			return projTree;
		},
		_addProjectsWithTeamMembersToList: function(projTree, hash){
			var me=this, curProj = projTree.ProjectRecord;
			if(curProj.data.TeamMembers.Count >0)
				hash[curProj.data.ObjectID] = curProj;
			for(var childProjRef in projTree){
				if(childProjRef !== 'ProjectRecord')
					me._addProjectsWithTeamMembersToList(projTree[childProjRef], hash);
			}
		},
		_allChildProjectToList: function(projTree, hash){
			var me=this, curProj = projTree.ProjectRecord;
			hash[curProj.data.ObjectID] = curProj;
			for(var childProjRef in projTree){
				if(childProjRef !== 'ProjectRecord')
					me._allChildProjectToList(projTree[childProjRef], hash);
			}
		},
		_allLeafProjectsToList: function(projTree, hash){
			var me=this, curProj = projTree.ProjectRecord;
			if(curProj.data.Children.Count === 0)
				hash[curProj.data.ObjectID] = curProj;
			for(var childProjRef in projTree){
				if(childProjRef !== 'ProjectRecord')
					me._allLeafProjectsToList(projTree[childProjRef], hash);
			}
		},
		loadAllProjects: function(){
			var me=this,
				store = Ext.create('Rally.data.wsapi.Store', {
					model: "Project",
					fetch: me.projectFields,
					limit:Infinity,
					disableMetaChangeEvent: true,
					context: {
						workspace: me.getContext().getWorkspace()._ref,
						project:null
					}
				});
			return me.reloadStore(store).then(function(store){
				var map = _.reduce(store.getRange(), function(map, project){
					map[project.data.ObjectID] = project;
					return map;
				}, {});
				return map;
			});
		},
		loadProjectsWithTeamMembers: function(rootProjectRecord){
			//rootProjectRecord is optional
			var me=this,
				projectsWithTeamMembers = {},
				store = Ext.create('Rally.data.wsapi.Store', {
					model: "Project",
					fetch: me.projectFields,
					limit:Infinity,
					disableMetaChangeEvent: true,
					context: {
						workspace: me.getContext().getWorkspace()._ref,
						project:null
					}
				});
			return me.reloadStore(store).then(function(store){
				if(rootProjectRecord){
					var projTree = me._storeItemsToProjTree(store.getRange());
					me._addProjectsWithTeamMembersToList(projTree[rootProjectRecord.data.ObjectID], projectsWithTeamMembers);
					return projectsWithTeamMembers;
				} else {
					return _.reduce(_.filter(store.getRange(),
						function(project){ return project.data.TeamMembers.Count > 0; }),
						function(map, project){
							map[project.data.ObjectID] = project;
							return map;
						}, {});
				}
			});
		},
		loadAllChildrenProjects: function(rootProjectRecord){
			//rootProjectRecord is optional
			var me=this,
				childrenProjects = {},
				store = Ext.create('Rally.data.wsapi.Store', {
					model: "Project",
					fetch: me.projectFields,
					limit:Infinity,
					disableMetaChangeEvent: true,
					context: {
						workspace: me.getContext().getWorkspace()._ref,
						project:null
					}
				});
			return me.reloadStore(store).then(function(store){
				if(rootProjectRecord){
					var projTree = me._storeItemsToProjTree(store.getRange());
					me._allChildProjectToList(projTree[rootProjectRecord.data.ObjectID], childrenProjects);
					return childrenProjects;
				} else {
					return _.reduce(store.getRange(), function(map, project){
						map[project.data.ObjectID] = project;
						return map;
					}, {});
				}
			});
		},
		loadAllLeafProjects: function(rootProjectRecord){
			//rootProjectRecord is optional
			var me=this,
				leafProjects = {},
				store = Ext.create('Rally.data.wsapi.Store', {
					model: "Project",
					fetch: me.projectFields,
					limit:Infinity,
					disableMetaChangeEvent: true,
					context:{
						workspace: me.getContext().getWorkspace()._ref,
						project:null
					}
				});
			return me.reloadStore(store).then(function(store){
				if(rootProjectRecord){
					var projTree = me._storeItemsToProjTree(store.getRange());
					me._allLeafProjectsToList(projTree[rootProjectRecord.data.ObjectID], leafProjects);
					return leafProjects;
				} else {
					return _.reduce(_.filter(store.getRange(),
						function(project){ return project.data.Children.Count === 0; }),
						function(map, project){
							map[project.data.ObjectID] = project;
							return map;
						}, {});
				}
			});
		},
		loadProjectByName: function(projectName){
			if(!projectName) return Q.reject('Invalid arguments: LPBN');
			var me=this,
				store = Ext.create('Rally.data.wsapi.Store',{
					model: 'Project',
					limit:1,
					pageSize:1,
					fetch: ['Name', 'ObjectID'],
					disableMetaChangeEvent: true,
					context:{
						workspace: me.getContext().getWorkspace()._ref,
						project: null
					},
					filters: [{
						property:'Name',
						value:projectName
					}]
				});
			return me.reloadStore(store).then(function(store){
				return Q(store.data.items.pop());
			});
		},

		/********************************************** Release loading ********************************************/
		loadAllReleases: function(projectRecord){
			var me=this,
				store = Ext.create('Rally.data.wsapi.Store',{
					model: 'Release',
					limit:Infinity,
					disableMetaChangeEvent: true,
					autoLoad:false,
					fetch: me.releaseFields,
					filters:[{
						property:'Project.ObjectID',
						value:projectRecord.data.ObjectID
					}],
					context:{
						workspace: me.getContext().getWorkspace()._ref,
						project:null
					}
				});
			return me.reloadStore(store).then(function(store){ return store.getRange(); });
		},
		loadReleasesAfterGivenDate: function(projectRecord, givenDate){
			/** gets releases for this project that have release date >= givenDate. returns promise that resolves to the releaseStore */
			var me=this,
				store = Ext.create('Rally.data.wsapi.Store',{
					model: 'Release',
					limit:Infinity,
					disableMetaChangeEvent: true,
					autoLoad:false,
					fetch: me.releaseFields,
					filters:[{
						property:'ReleaseDate',
						operator:'>=',
						value: new Date(givenDate).toISOString()
					},{
						property:'Project.ObjectID',
						value:projectRecord.data.ObjectID
					}],
					context:{
						workspace: me.getContext().getWorkspace()._ref,
						project:null
					}
				});
			return me.reloadStore(store).then(function(store){ return store.getRange(); });
		},
		loadReleasesBeforeGivenDate: function(projectRecord, givenDate){
			/** gets releases for this project that have release date <= givenDate. returns promise that resolves to the releaseStore */
			var me=this,
				store = Ext.create('Rally.data.wsapi.Store',{
					model: 'Release',
					limit:Infinity,
					autoLoad:false,
					disableMetaChangeEvent: true,
					fetch: me.releaseFields,
					filters:[{
						property:'ReleaseDate',
						operator:'<=',
						value: new Date(givenDate).toISOString()
					},{
						property:'Project.ObjectID',
						value:projectRecord.data.ObjectID
					}],
					context:{
						workspace: me.getContext().getWorkspace()._ref,
						project:null
					}
				});
			return me.reloadStore(store).then(function(store){ return store.getRange(); });
		},
		loadReleasesBetweenDates: function(projectRecord, startDate, endDate){
			var me=this,
				store = Ext.create('Rally.data.wsapi.Store',{
					model: 'Release',
					limit: Infinity,
					autoLoad:false,
					fetch: ['Name', 'ObjectID', 'ReleaseDate', 'ReleaseStartDate', 'Project'],
					context:{
						workspace: me.getContext().getWorkspace()._ref,
						project: null
					},
					filters:[{
						property:'Project.ObjectID',
						value: projectRecord.data.ObjectID
					},{
						property:'ReleaseDate',
						operator:'>',
						value: new Date(startDate).toISOString()
					},{
						property:'ReleaseStartDate',
						operator:'<',
						value: new Date(endDate).toISOString()
					}]
				});
			return me.reloadStore(store).then(function(store){ return store.getRange(); });
		},
		loadReleasesInTheFuture: function(projectRecord){
			return this.loadReleasesAfterGivenDate(projectRecord, new Date());
		},
		loadReleasesByNameUnderProject: function(releaseName, projectRecord){
			var me=this,
				store = Ext.create('Rally.data.wsapi.Store',{
					model: 'Release',
					limit:Infinity,
					disableMetaChangeEvent: true,
					autoLoad:false,
					fetch: me.releaseFields,
					filters:[{
						property:'Name',
						value: releaseName
					}],
					context:{
						project:projectRecord.data._ref,
						projectScopeDown:true,
						projectScopeUp:false
					}
				});
			return me.reloadStore(store).then(function(store){ return store.getRange(); });
		},
		loadReleaseByNameForProject: function(releaseName, projectRecord){
			var me=this,
				store = Ext.create('Rally.data.wsapi.Store',{
					model: 'Release',
					limit:Infinity,
					disableMetaChangeEvent: true,
					autoLoad:false,
					fetch: me.releaseFields,
					filters:[{
						property:'Name',
						value: releaseName
					},{
						property:'Project.ObjectID',
						value:projectRecord.data.ObjectID
					}],
					context:{
						workspace: me.getContext().getWorkspace()._ref,
						project:null
					}
				});
			return me.reloadStore(store).then(function(store){ return store.getRange().pop(); });
		},
		loadReleasesByNameContainsForProject: function(releaseName, projectRecord){
			var me=this,
				store = Ext.create('Rally.data.wsapi.Store',{
					model: 'Release',
					limit:Infinity,
					disableMetaChangeEvent: true,
					autoLoad:false,
					fetch: me.releaseFields,
					filters:[{
						property:'Name',
						operator:'contains',
						value: releaseName
					},{
						property:'Project.ObjectID',
						value:projectRecord.data.ObjectID
					}],
					context:{
						workspace: me.getContext().getWorkspace()._ref,
						project:null
					}
				});
			return me.reloadStore(store).then(function(store){ return store.getRange(); });
		},
		getScopedRelease: function(releaseRecords, projectOID, appPrefs){
			/** gets the most likely release to scope to base on the following order:
				1) if this.AppPrefs.projs[pid] is set to a release ObjectID, and the ReleaseStore has that release (you need
								to use preferences for this one)
				2) if we are currently in one of the releases
				3) the closest release planning date to the current date
			*/
			var me=this,
				d = new Date(),
				rs = releaseRecords,
				prefOID = appPrefs && appPrefs.projs && appPrefs.projs[projectOID] && appPrefs.projs[projectOID].Release;
			return (prefOID && _.find(rs, function(r){ return r.data.ObjectID == prefOID; })) ||
				_.find(rs, function(r){
					return (new Date(r.data.ReleaseDate) >= d) && (new Date(r.data.ReleaseStartDate) <= d);
				}) ||
				_.reduce(rs, function(best, r){
					if(best===null) return r;
					else {
						var d1 = new Date(best.data.ReleaseStartDate)*1, d2 = new Date(r.data.ReleaseStartDate)*1, now = new Date()*1;
						return (Math.abs(d1-now) < Math.abs(d2-now)) ? best : r;
					}
				}, null);
		}
	});
}());
                /**
	SUMMARY:
		This mixin is used if you want to listen to events in the parent window (e.g. useful for rally apps that
		resize with browser screen vertically or things that need to know browser scroll position)
		You also can artificially fire the events and have the listeners run using fireParentWindowEvent
*/
(function(){
	var Ext = window.Ext4 || window.Ext,
		windowListeners = {};

	Ext.define('Intel.lib.mixin.WindowListener', {

		_initWindowEventListener: function(eventName){
			if(!windowListeners) windowListeners = {};
			windowListeners[eventName] = [];

			window.parent['on' + eventName] = function(event){
				var listeners = windowListeners[eventName];
				for(var i=0, len=listeners.length; i<len; ++i)
					listeners[i](event);
			};
		},

		/** eventName is something like: resize, scroll, etc... */
		addWindowEventListener: function(eventName, fn){
			if(!windowListeners || !windowListeners[eventName])
				this._initWindowEventListener(eventName);
			windowListeners[eventName].push(fn);
		},

		/** eventName is something like: resize, scroll, etc... */
		fireParentWindowEvent: function(eventName){
			var me=this;
			if(!windowListeners || !windowListeners[eventName]) return;
			var listeners = windowListeners[eventName];
			for(var i=0, len=listeners.length; i<len; ++i) listeners[i]();
		}
	});
}());
                /**
	SUMMARY:
		this mixin is used to mess with the environment outside of the iframe that the rally app is put in.

	DEPENDENCIES:
		'Intel.lib.mixin.WindowListener' mixed in to app so listen for window events
*/
(function(){
	var Ext = window.Ext4 || window.Ext;

	var TOP_BAR_HEIGHT = 40,
		BOTTOM_BAR_HEIGHT = 24,
		BOTTOM_IFRAME_PADDING = 20,
		TITLE_BAR_HEIGHT = 33,
		IFRAME_HEADER_HEIGHT = 28;

	Ext.define('Intel.lib.mixin.IframeResize', {
		requires: ['Intel.lib.mixin.WindowListener'],

		/**
			makes app as large as screen, without the padding/margin.
			Makes it look more like a built-in rally app instead of a custom app.
			NOTE: as rally changes their interface overtime, this mixin will have to change as well to compensate for it.
		*/
		_fixRallyDashboard: function(){
			if(window && window.frameElement){
				var me=this,
					bottomEl = Ext.get(window.frameElement),
					portlet = bottomEl.up('.x-portlet'),
					dashboard = portlet.up('#mydash_portlet'), //has huge padding values
					titleBar = dashboard.down('.titlebar'), //redundant with app header bar
					domNodeW = bottomEl.dom, domNodeH = bottomEl.dom,
					innerHeight = window.parent.innerHeight,
					innerWidth = window.parent.innerWidth;

				//adjust widths
				while(true){
					domNodeW.style.width = (innerWidth - 4) + 'px';
					domNodeW.style.padding = '0';
					domNodeW.style.margin = '0';
					if(domNodeW.id === 'mydash_portlet') break;
					domNodeW = domNodeW.parentNode;
				}

				//adjust heights
				while(true){
					domNodeH.style.height = (innerHeight - (TOP_BAR_HEIGHT + BOTTOM_BAR_HEIGHT + TITLE_BAR_HEIGHT + IFRAME_HEADER_HEIGHT + BOTTOM_IFRAME_PADDING)) + 'px';
					if(domNodeH.classList.contains('x-portlet')) break;
					domNodeH = domNodeH.parentNode;
				}
				while(true){
					domNodeH.style.height = (innerHeight - (TOP_BAR_HEIGHT + BOTTOM_BAR_HEIGHT + TITLE_BAR_HEIGHT)) + 'px';
					if(domNodeH.id == 'mydash_portlet') break;
					domNodeH = domNodeH.parentNode;
				}
				dashboard.dom.style.height = (innerHeight - (TOP_BAR_HEIGHT + BOTTOM_BAR_HEIGHT)) + 'px';

				//final touches LOL
				dashboard.dom.style.padding = "0 2px 0 2px";
				titleBar.dom.style.padding = "2px";
				titleBar.dom.style.margin = "0";
			}
		},
		initFixRallyDashboard: function(){
			var me=this;
			if(me.addWindowEventListener){
				me.addWindowEventListener('resize', function(){ me._fixRallyDashboard(); });
			}
			me._fixRallyDashboard();
		},

		/**
			hides the draggable resize handle from under the app
		*/
		_disableResizeHandle: function(){
			var me=this, handle;
			if(window && window.frameElement){
				handle = Ext.get(window.frameElement).up('.x-portlet').down('.x-resizable-handle');
				if(handle) {
					handle.hide();
					handle.dom.onshow = function(){ if(handle) handle.hide(); };
				}
			}
		},
		initDisableResizeHandle: function(){
			var me=this;
			if(me.addWindowEventListener){
				me.addWindowEventListener('resize', function(){ me._disableResizeHandle(); });
			}
			me._disableResizeHandle();
		}
	});
}());
                /**
	DESCRIPTION:
		This mixin gives a window-centered alert or confirm dialog box that isn't ugly.

	ISSUES:
		Sometimes alert.setY() fails so we have to fallback to the ugly built-in alert
*/
(function(){
	var Ext = window.Ext4 || window.Ext;

	Ext.define('Intel.lib.mixin.PrettyAlert', {
		_getMessageBoxY: function(){
			var me=this,
				bottomEl = window.frameElement ? Ext.get(window.frameElement) : me.el,
				ph = window.frameElement ? window.parent.getWindowHeight() : window.innerHeight,
				ps = window.frameElement ? window.parent.getScrollY() : window.scrollY,
				ofy = ps + bottomEl.dom.getBoundingClientRect().top, //offset of top of the iframe ==== constant!!!
				iyOffset = Math.floor(ph/2 - ofy + ps - 50);
			return iyOffset<0 ? 0 : iyOffset;
		},

		_formatString: function(message){
			message = message || '';
			return (typeof message === 'string') ?
				message : (message.message ?
				message.message : JSON.stringify(message, null, '\t'));
		},

		/** non-ugly alert dialog */
		alert: function(title, message){
			if(arguments.length<1) return;
			if(arguments.length===1){
				message = title;
				title = '';
			}

			try {
				Ext.MessageBox.alert(this._formatString(title), this._formatString(message)).setY(this._getMessageBoxY());
				setTimeout(function(){
					var x = Ext.MessageBox.down('button');
					while(x.isHidden()) x = x.nextSibling();
					x.focus();
				}, 50);
			}
			catch(e){ alert(this._formatString(message)); }
		},

		/** non-ugly confirm dialog */
		confirm: function(title, message, fn){
			if(arguments.length<2) return;
			if(arguments.length===2){
				fn = message;
				message = title;
				title = '';
			}
			if(typeof fn !== 'function') fn = function(){};

			try {
				Ext.MessageBox.confirm(this._formatString(title), this._formatString(message), fn).setY(this._getMessageBoxY());
				setTimeout(function(){
					var x = Ext.MessageBox.down('button');
					while(x.isHidden()) x = x.nextSibling();
					x.focus();
				}, 50);
			}
			catch(e){ if(confirm(this._formatString(message))) fn('yes'); else fn('no'); }
		}
	});
}());
                /**
	intel workweek utility module. you can pass in Date objects, strings, or numbers.
	do not pass in Unix UTC millis though, or you will get wrong answer (eg: dont use Date.UTC(...))
**/
(function(){
	var Ext = window.Ext4 || window.Ext,
		intel_ww = {},
		SECOND = 1000,
		MINUTE = 60*SECOND,
		HOUR = 60*MINUTE,
		DAY = 24*HOUR,
		WEEK = 7*DAY;

	Ext.define('Intel.lib.mixin.IntelWorkweek', {
		/** calculates intel workweek, returns integer */
		getWorkweek: function(_date){  //ww1 always contains jan 1st
			var date = new Date(_date),
				yearStart = new Date(date.getFullYear(), 0, 1),
				dayIndex = yearStart.getDay(),
				ww01Start = new Date(yearStart - dayIndex*DAY),
				utcDateMillis = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()),
				ww01Millis = Date.UTC(ww01Start.getFullYear(), ww01Start.getMonth(), ww01Start.getDate()),
				timeDiff = utcDateMillis - ww01Millis,
				ww = Math.floor(timeDiff/WEEK) + 1,
				leap = (date.getFullYear() % 4 === 0),
				weekCount = ((leap && dayIndex >= 5) || (!leap && dayIndex === 6 )) ? 53 : 52; //weeks in this year
			return weekCount < ww ? 1 : ww;
		},

		/** returns the number of intel workweeks in the year the date is in */
		getWeekCount: function(_date){
			var date = new Date(_date),
				leap = (date.getFullYear() % 4 === 0),
				day = new Date(date.getFullYear(), 0, 1).getDay();
			return ((leap && day >= 5) || (!leap && day === 6 )) ? 53 : 52;
		},

		roundDateDownToWeekStart: function(_date){
			var date = new Date(_date),
				day = date.getDay(),
				monthDate = date.getDate(),
				month = date.getMonth(),
				year = date.getFullYear(),
				sundayMidday = new Date(new Date(year, month, monthDate)*1 - (day*DAY - 0.5*DAY)); //daylight savings ._.
			return new Date(sundayMidday.getFullYear(), sundayMidday.getMonth(), sundayMidday.getDate());
		},

		/**  gets list of Date()'s for each week start between start and end date*/
		getWorkweekDates: function(startDate, endDate){ //gets list of dates for each week. INCLUSIVE
			var startWeekDate = this.roundDateDownToWeekStart(startDate),
				endWeekDate = this.roundDateDownToWeekStart(endDate),
				startMillis = Date.UTC(startWeekDate.getFullYear(), startWeekDate.getMonth(), startWeekDate.getDate()),
				endMillis = Date.UTC(endWeekDate.getFullYear(), endWeekDate.getMonth(), endWeekDate.getDate()),
				totalWeeks = Math.floor((endMillis - startMillis) / WEEK)+1,
				weeks = new Array(totalWeeks);
			for(var i=0; i<totalWeeks; ++i) {
				var sundayMidday = new Date(startWeekDate*1 + (WEEK*i + HOUR*12));
				weeks[i] = new Date(sundayMidday.getFullYear(), sundayMidday.getMonth(), sundayMidday.getDate());
			}
			return weeks;
		},

		/** gets the Date() object of this ww and year */
		workweekToDate: function(ww, year){
			var yearStart = new Date(year, 0, 1),
				dayIndex = yearStart.getDay(),
				ww01StartMidday = new Date(yearStart - (dayIndex*DAY - 0.5*DAY)),
				sundayMidday = new Date(ww01StartMidday*1 + (ww-1)*WEEK);
			return new Date(sundayMidday.getFullYear(), sundayMidday.getMonth(), sundayMidday.getDate());
		},

		/** assumes DropDown uses Intel.lib.data.WorkweekDropdown model */
		getWorkweeksForDropdown: function(releaseStartDate, releaseEndDate){
			var workweeks = this.getWorkweekDates(releaseStartDate, releaseEndDate),
				data = new Array(workweeks.length);
			for(var i=0, len=workweeks.length; i<len; ++i){
				data[i] = {
					Workweek: 'ww' + this.getWorkweek(workweeks[i]),
					DateVal: workweeks[i]*1
				};
			}
			return data;
		}
	});
}());
                /**
	SUMMARY:
		This is a mixin for using highcharts with rally data data after it is run through the lookback calculator.
		It just formats the data, and adds trendlines and labels to the charts.

		This mixin assumes you are using UserStories with lookback api and you are mapping ScheduleState vs. Time.

	DEPENDENCIES:
		'Intel.lib.IntelRallyApp' parent class of app for me.ScheduleStates,
		'Intel.lib.mixin.IntelWorkweek' mixed into app for me.getWorkweek,
		Sylvester math library
		lodash
*/
(function(){
	var Ext = window.Ext4 || window.Ext;

	var ChartsTooltipDatemap = {}, //closure variable that maps the x values to date strings -- per chart
		RSquaredMap = {},
		defaultCFCConfig = {
			chart: {
				defaultSeriesType: "area",
				zoomType: "xy"
			},
			colors: [
				'#ABABAB',
				'#E57E3A',
				'#E5D038',
				'#0080FF',
				'#3A874F',
				'#26FF00',
				'#000000'
			],
			xAxis: {
				tickmarkPlacement: "on",
				title: {
					text: "Days",
					margin: 10
				},
				labels: {
					y: 20
				}
			},
			yAxis: {
				title: {
					text: "Points"
				},
				labels: {
					x: -5,
					y: 4
				}
			},
			tooltip: {
				formatter: function () {
					var sum = 0,
						datemap = ChartsTooltipDatemap[this.series.chart.container.id],
						rSquaredMap = RSquaredMap[this.series.chart.container.id];
					for(var i=4; i>= this.series.index; --i)
						sum += this.series.chart.series[i].data[this.point.x].y;
					return "<b>" + this.x + '</b>' + (datemap ? ' (' + datemap[this.point.x] + ')' : '') +
						((rSquaredMap && rSquaredMap[this.series.index]) ? '<br/><b>R<sup>2</sup> = ' + rSquaredMap[this.series.index].val : '') +
						"<br /><b>" + this.series.name + "</b>: " + ((100*this.y>>0)/100) +
						(this.series.index <=4 ? "<br /><b>Total</b>: " + ((100*sum>>0)/100) : '');
				}
			},
			plotOptions: {
				series: {
					marker: {
						enabled: false,
						states: {
							hover: {
								enabled: true
							}
						}
					},
					groupPadding: 0.01
				},
				area: {
					stacking: 'normal',
					lineColor: '#666666',
					lineWidth: 2,
					marker: {
						enabled: false
					}
				}
			}
		};

	Ext.define('Intel.lib.mixin.CumulativeFlowChartMixin', {
		requires:[
			'Intel.lib.IntelRallyApp',
			'Intel.lib.mixin.IntelWorkweek'
		],

		getDefaultCFCConfig: function(){
			return _.merge({}, defaultCFCConfig);
		},
		getCumulativeFlowChartColors: function(){
			var me=this,
				colors = me.getDefaultCFCConfig().colors,
				scheduleStates = me.ScheduleStates;
			if(scheduleStates.length >= 5) return {colors: colors};
			else return {colors: colors.slice(0, scheduleStates.length).concat(colors.slice(scheduleStates.length + 1))};
		},
		getValidTrendTypes: function(){
			return [
				'FromZero',
				'FromStartAccepted',
				'FromStartWork',
				'LastWeek',
				'LastSprint',
				'Last2Sprints',
				'LinearRegression',
				'LinearRegressionFromStartAccepted',
				'LinearRegressionFromStartWork'
			];
		},
		_getRSquared: function(ySeries, fSeries, lastIndex){
			//using algorithm from http://en.wikipedia.org/wiki/Coefficient_of_determination
			if(lastIndex <= 0) return 1;
			var ys = ySeries.data.slice(0, lastIndex),
				fs = fSeries.data.slice(0, lastIndex),
				meanY= _.reduce(ys, function(sum, yi){ return sum+(yi|| 0); }, 0)/ys.length,
				SStot = _.reduce(ys, function(sum, yi){ return sum + Math.pow((yi|| 0)-meanY, 2); }, 0),
				SSres = _.reduce(fs, function(sum, fi, i){ return sum + Math.pow((ys[i] || 0) - (fi || 0), 2); }, 0);
			return (1000*(1 - SSres/SStot)>>0)/1000;
		},
		_addProjectedTrendline: function(data, options){
			var me=this,
				totalPoints = options.totalPoints,
				trendType = options.trendType,
				validTypes = me.getValidTrendTypes(),
				slope, intercept, X, Y,
				scheduleStateSeries  = _.filter(data.series, function(s){ return me.ScheduleStates.indexOf(s.name) > -1; }),
				scheduleStatesSumList = _.times(scheduleStateSeries[0].length, function(n){
					return _.reduce(scheduleStateSeries, function(sum, s){ return sum + (s.data[n] || 0); }, 0);
				});
			trendType = _.find(validTypes, function(type){ return type == trendType; }) || validTypes[0];

			//initialize projected trendline
			var topScheduleState = me.ScheduleStates.slice(-1)[0],
				topScheduleStateSeries = _.find(data.series, function(s){ return s.name === topScheduleState; }), i, len,
				projectedTrend = {type:'spline', dashStyle:'Solid', name:'Projected', data:topScheduleStateSeries.data.slice()},
				begin=0,
				end=projectedTrend.data.length-1;

			if(trendType == 'FromZero'){
				for(i=end;i>=begin;--i) //start at the END, not at begin+1 (can go from 0 to 10 to 0. so start at last 0)
					if(projectedTrend.data[i]!==0){
						end = i; break; }
				slope = (end===begin) ? 0 : (projectedTrend.data[end] - projectedTrend.data[begin])/(end-begin);
				projectedTrend.data = _.map(projectedTrend.data, function(p, j){
					return (100*(projectedTrend.data[begin] + (j-begin)*slope)>>0)/100;
				});
			}
			if(trendType == 'FromStartAccepted'){
				for(i=1;i<projectedTrend.data.length;++i)
					if(projectedTrend.data[i]!==null && projectedTrend.data[i] !==0){
						begin = i-1; break; }
				for(i=end;i>=begin;--i) //start at the END, not at begin+1 (can go from 0 to 10 to 0. so start at last 0)
					if(projectedTrend.data[i]!==0){
						end = i; break; }
				slope = (end===begin) ? 0 : (projectedTrend.data[end] - projectedTrend.data[begin])/(end-begin);
				projectedTrend.data = _.map(projectedTrend.data, function(p, j){
					var pt = (100*(projectedTrend.data[begin] + (j-begin)*slope)>>0)/100;
					return pt < 0 ? 0 : pt;
				});
			}
			if(trendType == 'FromStartWork'){
				for(i=1;i<scheduleStatesSumList.length;++i)
					if(scheduleStatesSumList[i]!==null && scheduleStatesSumList[i] !==0){
						begin = i-1; break; }
				for(i=end;i>=begin;--i) //start at the END, not at begin+1 (can go from 0 to 10 to 0. so start at last 0)
					if(projectedTrend.data[i]!==0){
						end = i; break; }
				slope = (end===begin) ? 0 : (projectedTrend.data[end] - projectedTrend.data[begin])/(end-begin);
				projectedTrend.data = _.map(projectedTrend.data, function(p, j){
					var pt = (100*(projectedTrend.data[begin] + (j-begin)*slope)>>0)/100;
					return pt < 0 ? 0 : pt;
				});
			}
			if(trendType == 'LastWeek'){
				for(i=end;i>=begin;--i) //start at the END, not at begin+1 (can go from 0 to 10 to 0. so start at last 0)
					if(projectedTrend.data[i]!==0){
						end = i; break; }
				begin = (end - 5 < 0 ? 0 : end - 5);
				slope = (end===begin) ? 0 : (projectedTrend.data[end] - projectedTrend.data[begin])/(end-begin);
				projectedTrend.data = _.map(projectedTrend.data, function(p, j){
					var pt = (100*(projectedTrend.data[begin] + (j-begin)*slope)>>0)/100;
					return pt < 0 ? 0 : pt;
				});
			}
			if(trendType == 'LastSprint'){
				for(i=end;i>=begin;--i) //start at the END, not at begin+1 (can go from 0 to 10 to 0. so start at last 0)
					if(projectedTrend.data[i]!==0){
						end = i; break; }
				begin = (end - 10 < 0 ? 0 : end - 10);
				slope = (end===begin) ? 0 : (projectedTrend.data[end] - projectedTrend.data[begin])/(end-begin);
				projectedTrend.data = _.map(projectedTrend.data, function(p, j){
					var pt = (100*(projectedTrend.data[begin] + (j-begin)*slope)>>0)/100;
					return pt < 0 ? 0 : pt;
				});
			}
			if(trendType == 'Last2Sprints'){
				for(i=end;i>=begin;--i) //start at the END, not at begin+1 (can go from 0 to 10 to 0. so start at last 0)
					if(projectedTrend.data[i]!==0){
						end = i; break; }
				begin = (end - 20 < 0 ? 0 : end - 20);
				slope = (end===begin) ? 0 : (projectedTrend.data[end] - projectedTrend.data[begin])/(end-begin);
				projectedTrend.data = _.map(projectedTrend.data, function(p, j){
					var pt = (100*(projectedTrend.data[begin] + (j-begin)*slope)>>0)/100;
					return pt < 0 ? 0 : pt;
				});
			}
			if(trendType == 'LinearRegression'){
				//(Xt*X)^-1*Xt*Y = b
				for(i=end;i>=begin;--i) //start at the END, not at begin+1 (can go from 0 to 10 to 0. so start at last 0)
					if(projectedTrend.data[i]!==0){
						end = i; break; }
				X = $M(_.map(projectedTrend.data.slice(begin, end), function(p, j){ return [1, j]; }));
				Y = $M(_.map(projectedTrend.data.slice(begin, end), function(p){ return p; }));
				b = X.transpose().multiply(X).inverse().multiply(X.transpose().multiply(Y));
				slope = b.elements[1][0];
				intercept = b.elements[0][0];

				projectedTrend.data = _.map(projectedTrend.data, function(p, j){
					var pt = (100*(intercept + j*slope)>>0)/100;
					return pt < 0 ? 0 : pt;
				});
			}
			if(trendType == 'LinearRegressionFromStartAccepted'){
				//(Xt*X)^-1*Xt*Y = b
				for(i=1;i<projectedTrend.data.length;++i)
					if(projectedTrend.data[i]!==null && projectedTrend.data[i] !==0){
						begin = i-1; break; }
				for(i=end;i>=begin;--i) //start at the END, not at begin+1 (can go from 0 to 10 to 0. so start at last 0)
					if(projectedTrend.data[i]!==0){
						end = i; break; }
				X = $M(_.map(projectedTrend.data.slice(begin, end), function(p, j){ return [1, j]; }));
				Y = $M(_.map(projectedTrend.data.slice(begin, end), function(p){ return p; }));
				b = X.transpose().multiply(X).inverse().multiply(X.transpose().multiply(Y));
				slope = b.elements[1][0];
				intercept = b.elements[0][0];

				projectedTrend.data = _.map(projectedTrend.data, function(p, j){
					var pt = (100*(intercept + (j-begin)*slope)>>0)/100;
					return pt < 0 ? 0 : pt;
				});
			}
			if(trendType == 'LinearRegressionFromStartWork'){
				for(i=1;i<scheduleStatesSumList.length;++i)
					if(scheduleStatesSumList[i]!==null && scheduleStatesSumList[i] !==0){
						begin = i-1; break; }
				for(i=end;i>=begin;--i) //start at the END, not at begin+1 (can go from 0 to 10 to 0. so start at last 0)
					if(projectedTrend.data[i]!==0){
						end = i; break; }
				X = $M(_.map(projectedTrend.data.slice(begin, end), function(p, j){ return [1, j]; }));
				Y = $M(_.map(projectedTrend.data.slice(begin, end), function(p){ return p; }));
				b = X.transpose().multiply(X).inverse().multiply(X.transpose().multiply(Y));
				slope = b.elements[1][0];
				intercept = b.elements[0][0];

				projectedTrend.data = _.map(projectedTrend.data, function(p, j){
					var pt = (100*(intercept + (j-begin)*slope)>>0)/100;
					return pt < 0 ? 0 : pt;
				});
			}

			//apply label to correct point if needed IGNORE FIRST POINT!
			if(slope >= 0){
				for(i=1,len=projectedTrend.data.length; i<len;++i){
					if(projectedTrend.data[i] >= totalPoints){
						projectedTrend.data[i] = {
							color:'red',
							marker:{
								enabled:true,
								lineWidth:4,
								symbol:'circle',
								fillColor:'red',
								lineColor:'red'
							},
							y: projectedTrend.data[i]
						};
						break;
					}
				}
			}
			return projectedTrend;
		},
		updateCumulativeFlowChartData: function(data, options){
			_.merge({}, options);
			var me = this,
				now = new Date(),
				trendType = options.trendType,
				hideTrends = options.hideTrends,
				todayIndex = -1,
				datemap = [],
				rSquaredMap = [];

			//get the index that is today
			if(new Date(data.categories[0]) > now) todayIndex = -1;
			else if(new Date(data.categories[data.categories.length - 1]) < now) todayIndex = data.categories.length;
			else todayIndex = _.reduce(data.categories, function(savedI, c, i){
				if(new Date(c) > now && savedI === -1) savedI = (i-1);
				return savedI;
			}, -1);

			//get top scheduleState series
			var topScheduleState = me.ScheduleStates.slice(-1)[0],
				topScheduleStateSeries = _.find(data.series, function(s){ return s.name === topScheduleState; });

			//get ideal trendline if release has started
			var totalPoints = (new Date(data.categories[0]) > now ? 0 :
					_.reduce(data.series, function(sum, s){return sum + (s.data[s.data.length-1] || 0);
				}, 0) || 0),
				idealTrend, ratio;

			if(!hideTrends){
				idealTrend = {type:'spline', dashStyle:'Solid', name:'Ideal', data:new Array(data.categories.length)};
				ratio = (totalPoints/(data.categories.length-1)) || 0; //for NaN
				idealTrend.data = _.map(idealTrend.data, function(e, i){ return Math.round(100*(0 + i*ratio))/100; });
			}

			//zero future points, convert to workweeks, and set datemap
			_.each(data.categories, function(c, i, a){
				var d = new Date(c);
				a[i] = 'ww' + me.getWorkweek(d);
				datemap[i] = c;
				if(d>now){
					_.each(data.series, function(s, j){
						s.data = s.data.slice(0, i).concat(_.map(new Array(a.length - i), function(){ return 0; }));
					});
				}
			});

			if(!hideTrends){
				var projectedTrend = me._addProjectedTrendline(data, {totalPoints: totalPoints, trendType: trendType});
				data.series.push(projectedTrend);
				rSquaredMap[data.series.length-1] = {val: me._getRSquared(projectedTrend, topScheduleStateSeries, todayIndex)};
				data.series.push(idealTrend);
			}
			data.datemap = datemap;
			data.rSquaredMap = rSquaredMap;

			return data;
		},
		getCumulativeFlowChartTicks: function(startDate, endDate, width){
			var pixelTickWidth = 40,
				ticks = width/pixelTickWidth>>0,
				oneDay = 1000*60*60*24,
				days = (endDate*1 - startDate*1)/(oneDay/* *5/7 */)>>0, //NOT only workdays (now includes weekends)
				interval = ((days/ticks>>0)/7>>0)*7;
			return (interval < 7) ? 7 : interval; //make it weekly at the minimum
		},
		setCumulativeFlowChartDatemap: function(chartContainerId, datemap){
			ChartsTooltipDatemap[chartContainerId] = datemap;
		},
		setCumulativeFlowChartRSquaredMap: function(chartContainerId, rSquaredMap){
			RSquaredMap[chartContainerId] = rSquaredMap;
		}
	});
}());
                /**
	SUMMARY:
		This mixin is used to save user preferences per project, that can follow the user from app to app. So
		when you call saveAppsPreference it saves it to a preference filtered by user and the value of me.userAppsPref.

		Different apps can share the me.userAppsPref which is useful if you have 5 apps that should all scope to the same
		Release. So if the user scopes to Release R1 on one of the apps, when she goes to any of the other apps, it should
		scope to Release R1 as well.
**/
(function(){
	var Ext = window.Ext4 || window.Ext;

	Ext.define('Intel.lib.mixin.UserAppsPreference', {

		/** preference name SHOULD be overridden, unless you want all apps to share default preference-name */
		userAppsPref: 'intel-user-apps-preference',

		loadAppsPreference: function(){
			var me=this, deferred = Q.defer();
			Rally.data.PreferenceManager.load({
				filterByUser:true,
				filterByName: me.userAppsPref,
				success: function(prefs) {
					var appPrefs = prefs[me.userAppsPref];
					try{ appPrefs = JSON.parse(appPrefs); }
					catch(e){ appPrefs = { projs:{}, refresh:0};}
					deferred.resolve(appPrefs);
				},
				failure: deferred.reject
			});
			return deferred.promise;
		},
		saveAppsPreference: function(prefs){
			var me=this, s = {}, deferred = Q.defer();
			prefs = {projs:prefs.projs, refresh:prefs.refresh};
			s[me.userAppsPref] = JSON.stringify(prefs);
			Rally.data.PreferenceManager.update({
				filterByUser: true,
				filterByName: me.userAppsPref,
				settings: s,
				success: deferred.resolve,
				failure: deferred.reject
			});
			return deferred.promise;
		}
	});
}());
                /**
	SUMMARY:
		Use this mixin to load stores that have lots of records. it will load them in parallel instead of serially.
		what it does is loads 1 page and then based on the totalCount it loads the rest of the pages in parallel.

		The config you pass to parallelLoadLookbackStore and parallelLoadWsapiStore are the same configs you would pass
		to Rally.data.lookback.Store (or something like that) and Rally.data.wsapi.Store.
*/
(function(){
	var Ext = window.Ext4 || window.Ext;

	Ext.define('Intel.lib.mixin.ParallelLoader', {
		parallelLoadWsapiStore: function(config){
			var me=this, data = [], model;
			function doStoreLoad(page){
				var deferred = Q.defer(),
					store = Ext.create('Rally.data.wsapi.Store', _.merge({}, config, {
						pageSize:200,
						listeners:{
							load: function(store, records, successful){
								if(!successful) deferred.reject('could not load data from server');
								else deferred.resolve(store);
							}
						}
					}));
				store.loadPage(page);
				return deferred.promise;
			}
			function makeStore(){
				return Ext.create('Rally.data.wsapi.Store', {
					model: model,
					totalCount: data.length,
					data: data,
					disableMetaChangeEvent: true,
					load: function(){}
				});
			}
			return doStoreLoad(1).then(function(store){
				data = data.concat(store.getRange());
				model = store.model;
				var pages = ((store.totalCount/200>>0) + (store.totalCount%200 ? 1 : 0)) || 1;
				if(pages === 1) return makeStore();
				else return Q.all(_.times(pages-1, function(pageNum){
					return doStoreLoad(pageNum + 2).then(function(store){ data = data.concat(store.getRange()); });
				})).then(makeStore);
			});
		},
		parallelLoadLookbackStore: function(config){
			var me=this, data = [], model;
			function doStoreLoad(page){
				var deferred = Q.defer(),
					store = Ext.create('Rally.data.lookback.SnapshotStore', _.merge({}, config, {
						pageSize:20000,
						listeners:{
							load: function(store, records, successful){
								if(!successful) deferred.reject('could not load data from server');
								else deferred.resolve(store);
							}
						}
					}));
				store.loadPage(page);
				return deferred.promise;
			}
			function makeStore(){
				return Ext.create('Rally.data.lookback.SnapshotStore', {
					model: model,
					totalCount: data.length,
					data: data,
					disableMetaChangeEvent: true,
					load: function(){}
				});
			}
			return doStoreLoad(1).then(function(store){
				data = data.concat(store.getRange());
				model = store.model;
				var pages = ((store.totalCount/20000>>0) + (store.totalCount%20000 ? 1 : 0)) || 1;
				if(pages === 1) return makeStore();
				else return Q.all(_.times(pages-1, function(pageNum){
					return doStoreLoad(pageNum + 2).then(function(store){ data = data.concat(store.getRange()); });
				})).then(makeStore);
			});
		}
	});
}());

                /**
	SUMMARY:
		This mixin is used to communicate between apps the objectID of eachother, so they can link to eachother
		by saving their ObjectIDs in a preference tied to the workspace.

		To get the ObjectID of an app in teh workspace, you need to pass the ClassName of the app,
		which is the string that you pass you Ext.define(<className>, {appConfigObject})

	DEPENDENCIES:
		Q promise library
**/
(function(){
	var Ext = window.Ext4 || window.Ext,
		customAppRegisterObjectIdPref = 'intel-custom-app-objectid-register-preference';

	Ext.define('Intel.lib.mixin.CustomAppObjectIDRegister', {
		_loadCustomAppObjectIDSettings: function(){
			var me=this, deferred = Q.defer();
			Rally.data.PreferenceManager.load({
				workspace: me.getContext().getWorkspace()._ref,
				filterByName: customAppRegisterObjectIdPref,
				success: function(prefs){
					var map;
					try{ map = JSON.parse(prefs[customAppRegisterObjectIdPref]); }
					catch(e){ map = {}; }
					deferred.resolve(map);
				},
				failure: deferred.reject
			});
			return deferred.promise;
		},

		getCustomAppObjectID: function(appClassName){
			var me=this;
			return me._loadCustomAppObjectIDSettings().then(function(customAppOIDSettings){
				return customAppOIDSettings[appClassName] || null;
			});
		},
		setCustomAppObjectID: function(appClassName){
			var me=this, s = {},
				objectID = window.parent.location.hash.split("/").pop();

			if(!appClassName) return Q.reject('invalid appClassName');
			return me._loadCustomAppObjectIDSettings().then(function(customAppOIDSettings){
				var deferred = Q.defer();
				customAppOIDSettings[appClassName] = objectID;
				s[customAppRegisterObjectIdPref] = JSON.stringify(customAppOIDSettings, null, '  ');
				Rally.data.PreferenceManager.update({
					workspace: me.getContext().getWorkspace()._ref,
					filterByName: customAppRegisterObjectIdPref,
					settings: s,
					success: deferred.resolve,
					failure: deferred.reject
				});
				return deferred.promise;
			});
		}
	});
}());
                //TODO: find this on a CDN and remove it from here
(function(){
	var Ext = window.Ext4 || window.Ext;

	var POLL_INTERVAL_MS = 10;

	/*!
	 * CTemplate
	 * Version 1.1
	 * Copyright(c) 2011-2013 Skirtle's Den
	 * License: http://skirtlesden.com/ux/ctemplate

		MY NOTE: how this works. XTemplates use apply() to convert the template to the actual HTML. CTemplate converts XTemplate into a bunch
		of <p> tags and stores the ids of them and then immediately starts polling for them in doPolling. do POlling calls injectComponents
		after the poll interval and if injectComponents does not render all the components, it calls doPolling again.
		injectComponents calls renderComponent for each component.
		overwrite and doInsert call the XTemplate parent functions, which call CTemplate's apply, and then they call injectComponents.

		NOTE: everytime apply() is called, the me.ids[] is generated, and when injectComponents is finished, me.ids[] is empty!
	 */
	Ext.define('Skirtle.CTemplate',{
		extend: 'Ext.XTemplate',
		statics: { AUTO_ID: 0 },

		copyDepth: 10,
		cTpl: '<p id="ctemplate-{0}-{1}"></p>',
		isCTemplate: true,

		constructor: function(){
			var me = this;
			me.callParent(arguments);
			me.id = ++me.statics().AUTO_ID;
			me.reset();
		},

		copyValues: function(values, depth){
			/* Takes a recursive copy of the values provided, switching out components for placeholder values. The component ids
			 * are recorded and injectComponents() uses the ids to find the placeholder elements in the DOM and switch in the
			 * components.
			 */
			var me = this,
				id,
				copy = {},
				copyDepth = depth || me.copyDepth;

			if(copyDepth === 1) return values;

			if(Ext.isArray(values)){
				return Ext.Array.map(values, function(value) {
					return me.copyValues(value, copyDepth - 1);
				});
			}

			if(!Ext.isObject(values)) return values;

			// This is the key sleight-of-hand that makes the whole thing work
			if(values.isComponent){
				id = values.getId();
				me.ids.push(id);
				return Ext.String.format(me.cTpl, id, me.id);
			}

			Ext.Object.each(values, function(key, value) {
				// $comp is a special value for a renderTpl that references the current component
				copy[key] = key === '$comp' ? value : me.copyValues(value, copyDepth - 1);
			});

			return copy;
		},
		doInsert: function() { // Override
			//(calls this.apply() to convert ids to templates for XTemplate to put in the DOM
			var ret = this.callParent(arguments);
			this.injectComponents();// There's no guarantee this will succeed so we still need polling as well
			return ret;
		},
		doPolling: function(interval) {
			var me = this;
			me.pollInterval = interval;
			if(me.pollId) clearTimeout(me.pollId);
			me.pollId = Ext.defer(me.injectComponents, interval, me);
		},
		getPlaceholderEl: function(id) {
			return Ext.get('ctemplate-' + id + '-' + this.id);
		},
		injectComponents: function() {
			/* Attempts to substitute all placeholder elements with the real components. If a component is successfully injected
			 * or it has been destroyed then it won't be attempted again. This method is repeatedly invoked by a polling
			 * mechanism until no components remain, however relying on the polling is not advised. Instead it is preferable to
			 * call this method directly as soon as the generated HTML is inserted into the DOM.
			 */
			var me = this,
				ids = me.ids,
				index = ids.length - 1,
				id,
				cmp,
				placeholderEl;

			// Iterate backwards because we remove some elements in the loop
			for( ; index >= 0 ; --index){
				id = ids[index];
				cmp = Ext.getCmp(id);
				placeholderEl = me.getPlaceholderEl(id);
				if(me.renderComponent(cmp, placeholderEl) || !cmp){
					// Either we've successfully done the switch or the component has been destroyed
					Ext.Array.splice(ids, index, 1);
					if(placeholderEl) placeholderEl.remove();
				}
			}
			// Some components have not been injected. Polling acts both to do deferred injection and as a form of GC
			if(ids.length) me.doPolling(me.pollInterval); //originally was me.pollInterval * 1.5
		},
		overwrite: function(el){ // Override
			//(calls this.apply() to convert ids to templates for XTemplate to put in the DOM
			var dom,
				firstChild,
				ret;

			/* In IE setting the innerHTML will destroy the nodes for the previous content. If we try to reuse components it
			 * will fail as their DOM nodes will have been torn apart. We can't defend against external updates to the DOM
			 * but we can guard against the case where all updates come through this template.
			 */
			if(Ext.isIE){
				dom = Ext.getDom(el);
				while(dom.firstChild){
					dom.removeChild(dom.firstChild);
				}
			}
			ret = this.callParent(arguments);
			this.injectComponents(); // There's no guarantee this will succeed so we still need polling as well
			return ret;
		},
		renderComponent: function(cmp, placeholderEl){
			if(cmp && placeholderEl){
				var parent = placeholderEl.parent();

				// Move a component that has been rendered previously
				if(cmp.rendered) cmp.getEl().replace(placeholderEl);
				else cmp.render(parent, placeholderEl);

				// Some components (mostly form fields) reserve space but fail to show up without a repaint in IE6
				if(Ext.isIE6)	parent.repaint();
				return true;
			}
			else return false;
		},
		reset: function() {
			var me = this;
			me.ids = [];// The ids of injected components that haven't yet been rendered
			if(me.pollId){
				clearTimeout(me.pollId);
				me.pollId = null;
			}
		}
	}, function(ctemplate) {
		var apply = function(){
			var me = this,
				args = Ext.Array.slice(arguments);
			args[0] = me.copyValues(args[0]);
			me.doPolling(POLL_INTERVAL_MS); // As we're returning an HTML string/array we can't actually complete the injection here
			return me.callParent(args);
		};
		// The main override is different depending on whether we're using ExtJS 4.0 or 4.1+
		if(ctemplate.prototype.applyOut) ctemplate.override({ applyOut: apply });// 4.1+
		else {
			ctemplate.override({ applyTemplate: apply }); // 4.0
			ctemplate.createAlias('apply', 'applyTemplate');
		}
	});

	/*!
	 * Component Column
	 * Version 1.1
	 * Copyright(c) 2011-2013 Skirtle's Den
	 * License: http://skirtlesden.com/ux/component-column
	 */
	Ext.define('Skirtle.grid.column.Component', {
		alias: 'widget.componentcolumn',
		extend: 'Ext.grid.column.Column',
		requires: ['Skirtle.CTemplate'],

		autoWidthComponents: true, // Whether or not to automatically resize the components when the column resizes
		componentGC: true, // Whether or not to destroy components when they are removed from the DOM
		hasCustomRenderer: true, // Override the superclass - this must always be true or odd things happen, especially in IE
		lastFrameWidth: 12, // The estimated size of the cell frame. This is updated once there is a cell where it can be measured

		/* Defer durations for updating the component width when a column resizes. Required when a component has an animated
		 * resize that causes the scrollbar to appear/disappear. Otherwise the animated component can end up the wrong size.
		 *
		 * For ExtJS 4.0 both delays are required. For 4.1 just having the 10ms delay seems to be sufficient.
		 */
		widthUpdateDelay: [10, 400],

		constructor: function(cfg) {
			var me = this;

			me.callParent(arguments);

			// Array of component ids for both component queries and GC
			me.compIds = [];

			// We need a dataIndex, even if it doesn't correspond to a real field
			me.dataIndex = me.dataIndex || Ext.id(null, 'cc-dataIndex-');
			me.tpl = me.createTemplate(me.tpl);
			me.renderer = me.createRenderer(me.renderer);
			me.registerColumnListeners();
		},
		addRefOwner: function(child) {
			var me = this,
				fn = me.refOwnerFn || (me.refOwnerFn = function() { return me; });
			if(me.extVersion < 40200) child.getBubbleTarget = fn; // Component queries for ancestors use getBubbleTarget in 4.1 ...
			else child.getRefOwner = fn; // ... and getRefOwner in 4.2+
		},
		applyTemplate: function(data, value) {
			if(Ext.isDefined(value)) data[this.dataIndex] = value;
			return this.tpl.apply(data);
		},
		beforeViewRefresh: function() {
			/* In IE setting the innerHTML will destroy the nodes for the previous content. If we try to reuse components it
			 * will fail as their DOM nodes will have been torn apart. To defend against this we must remove the components
			 * from the DOM just before the grid view is refreshed.
			 */
			if (Ext.isIE) {
				var ids = this.compIds,
					index = 0,
					len = ids.length,
					item,
					el,
					parentEl;

				for ( ; index < len ; index++) {
					if ((item = Ext.getCmp(ids[index])) && (el = item.getEl()) && (el = el.dom) && (parentEl = el.parentNode)) {
						parentEl.removeChild(el);
					}
				}
			}
		},
		calculateFrameWidth: function(component) {
			var el = component.getEl(),
				parentDiv = el && el.parent(),
				// By default the TD has no padding but it is quite common to add some via a tdCls
				parentTd = parentDiv && parentDiv.parent();

			if(parentTd){
				// Cache the frame width so that it can be used as a 'best guess' in cases where we don't have the elements
				this.lastFrameWidth = parentDiv.getFrameWidth('lr') + parentTd.getFrameWidth('lr');
				return this.lastFrameWidth;
			}
		},
		createRenderer: function(renderer) {
			var me = this;
			return function(value, p, record) {
				var data = Ext.apply({}, record.data, record.getAssociatedData());
				if(renderer) value = renderer.apply(this, arguments); // Scope must be this, not me
				// Process the value even with no renderer defined as the record may contain a component config
				value = me.processValue(value);
				return me.applyTemplate(data, value);
			};
		},
		createTemplate: function(tpl) {
			return tpl && tpl.isTemplate ? tpl : Ext.create('Skirtle.CTemplate', tpl || ['{', this.dataIndex ,'}']);
		},
		destroyChild: function(child) { child.destroy(); },
		getRefItems: function(deep) {
			var items = this.callParent([deep]),
					ids = this.compIds,
					index = 0,
					len = ids.length,
					item;

			for( ; index < len ; index++){
				item = Ext.getCmp(ids[index]);
				if(item){
					items.push(item);
					if(deep && item.getRefItems) items.push.apply(items, item.getRefItems(true));
				}
			}
			return items;
		},
		onChildAfterRender: function(child){ this.resizeChild(child); },
		onChildBoxReady: function(child){
			// Pass false to avoid triggering deferred resize, the afterrender listener will already cover those cases
			this.resizeChild(child, false);
		},
		onChildDestroy: function(child){ Ext.Array.remove(this.compIds, child.getId()); },
		onChildResize: function(){ this.redoScrollbars(); },
		onColumnResize: function(column){ column.resizeAll(); },
		onColumnShow: function(column){ column.resizeAll(); },
		onColumnVisibilityChange: function(column) {
			// This is called in IE 6/7 as the components can still be seen even when a column is hidden
			var items = column.getRefItems(),
				index = 0,
				length = items.length,
				visible = !column.isHidden();

			// In practice this probably won't help but it shouldn't hurt either
			if(Ext.suspendLayouts) Ext.suspendLayouts();

			for( ; index < length ; ++index){
				items[index].setVisible(visible);
			}
			if(Ext.resumeLayouts) Ext.resumeLayouts(true);
		},
		onDestroy: function() {
				Ext.destroy(this.getRefItems());

				this.callParent();
		},
		onRender: function(){ // Override
			this.registerViewListeners();
			this.callParent(arguments);
		},
		onViewChange: function() {
			// View has changed, may be a full refresh or just a single row
			var me = this,
				tpl = me.tpl;

			// Batch the resizing of child components until after they've all been injected
			me.suspendResizing();

			if (tpl.isCTemplate) {
				// No need to wait for the polling, the sooner we inject the less painful it is
				tpl.injectComponents();
				// If the template picked up other components in the data we can just ignore them, they're not for us
				tpl.reset();
			}
			// A view change could mean scrollbar problems. Note this won't actually do anything till we call resumeResizing
			me.redoScrollbars();
			me.resumeResizing();
			me.performGC();
		},
		performGC: function() {
			// Component GC, try to stop components leaking
			var compIds = this.compIds,
				index = compIds.length - 1,
				comp,
				el;

			for( ; index >= 0 ; --index){
				// Could just assume that the component id is the el id but that seems risky
				comp = Ext.getCmp(compIds[index]);
				el = comp && comp.getEl();

				if (!el || (this.componentGC && (!el.dom || Ext.getDom(Ext.id(el)) !== el.dom))) {
					// The component is no longer in the DOM
					if(comp && !comp.isDestroyed) comp.destroy();
				}
			}
		},
		processValue: function(value) {
			var me = this,
				compIds = me.compIds,
				id, initialWidth, dom, parent;

			if(Ext.isObject(value) && !value.isComponent && value.xtype) {
				// Do not default to a panel, not only would it be an odd default but it makes future enhancements trickier
				value = Ext.widget(value.xtype, value);
			}

			if(value && value.isComponent){
				id = value.getId();
				// When the view is refreshed the renderer could return a component that's already in the list
				if(!Ext.Array.contains(compIds, id)) compIds.push(id);
				me.addRefOwner(value);
				me.registerListeners(value);
				if(value.rendered){
					/* This is only necessary in IE because it is just another manifestation of the innerHTML problems.
					 * The problem occurs when a record value is changed and the components in that same row are being
					 * reused. The view doesn't go through a full refresh, instead it performs a quick update on just the
					 * one row. Unfortunately this nukes the existing components so we need to remove them first.
					 */
					if(Ext.isIE){
						// TODO: Should this be promoted to CTemplate?
						dom = value.el.dom;
						parent = dom.parentNode;

						if(parent){
							if(me.extVersion === 40101){
								// Workaround for the bugs in Element.syncContent - p tag matches CTemplate.cTpl
								Ext.core.DomHelper.insertBefore(dom, {tag: 'p'});
							}
							// TODO: Removing the element like this could fall foul of Element GC
							parent.removeChild(dom);
						}
					}
				}
				else if (me.autoWidthComponents) {
					/* Set the width to a 'best guess' before the component is rendered to ensure that the component's
					 * layout is using a configured width and not natural width. This avoids problems with 4.1.1 where
					 * subsequent calls to setWidth are ignored because it believes the width is already correct but only
					 * the outermost element is actually sized correctly. We could use an arbitrary width but instead we
					 * make a reasonable guess at what the actual width will be to try to avoid extra resizing.
					 */
					initialWidth = me.getWidth() - me.lastFrameWidth;

					// Impose a minimum width of 4, we really don't want negatives values or NaN slipping through
					initialWidth = initialWidth > 4 ? initialWidth : 4;

					value.setWidth(initialWidth);
				}
				// Part of the same IE 6/7 hack as onColumnVisibilityChange
				if((Ext.isIE6 || Ext.isIE7) && me.isHidden()) value.hide();
			}

			return value;
		},
		redoScrollbars: function() {
			var me = this,
				grid = me.up('tablepanel');

			if(grid){
				// The presence of a resizeQueue signifies that we are currently suspended
				if(me.resizeQueue){
					me.redoScrollbarsRequired = true;
					return;
				}

				// After components are injected the need for a grid scrollbar may need redetermining
				if(me.extVersion < 40100){ // 4.0
					grid.invalidateScroller();
					grid.determineScrollbars();
				}
				else grid.doLayout(); // 4.1+
			}
		},
		registerColumnListeners: function() {
			var me = this;

			if(me.autoWidthComponents){
				// Need to resize children when the column resizes
				me.on('resize', me.onColumnResize);
				// Need to resize children when the column is shown as they can't be resized correctly while it is hidden
				me.on('show', me.onColumnShow);
			}
			if(Ext.isIE6 || Ext.isIE7){
				me.on({
					hide: me.onColumnVisibilityChange,
					show: me.onColumnVisibilityChange
				});
			}
		},
		registerListeners: function(component) {
			var me = this;

			// Remove the component from the child list when it is destroyed
			component.on('destroy', me.onChildDestroy, me);
			if(me.autoWidthComponents){
				// Need to resize children after render as some components (e.g. comboboxes) get it wrong otherwise
				component.on('afterrender', me.onChildAfterRender, me, {single: true});

				// With 4.1 boxready gives more reliable results than afterrender as it occurs after the initial sizing
				if(me.extVersion >= 40100) component.on('boxready', me.onChildBoxReady, me, {single: true});
			}

			// Need to redo scrollbars when a child resizes
			component.on('resize', me.onChildResize, me);
		},
		registerViewListeners: function() {
				var me = this,
					view = me.up('tablepanel').getView();
				me.mon(view, 'beforerefresh', me.beforeViewRefresh, me);
				me.mon(view, 'refresh', me.onViewChange, me);
				me.mon(view, 'itemupdate', me.onViewChange, me);
				me.mon(view, 'itemadd', me.onViewChange, me);
				me.mon(view, 'itemremove', me.onViewChange, me);
		},
		resizeAll: function() {
				var me = this;
				me.suspendResizing();
				me.resizeQueue = me.getRefItems();
				me.resumeResizing();
		},
		resizeChild: function(component, defer) {
			var me = this,
				frameWidth,
				newWidth,
				oldWidth,
				resizeQueue;

			if(me.resizingSuspended){
				resizeQueue = me.resizeQueue;
				if(!Ext.Array.contains(resizeQueue, component)) resizeQueue.push(component);
				return;
			}
			frameWidth = me.calculateFrameWidth(component);

			// TODO: Should we destroy the component here if it doesn't have a parent element? Already picked up anyway?
			if(Ext.isNumber(frameWidth)){
				newWidth = me.getWidth() - frameWidth;
				oldWidth = component.getWidth();

				// Returns true if a resize actually happened
				if(me.setChildWidth(component, newWidth, oldWidth)){
					// Avoid an infinite resizing loop, deferring will only happen once
					if(defer !== false){
						// Do the sizing again after a delay. This is because child panel collapse animations undo our sizing
						Ext.each(me.widthUpdateDelay, function(delay){
							Ext.defer(me.resizeChild, delay, me, [component, false]);
						});
					}
				}
			}
		},
		resumeResizing: function() {
			var me = this,
				index = 0,
				resizeQueue = me.resizeQueue,
				len = resizeQueue.length;

			if(!--me.resizingSuspended) {
				for ( ; index < len ; ++index){
					me.resizeChild(resizeQueue[index]);
				}
				me.resizeQueue = null;
				if (me.redoScrollbarsRequired) me.redoScrollbars();
			}
		},
		setChildWidth: function(component, newWidth, oldWidth) {
			if(oldWidth === newWidth) return false;
			component.setWidth(newWidth);
			return true;
		},
		suspendResizing: function() {
			var me = this;
			me.resizingSuspended = (me.resizingSuspended || 0) + 1;
			if(!me.resizeQueue) me.resizeQueue = [];
		}
	},
	function(cls){
		var proto = cls.prototype,
			version = Ext.getVersion();
		proto.extVersion = (version.getMajor() * 100 + version.getMinor()) * 100 + version.getPatch(); // ExtJS version detection
		// 4.1.1 initially reported its version as 4.1.0
		if(Ext.Element.prototype.syncContent && version.toString() === '4.1.0') proto.extVersion = 40101;
	});
}());

                /**
	SUMMARY:
		This combo has some built in defaults for static-comboboxes (which means you dont type into it, you just select from it)
	*/
(function(){
	var Ext = window.Ext4 || window.Ext;

	Ext.define('Intel.lib.component.FixedComboBox', {
		extend:'Ext.form.field.ComboBox',
		alias: ['widget.intelfixedcombo', 'widget.intelfixedcombobox'],

		editable: false,
		allowBlank:true,
		queryMode:'local',
		matchFieldWidth: false,
		listeners: {
			change:function(combo, newval, oldval){
				if(newval.length===0) combo.setValue(oldval || '');
			},
			focus: function(combo) {
				if(combo.store.findExact(combo.valueField, combo.getValue()) === -1) combo.setValue('');
				combo.expand();
			}
		}
	});
}());
                /**
	SUMMARY:
		This component is an easy release picker based off Intel.lib.component.FixedComboBox. You supply the
		Release Records to it on construction.

		YOU MUST PASS IT 2 THINGS IN THE CONFIG
			1: releases (array of release records)
			2: currentRelease (what to show as initial value
	*/
(function(){
	var Ext = window.Ext4 || window.Ext;

	Ext.define('Intel.lib.component.ReleasePicker', {
		extend: 'Intel.lib.component.FixedComboBox',
		alias: ['widget.intelreleasepicker'],

		constructor: function(options){
			if(!options.releases || !options.currentRelease) return;

			options.displayField = 'Name';
			options.value = options.currentRelease.data.Name;
			options.store = Ext.create('Ext.data.Store', {
				fields: ['Name'],
				data: _.map(options.releases, function(r){ return {Name: r.data.Name }; })
			});

			options.fieldLabel = options.fieldLabel || 'Release:';
			options.editable = options.editable || false;
			options.width = options.width || 240;
			options.labelWidth = options.labelWidth || 50;

			this.callParent([options]); //now that we have the extra stuff added
		}
	});
}());

                /**
	SUMMARY:
		This override makes the ComponentColumn component much more performant. Edits found using Chrome profiling
*/
(function(){
	var Ext = window.Ext4 || window.Ext;

	Ext.define('Intel.lib.component.ComponentColumn', {
		extend: 'Skirtle.grid.column.Component',
		alias: 'widget.intelcomponentcolumn',

		autoWidthComponents: false,
		componentGC: true,
		hasCustomRenderer: true,
		lastFrameWidth: 12,
		constructor: function(cfg) {
			var me = this;
			me.callParent(arguments);
		},
		registerViewListeners: function() {
			var me = this,
				view = me.up('tablepanel').getView();

			me.mon(view, 'beforerefresh', me.beforeViewRefresh, me);
			me.mon(view, 'refresh', me.onViewChange, me);
			//me.mon(view, 'itemupdate', me.onViewChange, me); //why are these necessary...
			//me.mon(view, 'itemadd', me.onViewChange, me);
			//me.mon(view, 'itemremove', me.onViewChange, me);
		},
		onViewChange: function() {
			var me = this, tpl = me.tpl;
			me.suspendResizing();
			if (tpl.isCTemplate) {
					tpl.injectComponents();
					tpl.reset();
			}
			//me.redoScrollbars();
			me.resumeResizing();
			me.performGC();
		},
		resumeResizing: function(){
			var me = this,
				index = 0,
				resizeQueue = me.resizeQueue,
				len = resizeQueue.length;
			if (!--me.resizingSuspended) {
				for ( ; index < len ; ++index) me.resizeChild(resizeQueue[index]);
				me.resizeQueue = null;
				/* if (me.redoScrollbarsRequired) {
						me.redoScrollbars();
				} */
			}
		},
		onChildResize: function() {
			//this.redoScrollbars();
		}
	});
}());
                /**
	SUMMARY:
		Fast CFD Calculator is a lot faster than the rally build in calculators because this is not a generic
		calculator. it specifically is used to aggregate items with PlanEstimate and ScheduleState fields in an area chart
		between two dates. Example app using this is Train CFD Charts.

	NOTE: you MUST give this calculator startDate, endDate, and ScheduleState in the config. ONLY.

	NOTE: if new Date() is between the start and end date, it will substitute new Date() for what would've been
		'todays' date in the dateArray. Example: startDate:2000/10/8, endDate:2000/12/8, now:2000/12/12 (3:13 pm),
		so in teh dateArray, what wouldve been 2000/12/12 will now become 2000/12/12 (3:13 pm). This makes the CFD data
		more up to date (granularity is not on average 12 hours, now it is whatever the ELTDate is).

	NOTE: _ValidFrom is inclusive, _ValidTo is exclusive!
*/
(function(){
	var Ext = window.Ext4 || window.Ext;

	Ext.define('Intel.lib.chart.FastCumulativeFlowCalculator', {
		constructor:function(options){
			this.scheduleStates = options.scheduleStates;
			this.startDate = options.startDate;
			this.endDate = options.endDate;
		},

		_getDates:function(){
			var dates = [], curDay = this.startDate, day=1000*60*60*24, n;
			while(curDay<this.endDate){
				n = curDay.getDay();
				//if(n!==0 && n!==6){ //dont get weekends. NOTE: now we get weekends
					if(this._dateToStringDisplay(curDay) === this._dateToStringDisplay(new Date())) dates.push(new Date());
					else dates.push(curDay);
				//	}
				curDay = new Date(curDay*1 + day);
			}
			return dates;
		},

		_dateToStringDisplay: function (date) {
			return Ext.Date.format(date, 'm/d/Y');
		},

		/** binsearches for the closest date to 'date' */
		_getIndexHelper:function(date, dateArray){
			var curVal = (dateArray.length/2), curInt = (curVal>>0), div=(curVal/2), lastInt=-1;
			while(curInt !== lastInt){
				if(dateArray[curInt]===date) return curInt;
				else if(dateArray[curInt]>date) curVal-=div;
				else curVal+=div;
				div/=2;
				lastInt = curInt;
				curInt = curVal>>0;
			}
			return curInt;
		},

		/** returns index in dateArray of the date before the input date */
		_getIndexBefore: function(date, dateArray){
			if(dateArray.length===0) return -1;
			var pos = this._getIndexHelper(date, dateArray);
			if(dateArray[pos] < date) return pos;
			else return pos-1;
		},

		/** returns index in dateArra of date after or on the input date */
		_getIndexOnOrAfter: function(date, dateArray){
			if(dateArray.length===0) return -1;
			var pos = this._getIndexHelper(date, dateArray);
			if(pos===dateArray.length-1) { if(dateArray[pos] >= date) return pos; else return -1; } //either start of list or everything is after 'date'
			else if(dateArray[pos] >= date) return pos;
			else return pos+1;
		},

		/** items is an array of snapshot records*/
		runCalculation:function(items){
			if(!this.scheduleStates || !this.startDate || !this.endDate) throw 'invalid constructor config';
			var dates = this._getDates(), day=1000*60*60*24,
				dateMapTemplate = _.map(new Array(dates.length), function(){ return 0;});
			var totals = _.reduce(this.scheduleStates, function(map, ss){
				map[ss] = dateMapTemplate.slice();
				return map;
			}, {});
			for(var itemIndex=0, len=items.length; itemIndex<len; ++itemIndex){
				var item = items[itemIndex].raw, //dont work with records;
					iStart = new Date(item._ValidFrom),
					iEnd = new Date(item._ValidTo),
					state = item.ScheduleState,
					pe = item.PlanEstimate;
				if(!pe || !state) continue; //no need to continue with this one
				var startIndex = this._getIndexOnOrAfter(iStart, dates),
					endIndex = this._getIndexBefore(iEnd, dates);
				if(startIndex===-1 || endIndex===-1) continue; //no need to continue here
				for(var i=startIndex;i<=endIndex;++i)
					totals[state][i]+=pe;
			}
			return {
				categories:_.map(dates, function(d){ return this._dateToStringDisplay(d); }, this),
				series: _.reduce(this.scheduleStates, function(outputArray, ss){
					return outputArray.concat([{name:ss, type:'area', dashStyle:'Solid', data:totals[ss]}]);
				}, [])
			};
		}
	});
}());
                (function(){
	var Ext = window.Ext4 || window.Ext;
	Ext.define('Intel.TrainPerformanceDashboard', {
		extend: 'Intel.lib.IntelRallyApp',
		componentCls: 'app',
		requires: [
			'Intel.lib.chart.FastCumulativeFlowCalculator'
		],
		mixins: [
			'Intel.lib.mixin.PrettyAlert',
			'Intel.lib.mixin.UserAppsPreference',
			'Intel.lib.mixin.IntelWorkweek',
			'Intel.lib.mixin.CumulativeFlowChartMixin',
			'Intel.lib.mixin.ParallelLoader',
			'Intel.lib.mixin.CustomAppObjectIDRegister'
		],
		items:[{
			xtype: 'container',
			id: 'retroWrapper',
			cls: 'chart-wrapper',
			items:[{
				xtype: 'container',
				id: 'datePickerWrapper',
				layout: {
					type: 'hbox'
				},
				items:[{
					xtype:'container',
					id: 'retroReleasePicker',
					width:'240px'
				},{
					xtype:'component',
					id:'cntClickForDateChange',
					cls:'clickForDateChange',
					width:'390px',
					autoEl: {
						tag: 'a',
						html: 'Please Click here to change the Release Start Date'
					},
					listeners   : {
						el : {
							click: {
								element: 'el', //bind to the underlying el property on the panel
								fn: function(){
									Rally.getApp()._buildReleasePickerStartDate();
								}
							}
						}
					}
				},{
					xtype:'container',
					id:'cntDatePickerWrapper',
					hidden: true,
					width: '390px',
					layout: {
						type: 'hbox',
						align:'left'
					}
				}]
			},{
				xtype:'container',
				id:'cntInformation',
				items:[{
					xtype:'component',
					cls:'help',
					autoEl: {
						tag: 'a',
						html: '<span>Help <img src="https://rally1.rallydev.com/slm/images/icon_help.gif" alt="Help" title="Help" ></span>'
					},
					listeners   : {
						el : {
							click: {
								element: 'el', //bind to the underlying el property on the panel
								fn: function(){
									var html = ['<ul class ="ulInformation"><li><b>Scope Delta</b> = (Final Workload - Original Commit) / Original Commit</li>',
										'<li><b>A/C Original</b> = Final Accepted / Original Commit</li>',
										'<li><b>A/C Final</b> = Final Accepted / Final Workload</li>',
										'<li>Sample dates are taken on <b>7th day</b> of the Release Start Date and on the Release End Date for Scope Delta , ',
											'and 10 days after Release Start Date for ' + Rally.getApp().PortfolioItemTypes[0] + ' Scope Change.</li>',
										'<li>If there are 0 points at Release End Date, then the ideal and projected data are taken for the sample.</li>',
										'<li>You can change the Release Start Date for the selected Release. This will update the sample date for the Release Start Date.</li>',
										'<li>Once Release Start Date for a selected Release is changed, it will be saved and reloaded with the saved Release Start Date in future.</li>',
										'<li><b>Final Accepted</b> is the total points for user stories that are accepted at the Release End Date',
										'<li><b>Final Workload</b> is the total points for user stories at the Release End Date',
										'<li><b>Initial Commit</b> is the total points for user stories at the Release Start Date</ul>'
										].join('\n');
									Rally.getApp().alert('Information on Data Calculations',html);
								}
							}
						}
					}
				}]

				},{
				xtype: 'container',//container divided later into three sub containers
				id: 'retroBarChartWrapper',
				cls: 'barchart-wrapper',
				layout: {
					type: 'hbox',
					align:'left'
				},
				renderTo: document.body,
				items:[{
					xtype:'container',
					id: 'retroChart',
					height: 390,
					width: '44%'
				},{
					xtype:'container',//Scope container Wrapper
					id : 'retroBarChartScopeWrapper',
					height: 350,
					width: '18%'
				},{
					xtype:'container',// CA original wrapper
					id: 'retroBarChartCaOriginalWrapper',
					height: 350,
					width: '18%'
				},{
					xtype:'container',
					id: 'retroBarChartCaFinalWrapper',
					height: 350,
					width: '18%'//CA final container
				}]
			},{
				xtype:'container',//legend
				id:'legend',
				html:[
					'<div class="legendwrapper">',
						'<div class="dtarget"></div>',
						'<div class="dtargetwrapper">Did Not Meet Target</div>',
						'<div class="mtarget"></div>',
						'<div class="mtargetwrapper">Met Target</div>',
						'<div class="atarget"></div>',
						'<div class="mtargetwrapper">A/C = Accept To Commit</div>',
					'</div>'].join('\n')
			},{
				xtype:'tabpanel',
				id: 'scopeGridWrapper',
				listeners: {
					boxready: function(){
						$('.x-tab-bar .x-tab-inner').css({'width':'130px','font-size':'12px'});
						$('.x-tab-bar .x-tab-default').removeClass("x-tab-default");
						$('.x-tab-bar').addClass("x-tab-default-override");
					}
				},
				items:[{
					xtype:'container',
					title: "Progress",
					id: 'scopeGrid'
				},{
					xtype:'container',
					title:"Art Scrum Fitness",
					items:[{
						xtype:'container',
						id: 'grdScrumHealthHeader'
					},{
						xtype:'container',
						id: 'grdScrumHealth',
						cls:'grd-ScrumHealth'
					}]

				}]
			}]
		}],
		projectFields: ['ObjectID', 'Releases', 'Children', 'Parent', 'Name', 'Project'], //override intel-rally-app
		portfolioItemFields: ['Name','ObjectID','FormattedID','Release','PlannedEndDate'], //override intel-rally-app
		releaseFields:  ['Name', 'ObjectID', 'ReleaseDate', 'ReleaseStartDate', 'Project'], //override intel-rally-app
		userAppsPref: 'intel-retro-dashboard',	//dont share release scope settings with other apps

		/****************************************************** RELEASE PICKER ********************************************************/
		_releasePickerSelected: function(combo, records){
			var me=this;
			Ext.getCmp('cntDatePickerWrapper').removeAll();
			Ext.getCmp('cntClickForDateChange').show();
			Ext.getCmp('cntDatePickerWrapper').hide();
			me.releaseStartDateChanged = false;
			if(me.ReleaseRecord.data.Name === records[0].data.Name) return;
			me.setLoading(true);
			me.ReleaseRecord = _.find(me.ReleaseRecords, function(rr){ return rr.data.Name == records[0].data.Name; });
			me._reloadEverything()
				.fail(function(reason){ me.alert('ERROR', reason); })
				.then(function(){ me.setLoading(false); })
				.done();
		},
		_buildReleasePicker: function(){
			var me = this;
			me.ReleasePicker = Ext.getCmp('retroReleasePicker').add({
				xtype: 'intelreleasepicker',//this is a intel component in intel-release-picker.js
				labelWidth: 40,
				releases: me.ReleaseRecords,
				currentRelease: me.ReleaseRecord,
				listeners: {
					change: function(combo, newval, oldval){if(newval.length===0) combo.setValue(oldval); },
					select: me._releasePickerSelected,
					scope: me
				}
			});
		},
		_buildReleasePickerStartDate: function(){
			Ext.getCmp('cntDatePickerWrapper').removeAll();
			Ext.getCmp('cntClickForDateChange').hide();
			Ext.getCmp('cntDatePickerWrapper').show();
			var me = this;
			//var	_6days = 1000 * 60 *60 *24*6;
			var datePickerDefaultDate;
			var rid = me.ReleaseRecord.data.ObjectID;
			var pid = me.ProjectRecord.data.ObjectID;
			var releaseSampleDataDate =  new Date(new Date(me.ReleaseRecord.data.ReleaseStartDate)*1 /* + _6days */);
			if(typeof me.AppsPref.projs[pid] !== 'object') me.AppsPref.projs[pid] = {};
			datePickerDefaultDate = !!(me.AppsPref.projs[pid][rid]) ? new Date(me.AppsPref.projs[pid][rid].ReleaseStartDate):releaseSampleDataDate ;
			var maxDate = me.ReleaseRecord.data.ReleaseDate > new Date() ? new Date() : me.ReleaseRecord.data.ReleaseDate;
			var items = [{
				xtype: 'rallydatefield',
				id:'ReleaseDatePicker',
				fieldLabel: 'Select Release Start Date',
				labelWidth:140,
				minValue: releaseSampleDataDate,
				maxValue: maxDate,
				value: datePickerDefaultDate,
				showToday:false
			},{
				xtype:'button',
				text: 'Update',
				id: "btnUpdateReleaseDate",
				scope: me,
				handler: function() {
					var txtValue = new Date(Ext.getCmp('ReleaseDatePicker').value).toLocaleDateString();
					if(Date.parse(txtValue) >= Date.parse(new Date(new Date(me.ReleaseRecord.data.ReleaseStartDate)*1 /* + _6days */)) && Date.parse(txtValue) <= Date.parse(maxDate)){
						//saving the release start date
						me.setLoading("Loading");
						return me.loadReleaseByNameForProject(me.ReleaseRecord.data.Name,me.ProjectRecord)
						.then(function(releaseRecordstore){
							//reseting it again as couldnt figure out why the time gets reset when date is picked from the calendar for past releases
							//TODO: find out why release record time get reset
							me.ReleaseRecord = releaseRecordstore;
							me.AppsPref.projs[pid] = me.AppsPref.projs[pid] || {};
							me.AppsPref.projs[pid][rid] =  me.AppsPref.projs[pid][rid] || {};
							me.AppsPref.projs[pid][rid].ReleaseStartDate = txtValue;
							return me.saveAppsPreference(me.AppsPref)
								.then(function(){
									me.releaseStartDateChanged = true;
									me.datePickerDate = txtValue;
									var date1 = me.ReleaseRecord.data.ReleaseStartDate,
										date2 = new Date(me.datePickerDate),
										_1day = 1000 * 60 * 60 * 24 ;
									var daysCountDifference = Math.floor(( Date.parse(date2) - Date.parse(date1) ) / _1day );
									//taking sample 7 days before and after the release
									//data for calculating scope change
									//commit to accept original and final calculation
									me.initialAddedDaysCount = me.releaseStartDateChanged && daysCountDifference >= 0 ? daysCountDifference : 6;
									me._buildCumulativeFlowChart();
									me._buildRetroChart();
									me._hideHighchartsLinks();
									Ext.getCmp('grdScrumHealthHeader').update(" ");
									Ext.getCmp('scopeGrid').removeAll();
									Ext.getCmp('grdScrumHealth').removeAll();
									me._buildScopeToReleaseStore();
									me._buildPortfolioProgressGrid();
									me._buildArtScrumFitnessGridStore();
									me._renderReleaseDetailHeader();
									me._buildFitnessGrid();
									me.setLoading(false);
								});
							})
						.fail(function(reason){ me.alert('ERROR', reason); })
						.done();

					}else{
						me.alert(
							"Date Validation Note:",
							"The entered date should be between Release start date(" + releaseSampleDataDate.toLocaleDateString() +") and Release end date("+ maxDate.toLocaleDateString() +")."
						);
					}
				}
			}];
			Ext.getCmp('cntDatePickerWrapper').add(items);
		},
		/****************************************************** DATA STORE METHODS ********************************************************/
		_getTeamTypeAndNumber: function(scrumName){ //NOTE this assumes that your teamNames are "<TeamType> <Number> - <TrainName>"
			var name = scrumName.split('-')[0],
				teamType = name.split(/\d/)[0],
				number = (teamType === name ? 1 : name.split(teamType)[1])*1;
			return {
				TeamType: teamType.trim(),
				Number: number
			};
		},
		_loadAllChildReleases: function(){
			var me = this, releaseName = me.ReleaseRecord.data.Name;
			me.ReleasesWithNameHash ={};
			return me.loadReleasesByNameUnderProject(releaseName, me.ScrumGroupRootRecord)
				.then(function(releaseRecords){
					me.ReleasesWithNameHash = _.reduce(releaseRecords, function(hash, rr){
						hash[rr.data.ObjectID] = true;
						return hash;
					}, {});
				});
		},
		_getPortfolioItems: function(){
			var me=this, releaseName = me.ReleaseRecord.data.Name;
			me.LowestPortfolioItemsHash = {};
			me.PortfolioItemsInReleaseStore = null;
			//NOTE: we are loading ALL lowestPortfolioItems b/c sometimes we run into issues where
			//userstories in one release are under portfolioItems in another release (probably a user
			// mistake). And this messes up the numbers in the topPortfolioItem filter box
			return me.loadPortfolioItemsOfType(me.ScrumGroupPortfolioProject, me.PortfolioItemTypes[0])
				.then(function(portfolioItemStore){
					var portfolioItemsInRelease = _.filter(portfolioItemStore.getRange(), function(pi){ return (pi.data.Release || {}).Name == releaseName; });
					me.PortfolioItemsInReleaseStore = Ext.create('Rally.data.wsapi.Store', {
						model: me['PortfolioItem/' + me.PortfolioItemTypes[0]],
						data: portfolioItemsInRelease,
						totalCount: portfolioItemsInRelease.length,
						disableMetaChangeEvent: true,
						load: function(){}
					});

					me.LowestPortfolioItemsHash = _.reduce(portfolioItemStore.getRange(), function(hash, r){
						hash[r.data.ObjectID] = (r.data.Release || {}).Name || 'No Release';
						return hash;
					}, {});
				});
		},
		_loadSnapshotStores: function(){
			var me = this,
				releaseStart = new Date(me.ReleaseRecord.data.ReleaseStartDate).toISOString(),
				releaseEnd = new Date(me.ReleaseRecord.data.ReleaseDate).toISOString(),
				releaseName = me.ReleaseRecord.data.Name,
				lowestPortfolioItemType = me.PortfolioItemTypes[0];
			me.AllSnapshots = [];
			me.TeamStores = {};
			var projectId = "";
			return Q.all(_.map(me.CurrentScrum ? [me.CurrentScrum] : me.LeafProjects, function(project){
				var parallelLoaderConfig = {
					context:{
						workspace: me.getContext().getGlobalContext().getWorkspace()._ref,
						project: project.data._ref
					},
					compress:true,
					findConfig: {
						_TypeHierarchy: 'HierarchicalRequirement',
						Project: project.data.ObjectID,
						_ValidFrom: { $lte: releaseEnd },
						_ValidTo: { $gt: releaseStart },
						Children: null
					},
					fetch: ['ScheduleState', 'PlanEstimate', 'Release', lowestPortfolioItemType, '_ValidFrom', '_ValidTo', 'ObjectID','Name'],
					hydrate: ['ScheduleState']
				};
				return me.parallelLoadLookbackStore(parallelLoaderConfig).then(function(snapshotStore){
					//only keep snapshots where (release.name == releaseName || (!release && portfolioItem.Release.Name == releaseName))
					var records = _.filter(snapshotStore.getRange(), function(snapshot){
						projectId = snapshot.data.Project;
						//console.log( projectId, snapshot.data[lowestPortfolioItemType], me.LowestPortfolioItemsHash[snapshot.data[lowestPortfolioItemType]], releaseName)
						return (me.ReleasesWithNameHash[snapshot.data.Release] ||
								(!snapshot.data.Release  && me.LowestPortfolioItemsHash[snapshot.data[lowestPortfolioItemType]] == releaseName)) &&
							(snapshot.data._ValidFrom != snapshot.data._ValidTo);
					});
					if(!me.TeamStores[projectId]) me.TeamStores[projectId] = [];
					me.TeamStores[projectId] = me.TeamStores[projectId].concat(records);
					me.AllSnapshots = me.AllSnapshots.concat(records);
				});
			}));
		},
		_getUserStoryFilter: function(){
			var me = this,
				releaseName = me.ReleaseRecord.data.Name,
				releaseDate = new Date(me.ReleaseRecord.data.ReleaseDate).toISOString(),
				releaseStartDate = new Date(me.ReleaseRecord.data.ReleaseStartDate).toISOString(),
				releaseNameFilter = Ext.create('Rally.data.wsapi.Filter', { property: 'Release.Name', value: releaseName }),// this will ONLY get leaf-stories (good)
				inIterationButNotReleaseFilter =
					Ext.create('Rally.data.wsapi.Filter', { property: 'Iteration.StartDate', operator:'<', value:releaseDate}).and(
					Ext.create('Rally.data.wsapi.Filter', { property: 'Iteration.EndDate', operator:'>', value:releaseStartDate})).and(
					Ext.create('Rally.data.wsapi.Filter', { property: 'Release.Name', operator: '!=', value: releaseName })).and(
          Ext.create('Rally.data.wsapi.Filter', { property: 'Iteration.Name', operator: 'contains', value: releaseName}).and(
					Ext.create('Rally.data.wsapi.Filter', { property: 'DirectChildrenCount', value: 0 }))),
				userStoryProjectFilter;

			if(!me.ScrumGroupRootRecord) //scoped outside scrum group
				userStoryProjectFilter = Ext.create('Rally.data.wsapi.Filter', {
					property: 'Project.ObjectID',
					value: me.CurrentScrum.data.ObjectID
				});
			else if(me.LeafProjects && Object.keys(me.LeafProjects).length) //load all US within scrum group
				userStoryProjectFilter = _.reduce(me.LeafProjects, function(filter, projectData, projectOID){
					var newFilter = Ext.create('Rally.data.wsapi.Filter', { property: 'Project.ObjectID', value: projectOID});
					if(filter) return filter.or(newFilter);
					else return newFilter;
				}, null);
			else throw "No scrums were found!";
			return Rally.data.wsapi.Filter.and([
				userStoryProjectFilter,
				Rally.data.wsapi.Filter.or([inIterationButNotReleaseFilter, releaseNameFilter])
			]);
		},
		_getStories: function(){
			var me=this,
				lowestPortfolioItem = me.PortfolioItemTypes[0],
				config = {
					model: 'HierarchicalRequirement',
					filters: [me._getUserStoryFilter()],
					fetch:['Name', 'ObjectID', 'Project', 'PlannedEndDate', 'ActualEndDate', 'StartDate', 'EndDate', 'Iteration',
							'Release', 'Description', 'Tasks', 'PlanEstimate', 'FormattedID', 'ScheduleState',
							'Blocked', 'BlockedReason', 'Blocker', 'CreationDate', lowestPortfolioItem],
					context:{
						workspace:me.getContext().getWorkspace()._ref,
						project: me.ProjectRecord.data._ref
					}
				};
			return me.parallelLoadWsapiStore(config).then(function(store){
				me.UserStoryStore = store;
				return store;
			});
		},
		_buildScopeToReleaseStore: function(){
			var me = this,
				userStorySnapshots = me.AllSnapshots,
				_10days = 1000 * 60 *60 *24*10,
				lowestPortfolioItemType = me.PortfolioItemTypes[0],
				date1 = me.ReleaseRecord.data.ReleaseStartDate,
				date2 = new Date(me.datePickerDate),
				daysCountDifference = Math.floor(( Date.parse(date2) - Date.parse(date1) )),
				startTargetDate = me.releaseStartDateChanged && daysCountDifference >0 ? new Date(new Date(me.ReleaseRecord.data.ReleaseStartDate)*1 + daysCountDifference): new Date(new Date(me.ReleaseRecord.data.ReleaseStartDate)*1 + _10days),
				finalTargetDate = new Date(new Date(me.ReleaseRecord.data.ReleaseDate)*1),
				scopeToReleaseGridRows = [],

				userStorySnapshotsInitialWithoutPortfolioItems = _.filter(userStorySnapshots, function (userStorySnapshot){
					return new Date(userStorySnapshot.data._ValidFrom) < startTargetDate &&
					new Date(userStorySnapshot.data._ValidTo) > startTargetDate &&
					!userStorySnapshot.data[lowestPortfolioItemType];
				}),
				userStorySnapshotsFinalWithoutPortfolioItems = _.filter(userStorySnapshots, function (userStorySnapshot){
					return new Date(userStorySnapshot.data._ValidFrom) < finalTargetDate &&
					new Date(userStorySnapshot.data._ValidTo) > finalTargetDate &&
					!userStorySnapshot.data[lowestPortfolioItemType];
				}),
				userStorySnapshotsInitialWithPortfolioItems = _.filter(userStorySnapshots, function (userStorySnapshot){
					return new Date(userStorySnapshot.data._ValidFrom) < startTargetDate &&
					new Date(userStorySnapshot.data._ValidTo) > startTargetDate &&
					!!userStorySnapshot.data[lowestPortfolioItemType];
				}),
				userStorySnapshotsFinalWithPortfolioItems = _.filter(userStorySnapshots, function (userStorySnapshot){
					return new Date(userStorySnapshot.data._ValidFrom) < finalTargetDate &&
					new Date(userStorySnapshot.data._ValidTo) > finalTargetDate &&
					!!userStorySnapshot.data[lowestPortfolioItemType];
				});
			me.WsapiUserStoryMap = {};
			var UserStoryStoreItems = !(me.UserStoryStore) ? {} : me.UserStoryStore.getRange();
			me.WsapiUserStoryMap = _.reduce(UserStoryStoreItems, function(hash, r, key){
				if(r.data[lowestPortfolioItemType] !== null){
					var portfolioItemID = r.data[lowestPortfolioItemType].ObjectID;
					hash[r.data[lowestPortfolioItemType].ObjectID] = _.filter(me.UserStoryStore.getRange(),function(f){
						if(f.data[lowestPortfolioItemType] !== null)
							return f.data[lowestPortfolioItemType].ObjectID === portfolioItemID;
					});
				}
				return hash;
			}, {});
			_.each(me.PortfolioItemsInReleaseStore.getRange(), function(portfolioItemRecord,key){
				var scopeToReleaseGridRow = {},
					releaseStartSnapshots =[],
					releaseFinalSnapshots =[],
					userStoriesForPortfolioItem = me.WsapiUserStoryMap[portfolioItemRecord.data.ObjectID],

					userStorySnapshotsInitialForPortfolioItem = _.filter(userStorySnapshotsInitialWithPortfolioItems, function (userStorySnapshot){
						return portfolioItemRecord.data.ObjectID === userStorySnapshot.data[lowestPortfolioItemType];
					}),
					userStorySnapshotsFinalForPortfolioItem = _.filter(userStorySnapshotsFinalWithPortfolioItems, function (userStorySnapshot){
						return portfolioItemRecord.data.ObjectID === userStorySnapshot.data[lowestPortfolioItemType];
					});

			releaseStartSnapshots = releaseStartSnapshots.concat(userStorySnapshotsInitialForPortfolioItem);
			releaseFinalSnapshots = releaseFinalSnapshots.concat(userStorySnapshotsFinalForPortfolioItem);

			_.each(userStoriesForPortfolioItem, function(wsapiUserStory){
				var userStorySnapshotsInitialWithoutPortfolioItem = _.filter(userStorySnapshotsInitialWithoutPortfolioItems, function (userStorySnapshot){
						return userStorySnapshot.data.ObjectID === wsapiUserStory.data.ObjectID;
					}),
					userStorySnapshotsFinalWithoutPortfolioItem = _.filter(userStorySnapshotsFinalWithoutPortfolioItems, function (userStorySnapshot){
						return userStorySnapshot.data.ObjectID === wsapiUserStory.data.ObjectID;
					});
				releaseStartSnapshots = releaseStartSnapshots.concat(userStorySnapshotsInitialWithoutPortfolioItem);
				releaseFinalSnapshots = releaseFinalSnapshots.concat(userStorySnapshotsFinalWithoutPortfolioItem);
			});

			if(releaseStartSnapshots.length > 0 || releaseFinalSnapshots.length > 0){
				var startDateAcceptedPoints = _.reduce(releaseStartSnapshots, function(sum, item){
						return sum + (item.data.ScheduleState =='Accepted' ? item.data.PlanEstimate*1 : 0);
					}, 0),
					startDateNotAcceptedPoints =_.reduce(releaseStartSnapshots, function(sum, item){
						return sum + (item.data.ScheduleState !='Accepted' ? item.data.PlanEstimate*1 : 0);
					}, 0),
					startDateTotalPoints = _.reduce(releaseStartSnapshots, function(sum, item){
						return sum + (item.data.PlanEstimate*1);
					}, 0),

					//to get the % complete at release end
					//EndtargetDate  = new Date('03/07/2015')
					//EndreleaseStartSnapshots = _.filter(snapshots, ss._validFrom < targetDate && ss._ValidTo > targetDate)
					//completedPOints = sum EndreleaseStartSnapshots by PlanEstimate if ScheduleState == 'Completed' || 'Accepted'
					//totalPoints = sum EndreleaseStartSnapshots by PlanEstimate
					finalDateAcceptedPoints = _.reduce(releaseFinalSnapshots, function(sum, item){
						return sum + (item.data.ScheduleState =='Accepted' ? item.data.PlanEstimate*1 : 0);
					}, 0),
					finalDateNotAcceptedPoints =_.reduce(releaseFinalSnapshots, function(sum, item){
						return sum + (item.data.ScheduleState !='Accepted' ? item.data.PlanEstimate*1 : 0);
					}, 0),
					finalDatetotalPoints = _.reduce(releaseFinalSnapshots, function(sum, item){
						return sum + (item.data.PlanEstimate*1);
					}, 0);

				scopeToReleaseGridRow.completedAtStart = startDateAcceptedPoints / startDateTotalPoints;
				scopeToReleaseGridRow.completedAtEnd = finalDateAcceptedPoints / finalDatetotalPoints;
				if(isFinite(scopeToReleaseGridRow.completedAtStart) === false) {
					scopeToReleaseGridRow.completedAtStart = 0;
				}
				if(isFinite(scopeToReleaseGridRow.completedAtEnd) === false) {
					scopeToReleaseGridRow.completedAtEnd = 0;
				}

				if(startDateTotalPoints === 0) scopeToReleaseGridRow.growth = finalDatetotalPoints;
				else scopeToReleaseGridRow.growth = (finalDatetotalPoints - startDateTotalPoints )/ startDateTotalPoints;

				scopeToReleaseGridRow.intent = startDateTotalPoints.toFixed(0);//startDateNotAcceptedPoints;//plan to do //inital planned
				scopeToReleaseGridRow.actual  = finalDatetotalPoints.toFixed(0); //finalDateNotAcceptedPoints;//acutal done //Points at end of release
				scopeToReleaseGridRow.FormattedID = portfolioItemRecord.data.FormattedID;
				scopeToReleaseGridRow.Name = portfolioItemRecord.data.Name;
				scopeToReleaseGridRow.ObjectID = portfolioItemRecord.data.ObjectID;
				scopeToReleaseGridRow.ProjectObjectID = portfolioItemRecord.data.Project.ObjectID;
				portfolioItemRecord.state = portfolioItemRecord.data.State;
				scopeToReleaseGridRows = scopeToReleaseGridRows.concat(scopeToReleaseGridRow);
			}
			});
			//the month starts from 0 so Jan is 0
			me.InitialTargetDate = [startTargetDate.getMonth() + 1 ,startTargetDate.getDate(),startTargetDate.getFullYear()].join('/');
			me.CompleteFinalTargetDate = [finalTargetDate.getMonth() + 1,finalTargetDate.getDate(),finalTargetDate.getFullYear()].join('/');
			me.gridstore = Ext.create('Ext.data.Store',{
				fields:['completedAtStart', 'completedAtEnd','growth','intent','actual','FormattedID','Name','ObjectID','ProjectObjectID','state'],
				data: scopeToReleaseGridRows
			});
		},
		_calculateDataIntegrity: function(projectStoreMap){
			var me = this;
			var releaseName = me.ReleaseRecord.data.Name;
			//blocked stories
			var blockedStories = 0, unsizedStoires= 0,  improperlySizedStoires= 0,  storyWithoutIteration= 0,
				storyWithoutRelease= 0,  unacceptedStoriesinPastIteration= 0,  storiesScheduleAftePortfolioItemEndDate= 0;

			blockedStories = _.size(_.filter(projectStoreMap, function(userStories){
				return userStories.data.Blocked === true;
				}));
			// unsized stories
			unsizedStoires = _.size(_.filter(projectStoreMap, function(userStories){
				return userStories.data.PlanEstimate === null;
				}));
			//improperly sized stories
			improperlySizedStoires = _.size(_.filter(projectStoreMap, function(userStories){
				if (userStories.data.PlanEstimate !== null &&
					userStories.data.PlanEstimate !== 0 &&
					userStories.data.PlanEstimate !== 1 &&
					userStories.data.PlanEstimate !== 2 &&
					userStories.data.PlanEstimate !== 4 &&
					userStories.data.PlanEstimate !== 8 &&
					userStories.data.PlanEstimate !== 16 )
          return userStories;
				}));
			//stories in release without iteration
			storyWithoutIteration = _.size(_.filter(projectStoreMap, function(userStories){
				return userStories.data.Iteration === null;
				}));
			//stories in iteration not attached to release
			storyinIterationNotAttachedToRelease = _.size(_.filter(projectStoreMap, function(userStories){
				if(!userStories.data.Iteration)	return false;
				return (userStories.data.Iteration.Name.indexOf(releaseName) > -1) &&(!userStories.data.Release || userStories.data.Release.Name.indexOf(releaseName) < 0);
			}));
			//unaccepted stories in past iteration
			var today = new Date(),
				lowestPortfolioItemType = me.PortfolioItemTypes[0];
			unacceptedStoriesinPastIteration = _.size(_.filter(projectStoreMap, function(userStories){
				if(userStories.data.Iteration!==null && userStories.data.Iteration.EndDate < today.toISOString() && userStories.data.ScheduleState!='Accepted')
					return userStories;
			}));
			// stories scheduled after lowestPortfolioItemType end date
			storiesScheduleAftePortfolioItemEndDate =  _.size(_.filter(projectStoreMap, function(userStories){
				if(userStories.data[lowestPortfolioItemType] !==null &&
					userStories.data.Iteration!==null &&
					userStories.data[lowestPortfolioItemType].PlannedEndDate < userStories.data.Iteration.StartDate &&
					userStories.data.ScheduleState !="Accepted")
					return userStories;
				}));
				return unsizedStoires + improperlySizedStoires + storyWithoutIteration + storyWithoutRelease + unacceptedStoriesinPastIteration + storiesScheduleAftePortfolioItemEndDate;
		},
		_buildFitnessGrid: function(){
			var me = this;
			me.loadedFitnesstab = true;
			var columnConfiguration = [
				{
					header: "Scrum Team",
					dataIndex: "scrumTeam",
					flex:2
				},
				{
					header: "Original Commit<br/>@" + me.initialCommitDate,
					dataIndex: "initalCommit",
					flex:2
				},
				{
					header: "Final Workload<br/>@" + me.finalCommitDate,
					dataIndex: "totalFinal",
					flex:2
				},
				{
					header: "Final Accepted<br/>@" + me.finalCommitDate,
					dataIndex: "finalAccepted",
					flex:2
				},
				{
					header: "Scope Change<br/> Good : Acceptable Increase = +10% <br/>Good : Aceeptable Decrease = -10%",
					dataIndex: "scopeChange",
					flex:2
				},
				{
					header: "Accept/Commit(Original) <br/> Good : >=90% but <=100% ",
					dataIndex: "acceptToCommit",
					flex:2
				},
				{
					header: "Data Integrity<br/>Good : DI <= 5 ",
					dataIndex: "dataIntegrity",
					flex:1
				}
			];
			var scrumDataRequireAttentionStore = Ext.create('Rally.data.custom.Store',{
					data: me.scrumDataRequireAttention
				});
			var scrumDataRequireAttentionGrid = Ext.create('Rally.ui.grid.Grid',{
					store: scrumDataRequireAttentionStore,
					showPagingToolbar: false,
					title:"<span class ='require-attention'>Require Attention</span>",
					columnCfgs:columnConfiguration
				});
			var scrumDataRecognizedStore = Ext.create('Rally.data.custom.Store',{
					data: me.scrumDataRecognized
				});
			var scrumDataRecognizedGrid = Ext.create('Rally.ui.grid.Grid',{
					store: scrumDataRecognizedStore,
					showPagingToolbar: false,
					title:"<span class ='good-job'> Recognized Scrum</span>",
					columnCfgs:columnConfiguration
				});
			var scrumDateReEnforceStore = Ext.create('Rally.data.custom.Store',{
					data: me.scrumDataReEnforce
				});
			var scrumDateReEnforceStoreGrid = Ext.create('Rally.ui.grid.Grid',{
					store: scrumDateReEnforceStore,
					showPagingToolbar: false,
					title: "How Can I Help?" ,
					columnCfgs:columnConfiguration
				});
			Ext.getCmp('grdScrumHealth').add(scrumDataRecognizedGrid);
			Ext.getCmp('grdScrumHealth').add(scrumDataRequireAttentionGrid);
			Ext.getCmp('grdScrumHealth').add(scrumDateReEnforceStoreGrid);
		},
		_renderReleaseDetailHeader: function() {
			var me = this,
				workWeek = me.ReleaseRecord.data.ReleaseDate < new Date() ? me.getWorkweek(me.ReleaseRecord.data.ReleaseDate) :    me.getWorkweek(new Date()),
				dataIntegrityDashboardLink = me.ARTDataIntegrityAppObjectID ?
					[
						"<span class ='link-achor good-job'>",
							"<a href='https://rally1.rallydev.com/#/" + me.ProjectRecord.data.ObjectID + "d/custom/" +
							me.ARTDataIntegrityAppObjectID + "' target='_blank'>",
								"Click here to view the ART Data Integrity Dashboard",
							"</a>",
						"</span>"
					].join('\n') :
					"ART Data Integrity Dashboard cannot be located!",
				dataAsOf = (me.ReleaseRecord.data.ReleaseDate < new Date() ? me.ReleaseRecord.data.ReleaseDate : new Date());

				var releaseDetailHeaderHtml = [
					"Release: " + me.ReleaseRecord.data.Name,
					"ReleaseStartDate: " + new Date(me.ReleaseRecord.data.ReleaseStartDate).toLocaleDateString(),
					"ReleaseEndDate: " + new Date(me.ReleaseRecord.data.ReleaseDate).toLocaleDateString(),
					"Data as of: " + dataAsOf.toLocaleDateString(),
					"WorkWeek: ww" + workWeek,
					"<br/>" + dataIntegrityDashboardLink
				].join(",");

				Ext.getCmp('grdScrumHealthHeader').update(releaseDetailHeaderHtml);

    },
		_buildPortfolioProgressGrid: function(){
			var me = this,
				lowestPortfolioItemType = me.PortfolioItemTypes[0];
			Ext.getCmp('scopeGrid').add({
				xtype: 'rallygrid',
				id: 'grid',
				showRowActionsColumn: false,
				selModel:{
					ignoreRightMouseSelection:true,
					checkOnly:true
				},
				enableEditing: false,
				autoScroll: true,
				height: 500,
				showPagingToolbar: false,
				title: [lowestPortfolioItemType] +' Progress in the Release ('+ me.InitialTargetDate + ' - ' + me.CompleteFinalTargetDate + ')',
				store: me.gridstore,
				columnCfgs: [{
					header: lowestPortfolioItemType + 's',
					dataIndex: "Name",
					flex:4,
					renderer: function(v, m, r) {
						//TODO: find why r.data.ProjectObjectID is empty , temp: replacing with me.ProjectRecord.data.ObjectID
						return Ext.String.format('<a href="{0}/#/{1}d/detail/portfolioitem/{2}/{3}" target="_blank">{4}: </a>{5}',
							me.BaseUrl, r.data.ProjectObjectID, lowestPortfolioItemType, r.data.ObjectID, r.data.FormattedID, v );
					}
				},{
					header: "% Complete<br/> @ Release Start " + me.InitialTargetDate,
					dataIndex: "completedAtStart",
					flex:2,
					xtype:'intelcomponentcolumn',
					tdCls: 'iconCell',
					resizable:false,
					draggable:false,
					renderer: function(v, meta, record){
						return {
							xtype:'progressbar',
							text:[(v* 100).toFixed(2), '%'].join(''),
							width:'100px',
							value:v
						};
					}
				},{
					header:"% Complete<br/>@ Release End " + me.CompleteFinalTargetDate,
					dataIndex: "completedAtEnd",
					flex:2,
					xtype:'intelcomponentcolumn',
					tdCls: 'iconCell',
					resizable:false,
					draggable:false,
					renderer: function(v, meta, record){
						return {
							xtype:'progressbar',
							text:[(v* 100).toFixed(2), '%'].join(''),
							width:'100px',
							value:v
						};
					}
				},{
					header: "Points Planned<br/> @ Release Start " + me.InitialTargetDate,
					dataIndex: "intent",
					flex:1
				},{
					header: "Final Points <br/>@ Release End " + me.CompleteFinalTargetDate,
					dataIndex: "actual",
					flex:1
				},{
					header: lowestPortfolioItemType + " Scope Change",
					dataIndex: "growth",
					flex:2,
					xtype:'intelcomponentcolumn',
					tdCls: 'iconCell',
					resizable:false,
					draggable:false,
					renderer: function(v, meta, record){
						var growthText = "";
						if(v > 0) growthText = '+' + [(v* 100).toFixed(2), '%'].join('');
						else growthText = [(v* 100).toFixed(2), '%'].join('');
						return {
							xtype:'container',
							html:growthText
						};
					}
				},{
					header: lowestPortfolioItemType + " State",
					dataIndex: "state",
					flex:1
				}]
			});

		},
		_buildArtScrumFitnessGridStore: function(){
			var me = this,
			calc = Ext.create('Intel.lib.chart.FastCumulativeFlowCalculator',{
					scheduleStates:me.ScheduleStates,
					startDate: me.ReleaseRecord.data.ReleaseStartDate,
					endDate: me.ReleaseRecord.data.ReleaseDate
				});
		//WsapiUserStoryMap for DataIntegrity
			me.TeamStoresDI = _.reduce(me.UserStoryStore.getRange(), function(hash, r,key){
				var teamName = r.data.Project.Name;
				hash[r.data.Project.Name] = _.filter(me.UserStoryStore.getRange(),function(f){ return f.data.Project.Name === teamName; });
				return hash;
			}, {});

			me.dataIntegrity[me.TeamType] = 0;
			me.initialAddedDaysCount = (typeof(me.initialAddedDaysCount) === "undefined") ? 6 : me.initialAddedDaysCount ;
//			me.dataIntegrity[teamName] = DI;
			//Fitness grid
			var recognized = "<span class='good-job'> Recognized scrum of the week</span>",
				requireAttention = "<span class='require-attention'> Require Attention</span>",
				requireHelp = "<span class='require-attention'>How can I help?</span>",
				requireHelpImage = "<span><img src='https://rally1.rallydev.com/slm/images/icon_help.gif' alt='Help' title='How can I help?' border='0' height='24' width='24'></span>",
				reInforce = " (Re Enforce)",
				good = "<span class='good-job'> (Good)</span>";
			me.scrumData = _.reduce(me.ProjectsOfFunction, function(hash, r,key){
				var teamName = r.data.Name,
					teamObjectID = r.data.ObjectID;
				if(!me.TeamStores[teamObjectID]) return hash;
				me.aggregateChartData[teamName] = me.updateCumulativeFlowChartData((calc.runCalculation(me.TeamStores[teamObjectID])), {trendType:'Last2Sprints'});
				var finalCommitIndex = me.aggregateChartData[teamName].categories.length - 1;
				hash[r.data.Name] = function fitnessGridColumnCalc(){
					var totalinitial = _.reduce(me.aggregateChartData[teamName].series,function(sum,s){
						if(s.name== "Projected" || s.name=="Ideal") return sum + 0;
						return  sum + s.data[me.initialAddedDaysCount];
					},0);
					var totalProjected = _.reduce(me.aggregateChartData[teamName].series,function(sum,s){
						if(s.name!= "Projected") return sum + 0;
						if(typeof(s.data[finalCommitIndex]) == "object") return sum +  s.data[finalCommitIndex].y;
						else	return sum + s.data[finalCommitIndex];
						},0);
					var totalIdeal = _.reduce(me.aggregateChartData[teamName].series,function(sum,s){
						if(s.name === "Ideal") return sum + s.data[finalCommitIndex];
						else	return sum + 0;
						},0);
					var DI =  me._calculateDataIntegrity(me.TeamStoresDI[teamName]);
					scopechange= ((totalIdeal - totalinitial)/totalinitial) * 100 ;
					acceptToCommit = (totalProjected/totalinitial)*100;
					var teamStatus ="",
						scopeStatus ="";
					scopeStatus =  (scopechange>= -10 && scopechange <= 10.99)? good : requireAttention;
					teamStatus  = (DI <= 5 &&	((scopechange >= -10 && scopechange <= 10.99) ) &&
					acceptToCommit>90 && acceptToCommit<=100.99) ? recognized : requireAttention;
					scopechange = ($.isNumeric(scopechange) ? (scopechange.toFixed(2)> 0 ? '+' + scopechange.toFixed(2) + '%' + scopeStatus : scopechange.toFixed(2) + '%'+ scopeStatus) : "-");
					acceptToCommit = ($.isNumeric(acceptToCommit) ? (acceptToCommit.toFixed(2) > 90 && acceptToCommit<=100.99 ? acceptToCommit.toFixed(2)+ '%'+ good : acceptToCommit.toFixed(2) + '%'+ requireAttention) : "-");
					/* scopechange = $.isNumeric(scopechange) ? scopechange : "-";
					acceptToCommit = $.isNumeric(acceptToCommit) ? acceptToCommit : "-"; */

					return {initalCommit : totalinitial,
						totalFinal : totalIdeal,
						finalAccepted : totalProjected,
						scopeChange:  scopechange,
						acceptToCommit: acceptToCommit,
						dataIntegrity: DI <= 5 ? DI + good :DI + requireAttention,
						scrumTeam:teamName,
						status:teamStatus,
						categories: (teamName.split("-")[0])
					};
			}();
			return hash;
			}, {});
		//all good
		me.scrumDataRecognized = [],
		me.scrumDataRequireAttention =[],
		me.scrumDataReEnforce =[];
		_.each(me.scrumData,function(team){
			if(team.status.indexOf(recognized) > -1 ){
				me.scrumDataRecognized.push(team);
			}else if(team.scopeChange.indexOf(requireAttention)> -1 && team.dataIntegrity.indexOf(requireAttention)> -1 && team.acceptToCommit.indexOf(requireAttention)> -1 ){
				team.scopeChange = team.scopeChange.replace(requireAttention,requireHelpImage);
				team.dataIntegrity = team.dataIntegrity.replace(requireAttention,requireHelpImage);
				team.acceptToCommit = team.acceptToCommit.replace(requireAttention,requireHelpImage);
				me.scrumDataRequireAttention.push(team);
			}else{
				team.scopeChange = team.scopeChange.replace(requireAttention,requireHelpImage);
				team.dataIntegrity = team.dataIntegrity.replace(requireAttention,requireHelpImage);
				team.acceptToCommit = team.acceptToCommit.replace(requireAttention,requireHelpImage);
				team.status = team.status.replace(requireAttention,requireHelp);
				me.scrumDataReEnforce.push(team);
			}
		});
		},
		_buildCumulativeFlowChart: function(){
			var me = this,
			calc = Ext.create('Intel.lib.chart.FastCumulativeFlowCalculator',{
					scheduleStates:me.ScheduleStates,
					startDate: me.ReleaseRecord.data.ReleaseStartDate,
					endDate: me.ReleaseRecord.data.ReleaseDate
				});

			//chart config setting
			//using jquery to use the high charts
			//uses ChartUpdater mixin
			//uses IntelWorkweek mixin
			me.initialAddedDaysCount = (typeof(me.initialAddedDaysCount) === "undefined") ? 6 : me.initialAddedDaysCount ;
			me.dataIntegrity ={},
			me.aggregateChartData ={};
			me.dataIntegrity[me.TeamType] = 0;
			me.totalInitial = {};
			var scopechange ={},
				acceptToCommit={},
				datemap = {},
				totalCommitReleaseStart = {},
				categories = [],
				dataAcceptCommit=[],
				dataScope=[],
				todayIndex = -1,
				lastIndex = -1;
			//calculation Scope Change and A/C
			function calculateScopeAC(teamName){
				var totalinitial = 0 ;
				var finalCommit = 0 ;
				var finalAccepted = 0;
				var totalProjected = 0 ;
				var totalideal = 0;
				var finalCommitIndex = me.aggregateChartData[teamName].categories.length - 1;
				_.each(me.aggregateChartData[teamName].series,function(f){
					if(f.name==="Accepted"){
						finalAccepted = finalAccepted + f.data[finalCommitIndex];}
					//we want to ignore the ideal and the projected from the aggregateChartData
					if(f.name !="Ideal" && f.name != "Projected"){
						//taking sample after 7 days and before 7 days
						//or date from date picker
						totalinitial = totalinitial + f.data[me.initialAddedDaysCount];
						finalCommit = finalCommit + f.data[finalCommitIndex];}
					//if the release is still on going we would like to use the projected data for the final commit
					if(f.name === "Projected"){
						totalProjected = totalProjected+ f.data[finalCommitIndex];}
					if(f.name === "Ideal"){
						totalideal = totalideal + f.data[finalCommitIndex];}
				});
				if(finalCommit === 0){
					finalCommit = totalideal > 0 ? totalideal : totalProjected;
					finalAccepted = totalProjected > 0 ? totalProjected : totalideal;}
				if(teamName === me.TeamType){
					me.total ={};
					me.total.initialCommit = totalinitial;
					me.total.finalCommit = finalCommit;
					me.total.finalAccepted = finalAccepted;}
				scopechange[teamName] = ((finalCommit - totalinitial)/totalinitial) * 100;
				acceptToCommit[teamName] = (finalAccepted/totalinitial)*100;
				categories.push(teamName.split("-")[0]);
				dataAcceptCommit.push(acceptToCommit[teamName]);
				dataScope.push(scopechange[teamName]);
			}
			//for the train as a whole
			me.aggregateChartData[me.TeamType] = me.updateCumulativeFlowChartData((calc.runCalculation(me.AllSnapshots)), {trendType:'Last2Sprints'});

			datemap[me.TeamType] = me.aggregateChartData[me.TeamType].datemap;
			calculateScopeAC(me.TeamType);
			me.initialCommitDate = datemap[me.TeamType][me.initialAddedDaysCount];
			me.finalCommitDate = datemap[me.TeamType][datemap[me.TeamType].length - 1];
			me.categories = me.TeamType;
			me.dataAcceptCommit = acceptToCommit[me.TeamType];
			me.dataScope = scopechange[me.TeamType];

			//cumulative flow chart
			var commitDataPlus =[];
			// commitDataMinus = [];
			//adding a line for the initial Commitment projection
			_.each(me.aggregateChartData[me.TeamType].categories,function(f,key){
				commitDataPlus.push(me.total.initialCommit);//commitDataMinus.push(total.initialCommit - 10);
			});
			//console.log(commitDataPlus,commitDataMinus);
			me.aggregateChartData[me.TeamType].series.push({
				colorIndex: 1,
				symbolIndex: 1,
				dashStyle: "shortdash",
				color: "red",
				data:commitDataPlus,
				name: "Commitment",
				type: "spline"
			});
			$("#retroChart").highcharts(Ext.Object.merge(me.getDefaultCFCConfig(), me.getCumulativeFlowChartColors(), {
				chart: {
					height: 400,
					width: me.getWidth()*0.42>>0,
					zoomType: ""
				},
				legend:{
					borderWidth:0,
					width:500,
					itemWidth: me.getWidth()*0.42>>0 - 50
				},
				title: {
					text: me.ProjectRecord.data.Name
				},
				subtitle:{
					text: me.ReleaseRecord.data.Name.split(' ')[0]
				},
				xAxis:{
					categories: me.aggregateChartData[me.TeamType].categories,
					tickInterval: me.getCumulativeFlowChartTicks(me.ReleaseRecord.data.ReleaseStartDate, me.ReleaseRecord.data.ReleaseDate, me.getWidth()*0.44),
					plotLines: [{
						color: '#58FAF4', // Color value
						dashStyle: 'shortdash', // Style of the plot line. Default to solid
						type: "spline",
						value: me.initialAddedDaysCount, // Value of where the line will appear
						width: 2,
						zIndex: 5,
						label : {
							text : 'Original Commit ',
							style:{
								color:'black',
								'text-shadow': '0 1px 0 white',
								background:'#40d0ed'
							}
						}
					},{
						color: '#58FAF4', // Color value
						dashStyle: 'shortdash', // Style of the plot line. Default to solid
						type: "spline",
						value: [me.aggregateChartData[me.TeamType].categories.length - 1], // Value of where the line will appear
						width: 2,
						zIndex: 5,
						label : {
							text : 'Final Workload & Accepted',
							style:{
								color:'black',
								'text-shadow': '0 1px 0 white'
							}
						}

					}]
				},
				series: me.aggregateChartData[me.TeamType].series
			}));
			me.setCumulativeFlowChartDatemap($("#retroChart").children()[0].id, datemap[me.TeamType]);
		},
		_hideHighchartsLinks: function(){
			$('.highcharts-container > svg > text:last-child').hide();
		},
		_buildRetroChart: function(){
			var me = this,
				scopeDeltaPerc = ((me.total.finalCommit - me.total.initialCommit)/((me.total.initialCommit))) * 100,
				originalCommitRatio = (me.total.finalAccepted/me.total.initialCommit)* 100,
				finalCommitRatio = (me.total.finalAccepted /me.total.finalCommit)* 100,
				dataseries = [],
				chartdefaultColorConfig = { colors: ['#3A874F','#7cb5ec'] },
				metTargetColorConfig = { colors: ['#40d0ed','#92D050'] },
				didnotMetTargetColorConfig = { colors: ['#40d0ed','#d05052'] } ,
				chartMax = []; //set the max so that all the chart look the same
			Highcharts.setOptions(chartdefaultColorConfig);
			var	defaultchartConfig = {
					chart: {
						type: 'column'
					},
					title: {
						text: scopeDeltaPerc > 0 ? 'Scope Delta: +' + scopeDeltaPerc.toFixed(2) + '%' :'Scope Delta:' + scopeDeltaPerc.toFixed(2) + '%'
					},
					subtitle: {
						text: Math.round(me.total.finalCommit) + ' of ' + Math.round(me.total.initialCommit)
					},
					xAxis: {
						categories: ['Original Commit','Final Workload'],
						tickLength:10
					},
					yAxis: {
						min: 0,
						tickPixelInterval: 50,
						title: {
							text: 'Total Points'
						},
						plotLines : [{
							name:'maxTarget',
							value : me.total.initialCommit + (0.1 * me.total.initialCommit), //max target,
							color : '#92d947',
							dashStyle : 'shortdash',
							width : 2,
							zIndex: 5,
							label : {
								text : 'Acceptable increase (+10%)',
								style:{
									color:'black',
									'text-shadow': '0 1px 0 white'
								}
							}
						},{
							name:'minTarget',
							value : me.total.initialCommit - (0.1 * me.total.initialCommit),
							color : '#92d947',
							dashStyle : 'shortdash',
							zIndex: 5,
							width : 2,
							label : {
								text : 'Acceptable decrease (-10%) ' ,
								style:{
									color:'black',
									'text-shadow': '0 1px 0 white'
								}
							}
						}]
					},
					tooltip: {
						valueDecimals: 1
					},
					plotOptions: {
						series: {
							stacking: 'normal',
							borderWidth: 2,
							borderColor: 'white',
							shadow: true
						},
						column: {colorByPoint: true}
					},
					series: [{
						name: ['Total'],
						showInLegend: false,
						data: [{
							name:'Original Commit',
							y:me.total.initialCommit
						},{
							name:'Final Workload',
							y: me.total.finalCommit
						}],
						dataLabels: {
							enabled: true,
							format: '{point.y:,.0f}'//show no decimal points
						}
					}]
				};
			chartMax.push(me.total.initialCommit,me.total.finalAccepted, me.total.finalCommit);
			defaultchartConfig.yAxis.max = Math.max.apply(null, chartMax);
			defaultchartConfig.yAxis.max = defaultchartConfig.yAxis.max + ((20/100) * defaultchartConfig.yAxis.max);//increasing the number by 20%
			Highcharts.setOptions((scopeDeltaPerc >= -10.99 && scopeDeltaPerc <= 10.99) ? metTargetColorConfig : didnotMetTargetColorConfig);
			if(scopeDeltaPerc >= 400){
				defaultchartConfig.yAxis.plotLines[1].label = {
					text : 'Acceptable decrease (-10%)',
					align: 'right',
					x: -10,
					y: 16
				};
			}
			$('#retroBarChartScopeWrapper').highcharts(defaultchartConfig);
			var	CaOriginalchartConfig = {
					subtitle: {
						text: Math.round(me.total.finalAccepted) + ' of ' + Math.round(me.total.initialCommit)
					},
					xAxis: {
						categories: ['Original Commit','Final Accepted'],
						tickLength:10
					},
					title: {
						text: 'A/C Original: ' + originalCommitRatio.toFixed(0) + '%'
					},
					yAxis: {
						plotLines : [{
							name:'maxTarget',
							value : (0.9 * me.total.initialCommit),
							color : '#92d947',
							dashStyle : 'shortdash',
							width : 2,
							zIndex: 5,
							label : {
								text :'Target >90%',
								align:'center',
								style:{
									color:'black',
									'text-shadow': '0 1px 0 white'
								}
							}
						}]
					},
					series: [{
						name: ['Total'],
						showInLegend: false,
						data: [{
							name:'Original Commit',
							y: me.total.initialCommit
						},{
							name:'Final Accepted',
							y: me.total.finalAccepted
						}],
						dataLabels: {
							enabled: true,
							format: '{point.y:,.0f}'//show no decimal points
						}
					}]
				};
			Highcharts.setOptions((originalCommitRatio >= 90) ? metTargetColorConfig : didnotMetTargetColorConfig );
			$("#retroBarChartCaOriginalWrapper").highcharts(Ext.Object.merge({}, defaultchartConfig, CaOriginalchartConfig));

			//third chart CA orginial
			var	caFinalchartConfig = {
					title: {
						text: 'A/C Final: ' + finalCommitRatio.toFixed(0) + '%'
					},
					subtitle: {
						text: Math.round(me.total.finalAccepted) + ' of ' + Math.round(me.total.finalCommit)
					},
					xAxis: {
						categories: ['Final Workload','Final Accepted'],
						tickLength:10
					},
					yAxis: {
						plotLines : [{
							name:'maxTarget',
							value : (0.9 * me.total.finalCommit),
							color : '#92d947',
							dashStyle : 'shortdash',
							width : 2,
							zIndex: 5,
							label : {
								text : 'Target >90%',
								align: 'center',
								style:{
									color:'black',
									'text-shadow': '0 1px 0 white'
								}
							}
						}]
					},
					series: [{
						name: ['Total'],
						showInLegend: false,
						data: [{
							name:'Final Workload',
							y:me.total.finalCommit
						},{
							name:'Final Accepted',
							y:me.total.finalAccepted
						}],
						dataLabels: {
							enabled: true,
							format: '{point.y:,.0f}'//show no decimal points
						}
					}]
				};
			finalCommitRatio = (me.total.finalAccepted /me.total.finalCommit)* 100;
			//plus minus 10 is acceptable
			Highcharts.setOptions((finalCommitRatio >= 90) ? metTargetColorConfig : didnotMetTargetColorConfig );
			$("#retroBarChartCaFinalWrapper").highcharts(Ext.Object.merge({}, defaultchartConfig, caFinalchartConfig));
		},
		_setUserPreferenceReleaseStartDate:function(){
			var me = this,
				rid = me.ReleaseRecord.data.ObjectID,
				pid = me.ProjectRecord.data.ObjectID;
			if(typeof me.AppsPref.projs[pid] !== 'object') me.AppsPref.projs[pid] = {};
			me.releaseStartDateChanged = (!!(me.AppsPref.projs[pid][rid]))? true : false;
			if(me.releaseStartDateChanged){
				me.datePickerDate = me.AppsPref.projs[pid][rid].ReleaseStartDate;
				var date1 = me.ReleaseRecord.data.ReleaseStartDate,
					date2 = new Date(me.datePickerDate),
					_1day = 1000 * 60 * 60 * 24 ;
				me.initialAddedDaysCount = Math.floor(( Date.parse(date2) - Date.parse(date1) ) / _1day );
			}
		},
		_loadScopeToReleaseTab: function(){
			var me = this;
			if(me.AllSnapshots.length === 0 ){
					me.alert('ERROR', me.ScrumGroupRootRecord.data.Name + ' has no data for release: ' + me.ReleaseRecord.data.Name);
					return;
				}else{
					me._buildScopeToReleaseStore();
					me._buildPortfolioProgressGrid();
				}
		},
		_loadARTScrumFitnessTab: function(){
			var me = this;
			return Q.all([
				me._getStories()
			])
			.then(function(){
				me._buildArtScrumFitnessGridStore();
				me._buildFitnessGrid();
				Ext.getCmp('scopeGridWrapper').setLoading(false);
				Ext.getCmp('datePickerWrapper').setLoading(false);
				me._renderReleaseDetailHeader();
			})
			.fail(function(reason){
				Ext.getCmp('scopeGridWrapper').setLoading(false);
				Ext.getCmp('datePickerWrapper').setLoading(false);
				me.alert('ERROR', reason);
			})
			.done();

		},
		_reloadEverything: function(){
			var me = this;
			me.setLoading('Loading Data');
			Ext.getCmp('scopeGrid').removeAll();
			Ext.getCmp('grdScrumHealthHeader').update(" ");
			Ext.getCmp('grdScrumHealth').removeAll();
			//hack to dynamically change the tab title
			//TODO: find a better way
			Ext.getCmp('scopeGridWrapper').tabBar.activeTab.update((me.PortfolioItemTypes[0] + " Progress").toUpperCase());
			$('.x-tab-bar .x-tab-active').css({'font-size':'12px'});
			//load all the child release to get the user story snap shots
			//get the portfolioItems from wsapi
			return Q.all([
				me._loadAllChildReleases() ,
				me._getPortfolioItems()
			])
			.then(function(){
				//loading it again if you have multiple tab open for the same app
				//and changing date from different tab
				//you will be able to see the right away
				return me.loadAppsPreference().then(function(appsPref){
					me.AppsPref = appsPref;
					me._setUserPreferenceReleaseStartDate();
				});
			})
			.then(function(){
				return me._loadSnapshotStores();
			})
			.then(function(){
				//load all the user story snap shot for release
				//load all the user stories for the release portfolioItems
				me._buildCumulativeFlowChart();
				me._buildRetroChart();
				me._hideHighchartsLinks();

			})
			.then(function(){
				if(me.AllSnapshots.length === 0 ){
					me.alert('ERROR', me.ScrumGroupRootRecord.data.Name + ' has no data for release: ' + me.ReleaseRecord.data.Name);
					return;
				}else{
					Ext.getCmp('scopeGridWrapper').setLoading("Loading Scrum Fitness Grid");
					Ext.getCmp('datePickerWrapper').setLoading("Loading Scrum Fitness Grid");
					me._loadScopeToReleaseTab();
					me._loadARTScrumFitnessTab();
				}
			});
		},
		launch: function() {
			var me = this;
			me.setLoading('Loading Configuration');
			me.configureIntelRallyApp()
				.then(function(){
					var scopeProject = me.getContext().getProject();
					return me.loadProject(scopeProject.ObjectID);
				})
				.then(function(scopeProjectRecord){
					me.ProjectRecord = scopeProjectRecord;
					return Q.all([ //two streams
						me.projectInWhichScrumGroup(me.ProjectRecord) /********* 1 ************/
							.then(function(scrumGroupRootRecord){
								if(scrumGroupRootRecord){
									me.ScrumGroupRootRecord = scrumGroupRootRecord;
									return Q.all([
										me.loadAllLeafProjects(me.ScrumGroupRootRecord)
											.then(function(leafProjects){
												me.LeafProjects = leafProjects;
												me.TeamType = me.ProjectRecord.data.Name.split(" ")[0];
												me.ProjectsOfFunction = _.filter(me.LeafProjects, function(proj){
													return me._getTeamTypeAndNumber(proj.data.Name).TeamType; //== me.TeamType;
												});
												if(_.find(leafProjects, function(p){ return p.data.ObjectID == me.ProjectRecord.data.ObjectID; }))
													me.CurrentScrum = me.ProjectRecord;
												else me.CurrentScrum = null;
											}),
										me.loadScrumGroupPortfolioProject(me.ScrumGroupRootRecord)
											.then(function(scrumGroupPortfolioProject){
												me.ScrumGroupPortfolioProject = scrumGroupPortfolioProject;
											})
									]);
								}
								else me.CurrentScrum = me.ProjectRecord;
							}),
						me.getCustomAppObjectID('Intel.DataIntegrityDashboard.Vertical').then(function(customAppObjectID){
							me.ARTDataIntegrityAppObjectID = customAppObjectID;
						}),
						me.loadAppsPreference() /******** load stream 2 *****/
							.then(function(appsPref){
								me.AppsPref = appsPref;
								var oneYear = 1000*60*60*24*365;
								var endDate = new Date();
								return me.loadReleasesBetweenDates(me.ProjectRecord, (new Date()*1 - oneYear), endDate);
							})
							.then(function(releaseRecords){
								me.ReleaseRecords = releaseRecords;
								var currentRelease = me.getScopedRelease(releaseRecords, me.ProjectRecord.data.ObjectID, null);
								if(currentRelease) me.ReleaseRecord = currentRelease;
								else return Q.reject('This project has no releases.');
							})
					]);
				})
				.then(function(){ me._buildReleasePicker(); })
				.then(function(){ return me._reloadEverything(); })
				.fail(function(reason){ me.alert('ERROR', reason); })
				.then(function(){ me.setLoading(false); })
				.done();
		}
	});
}());

            Rally.launchApp('Intel.TrainPerformanceDashboard', {
                name:"Train Performance Dashboard",
	            parentRepos:""
            });

        });
    </script>

    <style type="text/css">
        .app {
  /* Add app styles here */
}
.ulInformation {
  margin-top: -3px;
}
.clickForDateChange {
  text-decoration: underline;
  font-style: italic;
  cursor: pointer;
  padding-top: 5px;
  padding-left: 5px;
  margin-left: 5px;
}
#cntDatePickerWrapper {
  margin-left: 5px;
}
#datePickerWrapper {
  border-top: solid #BFBFBF 1px;
  border-left: solid #BFBFBF 1px;
  border-right: solid #BFBFBF 1px;
  padding-top: 5px;
  padding-left: 10px;
  background-color: #cbdbef;
}
/* .appInfo{
	float:right;
	text-align:right;
	font:bold;
	font-weight: bold;
	color: green;
} */
.rui-triggerfield .icon-calendar {
  background-image: none;
  color: #888;
  font-size: 12px;
  padding: 3px 3px 5px 4px;
}
.chart-wrapper {
  width: 100%;
}
.retrochart-wrapper {
  clear: both;
  float: right;
  /* 	border: solid green 1px; */
  /*change the width percentage to change the size of the donut-wrapper*/
}
.barchart-wrapper {
  border-bottom: solid #BFBFBF 1px;
  border-left: solid #BFBFBF 1px;
  border-right: solid #BFBFBF 1px;
  width: 100%;
  /*change the width percentage to change the size of the donut-wrapper*/
  /* 	background-color:red; */
}
#cntInformation {
  clear: both;
  float: right;
  text-decoration: underline;
  font-style: italic;
  font-weight: bold;
  color: green;
  text-align: right;
  border-top: solid #BFBFBF 1px;
  border-left: solid #BFBFBF 1px;
  border-right: solid #BFBFBF 1px;
  width: 100%;
}
.help {
  cursor: pointer;
}
.legendwrapper {
  margin-top: -35px;
  /* 	border:red solid 1px; */
  float: right;
  width: 40%;
  text-align: center;
}
.mtargetwrapper {
  float: left;
  color: #333333;
  font-size: 12px;
  font-weight: bold;
}
.mtarget {
  float: left;
  background-color: #92D050;
  height: 12px;
  width: 16px;
  margin-left: 50px;
  margin-right: 3px;
  margin-top: 3px;
}
.atarget {
  float: left;
  height: 12px;
  width: 16px;
  margin-left: 50px;
  margin-right: 3px;
  margin-top: 3px;
}
.dtargetwrapper {
  float: left;
  color: #333333;
  font-size: 12px;
  font-weight: bold;
  cursor: pointer;
}
.dtarget {
  float: left;
  background-color: #d05052;
  height: 12px;
  width: 16px;
  margin-left: 10px;
  margin-right: 3px;
  margin-top: 3px;
}
#grid_header_hd {
  color: #396295;
  font-size: 14px;
  font-weight: bold;
}
/* simple, float it right instead of left */
.progress-right .progress-bar-r {
  float: left;
  border: solid red 1px;
  height: 20px;
  text-align: left;
  background-color: #fc8585;
  text-shadow: 0 1px 0 #D05052;
  color: black;
}
div.sr-only {
  border: solid #D05052 1px;
  float: left;
  background-color: #D05052;
  color: white;
  height: 19px;
  overflow: visible;
  /*   direction: rtl; */
}
.require-attention {
  color: red;
}
.good-job {
  color: #1ab058;
  font-weight: bold;
}
.grd-ScrumHealth .x-panel-header {
  background-color: whitesmoke;
}
.grd-ScrumHealth .x-panel-header-text {
  color: #40d0ed;
  font-weight: bold;
}
#grdScrumHealthHeader {
  margin-top: 5px;
  font-size: 12px;
  text-align: center;
}
#grdScrumHealthHeader a {
  color: black;
  text-decoration: underline;
}
.x-tab-default-override {
  border-bottom: 1px solid #deecfd;
  background-color: #cbdbef;
  border-color: #8db3e3;
}
.x-tab-default-override a {
  border-color: #8db3e3;
  background-color: #cbdbef;
  cursor: pointer;
}
.x-tab-default-top {
  background-color: white !important;
}

    </style>
</head>
<body>
</body>
</html>