Sorting and filtering data based on the keyword in name - rally

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)

Related

Filter Control for Grid/TreeGrid

Is the Filter Control component available for the Grid/TreeGrid (similar to the filter within the Portfolio Items dashboard)? Per the SDK 2.0 documentation, it seems this component is only available for the GridBoard.
gridboard is a wrapper around treegrid or board, and filtercontrol plugin should work with a TreeGrid. Here is an example of a tree grid with enableHierarchy set to true and a rallygridboardcustomfiltercontrol plugin:
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function() {
Ext.create('Rally.data.wsapi.TreeStoreBuilder').build({
models: ['userstory'],
autoLoad: true,
enableHierarchy: true
}).then({
success: this._onStoreBuilt,
scope: this
});
},
_onStoreBuilt: function(store) {
var modelNames = ['userstory'];
var context = this.getContext();
this.add({
xtype: 'rallygridboard',
modelNames: modelNames,
context:context,
enableHierarchy: 'true',
toggleState: 'grid',
plugins: [
{
ptype: 'rallygridboardcustomfiltercontrol',
filterControlConfig: {
modelNames: modelNames
}
}
],
cardBoardConfig: {
attribute: 'ScheduleState'
},
gridConfig: {
store: store,
columnCfgs: [
'Name',
'ScheduleState',
'Owner',
'PlanEstimate'
]
},
height: this.getHeight()
});
}
});

Report to display all top level stories or portlio items in blocked status and include blocked reason

I would love a report that would display all top level user stories (or better yet portfolio items, if possible) that have one or more tasks in blocked status, with the blocked reasons included in the report. This would be across an entire release
I am thinking that many others would also want to view data like this as well, however I couldn't find this in any of the canned reports. Anyone familiar with a way to do this?
Thanks
Here is a custom app that builds a grid of blocked tasks with their workproducts. When a workproduct has a epic (parent) story and a parent feature, those are also shown in respective columns.
The full code is available in this github repo. You may copy/paste the html file into a custom page in Rally.
The js source:
Ext.define('CustomApp', {
extend: 'Rally.app.TimeboxScopedApp',
componentCls: 'app',
scopeType: 'iteration',
comboboxConfig: {
labelWidth: 100,
width: 300
},
onScopeChange: function() {
var filter = this.getContext().getTimeboxScope().getQueryFilter();
filter = filter.and({
property: 'Blocked',
value: true
});
filter.toString();
Ext.create('Rally.data.WsapiDataStore', {
model: 'Task',
fetch: ['ObjectID', 'FormattedID','Name', 'WorkProduct','Blocked', 'BlockedReason', 'Parent', 'Feature'],
autoLoad: true,
filters: [filter],
listeners: {
load: this._onDataLoaded,
scope: this
}
});
},
_onDataLoaded: function(store, data){
var tasks = [];
Ext.Array.each(data, function(task) {
console.log('Blocked',task.get('Blocked'));
console.log(task.get('WorkProduct')._type);
var t = {
ObjectID: task.get('ObjectID'),
FormattedID: task.get('FormattedID'),
Name: task.get('Name'),
_ref: task.get('_ref'),
WorkProduct: task.get('WorkProduct'),
Blocked: task.get('Blocked'),
BlockedReason: task.get('BlockedReason'),
WorkProductType: task.get('WorkProduct')._type
};
tasks.push(t);
}, this);
this._createGrid(tasks);
},
_createGrid: function(tasks) {
var that = this;
var myStore = Ext.create('Rally.data.custom.Store', {
data: tasks,
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: '', dataIndex: 'Name'
},
{
text: 'WorkProduct', dataIndex: 'WorkProduct',
renderer: function(val, meta, record) {
var type;
if(record.get('WorkProduct')._type === "HierarchicalRequirement") {
type = 'userstory';
}
else if(record.get('WorkProduct')._type === "Defect"){
type = 'defect';
}
return '' + record.get('WorkProduct').FormattedID + '';
}
},
{
text: 'Blocked Reason', dataIndex: 'BlockedReason'
},
{
text: 'Parent of WorkProduct', dataIndex: 'WorkProduct',
renderer: function(val, meta, record) {
if(record.get('WorkProduct')._type !== "HierarchicalRequirement") {
return 'n/a'
}
else{
return '' + record.get('WorkProduct').Parent.FormattedID + '';
}
}
},
{
text: 'Feature of WorkProduct', dataIndex: 'WorkProduct',
renderer: function(val, meta, record) {
if(record.get('WorkProduct')._type !== "HierarchicalRequirement") {
return 'n/a'
}
else{
return '' + record.get('WorkProduct').Feature.FormattedID + '';
}
}
}
]
});
}else{
this.grid.reconfigure(myStore);
}
}
});

How do I Query For Defects where with a requirement under a particular feature

When using a custom grid query based on defects I often filter based on the tags of the linked user story. I would like to use the actual feature hierarchy. e.g. Show all defects where the linked story is under a given feature or initiative. I could not work this out from looking at the documentation
Tags attribute exists on Artifact object from which Requirement inherits, hence Requirement.Tags can be traversed. Feature attribute does not exist on Requirement. It exists on HierarchicalRequirement, which inherits from Requirement, hence Requirement.Feature cannot be traversed.
In this context a custom grid may not be a suitable choice. But a custom app can be written that shows all those relationships. Here is a custom app that has two comboboxes: a Release and a Feature. The Feature combobox is populated based on the selection in the Release combobox. When a feature is selected from the second combobox a grid is populated with the feature's child stories and defects (if any) associated with those stories.
You may see the full code in this repo, and copy the html file into a custom page.
Here is the js file:
Ext.define('CustomApp', {
extend: 'Rally.app.TimeboxScopedApp',
componentCls: 'app',
scopeType: 'release',
comboboxConfig: {
fieldLabel: 'Select a Release:',
labelWidth: 100,
width: 300
},
addContent: function() {
this._makeCombobox();
},
onScopeChange: function() {
this._makeCombobox();
},
_makeCombobox: function() {
if (this.down('#features')) {
this.down('#features').destroy();
}
var features = Ext.create('Rally.ui.combobox.ComboBox',{
id: 'features',
storeConfig: {
model: 'PortfolioItem/Feature',
fetch: ['FormattedID','Name','Release', 'UserStories'],
pageSize: 100,
autoLoad: true,
filters: [this.getContext().getTimeboxScope().getQueryFilter()]
},
fieldLabel: 'select Feature',
listeners:{
ready: function(combobox){
if (combobox.getRecord()) {
console.log('ready',combobox.getRecord().get('_ref'));
this._onFeatureSelected(combobox.getRecord());
}
else{
console.log('selected release has no features');
if (this.down('#grid')) {
this.down('#grid').destroy();
}
}
},
select: function(combobox){
if (combobox.getRecord()) {
console.log('select',combobox.getRecord().get('_ref'));
this._onFeatureSelected(combobox.getRecord());
}
},
scope: this
}
});
this.add(features);
},
_onFeatureSelected:function(feature){
console.log('feature', feature.get('Name'));
var f = {
FormattedID: feature.get('FormattedID'),
Name: feature.get('Name'),
_ref: feature.get("_ref"),
UserStories: []
};
var collection = feature.getCollection('UserStories', {fetch: ['Name','FormattedID','Owner', 'Defects']});
var that = this;
var count = collection.getCount();
console.log(count);
var stories = [];
var pendingStories = count;
collection.load({
callback: function(records, operation, success){
Ext.Array.each(records, function(story){
var s = {
FormattedID: story.get('FormattedID'),
Name: story.get('Name'),
_ref: story.get("_ref"),
DefectCount: story.get('Defects').Count,
Defects: []
};
var defects = story.getCollection('Defects');
var defectcount = defects.getCount();
var pendingDefects = defectcount;
defects.load({
fetch: ['FormattedID'],
callback: function(records, operation, success){
Ext.Array.each(records, function(defect){
s.Defects.push({_ref: defect.get('_ref'),
FormattedID: defect.get('FormattedID')
});
}, this);
--pendingDefects;
if (pendingDefects === 0) {
console.log(story.get('FormattedID') + ' - ' + story.get('Name'));
--pendingStories;
if (pendingStories === 0) {
console.log('stories inside callback',stories);
}
}
console.log('makeGrid');
that._makeGrid(stories);
},
scope: this
});
stories.push(s);
}, this);
}
});
},
_makeGrid: function(stories) {
var c = Ext.create('Ext.Container', {
layout: {
type: 'absolute'
},
x: 400
});
this.add(c);
this._store = Ext.create('Rally.data.custom.Store', {
data: stories,
pageSize: 100,
remoteSort:false
});
if (!this.down('#grid')){
c.add({
xtype: 'rallygrid',
itemId: 'grid',
store: this._store,
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'Name', dataIndex: 'Name'
},
{
text: 'Defect Count', dataIndex: 'DefectCount'
},
{
text: 'Defects', dataIndex: 'Defects',
renderer: function(value) {
var html = [];
Ext.Array.each(value, function(defect){
html.push('' + defect.FormattedID + '')
});
return html.join(', ');
}
}
]
});
}
else{
this.down('#grid').reconfigure(this._store);
}
}
});

How to create a combobox of possible values

Is there a way to dynamically populate a combobox with the attributes a certain property of an artifact can take on?
e.g.
I have a custom field set up on User Stories. I want to be able to populate a combobox with all the possible values for this custom field without hard-coding it in.
In the code below the combobox is automatically populated with the allowed values of the custom field of dropdown type:
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
items: [
{
xtype: 'container',
itemId: 'kbFilter'
},
{
xtype: 'container',
itemId: 'grid',
width: 800
}
],
launch: function() {
this.down('#kbFilter').add({
xtype: 'checkbox',
cls: 'filter',
boxLabel: 'Filter table by custom field',
id: 'kbCheckbox',
scope: this,
handler: this._onSettingsChange
});
this.down('#kbFilter').add({
xtype: 'rallyattributecombobox',
cls: 'filter',
model: 'Defect',
field: 'MyKB',
listeners: {
ready: this._onKBComboBoxLoad,
select: this._onKBComboBoxSelect,
scope: this
}
});
},
_onKBComboBoxLoad: function(comboBox) {
this.kbComboBox = comboBox;
Rally.data.ModelFactory.getModel({
type: 'Defect',
success: this._onModelRetrieved,
scope: this
});
},
_getFilter: function() {
var filter = [];
if (Ext.getCmp('kbCheckbox').getValue()) {
filter.push({
property: 'MyKB',
operator: '=',
value: this.kbComboBox.getValue()
});
}
return filter;
},
_onKBComboBoxSelect: function() {
if (Ext.getCmp('kbCheckbox').getValue()) {
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',
'MyKB'
],
storeConfig: {
context: this.context.getDataContext(),
filters: this._getFilter()
},
showPagingToolbar: false
});
}
});
In this example I have a dropdown field with Name: myKB and Display Name: My KB.
In the WS API the name shows with prepended c_, as in c_myKB.
However, if I use c_myKB this error comes up:
Uncaught Rally.ui.combobox.FieldValueComboBox._populateStore(): field config must be specified when creating a Rally.ui.combobox.FieldValueComboBox
Use the display name of the field, without spaces.
Here is a screenshot showing this app in action:

Rally grid of features and rollups

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);
}
});