Related
Hi I would like to have a function to contain 2 filters first to filter the data based on the keyword of the name which contain the word "from mail" and the next is based on the iteration box . I've tried to do it but the grid shows no data the code i used:
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
grid: null,
launch: function() {
var filters = [];
var timeboxScope = this.getContext().getTimeboxScope();
if(timeboxScope) {
filters.push(timeboxScope.getQueryFilter());
}
this.getFilteredStoryModel(filters);
},
onTimeboxScopeChange: function(newTimeboxScope) {
var newFilters = [];
var updatedTimeboxScope = this.getContext().getTimeboxScope();
if (this.grid) {
this.grid.destroy();
}
if (updatedTimeboxScope) {
newFilters.push(newTimeboxScope.getQueryFilter());
}
this.getFilteredStoryModel(newFilters);
},
getFilteredStoryModel:function(queryFilters){
Ext.create('Rally.data.wsapi.Store', {
fetch: ['FormattedID','Name','Owner','ScheduleState'],
model:"User Story",
filters: queryFilters,
autoLoad:true,
listeners:{
load:function(myStore,myData,success){
console.log("got data:",myStore,myData,success);
//the data is got and store in myStore if success. and the _loadTagSummary is called with the myStore pass into it
this.displaydata(myStore);
},
scope:this
},
});
},
displaydata:function(mystorystore){
this.grid = this.add({
xtype: 'rallygrid',
model: mystorystore,
columnCfgs: [
'FormattedID',
'Name',
'Owner'
],
storeConfig: {
filters: [
{
property: 'Name',
operator: '=',
value: 'From Mail'
}
]
}
});
}
});
Thank you for the help
You're not super far off- I think you're getting tripped up by a subtle bug in onTimeboxScopeChange (not calling the parent method) and also some weirdness around trying to specify both a store and a storeConfig on the grid.
Here's what I came up with:
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
layout: 'fit', //take up all available space
launch: function() {
this._addGrid();
},
onTimeboxScopeChange: function(newTimeboxScope) {
this.callParent(arguments); //super important, or timebox scope in context doesn't ever get updated!
this._refreshGrid();
},
_addGrid: function() {
this.add({
xtype: 'rallygrid',
model: 'User Story',
columnCfgs: [
'FormattedID',
'Name',
'Owner'
],
storeConfig: {
filters: this._getFilters()
}
});
},
_getFilters: function() {
var filters = [
{
property: 'Name',
operator: '=',
value: 'From Mail'
}
];
var timeboxScope = this.getContext().getTimeboxScope();
if (timeboxScope) {
filters.push(timeboxScope.getQueryFilter());
}
return filters;
},
_refreshGrid: function() {
var grid = this.down('rallygrid'),
store = grid.getStore();
store.clearFilter(true);
store.filter(this._getFilters());
}
});
I used a few different examples in the docs to help with this:
Filterable Grid Example (for refreshing the grid)
Timebox Filtering (for the onTimeboxScopeChange bug)
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);
}
});
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
If I have a rallygrid of PortfolioItems and I include PercentDoneByStoryPlanEstimate in the column configs, I get the progress bar as expected. But when I hover over the progress bar, I don't see the tooltip with extra information that I see in Rally itself.
How do I get the tooltip to display in an app?
Sample code:
Rally.onReady(function() {
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function() {
Rally.data.ModelFactory.getModel({
type: 'PortfolioItem',
success: function(model) {
this.grid = this.add({
xtype: 'rallygrid',
model: model,
columnCfgs: [
'FormattedID',
'Name',
'PercentDoneByStoryPlanEstimate'
]
});
},
scope: this
});
}
});
Rally.launchApp('CustomApp', {
name: 'Grid Example'
});
});
You just need to specify a viewConfig and add the 'rallypercentdonetooltip' plugin like so:
success: function(model) {
this.grid = this.add({
xtype: 'rallygrid',
model: model,
columnCfgs: [
'Name',
'FormattedID',
'PercentDoneByStoryPlanEstimate'
],
viewConfig:{
plugins:[
{ptype:'rallypercentdonetooltip'}
]
}
});