Rally 为看板状态生成周期时间报告

Rally 为看板状态生成周期时间报告,rally,Rally,我正在rally tool中创建自定义HTML页面来编写代码,以获取每个故事从进入状态到离开状态的看板中每个状态的总停留天数,如下所示:[在图像中,uid12在“就绪状态”状态停留了10天,目前停留在“开发状态”是过去2天的状态。在故事uid34中,完成所有状态所需的总天数为32]。由于我是新手,请任何人帮我解决这个问题 这是一个基于的js代码,可以使用 您可以将此示例用作起点。当单击第一个网格中的一行时,它将构建循环时间的第二个网格。我的示例中的看板状态字段具有允许的“就绪”、“开发”和“完成

我正在rally tool中创建自定义HTML页面来编写代码,以获取每个故事从进入状态到离开状态的看板中每个状态的总停留天数,如下所示:[在图像中,uid12在“就绪状态”状态停留了10天,目前停留在“开发状态”是过去2天的状态。在故事uid34中,完成所有状态所需的总天数为32]。由于我是新手,请任何人帮我解决这个问题


这是一个基于的js代码,可以使用

您可以将此示例用作起点。当单击第一个网格中的一行时,它将构建循环时间的第二个网格。我的示例中的看板状态字段具有允许的“就绪”、“开发”和“完成”值

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