Is this a bug? When I query for portfolio items, and in the store I fetch:
fetch: ['UserStories', 'HierarchicalRequirement']
I don't see any children of the PI in the data returned. I checked with the manual API for that same OID and confirmed it does have children.
If I do a query with HierarchicalRequirements instead of PortfolioItems, and fetch the 'Children' field, I can see the entire Hierarchy.
What gives with PI's? Is the query broken for children?
Querying against the generic PortfolioItem endpoint will return you PIs from all levels of the hierarchy- most will have a Children collection of PIs. Only the bottom level PI will have a UserStories collection.
Assume you have a PI hierarchy like so:
Top
Middle
Bottom
To get Top items you'd query the PortfolioItem/Top type and fetch Children to include the Middle items.
To get Bottom items you'd query the PortfolioItem/Bottom type and fetch UserStories to include its children.
Edit: I'm able to get user stories back using this code in both 2.0p3 and 2.0p4:
Ext.create('Rally.data.WsapiDataStore', {
model: 'PortfolioItem/Feature',
context: this.getContext().getDataContext(),
fetch: ['UserStories', 'Name', 'FormattedID'],
autoLoad: true,
listeners: {
load: function(store, records) {
Ext.each(records, function(record) {
var stories = record.get('UserStories');
Ext.each(stories, function(story) {
console.log(story.FormattedID + ' - ' + story.Name);
});
});
}
}
});
Related
Question: How can I add pagination (infinite scroll) to my binded Firestore VuexFire reference without re-querying previously retrieved (and binded) results?
Background:
I am currently using VuexFire firestore binding to fill a timeline for most upvoted posts, as an action, in my Vuex store like this:
fillTimeLine: firebaseAction(
({ bindFirebaseRef }) => {
bindFirebaseRef(
'timelineResults',
db
.collection('POSTS')
.orderBy('combined_vote_score', 'desc')
.limit(30)
)
})
This will retrieve the top 30 highest rated posts in my firestore database to my vuex state variable timelineResults.
To add pagination I have found a non-VuexFire example like this:
How to paginate or infinite scroll by number of items in firestore?
var first = db.collection("....").orderBy("price", "desc").limitTo(20);
return first.get().then(function (documentSnapshots) {
// Get the last visible document
var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
console.log("last", lastVisible);
// Construct a new query starting at this document,
// get the next 25 cities.
var next = db.collection("....")
.orderBy("price", "desc")
.startAfter(lastVisible)
.limit(20);
});
Is there a way to combine the two examples and append results to a binded reference?
You could create a more generic action, just like this:
bindRef: firestoreAction(({ bindFirestoreRef }, { name, ref }) => {
bindFirestoreRef(name, ref);
}),
And then using it like:
this.bindRef({
name: 'timelineResults',
ref: db
.collection('POSTS')
.orderBy('combined_vote_score', 'desc')
.limit(30),
});
There you can change the ref according to your needs. In this case, when you detect the scroll limit:
// lastVisible: using the array position from the previous binding
// since with vuex's bound data you cannot get the snapshots
this.bindRef({
name: 'timelineResults',
ref: db
.collection('POSTS')
.orderBy('combined_vote_score', 'desc')
.startAfter(lastVisible)
.limit(20),
});
I am creating a rallygrid component and would like to have the grid items grouped by their parent's Name attribute (bonus if I could also display the ID of the parent). I added the groupBy:'Parent' configuration to the storeConfig of the grid and was surprised that no results were returned. I also tried using groupBy:'Parent.Name' but still nothing.
I know this is possible with other fields such as Owner, but I'm at a loss as to why the Parent wouldn't be usable as well. Is this a bug, or am I setting the config up incorrectly?
Thanks
Change the storeConfig to keep the records from trying to update after being grouped:
storeConfig : {
remoteSort : false,
remoteGroup : false,
remoteFilter : false,
}
Add a listener to the load event which assigns a root level property to the record and groups by that record value. (For some reason store.group('Parent.Name'); doesn't work.)
load: function(store) {
store.each(function(record) {
record.set('ParentName', record.get('Parent') && record.get('Parent').Name || '-- Unparented --');
});
store.group('ParentName');
}
I thought it was a bug with the SDK too, but per WS API documentation, Parent, unlike Owner, or Feature is not sortable.
So when I use groupField: 'Parent' the grid is empty, and response showed error:
Ext.data.JsonP.callback6({"QueryResult": {..., "Errors": ["Cannot sort using attribute Parent"]
It is trying to sort by Parent, but Parent attribute is not sortable. So the SDK ran into a WS API limitation.
On a side note, I did not use groupBy, instead I used groupField on the store (in this example I grouped by Kanban field) :
var myStore = Ext.create('Rally.data.WsapiDataStore',{
model: 'UserStory',
groupField: 'c_MyKB',
//...
});
And then used features: [{ftype:'grouping'}] in the grid.
this._myGrid = Ext.create('Ext.grid.Panel', {
store: myStore,
features: [{ftype:'grouping'}],
//...
I'd like to put together a StoryMap app using Initiative (second level) portfolio items as the backbone (columns). To do this the app needs to query for all the second level portfolio items, and then use each PI as a column header in the rallycardboard.
I've gotten the cardboard to display the column headers correctly, but I have not been able to get it to display the cards, which should be the first level of portfolio items (PortfolioItem/Feature).
Here is my code so far:
launch: function() {
this._getInitiativeStore();
},
_getInitiativeStore: function() {
this.initiativeStore = Ext.create('Rally.data.wsapi.Store', {
model: 'PortfolioItem/Initiative',
fetch: ['Name', 'Children'],
autoLoad: true,
listeners: {
load: this._createCardBoard,
scope: this
}
});
},
_createCardBoard: function(store, records) {
var initiativeColumns = [];
Ext.each(records, function(record) {
initiativeColumns.push({
xtype: 'rallycardboardcolumn',
columnHeaderConfig: {
xtype: 'rallycardboardcolumnheader',
fieldToDisplay: 'Name',
record: record,
},
cardConfig: {
xtype: 'rallycard',
record: 'PortfolioItem/Feature'
},
fields: ['Name', 'Parent'],
valueField: 'Parent',
value: record.get('_ref') // BUG FIXED HERE. Was: record.get('Parent')
});
}, this);
var cardBoardConfig = {
xtype: 'rallycardboard',
types: ['PortfolioItem/Feature'],
columns: initiativeColumns,
attribute: 'Parent',
};
var cardBoard = this.add(cardBoardConfig);
console.log('cardboard', cardBoard);
}
I realize I am using this perhaps a bit differently than the authors have planned for, but I'm willing to extend the rallycardboard and rallycolumnheader objects with Ext.define if that's what it takes. I'm starting to look at the Rally source code but its slow going so far.
I was able to figure out the problem by using Ext.define() to override the cardboardcolumn getStoreFilter function to print out its filter value. Probably for somebody good with a browser debugger that would not have been necessary, but I'm not and it pinpointed the problem right away: The "value" field of the initiativeColumn configs should have been record.get('_ref'), not record.get('Parent'). I'll edit the code above so it works.
Now the basic board works great as a story map with portfolio items! Next step is to see if I can incorporate the concept of releases into the map.
Also, I think I found a bug in the 'rallycardboard' constructor-- if I pass it a context reference like: context: { project: 'project/XXX'} where XXX is an OID, it crashes. Instead I need to instantiate a context object and pass that. But that's inconsistent from other items like the wsapi store. Workaround is easy, but it is a bit annoying.
Best I can tell, sorting any column in one of my grids (which is configured with a store calling the LBAPI) re-executes all of my App code from the store's definition onward. Is this expected behavior? I would not think we would need to re-query the data used in the grid to sort it.
I defined the store as follows:
// fetch the snapshot of all leaf level stories for the PI
var ssPiLeafStories = Ext.create('Rally.data.lookback.SnapshotStore', {
context: {
workspace: this.context.getWorkspace(),
project: this.context.getProject()
},
pageSize: 10000000,
fetch: find,
rawFind: query,
hydrate: ["ScheduleState", "c_" + this.gKanbanStateFieldName],
autoLoad: true,
listeners: {
scope: this,
load: this._processPiLeafStories
}
});
...sorting any column in the grid drives all of the App's logic from the function _processPiLeafStories and beyond (I think!). Any suggestions on how to just sort the grid instead of re-running the bulk of the App would be appreciated.
Thanks!
I am working to create an RPM tree that can be used to select leaf stories from different levels of the RPM hierarchy.
I was able to get close to the desired functionality by using the method described here, however there appear to be some inconsistencies in the number of leaf stories being returned at each level of the RPM.
The following Image shows the tree (Project names covered for privacy purposes). The yellow badges show the number of leaf stories found below that level of the RPM hierarchy. As you can see from the image, the numbers are inconsistent. (Below the Initiative shows 23 leaf stories and below one of the Rollups shows 44.) There are in fact 44 leaf stories below that Rollup so the problem appears to be from querying at the Initiative level.
Here is the function I wrote which is intended to return an array of all the OIDs for leaf stories below the selected RPM node:
function getDescendants(OID, callback) {
Ext.create('Rally.data.lookback.SnapshotStore', {
autoLoad: true,
pageSize: 1000000,
fetch: ['ObjectID'],
filters: [
{ property: '_ItemHierarchy', value: OID },
{ property: 'Children', value: null },
{ property: '__At', value: new Date().toISOString() },
{ property: '_TypeHierarchy', value: 'HierarchicalRequirement' }
],
listeners: {
load: function(model, data, success) {
if (data && data.length && success) {
var descendants = [];
Ext.Array.each(data, function(story) {
descendants.push(story.get('ObjectID'));
});
callback(Ext.Array.unique(descendants));
} else {
callback([]);
}
}
}
});
}
That query looks correct to me. I think you've encountered a known defect that existed in the data stream to the Lookback API. The problem in the stream has been corrected, but the work to go back and correct the faulty history is still in the team backlog. If you want to track its progress with Support, the defect has ID DE15647.
The workaround (since you're only querying the current data) is to un-parent and re-parent affected items.
Sorry.
Edit: Some more detail on the problem - For a period of time, whenever a PortfolioItem (Strategy, Theme, Feature, Initiative) was created and had its parent set at the same time, the Lookback API service didn't get notified of the new PortfolioItem's parent. This problem is now resolved, but the old data is still to be fixed. You could search for PIs that potentially have this problem by looking for ones in that _ItemHierarchy with a null Parent field.
To get the PIs with null parents (potential orphans):
fetch: ['ObjectID', '_ValidFrom', '_ValidTo'],
filters: [
{ property: '_ItemHierarchy', value: OID }, // only include this if you're limiting to a sub-hierarchy
{ property: '_TypeHierarchy', value: 'PortfolioItem' },
{ property: 'Parent', value: null },
{ property: '__At', value: 'current' }
]
For each of these 'orphans', check for a parent that has it as a child:
fetch: ['ObjectID'],
filters: [
{ property: 'Children', value: currentChild.ObjectID },
{ property: '_TypeHierarchy', value: 'PortfolioItem' },
{ property: '_ValidFrom', operator: '<=' value: currentChild._ValidFrom.toISOString() },
{ property: '_ValidTo', operator: '>' value: currentChild._ValidFrom.toISOString() }
]
For each one, if you find a parent that's claiming the child (at the time the child was created), you know you've a snapshot affected by this problem and can fix it by clearing its parent in ALM, saving, then resetting its parent and saving again. I included __At: 'current' in the first check, since there's a chance the orphaned PI had a different parent assigned later and you wouldn't want to overwrite that. Hope this helps.