On my cardboard app trying to show total of all features actual points for each column(Release) on the header, but that is not getting displayed - rally

On my cardboard app trying to show total of all features actual points for each column(Release) on the header, but that is not getting displayed.
See the image below the cards are my portfolioitem/feature, in particular release.
On each header with release information, below progress-bar I want to show total of all features actual points in that release.
Below is the code for that, I am not getting why actual_points are not getting displayed. I am doing something wrong, but I don't know where exactly it is
Ext.override(Rally.ui.cardboard.CardBoard,{
_buildColumnsFromModel: function() {
var me = this;
var model = this.models[0];
if (model) {
if ( this.attribute === "Release" ) {
var retrievedColumns = [];
retrievedColumns.push({
value: null,
columnHeaderConfig: {
headerTpl: "{name}",
headerData: {
name: "Backlog"
}
}
});
this._getLocalReleases(retrievedColumns, this.globalVar);
}
}
},
_getLocalReleases: function(retrievedColumns, today_iso) {
var me = this;
if (today_iso == undefined) {
today_iso = Rally.util.DateTime.toIsoString(new Date(),false);
}
var filters = [{property:'ReleaseDate',operator:'>',value:today_iso}];
var iteration_names = [];
Ext.create('Rally.data.WsapiDataStore',{
model:me.attribute,
autoLoad: true,
filters: filters,
context: { projectScopeUp: false, projectScopeDown: false },
sorters: [
{
property: 'ReleaseDate',
direction: 'ASC'
}
],
//limit: Infinity,
pageSize: 4,
//buffered: true,
//purgePageCount: 4,
fetch: ['Name','ReleaseStartDate','ReleaseDate','PlannedVelocity'],
listeners: {
load: function(store,records) {
Ext.Array.each(records, function(record){
console.log("records values", records);
var start_date = Rally.util.DateTime.formatWithNoYearWithDefault(record.get('ReleaseStartDate'));
var end_date = Rally.util.DateTime.formatWithNoYearWithDefault(record.get('ReleaseDate'));
//this._getMileStones(record.get('ReleaseStartDate'), record.get('ReleaseDate'), this.project);
iteration_names.push(record.get('Name'));
//iteration_names.push(record.get('ReleaseDate'));
retrievedColumns.push({
value: record,
_planned_velocity: 0,
_actual_points: 0,
_missing_estimate: false,
columnHeaderConfig: {
headerTpl: "{name}<br/>{start_date} - {end_date}",
headerData: {
name: record.get('Name'),
start_date: start_date,
end_date: end_date,
planned_velocity: 0,
actual_points: 0,
missing_estimate: false
}
}
});
});
this._getAllReleases(retrievedColumns,iteration_names);
},
scope: this
}
});
},
_getAllReleases: function(retrievedColumns,iteration_names, today_iso) {
var me = this;
if (today_iso == undefined) {
today_iso = Rally.util.DateTime.toIsoString(new Date(),false);
}
var filters = [{property:'ReleaseDate',operator:'>',value:today_iso}];
Ext.create('Rally.data.WsapiDataStore',{
model:me.attribute,
autoLoad: true,
filters: filters,
sorters: [
{
property: 'ReleaseDate',
direction: 'ASC'
}
],
fetch: ['Name','Project','PlannedVelocity'],
listeners: {
load: function(store,records) {
Ext.Array.each(records, function(record){
var planned_velocity = record.get('PlannedVelocity') || 0;
var actual_points = record.get('LeafStoryPlanEstimateTotal') || 0;
var index = Ext.Array.indexOf(iteration_names[0],record.get('Name'));
if (planned_velocity == 0 ) {
retrievedColumns[index+1]._missing_estimate = true;
}
retrievedColumns[index+1]._actual_points += actual_points;
retrievedColumns[index+1]._planned_velocity += planned_velocity;
});
this.fireEvent('columnsretrieved',this,retrievedColumns);
this.columnDefinitions = [];
_.map(retrievedColumns,this.addColumn,this);
this._renderColumns();
},
scope: this
}
});
}
});
Ext.define('Rally.technicalservices.plugin.ColumnHeaderUpdater', {
alias: 'plugin.tscolumnheaderupdater',
extend: 'Ext.AbstractPlugin',
config: {
/**
*
* #type {String} The name of the field holding the card's estimate
*
* Defaults to c_FeatureEstimate (try LeafStoryPlanEstimateTotal)
*/
field_to_aggregate: "planned_velocity",
/**
* #property {Number} The current count of feature estimates
*/
total_feature_estimate: 0,
fieldToDisplay: "actual_points",
/**
* #property {String|Ext.XTemplate} the header template to use
*/
headerTpl: new Rally.technicalservices.template.LabeledProgressBarTemplate({
fieldLabel: 'Features Planned vs Planned Velocity: ',
calculateColorFn: function(data) {
if ( data.percentDone > 0.9 ) {
return '#EDB5B1';
}
return '#99CCFF';
},
showDangerNotificationFn: function(data) {
return data.missing_estimate;
},
generateLabelTextFn: function(data) {
if ( data.percentDone === -1 ) {
return "No Planned Velocity";
} else {
var text_string = "";
if ( data.field_to_aggregate === "planned_velocity" ) {
text_string = this.calculatePercent(data) + '%';
} else {
text_string = 'By Story: ' + this.calculatePercent(data) + '%';
}
return text_string;
}
}
})
//headerTpl: '<div class="wipLimit">({total_feature_estimate} of {planned_velocity})</div>'
},
constructor: function(config) {
this.callParent(arguments);
if(Ext.isString(this.headerTpl)) {
this.headerTpl = Ext.create('Ext.XTemplate', this.headerTpl);
}
},
init: function(column) {
this.column = column;
if ( column.value === null ) {
this.headerTpl = new Ext.XTemplate('');
}
this.planned_velocity = this.column._planned_velocity;
this.missing_estimate = this.column._missing_estimate;
this.actual_points = this.column._actual_points;
this.column.on('addcard', this.recalculate, this);
this.column.on('removecard', this.recalculate, this);
this.column.on('storeload', this.recalculate, this);
this.column.on('afterrender', this._afterRender, this);
this.column.on('ready', this.recalculate, this);
this.column.on('datachanged', this.recalculate, this);
},
destroy: function() {
if(this.column) {
delete this.column;
}
},
_afterRender: function() {
if ( this.feature_estimate_container ) {
this.feature_estimate_container.getEl().on('click', this._showPopover, this);
}
},
recalculate: function() {
this.total_feature_estimate = this.getTotalFeatureEstimate();
this.refresh();
},
refresh: function() {
var me = this;
if (this.feature_estimate_container) {
this.feature_estimate_container.update(this.headerTpl.apply(this.getHeaderData()));
} else {
this.feature_estimate_container = Ext.widget({
xtype: 'container',
html: this.headerTpl.apply(this.getHeaderData())
});
this.column.getColumnHeader().getHeaderTitle().add(this.feature_estimate_container);
}
if ( this.feature_estimate_container && this.feature_estimate_container.getEl()) {
this.feature_estimate_container.getEl().on('click', this._showPopover, this);
}
},
_showPopover: function() {
var me = this;
if ( me.planned_velocity > 0 ) {
if ( this.popover ) { this.popover.destroy(); }
this.popover = Ext.create('Rally.ui.popover.Popover',{
target: me.column.getColumnHeader().getHeaderTitle().getEl(),
items: [ me.getSummaryGrid() ]
});
this.popover.show();
}
},
getSummaryGrid: function() {
var me = this;
var estimate_title = "Feature Estimates";
if ( this.field_to_aggregate !== "c_FeatureEstimate") {
estimate_title = "Story Estimates";
}
var store = Ext.create('Rally.data.custom.Store',{
data: [
{
'PlannedVelocity': me.planned_velocity,
'ActualPoints': me.actual_points,
'TotalEstimate': me.getTotalFeatureEstimate(),
'Remaining': me.getCapacity(),
'MissingEstimate': me.missing_estimate
}
]
});
var grid = Ext.create('Rally.ui.grid.Grid',{
store: store,
columnCfgs: [
{ text: 'Plan', dataIndex:'PlannedVelocity' },
{ text: estimate_title, dataIndex: 'TotalEstimate' },
{ text: 'Remaining', dataIndex: 'Remaining' },
{ text: 'Team Missing Plan?', dataIndex: 'MissingEstimate' }
],
showPagingToolbar: false
});
return grid;
},
getHeaderData: function() {
var total_feature_estimate = this.getTotalFeatureEstimate();
actual_points = 0;
var percent_done = -1;
planned_velocity = 20;
if ( planned_velocity > 0 ) {
percent_done = total_feature_estimate / 4;
}
return {
actual_points: actual_points,
total_feature_estimate: total_feature_estimate,
planned_velocity: planned_velocity,
percentDone: percent_done,
field_to_aggregate: this.field_to_aggregate,
missing_estimate: this.missing_estimate
};
},
getCapacity: function() {
return this.planned_velocity - this.getTotalFeatureEstimate();
},
getTotalFeatureEstimate: function() {
var me = this;
var total = 0;
var total_unaligned = 0;
var records = this.column.getRecords();
Ext.Array.each(records, function(record){
var total_points = record.get('AcceptedLeafStoryPlanEstimateTotal');
var feature_estimate = record.get(me.field_to_aggregate) || 0;
var unaligned_estimate = record.get('UnalignedStoriesPlanEstimateTotal') || 0;
total += parseFloat(total_points,10);
});
if ( me.field_to_aggregate !== "planned_velocity" ) {
total = total
}
return total;
}
});

Related

Blazor WebAssembly Paypal

I am trying to implement a paypal connection to my application. So far I have tried adding a javascript script to index.html in my client project:
<script src="https://www.paypal.com/sdk/js?client-id=MY-CLIENT-CODE"></script>
<script>
function initPayPalButton() {
var shipping = 0;
var itemOptions = 3;/*document.querySelector("#smart-button-container #item-options");*/
var quantity = parseInt();
var quantitySelect = 1;/*document.querySelector("#smart-button-container #quantitySelect");*/
if (!isNaN(quantity)) {
quantitySelect.style.visibility = "visible";
}
var orderDescription = 'Fund';
if (orderDescription === '') {
orderDescription = 'Item';
}
paypal.Buttons({
style: {
shape: 'rect',
color: 'blue',
layout: 'vertical',
label: 'paypal',
},
createOrder: function (data, actions) {
var selectedItemDescription = itemOptions.options[itemOptions.selectedIndex].value;
var selectedItemPrice = parseFloat(itemOptions.options[itemOptions.selectedIndex].getAttribute("price"));
var tax = (0 === 0) ? 0 : (selectedItemPrice * (parseFloat(0) / 100));
if (quantitySelect.options.length > 0) {
quantity = parseInt(quantitySelect.options[quantitySelect.selectedIndex].value);
} else {
quantity = 1;
}
tax *= quantity;
tax = Math.round(tax * 100) / 100;
var priceTotal = quantity * selectedItemPrice + parseFloat(shipping) + tax;
priceTotal = Math.round(priceTotal * 100) / 100;
var itemTotalValue = Math.round((selectedItemPrice * quantity) * 100) / 100;
return actions.order.create({
purchase_units: [{
description: orderDescription,
amount: {
currency_code: 'USD',
value: priceTotal,
breakdown: {
item_total: {
currency_code: 'USD',
value: itemTotalValue,
},
shipping: {
currency_code: 'USD',
value: shipping,
},
tax_total: {
currency_code: 'USD',
value: tax,
}
}
},
items: [{
name: selectedItemDescription,
unit_amount: {
currency_code: 'USD',
value: selectedItemPrice,
},
quantity: quantity
}]
}]
});
},
onApprove: function (data, actions) {
return actions.order.capture().then(function (details) {
alert('Transaction completed by ' + details.payer.name.given_name + '!');
/*dotNetHelper.invokeMethodAsync('OnApprove', data, details);*/
});
},
onError: function (err) {
console.log(err);
},
}).render('#paypal-button-container');
}
</script>
and try to get this in my razor page
#inject IJSRuntime JSRuntime
<div id="paypal-button-container"></div>
#code
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JSRuntime.InvokeVoidAsync("initPayPalButton");
StateHasChanged();
}
}
But when i run the application a error raised:
Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Could not find 'initPayPalButton' ('initPayPalButton' was undefined).
Error: Could not find 'initPayPalButton' ('initPayPalButton' was undefined).
Where am i wrong?

Can't pass my data to the mounted() method

I am fairly new to Vue so excuse my stupidity!!
Can anyone tell me what I am doing wrong in my code below that is preventing me from getting my 'series_interactions_daily' variable data from inside the mounted() method please? I am console.log()'ing it but nothing shows! I can print 'series_interactions_daily' to screen in the HTML though!
<script>
import * as am4core from "#amcharts/amcharts4/core";
import * as am4charts from "#amcharts/amcharts4/charts";
import am4themes_animated from "#amcharts/amcharts4/themes/animated";
am4core.useTheme(am4themes_animated);
export default {
mounted() {
let chart = am4core.create(this.$refs.chartdiv, am4charts.XYChart);
chart.paddingRight = 20;
let dummydata = [];
let visits = 10;
for (let i = 1; i < 366; i++) {
visits += Math.round((Math.random() < 0.5 ? 1 : -1) * Math.random() * 10);
dummydata.push({ date: new Date(2018, 0, i), name: "name" + i, value: visits });
}
chart.data = dummydata;
console.log(this.series_interactions_daily);
let dateAxis = chart.xAxes.push(new am4charts.DateAxis());
dateAxis.renderer.grid.template.location = 0;
let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis.tooltip.disabled = true;
valueAxis.renderer.minWidth = 35;
let series = chart.series.push(new am4charts.LineSeries());
series.dataFields.dateX = "date";
series.dataFields.valueY = "value";
series.tooltipText = "{valueY.value}";
chart.cursor = new am4charts.XYCursor();
let scrollbarX = new am4charts.XYChartScrollbar();
scrollbarX.series.push(series);
chart.scrollbarX = scrollbarX;
this.chart = chart;
},
beforeDestroy() {
if (this.chart) {
this.chart.dispose();
}
},
data() {
return {
campaign_id: this.$route.params.campaign_id,
campaign: [],
dateranges: [],
total_interactions: '',
average_daily_interactions: '',
series_interactions_daily: [],
}
},
methods: {
onChange(event) {
this.$router.push('?range=' + event.target.value);
this.$http.get('https://dev.local/api/portal/campaign/' + this.campaign_id + '?range=' + event.target.value)
.then(function(data){
return data.json();
}).then(function(data){
this.campaign = data.campaign;
this.dateranges = data.dateranges;
this.total_interactions = data.totalInteractions;
this.average_daily_interactions = data.averagreDailyInteractions;
this.series_interactions_daily = data.interactions_per_day.stats.interaction.daily;
});
}
},
created() {
this.$http.get('https://dev.local/api/portal/campaign/' + this.campaign_id + '?' + this.axiosParams)
.then(function(data){
return data.json();
}).then(function(data){
this.campaign = data.campaign;
this.dateranges = data.dateranges;
this.total_interactions = data.totalInteractions;
this.average_daily_interactions = data.averagreDailyInteractions;
this.series_interactions_daily = data.interactions_per_day.stats.interaction.daily;
});
},
computed: {
axiosParams() {
const params = new URLSearchParams();
if(this.$route.query.range) params.append('range', this.$route.query.range);
return params;
}
},
filters: {
},
directives: {
},
}
</script>
EDIT
OK, so I have create a method to fetchData() from mounted(){} - however I am still not getting my 'series_interactions_daily' variable data!
<script>
import * as am4core from "#amcharts/amcharts4/core";
import * as am4charts from "#amcharts/amcharts4/charts";
import am4themes_animated from "#amcharts/amcharts4/themes/animated";
am4core.useTheme(am4themes_animated);
export default {
mounted() {
this.fetchData();
let chart = am4core.create(this.$refs.chartdiv, am4charts.XYChart);
chart.paddingRight = 20;
let dummydata = [];
let visits = 10;
for (let i = 1; i < 366; i++) {
visits += Math.round((Math.random() < 0.5 ? 1 : -1) * Math.random() * 10);
dummydata.push({ date: new Date(2018, 0, i), name: "name" + i, value: visits });
}
chart.data = dummydata;
console.log(this.series_interactions_daily);
let dateAxis = chart.xAxes.push(new am4charts.DateAxis());
dateAxis.renderer.grid.template.location = 0;
let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis.tooltip.disabled = true;
valueAxis.renderer.minWidth = 35;
let series = chart.series.push(new am4charts.LineSeries());
series.dataFields.dateX = "date";
series.dataFields.valueY = "value";
series.tooltipText = "{valueY.value}";
chart.cursor = new am4charts.XYCursor();
let scrollbarX = new am4charts.XYChartScrollbar();
scrollbarX.series.push(series);
chart.scrollbarX = scrollbarX;
this.chart = chart;
},
beforeDestroy() {
if (this.chart) {
this.chart.dispose();
}
},
data() {
return {
campaign_id: this.$route.params.campaign_id,
campaign: [],
dateranges: [],
total_interactions: '',
average_daily_interactions: '',
series_interactions_daily: [],
}
},
methods: {
fetchData() {
this.$http.get('https://dev.local/api/portal/campaign/' + this.campaign_id + '?' + this.axiosParams)
.then(function(data){
return data.json();
}).then(function(data){
this.campaign = data.campaign;
this.dateranges = data.dateranges;
this.total_interactions = data.totalInteractions;
this.average_daily_interactions = data.averagreDailyInteractions;
this.series_interactions_daily = data.interactions_per_day.stats.interaction.daily;
});
},
onChange(event) {
this.$router.push('?range=' + event.target.value);
this.$http.get('https://connie.laravel/api/portal/campaign/' + this.campaign_id + '?range=' + event.target.value)
.then(function(data){
return data.json();
}).then(function(data){
this.campaign = data.campaign;
this.dateranges = data.dateranges;
this.total_interactions = data.totalInteractions;
this.average_daily_interactions = data.averagreDailyInteractions;
this.series_interactions_daily = data.interactions_per_day.stats.interaction.daily;
});
}
},
created() {
},
computed: {
axiosParams() {
const params = new URLSearchParams();
if(this.$route.query.range) params.append('range', this.$route.query.range);
return params;
}
},
filters: {
},
directives: {
},
}
</script>
Any help appreciated.
Thanks,
K...
In fetchData, return the promise returned by the async $http call:
fetchData() {
return this.$http.get(...)
...
}
Now you can register a callback on that promise in mounted:
mounted() {
this.fetchData().then(result => {
...
});
}
Or just await it with async/await:
async mounted() {
await this.fetchData();
...
// Now everything will be ready
}
As #tao mentioned in comments, note that the await would delay any code that comes after it. If you don't have anything else to do in the hook anyway that's no problem but it's something to understand.

Generate cycle time report for kanban board states

I am creating custom HTML page in rally tool to write the code to get the total number of days each story stayed in each state in the kanban board from the day it entered the state and till it leaves the state as shown below:[In the image, uid12 stayed in "ready state" State for 10 days and currently staying in "development state " state from last 2 days.. In story uid34,total number of days it took to complete all states is 32]. Can anyone please help me with this as i am new to rally.
1
Here is a js code based on AppSDK2 that can be compiled to html using rally-app-builder
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function(){
var context = this.getContext();
var project = context.getProject()['ObjectID'];
console.log(project);
var that = this;
var panel = Ext.create('Ext.panel.Panel', {
layout: 'hbox',
itemId: 'parentPanel',
componentCls: 'panel',
items: [
{
xtype: 'panel',
width: 600,
itemId: 'childPanel1'
},
{
xtype: 'panel',
width: 600,
itemId: 'childPanel2'
}
],
});
this.add(panel);
Ext.create('Rally.data.lookback.SnapshotStore', {
fetch : ['Name','c_KanbanState','_UnformattedID', '_TypeHierarchy'],
filters : [{
property : '__At',
value : 'current'
},
{
property : '_TypeHierarchy',
value : 'HierarchicalRequirement'
},
{
property : '_ProjectHierarchy',
value: project
},
{
property : 'c_KanbanState',
operator : 'exists',
value : true
}
],
hydrate: ['_TypeHierarchy', 'c_KanbanState'],
listeners: {
load: this.onStoriesLoaded,
scope: this
}
}).load({
params : {
compress : true,
removeUnauthorizedSnapshots : true
}
});
},
onStoriesLoaded: function(store, data){
var that = this;
var stories = [];
var id;
_.each(data, function(record) {
var artifactType = record.get('_TypeHierarchy');
if (artifactType[artifactType.length - 1] == "HierarchicalRequirement") {
id = 'US' + record.get('_UnformattedID');
} else if (artifactType[artifactType.length - 1] == "Defect") {
id = 'DE' + record.get('_UnformattedID');
}
stories.push({
Name: record.get('Name'),
FormattedID: id,
UnformattedID: record.get('_UnformattedID'),
c_KanbanState: record.get('c_KanbanState')
});
console.log(stories);
});
var myStore = Ext.create('Rally.data.custom.Store', {
data: stories
});
if (!this.down('#allStoriesGrid')) {
this.down('#childPanel1').add({
xtype: 'rallygrid',
id: 'allStoriesGrid',
store: myStore,
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID',
},
{
text: 'Name', dataIndex: 'Name', flex: 1,
},
{
text: 'Current Kanban State', dataIndex: 'c_KanbanState'
}
],
listeners: {
cellclick: function( grid, td, cellIndex, record, tr, rowIndex){
id = grid.getStore().getAt(rowIndex).get('UnformattedID');
console.log('id', id);
that.getStoryModel(id);//to build a grid of Kanban allowed values
}
}
});
}else{
this.down('#allStoriesGrid').reconfigure(myStore);
}
},
getStoryModel:function(id){
var workspace = this.getContext().getWorkspaceRef();
var project = this.getContext().getProjectRef();
console.log('workspace',workspace);
console.log('project',project);
console.log('get story model');
var that = this;
this.arr=[];
Rally.data.ModelFactory.getModel({
type: 'User Story',
success: function(model){
var allowedValuesStore = model.getField('c_KanbanState').getAllowedValueStore( );
that.getDropdownValues(allowedValuesStore, id);
}
});
},
getDropdownValues:function(allowedValuesStore, id){
var that = this;
allowedValuesStore.load({
scope: this,
callback: function(records, operation, success){
_.each(records, function(val){
//AllowedAttributeValue object in WS API has StringValue
var v = val.get('StringValue');
that.arr.push(v);
});
console.log('arr', this.arr);
that.getStoryById(id);
}
});
},
getStoryById:function(id){
var that = this;
var snapStore = Ext.create('Rally.data.lookback.SnapshotStore', {
fetch: ['c_KanbanState', 'Blocked'],
hydrate:['c_KanbanState','Blocked'],
filters : [
{
property : '_UnformattedID',
value : id
}
],
sorters:[
{
property : '_ValidTo',
direction : 'ASC'
}
]
});
snapStore.load({
params: {
compress: true,
removeUnauthorizedSnapshots : true
},
callback : function(records, operation, success) {
that.onDataLoaded(records, id);
}
});
},
onDataLoaded:function(records, id){
var times = [];
var measure = 'second';
//-----------------------ready
var ready = _.filter(records, function(record) {
return record.get('c_KanbanState') === 'ready';
});
var cycleTimeFromReadyToDev = '';
if (_.size(ready) > 0) {
var ready1 = _.first(ready);
var ready2 = _.last(ready);
var readyDate1 = new Date(ready1.get('_ValidFrom'));
if (ready2.get('_ValidTo') === "9999-01-01T00:00:00.000Z") { //infinity
readyDate2 = new Date(); //now
}
else{
var readyDate2 = new Date(ready2.get('_ValidTo'));
}
cycleTimeFromReadyToDev = Rally.util.DateTime.getDifference(readyDate2,readyDate1, measure );
}
times.push(cycleTimeFromReadyToDev);
//----------------------dev
var dev = _.filter(records, function(record) {
return record.get('c_KanbanState') === 'dev';
});
var cycleTimeFromDevToDone = '';
if (_.size(dev) > 0) {
var dev1 = _.first(dev);
var dev2 = _.last(dev);
var devDate1 = new Date(dev1.get('_ValidFrom'));
if (dev2.get('_ValidTo') === "9999-01-01T00:00:00.000Z") { //infinity
devDate2 = new Date(); //now
}
else{
var devDate2 = new Date(dev2.get('_ValidTo'));
}
cycleTimeFromInProgressToDone = Rally.util.DateTime.getDifference(devDate2,devDate1, measure );
}
times.push(cycleTimeFromDevToDone);
//------------------------done
var done = _.filter(records, function(record) {
return record.get('c_KanbanState') === 'done';
});
console.log('done',done);
var cycleTimeFromDoneToReleased = '';
if (_.size(done) > 0) {
var done1 = _.first(done);
var done2 = _.last(done);
var doneDate1 = new Date(done1.get('_ValidFrom'));
if (done2.get('_ValidTo') === "9999-01-01T00:00:00.000Z") { //infinity
doneDate2 = new Date(); //now
}
else{
var doneDate2 = new Date(done2.get('_ValidTo'));
}
cycleTimeFromDoneToReleased = Rally.util.DateTime.getDifference(doneDate2,doneDate1, measure );
}
times.push(cycleTimeFromDoneToReleased);
//skip first '' element of the this.arr and last 'released' element of this.arr because
//do not care for cycle times in first and last kanban states
this.arrShortened = _.without(this.arr, _.first(this.arr),_.last(this.arr)) ;
cycleTimes = _.zip(this.arrShortened, times);
cycleTimes = _.object(cycleTimes);
var cycleTimesArray = [];
cycleTimesArray.push(cycleTimes);
var store = Ext.create('Rally.data.custom.Store',{
data: cycleTimesArray,
pageSize: 100
});
var columnConfig = [];
_.each(cycleTimes,function(c,key){
var columnConfigElement = _.object(['text', 'dataIndex', 'flex'], ['time spent in ' + key, key, 1]);
columnConfig.push(columnConfigElement);
});
var title = 'KanbanState cycle time for US' + id + ' in ' + measure + 's'
if (!this.grid) {
this.grid = this.down('#childPanel2').add({
xtype: 'rallygrid',
title: title,
itemId: 'grid2',
store: store,
columnCfgs: columnConfig
});
}
else{
this.down('#grid2').reconfigure(store);
}
}
});
You can use this example as a starting point. It builds a second grid of cycle times when a row in a first grid is clicked. The KanbanState field in my example has 'ready','dev' and 'done' allowed values

Rally Kanban # of days since first state?

Is there a way to calculate the number of days since the card has been in the first state? Lets use say I use a custom field \for the kanban states. 1,2,3,4 If a card is in state 3 then how long has it been since # 1?
I am not sure of a way to automate it or flag items but if you review the US/DE in question just take a quick look at the revision history.
Any changes in state should be logged in the history.
Use Lookback API to access historic data.
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, which can provide you with the current state of objects, but does not have historical data.
LBAPI documentation is available here
Here is an example that builds a grid of stories with a Kanban state. When a story's row is double-clicked, the time this story has spent in three Kanban states is calculated and a grid is built to show the values:
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function(){
var x = Ext.create('Rally.data.lookback.SnapshotStore', {
fetch : ['Name','c_Kanban','_UnformattedID', '_TypeHierarchy'],
filters : [{
property : '__At',
value : 'current'
},
{
property : '_TypeHierarchy',
value : 'HierarchicalRequirement'
},
{
property : '_ProjectHierarchy',
value : 22222
},
{
property : 'c_Kanban', //get stories with Kanban state
operator : 'exists',
value : true
}
],
hydrate: ['_TypeHierarchy', 'c_Kanban'],
listeners: {
load: this.onStoriesLoaded,
scope: this
}
}).load({
params : {
compress : true,
removeUnauthorizedSnapshots : true
}
});
},
//make grid of stories with Kanban state
onStoriesLoaded: function(store, data){
var that = this;
var stories = [];
var id;
Ext.Array.each(data, function(record) {
var artifactType = record.get('_TypeHierarchy');
if (artifactType[artifactType.length - 1] == "HierarchicalRequirement") {
id = 'US' + record.get('_UnformattedID');
} else if (artifactType[artifactType.length - 1] == "Defect") {
id = 'DE' + record.get('_UnformattedID');
}
stories.push({
Name: record.get('Name'),
FormattedID: id,
UnformattedID: record.get('_UnformattedID'),
c_Kanban: record.get('c_Kanban')
});
console.log(stories);
});
var myStore = Ext.create('Rally.data.custom.Store', {
data: stories
});
if (!this.down('#allStoriesGrid')) {
this.add({
xtype: 'rallygrid',
id: 'allStoriesGrid',
store: myStore,
width: 500,
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID',
},
{
text: 'Name', dataIndex: 'Name', flex: 1,
},
{
text: 'Kanban', dataIndex: 'c_Kanban'
}
],
listeners: {
celldblclick: function( grid, td, cellIndex, record, tr, rowIndex){
id = grid.getStore().getAt(rowIndex).get('UnformattedID');
console.log('id', id);
that.getStoryModel(id); //to eventually build a grid of Kanban allowed values
}
}
});
}else{
this.down('#allStoriesGrid').reconfigure(myStore);
}
},
getStoryModel:function(id){
console.log('get story model');
var that = this;
this.arr=[];
//get a model of user story
Rally.data.ModelFactory.getModel({
type: 'User Story',
context: {
workspace: '/workspace/11111',
project: 'project/22222'
},
success: function(model){
//Get store instance for the allowed values
var allowedValuesStore = model.getField('c_Kanban').getAllowedValueStore( );
that.getDropdownValues(allowedValuesStore, id);
}
});
},
getDropdownValues:function(allowedValuesStore, id){
var that = this;
//load data into the store
allowedValuesStore.load({
scope: this,
callback: function(records, operation, success){
_.each(records, function(val){
//AllowedAttributeValue object in WS API has StringValue
var v = val.get('StringValue');
that.arr.push(v);
});
console.log('arr', this.arr);
that.getStoryById(id); //former makeStore
}
});
},
getStoryById:function(id){
var that = this;
var snapStore = Ext.create('Rally.data.lookback.SnapshotStore', {
fetch: ['c_Kanban', 'Blocked'],
hydrate:['c_Kanban','Blocked'],
filters : [
{
property : '_UnformattedID',
value : id //15
}
],
sorters:[
{
property : '_ValidTo',
direction : 'ASC'
}
]
});
snapStore.load({
params: {
compress: true,
removeUnauthorizedSnapshots : true
},
callback : function(records, operation, success) {
that.onDataLoaded(records, id);
}
});
},
onDataLoaded:function(records, id){
var times = [];
var measure = 'second';
//-----------------------backlog
var backlog = _.filter(records, function(record) {
return record.get('c_Kanban') === 'backlog';
});
console.log('backlog',backlog);
var cycleTimeFromBacklogToInProgress = '';
if (_.size(backlog) > 0) {
var backlog1 = _.first(backlog);
var backlog2 = _.last(backlog);
var backlogDate1 = new Date(backlog1.get('_ValidFrom'));
if (backlog2.get('_ValidTo') === "9999-01-01T00:00:00.000Z") { //infinity
backlogDate2 = new Date(); //now
}
else{
var backlogDate2 = new Date(backlog2.get('_ValidTo'));
}
cycleTimeFromBacklogToInProgress = Rally.util.DateTime.getDifference(backlogDate2,backlogDate1, measure );
}
times.push(cycleTimeFromBacklogToInProgress);
//console.log(cycleTimeFromBacklogToInProgress);
//----------------------in progress
var inProgress = _.filter(records, function(record) {
return record.get('c_Kanban') === 'in-progress';
});
console.log('in-progress',inProgress);
var cycleTimeFromInProgressToDone = '';
if (_.size(inProgress) > 0) {
var inProgress1 = _.first(inProgress);
var inProgress2 = _.last(inProgress);
var inProgressDate1 = new Date(inProgress1.get('_ValidFrom'));
if (inProgress2.get('_ValidTo') === "9999-01-01T00:00:00.000Z") { //infinity
inProgressDate2 = new Date(); //now
}
else{
var inProgressDate2 = new Date(inProgress2.get('_ValidTo'));
}
cycleTimeFromInProgressToDone = Rally.util.DateTime.getDifference(inProgressDate2,inProgressDate1, measure );
}
times.push(cycleTimeFromInProgressToDone);
//console.log(cycleTimeFromInProgressToDone);
//------------------------done
var done = _.filter(records, function(record) {
return record.get('c_Kanban') === 'done';
});
console.log('done',done);
var cycleTimeFromDoneToReleased = '';
if (_.size(done) > 0) {
var done1 = _.first(done);
var done2 = _.last(done);
var doneDate1 = new Date(done1.get('_ValidFrom'));
if (done2.get('_ValidTo') === "9999-01-01T00:00:00.000Z") { //infinity
doneDate2 = new Date(); //now
}
else{
var doneDate2 = new Date(done2.get('_ValidTo'));
}
cycleTimeFromDoneToReleased = Rally.util.DateTime.getDifference(doneDate2,doneDate1, measure );
}
times.push(cycleTimeFromDoneToReleased);
//console.log(cycleTimeFromDoneToReleased);
/**********
skip first '' element of the this.arr and last 'released' element of this.arr because
do not care for cycle times in first and last kanban states
Originally: arr ["", "backlog", "in-progress", "done", "released"] ,shorten to: ["backlog", "in-progress", "done"]
**********/
this.arrShortened = _.without(this.arr, _.first(this.arr),_.last(this.arr)) ;
console.log('this.arrShortened with first and last skipped', this.arrShortened); //["backlog", "in-progress", "done"]
cycleTimes = _.zip(this.arrShortened, times);
//console.log('cycleTimes as multi-dimentional array', cycleTimes);
cycleTimes = _.object(cycleTimes);
//console.log('cycleTimes as object', cycleTimes); //cycleTimes as object Object {backlog: 89, in-progress: 237, done: 55}
var cycleTimesArray = [];
cycleTimesArray.push(cycleTimes);
console.log('cycleTimesArray',cycleTimesArray);
var store = Ext.create('Rally.data.custom.Store',{
data: cycleTimesArray,
pageSize: 100
});
var columnConfig = [];
_.each(cycleTimes,function(c,key){
var columnConfigElement = _.object(['text', 'dataIndex', 'flex'], ['time spent in ' + key, key, 1]);
columnConfig.push(columnConfigElement);
});
var title = 'Kanban cycle time for US' + id + ' in ' + measure + 's'
if (!this.grid) {
this.grid = this.add({
xtype: 'rallygrid',
title: title,
width: 500,
itemId: 'grid2',
store: store,
columnCfgs: columnConfig
});
}
else{
this.down('#grid2').reconfigure(store);
}
}
});

Change Value of a dojo tree node

I'm trying to change the value of a dojo tree to display the correct icon. I was hopping that I could get the object with fetchItemByIdentity() and change the value there but the item is null
_target: null,
_treeModel: null,
constructor: function(target, uuid) {
this._target = target;
this._uuid = uuid;
// from somewhere else the value get's changed
topic.subscribe("questionChanged", lang.hitch(this, function(object, id) {
var item = this._treeModel.fetchItemByIdentity({
identifier: id,
onItem: function(item, request) { alert("item " + item); }
});
}));
},
buildTree: function() {
xhr.get({
// The URL to request
url: er.getAbsoluteUrl("/secure/staticquestion/tree?uuid=" + this._uuid),
handleAs: "json",
headers: {
"Content-Type": "application/json; charset=utf-8"
},
preventCache: 'true',
// The method that handles the request's successful result
load: lang.hitch(this, function(response) {
var rawdata = new Array();
rawdata.push(response);
var store = new ItemFileReadStore({
data: {
identifier: "uuid",
label: "name",
items: rawdata
}
});
this._loadtree(store);
}),
error: function(err, ioArgs) {
errorDialog.show(err.message);
}
});
},
_loadtree: function(store) {
this._treeModel = new TreeStoreModel({
store: store,
query: {
name: 'root'
},
childrenAttrs: [ "children" ],
mayHaveChildren: function(object) {
return object.children.length > 0;
}
});
var tree = new Tree({ // create a tree
model: this._treeModel, // give it the model
showRoot: false,
getIconClass: function(/* dojo.data.Item */item, /* Boolean */opened) {
if (!item || this.model.mayHaveChildren(item)) {
return opened ? "dijitFolderOpened" : "dijitFolderClosed";
} else if (item.comment == 'false') {
return (item.answer == 'YES') ? "dijitLeafNoCommentYes"
: ((item.answer == 'NO') ? "dijitLeafNoCommentNo" : "dijitLeafNoComment");
} else if (item.comment == 'true') {
return (item.answer == 'YES') ? "dijitLeafYes" : ((item.answer == 'NO') ? "dijitLeafNo"
: "dijitLeaf");
}
return "dijitLeaf";
},
}, this._target); // target HTML element's id
tree.on("click", function(object) {
topic.publish("staticQuestionSelected", object);
}, true);
tree.startup();
}
I'm glad for help, thanks!
Ok, I found my issue: I need to use a ItemFileWriteStore and there I can change values with store.setValue(item, attribute, value). The tree updates itself afterwards.