How do I determine how long something spent in a state? Here is a query I have for pulling specifics on a user story, but I'm trying to understand how to get the duration something spent in In Progress before going to completed. To be more specific, customizing Cycle Time
https://rally1.rallydev.com/analytics/v2.0/service/rally/workspace/xxxx/artifact/snapshot/query.js?find={"FormattedID":"US41","_PreviousValues.ScheduleState":"In-Progress"}&fields=["ScheduleState","_ValidFrom","_ValidTo","_PreviousValues"]&hydrate=["ScheduleState","_ValidFrom","_ValidTo","_PreviousValues"]&sort={_ValidFrom: -1}&pagesize=1
I don't see where the ValidFrom and ValidTo provides that information.
This solution seems to be working for me. Hope it helps!
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function() {
Ext.create('Rally.data.lookback.SnapshotStore', {
fetch : ['ScheduleState'],
hydrate : ['ScheduleState'],
filters : [{
property : '_UnformattedID',
value : 41
}],
sorters : [{
property : '_ValidTo',
direction : 'ASC'
}]
}).load({
params : {
compress : true,
removeUnauthorizedSnapshots : true
},
callback : function(records, operation, success) {
var cycleTime = Rally.util.DateTime.getDifference(new Date(Rally.util.Array.last(Ext.Array.filter(records, function(record) {
return record.get('ScheduleState') === 'Accepted';
})).get('_ValidFrom')), new Date(Rally.util.Array.last(Ext.Array.filter(records, function(record) {
return record.get('ScheduleState') === 'In-Progress';
})).get('_ValidFrom')), 'day'));
}
});
}
});
Related
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);
}
}
});
}
});
I am displaying snapshot store records in a rallygrid component and would like to make it so the ID field is clickable and brings up the detail page for that work item. Since snapshot records include an "_UnformattedID" rather than "FormattedID", I tried to accomplish this using a column renderer, which adds the text as a link:
renderer: function(val, meta, record) {
return 'US' + record.get('_UnformattedID') + '';
}
This works perfectly for me, as a non-SSO user, but users in our workspace who are using SSO have reported that the link simply brings them to their default start page. Not the detail page they were expecting.
Is there a better way I could be accomplishing this that would allow all users the functionality?
SSO implementation is different across organization, but this trick worked for me.
I detect the host:
this._host = window.location.hostname;
and then I use host when building the return value of the renderer since the resulting URL in SSO and Non-SSO scenarios in my environment only differ in the host portion.
{
text: 'Formatted ID', dataIndex: 'UnformattedID',
renderer: function(val, meta, record) {
return 'US' + record.get('UnformattedID') + '';
}
}
.
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function() {
this._host = window.location.hostname;
console.log('host', this._host);
var iterationComboBox = Ext.create('Rally.ui.combobox.IterationComboBox',{
listeners:{
ready: function(combobox){
this._iterationOid = combobox.getRecord().get('ObjectID');
this._loadStories(this._iterationOid);
},
select: function(combobox){
this._iterationOid = combobox.getRecord().get('ObjectID');
this._loadStories(this._iterationOid);
},
scope: this
}
});
this.add(iterationComboBox);
},
_loadStories:function(iterationOid){
console.log('loading stories for ', iterationOid);
var myStore = Ext.create('Rally.data.lookback.SnapshotStore', {
autoLoad:true,
fetch : ['Name','_UnformattedID','ScheduleState','_TypeHierarchy'],
filters : [{
property : '__At',
value : 'current'
},
{
property : '_TypeHierarchy',
value : 'HierarchicalRequirement'
},
{
property : 'Iteration',
value : iterationOid
}
],
hydrate: ['_TypeHierarchy'],
listeners: {
load: function(store,records,success){
console.log("loaded %i records", records.length);
this._onDataLoaded(myStore, records);
},
scope:this
}
});
},
_onDataLoaded: function(store,data){
console.log('count',store.getCount());
var that = this;
var records = [];
Ext.Array.each(data, function(record) {
records.push({
Name: record.get('Name'),
ObjectID: record.get('ObjectID'),
UnformattedID: record.get('_UnformattedID')
});
});
var myStore = Ext.create('Rally.data.custom.Store', {
data: records
});
if (!this.down('#grid')) {
this.add({
xtype: 'rallygrid',
id: 'grid',
store: myStore,
columnCfgs: [
{
text: 'Name', dataIndex: 'Name', flex: 1
},
{
text: 'Formatted ID', dataIndex: 'UnformattedID',
renderer: function(val, meta, record) {
return 'US' + record.get('UnformattedID') + '';
}
}
]
});
}
else{
console.log(store);
this.down('#grid').reconfigure(myStore);
}
}
});
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.
I want to display the initiative and total number of defects raised for the initiative.
Tried with following snippet, but i was not able to relate initiative and the defect.
_getInitiatives: function () {
Ext.create("Rally.data.WsapiDataStore", {
model: "PortfolioItem/initiative",
fetch: ["Project", "Notes", "Name", "Children", "FormattedID"],
limit: 1 / 0,
context: {
project: "/project/xxx",
projectScopeDown: !0,
projectScopeUp: !1
},
autoLoad: !0,
listeners: {
load:this._onDefectsLoaded,
scope: this
}
})
},
_onDefectsLoaded: function(store,data){
this.stories = data;
Ext.create('Rally.data.WsapiDataStore',{
model: 'User Story',
limit: "Infinity",
context: {
project :'/project/xxx',
projectScopeUp: false,
projectScopeDown: true
},
autoLoad: true,
fetch:['FormattedID','Name','Defects','Feature'],
scope:this,
listeners: {
//load: this._onAllDefectsLoaded,
load: this._onDataLoaded,
scope: this
}
});
}
Please provide fix/suggestion for the above mentioned problem
I have found that it is easier to use the Lookback API for requests which reference the RPM, rather than WSAPI. Here is some code which gets all the Initiative records from the project, and then fetches the defect counts for each initiative and applies that count to the record. Hope this helps!
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function() {
Ext.create('Rally.data.lookback.SnapshotStore', {
fetch : ['Name','ObjectID'],
filters : [{
property : '__At',
value : 'current'
},{
property : '_TypeHierarchy',
value : 'PortfolioItem/Initiative'
}]
}).load({
params : {
compress : true,
removeUnauthorizedSnapshots : true
},
callback : function(records, operation, success) {
var me = this;
Deft.Promise.all(Ext.Array.map(records, function(record) {
return me.getInitiativeDefectCount(record.get('ObjectID')).then({
success: function(defectCount) {
record.set('DefectCount', defectCount);
}
});
})).then({
success: function() {
console.log(records);
}
});
},
scope : this
});
},
getInitiativeDefectCount: function(initiativeObjectID) {
var deferred = Ext.create('Deft.Deferred');
Ext.create('Rally.data.lookback.SnapshotStore', {
pageSize : 1,
filters : [{
property : '__At',
value : 'current'
},{
property : '_TypeHierarchy',
value : 'Defect'
},{
property : '_ItemHierarchy',
operator : 'in',
value : initiativeObjectID
}]
}).load({
params : {
compress : true,
removeUnauthorizedSnapshots : true
},
callback : function(records, operation, success) {
deferred.resolve(operation.resultSet.totalRecords);
}
});
return deferred.promise;
}
});
I want to create a chart of how many tasks are in a given Schedule State during the length of the sprint. Is it possible to call WsapiDataStore on each day?
What you are looking for is a lookback Snapshot Store , using the Lookback API - this allows you to specify a date or a point in time that you want to query by.
A typical use looks like this:
Ext.create('Rally.data.lookback.SnapshotStore', {
pageSize : 10000,
fetch : ['fetch'],
filters : [{
property : '__At',
value : 'current'
},{
property : '_ItemHierarchy',
value : 'HierarchicalRequirement'
}]
}).load({
callback : function(records) {
Ext.Array.each(records, function(record) {
// do something with each record
});
}
});
WsapiDataStore is not intended for historic data. You need to use Rally.data.lookback.SnapshotStore which retrieves data from the Lookback API.
Lookback API allows to see what any work item or collection of work items looked like in the past. This is different from using WS API directly (or via WsapiDataStore) which can provide you with the current state of objects, but does not have historical data.
LBAPI documentation is available here
As far as Rally release object's attributes see WS API object model here. But it is not clear from your comment what you mean by data for the entire release. If you are interested in getting back user stories assigned to a specific release then your query object should be hierarchical requirement and not release, and you may filter by release.
Here is an app that builds a chart using a Release dropdown. Based on the selection in the dropdown the chart is refreshed (it is destroyed and added):
Ext.define('CustomApp', {
extend: 'Rally.app.TimeboxScopedApp',
componentCls: 'app',
scopeType: 'release',
comboboxConfig: {
fieldLabel: 'Select a Release:',
labelWidth: 100,
width: 300
},
addContent: function() {
this._makeStore();
},
onScopeChange: function() {
this._makeStore();
},
_makeStore: function() {
Ext.create('Rally.data.WsapiDataStore', {
model: 'UserStory',
autoLoad: true,
filters: [this.getContext().getTimeboxScope().getQueryFilter()],
listeners: {
load: this._onDataLoaded,
scope: this
}
});
},
_onDataLoaded: function(store, data) {
var records = [];
var scheduleStateGroups = ["Defined","In-Progress","Completed","Accepted"]
// State count variables
var definedCount = 0;
var inProgressCount = 0;
var completedCount = 0;
var acceptedCount = 0;
// Loop through returned data and group/count by ScheduleState
Ext.Array.each(data, function(record) {
//Perform custom actions with the data here
//Calculations, etc.
scheduleState = record.get('ScheduleState');
switch(scheduleState)
{
case "Defined":
definedCount++;
break;
case "In-Progress":
inProgressCount++;
break;
case "Completed":
completedCount++;
break;
case "Accepted":
acceptedCount++;
}
});
if (this.down('#myChart')) {
this.remove('myChart');
}
this.add(
{
xtype: 'rallychart',
height: 400,
itemId: 'myChart',
chartConfig: {
chart: {
},
title: {
text: 'User Story Schedule State Counts',
align: 'center'
},
xField : 'ScheduleState',
xAxis: [
{
//categories: scheduleStateGroups,
title: {
text: 'ScheduleState'
}
}
],
yAxis: {
title: {
text: 'Count'
}
},
plotOptions : {
column: {
color: '#F00'
},
series : {
animation : {
duration : 2000,
easing : 'swing'
}
}
}
},
chartData: {
categories: scheduleStateGroups,
series: [
{
type: 'column',
data: [definedCount, inProgressCount, completedCount, acceptedCount]
}
]
}
}
);
this.down('#myChart')._unmask();
}
});