SDK: filtering a rallycardboard - rally

How do you filter the cards in a rallycardboard using the SDK?
It seems simple enough, using the filter() function (though I'm not sure why there does not seem to be an option in the configs), but I can't seem to get it to work.
Here's my latest attempt:
var cardBoardConfig = {
xtype: 'rallycardboard',
types: ['PortfolioItem/Feature'],
columns: initiativeColumns,
attribute: 'Parent',
context: context
};
var cardBoard = this.add(cardBoardConfig);
var releaseFilter = Ext.create('Rally.data.QueryFilter',
{ property: 'Release.Name', operator: '!=', value: ''});
cardBoard.filter(releaseFilter);
I realize the filter it is looking for is actually a Ext.util.filter, but variants with that don't seem to work either. For example, I have tried:
var releaseFilter = Ext.create('Ext.util.Filter', { property: 'Release', operator: '=', value: null});
My goal here is to filter the cards based on whether they are assigned to a particular release or not.

If you look at the source for CardBoard's filter, you'll see that it's not implemented yet. That's pretty bad and we should fix that.
It looks like you can use cardboard.applyFilters([filter]) as an alternative. If that doesn't work, then this should work as a server side approach:
var cardBoardConfig = {
xtype: 'rallycardboard',
types: ['PortfolioItem/Feature'],
columns: initiativeColumns,
attribute: 'Parent',
context: context,
// have the server do the filtering
storeConfig: {
filters: [
{ property: 'Release.Name', operator: '!=', value: ''}
]
}
};
var cardBoard = this.add(cardBoardConfig);

Related

Virtual "name" field?

I need to have the name field of a model be virtual, created by concatenating two real fields together. This name is just for display only. I've tried the virtual examples in the doc, no luck. Keystone 4 beta5.
var keystone = require('keystone')
_ = require('underscore');
var Types = keystone.Field.Types;
/**
* Foo Model
* ==================
*/
var Foo = new keystone.List('Foo', {
map: {name: 'fooname'},
track: true
});
Foo.add({
bar: { type: Types.Relationship, required: true, initial: true, label: 'Barref', ref: 'Bar', many: false },
order: { type: Types.Select, required: true, initial: true, label: 'Order', options: _.range(1,100) },
price: { type: Types.Money, format: '$0,0.00', label: 'Price', required: true, initial: true },
});
Foo.schema.virtual('fooname').get(function() {
return this.bar+ ' ' + this.order;
});
Foo.defaultColumns = 'fooname, bar, order, price';
Foo.register();
When I use this model definition, I don't see the virtual name in the defaultcolumns list. I want to make a virtual name so lookups are easier when this model is used as a relationship.
You don't need a virtual to do this. Keystone allows you to track and recalculate a field every time the document is saved. You can enable those options in order to create a function which concatenates these two values for you (either synchronously or asynchronously, your choice.)
One other thing I noticed is that bar is a Relationship, which means you will need to populate that relationship prior to getting any useful information out of it. That also means your value function will have to be asynchronous, which is as simple as passing a callback function as an argument to that function. Keystone does the rest. If you don't need any information from this bar, and you only need the _id (which the model always has), you can do without the keystone.list('Bar') function that I included.
http://keystonejs.com/docs/database/#fields-watching
The map object also refers to an option on your model, so you'll need a fooname attribute on your model in any scenario, though it gets calculated dynamically.
var keystone = require('keystone'),
_ = require('underscore');
var Types = keystone.Field.Types;
/**
* Foo Model
* ==================
*/
var Foo = new keystone.List('Foo', {
map: {name: 'fooname'},
track: true
});
Foo.add({
fooname: { type: Types.Text, watch: true, value: function (cb) {
// Use this if the "bar" that this document refers to has some information that is relevant to the naming of this document.
keystone.list('Bar').model.findOne({_id: this.bar.toString()}).exec(function (err, result) {
if (!err && result) {
// Result now has all the information of the current "bar"
// If you just need the _id of the "bar", and don't need any information from it, uncomment the code underneath the closure of the "keystone.list('Bar')" function.
return cb(this.bar.name + " " + this.order);
}
});
// Use this if you don't need anything out of the "bar" that this document refers to, just its _id.
// return cb(this.bar.toString() + " " + this.order);
} },
bar: { type: Types.Relationship, required: true, initial: true, label: 'Barref', ref: 'Bar', many: false },
order: { type: Types.Select, required: true, initial: true, label: 'Order', options: _.range(1,100) },
price: { type: Types.Money, format: '$0,0.00', label: 'Price', required: true, initial: true },
});
Foo.defaultColumns = 'fooname, bar, order, price';
Foo.register();
try this:
Foo.schema.pre('save', function (next) {
this.name = this.bar+ ' '+ this.order;
next();
});
Could you provide more information? What is currently working? How should it work?
Sample Code?
EDIT:
After creating the model Foo, you can access the Mongoose schema using the attribute Foo.schema. (Keystone Concepts)
This schema provides a pre-hook for all methods, which registered hooks. (Mongoose API Schema#pre)
One of those methods is save, which can be used like this:
Foo.schema.pre('save', function(next){
console.log('pre-save');
next();
});

Building FAQ lists with Keystone Lists

Here is the pseudo-code of what I want:
FAQ = {
name: 'Foobar FAQ',
items:[
//question/answer pairs here
]
}
How can I accomplish this in Keystone?
Here's what I've got so far:
var keystone = require('keystone');
var Types = keystone.Field.Types;
var FAQ = new keystone.List('FAQ',{
track: true
});
FAQ.add({
name: {type: String}
items: {} // ???
});
FAQ.register();
I'm unsure how to accomplish this. I'm brand new to React, Keystonejs and Mongodb.
This can be done through a Relationship field type.
items: { type: Types.Relationship, ref: 'Pair', many: true }
where Pair is the name of your Question/Answer pair list object.
For more info, see: http://keystonejs.com/docs/database/#relationships

Filtering items in a rallymultiobjectpicker

I want to present the user with a checkbox list of portfolio item features which are currently assigned to a particular release.
The rallymultiobjectpicker with modelType set to portfolioitem/feature looks perfect for this, but it is unclear to me how to filter the objects that it displays.
Presumably this is in the storeConfig parameter? I've tried the following to no effect:
{
xtype: 'rallymultiobjectpicker',
modelType: 'portfolioitem/feature',
fieldLabel: 'Select Features',
storeConfig: {
filters: [{
property: 'Release.Name',
operator: '=',
value: myRelease
}]
},
}
As a workaround until the bug is fixed, you might try filtering on the load, like this:
{
xtype: 'rallymultiobjectpicker',
modelType: 'portfolioitem/feature',
fieldLabel: 'Select Features',
storeConfig: {
fetch: ['Release','Name']
},
storeLoadOptions: function(records) {
var store = this;
Ext.Array.each(store.getRecords(), function(record,idx){
var release = record.get('Release');
var name = null;
if ( !release || release.Name !== myRelease ) {
store.remove(record);
}
});
}
}
It's going to be slower because it goes and gets all the features first, but it seems to work.

Manually adding columns to rallycardboard component

I am creating a rallycardboard where each column represents a release, and the cards are Features to be scheduled into those releases. The default mechanics of the component render all available releases as columns on the board. For our particular application this is unreasonable, since there are thousands of Releases in our workspace.
I was able to overwrite the addColumn method to only include a column if it is a Release which at least one Feature from the group is assigned to. The next step is to make it so that a user can manually add a Release column that doesn't currently have any assigned work. To do this, I stored all the excluded columns from the first step and created a combo box with those values. I would like it so that when the user selects a Release from the combo box, that Release column is added to the board.
I was able to reconfigure my addColumn method to allow for manual override (as apposed to trying to match with an existing Feature's Release). I verified that the column was added to the boards columns by calling board.getColumns() and the configurations look the same for both the existing and added columns. However, I get an error message when calling board.renderColumns() which appears to be the result of trying to write to a container that doesn't yet exist (the column isn't created yet).
Maybe I'm going about this the wrong way. Is there another way I can more easily decide which columns to include, and which to exclude, on the rallycardboard component?
Here is an example where the board columns are based on releases that have features scheduled. To add a columns for releases that have no features currently scheduled select releases from the mulitpicker.
The app is available in this github repo.
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
_releasesWithFeatures: [],
_uniqueColumns: [],
_additionalColumns: [],
_updatedColumns: [],
_cardBoard: null,
launch: function() {
var that = this;
this._releasePicker = Ext.create('Rally.ui.picker.MultiObjectPicker', {
fieldLabel: 'Choose a Release',
modelType: 'Release'
});
this.add(this._releasePicker);
this.add({
xtype: 'rallybutton',
id: 'getReleases',
text: 'Add Selected Releases',
handler: function(){
that._getSelectedReleases();
}
})
Ext.create('Rally.data.WsapiDataStore', {
model: 'PortfolioItem/Feature',
fetch: ['FormattedID','Name','Release'],
pageSize: 100,
autoLoad: true,
filters: [
{
property: 'Release',
operator: '!=',
value: null
}
],
listeners: {
load: this._onScheduledFeaturesLoaded,
scope: this
}
});
},
_onScheduledFeaturesLoaded: function(store, data){
var that = this;
if (data.length !==0) {
_.each(data, function(feature){
console.log('feature ', feature.get('FormattedID'), 'scheduled for ', feature.get('Release')._refObjectName, feature.get('Release')._ref);
that._releasesWithFeatures.push(feature.get('Release'))
});
that._makeBoard();
}
else{
console.log('there are no features scheduled for a release')
}
},
_makeBoard: function(){
if (this._cardBoard) {
this._cardBoard.destroy();
}
var columns = [];
_.each(this._releasesWithFeatures, function(rel){
columns.push({value: rel._ref, columnHeaderConfig: {headerTpl: '{release}', headerData: {release: rel._refObjectName}}});
});
this._uniqueColumns = _.uniq(columns, 'value');
var cardBoard = {
xtype: 'rallycardboard',
itemId: 'piboard',
types: ['PortfolioItem/Feature'],
attribute: 'Release',
fieldToDisplay: 'Release',
columns: this._uniqueColumns
};
this._cardBoard = this.add(cardBoard);
},
_getSelectedReleases: function(){
var that = this;
var expandedColumns = [];
var selectedReleases = this._releasePicker._getRecordValue();
console.log(selectedReleases.length);
if (selectedReleases.length > 0) {
_.each(selectedReleases, function(rel) {
var releaseName = rel.get('Name');
var releaseRef = rel.get('_ref');
that._additionalColumns.push({value: releaseRef, columnHeaderConfig: {headerTpl: '{release}', headerData: {release: releaseName}}});
});
}
expandedColumns = _.union(that._uniqueColumns, that._additionalColumns);
this._updatedColumns = _.uniq(expandedColumns, 'value');
this._updateBoard();
},
_updateBoard: function(){
var that = this;
if (this._cardBoard) {
this._cardBoard.destroy();
}
var cardBoard = {
xtype: 'rallycardboard',
types: ['PortfolioItem/Feature'],
attribute: 'Release',
fieldToDisplay: 'Release',
columns: that._updatedColumns,
};
this._cardBoard = this.add(cardBoard);
}
});

Rally App2.0 - Retrieve a specific story

I am getting the hang of 2.0, but a bit stuck on something that seems simple.
Basically, I have created a new app for my team to use (thanks all for the help). I thought it would be cool to have a way I could add messages to the dashboard.
I decided the easiest way to accomplish this, is to create a story and in my code simply query that one story, grab the description and show it in the app. Sounds easy enough right?
I am having a bit of a time simple running out grabbing the Description field and showing it. I know it sounds odd, but it seems so complicated. I have tried this way
showMessage: function (message) {
debugger;
this.add({
xtype: 'label',
html: message
});
},
getMessage: function () {
var defectStore = Ext.create('Rally.data.WsapiDataStore', {
model: 'UserStory',
fetch: ['Description'],
filters: [{
property: 'FormattedID',
operator: '=',
value: 'US13258'
}],
autoLoad: true,
listeners: {
load: function (store, records) {
debugger;
if (records)
return records[0].get("Description");
}
}
});
},
But seems to get caught up in event spaghetti. Surely there is an easier way :)
Just want to go grab a specific stories description field...
You can use a model's load method to do this:
var storyOid = 12345;
//Get the story model
Rally.data.ModelFactory.getModel({
type: 'UserStory',
success: function(model) {
//Load the specific story
model.load(storyOid, {
fetch: ['Description']
success: function(record) {
//success!
var description = record.get('Description');
}
});
}
});
I'm not sure why you're trying to do it with a listener, but I would just call load and get the result upon success, like so:
getMessage: function (storyID) {
var defectStore = Ext.create('Rally.data.WsapiDataStore', {
model: 'UserStory',
fetch: ['Description'],
filters: [{
property: 'FormattedID',
operator: '=',
value: storyID
}],
autoLoad: true
});
defectStore.load({scope: this, callback: function(records, operation, success) {
if(success){
console.log(records[0].get('Description')); // additional logic here
} else {
console.log('You ruined the store. Jerk.');
}
}});
}
I'm thinking you might have some issues, though, unless call showMessage after you check for success, as extJS operates asynchronously.