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

时间:2016-02-16 06:52:31

标签: rally

我正在拉力工具中创建自定义HTML页面,以编写代码,以获取每个故事在 kanban board 中的每个状态从进入州后直到它的总天数离开状态如下图所示:[在图像中,uid12保持在"准备状态"状态为10天,目前处于"发展状态"过去2天的状态..在故事uid34中,完成所有州所花费的总天数是32]。任何人都可以帮助我,因为我是新来的集会。

1

1 个答案:

答案 0 :(得分:0)

以下是基于AppSDK2的js代码,可以使用rally-app-builder

编译为html
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);
         }
    }
});

您可以将此示例用作起点。当单击第一个网格中的行时,它会构建第二个循环时间网格。我的示例中的KanbanState字段已准备好'' dev'并且'完成'允许的值 enter image description here