Rally 如何按故事时数获取投资组合传奇项目的状态?
有人知道一种简单的方法,可以在与“Portfolioitem/Saga”相关的报道时间内完成百分比吗?CA中现有的portfolio items应用程序仅显示按故事计划估算完成的百分比,我也想获得任务时数的详细信息 我已经编写了以下自定义应用程序来包含数据,但是我的表在返回数据之前被加载。请帮忙Rally 如何按故事时数获取投资组合传奇项目的状态?,rally,Rally,有人知道一种简单的方法,可以在与“Portfolioitem/Saga”相关的报道时间内完成百分比吗?CA中现有的portfolio items应用程序仅显示按故事计划估算完成的百分比,我也想获得任务时数的详细信息 我已经编写了以下自定义应用程序来包含数据,但是我的表在返回数据之前被加载。请帮忙 Ext.define('Rally.app.PortfolioGrid', { extend: 'Rally.app.App', componentCls
Ext.define('Rally.app.PortfolioGrid', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function() {
console.log('Working');
this._loadComboBox();
},
_loadComboBox : function() {
this.searchComboBox = Ext.create('Rally.ui.combobox.ArtifactSearchComboBox', {
fieldLabel: 'Saga',
noEntryText: '',
emptyText: 'Enter saga ID or keyword...',
//grow: true,
//allowNoEntry: false,
storeConfig: {
autoLoad: true,
models: ['PortfolioItem/Saga']
},
listeners: {
select: function(combobox, records) {
this._loadData();
},
scope: this
}
});
this.add(this.searchComboBox);
},
_loadData: function () {
var selectedID = this.searchComboBox.getRecord().get('FormattedID');
console.log('Selected Saga', selectedID);
var myFilters = [
{
property: 'FormattedID',
operation: '=',
value: selectedID
},
];
if (this.myStore) {
console.log('store exists');
this.myStore.setFilter(myFilters);
this.myStore.load();
// create store
} else {
this.myStore = Ext.create('Rally.data.wsapi.Store', {
model: 'PortfolioItem/Saga',
autoLoad: true,
fetch: ['FormattedID', 'Name', 'Children', 'Release', 'State', 'PercentDoneByStoryPlanEstimate', 'PercentDoneByStoryCount', 'Project', 'Owner'],
filters: myFilters,
listeners: {
load: this._onDataLoaded,
scope: this
}
});
}
},
_onDataLoaded: function(store, data){
var features = [];
var pendingstories = data.length;
Ext.Array.each(data, function(feature) {
var f = {
FormattedID: feature.get('FormattedID'),
Name: feature.get('Name'),
_ref: feature.get("_ref"),
State: (feature.get('State') && feature.get('State').Name) || ' ',
Project: (feature.get('Project') && feature.get('Project').Name) || ' ',
Owner: (feature.get('Owner') && feature.get('Owner')._refObjectName) || 'No Owner',
PercentDoneByStoryPlanEstimate: Math.floor(feature.get('PercentDoneByStoryPlanEstimate') * 100) + '%',
PercentDoneByStoryCount: Math.floor(feature.get('PercentDoneByStoryCount') * 100) + '%',
Children: [],
totalPlannedHours: 0,
totalHoursRemaining: 0,
totalHoursCompleted: 0,
Percentage: 0
};
var sagaFeatures = feature.getCollection('Children');
sagaFeatures.load({
fetch: ['FormattedID', 'Parent', 'UserStories', 'DirectChildrenCount'],
callback: function(records, operation, success){
Ext.Array.each(records, function(child){
var s = {
FormattedID: child.get('FormattedID'),
};
if (child.get('DirectChildrenCount') > 0) {
child.getCollection('UserStories').load({
fetch: ['FormattedID', 'TaskEstimateTotal', 'TaskRemainingTotal'],
callback: function(records, operation, success) {
Ext.Array.each(records, function(us) {
f.totalPlannedHours += us.get('TaskEstimateTotal');
f.totalHoursRemaining += us.get('TaskRemainingTotal');
f.totalHoursCompleted = f.totalPlannedHours - f.totalHoursRemaining
console.log("Total Hours Completed", f.totalHoursRemaining)
f.Percentage = Math.floor((f.totalHoursCompleted / f.totalPlannedHours) * 100) + '%';
}, this);
}, scope: this});
}
f.Children.push(s);
}, this);
},
scope: this
});
features.push(f);
--pendingstories;
if (pendingstories === 0) {
this._createGrid(features);
}
}, this);
this._createGrid(features);
},
_createGrid: function(features) {
var myCustomStore = Ext.create('Rally.data.custom.Store', {
data: features
//pageSize: 100,
});
if (!this.grid) {
this.grid = this.add({
xtype: 'rallygrid',
flex: 1,
store: myCustomStore,
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'Name', dataIndex: 'Name'
},
{
text: 'State', dataIndex: 'State'
},
{
text: 'Saga Features', dataIndex: 'Children',
renderer: function(value) {
var html = [];
Ext.Array.each(value, function(child){
html.push('<a href="' + Rally.nav.Manager.getDetailUrl(child) + '">' + child.FormattedID + '</a>')
});
return html.join(', ');
}
},
{
text: 'Percent Done By Story Plan Estimate', dataIndex: 'PercentDoneByStoryPlanEstimate'
},
{
text: 'Percent Done By Story Count', dataIndex: 'PercentDoneByStoryCount'
},
{
text: 'Total Planned Hours', dataIndex: 'totalPlannedHours'
},
{
text: 'Total Completed Hours', dataIndex: 'totalHoursCompleted'
},
{
text: 'Percent Done by Story Hours', dataIndex: 'Percentage'
},
{
text: 'Project', dataIndex: 'Project'
},
{
text: 'Owner', dataIndex: 'Owner'
}
]
});
}
else {
this.grid.reconfigure(myCustomStore);
}
}
});
Ext.define('Rally.app.PortfolioGrid'{
扩展:“Rally.app.app”,
组件CLS:“应用程序”,
启动:函数(){
console.log('Working');
这是。_loadComboBox();
},
_loadComboBox:函数(){
this.searchComboBox=Ext.create('Rally.ui.combobox.ArtifactSearchComboBox'{
fieldLabel:“传奇”,
noEntryText:“”,
emptyText:'输入saga ID或关键字…',
//成长:是的,
//allowNoEntry:错误,
storeConfig:{
自动加载:对,
型号:['PortfolioItem/Saga']
},
听众:{
选择:函数(组合框,记录){
这是。_loadData();
},
范围:本
}
});
this.add(this.searchComboBox);
},
_loadData:函数(){
var selectedID=this.searchComboBox.getRecord().get('FormattedID');
console.log('selectedsaga',selectedID);
var myFilters=[
{
属性:“FormattedID”,
操作:'=',
值:selectedID
},
];
if(this.myStore){
log('storeexists');
this.myStore.setFilter(myFilters);
this.myStore.load();
//创建存储
}否则{
this.myStore=Ext.create('Rally.data.wsapi.Store'{
型号:“PortfolioItem/Saga”,
自动加载:对,
获取:['FormattedID','Name','Children','Release','State','PercentDoneByStoryPlanEstimate','PercentDoneByStoryCount','Project','Owner'],
过滤器:myFilters,
听众:{
加载:这个。加载后,
范围:本
}
});
}
},
_onDataLoaded:函数(存储、数据){
var特征=[];
var pendingstories=data.length;
Ext.Array.each(数据、函数(功能){
变量f={
FormattedID:feature.get('FormattedID'),
名称:feature.get('Name'),
_ref:feature.get(“\u ref”),
State:(feature.get('State')&&feature.get('State').Name)|,
项目:(feature.get('Project')&&feature.get('Project').Name)|,
所有者:(feature.get('Owner')&&feature.get('Owner')。_-reobjectname)| |'无所有者',
PercentDoneByStoryPlanEstimate:Math.floor(feature.get('PercentDoneByStoryPlanEstimate')*100)+'%,
PercentDoneByStoryCount:Math.floor(feature.get('PercentDoneByStoryCount')*100)+'%',
儿童:[],
计划总小时数:0,
剩余总小时数:0,
完成的总小时数:0,
百分比:0
};
var sagaFeatures=feature.getCollection('Children');
sagaFeatures.load({
获取:['FormattedID','Parent','UserStories','DirectChildrenCount'],
回调:函数(记录、操作、成功){
Ext.Array.each(记录,函数(子){
变量s={
FormattedID:child.get('FormattedID'),
};
if(child.get('DirectChildrenCount')>0){
child.getCollection('UserStories').load({
获取:['FormattedID','TaskEstimateTotal','TaskRemainingTotal'],
回调:函数(记录、操作、成功){
Ext.Array.each(记录、函数(us){
f、 totalPlannedHours+=us.get('TaskEstimateTotal');
f、 totalHoursRemaining+=us.get('TaskRemainingTotal');
f、 totalHoursCompleted=f.totalPlannedHours-f.TotalHours剩余
console.log(“完成的总小时数”,f.TotalHours剩余时间)
f、 百分比=数学楼层((f.总完工小时数/f.总计划小时数)*100)+'%;
},这个);
},范围:本});
}
this.myStore = Ext.create('Rally.data.wsapi.Store', {
model: 'UserStory',
autoLoad: true,
limit: Infinity,
pageSize: 2000,
fetch: ['FormattedID', 'Name', 'TaskEstimateTotal', 'TaskRemainingTotal', 'Feature', 'Parent', 'Children'],
filters: [{ property: 'Feature.Parent', operator: '=', value: this.searchComboBox.getRecord().get('_ref')}],
listeners: {
load: this._onDataLoaded,
scope: this
}
});
_onDataLoaded: function(store) {
var sagas = {};
var stories = store.getRange();
_.each(stories, function(story) {
var feature = store.get('Feature'),
saga = feature.Parent;
if (!sagas[saga._ref]) {
sagas[saga._ref] = Ext.clone(saga);
}
var featureChildren = sagas[saga._ref].Children.values = sagas[saga._ref].Children.values || {};
if (!featureChildren[feature._ref]) {
featureChildren[feature._ref] = Ext.clone(feature);
}
var storyChildren = featureChildren[feature._ref].UserStories.values = featureChildren[feature._ref].UserStories.values || {};
if (!storyChildren[story._ref]) {
storyChildren[story._ref] = Ext.clone(story);
}
});
//now you have an object of sagas, just needs to be an array for your custom store data
var customStoreData = _.values(sagas);
this._createGrid(customStoreData);
}