(function() {
    var Ext = window.Ext4 || window.Ext;

    Ext.define('Rally.apps.customlist.CustomListApp', {
        extend: 'Rally.app.GridBoardApp',
        requires: [
            'Deft.Promise',
            'Rally.apps.customlist.Settings',
            'Rally.data.ModelTypes',
            'Rally.data.util.Sorter',
            'Rally.data.wsapi.Filter'
        ],

        statePrefix: 'customlist',
        disallowedAddNewTypes: ['user', 'userprofile', 'useriterationcapacity', 'testcaseresult', 'task', 'scmrepository', 'project', 'changeset', 'change', 'builddefinition', 'build'],

        initComponent: function () {
            this.defaultSettings = {
                query: this.appContainer.query,
                order: this.appContainer.order,
                type: this.appContainer.url,
                columnNames: (this.appContainer.fetch || '').split(',')
            };

            this.callParent(arguments);
        },

        getSettingsFields: function() {
            return Rally.apps.customlist.Settings.getFields(this.getContext());
        },

        loadModelNames: function () {
            this.modelNames = _.compact([this.getSetting('type')]);
            this._setColumnNames(this.getSetting('columnNames'));
            return Deft.Promise.when(this.modelNames);
        },

        loadGridBoard: function () {
            if (_.isEmpty(this.modelNames)) {
                Ext.defer(function () {
                    this.owner.showSettings();
                    this.publishComponentReady();
                }, 1, this);
            } else {
                this.enableAddNew = this._shouldEnableAddNew();
                this.callParent(arguments);
            }
        },

        getGridConfig: function () {
            return _.merge(this.callParent(arguments), {
                listeners: {
                    beforestaterestore: this._onBeforeGridStateRestore,
                    beforestatesave: this._onBeforeGridStateSave,
                    scope: this
                }
            });
        },

        getGridStoreConfig: function () {
            var sorters = this._getValidSorters(Rally.data.util.Sorter.sorters(this.getSetting('order')));

            if (_.isEmpty(sorters)) {
                var rankField = this.getContext().getWorkspace().WorkspaceConfiguration.DragDropRankingEnabled ? 'DragAndDropRank' : 'Rank';
                var defaultSort = Rally.data.ModelTypes.areArtifacts(this.modelNames) ? rankField : Rally.data.util.Sorter.getDefaultSort(this.modelNames[0]);

                sorters = Rally.data.util.Sorter.sorters(defaultSort);
            }

            return _.merge({
                sorters: sorters
            }, this.appContainer.pagesize ? { pageSize: this.appContainer.pagesize } : {});
        },

        getAddNewConfig: function () {
            return _.merge(this.callParent(arguments), {
                minWidth: 700,
                openEditorAfterAddFailure: false
            });
        },

        getFieldPickerConfig: function () {
            return _.merge(this.callParent(arguments), {
                buttonConfig: {
                    disabled: !this._userHasPermissionsToEditPanelSettings()
                }
            });
        },

        getPermanentFilters: function () {
            var query = this.getSetting('query');
            return query ? [ Rally.data.wsapi.Filter.fromQueryString(query) ] : [];
        },

        _shouldEnableAddNew: function() {
            return !_.contains(this.disallowedAddNewTypes, this.getSetting('type').toLowerCase());
        },

        _setColumnNames: function (columnNames) {
            columnNames = _.isString(columnNames) ? columnNames.split(',') : columnNames;

            // Note: When enableHierarchy is true (artifacts only for now), the FormattedID column is always added
            // If we include FormattedID in columns names, we get 2 ID columns, which is poopy.
            // This problem should be fixed for reals in the SDK somewhere eventually.
            if (Rally.data.ModelTypes.areArtifacts(this.modelNames)) {
                columnNames = _.reject(columnNames, function (columnName) {
                    return _.isString(columnName) && columnName.toLowerCase() === 'formattedid';
                });
            }

            this.columnNames = _.compact(columnNames);
        },

        _onBeforeGridStateRestore: function (grid, state) {
            if (!state) {
                return;
            }

            if (state.columns) {
                var appScopedColumnNames = this.getColumnCfgs();
                var userScopedColumnNames = this._getColumnNamesFromState(state);

                if(_.difference(appScopedColumnNames, userScopedColumnNames).length > 0) {
                    state.columns = appScopedColumnNames;
                } else {
                    state.columns = _.filter(state.columns, function (column) {
                        return _.contains(appScopedColumnNames, _.isObject(column) ? column.dataIndex : column);
                    });
                }
            }

            if (state.sorters) {
                state.sorters = this._getValidSorters(state.sorters);
                if (_.isEmpty(state.sorters)) {
                    delete state.sorters;
                }
            }
        },

        _onBeforeGridStateSave: function (grid, state) {
            var newColumnNames = this._getColumnNamesFromState(state);

            if (!_.isEmpty(newColumnNames)) {
                this._setColumnNames(newColumnNames);

                if (this._userHasPermissionsToEditPanelSettings()) {
                    this.updateSettingsValues({
                        settings: {
                            columnNames: newColumnNames.join(',')
                        }
                    });
                }
            }
        },

        _getValidSorters: function (sorters) {
            return _.filter(sorters, function (sorter) {
                return _.any(this.models, function (model) {
                    var field = model.getField(sorter.property);
                    return field && field.sortable;
                });
            }, this);
        },

        _userHasPermissionsToEditPanelSettings: function () {
            return this.appContainer.isEditable;
        },

        _getColumnNamesFromState: function (state) {
            return _(state && state.columns).map(function (newColumn) {
                return _.isObject(newColumn) ? newColumn.dataIndex : newColumn;
            }).compact().value();
        }
    });
})();

@@ -3370,16 +3370,53 @@
  return 
+this._getTimeboxScopeFilter().concat(
 query ? 
@@ -3467,16 +3467,410 @@
 ery) %5D :
+ %5B%5D);%0A        %7D,%0A%0A        _getTimeboxScopeFilter: function () %7B%0A            var timeboxScope = this.getContext().getTimeboxScope();%0A            var hasTimeboxField = timeboxScope && _.any(this.models, function (model) %7B%0A                return model.hasField(Ext.String.capitalize(timeboxScope.getType()));%0A            %7D);%0A            return hasTimeboxField ? %5B timeboxScope.getQueryFilter() %5D :
  %5B%5D;%0A   

Verify TreeGrid custom App functionality