我正在尝试在 Rally 中创建自定义计划页面。我想限制我们分配给用户的任务数量。我可以轻松创建一个网格,列出每个用户的每个任务,但我不知道如何获得估计小时数的总和。
然后我尝试创建一个自定义报告,但该报告无法计算分配给一个人的任务小时数 - 即使我将任务标记给一个人并对其进行查询。
有任何想法吗?
我正在尝试在 Rally 中创建自定义计划页面。我想限制我们分配给用户的任务数量。我可以轻松创建一个网格,列出每个用户的每个任务,但我不知道如何获得估计小时数的总和。
然后我尝试创建一个自定义报告,但该报告无法计算分配给一个人的任务小时数 - 即使我将任务标记给一个人并对其进行查询。
有任何想法吗?
我刚刚开始使用 Rally,这正是我所追求的。恐怕我对添加自定义应用程序完全陌生,我已经尝试过,但没有呈现任何内容(我显然在某处做错了什么)
我根据nickm 的回答创建了以下 HTML,并想知道是否有人能指出我哪里出错了。
我假设我需要创建一个 div 来包含网格(以及一个包含迭代下拉列表的元素),但我不知道为它们分配什么 html id
我的完整html如下,我在脚本部分复制了nickm的代码。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>My App</title>
<!--App information-->
<meta name="Name" content="App: My App"/>
<meta name="Version" content="1.0"/>
<meta name="Vendor" content=""/>
<!--Include SDK-->
<script type="text/javascript" src="/apps/1.29/sdk.js"></script>
<!--App code-->
<script type="text/javascript">
function onLoad() {
//Add app code here
Ext.define('CustomApp', {
extend: 'Rally.app.TimeboxScopedApp',
componentCls: 'app',
scopeType: 'iteration',
comboboxConfig: {
fieldLabel: 'Select an Iteration:',
labelWidth: 100,
width: 300
},
addContent: function() {
this._makeStore();
},
_makeStore: function(){
Ext.create('Rally.data.WsapiDataStore', {
model: 'Task',
fetch: ['FormattedID','Name','Owner','Estimate'],
pageSize: 100,
autoLoad: true,
filters: [this.getContext().getTimeboxScope().getQueryFilter()],
listeners: {
load: this._onTasksLoaded,
scope: this
}
});
},
onScopeChange: function() {
this._makeStore();
},
_onTasksLoaded: function(store, data){
this.tasks = data;
Ext.create('Rally.data.WsapiDataStore', {
model: 'UserIterationCapacity',
fetch: ['User', 'TaskEstimates', 'Iteration', 'Capacity'],
pageSize: 100,
autoLoad: true,
filters: [this.getContext().getTimeboxScope().getQueryFilter()],
listeners: {
load: this._onAllDataLoaded,
scope: this
}
});
},
_onAllDataLoaded: function(store, data){
var tasks = [];
var users = [];
that = this
if (data.length ===0) {
this._createGrid(); //to refresh grid when no items in iteration
}
Ext.Array.each(this.tasks, function(task) {
var owner = task.get('Owner');
var total;
var cap;
Ext.Array.each(data, function(capacity){
//some tasks have no owner. If this condition is not checked Uncaught TypeError: Cannot read property '_refObjectName' of null
if (owner && capacity.get('User')._refObjectName === owner._refObjectName) {
total = capacity.get('TaskEstimates');
cap = capacity.get('Capacity');
}
});
var t = {
FormattedID: task.get('FormattedID'),
_ref: task.get("_ref"),
Name: task.get('Name'),
Estimate: task.get('Estimate'),
Owner: (owner && owner._refObjectName) || 'None',
Capacity: cap,
TaskEstimates: total
};
that._createGrid(tasks);
tasks.push(t);
});
},
_createGrid: function(stories) {
var myStore = Ext.create('Rally.data.custom.Store', {
data: stories,
pageSize: 100,
});
if (!this.grid) {
this.grid = this.add({
xtype: 'rallygrid',
itemId: 'mygrid',
store: myStore,
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'Name', dataIndex: 'Name'
},
{
text: 'Estimate', dataIndex: 'Estimate'
},
{
text: 'Owner', dataIndex: 'Owner'
},
{
text: 'User Iteration Capacity', dataIndex: 'Capacity'
},
{
text: 'Task Estimates Total', dataIndex: 'TaskEstimates'
}
]
});
}else{
this.grid.reconfigure(myStore);
}
}
});
}
rally.addOnLoad(onLoad);
</script>
<!--App styles-->
<style type="text/css">
.myApp {
/* Add app styles here */
}
</style>
</head>
<body class="myApp">
<div id="mygrid"></div>
</body>
</html>
非常感谢您的帮助
此自定义应用程序通过迭代构建任务网格,其中包括 UserIterationCapacity 数据,特别是 TaskEstimates 和 Capacity 的总和。
Ext.define('CustomApp', {
extend: 'Rally.app.TimeboxScopedApp',
componentCls: 'app',
scopeType: 'iteration',
comboboxConfig: {
fieldLabel: 'Select an Iteration:',
labelWidth: 100,
width: 300
},
addContent: function() {
this._makeStore();
},
_makeStore: function(){
Ext.create('Rally.data.WsapiDataStore', {
model: 'Task',
fetch: ['FormattedID','Name','Owner','Estimate'],
pageSize: 100,
autoLoad: true,
filters: [this.getContext().getTimeboxScope().getQueryFilter()],
listeners: {
load: this._onTasksLoaded,
scope: this
}
});
},
onScopeChange: function() {
this._makeStore();
},
_onTasksLoaded: function(store, data){
this.tasks = data;
Ext.create('Rally.data.WsapiDataStore', {
model: 'UserIterationCapacity',
fetch: ['User', 'TaskEstimates', 'Iteration', 'Capacity'],
pageSize: 100,
autoLoad: true,
filters: [this.getContext().getTimeboxScope().getQueryFilter()],
listeners: {
load: this._onAllDataLoaded,
scope: this
}
});
},
_onAllDataLoaded: function(store, data){
var tasks = [];
var users = [];
that = this
if (data.length ===0) {
this._createGrid(); //to refresh grid when no items in iteration
}
Ext.Array.each(this.tasks, function(task) {
var owner = task.get('Owner');
var total;
var cap;
Ext.Array.each(data, function(capacity){
//some tasks have no owner. If this condition is not checked Uncaught TypeError: Cannot read property '_refObjectName' of null
if (owner && capacity.get('User')._refObjectName === owner._refObjectName) {
total = capacity.get('TaskEstimates');
cap = capacity.get('Capacity');
}
});
var t = {
FormattedID: task.get('FormattedID'),
_ref: task.get("_ref"),
Name: task.get('Name'),
Estimate: task.get('Estimate'),
Owner: (owner && owner._refObjectName) || 'None',
Capacity: cap,
TaskEstimates: total
};
that._createGrid(tasks);
tasks.push(t);
});
},
_createGrid: function(stories) {
var myStore = Ext.create('Rally.data.custom.Store', {
data: stories,
pageSize: 100,
});
if (!this.grid) {
this.grid = this.add({
xtype: 'rallygrid',
itemId: 'mygrid',
store: myStore,
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'Name', dataIndex: 'Name'
},
{
text: 'Estimate', dataIndex: 'Estimate'
},
{
text: 'Owner', dataIndex: 'Owner'
},
{
text: 'User Iteration Capacity', dataIndex: 'Capacity'
},
{
text: 'Task Estimates Total', dataIndex: 'TaskEstimates'
}
]
});
}else{
this.grid.reconfigure(myStore);
}
}
});