Rally - Data Context and InlineFilter issue in a RallyGridBoard - rally

Objective :
Trying to build a Rally App to populate "PortFolioItem/features" based upon a Project value selected from a userSearchCombo box.
Methodology:
1. Creating a UserSearchComboBox based upon the 'Project' model.
2. Depending upon the value selected from the combo box,Creating a TreeStore based on the model 'PortfolioItem/Feature'.
The context scoped for the tree Store is based on the value selected from the Project Search Combo Box
app.portfoliotreeStore = Ext.create('Rally.data.wsapi.TreeStoreBuilder').build({
models: modelNames,
autoLoad: true,
enableHierarchy: true,
limit: 'Infinity',
remoteSort: true,
// sorters: app.appSorters,
//fetch: ['Name', 'FormattedID', 'ScheduleState', 'PlanEstimate', 'Owner', 'Project'],
//filters: filters,
// filters: this.getQueryFilter(),
context: {
project: app.selProjectRef,
projectScopeDown: true
}
}).then({
success: this._addGridboard,
failure: this.showErrorNotification,
scope: this
});
In the Add grid Board Method ,created a gridBoard with some plugins
_addGridboard: function(store) {
console.log("Entering _addGridBoard", store);
app.portfoliotreeStore = store;
var modelNames = ['portfolioitem/PPMFeature']
var filters = app.getStoreFilters(app.selProjectName);
console.log("the Grid", app.down('#gridContainer'));
if (app.down('#gridContainer')) {
app.down('#gridContainer').remove('gridBoard');
}
//this.gridBoardConfig = app._getGridBoardConfig(store);
var dataContext = app.getContext().getDataContext();
console.log("DATA CONTEXT ::::", dataContext);
gridStateString = this.statePrefix + "-treegrid";
gridStateId = this.getContext().getScopedStateId(gridStateString);
app.down('#gridContainer').add({
xtype: 'rallygridboard',
itemId: 'gridBoard',
// context: this.getContext(),
modelNames: ['portfolioitem/PPMFeature'],
toggleState: 'grid',
plugins: [
'rallygridboardaddnew', {
ptype: 'rallygridboardinlinefiltercontrol',
inlineFilterButtonConfig: {
//stateful: true,
//stateId: this.getContext().getScopedStateId('filters-1'),
//collapsed: true,
context: this.getContext(),
modelNames: ['portfolioitem/PPMFeature'],
inlineFilterPanelConfig: {
collapsed: true,
quickFilterPanelConfig: {
whiteListFields: [
'Tags',
'Milestones'
],
defaultFields: [
'ArtifactSearch',
'Owner',
'ModelType',
'Milestones'
]
}
}
}
},
{
ptype: 'rallygridboardfieldpicker',
headerPosition: 'left',
modelNames: this._getModelNames()
//stateful: true,
//stateId: this.getContext().getScopedStateId('columns-example')
}
],
stateful: true,
listeners: {
'staterestore': {
fn: this._onGridStateRestore,
single: true
},
'load': {
fn: this._onGridload,
single: true
},
scope: this
},
gridConfig: {
store: store,
storeConfig: {
filters: this.getQueryFilter()
},
columnCfgs: [
'Name',
'Project',
'ScheduleState',
'Owner',
'PlanEstimate'
]
// derivedColumns: this.getDerivedColumns()
},
/*
gridConfig: {
store: store,
storeConfig: {
filters: this.getQueryFilter()
},
columnCfgs: [
'Name',
'Project',
'ScheduleState',
'Owner',
'PlanEstimate'
],
// derivedColumns: this.getDerivedColumns()
},
*/
height: this.getHeight()
});
},
Please Note the following
Not passing any context to the grid component - Reason:: if i pass
"this.getContext" to the Grid, duplicates records are initially loaded into
the grid , once i click on any header to sort the grid,the duplicates disappear.
Issue: catch 22 Situation
The only way for me to get rid of the duplicates is to not pass the context to the GridBoard, However this impacts my Plugin - "rallygridboardinlinefiltercontrol"
if i scroll over my InlinefilterButton , it throws this error
Uncaught TypeError: Cannot read property 'collapsed' of undefined
at constructor._onBeforeShowToolTip (VM556 sdk.js:100)
at constructor.fire (VM556 sdk.js:5)
at constructor.continueFireEvent (VM556 sdk.js:6)
at constructor.fireEventArgs (VM556 sdk.js:6)
at constructor.prototype.fireEventArgs (VM556 sdk.js:18)
at constructor.fireEvent (VM556 sdk.js:6)
at constructor.show (VM556 sdk.js:15)
at constructor.callParent (VM556 sdk.js:2)
at constructor.show (VM556 sdk.js:25)
at constructor.showFromDelay (VM556 sdk.js:25)
And when i Click on it , it Throws this Error
Uncaught TypeError: Cannot read property 'toggleCollapse' of undefined
at constructor._toggleFilterPanel (VM556 sdk.js:100)
at call (VM556 sdk.js:5)
It does not impact my other filters

Ok , I finally found the Issue ... It appears , i need to pass the Context to the grid , but i also need to switch the autoload on the TreeGrid to false , this is because the plugin owns the responsibility of loading the store .... YAY !! Eureka Eureaka I got this ... Can any one sugest any good book on Rally..

Related

How to add custom data in a story board?

Question 1: Is it possible to add custom data (Ex: testcase count : 5) to each card in a story board? If so, how? I couldn't find an example or specific information in documentation.
Question 2: Is it possible to get testcase count (including child story testcases)for a highlevel story in one query?
Please let me know. Here's my code
Ext.define('Rally.Story.View', {
extend: 'Rally.app.App',
launch: function() {
this.add({
xtype: 'rallyfieldvaluecombobox',
fieldLabel: 'Filter by Target Release:',
model: 'UserStory',
field: 'c_TargetRelease',
value: '15.0',
listeners: {
select: this._onSelect,
ready: this._onLoad,
scope: this
}
});
},
_onLoad: function() {
this.add({
xtype: 'rallycardboard',
types: ['User Story'],
attribute: 'ScheduleState',
readOnly: true,
fetch: ['Name', 'TestCases', 'c_StoryType', 'c_TargetRelease', 'PlanEstimate', 'Priority', 'TaskEstimateTotal', 'TaskRemainingTotal'],
context: this.getContext(),
cardConfig: {
editable: false,
showIconsAndHighlightBorder: false,
fields: ['Name', 'c_StoryType', 'c_TargetRelease', 'PlanEstimate', 'c_PriorityBin', 'Parent', 'TestCases', 'TaskEstimateTotal', 'TaskRemainingTotal']
},
storeConfig: {
filters: [
{
property: 'c_StoryType',
value: 'SAGA Feature'
},
{
property: 'c_TargetRelease',
operator: '=',
value: this.down('rallyfieldvaluecombobox').getValue()
}
]
}
});
},
_onSelect: function() {
var board = this.down('rallycardboard');
board.refresh({
storeConfig: {
filters: [
{
property: 'c_StoryType',
value: 'SAGA Feature'
},
{
property: 'c_TargetRelease',
operator: '=',
value: this.down('rallyfieldvaluecombobox').getValue()
}
]
}
});
},
});
Here's a sample card I made that contains the test case count:
You can add a field by simply including an object with some rendering information in it instead of just a simple string in the fields array in the cardConfig:
cardConfig: {
fields: [
'Name', //simple string field to show
{
name: 'TCCount', //field name
hasValue: function() {return true;}, //always show this field
renderTpl: Ext.create('Rally.ui.renderer.template.LabeledFieldTemplate', {
fieldLabel: 'Test Case Count', //the field label
valueTemplate: Ext.create('Ext.XTemplate',
['{[this.getTestCaseCount(values)]}',
{getTestCaseCount: function(data) { return data.Summary.TestCases.Count;}}])
})
},
//additional string fields
'PlanEstimate', 'Parent', 'TestCases', 'TaskEstimateTotal', 'TaskRemainingTotal']
}
This ended up being less straightforward than I thought it might be, but at least it is doable. The key part is using the LabeledFieldTemplate, specifying a field label and a value template to actually render the content.
You'll also notice the little beaker status icon in the footer which is automatically rendered because TestCases was included in the fields list.
As for your second question there is no roll up field on story for the total number of test cases included on child stories.

the experimental hierarchical tree for rally

I see that Hierarchical trees are labeled as experimental on the Rally site (https://help.rallydev.com/apps/2.0rc3/doc/#!/api/Rally.ui.grid.TreeGrid). I wanted to build an app using the hierarchical tree and I had a few questions about the features. Is it possible to filter the tree or no? Also can i add up the totals of the tasks for a given userstory (estimate, todo, actual, etc) and list that total as the userstory value? Is there another way to get a list of the userstories with the tasks in a list beneath it?
A not-treegrid example: this app that uses group and summary features in a grid of tasks in current iteration grouped by workproduct (user story), where Estimate values of individual tasks are summed up. Full code is in this github repo.
launch: function() {
var that = this;
var today = new Date().toISOString();
var stories = Ext.create('Rally.data.wsapi.Store', {
model: 'UserStory',
fetch: ['Tasks'],
filters: [
{
property: 'Iteration.StartDate',
operator: '<=',
value: today
},
{
property: 'Iteration.EndDate',
operator: '>=',
value: today
}
]
});
stories.load().then({
success: this.loadTasks,
scope: this
}).then({
success:function(results) {
that.makeGrid(results);
},
failure: function(){
console.log("oh noes!")
}
});
},
loadTasks: function(stories){
console.log("load tasks",stories)
var promises = [];
_.each(stories, function(story){
var tasks = story.get('Tasks');
if (tasks.Count > 0) {
tasks.store = story.getCollection('Tasks',{fetch:['Name','FormattedID','Estimate','State','Blocked','WorkProduct']});
promises.push(tasks.store.load());
}
});
return Deft.Promise.all(promises);
},
makeGrid: function(results){
var tasks = _.flatten(results);
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 "Estimate Total";
}
},
{
text: 'Name',dataIndex: 'Name',
},
{
text: 'State',dataIndex: 'State',xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.ScheduleStateTemplate',
{
states: ['Defined', 'In-Progress', 'Completed'],
field: {
name: 'State'
}
})
},
{
text: 'Estimate',dataIndex: 'Estimate',
summaryType: 'sum',
},
{
text: 'WorkProduct',dataIndex: 'WorkProduct',
renderer: function(val, meta, record) {
return '' + record.get('WorkProduct').FormattedID + '';
}
},
]
});
}
Update: If you want to filter the task store include a filter here:
tasks.store = story.getCollection('Tasks',{fetch:['Name','FormattedID','Estimate','State','Blocked','WorkProduct'],filters:{property: 'State',operator: '<',value: 'Completed'}});
A treegrid example: Rally.ui.grid.TreeGrid you referred is still a work in progress. I have not seen a working example of a story hierarchy using a treegrid but it does not mean it's impossible.
When I tested a story hierarchy, child stories did not appear under epic stories, however a story/task hierarchy worked. The filtering worked too. Here is an example:
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch:function(){
Ext.create('Rally.data.wsapi.TreeStoreBuilder').build({
models: ['userstory'],
autoLoad: true,
filters:[
{
property: 'Name',
operator: 'contains',
value: 'story'
}
],
enableHierarchy: true
}).then({
success: function(store) {
var grid = Ext.create('Ext.Container', {
items: [{
xtype: 'rallytreegrid',
columnCfgs: [
'Name',
'Owner'
],
store: store
}]
});
that.add(grid);
}
});
}
The screenshot below shows that tasks are nested under a child story as expected,but the child story is not nested under parent. The grid is filtered by Name as expected:

How to display ScheduleState and Blocked columns in custom Grid the way Rally's grid does

I'm trying to create a custom Rally app that displays data in a grid view. In another question Rally SDK App Using Grid with collapsible tree of children stories, nickm posted some sample code
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function() {
var today = new Date().toISOString();
Ext.create('Rally.data.wsapi.Store', {
model: 'UserStory',
fetch: ['ObjectID', 'FormattedID', 'Name', 'ScheduleState', 'Feature'],
autoLoad: true,
filters: [
{
property: 'Iteration.StartDate',
operator: '<=',
value: today
},
{
property: 'Iteration.EndDate',
operator: '>=',
value: today
},
{
property: 'Feature',
operator: '!=',
value: null
}
],
listeners: {
load: this._onDataLoaded,
scope: this
}
});
},
_onDataLoaded: function(store, records){
var that = this;
var promises = [];
_.each(records, function(story) {
promises.push(that._getFeature(story, that));
});
Deft.Promise.all(promises).then({
success: function(results) {
that._stories = results;
that._makeGrid();
}
});
},
_getFeature: function(story, scope) {
var deferred = Ext.create('Deft.Deferred');
var that = scope;
var featureOid = story.get('Feature').ObjectID;
Rally.data.ModelFactory.getModel({
type: 'PortfolioItem/Feature',
scope: this,
success: function(model, operation) {
fetch: ['State'],
model.load(featureOid, {
scope: this,
success: function(record, operation) {
var featureState = record.get('State')._refObjectName;
var storyRef = story.get('_ref');
var storyOid = story.get('ObjectID');
var storyFid = story.get('FormattedID');
var storyName = story.get('Name');
var storyState = story.get('ScheduleState');
var feature = story.get('Feature');
result = {
"_ref" : storyRef,
"ObjectID" : storyOid,
"FormattedID" : storyFid,
"Name" : storyName,
"ScheduleState" : storyState,
"Feature" : feature,
"FeatureState" : featureState,
"FeatureID" : featureOid
};
deferred.resolve(result);
}
});
}
});
return deferred;
},
_makeGrid: function() {
var that = this;
if (that._grid) {
that._grid.destroy();
}
var gridStore = Ext.create('Rally.data.custom.Store', {
data: that._stories,
groupField: 'FeatureID',
pageSize: 1000,
});
that._grid = Ext.create('Rally.ui.grid.Grid', {
itemId: 'storyGrid',
store: gridStore,
features: [{ftype:'grouping'}],
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'Name', dataIndex: 'Name',
},
{
text: 'ScheduleState', dataIndex: 'ScheduleState',
},
{
text: 'Feature', dataIndex: 'Feature',
renderer: function(val, meta, record) {
return '' + record.get('Feature').FormattedID + '';
}
},
{
text: 'Feature State', dataIndex: 'FeatureState',
}
]
});
that.add(that._grid);
that._grid.reconfigure(gridStore);
}
});
I'd like to display the ScheduleState and Blocked columns the same way that the Rally Grid shows them (as graphic representations). I've tried to figure out how to use templatecolumn xtype by using the following in my columnCfgs block:
{ text: 'State', dataIndex: 'ScheduleState', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.ScheduleStateTemplate') }
This fails and causes a JS error in the sdk-debug.js:
Uncaught TypeError: Cannot read property 'getAllowedValueStore' of
undefined sdk-debug.js:190539 Ext.define.loadStates
I get different errors with the Blocked column but I haven't been able to figure out how to get it to display as the red blocked icon.
With some tweaking I expect this to work in the next release of AppSDK2, but right now the rendering of the ScheduleState and Blocked will only work with wsapi data store. Using
tpl: Ext.create('Rally.ui.renderer.template.ScheduleStateTemplate')
is not sufficient. Custom store has no access to the valid states.
UPDATE
Rendering of ScheduleState and Blocked works in the current x version of AppSDK2 as of the date of this update, 5/22
<script type="text/javascript" src="/apps/x/sdk.js"></script>
This fix will eventually make its way to the next official release of AppSDK2 but for now you may use it with x version of AppSDK2.
Warning: x version of AppSDK is never stable - changes are made to it constantly.
See full code example here.
In x version of AppSDK you may do this:
{
text: 'ScheduleState', dataIndex: 'ScheduleState', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.ScheduleStateTemplate',
{
states: ['Defined', 'In-Progress', 'Completed', 'Accepted'],
field: {name: 'ScheduleState' }
})
},
{
text: 'Blocked', dataIndex: 'Blocked', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.BlockedTemplate')
}
Also, for now, set this to false in the grid config:
enableBlockedReasonPopover: false

Custom Rally Grid column together with Rally data columns

I am trying to create an app that will display all test sets in the current project and their states in terms of pass/fail totals.
Issues that I face (BTW just started learning ExtJS and Rally SDK yesterday):
- I need to understand how can I use the currently selected project as the one sued in the grid as a filter
- how should a test set pass/fail totals be queried and then displayed in a column in the grid - example : Test set 123 | 45/70
This is an example of an app that uses a project picker and builds a grid of Test Sets by Project. Also see a code example from this post. There is no filed in Web Services API where pass/fail totals are calculated. You will have to iterate over results and calculate totals in your code. I would encourage limiting the number of test case results by some criteria, e.g. CreationDate. In a scenario when test case results are automated the sheer volume of the data can be problematic.
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function() {
var c = Ext.create('Ext.Container', {
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
}
},
],
});
this.add(c);
},
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'],
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'),
TestCaseStatus: testset.get('TestCaseStatus')
};
testSets.push(ts);
});
this.updateGrid(testSets);
},
updateGrid: function(testSets){
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 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: 'TestCaseStatus', dataIndex: 'TestCaseStatus'
}
]
});
this.add(g);
},
});
The full html source of this example is available from this repo

ExtJS RowExpander - nextBd is null

I have problem configuring RowExpander for my grid. When the grid renders the expander is already opened for each row and with nothing inside. When i click on its icon the following error is generated: nextBd is null. I found very similar problem here http://www.sencha.com/forum/showthread.php?185837-Grid-Panel-PlugIn-Rowexpander-nextBd-is-null but the solution does not work for me and still dont get it why plugin config cannot be passed in initComponent method:
Here is my grid code:
Ext.define('GSIP.view.plans.PlanReqList' ,{
extend: 'Ext.grid.Panel',
alias : 'widget.gsip_devplan_list',
id: 'gsip_plan_list',
plugins: [{
ptype: 'rowexpander',
rowBodyTpl : [
'Nazwa:{name}'
]
}],
//title:i18n.getMsg('gsip.view.PlanReqList.title'),
layout: 'fit',
initComponent: function() {
this.store = 'DevPlan';
// this.plugins = [{
// ptype: 'rowexpander',
// rowBodyTpl : [
// {name}
// ]
// }];
this.features = [{ftype:'filters', encode:false, local:true},{ftype:'grouping'}];
this.tbar = [{
xtype:'commandbutton',
id: 'newReq',
iconCls:'icon-application_add',
text: i18n.getMsg('gsip.view.plans.PlanReqList.addReq'),
command: 'newReq',
}];
this.viewConfig = {
forceFit:true,
getRowClass: function(record, index) {
var c = record.get('elapsedPercent');
if (c >= 0) {
return 'elapsed-normal';
}
}
}
this.columns = [
{header: "Id", dataIndex: "id", width:50, sortable: true, filter:{type:'numeric'}},
{header: i18n.getMsg('gsip.view.plans.PlanReqList.column.name'), dataIndex: "name", flex:1, sortable: true, filter:{type:'string'} },
}
];
this.callParent(arguments);
},
The rowexpander plugin makes use of a feature called rowbody.
In your initComponent() you override this.features (which already includes rowbody) with this line:
this.features = [{ftype:'filters', encode:false, local:true},{ftype:'grouping'}];
Thus the rowbody feature is not included; thus the .x-grid-rowbody-tr class is not injected; thus rowexpander can't find such class for nextBd and returns null.
You should try:
var iNewFeatures = [{ftype:'filters', encode:false, local:true},{ftype:'grouping'}];
this.features = iNewFeatures.concat( this.features );
Lastly, plugins cannot be initiated in InitComponent(), you can either declare them as configs, or within the constructor. See this thread for more info.