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);
}
});
Related
Does anyone know an easy way to get a percent done by story hours related to 'Portfolioitem/Saga'? The existing portfolio items app in CA only shows percent done by story plan estimate and I'd like to get the task hours details as well.
I've written the following custom app to include the data but my table gets loaded before the data is returned. Please help!
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('' + child.FormattedID + '')
});
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);
}
}
});
Can you load the data from the bottom up instead and save yourself all the many nested loops and store loads?
I think you should be able to just create a store of stories beneath the selected saga...
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
}
});
And then just crunch your data into the appropriate data structure, bucketing them by Feature. Something like 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);
}
Something like that should get you close. You'll need to move around your code a little bit where you're calculating your rollup totals, and your renderer for the Children column will be a tiny bit different- just loop over _.values(value.values) instead.
I have a custom grid that displays open tasks filtered by (Owner = some-user#company.com).
I would like to include the last revision for each task in a custom grid, but the Revision column is not available on the settings dialog. How to traverse from Revision History to individual revisions?
It can't be done with a custom grid, but can be done with a custom code. Here is an app example that populates a grid based on a selection in the UserSearchComboBox , and then displays the last revision of a selected task on a click event.
You may copy the html file into a custom page from this github repo:
Here is the js file:
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function() {
var context = this.getContext ();
var currentProject = context.getProject()._ref;
var panel = Ext.create('Ext.panel.Panel', {
layout: 'hbox',
itemId: 'parentPanel',
componentCls: 'panel',
items: [
{
xtype: 'rallyusersearchcombobox',
fieldLabel: 'SELECT USER:',
project: currentProject,
listeners:{
ready: function(combobox){
this._onUserSelected(combobox.getRecord());
},
select: function(combobox){
if (this.down('#c').html !== 'No task is selected') {
Ext.getCmp('c').update('No task is selected');
}
this._onUserSelected(combobox.getRecord());
},
scope: this
}
},
{
xtype: 'panel',
title: 'Tasks',
width: 600,
itemId: 'childPanel1'
},
{
xtype: 'panel',
title: 'Last Revision',
width: 600,
itemId: 'childPanel2'
}
],
});
this.add(panel);
this.down('#childPanel2').add({
id: 'c',
padding: 10,
maxWidth: 600,
maxHeight: 400,
overflowX: 'auto',
overflowY: 'auto',
html: 'No task is selected'
});
},
_onUserSelected:function(record){
var user = record.data['_ref'];
if(user){
var filter = Ext.create('Rally.data.QueryFilter', {
property: 'Owner',
operator: '=',
value: user
});
filter = filter.and({
property: 'State',
operator: '<',
value: 'Completed'
});
filter.toString();
Ext.create('Rally.data.WsapiDataStore', {
model: 'Task',
fetch: [ 'DragAndDropRank','FormattedID','Name','State','RevisionHistory'],
autoLoad: true,
filters : [filter],
sorters:[
{
property: 'DragAndDropRank',
direction: 'ASC'
}
],
listeners: {
load: this._onTaskDataLoaded,
scope: this
}
});
}
},
_onTaskDataLoaded: function(store, data) {
var customRecords = [];
Ext.Array.each(data, function(task, index) {
customRecords.push({
_ref: task.get('_ref'),
FormattedID: task.get('FormattedID'),
Name: task.get('Name'),
RevisionID: Rally.util.Ref.getOidFromRef(task.get('RevisionHistory')),
});
}, this);
this._updateGrid(store,data);
},
_updateGrid: function(store, data){
if (!this.down('#g')) {
this._createGrid(store,data);
}
else{
this.down('#g').reconfigure(store);
}
},
_createGrid: function(store,data){
var that = this;
var g = Ext.create('Rally.ui.grid.Grid', {
id: 'g',
store: store,
enableRanking: true,
columnCfgs: [
{text: 'Formatted ID', dataIndex: 'FormattedID'},
{text: 'Name', dataIndex: 'Name'},
{text: 'State', dataIndex: 'State'},
{text: 'Last Revision',
renderer: function (v, m, r) {
var id = Ext.id();
Ext.defer(function () {
Ext.widget('button', {
renderTo: id,
text: 'see',
width: 50,
handler: function () {
console.log('r', r.data);
that._getRevisionHistory(data, r.data);
}
});
}, 50);
return Ext.String.format('<div id="{0}"></div>', id);
}
}
],
height: 400,
});
this.down('#childPanel1').add(g);
},
_getRevisionHistory: function(taskList, task) {
this._task = task;
this._revisionModel = Rally.data.ModelFactory.getModel({
type: 'RevisionHistory',
scope: this,
success: this._onModelCreated
});
},
_onModelCreated: function(model) {
model.load(Rally.util.Ref.getOidFromRef(this._task.RevisionHistory._ref),{
scope: this,
success: this._onModelLoaded
});
},
_onModelLoaded: function(record, operation) {
record.getCollection('Revisions').load({
fetch: true,
scope: this,
callback: function(revisions, operation, success) {
this._onRevisionsLoaded(revisions, record);
}
});
},
_onRevisionsLoaded: function(revisions, record) {
var lastRev = _.first(revisions).data;
console.log('_onRevisionsLoaded: ',lastRev.Description, lastRev.RevisionNumber, lastRev.CreationDate );
this._displayLastRevision(lastRev.Description,lastRev.RevisionNumber, lastRev.CreationDate );
},
_displayLastRevision:function(desc, num, date){
Ext.getCmp('c').update('<b>' + this._task.FormattedID + '</b><br/><b>Revision CreationDate: </b>' + date +'<br /><b>Description:</b>' + desc + '<br /><b>Revision Number:</b>' + num + '<br />');
}
});
When using a custom grid query based on defects I often filter based on the tags of the linked user story. I would like to use the actual feature hierarchy. e.g. Show all defects where the linked story is under a given feature or initiative. I could not work this out from looking at the documentation
Tags attribute exists on Artifact object from which Requirement inherits, hence Requirement.Tags can be traversed. Feature attribute does not exist on Requirement. It exists on HierarchicalRequirement, which inherits from Requirement, hence Requirement.Feature cannot be traversed.
In this context a custom grid may not be a suitable choice. But a custom app can be written that shows all those relationships. Here is a custom app that has two comboboxes: a Release and a Feature. The Feature combobox is populated based on the selection in the Release combobox. When a feature is selected from the second combobox a grid is populated with the feature's child stories and defects (if any) associated with those stories.
You may see the full code in this repo, and copy the html file into a custom page.
Here is the js file:
Ext.define('CustomApp', {
extend: 'Rally.app.TimeboxScopedApp',
componentCls: 'app',
scopeType: 'release',
comboboxConfig: {
fieldLabel: 'Select a Release:',
labelWidth: 100,
width: 300
},
addContent: function() {
this._makeCombobox();
},
onScopeChange: function() {
this._makeCombobox();
},
_makeCombobox: function() {
if (this.down('#features')) {
this.down('#features').destroy();
}
var features = Ext.create('Rally.ui.combobox.ComboBox',{
id: 'features',
storeConfig: {
model: 'PortfolioItem/Feature',
fetch: ['FormattedID','Name','Release', 'UserStories'],
pageSize: 100,
autoLoad: true,
filters: [this.getContext().getTimeboxScope().getQueryFilter()]
},
fieldLabel: 'select Feature',
listeners:{
ready: function(combobox){
if (combobox.getRecord()) {
console.log('ready',combobox.getRecord().get('_ref'));
this._onFeatureSelected(combobox.getRecord());
}
else{
console.log('selected release has no features');
if (this.down('#grid')) {
this.down('#grid').destroy();
}
}
},
select: function(combobox){
if (combobox.getRecord()) {
console.log('select',combobox.getRecord().get('_ref'));
this._onFeatureSelected(combobox.getRecord());
}
},
scope: this
}
});
this.add(features);
},
_onFeatureSelected:function(feature){
console.log('feature', feature.get('Name'));
var f = {
FormattedID: feature.get('FormattedID'),
Name: feature.get('Name'),
_ref: feature.get("_ref"),
UserStories: []
};
var collection = feature.getCollection('UserStories', {fetch: ['Name','FormattedID','Owner', 'Defects']});
var that = this;
var count = collection.getCount();
console.log(count);
var stories = [];
var pendingStories = count;
collection.load({
callback: function(records, operation, success){
Ext.Array.each(records, function(story){
var s = {
FormattedID: story.get('FormattedID'),
Name: story.get('Name'),
_ref: story.get("_ref"),
DefectCount: story.get('Defects').Count,
Defects: []
};
var defects = story.getCollection('Defects');
var defectcount = defects.getCount();
var pendingDefects = defectcount;
defects.load({
fetch: ['FormattedID'],
callback: function(records, operation, success){
Ext.Array.each(records, function(defect){
s.Defects.push({_ref: defect.get('_ref'),
FormattedID: defect.get('FormattedID')
});
}, this);
--pendingDefects;
if (pendingDefects === 0) {
console.log(story.get('FormattedID') + ' - ' + story.get('Name'));
--pendingStories;
if (pendingStories === 0) {
console.log('stories inside callback',stories);
}
}
console.log('makeGrid');
that._makeGrid(stories);
},
scope: this
});
stories.push(s);
}, this);
}
});
},
_makeGrid: function(stories) {
var c = Ext.create('Ext.Container', {
layout: {
type: 'absolute'
},
x: 400
});
this.add(c);
this._store = Ext.create('Rally.data.custom.Store', {
data: stories,
pageSize: 100,
remoteSort:false
});
if (!this.down('#grid')){
c.add({
xtype: 'rallygrid',
itemId: 'grid',
store: this._store,
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'Name', dataIndex: 'Name'
},
{
text: 'Defect Count', dataIndex: 'DefectCount'
},
{
text: 'Defects', dataIndex: 'Defects',
renderer: function(value) {
var html = [];
Ext.Array.each(value, function(defect){
html.push('' + defect.FormattedID + '')
});
return html.join(', ');
}
}
]
});
}
else{
this.down('#grid').reconfigure(this._store);
}
}
});
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 queried for a WSAPI DataStore of PortfolioItem/Feature. With each Feature object, I call getCollection('UserStories') to get the array of user stories that Feature. For each User Story I would like to grab the Owner information.
How do I get the actual User object from my reference to the Owner field that is available on the User Story object?
In addition to WsapiDataStore, a Rally.data.custom.Store is needed, where the owner is accessed like this:
Ext.Array.each(records, function(story){
f.UserStories.push({_ref: story.get('_ref'),
FormattedID: story.get('FormattedID'),
Owner: (story.get('Owner') && story.get('Owner')._refObjectName) || 'None'
});
}
Here is a code that builds a grid of Features with their children stories, and the Owners of the stories.
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(', ');
}
},
{
text: 'Owner', dataIndex: 'UserStories',
renderer: function(value) {
var html = [];
Ext.Array.each(value, function(userstory){
html.push(userstory.Owner);
});
return html.join(', ');
}
}
]
});
},
_onDataLoaded: function(store, data){
var features = [];
var pendingstories = data.length;
var owner;
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','Owner'],
callback: function(records, operation, success){
Ext.Array.each(records, function(story){
f.UserStories.push({_ref: story.get('_ref'),
FormattedID: story.get('FormattedID'),
Owner: (story.get('Owner') && story.get('Owner')._refObjectName) || 'None'
});
}, this);
--pendingstories;
if (pendingstories === 0) {
this._createGrid(features);
}
},
scope: this
});
features.push(f);
}, this);
}
});