1

有没有办法计算自卡处于第一个状态以来的天数?假设我为看板状态使用自定义字段 \。1,2,3,4 如果一张牌处于状态 3,那么它距离 #1 有多久了?

4

2 回答 2

1

我不确定是否有办法将其自动化或标记项目,但如果您查看有问题的美国/德国,只需快速查看修订历史。

状态的任何更改都应记录在历史记录中。

于 2013-11-01T19:01:41.670 回答
0

使用 Lookback API 访问历史数据。

Lookback API 允许查看过去的任何工作项或工作项集合的样子。这与直接使用 WS API 不同,后者可以为您提供对象的当前状态,但没有历史数据。

LBAPI 文档可在此处获得

这是一个使用看板状态构建故事网格的示例。当双击一个故事的行时,将计算该故事在三个看板状态中花费的时间,并构建一个网格来显示这些值:

在此处输入图像描述

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);
         }
    }

});
于 2013-11-02T03:19:08.967 回答