Ext.define("tag-management", {
extend: 'Rally.app.App',
componentCls: 'app',
logger: new Rally.technicalservices.Logger(),
defaults: { margin: 10 },
items: [
{xtype:'container',itemId:'message_box',tpl:'Hello, {_refObjectName}'},
{xtype:'container',itemId:'display_box'}
],
integrationHeaders : {
name : "tag-management"
},
config: {
defaultSettings: {
useLookback: false
}
},
launch: function() {
this.tagMetrics = Ext.create('CATS.tag-management.utils.TagMetrics',{});
//this.tagMetrics.on('refreshcount', this._updateTagData, this);
this.subscribe('tagDataUpdated', this._updateTagData, this);
this.subscribe('tagArchived', this._updateArchivedTagData, this);
this.subscribe('tagDeleted', this._updateDeletedTagData, this);
this.subscribe('tagsDeleted', this._updateDeletedTagData, this);
this._fetchPortfolioItemTypes().then({
success: this._initializeApp,
failure: this._showErrorNotification,
scope: this
});
this._fetchTags();
this._fetchCurrentTagUsageData();
// this._fetchTags().then({
// success: this._buildTagMetrics,
// failure: this._showErrorNotification,
// scope: this
// }).always(function(){
// this.setLoading(false);
// },this);
},
_showErrorNotification: function(msg){
Rally.ui.notify.Notifier.showError({message: msg});
},
_updateArchivedTagData: function(tags){
this.logger.log('updateArchivedTagData');
this.tagMetrics.archiveTags(tags);
this._updateView();
},
_updateDeletedTagData: function(tags){
this.tagMetrics.deleteTags(tags);
var msg = "1 Tag Deleted.";
if (Ext.isArray(tags)){
msg = tags.length + ' Tags Deleted.';
}
Rally.ui.notify.Notifier.show({message: msg});
this._updateView();
},
_updateTagData: function(tags){
this.logger.log('_updateTagData', tags);
this.setLoading('Updating Tag Usage...');
this._fetchSpecificTagArtifacts(tags).then({
success: this._updateView,
failure: this._showErrorNotification,
scope: this
}).always(function(){
this.setLoading(false);
},this);
},
_updateView: function(bt){
this.logger.log('_updateView', bt);
this.setLoading(false);
this.tagMetrics.un('update');
// this.unsubscribe('tagDataLoaded');
this.down('#gridBox').removeAll();
var data = this.tagMetrics.getData(this.getShowDups(), this.getUsageLessThan(), this.getMonthsSinceUsed(), this.getShowArchived(), this.getShowUnused(), this.getNameContains()),
msg = "No Tags found that meet the selected filters.";
if (this.getShowDups()){
msg = "No duplicate Tags found."
}
this.store = Ext.create('Rally.data.custom.Store', data);
var grid = this.down('#gridBox').add({
xtype: 'rallygrid',
store: this.store,
columnCfgs: this._getColumnCfgs(),
showPagingToolbar: false,
enableBulkEdit: true,
enableEditing: false,
scroll: 'vertical',
viewConfig: {
emptyText: '
' + msg +'
'
},
bulkEditConfig: {
items: [{
xtype: 'tagmanagementbulkarchive'
},{
xtype: 'tagmanagementbulkdelete'
}]
},
showRowActionsColumn: false,
showPagingToolbar: false
});
var height = Math.max(this.getHeight() - this.down('#selectorBox').getHeight() - this.down('#toggleBox').getHeight(), 100);
grid.setHeight(height);
},
_getColumnCfgs: function(){
var portfolioItemTypes = this.portfolioItemTypes,
cols = [{
xtype: 'rallyrowactioncolumn',
rowActionsFn: function (record) {
return [
{
xtype: 'tagreplacemenuitem',
record: record,
portfolioItemTypes: portfolioItemTypes
},{
xtype: 'tagarchivemenuitem',
record: record
},{
xtype: 'tagdeletemenuitem',
record: record
}
];
}
},{
dataIndex: 'Name',
text: 'Name'
}];
if (this.getShowArchived()){
cols.push({
dataIndex: 'Archived',
text: 'Archived'
});
}
if (this.getShowHistory()){
cols.push({
dataIndex: 'lastUsed',
text: 'Last Used'
});
}
cols.push({
dataIndex: 'count',
text: 'Uses',
renderer: function(v){
return v || "--";
}
});
cols.push({
dataIndex: 'CreationDate',
text: 'Date Created'
});
return cols;
},
_initializeApp: function(portfolioItemTypes){
this.portfolioItemTypes = _.map(portfolioItemTypes, function(p){ return p.get('TypePath'); });
this.logger.log('_initializeApp', this.portfolioItemTypes);
this.removeAll();
var toggleBox = this.add({
xtype: 'container',
itemId: 'toggleBox',
layout: 'hbox'
});
var selectorBox = this.add({
xtype: 'container',
itemId: 'selectorBox',
layout: 'hbox'
});
var gridBox = this.add({
xtype: 'container',
itemId: 'gridBox'
});
this._addToggleButton('btDuplicates', 'icon-copy','Show Duplicate Tags', this._toggleButton);
this._addToggleButton('btHistory', 'icon-history','Show Last Used Date', this._fetchHistory);
this._addToggleButton('btArchived', 'icon-archive','Show Archived Tags', this._toggleButton);
this._addToggleButton('btUnused', 'icon-none','Show Unused Tags', this._toggleButton);
var name = selectorBox.add({
xtype: 'rallytextfield',
fieldLabel: 'Name contains',
labelAlign: 'right',
itemId: 'nameContains',
margin: 5,
labelWidth: 100,
height: 22,
width: 200
});
var usage = selectorBox.add({
xtype: 'rallynumberfield',
fieldLabel: 'Usage Less Than',
labelAlign: 'right',
itemId: 'usedFewerThan',
margin: 5,
labelWidth: 100,
width: 150,
minValue: 1,
maxValue: 1000
});
// usage.on('change', this._updateView, this);
var usedSince = selectorBox.add({
xtype: 'rallynumberfield',
fieldLabel: 'Months Since Last Usage',
labelAlign: 'right',
itemId: 'monthsSinceUsed',
margin: 5,
labelWidth: 150,
width: 200,
minValue: 0,
maxValue: 120
});
// usedSince.on('change', this._updateView, this);
this._toggleButton();
var bt = toggleBox.add({
xtype: 'rallybutton',
text: 'Update',
margin: 5
});
bt.on('click',this._updateRequested, this);
// if (this.getUseLookback()){
// this.setLoading('Loading Tag Usage Data from the Lookback API');
// this.logger.log('back BEGIN');
// this.back().then({
// success: function(snapshots){
// this.logger.log('snapshot count', snapshots.length);
//
// this.tagMetrics.addCurrentSnapshots(snapshots)
// },
// failure: this._showErrorNotification,
// scope: this
// }).always(function(){
// this.logger.log('back END');
// this.setLoading(false);
// },this );
//
// } else {
// this.setLoading('Loading Tag Usage Data from WSAPI');
// this.logger.log('_fetchCurrentTagDataFromWsapi BEGIN');
// this._fetchCurrentTagDataFromWsapi().then({
// success: function(results){
// var records = _.flatten(results);
// this.logger.log('record count', records.length);
// this.tagMetrics.addCurrentWsapiRecords(records);
// },
// failure: this._showErrorNotification,
// scope: this
// }).always(function(){
// this.logger.log('_fetchCurrentTagDataFromWsapi END');
// this.setLoading(false);
// },this );
// }
},
_addToggleButton: function(itemId, iconCls, toolTip, fn){
var bt = this.down('#toggleBox').add({
xtype: 'rallybutton',
iconCls: iconCls,
cls: 'secondary rly-small',
enableToggle: true,
itemId: itemId,
toolTipText: toolTip,
margin: 5
});
bt.on('toggle', fn, this);
},
_updateRequested: function(){
this.logger.log('_updateRequested');
// this.subscribe('tagDataLoaded', this._updateView, this);
if (this.tagMetrics._isLoaded(this.getShowHistory())){
this._updateView();
} else {
this.tagMetrics.on('update', this._updateView, this);
this.setLoading(true);
}
// var me = this;
// Ext.defer(function(){
// me.publish('requestDataStatus', me.getShowHistory());
// },1000, this);
},
// _buildTagMetrics: function(tagRecords){
// this.tagMetrics = Ext.create('CATS.tag-management.utils.TagMetrics',{
// tags: tagRecords,
// listeners: {
// ready: this._initializeApp,
// update: this._updateView,
// scope: this
// }
// });
// },
getShowHistory: function(){
return this.down('#btHistory').pressed;
},
getShowDups: function(){
return this.down('#btDuplicates').pressed;
},
getMonthsSinceUsed: function(){
return this.down('#monthsSinceUsed').getValue() || 0;
},
getNameContains: function(){
return this.down('#nameContains').getValue() || null;
},
getUsageLessThan: function(){
return this.down('#usedFewerThan').getValue() || 0;
},
getShowArchived: function(){
return this.down('#btArchived').pressed;
},
getShowUnused: function(){
return this.down('#btUnused').pressed;
},
getUseLookback: function(){
if ((this.getSetting('useLookback') === "true") || (this.getSetting('useLookback') === true)){
return true;
};
return false;
},
_getModels: function(){
return ['Defect', 'DefectSuite', 'UserStory','TestSet','Task'].concat(this.portfolioItemTypes);
},
_fetchSpecificTagArtifacts: function(tags){
var deferred = Ext.create('Deft.Deferred');
var filters = _.map(tags, function(t){
return {
property: 'Tags',
operator: 'contains',
value: t._ref
};
});
if (filters.length > 1){
filters = Rally.data.wsapi.Filter.or(filters);
}
var tagMetrics = this.tagMetrics;
Ext.create('Rally.data.wsapi.artifact.Store',{
models: this._getModels(),
fetch: ['Tags','Name'],
filters: filters,
context: {project: null}
}).load({
callback: function(records, operation){
if (operation.wasSuccessful()){
Ext.Array.each(tags, function(t){
tagMetrics.updateTagUsage(t, records);
});
deferred.resolve();
} else {
deferred.reject("Error updating Tag counts: " + operation.error.errors.join(','));
}
}
});
return deferred.promise;
},
_fetchHistory: function(bt, pressed){
this.logger.log('_fetchHistory', pressed);
if (pressed) {
this._toggleButton(bt, pressed, true)
this._fetchHistoricalTagData().then({
success: function(snapshots){
this.tagMetrics.addHistoricalSnapshots(snapshots);
},
failure: this._showErrorNotification,
scope: this
});
} else {
this._toggleButton(bt, pressed);
}
},
_toggleButton: function(bt, pressed, noAction){
if (bt){
if (pressed){
bt.removeCls('secondary');
bt.addCls('primary');
} else {
bt.removeCls('primary');
bt.addCls('secondary');
}
}
if (this.getShowDups()){
this.down('#monthsSinceUsed').hide();
this.down('#usedFewerThan').hide();
} else {
this.down('#usedFewerThan').show();
if (this.getShowHistory()){
this.down('#monthsSinceUsed').show();
} else {
this.down('#monthsSinceUsed').hide();
}
}
// if (noAction) { return; }
// this._updateView();
},
_fetchCurrentTagDataFromWsapi: function(){
this.logger.log('_fetchCurrentTagDataFromWsapi');
return Deft.Promise.all([
// this._fetchCurrentTagDataForModelFromWsapi("PortfolioItem/Feature"),
// this._fetchCurrentTagDataForModelFromWsapi("PortfolioItem/Initiative"),
this._fetchCurrentTagDataForModelFromWsapi("PortfolioItem"),
// this._fetchCurrentTagDataForModelFromWsapi("SchedulableArtifact"),
this._fetchCurrentTagDataForModelFromWsapi("HierarchicalRequirement"),
this._fetchCurrentTagDataForModelFromWsapi("Defect"),
this._fetchCurrentTagDataForModelFromWsapi("TestSet"),
this._fetchCurrentTagDataForModelFromWsapi("DefectSuite"),
this._fetchCurrentTagDataForModelFromWsapi("TestCase")
]).then({
success: function(results){
this.logger.log('_fetchCurrentTagDataFromWsapi Success');
var records = _.flatten(results);
this.tagMetrics.addCurrentWsapiRecords(records);
},
failure: this._showErrorNotification,
scope: this
});
},
_fetchCurrentTagDataForModelFromWsapi: function(modelName){
var deferred = Ext.create('Deft.Deferred');
Ext.create('Rally.data.wsapi.Store',{
model: modelName,
fetch: ['Tags','ObjectID','Name','LastUpdateDate'],
filters: this.getArtifactFilters(),
pageSize: 2000,
context: { project: null },
limit: "Infinity"
}).load({
callback: function(records,operation,success){
if (operation.wasSuccessful()){
deferred.resolve(records);
} else {
deferred.reject(operation.error.errors.join(","));
}
}
});
return deferred.promise;
},
_fetchPortfolioItemTypes: function(){
return this._fetchWsapiRecords({
model: 'TypeDefinition',
fetch: ['Name','TypePath','Ordinal'],
filters: [{
property: 'TypePath',
operator: 'contains',
value: 'PortfolioItem/'
}],
sorters: [{
property: 'Ordinal',
direction: 'ASC'
}]
});
},
_fetchWsapiRecords: function(config){
var deferred = Ext.create('Deft.Deferred');
var me = this;
if (!config.limit){ config.limit = "Infinity"; }
if (!config.pageSize){ config.pageSize = 2000; }
this.logger.log("Starting load:",config);
Ext.create('Rally.data.wsapi.Store', config).load({
callback : function(records, operation, successful) {
if (successful){
deferred.resolve(records);
} else {
me.logger.log("Failed: ", operation);
deferred.reject('Problem fetching: ' + operation.error.errors.join('. '));
}
}
});
return deferred.promise;
},
getTagFilters: function(){
var nameValue = this.down('#txtName') && this.down('#txtName').getValue();
if (nameValue){
return [{
property: 'Name',
operator: 'contains',
value: nameValue
}];
}
return [];
},
getArtifactFilters: function(){
var nameValue = this.down('#txtName') && this.down('#txtName').getValue();
if (nameValue){
return [{
property: 'Tags.Name',
operator: 'contains',
value: nameValue
}];
}
return [{
property: 'Tags.ObjectID',
operator: '>',
value: 0
}];
},
_fetchTags: function(){
var deferred = Ext.create('Deft.Deferred');
Ext.create('Rally.data.wsapi.Store',{
model: 'Tag',
fetch: ['ObjectID','Name','Archived','CreationDate'],
pageSize: 2000,
//filters: this.getTagFilters(),
limit: "Infinity"
}).load({
callback: function(records,operation,success){
if (operation.wasSuccessful()){
this.logger.log('_fetchTags success', records.length);
this.tagMetrics.addTagRecords(records);
deferred.resolve(records);
} else {
var msg = operation && operation.error && operation.error.errors && operation.error.errors.join(',');
this._showErrorNotification(msg);
deferred.reject(msg);
}
},
scope: this
});
return deferred.promise;
},
_fetchHistoricalTagData: function(){
var deferred = Ext.create('Deft.Deferred');
Ext.create('Rally.data.lookback.SnapshotStore',{
fetch: ['Tags','_ValidFrom','_ValidTo',"_PreviousValues.Tags"],
find: {
"Tags": this.getLookbackTagFilterValue(),
"_PreviousValues.Tags": {$exists: true}
},
removeUnauthorizedSnapshots: true,
limit: "Infinity",
useHttpPost: true
}).load({
callback: function(records,operation,success){
if (operation.wasSuccessful()){
this.logger.log('_fetchHistoricalTagData success', records.length);
this.tagMetrics.addHistoricalSnapshots(records);
deferred.resolve(records);
} else {
var msg = operation && operation.error && operation.error.errors && operation.error.errors.join(',');
this._showErrorNotification(msg);
deferred.reject(msg);
}
},
scope: this
});
return deferred.promise;
},
getLookbackTagFilterValue: function(){
var nameValue = this.down('#txtName') && this.down('#txtName').getValue();
if (nameValue){
//todo return array of tag oids that match.
}
return {$exists: true};
},
_fetchCurrentTagUsageData: function(){
if (this.getUseLookback()){
this._fetchCurrentTagDataFromLookback();
} else {
this._fetchCurrentTagDataFromWsapi();
}
},
_fetchCurrentTagDataFromLookback: function(){
var deferred = Ext.create('Deft.Deferred');
this.logger.log('_fetchCurrentTagDataFromLookback');
Ext.create('Rally.data.lookback.SnapshotStore',{
fetch: ['Tags','_ValidFrom','_ValidTo','_TypeHierarchy'],
find: {
"Tags": this.getLookbackTagFilterValue(),
"__At": "current"
},
removeUnauthorizedSnapshots: true,
limit: Infinity,
useHttpPost: true
}).load({
callback: function(records,operation,success){
if (operation.wasSuccessful()){
this.logger.log('_fetchCurrentTagDataFromLookback', records.length, operation);
this.tagMetrics.addCurrentSnapshots(records);
deferred.resolve(records);
} else {
var msg = operation && operation.error && operation.error.errors && operation.error.errors.join(',') || "Error loading current tag data from Lookback.";
this._showErrorNotification(msg);
deferred.reject(msg);
}
},
scope: this
});
return deferred.promise;
},
getOptions: function() {
return [
{
text: 'About...',
handler: this._launchInfo,
scope: this
}
];
},
getSettingsFields: function(){
return [{
name: 'useLookback',
xtype: 'rallycheckboxfield',
fieldLabel: 'Enable Lookback Usage',
labelAlign: 'right'
}];
},
_launchInfo: function() {
if ( this.about_dialog ) { this.about_dialog.destroy(); }
this.about_dialog = Ext.create('Rally.technicalservices.InfoLink',{});
},
isExternal: function(){
return typeof(this.getAppId()) == 'undefined';
}
});