Rally 如何查询在特定功能下有需求的缺陷

Rally 如何查询在特定功能下有需求的缺陷,rally,appsdk2,Rally,Appsdk2,当使用基于缺陷的自定义网格查询时,我经常根据链接用户故事的标记进行过滤。我想使用实际的功能层次结构。e、 g.显示链接故事在给定功能或计划下的所有缺陷。通过查看文档,我无法解决这个问题,因为需求继承的工件对象上存在标签属性,因此可以遍历Requirement.Tags。需求上不存在功能属性。它存在于继承自需求的HierarchicalRequirement上,因此无法遍历Requirement.Feature。 在这种情况下,自定义网格可能不是合适的选择。但是可以编写一个自定义应用程序来显示所有

当使用基于缺陷的自定义网格查询时,我经常根据链接用户故事的标记进行过滤。我想使用实际的功能层次结构。e、 g.显示链接故事在给定功能或计划下的所有缺陷。通过查看文档,我无法解决这个问题,因为需求继承的工件对象上存在标签属性,因此可以遍历Requirement.Tags。需求上不存在功能属性。它存在于继承自需求的HierarchicalRequirement上,因此无法遍历Requirement.Feature。 在这种情况下,自定义网格可能不是合适的选择。但是可以编写一个自定义应用程序来显示所有这些关系。这是一个自定义应用程序,它有两个组合框:一个版本和一个功能。“功能”组合框是根据“发布”组合框中的选择填充的。当从第二个组合框中选择某个功能时,网格将填充该功能的子故事和与这些故事相关联的缺陷(如果有)。 您可以看到完整的代码,并将复制到自定义页面中

以下是js文件:

Ext.define('CustomApp', {
    extend: 'Rally.app.TimeboxScopedApp',
    componentCls: 'app',
    scopeType: 'release',
    comboboxConfig: {
        fieldLabel: 'Select a Release:',
        labelWidth: 100,
        width: 300
    },
    addContent: function() {
        this._makeCombobox();
    },

   onScopeChange: function() {
        this._makeCombobox();
    },

    _makeCombobox: function() {
    if (this.down('#features')) {
        this.down('#features').destroy();
    }
    var features = Ext.create('Rally.ui.combobox.ComboBox',{
        id: 'features',
        storeConfig: {
        model: 'PortfolioItem/Feature',
        fetch: ['FormattedID','Name','Release', 'UserStories'],
        pageSize: 100,
        autoLoad: true,
        filters: [this.getContext().getTimeboxScope().getQueryFilter()]
        },
        fieldLabel: 'select Feature',
        listeners:{
                ready: function(combobox){
            if (combobox.getRecord()) {
            console.log('ready',combobox.getRecord().get('_ref'));
            this._onFeatureSelected(combobox.getRecord());
            }
            else{
            console.log('selected release has no features');
            if (this.down('#grid')) {
                this.down('#grid').destroy();
            }
            }
        },
                select: function(combobox){
            if (combobox.getRecord()) {
            console.log('select',combobox.getRecord().get('_ref'));
            this._onFeatureSelected(combobox.getRecord());
            }

                },
                scope: this
            }
    });
    this.add(features);
    },
    _onFeatureSelected:function(feature){
    console.log('feature', feature.get('Name'));

     var f  = {
            FormattedID: feature.get('FormattedID'),
            Name: feature.get('Name'),
            _ref: feature.get("_ref"),
            UserStories: []
        };

    var collection = feature.getCollection('UserStories', {fetch: ['Name','FormattedID','Owner', 'Defects']});
    var that = this;
    var count = collection.getCount();
    console.log(count);
    var stories = [];
    var pendingStories = count;
      collection.load({
            callback: function(records, operation, success){
                Ext.Array.each(records, function(story){
             var s  = {
                                FormattedID: story.get('FormattedID'),
                                Name: story.get('Name'),
                                _ref: story.get("_ref"),
                                DefectCount: story.get('Defects').Count,
                                Defects: []
                            };
                var defects = story.getCollection('Defects');
                var defectcount = defects.getCount();
                var pendingDefects = defectcount;
                 defects.load({
                                fetch: ['FormattedID'],
                                callback: function(records, operation, success){
                                    Ext.Array.each(records, function(defect){
                                        s.Defects.push({_ref: defect.get('_ref'),
                                                        FormattedID: defect.get('FormattedID')
                                                    });
                                    }, this);

                                    --pendingDefects;
                                    if (pendingDefects === 0) {
                     console.log(story.get('FormattedID') + ' - ' + story.get('Name'));
                    --pendingStories;
                     if (pendingStories === 0) {
                        console.log('stories inside callback',stories);
                     }

                                    }
                    console.log('makeGrid');
                                    that._makeGrid(stories);
                                },
                                scope: this
                            });
            stories.push(s);
            }, this);           
        }

        });
    },

     _makeGrid: function(stories) {
     var c = Ext.create('Ext.Container', {
        layout: {
        type: 'absolute'
        },
        x: 400
    });
    this.add(c);
        this._store = Ext.create('Rally.data.custom.Store', {
                data: stories,
                pageSize: 100,
                remoteSort:false
            });

        if (!this.down('#grid')){
         c.add({
            xtype: 'rallygrid',
            itemId: 'grid',
            store: this._store,
            columnCfgs: [
                {
                   text: 'Formatted ID', dataIndex: 'FormattedID', xtype: 'templatecolumn',
                    tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
                },
                {
                    text: 'Name', dataIndex: 'Name'
                },
         {
                    text: 'Defect Count', dataIndex: 'DefectCount'
                },
                {
                    text: 'Defects', dataIndex: 'Defects', 
                    renderer: function(value) {
                        var html = [];
                        Ext.Array.each(value, function(defect){
                            html.push('<a href="' + Rally.nav.Manager.getDetailUrl(defect) + '">' + defect.FormattedID + '</a>')
                        });
                        return html.join(', ');
                    }
                }
            ]

        });
        }
        else{
            this.down('#grid').reconfigure(this._store);
        }
    }
});