Hydrate object fields - rally

I want to get Feature object for User Stories that I got from lookback API.
But when I try to hydrate Feature I get only UnFormatted feature ID.
Can I get real Feature objects for User Stories from lookback result set?
Below the example of code that I use for retrieving data:
storeConfig: {
find: {
"_TypeHierarchy": { '$in' : [-51038] },
"Children": null
},
fetch: ["ScheduleState", "PlanEstimate", "ObjectID", "_ValidFrom", "_ValidTo", "c_BaselineDeliveryConfidence", "Name", "Feature"],
hydrate: ["ScheduleState", "c_BaselineDeliveryConfidence", "Name", "Feature"],
sort: {
"_ValidFrom": 1
},
compress: true,
useHttpPost: true

It is not possible to hydrate objects straight out of the LBAPI. However, I have been working on a helper class to do just that, using a method similar to what Nick suggested.
https://github.com/ConnerReeves/RallyHelpers/blob/master/RecordHydrator/RecordHydrator.js
Here's an example of how it's used. I'm gathering all leaf User Stories (that have an iteration assignment) and then hydrating that Initiative field:
launch: function() {
var self = this;
Ext.create('Rally.data.lookback.SnapshotStore', {
limit : Infinity,
fetch : ['Name','Iteration'],
filters : [{
property : '__At',
value : 'current'
},{
property : '_TypeHierarchy',
value : 'HierarchicalRequirement'
},{
property : 'Iteration',
operator : '!=',
value : null
},{
property : 'Children',
value : null
}]
}).load({
params : {
compress : true,
removeUnauthorizedSnapshots : true
},
callback : function(records, operation, success) {
self._hydrateRecords(records);
}
});
},
_hydrateRecords: function(records) {
Ext.create('CustomApp.RecordHydrator', {
fields: [{
name : 'Iteration',
hydrate : ['Name','StartDate','EndDate']
}]
}).hydrate(records).then({
success: function(hydratedRecords) {
console.log(_.groupBy(hydratedRecords, function(record) {
return record.get('Iteration') && record.get('Iteration').get('Name');
}));
}
});
}

Feature is a full object to which a user story has a reference to (via Feature attribute).
Your code which is similar to this query:
https://rally1.rallydev.com/analytics/v2.0/service/rally/workspace/111/artifact/snapshot/query.js?find={"_TypeHierarchy":"HierarchicalRequirement"}&fields=["Name","ScheduleState","PlanEstimate","Feature"]&hydrate=["ScheduleState"]
will return something like this:
{
Feature: 12483739639,
Name: "my story",
ScheduleState: "Defined",
PlanEstimate: 3
}
where 12483739639 is ObjectID of the Feature. Adding "Feature" to the hydrate will not make a difference.
If you want to get the full Feature object or some of its attributes, in your code you may use the OID of the feature and issue a separate query. You may also push those OIDs into an array and use $in operator in that second query.

Related

How to display leaf node story count on a board

I' m struggling to display leaf node count on individual cards of a feature story board. I' m using Lookback API to query leaf node stories. Below code is not displaying data correctly and its skipping the field for an entire column. Could you please let me know what I 'm doing wrong here or if there's an easy way to do this. I 'm a novice app developer. Thanks in advance!
cardConfig: {
editable: false,
showIconsAndHighlightBorder: false,
fields: [
'Name',
{
name: 'LeafCount', //field name
hasValue: function() {return true;}, //always show this field
renderTpl: Ext.create('Rally.ui.renderer.template.LabeledFieldTemplate', {
fieldLabel: 'Leaf story Count', //the field label
valueTemplate: Ext.create('Ext.XTemplate',
['{[this.getLeafCount(values)]}',
{getLeafCount: function(data) {
var OID = data.ObjectID;
snapshotStore = Ext.create('Rally.data.lookback.SnapshotStore', {
listeners: {
load: function(store, records, success) {
console.log(records.length);
count = records.length;
}
},
autoLoad: true,
useHttpPost: true,
fetch: ['Name', 'FormattedID', 'ObjectID'],
find: {
"_ItemHierarchy": OID,
"_TypeHierarchy": "HierarchicalRequirement",
"Children": null,
"__At": "current"
},
scope: this
});
return count;
// return snapshotStore.totalCount;
}
}])
})
},
//additional string fields
'c_StoryType', 'Project', 'PlanEstimate', 'Parent', 'TaskEstimateTotal', 'TaskRemainingTotal']
},
This isn't working for you because all the data needs to be available when the template is evaluated- there isn't time to do an async store load before rendering the value.
For this specific case can't you just show the built-in LeafStoryCount field on your cards?

How to query for all Artifacts of a Milestone

I am attempting to create reports on Milestones using App SDK 2.0 and would like to find all User Stories that have been assigned to the Milestone.
I tried pulling out the Artifacts from a Milestone using getCollection.
Ext.create('Rally.data.wsapi.Store', {
model : 'Milestone',
filters : [ {
property : 'ObjectID',
operator : '=',
value : milestone.get("ObjectID")
} ],
fetch : [ 'Artifacts' ],
limit : Infinity,
autoLoad : true,
listeners : {
load : function(store, records) {
var record = records[0];
var info = record.get('Artifacts');
var count = info.Count;
record.getCollection('Artifacts').load({
fetch : [ 'ObjectID' ],
callback : function(records, operation, success) {
Ext.Array.each(records, function(artifact) {
console.log(artifact.get('ObjectID'));
});
}
});
}
}
});
I get the following error:
Uncaught Rally.data.ModelFactory.getModel(): Could not find registered
factory for type: Artifact sdk-debug.js:7078
From https://rally1.rallydev.com/slm/doc/webservice/, it doesn't seem that Milestones is query able on the User Story or PortfolioItem. I tried it anyway using Tasks syntax and it nothing was returned.
Ext.create('Rally.data.wsapi.Store', {
model : 'UserStory',
filters : [ {
property : 'Milestones',
operator : 'contains',
value : milestone.get("_ref")
} ],
fetch : [ 'ObjectID' ],
limit : Infinity,
autoLoad : true,
listeners : {
load : function(store, records) {
console.log(records);
}
}
});
There is a defect in AppSDK2 that it does not work with abstract types, e.g. Artifact, UserPermissions.
Your first code example ran into that defect.
But your second code example must work. I suspect if it does not work for you it is a matter of scoping. Perhaps the milestone you filter by is not in the project you are scoped to. You may hard code a project ref in the context of the store to make sure it is looking in the right project. I initially tested your code in my default project and it worked as is, and then I modified it as follows to make sure it finds a milestone in a non-default project:
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function() {
Ext.create('Rally.data.wsapi.Store', {
model : 'UserStory',
context:{
project: '/project/16662089077'
},
filters : [
{
property : 'Milestones',
operator : 'contains',
value : "/milestone/33215216897"
}
],
fetch : [ 'ObjectID' ],
limit : Infinity,
autoLoad : true,
listeners : {
load : function(store, records) {
console.log(records);
}
}
});
}
});

Using collection summaries for multi-select fields

Using sdk2, I want to know how many tests are in a project, and how many of those tests are manual vs. automated. It seems like I should be able to use Collection Summaries to do this. The advantage of using summaries is that then I can get a count of tests back instead of a list of hundreds of tests, or making multiple queries (one for all tests, and one for manual tests).
But the example shows how to query for a summary of defects on a user story. It does now show if it is possible to get a summary of a multi-select or another type of field.
I tried to guess what the syntax might be below, but it didn't work:
Ext.create('Rally.data.wsapi.Store', {
model: 'TestCase',
fetch: ['Method:summary[Manual,Automated]'], // This is the key statement: how to fetch a summary?
pageSize: 1,
autoLoad: true,
listeners: {
load: function(store, records) {
var testcase = records[0];
var methodInfo = testase.get('Method');
var manualCount = methodInfo.Manual;
}
}
});
Is it possible to do this in a query with just one result using collection summaries?
This certainly would be useful for non-collection attributes such as TestCase.Method. WSAPI 2.0 Collection Summaries, however, were developed to provide a convenient way to get and summarize counts for Child Collections of Artifacts, without having to go back to WSAPI and query the collection itself. Because WSAPI 2.0 removed the ability to automatically hydrate Child Collections for performance reasons, the summary capability was important.
Thus, the summary method works for summarizing attribute counts for a Child Collections of Objects on an Artifact, for example:
Summarizing Tasks by State on Defects: https://rally1.rallydev.com/slm/webservice/v2.0/defect?fetch=Tasks:summary[State]&order=Rank
Defects by Owner on Stories: https://rally1.rallydev.com/slm/webservice/v2.0/hierarchicalrequirement?fetch=Defects:summary[Owner]&order=ScheduleState
To save loading of an entire store just to get attribute counts, you could set a filter on your store by TestCase Method, and use a unit page size to prevent loading the full set of records. Then use getTotalCount() to summarize the counts you want.
However, this could get a bit cumbersome by having to load a WsapiStore and deal with a callback for each attribute you wish to summarize.
By using Deft.js and Promises though, it is a bit more palatable. Here's a basic example that uses promises and Deft.Deferred to implement a _getCount(modelType, attribute, attrValue) function:
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
items: [
{
xtype: 'container',
itemId: 'gridContainer',
columnWidth: 1
}
],
_summaryGrid: null,
launch: function() {
this._summarizeTestCaseCounts();
},
_summarizeTestCaseCounts: function() {
var me = this;
var promises = [];
var resultArray = [];
promises.push(me._getCount('TestCase', 'Method', 'Manual'));
promises.push(me._getCount('TestCase', 'LastVerdict', 'Failed'));
promises.push(me._getCount('TestCase', 'LastVerdict', 'Pass'));
Deft.Promise.all(promises).then({
success: function(results) {
Ext.Array.each(results, function(result) {
resultArray.push(result);
console.log(result);
});
// Create grid from summarized results
me._makeGrid(resultArray);
}
});
},
_getCount: function(modelType, attribute, attrValue) {
var deferred = Ext.create('Deft.Deferred');
var artifactStore = Ext.create('Rally.data.wsapi.Store', {
model: modelType,
pagesize: 1,
autoLoad: true,
filters: [
{
property: attribute,
operator: '=',
value: attrValue
}
],
sorters: [
{
property: 'FormattedID',
direction: 'ASC'
}
],
listeners: {
load: function(store, records) {
var manualCount = store.getTotalCount();
result = {
"ModelType": modelType,
"Attribute": attribute,
"Value": attrValue,
"Count": manualCount
};
deferred.resolve(result);
}
}
});
return deferred;
},
_makeGrid: function(results) {
var me = this;
if (me._summaryGrid) {
me._summaryGrid.destroy();
}
var gridStore = Ext.create('Rally.data.custom.Store', {
data: results,
pageSize: 5,
remoteSort: false
});
me._summaryGrid = Ext.create('Rally.ui.grid.Grid', {
itemId: 'artifactGrid',
store: gridStore,
columnCfgs: [
{
text: 'Artifact', dataIndex: 'ModelType'
},
{
text: 'Attribute', dataIndex: 'Attribute'
},
{
text: 'Value', dataIndex: 'Value'
},
{
text: 'Count', dataIndex: 'Count'
}
]
});
me.down('#gridContainer').add(me._summaryGrid);
me._summaryGrid.reconfigure(gridStore);
}
});
As an aside, Matt Greer recently wrote a fantastic blog posting outlining his deep-dive on using Deft.js promises. It was very helpful to me in understanding how to use them when building Rally Apps.

Adding filters with "And" and "or" in 2.0rc1

I am having problem in using filters with "And" and "or" clause. Basically i want to get any userstories having text "Mobile" or "Testing"
I am trying like this :
_createFilters:function(){
var filter = Ext.create('Rally.data.QueryFilter',
{
property: 'Name',
operator: 'contains',
value: 'Mobile'
}
);
filter = filter.or(Ext.create('Rally.data.QueryFilter',
{
property: 'Name',
operator: 'contains',
value: 'Testing'
}));
return filter.toString();
},
_makeDefectsStore:function(ref){
Ext.create('Rally.data.WsapiDataStore',{ // will fetch userstories that have defects
model: 'User Story',
limit: "Infinity",
context: {
project :'/project/xxxx',
projectScopeUp: false,
projectScopeDown: true
},
autoLoad: true,
fetch:['FormattedID','Name','Defects','Feature'],
filters: [this._createFilters()],
scope:this,
listeners: {
load: this._onAllDefectsLoaded,
scope: this
}
});
}
Here filter is returning null, otherwise all the userstories are returned without filtering . Please provide suggestion/fix
Nothing immediately pops out as being incorrect with your filter construction, you might try this simpler version though and see what you get:
Ext.create('Rally.data.WsapiDataStore', {
model : 'UserStory',
filters : Rally.data.QueryFilter.or([{
property : 'Name',
operator : 'contains',
value : 'Testing'
},{
property : 'Name',
operator : 'contains',
value : 'Mobile'
}])
});

Hydrate Tags In Lookback API

I'm trying to get the tags associated with a defect using the lookback api. It seems no matter what I try, I can never get the tags to hydrate. Any Ideas?
I get the following data on return of the query:
ScheduleState: "Backlog"
Tags: Array[1]
0: 3230012667
length: 1
__proto__: Array[0]
My query code is:
Ext.create('Rally.data.lookback.SnapshotStore', {
fetch: ['Name','ScheduleState', 'Project', 'Tags'],
autoLoad: true,
listeners: {
load: function(store, records) {
console.log(store);
}
},
hydrate: ['Tags'],
fields: ['Name','ScheduleState', 'Project', 'Tags'],
filters: [
{
property: '_TypeHierarchy',
operator: '=',
value: 'Defect'
},
{
property: 'ScheduleState',
operator: '!=',
value: 'Accepted'
},
{
property: '__At',
value: dateString
}
]
});
I don't believe you can hydrate tags with the LBAPI. What I would do instead is use the WSAPI create a hash map which maps the tags Object ID to its name. Here's some code to get that done:
Ext.create('Rally.data.WsapiDataStore', {
limit : Infinity,
model : 'Tag',
fetch : ['ObjectID','Name']
}).load({
callback: function(store) {
var tagNameMap = Ext.create('Ext.util.HashMap');
Ext.Array.each(store.getRecords(), function(record) {
tagNameMap.add(record.get('ObjectID'), record.get('Name'));
});
getDefects(tagNameMap); //Pass the tag name map to the LBAPI request
}
});
Now just use tagNameMap.get(tagOID) to get the name of the tag.
Hope this helps :)