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);
}
});
Related
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>
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>
I tried to add a sorter to a Rally.data.WsapiDataStore and it did not work.
Is it possible to sort by a parent's field?
Ext.create('Rally.data.WsapiDataStore', {
model: 'UserStory',
fetch: ['FormattedID','Name','HasParent','Parent'],
pageSize: 100,
autoLoad: true,
sorters: [
{
property: 'Parent.FormattedID',
direction: 'DESC'
}
],
listeners: {
load: this._onDataLoaded,
scope: this
}
});
Also, I tried to filter by "HasParent", and it did not work either.
filters: [
{
property: 'HasParent',
operator: '=',
value: true
}
]
Thanks!
Here is an example where a grid is sorted by a Parent's story's FormattedID. The sorter is added to a Rally.data.custom.Store, and not to Rally.data.WsapiDataStore
<!DOCTYPE html>
<html>
<head>
<title>TCofUS</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: 'UserStory',
fetch: ['FormattedID','Name','HasParent','Parent'],
pageSize: 100,
autoLoad: true,
listeners: {
load: this._onDataLoaded,
scope: this
}
});
},
_createGrid: function(stories) {
this.add({
xtype: 'rallygrid',
store: Ext.create('Rally.data.custom.Store', {
data: stories,
pageSize: 100,
sorters: [
{
property: 'Parent',
direction: 'DESC'
}
],
}),
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'Name', dataIndex: 'Name'
},
{
text: 'Parent', dataIndex: 'Parent',
renderer: function(parent) {
return ('' + parent + '');
}
}
]
});
},
_onDataLoaded: function(store, data){
var stories = [];
Ext.Array.each(data, function(story) {
var parent = story.get('Parent');
var s = {
FormattedID: story.get('FormattedID'),
Name: story.get('Name'),
_ref: story.get("_ref"),
Parent: (parent && parent.FormattedID) || 'None',
};
stories.push(s);
},
this);
this._createGrid(stories);
}
});
Rally.launchApp('CustomApp', {
name:"TCofUS",
//parentRepos:""
});
});
</script>
<style type="text/css">
.app {
/* Add app styles here */
}
</style>
</head>
<body></body>
</html>
As far as filters, per WS API documentation HasParent cannot be used in queries.
The code above checks for the pretense of a parent and prints 'None' if there is no parent:
Parent: (parent && parent.FormattedID) || 'None'
An alternative way to use the filter would be to simply filter on Parent != ''
filters: [
{
property: 'Parent',
operator: '!=',
value: ''
}
]
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);
},
Having trouble showing parent feature of user stories and sorting by that same parent field. Here's my code. I see empty value in Parent column unless the parent is another user story. And I am not able to sort by Parent field.
Your help would be greatly appreciated!
Thanks!
<script type="text/javascript">
Rally.onReady(function() {
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
items: [
{
xtype: 'container',
itemId: 'iterationFilter'
},
{
xtype: 'container',
itemId: 'grid',
width: 800
}
],
launch: function() {
this.down('#iterationFilter').add({
xtype: 'rallyiterationcombobox',
cls: 'filter',
model: 'UserStory',
field: 'Iteration',
listeners: {
ready: this._onIterationComboBoxLoad,
select: this._onIterationComboBoxSelect,
scope: this
}
});
},
_onIterationComboBoxLoad: function(comboBox) {
this.iterationComboBox = comboBox;
Rally.data.ModelFactory.getModel({
type: 'UserStory',
success: this._onModelRetrieved,
scope: this
});
},
_getFilter: function() {
var filter = [];
filter.push({
property: 'Iteration',
operator: '=',
value: this.iterationComboBox.getValue()
});
return filter;
},
_onIterationComboBoxSelect: function() {
this._onSettingsChange();
},
_onSettingsChange: function() {
this.grid.filter(this._getFilter(), true, true);
},
_onModelRetrieved: function(model) {
this.grid = this.down('#grid').add({
xtype: 'rallygrid',
model: model,
columnCfgs: [
'FormattedID',
'Name',
'Plan Estimate',
'Parent',
'Schedule State',
'StoryType'
],
storeConfig: {
context: this.context.getDataContext(),
filters: this._getFilter()
},
showPagingToolbar: true,
enableEditing: false
});
}
});
});
Rally.launchApp('CustomApp', {
name: 'Defect Dashboard'
});
</script>
User Stories have two different fields for their Parent. If the Parent is another story it uses Parent. In the case where the Parent is a Portfolio Item like a Feature the parent will be called PortfolioItem.
You can see the fields on user story by looking at our webservice docs.
In your example you would have to change your column configs to include PorfolioItem
columnCfgs: [
'FormattedID',
'Name',
'Plan Estimate',
'PortfolioItem',
'Schedule State',
'StoryType'
],
I was at least able to show the name of feature for each user story. I am still having an issue to make it sortable though. :(
<!DOCTYPE html>
<html>
<head>
<title>Grid Example</title>
<script type="text/javascript" src="/apps/2.0p4/sdk.js?wsapiVersion=1.38"></script>
<script type="text/javascript">
Rally.onReady(function() {
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
items: [
{
xtype: 'container',
itemId: 'iterationFilter'
},
{
xtype: 'container',
itemId: 'grid'
//width: 800
}
],
launch: function() {
this.down('#iterationFilter').add({
xtype: 'rallyiterationcombobox',
cls: 'filter',
model: 'UserStory',
field: 'Iteration',
listeners: {
ready: this._onIterationComboBoxLoad,
select: this._onIterationComboBoxSelect,
scope: this
}
});
},
_onIterationComboBoxLoad: function(comboBox) {
this.iterationComboBox = comboBox;
Rally.data.ModelFactory.getModel({
type: 'UserStory',
success: this._onModelRetrieved,
scope: this
});
},
_getFilter: function() {
var filter = [];
filter.push({
property: 'Iteration',
operator: '=',
value: this.iterationComboBox.getValue()
});
return filter;
},
_onIterationComboBoxSelect: function() {
this._onSettingsChange();
},
_onSettingsChange: function() {
this.grid.filter(this._getFilter(), true, true);
},
_onModelRetrieved: function(model) {
this.grid = this.down('#grid').add({
xtype: 'rallygrid',
model: model,
columnCfgs: [
'FormattedID',
'Name',
'PlanEstimate',
{
text: 'Feature',
dataIndex: 'PortfolioItem',
renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
if (value != null) {
return value.Name;
}
return '';
}
},
'ScheduleState',
'StoryType'
],
storeConfig: {
context: this.context.getDataContext(),
remoteSort: false,
filters: this._getFilter()
},
showPagingToolbar: true,
enableEditing: false
});
}
});
});
Rally.launchApp('CustomApp', {
name: 'Defect Dashboard'
});
</script>
<style type="text/css">
.filter {
float: left;
margin: 5px 5px;
vertical-align: middle;
}
</style>
</head>
<body></body>
</html>
Looking at your revised solution with the renderer, all you need to add is a doSort method to the custom column you have set up. Make sure you set remoteSort to false on the store, then you can override the sorting with a method:
doSort: function(state) {
var ds = this.up('grid').getStore();
var field = this.getSortParam();
ds.sort({
property: field,
direction: state,
sorterFn: function(v1, v2){
v1 = v1.get(field);
v2 = v2.get(field);
return v1.length > v2.length ? 1 : (v1.length < v2.length ? -1 : 0);
}
});
}
This sorter happens to sort by the length of the the field, but you can change it to do what you want. See Ext js sorting custom column by contents