Rally - array does not contain all pushed elements - rally

I have an app that I am trying to use but it seems that while iterating through arrays and pushing into another array.i.e combining the arrays into one is not working for me. Example - I see all 213 pushes to this array but when I check its contents they are less.
Here is the code that shows me incomplete array push list.
For 213 test cases test set only 67 are pushed and present in the array
that = this;
that._testSetTestList = [];
console.log('testsetdata',testsetdata);
Ext.Array.each(testsetdata, function(record) {
console.log('record.get(TestCases)',record.get('TestCases'));
Ext.Array.each(record.get('TestCases'), function(name, index) {
that._testSetTestList.push({
resID: name.FormattedID,
resName: name.Name,
resObject: name.ObjectID,
resSetID: record.get('FormattedID'),
resSet: record.get('Name'),
resSetObject: record.get('ObjectID'),
resSetProject: name.Project.ObjectID
});
console.log('_testSetTestList.push',{
resID: name.FormattedID
});
});
});
Can anyone guide me to what I am doing wrong if anything.

Try using this code instead:
this._testSetTestList = Ext.Array.flatten(Ext.Array.map(testsetdata, function(record) {
return Ext.Array.map(record.get('TestCases'), function(name, index) {
return {
resID: name.FormattedID,
resName: name.Name,
resObject: name.ObjectID,
resSetID: record.get('FormattedID'),
resSet: record.get('Name'),
resSetObject: record.get('ObjectID'),
resSetProject: name.Project.ObjectID
};
});
}))

The issue in my case was not the code but the scope..I was trying to get the test case results for test cases that were not directly reachable in the project tree. We have the test cases residing in several projects but then we use them in test sets under different projects. If the test cases that are part of the queried test sets are directly reachable for the project for which we view this page, then test case results were accounted for BUT if the test cases were in projects that were siblings to the one that view the app from then the query could not find them and take their test case results. The solution was to consolidate all test cases under the correct project so that the app can access them from any required project.

Based on the example of using promises from this github repo, here is a code that builds a grid of test sets with their collection of test cases, where elements of array of test sets setsWithCases is pushed in to another array testsets, and the second array is used to populate the grid. The second array contains all elements of the first array. I am using LowDash _.each, included with AppSDK2.
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentsCls: 'app',
launch: function(){
var aStore = Ext.create('Rally.data.wsapi.Store', {
model: 'TestSet',
fetch: ['ObjectID', 'FormattedID', 'Name', 'TestCases'],
autoLoad: true,
context:{
projectScopeUp: false,
projectScopeDown: false
},
listeners:{
scope: this,
load: this._onStoreLoaded
}
});
},
_onStoreLoaded: function(store, records){
var setsWithCases = [];
var testsets = [];
var that = this;
var promises = [];
_.each(records, function(testset){
promises.push(that._getTestCases(testset, that));
});
Deft.Promise.all(promises).then({
success: function(results) {
_.each(results, function(result) {
if (result.TestCases.length > 0) {
setsWithCases.push(result);
}
});
_.each(setsWithCases, function(testset){
testsets.push(testset);
});
that._makeGrid(testsets);
}
});
},
_getTestCases:function(testset, scope){
var deferred = Ext.create('Deft.Deferred');
var that = scope;
var testcases = [];
var result = {};
var testsetRef = testset.get('_ref');
var testsetObjectID = testset.get('ObjectID');
var testsetFormattedID = testset.get('FormattedID');
var testsetName = testset.get('Name');
var testcaseCollection = testset.getCollection("TestCases", {fetch: ['Name', 'FormattedID']});
var testcaseCount = testcaseCollection.getCount();
testcaseCollection.load({
callback: function(records, operation, success){
_.each(records, function(testcase){
testcases.push(testcase);
});
result = {
"_ref": testsetRef,
"ObjectID": testsetObjectID,
"FormattedID": testsetFormattedID,
"Name": testsetName,
"TestCases": testcases
};
deferred.resolve(result);
}
});
return deferred;
},
_makeGrid:function(testsets){
var that = this;
var gridStore = Ext.create('Rally.data.custom.Store', {
data: testsets,
pageSize: 1000,
remoteSort: false
});
var aGrid = Ext.create('Rally.ui.grid.Grid', {
itemId: 'testsetGrid',
store: gridStore,
columnCfgs:[
{
text: 'Formatted ID', dataIndex: 'FormattedID', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'Name', dataIndex: 'Name', flex: 1
},
{
text: 'TestCases', dataIndex: 'TestCases', flex:1,
renderer: function(value) {
var html = [];
_.each(value, function(testcase){
html.push('' + testcase.get('FormattedID') + '' + ' ' + testcase.get('Name'));
});
return html.join(', ');
}
}
]
});
that.add(aGrid);
}
});

Related

Using a custom Drop Down List field to set a value in a grid

I'm trying to use the Rally 2.1 SDK to set a custom data field (c_wsjf) in a grid. I have a custom drop down list that I want to check the value of (c_TimeCrticalitySizing).
I created c_TimeCrticalitySizing as a feature card field in my Rally workspace with different string values (such as "No decay"). Every drop down list value will set the custom field to a different integer. When I try to run the app in Rally I get this error:
"Uncaught TypeError: Cannot read property 'isModel' of undefined(…)"
I'm thinking the drop down list value may not be a string.
How would I check what the type of the drop down list value is?
How could I rewrite this code to correctly check the value of the drop down list so I can set my custom field to different integers?
Here's my code block for the complete app. I'm still trying to hook up a search bar so for now I directly call _onDataLoaded() from the launch() function.
// START OF APP CODE
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
featureStore: undefined,
featureGrid: undefined,
items: [ // pre-define the general layout of the app; the skeleton (ie. header, content, footer)
{
xtype: 'container', // this container lets us control the layout of the pulldowns; they'll be added below
itemId: 'widget-container',
layout: {
type: 'hbox', // 'horizontal' layout
align: 'stretch'
}
}
],
// Entry point of the app
launch: function() {
var me = this;
me._onDataLoaded();
},
_loadSearchBar: function() {
console.log('in loadsearchbar');
var me = this;
var searchComboBox = Ext.create('Rally.ui.combobox.SearchComboBox', {
itemId: 'search-combobox',
storeConfig: {
model: 'PortfolioItem/Feature'
},
listeners: {
ready: me._onDataLoaded,
select: me._onDataLoaded,
scope: me
}
});
// using 'me' here would add the combo box to the app, not the widget container
this.down('#widget-container').add(searchComboBox); // add the search field to the widget container <this>
},
// If adding more filters to the grid later, add them here
_getFilters: function(searchValue){
var searchFilter = Ext.create('Rally.data.wsapi.Filter', {
property: 'Search',
operation: '=',
value: searchValue
});
return searchFilter;
},
// Sets values once data from store is retrieved
_onDataLoaded: function() {
console.log('in ondataloaded');
var me = this;
// look up what the user input was from the search box
console.log("combobox: ", this.down('#search-combobox'));
//var typedSearch = this.down('#search-combobox').getRecord().get('_ref');
// search filter to apply
//var myFilters = this._getFilters(typedSearch);
// if the store exists, load new data
if (me.featureStore) {
//me.featureStore.setFilter(myFilters);
me.featureStore.load();
}
// if not, create it
else {
me.featureStore = Ext.create('Rally.data.wsapi.Store', {
model: 'PortfolioItem/Feature',
autoLoad: true,
listeners: {
load: me._createGrid,
scope: me
},
fetch: ['FormattedID', 'Name', 'TimeCriticality',
'RROEValue', 'UserBusinessValue', 'JobSize', 'c_TimeCriticalitySizing']
});
}
},
// create a grid with a custom store
_createGrid: function(store, data){
var me = this;
var records = _.map(data, function(record) {
//Calculations, etc.
console.log(record.get('c_TimeCriticalitySizing'));
var timecritsize = record.get('c_TimeCriticalitySizing');
//console.log(typeof timecritsize);
var mystr = "No decay";
var jobsize = record.get('JobSize');
var rroe = record.get('RROEValue');
var userval = record.get('UserBusinessValue');
var timecrit = record.get('TimeCriticality');
// Check that demoniator is not 0
if ( record.get('JobSize') > 0){
if (timecritsize === mystr){
var priorityScore = (timecrit + userval + rroe) / jobsize;
return Ext.apply({
c_wsjf: Math.round(priorityScore * 10) / 10
}, record.getData());
}
}
else{
return Ext.apply({
c_wsjf: 0
}, record.getData());
}
});
// Add the grid
me.add({
xtype: 'rallygrid',
showPagingToolbar: true,
showRowActionsColumn: true,
enableEditing: true,
store: Ext.create('Rally.data.custom.Store', {
data: records
}),
// Configure each column
columnCfgs: [
{
xtype: 'templatecolumn',
text: 'ID',
dataIndex: 'FormattedID',
width: 100,
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'WSJF Score',
dataIndex: 'c_wsjf',
width: 150
},
{
text: 'Name',
dataIndex: 'Name',
flex: 1,
width: 100
}
]
});
}
});
// END OF APP CODE
The app works great until I add the if (timecritsize === mystr) conditional.
I also use console.log() to check that I've set all values for timecritsize to "No decay"

Getting Accepted Stories that have Tasks with TimeSpent

I'm writing an application that should load Accepted stories that have tasks with integer in the Time Spent field.
As I can see in some tests I made the task fields that are accessible through UserStory is: 'Tasks', 'TaskActualTotal', 'TaskEstimateTotal', 'TaskRemainingTotal' and 'TaskStatus'.
Tasks has a _ref attribute with a link to a JSON with all tasks for this story. How may I explore this since I'm using Rally API? Or Is there a better way to do this?
UPDATE:
So this is pretty much i have now.
var storiesCall = Ext.create('Rally.data.WsapiDataStore', {
model: 'UserStory',
fetch: ['Tasks']
});
storiesCall.load().then({
success: this.loadTasks,
scope: this
});
loadTasks: function(stories) {
storiesCall = _.flatten(stories);
console.log(stories.length);
_.each(stories, function(story) {
var tasks = story.get('Tasks');
if(tasks.Count > 0) {
tasks.store = story.getCollection('Tasks', {fetch: ['Name','FormattedID','Workproduct','Estimate','TimeSpent']});
console.log(tasks.store.load().deferred);
}
else{
console.log('no tasks');
}
});
}
tasks.store.load().deferred is returning the following object:
Note that we can see the task data on value[0] but when i try to wrap it out with tasks.store.load().deferred.value[0] its crashing.
Any thoughts?
Per WS API doc, TimeSpent field on Task object (which is populated automatically from entries in Rally Timesheet/Time Tracker module) cannot be used in queries, so something like this (TimeSpent > 0) will not work.
Also, a UserStory object (referred to as HierarchicalRequirement in WS API) does not have a field where child tasks' TimeSpent rolls up to the story similar to how child tasks' Estimate rolls up to TaskEstimateTotal on a story.
It is possible to get TimeSpent for each task and then add them up by accessing a story's Tasks collection as done in this app:
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function() {
var initiatives = Ext.create('Rally.data.wsapi.Store', {
model: 'PortfolioItem/Initiative',
fetch: ['Children']
});
initiatives.load().then({
success: this.loadFeatures,
scope: this
}).then({
success: this.loadParentStories,
scope: this
}).then({
success: this.loadChildStories,
scope: this
}).then({
success: this.loadTasks,
failure: this.onFailure,
scope: this
}).then({
success: function(results) {
results = _.flatten(results);
_.each(results, function(result){
console.log(result.data.FormattedID, 'Estimate: ', result.data.Estimate, 'WorkProduct:', result.data.WorkProduct.FormattedID, 'TimeSpent', result.data.TimeSpent );
});
this.makeGrid(results);
},
failure: function(error) {
console.log('oh, noes!');
},
scope: this
});
},
loadFeatures: function(initiatives) {
var promises = [];
_.each(initiatives, function(initiative) {
var features = initiative.get('Children');
if(features.Count > 0) {
features.store = initiative.getCollection('Children',{fetch: ['Name','FormattedID','UserStories']});
promises.push(features.store.load());
}
});
return Deft.Promise.all(promises);
},
loadParentStories: function(features) {
features = _.flatten(features);
var promises = [];
_.each(features, function(feature) {
var stories = feature.get('UserStories');
if(stories.Count > 0) {
stories.store = feature.getCollection('UserStories', {fetch: ['Name','FormattedID','Children']});
promises.push(stories.store.load());
}
});
return Deft.Promise.all(promises);
},
loadChildStories: function(parentStories) {
parentStories = _.flatten(parentStories);
var promises = [];
_.each(parentStories, function(parentStory) {
var children = parentStory.get('Children');
if(children.Count > 0) {
children.store = parentStory.getCollection('Children', {fetch: ['Name','FormattedID','Tasks']});
promises.push(children.store.load());
}
});
return Deft.Promise.all(promises);
},
loadTasks: function(stories) {
stories = _.flatten(stories);
var promises = [];
_.each(stories, function(story) {
var tasks = story.get('Tasks');
if(tasks.Count > 0) {
tasks.store = story.getCollection('Tasks', {fetch: ['Name','FormattedID','Workproduct','Estimate','TimeSpent']});
promises.push(tasks.store.load());
}
else{
console.log('no tasks');
}
});
return Deft.Promise.all(promises);
},
makeGrid:function(tasks){
var data = [];
_.each(tasks, function(task){
data.push(task.data);
});
_.each(data, function(record){
record.Story = record.WorkProduct.FormattedID + " " + record.WorkProduct.Name;
});
this.add({
xtype: 'rallygrid',
showPagingToolbar: true,
showRowActionsColumn: true,
editable: false,
store: Ext.create('Rally.data.custom.Store', {
data: data,
groupField: 'Story'
}),
features: [{ftype:'groupingsummary'}],
columnCfgs: [
{
xtype: 'templatecolumn',text: 'ID',dataIndex: 'FormattedID',width: 100,
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate'),
summaryRenderer: function() {
return "Totals";
}
},
{
text: 'Name',dataIndex: 'Name'
},
{
text: 'TimeSpent',dataIndex: 'TimeSpent',
summaryType: 'sum'
},
{
text: 'Estimate',dataIndex: 'Estimate',
summaryType: 'sum'
}
]
});
}
});

Rally How can I export all the test cases that were run for a test set to CSV

We want to take a number test cases from previous iterations / test sets and import them into a new test set. The problem is that we cannot export the test cases from a test set into CSV or any other data format as all we get is a printable report. We have also tried copy and paste the printable report into MS Excel but it does not give a data format.
Any suggestions / HTML forms that can be used.
It is possible to copy TestSets in Rally UI and that functionality allows re-using member Test Cases from Iteration to Iteration or from Release to Release. When you copy Test Sets they preserve the member Test Cases, including Test Steps. Rally recommends this method to handle testing. If the same tests are use repeatedly, copy the test sets that contain them into the new iteration directly in the Rally UI. This resets the results for that iteration and keeps you from duplicating the testcases.
Mark's Ruby tools provide an alternative which allows selective copying of member test cases - something that does not happen when a testset is copied in the UI.
Here is a javascript example using AppSDK2. You may certainly customize it further. The main point of this example is to illustrate how to update a collection using AppSDK2.
A user may select a "source" iteration from the iteration combobox, then a testset combobox is populated with testsets scheduled for this iteration. Next a user can select a destination iteration from the third combobox that limits iterations to current and future iterations, and create a new testset scheduled for the "destination" iteration. The testcases from the "source" testset are copied to the new testset.
Currently updating collections will not work if the app is run outside of Rally, but this limitation will be corrected soon. It will allow collection updates outside of rally using CORS as long as rab run command is used. The code below should work inside Rally now. The deployment html is available from this github repo.
Ext.define('CustomApp', {
extend: 'Rally.app.TimeboxScopedApp',
componentCls: 'app',
scopeType: 'iteration',
comboboxConfig: {
fieldLabel: 'Select a source Iteration',
labelWidth: 150,
width: 350
},
onScopeChange: function() {
if (!this.down('#parentPanel')) {
this._panel = Ext.create('Ext.panel.Panel', {
layout: 'hbox',
itemId: 'parentPanel',
componentCls: 'panel',
items: [
{
xtype: 'container',
itemId: 'pickerContainer',
},
{
xtype: 'container',
itemId: 'iterationContainer',
}
]
});
this.add(this._panel);
}
if (this.down('#testSetComboxBox')) {
this.down('#testSetComboxBox').destroy();
}
var testSetComboxBox = Ext.create('Rally.ui.combobox.ComboBox',{
itemId: 'testSetComboxBox',
storeConfig: {
model: 'TestSet',
limit: Infinity,
pageSize: 100,
autoLoad: true,
filters: [this.getContext().getTimeboxScope().getQueryFilter()]
},
fieldLabel: 'Select a TestSet',
listeners:{
ready: function(combobox){
if (combobox.getRecord()) {
this._onTestSetSelected(combobox.getRecord());
}
else{
console.log('selected iteration has no testsets');
}
},
select: function(combobox){
if (combobox.getRecord()) {
this._onTestSetSelected(combobox.getRecord());
}
},
scope: this
}
});
this.down('#pickerContainer').add(testSetComboxBox);
},
_onTestSetSelected:function(testset){
var id = testset.get('ObjectID');
this._name = testset.get('Name');
testset.self.load(id, {
fetch: ['Name','TestCases'],
callback: this._onSourceRecordRead,
scope: this
});
},
_onSourceRecordRead: function(record) {
var that = this;
that._testcases = [];
var testcaseStore = record.getCollection('TestCases',{fetch:['Name','FormattedID']});
testcaseStore.load({
callback: function() {
_.each(testcaseStore.getRange(), function(tc){
that._testcases.push(tc.data._ref);
});
console.log(that._testcases);
that._selectFutureIteration();
}
});
},
_selectFutureIteration: function(){
if (!this.down('#iterationComboxBox')) {
var iterationComboxBox = Ext.create('Rally.ui.combobox.ComboBox',{
itemId: 'iterationComboxBox',
storeConfig: {
model: 'Iteration',
limit: Infinity,
pageSize: 100,
autoLoad: true,
filters: [
{
property: 'StartDate',
operator: '>=',
value: (new Date()).toISOString()
}
]
},
fieldLabel: 'Select a destination Iteration',
labelWidth: 150,
listeners:{
ready: function(combobox){
if (combobox.getRecord()) {
this._onFutureIterationSelected(combobox.getRecord());
}
else{
console.log('no current or future iterations');
}
},
select: function(combobox){
if (combobox.getRecord()) {
this._onFutureIterationSelected(combobox.getRecord());
}
},
scope: this
}
});
this.down('#iterationContainer').add(iterationComboxBox);
}
},
_onFutureIterationSelected:function(iteration){
var that = this;
that._iteration = iteration.data._ref;
if (!this.down('#create')) {
var createButton = Ext.create('Ext.Container', {
items: [
{
xtype : 'rallybutton',
text : 'create a testset',
itemId: 'create',
handler: function() {
that._createTestSet();
}
}
]
});
this.add(createButton);
}
},
_createTestSet: function(){
var that = this;
console.log('create testset scheduled for ', that._iteration);
Rally.data.ModelFactory.getModel({
type: 'TestSet',
success: function(model) {
that._model = model;
var ts = Ext.create(model, {
Name: that._name + 'Copy',
Iteration: that._iteration
});
ts.save({
callback: function(result, operation) {
if(operation.wasSuccessful()) {
console.log(result.get('Name'), ' ', result.get('Iteration')._refObjectName);
that._readRecord(result);
}
else{
console.log("?");
}
}
});
}
});
},
_readRecord: function(result) {
var id = result.get('ObjectID');
this._model.load(id, {
fetch: ['Name','TestCases'],
callback: this._onRecordRead(result),
scope: this
});
},
_onRecordRead: function(record, operation) {
console.log('There are ', record.get('TestCases').Count, ' in ', record.get('Name') );
var that = this;
var testcaseStore = record.getCollection('TestCases');
testcaseStore.load({
callback: function() {
testcaseStore.add(that._testcases);
testcaseStore.sync({
callback: function() {
console.log('success');
}
});
}
});
}
});
There are a couple of Ruby scripts that might help you here:
https://github.com/markwilliams970/Rally-Test-Set-Export
https://github.com/markwilliams970/Rally-Add-TestCases-TestSet
These do require a working Ruby scripting environment and some familiarity with configuring/running scripts at the command line. However, working in combination, these should be enough to accomplish your goal.

Rally SDK 2 manually filter or specify rallycardboard columns displayed

I am trying to create a simple rallycardboard app that displays projects as columns with the project backlog stories as cards. Then allow the drag/drop of cards to set the project. Code is attached.
If I specify 'Project" as the attribute, the board contains columns for all projects in the workspace. I wish to limit the columns shown to either
Scoped parent and children, or
Code a list of project columns. I have tried the manipulate the columns, columnConfig, context settings, but nothing produces the desired results.
<!DOCTYPE html>
<html>
<head>
<title>CardBoard Example</title>
<script type="text/javascript" src="/apps/2.0rc2/sdk.js"></script>
<script type="text/javascript">
Rally.onReady(function() {
Ext.define('ProjBoard', {
extend: 'Rally.app.App',
launch: function() {
if (cardBoardConfig) {
cardBoardConfig.destroy();
}
var cardBoardConfig = {
xtype: 'rallycardboard',
types: ['User Story'],
attribute: 'Project',
fieldToDisplay: 'Project',
cardConfig: {
fields: ['Project', 'Parent','Iteration']
},
storeConfig: {
filters: [
{ property: 'ScheduleState', operator: '<', value: 'In-Progress' },
{ property: 'Iteration', operator: '=', value: '' }
],
sorters: [
{ property: 'Rank', direction: 'DESC' }
],
//Specify current project and scoping
context: this.getContext().getDataContext()
}
};
this.add(cardBoardConfig);
}
});
Rally.launchApp('ProjBoard', {
name: 'Backlog Project Board'
});
});
</script>
<style type="text/css">
</style>
</head>
<body></body>
</html>
You should be able to specify the columns via config:
https://help.rallydev.com/apps/2.0rc2/doc/#!/api/Rally.ui.cardboard.CardBoard-cfg-columns
columns: [
{
value: '/project/12345',
columnHeaderConfig: {
headerTpl: '{project}',
headerData: {project: 'Project 1'}
}
},
//more columns...
]
The code below allowed me to cut down a dozen of project columns to three. First I get current project and query a collection of its child projects to build an array of projects I want to have on the board (you may choose a different criteria for what projects you want on the board), and then I extended Rally.ui.cardboard.CardBoard to overwrite its _buildColumnsFromModel method where only columns that meet this condition are filtered in :
retrievedColumns = _.select(retrievedColumns, function(project){
return that.arrayOfProjectRefs.indexOf(project.value) != -1
});
Here is the full js file. Apart from those changes, this is your code.
Ext.define('CustomApp', { extend: 'Rally.app.App', componentCls: 'app',
launch: function() {
var that = this;
that.arrayOfProjectRefs = [];
var p = this.getContext().getProject();
Ext.create('Rally.data.wsapi.Store', {
model: 'Project',
fetch: ['Children'],
filters:[
{
Property: '_ref',
value: p
}
],
pageSize: 1,
autoLoad: true,
listeners: {
load: function(store, records) {
var project = records[0];
var childProjects = project.get('Children');
var childProjectsCount = project.get('Children').Count;
console.log('childProjectsCount', childProjectsCount);
that.arrayOfProjectRefs.push(project.get('_ref'));
project.getCollection('Children').load({
fetch: ['_ref', 'Name', 'State'],
callback: function(records, operation, success) {
Ext.Array.each(records, function(child) {
console.log(child.get('_ref') + ' - ' + child.get('Name') + child.get('State'));
if (child.get('State') === 'Open') {
that.arrayOfProjectRefs.push(child.get('_ref'));
--childProjectsCount;
if (childProjectsCount === 0) {
that._buildBoard();
}
}
});
}
});
}
}
});
},
_buildBoard:function(){
var that = this;
console.log('app._arrayOfProjectRefs', this.arrayOfProjectRefs);
Ext.define('ProjectCardboard', {extend: 'Rally.ui.cardboard.CardBoard',
xtype: 'projectCardboard',
_buildColumnsFromModel: function() {
var model = this.models[0];
if (model) {
var attribute = model.getField('Project');
if (attribute) {
attribute.getAllowedValueStore().load({
callback: function(records, operation, success) {
var retrievedColumns = _.map(records, function(allowedValue) {
var displayValue, value = allowedValue.get('StringValue');
if (!value && attribute.attributeDefinition.AttributeType.toLowerCase() === 'rating') {
value = "None";
} else if (attribute.attributeDefinition.AttributeType.toLowerCase() === 'object') {
displayValue = value;
value = allowedValue.get('_ref');
if (value === 'null') {
value = null;
}
}
return {
value: value,
columnHeaderConfig: {
headerTpl: displayValue || value || 'None'
}
};
});
this.fireEvent('columnsretrieved', this, retrievedColumns);
retrievedColumns = _.select(retrievedColumns, function(project){
return that.arrayOfProjectRefs.indexOf(project.value) != -1
});
console.log('retrievedColumns after filter', retrievedColumns)
this.columnDefinitions = [];
_.each(retrievedColumns, this.addColumn, this);
this.renderColumns();
},
scope: this
});
}
}
}
});
var addNewConfig = {
xtype: 'rallyaddnew',
recordTypes: ['User Story'],
ignoredRequiredFields: ['Name', 'Iteration'],
showAddWithDetails: false,
};
this.addNew = this.add(addNewConfig);
var myCardConfig = {
xtype: 'rallycard',
fields: ['ScheduleState','Name'],
maxHeight: 100
}
var cardBoardConfig = {
xtype: 'projectCardboard',
types: ['User Story'],
attribute: 'Project',
cardConfig: myCardConfig
};
this.cardBoard = this.add(cardBoardConfig);
}
});

In Rally, when copying a test set, how do you copy the test case results with the test set?

I'm fairly new to Rally and so far have only used the web interface (I haven't used the Rally APIs from a programming languages yet). Occasionally we have a test set that we don't finish in an iteration, so we'd like to be able to copy the test set to the next iteration but retain the test case results entered so far in the new iteration so that we don't have to look in 2 different places for the complete test set results. Perhaps one solution is better iteration planning, but I'm still curious if there's a way to copy test case results along with a test set when copying a test set.
Test Case Result cannot be copied. It can be created in UI or Web Services API with the identical data, but it does not support a copy functionality.
For a general example of how to create a TCR object using a browser REST client see this SO post here.
Here is a poof of concept app that creates a copy of an existing TCR. The app filters TestSets by Iteration, and loads those TestSets into a combobox. Based on the TestSet selection in the combobox another combobox is created, populated with TestCases. When a Test Case is selected from that combobox a grid is built with TestCaseResults. Doubleclicking on a TCR in the grid will invoke a copy function. See AppSDK2 topic on how to copy records here. You may extend the code per your specifications. The source is in this github repo.
Minimally you need to change ObjectIDs of destination test case and destination test set in _copyRecordOnDoubleClick method below:
Ext.define('CustomApp', {
extend: 'Rally.app.TimeboxScopedApp',
componentCls: 'app',
scopeType: 'iteration',
comboboxConfig: {
fieldLabel: 'Select an Iteration:',
labelWidth: 100,
width: 300
},
onScopeChange: function() {
if (this.down('#testSetComboxBox')) {
this.down('#testSetComboxBox').destroy();
}
if (this.down('#testCaseComboxBox')) {
this.down('#testCaseComboxBox').destroy();
}
if (this.down('#resultsGrid')) {
this.down('#resultsGrid').destroy();
}
var testSetComboxBox = Ext.create('Rally.ui.combobox.ComboBox',{
id: 'testSetComboxBox',
storeConfig: {
model: 'TestSet',
pageSize: 100,
autoLoad: true,
filters: [this.getContext().getTimeboxScope().getQueryFilter()]
},
fieldLabel: 'select TestSet',
listeners:{
ready: function(combobox){
if (combobox.getRecord()) {
this._onTestSetSelected(combobox.getRecord());
}
else{
console.log('selected iteration has no testsets');
}
},
select: function(combobox){
if (combobox.getRecord()) {
this._onTestSetSelected(combobox.getRecord());
}
},
scope: this
}
});
this.add(testSetComboxBox);
},
_onTestSetSelected:function(selectedTestset){
var testCases = selectedTestset.getCollection('TestCases', {fetch: ['FormattedID','ObjectID', 'Results']});
var ts = {
FormattedID: selectedTestset.get('FormattedID'),
TestCaseCount: selectedTestset.get('TestCases').Count,
TestCases: [],
ResultCount: 0
};
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;
ts.TestCases.push({_ref: testcase.get('_ref'),
FormattedID: testcase.get('FormattedID'),
ObjectID: testcase.get('ObjectID')
});
}, this);
this._makeTestCaseCombobox(ts.TestCases);
},
scope: this
});
},
_makeTestCaseCombobox:function(testcases){
if (this.down('#testCaseComboxBox')) {
this.down('#testCaseComboxBox').destroy();
}
if (this.down('#resultsGrid')) {
this.down('#resultsGrid').destroy();
}
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
}
)
});
var filters = Ext.create('Rally.data.QueryFilter', filterArray[0]);
filterArray = _.rest(filterArray,1);
_.each(filterArray, function(filter){
filters = filters.or(filter)
},1);
var testCaseComboxBox = Ext.create('Rally.ui.combobox.ComboBox',{
id: 'testCaseComboxBox',
storeConfig: {
model: 'TestCase',
pageSize: 100,
autoLoad: true,
filters:filters,
fetch: true
},
fieldLabel: 'select TestCase',
listeners:{
ready: function(combobox){
if (combobox.getRecord()) {
this._onTestCaseSelected(combobox.getRecord());
}
else{
console.log('selected testset has no testcases');
}
},
select: function(combobox){
if (combobox.getRecord()) {
this._onTestCaseSelected(combobox.getRecord());
}
},
scope: this
}
});
this.add(testCaseComboxBox);
}
else{
console.log('selected testset has no testcases');
}
},
_onTestCaseSelected:function(selectedTestcase){
var results = selectedTestcase.getCollection('Results', {fetch: ['ObjectID','Date', 'TestSet', 'TestCase', 'Build', 'Verdict']});
var tc = {
ObjectID: selectedTestcase.get('ObjectID'),
FormattedID: selectedTestcase.get('FormattedID'),
Results: []
};
results.load({
callback: function(records, operation, success){
Ext.Array.each(records, function(result){
console.log("result.get('ObjectID')", result.get('ObjectID'));
console.log("result.get('Verdict')", result.get('Verdict'));
tc.Results.push({_ref: result.get('_ref'),
ObjectID: result.get('ObjectID'),
Date: result.get('Date'),
Build: result.get('Build'),
Verdict: result.get('Verdict')
});
}, this);
this._updateGrid(tc.Results);
},
scope: this
});
},
_updateGrid: function(results){
var store = Ext.create('Rally.data.custom.Store', {
data: results,
pageSize: 100
});
if (!this.down('#resultsGrid')) {
this._createGrid(store);
}
else{
this.down('#resultsGrid').reconfigure(store);
}
},
_createGrid: function(store){
var that = this;
var that = this;
var resultsGrid = Ext.create('Rally.ui.grid.Grid', {
id: 'resultsGrid',
store: store,
columnCfgs: [
{
text: 'ObjectID ID', dataIndex: 'ObjectID',
},
{
text: 'Date', dataIndex: 'Date',
},
{
text: 'Build', dataIndex: 'Build',
},
{
text: 'Verdict', dataIndex: 'Verdict',
},
],
listeners: {
celldblclick: function( grid, td, cellIndex, record, tr, rowIndex){
that._copyRecordOnDoubleClick(record);
}
}
});
this.add(resultsGrid);
},
_copyRecordOnDoubleClick: function(record){
var that = this;
console.log('record', record);
Rally.data.ModelFactory.getModel({
type: 'TestCaseResult',
success: function(model) {
that._model = model;
var copy = Ext.create(model, {
Date: record.get('Date'),
Build: record.get('Build'),
Verdict: record.get('Verdict'),
TestCase: '/testcase/17237838118',
TestSet: '/testset/17234968911'
});
copy.save({
callback: function(result, operation) {
if(operation.wasSuccessful()) {
console.log('result',result);
}
else{
console.log("problem");
}
}
});
}
});
}
});