I'm in the middle of an aggregation pipeline when I have the following data passed from the previous step as input to a "count all leaves in all branches/trees/forests" as indicated in this question: Need guidance on mongo aggregate lookup to count subnodes in all child nodes for each node
{
forest_ranger: "bob",
some_other_data: {},
forests: [
{
forest_id: 'forestA',
trees: [
{
tree_id: 'treeA',
branches: [
{
branch_id: 'branchA',
}
]
}
]
}
],
}
I have that all working properly, but only if I'm reading directly from a collection.
My question is - how can I
a) save forest_ranger and some_other_data "for later"
b) and start the next stage by mapping forests as the array of
documents that are input to the "count all leaves"?
Note - I tried to get just (b) working for starters, but I can't seem to treat "forests" as an array so that the solution in the link above can do its magic.
Then- when that phase is done, extract what was stored in (a) to get the original format back with all the counts.
That is the main disadvantage of using $group. You have to explicitly carry forward every non participating fields using either $first or $last like this:
Get the test data with those additional fields from this new MongoPlayground link.
Check the updated query taken from the link:
db.Forests.aggregate([
{ $unwind: "$forests" },
{ $unwind: "$forests.trees" },
{ $unwind: "$forests.trees.branches" },
{
$lookup: {
from: "Leaves",
localField: "forests.trees.branches.branch_id",
foreignField: "branch_id",
as: "forests.trees.branches.leaves"
}
},
{
$addFields: {
"forests.trees.branches.leaf_count": { $size: "$forests.trees.branches.leaves" }
}
},
{
$project: { "forests.trees.branches.leaves": 0 }
},
{
$group: {
_id: {
forest_id: "$forests.forest_id",
tree_id: "$forests.trees.tree_id"
},
forest_ranger: { $first: "$forest_ranger" },
some_other_data: { $first: "$some_other_data" },
leaf_count: { $sum: "$forests.trees.branches.leaf_count" },
branches: { $push: "$forests.trees.branches" }
}
},
{
$group: {
_id: "$_id.forest_id",
forest_ranger: { $first: "$forest_ranger" },
some_other_data: { $first: "$some_other_data" },
leaf_count: { $sum: "$leaf_count" },
trees: {
$push: {
leaf_count: { $sum: "$leaf_count" },
tree_id: "$_id.tree_id",
branches: "$branches"
}
}
}
},
{
$group: {
_id: null,
forest_ranger: { $first: "$forest_ranger" },
some_other_data: { $first: "$some_other_data" },
leaf_count: { $sum: "$leaf_count" },
forests: {
$push: {
leaf_count: { $sum: "$leaf_count" },
forest_id: "$_id",
trees: "$trees"
}
}
}
},
{
$project: { _id: 0 }
}
])
Output:
{
"forest_ranger" : "bob",
"some_other_data" : {
"data" : "Dummy data"
},
"leaf_count" : 4,
"forests" : [
{
"leaf_count" : 3,
"forest_id" : "forestA",
"trees" : [
{
"leaf_count" : 1,
"tree_id" : "treeB",
"branches" : [
{
"branch_id" : "branchB",
"leaf_count" : 1
}
]
},
{
"leaf_count" : 2,
"tree_id" : "treeA",
"branches" : [
{
"branch_id" : "branchA",
"leaf_count" : 1
},
{
"branch_id" : "branchA1",
"leaf_count" : 1
},
{
"branch_id" : "branchA2",
"leaf_count" : 0
}
]
}
]
},
{
"leaf_count" : 1,
"forest_id" : "forestB",
"trees" : [
{
"leaf_count" : 0,
"tree_id" : "treeD",
"branches" : [
{
"branch_id" : "branchD",
"leaf_count" : 0
}
]
},
{
"leaf_count" : 1,
"tree_id" : "treeC",
"branches" : [
{
"branch_id" : "branchC",
"leaf_count" : 1
}
]
}
]
},
{
"leaf_count" : 0,
"forest_id" : "forestC",
"trees" : [
{
"leaf_count" : 0,
"tree_id" : "treeE",
"branches" : [
{
"branch_id" : "branchE",
"leaf_count" : 0
}
]
}
]
}
]
}
I am trying to use the Rally App Catalog and the custom list app
I would like to add an additional selector (item type) and rename some of the headers on the fly. First I just want the sample to run. As it currently stands, it looks broken to me.
When I use the rally-app-builder build function and then paste the result app.html into a custom app I get a blank page with working app settings.
Here the uncompressed version from my initial attempt.
<script src="https://rally1.rallydev.com/apps/2.1/sdk-debug.js"></script>
<!DOCTYPE html>
<html>
<head>
<title>Custom List</title>
<script type="text/javascript" src="/apps/2.1/sdk.js"></script>
<script type="text/javascript">
Rally.onReady(function() {
(function() {
var Ext = window.Ext4 || window.Ext;
var getHiddenFieldConfig = function(name) {
return {
name: name,
xtype: 'rallytextfield',
hidden: true,
handlesEvents: {
typeselected: function(type) {
this.setValue(null);
}
}
};
};
Ext.define('Rally.apps.customlist.Settings', {
singleton: true,
requires: [
'Rally.ui.combobox.FieldComboBox',
'Rally.ui.combobox.ComboBox',
'Rally.ui.CheckboxField'
],
getFields: function(app) {
this.app = app;
return [{
name: 'type',
xtype: 'rallycombobox',
allowBlank: false,
autoSelect: false,
shouldRespondToScopeChange: true,
context: this.app.getContext(),
initialValue: 'HierarchicalRequirement',
storeConfig: {
model: Ext.identityFn('TypeDefinition'),
sorters: [{
property: 'DisplayName'
}],
fetch: ['DisplayName', 'ElementName', 'TypePath', 'Parent', 'UserListable'],
filters: [{
property: 'UserListable',
value: true
}],
autoLoad: false,
remoteSort: false,
remoteFilter: true
},
displayField: 'DisplayName',
valueField: 'TypePath',
listeners: {
select: function(combo) {
this.app.clearFiltersAndSharedViews();
combo.fireEvent('typeselected', combo.getRecord().get('TypePath'), combo.context);
},
scope: this
},
bubbleEvents: ['typeselected'],
readyEvent: 'ready',
handlesEvents: {
projectscopechanged: function(context) {
this.refreshWithNewContext(context);
}
}
}, {
type: 'query'
}, {
name: 'showControls',
xtype: 'rallycheckboxfield',
fieldLabel: 'Show Control Bar'
},
getHiddenFieldConfig('columnNames'),
getHiddenFieldConfig('order')
];
}
});
})();
(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.BulkRecordUpdater',
'Rally.data.ModelTypes',
'Rally.data.PreferenceManager',
'Rally.data.util.Sorter',
'Rally.data.wsapi.Filter',
'Rally.ui.gridboard.plugin.GridBoardInlineFilterControl',
'Rally.ui.gridboard.plugin.GridBoardSharedViewControl',
'Rally.ui.notify.Notifier',
'Rally.util.String'
],
disallowedAddNewTypes: ['user', 'userprofile', 'useriterationcapacity', 'testcaseresult', 'task', 'scmrepository', 'project', 'changeset', 'change', 'builddefinition', 'build', 'program'],
orderedAllowedPageSizes: [10, 25, 50, 100, 200],
readOnlyGridTypes: ['build', 'change', 'changeset'],
statePrefix: 'customlist',
allowExpansionStateToBeSaved: false,
isEditable: true,
config: {
defaultSettings: {
showControls: true
}
},
initComponent: function() {
this.appName = 'CustomList-' + this.getAppId();
this.callParent(arguments);
},
getSettingsFields: function() {
return Rally.apps.customlist.Settings.getFields(this);
},
loadModelNames: function() {
this.modelNames = _.compact([this._getTypeSetting()]);
this._setColumnNames(this._getColumnNamesSetting());
return Deft.Promise.when(this.modelNames);
},
addGridBoard: function() {
this.callParent(arguments);
if (!this.getSetting('showControls')) {
this.gridboard.getHeader().hide();
}
},
loadGridBoard: function() {
if (_.isEmpty(this.modelNames)) {
Ext.defer(function() {
this.fireEvent('settingsneeded', this);
this.publishComponentReady();
}, 1, this);
} else {
this.enableAddNew = this._shouldEnableAddNew();
this.enableRanking = this._shouldEnableRanking();
this.callParent(arguments);
}
},
getGridConfig: function() {
var config = _.merge(this.callParent(arguments), {
allColumnsStateful: true,
enableEditing: !_.contains(this.readOnlyGridTypes, this._getTypeSetting().toLowerCase()),
listeners: {
beforestaterestore: this._onBeforeGridStateRestore,
beforestatesave: this._onBeforeGridStateSave,
scope: this
},
pagingToolbarCfg: {
hidden: !this.getSetting('showControls'),
pageSizes: this.orderedAllowedPageSizes
}
});
var invalidQueryFilters = Rally.util.Filter.findInvalidSubFilters(this._getQueryFilter(), this.models);
if (invalidQueryFilters.length) {
config.store.on('beforeload', function(store) {
Ext.defer(function() {
store.fireEvent('load', store, store.getRootNode(), [], true);
}, 1);
return false;
});
this._showInvalidQueryMessage(config, _.map(invalidQueryFilters, function(filter) {
return 'Could not find the attribute "' + filter.property.split('.')[0] + '" on type "' + this.models[0].displayName + '" in the query segment "' + filter.toString() + '"';
}, this));
}
return config;
},
getColumnCfgs: function() {
return _.union(this.callParent(arguments), _.isEmpty(this.columnNames) && this.enableRanking ? ['DragAndDropRank'] : []);
},
getFilterControlConfig: function() {
return _.merge(this.callParent(arguments), {
listeners: {
beforestaterestore: {
fn: this._onBeforeFilterButtonStateRestore,
scope: this
}
}
});
},
getGridBoardCustomFilterControlConfig: function() {
var context = this.getContext();
var isArtifactModel = this.models[0].isArtifact();
var blackListFields = isArtifactModel ? ['ModelType', 'PortfolioItemType'] : ['ArtifactSearch', 'ModelType'];
var whiteListFields = isArtifactModel ? ['Milestones', 'Tags'] : [];
if (this.models[0].isProject()) {
blackListFields.push('SchemaVersion');
} else if (this.models[0].isRelease()) {
blackListFields.push('ChildrenPlannedVelocity', 'Version');
}
var config = {
ptype: 'rallygridboardinlinefiltercontrol',
inlineFilterButtonConfig: {
stateful: true,
stateId: context.getScopedStateId('custom-list-inline-filter'),
legacyStateIds: [
this.getScopedStateId('owner-filter'),
this.getScopedStateId('custom-filter-button')
],
filterChildren: true,
inlineFilterPanelConfig: {
quickFilterPanelConfig: {
defaultFields: isArtifactModel ? ['ArtifactSearch', 'Owner'] : [],
addQuickFilterConfig: {
blackListFields: blackListFields,
whiteListFields: whiteListFields
}
},
advancedFilterPanelConfig: {
advancedFilterRowsConfig: {
propertyFieldConfig: {
blackListFields: blackListFields,
whiteListFields: whiteListFields
}
}
}
}
}
};
if (isArtifactModel) {
config.inlineFilterButtonConfig.modelNames = this.modelNames;
} else {
config.inlineFilterButtonConfig.model = this.models[0];
}
return config;
},
getSharedViewConfig: function() {
var context = this.getContext();
return {
ptype: 'rallygridboardsharedviewcontrol',
sharedViewConfig: {
stateful: true,
stateId: context.getScopedStateId('custom-list-shared-view'),
enableUrlSharing: this.isFullPageApp !== false
}
};
},
getGridBoardConfig: function() {
var config = this.callParent(arguments);
return _.merge(config, {
listeners: {
viewchange: function() {
this.loadGridBoard();
},
filterchange: function() {
this.gridboard.getGridOrBoard().noDataPrimaryText = undefined;
this.gridboard.getGridOrBoard().noDataSecondaryText = undefined;
},
scope: this
}
});
},
onTreeGridReady: function(grid) {
if (grid.store.getTotalCount() > 10) {
this.gridboard.down('#pagingToolbar').show();
}
this.callParent(arguments);
},
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 {
listeners: {
warning: {
fn: this._onGridStoreWarning,
scope: this
}
},
pageSize: 10,
sorters: sorters
};
},
getAddNewConfig: function() {
var config = {
minWidth: 700,
openEditorAfterAddFailure: false,
margin: 0
};
if (!this.getContext().isFeatureEnabled('F6971_REACT_DASHBOARD_PANELS')) {
config.disableAddButton = this.appContainer.slug === 'incompletestories';
}
return _.merge(this.callParent(arguments), config);
},
getFieldPickerConfig: function() {
return _.merge(this.callParent(arguments), {
buttonConfig: {
disabled: !this._userHasPermissionsToEditPanelSettings()
},
gridAlwaysSelectedValues: function() {
return [];
},
gridFieldBlackList: this._getTypeSetting().toLowerCase() === 'task' ? ['Rank'] : []
});
},
getPermanentFilters: function() {
return this._getQueryFilter().concat(this._getTimeboxScopeFilter()).concat(this._getProjectFilter());
},
onTimeboxScopeChange: function() {
this.callParent(arguments);
this.loadGridBoard();
},
clearFiltersAndSharedViews: function() {
var context = this.getContext();
if (this.gridboard) {
this.gridboard.down('rallyinlinefilterpanel').clear();
this.gridboard.down('rallysharedviewcombobox').reset();
}
Ext.create('Rally.data.wsapi.Store', {
model: Ext.identityFn('preference'),
autoLoad: true,
filters: [{
property: 'AppId',
value: context.getAppId()
}, {
property: 'Type',
value: 'View'
}, {
property: 'Workspace',
value: context.getWorkspace()._ref
}],
context: context.getDataContext(),
listeners: {
load: function(store, records) {
if (!_.isEmpty(records)) {
var batchStore = Ext.create('Rally.data.wsapi.batch.Store', {
requester: this,
data: records
});
batchStore.removeAll();
batchStore.sync();
}
store.destroyStore();
},
scope: this
}
});
},
_getTypeSetting: function() {
return this.getSetting('type') || this.getSetting('url');
},
_getColumnNamesSetting: function() {
return this.getSetting('columnNames') ||
(this.getSetting('fetch') || '').split(',');
},
_getQueryFilter: function() {
var query = new Ext.Template(this.getSetting('query')).apply({
projectName: this.getContext().getProject().Name,
projectOid: this.getContext().getProject().ObjectID,
user: this.getContext().getUser()._ref
});
if (query) {
try {
return [Rally.data.wsapi.Filter.fromQueryString(query)];
} catch (e) {
Rally.ui.notify.Notifier.showError({
message: e.message
});
}
}
return [];
},
_getProjectFilter: function() {
return this.modelNames[0].toLowerCase() === 'milestone' ? [
Rally.data.wsapi.Filter.or([{
property: 'Projects',
operator: 'contains',
value: this.getContext().getProjectRef()
}, {
property: 'TargetProject',
operator: '=',
value: null
}])
] : [];
},
_getTimeboxScopeFilter: function() {
var timeboxScope = this.getContext().getTimeboxScope();
var hasTimeboxField = timeboxScope && _.any(this.models, timeboxScope.isApplicable, timeboxScope);
return hasTimeboxField ? [timeboxScope.getQueryFilter()] : [];
},
_shouldEnableAddNew: function() {
return !_.contains(this.disallowedAddNewTypes, this._getTypeSetting().toLowerCase());
},
_shouldEnableRanking: function() {
return this._getTypeSetting().toLowerCase() !== 'task';
},
_setColumnNames: function(columnNames) {
this.columnNames = _.compact(_.isString(columnNames) ? columnNames.split(',') : columnNames);
},
_onBeforeFilterButtonStateRestore: function(filterButton, state) {
if (state && state.filters && state.filters.length) {
var stateFilters = _.map(state.filters, function(filterStr) {
return Rally.data.wsapi.Filter.fromQueryString(filterStr);
});
var validFilters = Rally.util.Filter.removeNonapplicableTypeSpecificFilters(stateFilters, this.models);
state.filters = _.invoke(validFilters, 'toString');
}
},
_hasViewSelected: function() {
var sharedViewConfig = this.getSharedViewConfig().sharedViewConfig;
if (sharedViewConfig && sharedViewConfig.stateId) {
var value = (Ext.state.Manager.get(sharedViewConfig.stateId) || {}).value;
return !_.isEmpty(value);
}
return false;
},
_onBeforeGridStateRestore: function(grid, state) {
if (!state) {
return;
}
if (state.columns) {
var appScopedColumnNames = this._getValidUuids(grid, this.getColumnCfgs());
var userScopedColumnNames = this._getValidUuids(grid, state.columns);
if (this._hasViewSelected()) {
state.columns = userScopedColumnNames;
} else {
// Get the columns that are present in the app scope and not in the user scope
var differingColumns = _.difference(appScopedColumnNames, userScopedColumnNames);
// If there are columns in the app scope that are not in the
// user scope, append them to the user scope to preserve
// user scope column order
if (differingColumns.length > 0) {
state.columns = state.columns.concat(differingColumns);
}
// Filter out any columns that are in the user scope that are not in the
// app scope
state.columns = _.filter(state.columns, function(column) {
return _.contains(appScopedColumnNames, _.isObject(column) ? column.dataIndex : column);
}, this);
}
}
if (state.sorters) {
state.sorters = this._getValidSorters(state.sorters);
if (_.isEmpty(state.sorters)) {
delete state.sorters;
}
}
},
_getValidUuids: function(grid, columns) {
return _.reduce(columns, function(result, column) {
var dataIndex = this._getColumnDataIndex(column);
var field = this._getModelField(grid, dataIndex);
if (field) {
result.push(dataIndex);
}
return result;
}, [], this);
},
_getModelField: function(grid, dataIndex) {
return grid.getModels()[0].getField(dataIndex);
},
_getColumnDataIndex: function(column) {
return _.isObject(column) ? column.dataIndex : column;
},
_onBeforeGridStateSave: function(grid, state) {
var newColumnNames = this._getColumnNamesFromState(state);
if (!_.isEmpty(newColumnNames)) {
this._setColumnNames(newColumnNames);
if (this._userHasPermissionsToEditPanelSettings()) {
this.updateSettingsValues({
settings: {
columnNames: newColumnNames.join(',')
}
});
}
}
},
_onGridStoreWarning: function(store, warnings, operation) {
var couldNotParseWarnings = _.filter(warnings, function(warning) {
return Rally.util.String.startsWith(warning, 'Could not parse ');
});
if (couldNotParseWarnings.length) {
_.assign(operation.resultSet, {
count: 0,
records: [],
total: 0,
totalRecords: 0
});
this._showInvalidQueryMessage(this.gridboard.getGridOrBoard(), couldNotParseWarnings);
}
},
_showInvalidQueryMessage: function(gridOrGridConfig, secondaryTextStrings) {
gridOrGridConfig.noDataPrimaryText = 'Invalid Query';
gridOrGridConfig.noDataSecondaryText = _.map(secondaryTextStrings, function(str) {
return '<div>' + str + '</div>';
}).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.isEditable;
},
_getColumnNamesFromState: function(state) {
return _(state && state.columns).map(function(newColumn) {
return _.isObject(newColumn) ? newColumn.dataIndex : newColumn;
}).compact().value();
}
});
})();
Rally.launchApp('Rally.apps.customlist.CustomListApp', {
name: "Custom List",
parentRepos: ""
});
});
</script>
</head>
<body>
</body>
</html>
Ok, I got results by commenting out the following lines in getAddNewConfig:
getAddNewConfig: function () {
var config = {
minWidth: 700,
openEditorAfterAddFailure: false,
margin: 0
};
//if(!this.getContext().isFeatureEnabled('F6971_REACT_DASHBOARD_PANELS')) {
// config.disableAddButton = this.appContainer.slug === 'incompletestories';
//}
return _.merge(this.callParent(arguments), config);
},
It seems that 'this.appContainer.slug' is undefined for me.
Here's the final working CustomListApp.js generated from the comment stream in the answer above. The main tweaks were deleting some old code checking a feature toggle and giving it a default type to display (defect in this case) in the defaultSettings block.
(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.BulkRecordUpdater',
'Rally.data.ModelTypes',
'Rally.data.PreferenceManager',
'Rally.data.util.Sorter',
'Rally.data.wsapi.Filter',
'Rally.ui.gridboard.plugin.GridBoardInlineFilterControl',
'Rally.ui.gridboard.plugin.GridBoardSharedViewControl',
'Rally.ui.notify.Notifier',
'Rally.util.String'
],
disallowedAddNewTypes: ['user', 'userprofile', 'useriterationcapacity', 'testcaseresult', 'task', 'scmrepository', 'project', 'changeset', 'change', 'builddefinition', 'build', 'program'],
orderedAllowedPageSizes: [10, 25, 50, 100, 200],
readOnlyGridTypes: ['build', 'change', 'changeset'],
statePrefix: 'customlist',
allowExpansionStateToBeSaved: false,
isEditable: true,
config: {
defaultSettings: {
showControls: true,
type: 'defect' //important to include a default type to display
}
},
initComponent: function () {
this.appName = 'CustomList-' + this.getAppId();
if (this.defaultSettings.url) {
Ext.apply(this.defaultSettings, { type: this.defaultSettings.url });
}
this.callParent(arguments);
},
getSettingsFields: function() {
return Rally.apps.customlist.Settings.getFields(this);
},
loadModelNames: function () {
this.modelNames = _.compact(this.getTypeSetting());
this._setColumnNames(this._getColumnNamesSetting());
return Deft.Promise.when(this.modelNames);
},
addGridBoard: function () {
this.callParent(arguments);
if (!this.getSetting('showControls')) {
this.gridboard.getHeader().hide();
}
},
loadGridBoard: function () {
if (_.isEmpty(this.modelNames)) {
Ext.defer(function () {
this.fireEvent('settingsneeded', this);
this.publishComponentReady();
}, 1, this);
} else {
this.enableAddNew = this._shouldEnableAddNew();
this.enableRanking = this._shouldEnableRanking();
this.callParent(arguments);
}
},
getGridConfig: function () {
var config = _.merge(this.callParent(arguments), {
allColumnsStateful: true,
enableEditing: _.intersection(this.readOnlyGridTypes, this.getTypeSetting()).length === 0,
listeners: {
beforestaterestore: this._onBeforeGridStateRestore,
beforestatesave: this._onBeforeGridStateSave,
scope: this
},
pagingToolbarCfg: {
hidden: !this.getSetting('showControls'),
pageSizes: this.orderedAllowedPageSizes
}
});
var invalidQueryFilters = Rally.util.Filter.findInvalidSubFilters(this._getQueryFilter(), this.models);
if (invalidQueryFilters.length) {
config.store.on('beforeload', function (store) {
Ext.defer(function () {
store.fireEvent('load', store, store.getRootNode(), [], true);
}, 1);
return false;
});
this._showInvalidQueryMessage(config, _.map(invalidQueryFilters, function (filter) {
return 'Could not find the attribute "'+ filter.property.split('.')[0] +'" on type "'+ this.models[0].displayName +'" in the query segment "'+ filter.toString() + '"';
}, this));
}
return config;
},
getColumnCfgs: function() {
return _.union(this.callParent(arguments), _.isEmpty(this.columnNames) && this.enableRanking ? ['DragAndDropRank'] : []);
},
getFilterControlConfig: function () {
return _.merge(this.callParent(arguments), {
listeners: {
beforestaterestore: {
fn: this._onBeforeFilterButtonStateRestore,
scope: this
}
}
});
},
getGridBoardCustomFilterControlConfig: function() {
var context = this.getContext();
var isArtifactModel = this.models[0].isArtifact();
var blackListFields = isArtifactModel ? ['ModelType', 'PortfolioItemType', 'LastResult'] : ['ArtifactSearch', 'ModelType'];
var whiteListFields = isArtifactModel ? ['Milestones', 'Tags'] : [];
if (this.models[0].isProject()) {
blackListFields.push('SchemaVersion');
} else if (this.models[0].isRelease()) {
blackListFields.push('ChildrenPlannedVelocity', 'Version');
}
var config = {
ptype: 'rallygridboardinlinefiltercontrol',
inlineFilterButtonConfig: {
stateful: true,
stateId: context.getScopedStateId('custom-list-inline-filter'),
legacyStateIds: [
this.getScopedStateId('owner-filter'),
this.getScopedStateId('custom-filter-button')
],
filterChildren: true,
inlineFilterPanelConfig: {
quickFilterPanelConfig: {
defaultFields: isArtifactModel ? ['ArtifactSearch', 'Owner'] : [],
addQuickFilterConfig: {
blackListFields: blackListFields,
whiteListFields: whiteListFields
}
},
advancedFilterPanelConfig: {
advancedFilterRowsConfig: {
propertyFieldConfig: {
blackListFields: blackListFields,
whiteListFields: whiteListFields
}
}
}
}
}
};
if (isArtifactModel) {
config.inlineFilterButtonConfig.modelNames = this.modelNames;
} else {
config.inlineFilterButtonConfig.model = this.models[0];
}
return config;
},
getSharedViewConfig: function() {
var context = this.getContext();
return {
ptype: 'rallygridboardsharedviewcontrol',
sharedViewConfig: {
stateful: true,
stateId: context.getScopedStateId('custom-list-shared-view'),
enableUrlSharing: this.isFullPageApp !== false
}
};
},
getGridBoardConfig: function () {
var config = this.callParent(arguments);
return _.merge(config, {
listeners: {
viewchange: function() {
this.loadGridBoard();
},
filterchange: function() {
this.gridboard.getGridOrBoard().noDataPrimaryText = undefined;
this.gridboard.getGridOrBoard().noDataSecondaryText = undefined;
},
scope: this
}
});
},
onTreeGridReady: function (grid) {
if (grid.store.getTotalCount() > 10) {
this.gridboard.down('#pagingToolbar').show();
}
this.callParent(arguments);
},
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 {
listeners: {
warning: {
fn: this._onGridStoreWarning,
scope: this
}
},
pageSize: 10,
sorters: sorters
};
},
getAddNewConfig: function () {
var config = {
minWidth: 700,
openEditorAfterAddFailure: false,
margin: 0
};
return _.merge(this.callParent(arguments), config);
},
getFieldPickerConfig: function () {
return _.merge(this.callParent(arguments), {
buttonConfig: {
disabled: !this._userHasPermissionsToEditPanelSettings()
},
gridAlwaysSelectedValues: function () { return []; },
gridFieldBlackList: this._shouldEnableRanking() ? [] : ['Rank']
});
},
getPermanentFilters: function () {
return this._getQueryFilter().concat(this._getTimeboxScopeFilter()).concat(this._getProjectFilter());
},
onTimeboxScopeChange: function() {
this.callParent(arguments);
this.loadGridBoard();
},
clearFiltersAndSharedViews: function() {
var context = this.getContext();
if (this.gridboard) {
this.gridboard.down('rallyinlinefilterpanel').clear();
this.gridboard.down('rallysharedviewcombobox').reset();
}
Ext.create('Rally.data.wsapi.Store', {
model: Ext.identityFn('preference'),
autoLoad: true,
filters: [
{property: 'AppId', value: context.getAppId()},
{property: 'Type', value: 'View'},
{property: 'Workspace', value: context.getWorkspace()._ref}
],
context: context.getDataContext(),
listeners: {
load: function(store, records) {
if(!_.isEmpty(records)) {
var batchStore = Ext.create('Rally.data.wsapi.batch.Store', {
requester: this,
data: records
});
batchStore.removeAll();
batchStore.sync();
}
store.destroyStore();
},
scope: this
}
});
},
getTypeSetting: function() {
return (this.getSetting('type') || this.getSetting('url') || '').toLowerCase().split(',');
},
_getColumnNamesSetting: function() {
return this.getSetting('columnNames') ||
(this.getSetting('fetch') || '').split(',');
},
_getQueryFilter: function () {
var query = new Ext.Template(this.getSetting('query')).apply({
projectName: this.getContext().getProject().Name,
projectOid: this.getContext().getProject().ObjectID,
user: this.getContext().getUser()._ref
});
if (query) {
try {
return [ Rally.data.wsapi.Filter.fromQueryString(query) ];
} catch(e) {
Rally.ui.notify.Notifier.showError({ message: e.message });
}
}
return [];
},
_getProjectFilter: function () {
return this.modelNames[0].toLowerCase() === 'milestone' ? [
Rally.data.wsapi.Filter.or([
{ property: 'Projects', operator: 'contains', value: this.getContext().getProjectRef() },
{ property: 'TargetProject', operator: '=', value: null }
])
] : [];
},
_getTimeboxScopeFilter: function () {
var timeboxScope = this.getContext().getTimeboxScope();
var hasTimeboxField = timeboxScope && _.any(this.models, timeboxScope.isApplicable, timeboxScope);
return hasTimeboxField ? [ timeboxScope.getQueryFilter() ] : [];
},
_shouldEnableAddNew: function() {
return _.intersection(this.disallowedAddNewTypes, this.getTypeSetting()).length === 0;
},
_shouldEnableRanking: function() {
return !_.contains(this.getTypeSetting(), 'task');
},
_setColumnNames: function (columnNames) {
this.columnNames = _.compact(_.isString(columnNames) ? columnNames.split(',') : columnNames);
},
_onBeforeFilterButtonStateRestore: function (filterButton, state) {
if (state && state.filters && state.filters.length) {
var stateFilters = _.map(state.filters, function (filterStr) {
return Rally.data.wsapi.Filter.fromQueryString(filterStr);
});
var validFilters = Rally.util.Filter.removeNonapplicableTypeSpecificFilters(stateFilters, this.models);
state.filters = _.invoke(validFilters, 'toString');
}
},
_hasViewSelected: function() {
var sharedViewConfig = this.getSharedViewConfig().sharedViewConfig;
if (sharedViewConfig && sharedViewConfig.stateId) {
var value = (Ext.state.Manager.get(sharedViewConfig.stateId) || {}).value;
return !_.isEmpty(value);
}
return false;
},
_onBeforeGridStateRestore: function (grid, state) {
if (!state) {
return;
}
if (state.columns) {
var appScopedColumnNames = this._getValidUuids(grid, this.getColumnCfgs());
var userScopedColumnNames = this._getValidUuids(grid, state.columns);
if (this._hasViewSelected()) {
state.columns = userScopedColumnNames;
} else {
// Get the columns that are present in the app scope and not in the user scope
var differingColumns = _.difference(appScopedColumnNames, userScopedColumnNames);
// If there are columns in the app scope that are not in the
// user scope, append them to the user scope to preserve
// user scope column order
if (differingColumns.length > 0) {
state.columns = state.columns.concat(differingColumns);
}
// Filter out any columns that are in the user scope that are not in the
// app scope
state.columns = _.filter(state.columns, function (column) {
return _.contains(appScopedColumnNames, _.isObject(column) ? column.dataIndex : column);
}, this);
}
}
if (state.sorters) {
state.sorters = this._getValidSorters(state.sorters);
if (_.isEmpty(state.sorters)) {
delete state.sorters;
}
}
},
_getValidUuids: function(grid, columns) {
return _.reduce(columns, function(result, column) {
var dataIndex = this._getColumnDataIndex(column);
var field = this._getModelField(grid, dataIndex);
if (field) {
result.push(dataIndex);
}
return result;
}, [], this);
},
_getModelField: function(grid, dataIndex) {
return grid.getModels()[0].getField(dataIndex);
},
_getColumnDataIndex: function(column) {
return _.isObject(column) ? column.dataIndex : column;
},
_onBeforeGridStateSave: function (grid, state) {
var newColumnNames = this._getColumnNamesFromState(state);
if (!_.isEmpty(newColumnNames)) {
this._setColumnNames(newColumnNames);
if (this._userHasPermissionsToEditPanelSettings()) {
this.updateSettingsValues({
settings: {
columnNames: newColumnNames.join(',')
}
});
}
}
},
_onGridStoreWarning: function(store, warnings, operation) {
var couldNotParseWarnings = _.filter(warnings, function(warning){
return Rally.util.String.startsWith(warning, 'Could not parse ');
});
if(couldNotParseWarnings.length) {
_.assign(operation.resultSet, {
count: 0,
records: [],
total: 0,
totalRecords: 0
});
this._showInvalidQueryMessage(this.gridboard.getGridOrBoard(), couldNotParseWarnings);
}
},
_showInvalidQueryMessage: function(gridOrGridConfig, secondaryTextStrings) {
gridOrGridConfig.noDataPrimaryText = 'Invalid Query';
gridOrGridConfig.noDataSecondaryText = _.map(secondaryTextStrings, function(str){
return '<div>' + str + '</div>';
}).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.isEditable;
},
_getColumnNamesFromState: function (state) {
return _(state && state.columns).map(function (newColumn) {
return _.isObject(newColumn) ? newColumn.dataIndex : newColumn;
}).compact().value();
}
});
})();
Programmatically developing a TableContainer. All the child information and properties of TableContainer are fetched from a JSON file. In the client javascript code, we process this inoformation to prepare the TableContainer.
Planned to use TableContainer label as the Header for that container, but failed , as the container label is placed in the center of the first column as shown in the screenshot-1
I am expecting the headers to appear as in the screenshot-2
FYI, I have many tablecontainers placed in Root Table container where each table container should have a header
Following is the json:
"element":[
{
"type":"container",
"propertylist":{
"property":[
{
"name":"label",
"value":"Common Parameters"
},
{
"name":"id",
"value":"commonParams"
}
]
},
"element":[
{
"type":"textfield",
"propertylist":{
"property":[
{
"name":"label",
"value":"Profile Name"
},
{
"name":"id",
"value":"profileName"
}
]
}
},
{
"type":"textfield",
"propertylist":{
"property":[
{
"name":"label",
"value":"Description"
},
{
"name":"id",
"value":"description"
}
]
}
},
{
"type":"checkbox",
"propertylist":{
"property":[
{
"name":"label",
"value":"Dual-ended"
},
{
"name":"checked",
"value":false
},
{
"name":"id",
"value":"dualEnded"
}
]
}
},
{
"type":"checkbox",
"propertylist":{
"property":[
{
"name":"label",
"value":"DST is OAM-aware"
},
{
"name":"checked",
"value":false
},
{
"name":"id",
"value":"dstIsOAMAware"
}
]
}
},
{
"type":"combobox",
"propertylist":{
"property":[
{
"name":"label",
"value":"Traffic Type"
},
{
"name":"store",
"value":"{{name='Y.1731 OAM', id:1},{name='Simulated Customer',id:2}}"
},
{
"name":"value",
"value":"1"
},
{
"name":"id",
"value":"trafficType"
}
]
}
},
{
"type":"combobox",
"propertylist":{
"property":[
{
"name":"label",
"value":"MEG Level"
},
{
"name":"store",
"value":"{{id:0},{id:1},{id:2},{id:3},{id:4},{id:5}{id:6},{id:7}}"
},
{
"name":"value",
"value":"8"
},
{
"name":"id",
"value":"megLevel"
}
]
}
},
{
"type":"combobox",
"actionlist":{
"action":[
{
"name":"onclick",
"value":"handleUserDefinedFramesize"
}
]
},
"propertylist":{
"property":[
{
"name":"label",
"value":"Frame Size"
},
{
"name":"values",
"value":"{{id:1,name:'64 bytes'},{id:1,name:'128 bytes'},{id:1,name:'256 bytes'},{id:1,name:'512 bytes'},{id:1,name:'1024 bytes'},{id:1,name:'1280 bytes'},{id:1,name:'1518 bytes'},{id:1,name:'MTU'},{id:1,name:'User-defined'}}"
},
{
"name":"value",
"value":"4"
},
{
"name":"id",
"value":"frameSize"
}
]
}
},
{
"type":"textfield",
"propertylist":{
"property":[
{
"name":"label",
"value":"User-defined Frame Size"
},
{
"name":"units",
"value":"bytes"
},
{
"name":"disabled",
"value":"true"
},
{
"name":"value",
"value":"2000"
},
{
"name":"id",
"value":"userDefinedFS"
}
]
}
},
{
"type":"textfield",
"propertylist":{
"property":[
{
"name":"label",
"value":"Dwell Time"
},
{
"name":"units",
"value":"msecs"
},
{
"name":"value",
"value":"500"
},
{
"name":"id",
"value":"dwellTime"
}
]
}
}
]
}
Code to process the json:
function process(metaData){
//alert(JSON.stringify(metaData));
switch(metaData.type){
case "container":
var tableContainer = new dojox.layout.TableContainer({
cols: 1,
"labelWidth": "150"
});
if(metaData.propertylist){
metaData.propertylist.property.forEach(function(prop) {
//var row = domConstruct.toDom('<div class="greyHeader" style="width:40% !important;">' + prop.value + '</div>');
//tabContainer.addChild(row);
tableContainer.set(prop.name, prop.value);
});
}
return tableContainer;
break;
case "textfield":
var textBox = new dijit.form.TextBox();
metaData.propertylist.property.forEach(function(prop) {
textBox.set(prop.name, prop.value);
});
return textBox;
break;
case "checkbox":
var checkBox = new dijit.form.CheckBox();
metaData.propertylist.property.forEach(function(prop) {
checkBox.set(prop.name, prop.value);
});
return checkBox;
break;
case "combobox":
// store is a mandatory param that is required otherwise this.domNode is null
var filteringSelect = new dijit.form.FilteringSelect({store:new Memory({data:[]})});
console.log(JSON.stringify(metaData.propertylist.property));
metaData.propertylist.property.forEach(function(prop) {
if(prop.name == 'store'){
filteringSelect.store = new Memory({data: prop.value});
}else{
filteringSelect.set(prop.name, prop.value);
}
});
return filteringSelect;
break;
case "button":
var button = new Button();
metaData.propertylist.property.forEach(function(prop) {
button.set(prop.name, prop.value);
});
return button;
break;
case "contentpane":
var contentPane = new ContentPane();
if(metaData.propertylist){
metaData.propertylist.property.forEach(function(prop) {
contentPane.set(prop.name, prop.value);
});
}
return contentPane;
break;
}
}
Please let me know, how i can use table container's label as a header
Curious to know how we can add html text to a table container.
I have found a solution for this problem
Written a custom widget which takes label as a constructor parameter, and overridden the buildRendering function to return the following
this.domNode = domConstruct.toDom('' + '' + '');
then it appears as a label