How can I create reports in Rally for Test cases executed based on Iteration? I am not able to create those reports from Reports tab in Rally.
Test Cases do not have an Iteration field use in a query. Test Cases are scheduled to an iteration indirectly via Test Sets. Here is an app that builds two grids: a grid of stories and a grid of test sets based on a selection in the iteration dropdown. The TestSet grid has a column that lists Test Cases associated with a given Test Set. Copy and paste the html code below into a custom page.
<!DOCTYPE html>
<html>
<head>
<title>Stories and TestSets by Iteration</title>
<script type="text/javascript" src="/apps/2.0rc1/sdk.js"></script>
<script type="text/javascript">
Rally.onReady(function () {
Ext.define('CustomApp', {
extend: 'Rally.app.TimeboxScopedApp',
componentCls: 'app',
scopeType: 'iteration',
addContent: function() {
var panel = Ext.create('Ext.panel.Panel', {
width: 1200,
layout: 'column',
itemId: 'parentPanel',
componentCls: 'panel',
items: [
{
xtype: 'panel',
title: 'Stories',
itemId: 'childPanel1',
columnWidth: 0.3
},
{
xtype: 'panel',
title: 'Test Sets with Test Cases',
itemId: 'childPanel2',
columnWidth: 0.7
}
]
});
this.add(panel);
this._makeStore();
},
onScopeChange: function() {
console.log('onScopeChange');
this._makeStore();
},
_makeStore: function(){
var storyStore = Ext.create('Rally.data.WsapiDataStore', {
model: 'UserStory',
fetch: ['FormattedID','Name'],
pageSize: 100,
autoLoad: true,
filters: [this.getContext().getTimeboxScope().getQueryFilter()],
listeners: {
load: this._onStoriesLoaded,
scope: this
}
});
},
_onStoriesLoaded: function(store, data){
var userStories = [];
Ext.Array.each(data, function(story) {
var s = {
FormattedID: story.get('FormattedID'),
_ref: story.get("_ref"),
Name: story.get('Name'),
};
userStories.push(s);
});
this._createStoryGrid(userStories);
},
_createStoryGrid:function(stories){
var that = this;
var storyStore = Ext.create('Rally.data.custom.Store', {
data: stories,
pageSize: 100
});
if (!this.down('#storygrid')) {
this.down('#childPanel1').grid = this.down('#childPanel1').add({
xtype: 'rallygrid',
itemId: 'storygrid',
store: storyStore,
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'Name', dataIndex: 'Name',flex:2
}
],
listeners: {
render: this._makeAnotherStore,
scope: this
}
});
}else{
this.down('#childPanel1').grid.reconfigure(storyStore);
this._makeAnotherStore(this);
}
},
_makeAnotherStore: function(){
Ext.create('Rally.data.WsapiDataStore', {
model: 'TestSet',
fetch: ['FormattedID', 'TestCases', 'TestCaseStatus'],
pageSize: 100,
autoLoad: true,
filters: [this.getContext().getTimeboxScope().getQueryFilter()],
listeners: {
load: this._onTestSetsLoaded,
scope: this
}
});
},
_onTestSetsLoaded: function(store, data){
var testSets = [];
var pendingTestCases = data.length;
console.log(data.length);
if (data.length ===0) {
this._createTestSetGrid(testSets); //to force refresh on testset grid when there are no testsets in the iteration
}
Ext.Array.each(data, function(testset){
var ts = {
FormattedID: testset.get('FormattedID'),
_ref: testset.get('_ref'), //required to make FormattedID clickable
TestCaseStatus: testset.get('TestCaseStatus'),
TestCaseCount: testset.get('TestCases').Count,
TestCases: []
};
var testCases = testset.getCollection('TestCases');
testCases.load({
fetch: ['FormattedID'],
callback: function(records, operation, success){
Ext.Array.each(records, function(testcase){
ts.TestCases.push({_ref: testcase.get('_ref'),
FormattedID: testcase.get('FormattedID')
});
}, this);
--pendingTestCases;
if (pendingTestCases === 0) {
this._createTestSetGrid(testSets);
}
},
scope: this
});
testSets.push(ts);
},this);
},
_createTestSetGrid: function(testsets) {
var testSetStore = Ext.create('Rally.data.custom.Store', {
data: testsets,
pageSize: 100,
});
if (!this.down('#testsetgrid')) {
this.down('#childPanel2').grid = this.down('#childPanel2').add({
xtype: 'rallygrid',
itemId: 'testsetgrid',
store: testSetStore,
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'Test Case Count', dataIndex: 'TestCaseCount',
},
{
text: 'Test Case Status', dataIndex: 'TestCaseStatus',flex:1
},
{
text: 'TestCases', dataIndex: 'TestCases',flex:1,
renderer: function(value) {
var html = [];
Ext.Array.each(value, function(testcase){
html.push('' + testcase.FormattedID + '')
});
return html.join(', ');
}
}
]
});
}else{
this.down('#childPanel2').grid.reconfigure(testSetStore);
}
}
});
Rally.launchApp('CustomApp', {
name:"Stories and TestSets by Iteration",
//parentRepos:""
});
});
</script>
<style type="text/css">
.app {
/* Add app styles here */
}
.panel{
left: 15%
}
</style>
</head>
<body></body>
</html>
Related
I am trying to figure out a way to have a Rally summary page displaying all test sets per the globally chosen project and particulary the exact numbers of pass/totals per test set.
I can display this using TestCaseStatus but the strings returned are not what I want. I read through some posts and it seems that the only way to get this kind of details is to iterate through all test set test cases and check if they are passing or not on the client side; also to count them up.
Can anyone provide a working example of how to iterate through the test set test cases and count their last verdict BUT only for the current project not the last verdict in general?
An example of a custom add that displays a grid of Test Sets based on a Project selection and then displays a grid of associated test cases and their test case results when a user double clicks on a row in a first grid is available in this github repo. You may copy the html file to a custom html page you create for this purpose.
Here is a js file:
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function() {
var panel = Ext.create('Ext.panel.Panel', {
layout: 'hbox',
itemId: 'parentPanel',
componentCls: 'panel',
items: [
{
xtype: 'rallyprojectpicker',
fieldLabel: 'select project',
listeners:{
change: function(combobox){
if ( this.down('#g')) {
console.log('grid exists');
Ext.getCmp('g').destroy();
console.log('grid deleted');
}
this.onProjectSelected(combobox.getSelectedRecord());
},
scope: this
}
},
{
xtype: 'panel',
title: 'Test Sets',
itemId: 'childPanel1'
},
{
xtype: 'panel',
title: 'Test Cases',
width: 600,
itemId: 'childPanel2'
}
],
});
this.add(panel);
},
onProjectSelected:function(record){
var project = record.data['_ref'];
console.log('project', project);
var testSetStore = Ext.create('Rally.data.WsapiDataStore', {
model: 'TestSet',
fetch: ['FormattedID','Name', 'Project', 'TestCaseStatus', 'TestCases'],
pageSize: 100,
autoLoad: true,
filters: [
{
property: 'Project',
value: project
}
],
listeners: {
load: this.onTestSetsLoaded,
scope: this
}
});
},
onTestSetsLoaded:function(store, data){
var testSets = [];
Ext.Array.each(data, function(testset) {
var ts = {
FormattedID: testset.get('FormattedID'),
_ref: testset.get('_ref'),
Name: testset.get('Name'),
TestCaseCount: testset.get('TestCases').Count,
TestCaseStatus: testset.get('TestCaseStatus')
};
testSets.push(ts);
});
this.updateGrid(testSets);
},
updateGrid: function(testSets){
if (this.down('#g2')) {
console.log('g2 exists');
var store = this.down('#g2').getStore();
store.removeAll();
}
var store = Ext.create('Rally.data.custom.Store', {
data: testSets,
pageSize: 100
});
if (!this.down('#g')) {
this.createGrid(store);
}
else{
this.down('#g').reconfigure(store);
}
},
createGrid: function(store){
console.log("load grid", store);
var that = this;
var g = Ext.create('Rally.ui.grid.Grid', {
id: 'g',
store: store
});
var g = Ext.create('Rally.ui.grid.Grid', {
id: 'g',
store: store,
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'Name', dataIndex: 'Name'
},
{
text: 'Test Case Count', dataIndex: 'TestCaseCount',
},
{
text: 'TestCaseStatus', dataIndex: 'TestCaseStatus'
}
],
listeners: {
celldblclick: function( grid, td, cellIndex, record, tr, rowIndex){
var id = grid.getStore().getAt(rowIndex).get('FormattedID');
console.log('id', id);
that.getTestCases(id);
}
}
});
this.down('#childPanel1').add(g);
},
getTestCases:function(id){
var selectedTestSetStore = Ext.create('Rally.data.WsapiDataStore', {
model: 'TestSet',
fetch: ['FormattedID','Name', 'TestCases'],
pageSize: 100,
autoLoad: true,
filters: [
{
property: 'FormattedID',
operator: '=',
value: id
}
],
listeners: {
load: this.onSelectedTestSetLoaded,
scope: this
}
});
},
onSelectedTestSetLoaded:function(store, data){
console.log('store',store);
console.log('data',data);
var selectedTestSets = [];
var pendingTestCases = data.length;
if (data.length ===0) {
this.createTestSetGrid(selectedTestSets);
}
Ext.Array.each(data, function(selectedTestset){
var ts = {
FormattedID: selectedTestset.get('FormattedID'),
TestCaseCount: selectedTestset.get('TestCases').Count,
TestCases: [],
ResultCount: 0
};
var testCases = selectedTestset.getCollection('TestCases', {fetch: ['FormattedID','ObjectID', 'Results']});
console.log('testCases:', selectedTestset.get('TestCases').Count, testCases);
testCases.load({
callback: function(records, operation, success){
Ext.Array.each(records, function(testcase){
console.log("testcase.get('FormattedID')", testcase.get('FormattedID'));
console.log("testcase.get('Results').Count", testcase.get('Results').Count);
ts.ResultCount = testcase.get('Results').Count;
console.log('ts.ResultCount', ts.ResultCount);
ts.TestCases.push({_ref: testcase.get('_ref'),
FormattedID: testcase.get('FormattedID'),
ObjectID: testcase.get('ObjectID')
});
}, this);
--pendingTestCases;
if (pendingTestCases === 0) {
this.makeTestCaseStore(ts.TestCases);
}
},
scope: this
});
console.log('ts', ts);
selectedTestSets.push(ts);
},this);
},
makeTestCaseStore:function(testcases){
console.log('makeTestCaseStore'); //ok
if (testcases.length>0) {
var idArray = [];
_.each(testcases, function(testcase){
console.log(testcase);
console.log('OID', testcase['ObjectID']);
idArray.push(testcase['ObjectID']);
});
console.log('idArray',idArray);
var filterArray = [];
_.each(idArray, function(id){
filterArray.push(
{
property: 'ObjectID',
value:id
}
)
});
console.log('filterArray', filterArray); //ok
var filters = Ext.create('Rally.data.QueryFilter', filterArray[0]);
filterArray = _.rest(filterArray,1);
_.each(filterArray, function(filter){
filters = filters.or(filter)
},1);
var testCaseStore = Ext.create('Rally.data.WsapiDataStore', {
model: 'TestCase',
fetch: ['FormattedID','Name', 'ObjectID', 'Results'],
pageSize: 100,
autoLoad: true,
filters: [filters],
listeners: {
load: this.onTestCasesLoaded,
scope: this
}
});
}
else{
if (this.down('#g2')) {
var store = this.down('#g2').getStore();
store.removeAll();
}
}
},
onTestCasesLoaded:function(store,data){
console.log('onTestCasesLoaded');
console.log('store',store);
console.log('data',data);
var testCases = [];
var pendingResults = data.length;
Ext.Array.each(data, function(testcase) {
var tc = {
FormattedID: testcase.get('FormattedID'),
_ref: testcase.get('_ref'),
Name: testcase.get('Name'),
ResultsCount: testcase.get('Results').Count,
Results: []
};
var results = testcase.getCollection('Results');
results.load({
fetch: ['Verdict','Date','Build'],
callback: function(records, operation, success){
Ext.Array.each(records, function(result){
tc.Results.push({_ref: result.get('_ref'),
Verdict: result.get('Verdict'),
Date: result.get('Date'),
Build: result.get('Build'),
});
},this);
--pendingResults;
if (pendingResults === 0) {
this.updateGrid2(testCases);
}
},
scope:this
});
testCases.push(tc);
}, this);
},
updateGrid2: function(testCases){
console.log(testCases);
var store = Ext.create('Rally.data.custom.Store', {
data: testCases,
pageSize: 100
});
if (!this.down('#g2')) {
this.createGrid2(store);
}
else{
this.down('#g2').reconfigure(store);
}
},
createGrid2: function(store){
console.log("load grid", store);
var that = this;
var g2 = Ext.create('Rally.ui.grid.Grid', {
id: 'g2',
store: store,
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'Name', dataIndex: 'Name',
},
{
text: 'Results Count', dataIndex: 'ResultsCount',
},
{
text: 'Results', dataIndex: 'Results', flex:1,
renderer: function(value) {
var html = [];
Ext.Array.each(value, function(result){
html.push('<b>Verdict:</b> ' + result.Verdict + '<br />' + '<b>Date:</b> ' + Rally.util.DateTime.toIsoString(result.Date,true) + '<br />' + '<b>Build</b> ' + result.Build + '<br />')
});
return html.join('<br /><br />');
}
}
]
});
this.down('#childPanel2').add(g2);
}
});
I have a test set that contains more than 200 test cases. I am trying to fetch all the test cases using the code below. However none of the config works
testSet.getCollection('TestCases').load({
limit: Infinity,
scope:this,
callback: function(testCases, operation, success) {
}
});
You can also try passing your config to the getCollection method instead. I think there are some bugs around directly passing them into load. I've had good luck doing something like this:
testSet.getCollection('TestCases', {
limit: Infinity,
autoLoad: true
listeners: {
load: function(store, records) {
//process testcases
},
scope: this
}
});
Here is a code example that builds a grid of test sets with associated test cases. TestSets are filtered by Release:
Ext.define('CustomApp', {
extend: 'Rally.app.TimeboxScopedApp',
componentCls: 'app',
scopeType: 'release',
addContent: function() {
this._makeStore();
},
onScopeChange: function() {
this._makeStore();
},
_makeStore: function(){
Ext.create('Rally.data.WsapiDataStore', {
model: 'TestSet',
fetch: ['FormattedID', 'TestCases', 'TestCaseStatus'],
pageSize: 100,
autoLoad: true,
filters: [this.getContext().getTimeboxScope().getQueryFilter()],
listeners: {
load: this._onTestSetsLoaded,
scope: this
}
});
},
_onTestSetsLoaded: function(store, data){
var testSets = [];
var pendingTestCases = data.length;
console.log(data.length);
if (data.length ===0) {
this._createTestSetGrid(testSets);
}
Ext.Array.each(data, function(testset){
var ts = {
FormattedID: testset.get('FormattedID'),
_ref: testset.get('_ref'), //required to make FormattedID clickable
TestCaseStatus: testset.get('TestCaseStatus'),
TestCaseCount: testset.get('TestCases').Count,
TestCases: []
};
var testCases = testset.getCollection('TestCases');
testCases.load({
fetch: ['FormattedID'],
callback: function(records, operation, success){
Ext.Array.each(records, function(testcase){
ts.TestCases.push({_ref: testcase.get('_ref'),
FormattedID: testcase.get('FormattedID')
});
}, this);
--pendingTestCases;
if (pendingTestCases === 0) {
this._createTestSetGrid(testSets);
}
},
scope: this
});
testSets.push(ts);
},this);
},
_createTestSetGrid: function(testsets) {
var testSetStore = Ext.create('Rally.data.custom.Store', {
data: testsets,
pageSize: 100,
});
if (!this.down('#testsetgrid')) {
this.grid = this.add({
xtype: 'rallygrid',
itemId: 'testsetgrid',
store: testSetStore,
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'Test Case Count', dataIndex: 'TestCaseCount',
},
{
text: 'Test Case Status', dataIndex: 'TestCaseStatus',flex:1
},
{
text: 'TestCases', dataIndex: 'TestCases',flex:1,
renderer: function(value) {
var html = [];
Ext.Array.each(value, function(testcase){
html.push('' + testcase.FormattedID + '')
});
return html.join(', ');
}
}
]
});
}else{
this.grid.reconfigure(testSetStore);
}
}
});
I'm searching for Rally custom html to show all test cases in test sets associated with a specific release. Along with each test case should be shown its most recent result - but only those results from any of the test sets associated with the specified release. If a test case has no result in any of the test sets associated with the release it should still be listed, and shown as having no result.
Because we run releases in parallel, I can't use iteration dates falling within release start and end dates as a way to identify which test sets and/or results are relevant to the release. This is the case in some of Rally's RQM toolkit examples.
Although it can be achieved by doing "Track - Release Status" and clicking Test Cases its too many clicks, and the testsets are many pages through the list of stories and defects and also that view can't be contained in a higher level dashboard.
Any help appreciated.
Thanks,
Andy
Here is an example you may start with. This AppSDK2 app builds two grids: stories and test sets filtered by release. The Test Set gird shows associated test cases and test case status. The html source code can be copied to HTML section of a custom page in Rally. The js source file is in this GitHub repo.
<!DOCTYPE html>
<html>
<head>
<title>Stories and TestSets by Release</title>
<script type="text/javascript" src="/apps/2.0rc1/sdk.js"></script>
<script type="text/javascript">
Rally.onReady(function () {
Ext.define('CustomApp', {
extend: 'Rally.app.TimeboxScopedApp',
componentCls: 'app',
scopeType: 'release',
addContent: function() {
var panel = Ext.create('Ext.panel.Panel', {
width: 1200,
layout: 'column',
itemId: 'parentPanel',
componentCls: 'panel',
items: [
{
xtype: 'panel',
title: 'Stories',
itemId: 'childPanel1',
columnWidth: 0.3
},
{
xtype: 'panel',
title: 'Test Sets with Test Cases',
itemId: 'childPanel2',
columnWidth: 0.7
}
]
});
this.add(panel);
this._makeStore();
},
onScopeChange: function() {
console.log('onScopeChange');
this._makeStore();
},
_makeStore: function(){
var storyStore = Ext.create('Rally.data.WsapiDataStore', {
model: 'UserStory',
fetch: ['FormattedID','Name'],
pageSize: 100,
autoLoad: true,
filters: [this.getContext().getTimeboxScope().getQueryFilter()],
listeners: {
load: this._onStoriesLoaded,
scope: this
}
});
},
_onStoriesLoaded: function(store, data){
var userStories = [];
Ext.Array.each(data, function(story) {
var s = {
FormattedID: story.get('FormattedID'),
_ref: story.get("_ref"),
Name: story.get('Name'),
};
userStories.push(s);
});
this._createStoryGrid(userStories);
},
_createStoryGrid:function(stories){
var that = this;
var storyStore = Ext.create('Rally.data.custom.Store', {
data: stories,
pageSize: 100
});
if (!this.down('#storygrid')) {
this.down('#childPanel1').grid = this.down('#childPanel1').add({
xtype: 'rallygrid',
itemId: 'storygrid',
store: storyStore,
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'Name', dataIndex: 'Name',flex:2
}
],
listeners: {
render: this._makeAnotherStore,
scope: this
}
});
}else{
this.down('#childPanel1').grid.reconfigure(storyStore);
this._makeAnotherStore(this);
}
},
_makeAnotherStore: function(){
Ext.create('Rally.data.WsapiDataStore', {
model: 'TestSet',
fetch: ['FormattedID', 'TestCases', 'TestCaseStatus'],
pageSize: 100,
autoLoad: true,
filters: [this.getContext().getTimeboxScope().getQueryFilter()],
listeners: {
load: this._onTestSetsLoaded,
scope: this
}
});
},
_onTestSetsLoaded: function(store, data){
var testSets = [];
var pendingTestCases = data.length;
console.log(data.length);
if (data.length ===0) {
this._createTestSetGrid(testSets);
}
Ext.Array.each(data, function(testset){
var ts = {
FormattedID: testset.get('FormattedID'),
_ref: testset.get('_ref'),
TestCaseStatus: testset.get('TestCaseStatus'),
TestCaseCount: testset.get('TestCases').Count,
TestCases: []
};
var testCases = testset.getCollection('TestCases');
testCases.load({
fetch: ['FormattedID'],
callback: function(records, operation, success){
Ext.Array.each(records, function(testcase){
ts.TestCases.push({_ref: testcase.get('_ref'),
FormattedID: testcase.get('FormattedID')
});
}, this);
--pendingTestCases;
if (pendingTestCases === 0) {
this._createTestSetGrid(testSets);
}
},
scope: this
});
testSets.push(ts);
},this);
},
_createTestSetGrid: function(testsets) {
var testSetStore = Ext.create('Rally.data.custom.Store', {
data: testsets,
pageSize: 100,
});
if (!this.down('#testsetgrid')) {
this.down('#childPanel2').grid = this.down('#childPanel2').add({
xtype: 'rallygrid',
itemId: 'testsetgrid',
store: testSetStore,
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'Test Case Count', dataIndex: 'TestCaseCount',
},
{
text: 'Test Case Status', dataIndex: 'TestCaseStatus',flex:1
},
{
text: 'TestCases', dataIndex: 'TestCases',flex:1,
renderer: function(value) {
var html = [];
Ext.Array.each(value, function(testcase){
html.push('' + testcase.FormattedID + '')
});
return html.join(', ');
}
}
]
});
}else{
this.down('#childPanel2').grid.reconfigure(testSetStore);
}
}
});
Rally.launchApp('CustomApp', {
name:"Stories and TestSets by Release",
//parentRepos:""
});
});
</script>
<style type="text/css">
.app {
/* Add app styles here */
}
.panel{
left: 15%
}
</style>
</head>
<body></body>
</html>
I am trying to make a grid that displays both features a rollups - (the rollup and its children).
The individual first query works when I set the model of the grid to "PortfolioItem/Feature", but as soon as I change the model to just "PortfolioItem" it the grid does not display any data - and adding the OR to the filter certainly does not help matters.
var filter = Ext.create('Rally.data.QueryFilter', {
property: 'Parent.ObjectID',
operator: '=',
value: id
});
filter = filter.or({
property: 'ObjectID',
operator: '=',
value: id
});
Am I going about this in the wrong way? I know I have made a grid of Features and Rollups before using the PortfolioItem model, but I that was filtering based on start and end dates.
Here is a simple grid that displays all Portfolio Item types, Themes, Initiatives, Features, where
model: 'PortfolioItem'
If I fetch an attribute specific to only one PI type, e.g. UserStories on PortfolioItem/Feature:
fetch: ['FormattedID','Name']
and have something like this in the code:
StoryCount: feature.get('UserStories').Count
I will see the same outcome that you report, when the grid does not display any data.
<!DOCTYPE html>
<html>
<head>
<title>PIGridExample</title>
<script type="text/javascript" src="/apps/2.0rc1/sdk.js"></script>
<script type="text/javascript">
Rally.onReady(function () {
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function() {
Ext.create('Rally.data.WsapiDataStore', {
model: 'PortfolioItem',
fetch: ['FormattedID','Name'],
pageSize: 100,
autoLoad: true,
listeners: {
load: this._onDataLoaded,
scope: this
}
});
},
_onDataLoaded: function(store, data) {
var records = [];
Ext.Array.each(data, function(record) {
records.push({
Name: record.get('Name'),
FormattedID: record.get('FormattedID'),
});
});
this.add({
xtype: 'rallygrid',
store: Ext.create('Rally.data.custom.Store', {
data: records
}),
columnCfgs: [
{
text: 'Name', dataIndex: 'Name', flex: 1
},
{
text: 'FormattedID', dataIndex: 'FormattedID'
}
]
});
}
});
Rally.launchApp('CustomApp', {
name:"PIGridExample"
//parentRepos:""
});
});
</script>
<style type="text/css">
.app {
/* Add app styles here */
}
</style>
</head>
<body></body>
</html>
Here is an example of an app with a grid of Features and their user stores:
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function() {
Ext.create('Rally.data.WsapiDataStore', {
model: 'PortfolioItem/Feature',
fetch: ['FormattedID','Name','UserStories'],
pageSize: 100,
autoLoad: true,
listeners: {
load: this._onDataLoaded,
scope: this
}
});
},
_createGrid: function(features) {
this.add({
xtype: 'rallygrid',
store: Ext.create('Rally.data.custom.Store', {
data: features,
pageSize: 100
}),
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'Name', dataIndex: 'Name'
},
{
text: 'Story Count', dataIndex: 'StoryCount'
},
{
text: 'User Stories', dataIndex: 'UserStories',
renderer: function(value) {
var html = [];
Ext.Array.each(value, function(userstory){
html.push('' + userstory.FormattedID + '')
});
return html.join(', ');
}
}
]
});
},
_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"),
StoryCount: feature.get('UserStories').Count,
UserStories: []
};
var stories = feature.getCollection('UserStories');
stories.load({
fetch: ['FormattedID'],
callback: function(records, operation, success){
Ext.Array.each(records, function(story){
f.UserStories.push({_ref: story.get('_ref'),
FormattedID: story.get('FormattedID')
});
}, this);
--pendingstories;
if (pendingstories === 0) {
this._createGrid(features);
}
},
scope: this
});
features.push(f);
}, this);
}
});
I wrote an app that builds a grid of User Stories filtered by Iteration. It seems to work, and there are no errors but the list of Stories is incomplete. Some Iterations have half of the stories missing in the grid. All Stories are in the same Project. What am I doing wrong? If you have suggestions on how to improve the code below please let me know. Thanks!
<!DOCTYPE html>
<html>
<head>
<title>StoriesByIteration</title>
<script type="text/javascript" src="/apps/2.0rc1/sdk.js"></script>
<script type="text/javascript">
Rally.onReady(function () {
Ext.define('CustomApp', {
extend: 'Rally.app.TimeboxScopedApp',
componentCls: 'app',
scopeType: 'iteration',
comboboxConfig: {
labelWidth: 100,
width: 300
},
addContent: function() {
this._makeStore();
},
onScopeChange: function() {
this._makeStore();
},
_makeStore: function(){
Ext.create('Rally.data.WsapiDataStore', {
model: 'UserStory',
fetch: ['FormattedID','Name'],
autoLoad: true,
filters: [this.getContext().getTimeboxScope().getQueryFilter()],
listeners: {
load: this._onDataLoaded,
scope: this
}
});
},
_onDataLoaded: function(store, data){
var stories = [];
Ext.Array.each(data, function(story) {
var s = {
FormattedID: story.get('FormattedID'),
Name: story.get('Name'),
};
this._createGrid(stories);
stories.push(s);
}, this);
},
_createGrid: function(stories) {
var myStore = Ext.create('Rally.data.custom.Store', {
data: stories,
pageSize: 100,
});
if (!this.grid) {
this.grid = this.add({
xtype: 'rallygrid',
store: myStore,
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'Name', dataIndex: 'Name'
}
]
});
} else {
this.grid.reconfigure(myStore);
}
}
});
Rally.launchApp('CustomApp', {
name:"StoriesByIteration",
});
});
</script>
<style type="text/css">
.app { }
</style>
</head>
<body></body>
</html>
You need to move
this._createGrid(stories);
outside of the loop:
_onDataLoaded: function(store, data){
var stories = [];
Ext.Array.each(data, function(story) {
var s = {
FormattedID: story.get('FormattedID'),
Name: story.get('Name'),
};
stories.push(s);
}, this);
this._createGrid(stories);
},