mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-01-26 08:16:44 -06:00
429 lines
13 KiB
JavaScript
429 lines
13 KiB
JavaScript
/////////////////////////////////////////////////////////////
|
|
//
|
|
// pgAdmin 4 - PostgreSQL Tools
|
|
//
|
|
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
|
// This software is released under the PostgreSQL Licence
|
|
//
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
define('misc.statistics', [
|
|
'sources/gettext', 'underscore', 'jquery', 'backbone',
|
|
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backgrid', 'alertify', 'sources/size_prettify',
|
|
'sources/misc/statistics/statistics',
|
|
], function(
|
|
gettext, _, $, Backbone, pgAdmin, pgBrowser, Backgrid, Alertify, sizePrettify,
|
|
statisticsHelper
|
|
) {
|
|
|
|
if (pgBrowser.NodeStatistics)
|
|
return pgBrowser.NodeStatistics;
|
|
|
|
pgBrowser.NodeStatistics = pgBrowser.NodeStatistics || {};
|
|
|
|
if (pgBrowser.NodeStatistics.initialized) {
|
|
return pgBrowser.NodeStatistics;
|
|
}
|
|
|
|
var SizeFormatter = Backgrid.SizeFormatter = function() {};
|
|
_.extend(SizeFormatter.prototype, {
|
|
/**
|
|
Takes a raw value from a model and returns the human readable formatted
|
|
string for display.
|
|
|
|
@member Backgrid.SizeFormatter
|
|
@param {*} rawData
|
|
@param {Backbone.Model} model Used for more complicated formatting
|
|
@return {*}
|
|
*/
|
|
fromRaw: function(rawData) {
|
|
return sizePrettify(rawData);
|
|
},
|
|
toRaw: function(formattedData) {
|
|
return formattedData;
|
|
},
|
|
});
|
|
|
|
var PGBooleanCell = Backgrid.Extension.SwitchCell.extend({
|
|
defaults: _.extend({}, Backgrid.Extension.SwitchCell.prototype.defaults),
|
|
}),
|
|
typeCellMapper = {
|
|
// boolean
|
|
16: PGBooleanCell,
|
|
// int8
|
|
20: Backgrid.IntegerCell,
|
|
// int2
|
|
21: Backgrid.IntegerCell,
|
|
// int4
|
|
23: Backgrid.IntegerCell,
|
|
// float4
|
|
700: Backgrid.NumberCell,
|
|
// float8
|
|
701: Backgrid.NumberCell,
|
|
// numeric
|
|
1700: Backgrid.NumberCell,
|
|
// abstime
|
|
702: Backgrid.DatetimeCell,
|
|
// reltime
|
|
703: Backgrid.DatetimeCell,
|
|
// date
|
|
1082: Backgrid.DatetimeCell.extend({
|
|
includeDate: true,
|
|
includeTime: false,
|
|
includeMilli: false,
|
|
}),
|
|
// time
|
|
1083: Backgrid.DatetimeCell.extend({
|
|
includeDate: false,
|
|
includeTime: true,
|
|
includeMilli: true,
|
|
}),
|
|
// timestamp
|
|
1114: Backgrid.DatetimeCell.extend({
|
|
includeDate: true,
|
|
includeTime: true,
|
|
includeMilli: true,
|
|
}),
|
|
// timestamptz
|
|
1184: 'string'
|
|
/* Backgrid.DatetimeCell.extend({
|
|
includeDate: true, includeTime: true, includeMilli: true
|
|
}) */
|
|
,
|
|
1266: 'string',
|
|
/* Backgrid.DatetimeCell.extend({
|
|
includeDate: false, includeTime: true, includeMilli: true
|
|
}) */
|
|
},
|
|
GRID_CLASSES = 'backgrid presentation table table-bordered table-noouter-border table-hover',
|
|
wcDocker = window.wcDocker;
|
|
|
|
_.extend(
|
|
PGBooleanCell.prototype.defaults.options, {
|
|
onText: gettext('True'),
|
|
offText: gettext('False'),
|
|
onColor: 'success',
|
|
offColor: 'ternary',
|
|
size: 'mini',
|
|
}
|
|
);
|
|
|
|
_.extend(pgBrowser.NodeStatistics, {
|
|
init: function() {
|
|
if (this.initialized) {
|
|
return;
|
|
}
|
|
this.initialized = true;
|
|
_.bindAll(
|
|
this,
|
|
'showStatistics', 'toggleVisibility',
|
|
'__createMultiLineStatistics', '__createSingleLineStatistics', '__loadMoreRows');
|
|
|
|
_.extend(
|
|
this, {
|
|
collection: new(Backbone.Collection)(null),
|
|
statistic_columns: [{
|
|
editable: false,
|
|
name: 'statistics',
|
|
label: gettext('Statistics'),
|
|
cell: 'string',
|
|
headerCell: Backgrid.Extension.CustomHeaderCell,
|
|
cellHeaderClasses: 'width_percent_25',
|
|
}, {
|
|
editable: false,
|
|
name: 'value',
|
|
label: gettext('Value'),
|
|
cell: 'string',
|
|
}],
|
|
columns: null,
|
|
grid: null,
|
|
});
|
|
|
|
this.panel = pgBrowser.docker.findPanels('statistics');
|
|
if(this.panel.length > 0) this.toggleVisibility(this.panel[0].isVisible());
|
|
},
|
|
|
|
toggleVisibility: function(visible, closed=false) {
|
|
if (visible) {
|
|
this.panel = pgBrowser.docker.findPanels('statistics');
|
|
var t = pgBrowser.tree,
|
|
i = t.selected(),
|
|
d = i && t.itemData(i),
|
|
n = i && d && pgBrowser.Nodes[d._type];
|
|
|
|
pgBrowser.NodeStatistics.showStatistics.apply(
|
|
pgBrowser.NodeStatistics, [i, d, n]
|
|
);
|
|
|
|
// We will start listening the tree selection event.
|
|
pgBrowser.Events.on(
|
|
'pgadmin-browser:tree:selected',
|
|
pgBrowser.NodeStatistics.showStatistics
|
|
);
|
|
pgBrowser.Events.on(
|
|
'pgadmin-browser:tree:refreshing',
|
|
pgBrowser.NodeStatistics.refreshStatistics,
|
|
this
|
|
);
|
|
} else {
|
|
if(closed) {
|
|
$(this.panel[0]).data('node-prop', '');
|
|
}
|
|
// We don't need to listen the tree item selection event.
|
|
pgBrowser.Events.off(
|
|
'pgadmin-browser:tree:selected',
|
|
pgBrowser.NodeStatistics.showStatistics
|
|
);
|
|
pgBrowser.Events.off(
|
|
'pgadmin-browser:tree:refreshing',
|
|
pgBrowser.NodeStatistics.refreshStatistics,
|
|
this
|
|
);
|
|
}
|
|
},
|
|
|
|
// Fetch the actual data and update the collection
|
|
__updateCollection: function(url, node, item, node_type) {
|
|
var $container = this.panel[0].layout().scene().find('.pg-panel-content'),
|
|
$msgContainer = $container.find('.pg-panel-statistics-message'),
|
|
$gridContainer = $container.find('.pg-panel-statistics-container'),
|
|
panel = this.panel,
|
|
self = this,
|
|
msg = '',
|
|
n_type = node_type;
|
|
|
|
if (node) {
|
|
msg = gettext('No statistics are available for the selected object.');
|
|
/* We fetch the statistics only for those node who set the parameter
|
|
* showStatistics function.
|
|
*/
|
|
|
|
// Avoid unnecessary reloads
|
|
var treeHierarchy = node.getTreeNodeHierarchy(item);
|
|
var cache_flag = {
|
|
node_type: node_type,
|
|
url: url,
|
|
};
|
|
if (_.isEqual($(panel[0]).data('node-prop'), cache_flag)) {
|
|
return;
|
|
}
|
|
// Cache the current IDs for next time
|
|
$(panel[0]).data('node-prop', cache_flag);
|
|
|
|
if (statisticsHelper.nodeHasStatistics(node, item)) {
|
|
msg = '';
|
|
var timer;
|
|
// Set the url, fetch the data and update the collection
|
|
var ajaxHook = function() {
|
|
$.ajax({
|
|
url: url,
|
|
type: 'GET',
|
|
beforeSend: function(xhr) {
|
|
xhr.setRequestHeader(
|
|
pgAdmin.csrf_token_header, pgAdmin.csrf_token
|
|
);
|
|
// Generate a timer for the request
|
|
timer = setTimeout(function() {
|
|
// notify user if request is taking longer than 1 second
|
|
|
|
$msgContainer.text(gettext('Retrieving data from the server...'));
|
|
$msgContainer.removeClass('d-none');
|
|
if (self.grid) {
|
|
self.grid.remove();
|
|
}
|
|
}, 1000);
|
|
},
|
|
})
|
|
.done(function(res) {
|
|
// clear timer and reset message.
|
|
clearTimeout(timer);
|
|
$msgContainer.text('');
|
|
if (res.data) {
|
|
var data = self.data = res.data;
|
|
if (node.hasCollectiveStatistics || data['rows'].length > 1) {
|
|
// Listen scroll event to load more rows
|
|
pgBrowser.Events.on(
|
|
'pgadmin-browser:panel-statistics:' +
|
|
wcDocker.EVENT.SCROLLED,
|
|
self.__loadMoreRows
|
|
);
|
|
self.__createMultiLineStatistics.call(self, data, node.statsPrettifyFields);
|
|
} else {
|
|
// Do not listen the scroll event
|
|
pgBrowser.Events.off(
|
|
'pgadmin-browser:panel-statistics:' +
|
|
wcDocker.EVENT.SCROLLED,
|
|
self.__loadMoreRows
|
|
);
|
|
self.__createSingleLineStatistics.call(self, data, node.statsPrettifyFields);
|
|
}
|
|
|
|
if (self.grid) {
|
|
delete self.grid;
|
|
self.grid = null;
|
|
}
|
|
|
|
self.grid = new Backgrid.Grid({
|
|
emptyText: gettext('No data found'),
|
|
columns: self.columns,
|
|
collection: self.collection,
|
|
className: GRID_CLASSES,
|
|
});
|
|
self.grid.render();
|
|
$gridContainer.empty();
|
|
$gridContainer.append(self.grid.$el);
|
|
|
|
if (!$msgContainer.hasClass('d-none')) {
|
|
$msgContainer.addClass('d-none');
|
|
}
|
|
$gridContainer.removeClass('d-none');
|
|
|
|
} else if (res.info) {
|
|
if (!$gridContainer.hasClass('d-none')) {
|
|
$gridContainer.addClass('d-none');
|
|
}
|
|
$msgContainer.text(res.info);
|
|
$msgContainer.removeClass('d-none');
|
|
}
|
|
})
|
|
.fail(function(xhr, error, message) {
|
|
var _label = treeHierarchy[n_type].label;
|
|
pgBrowser.Events.trigger(
|
|
'pgadmin:node:retrieval:error', 'statistics', xhr, error, message, item
|
|
);
|
|
if (!Alertify.pgHandleItemError(xhr, error, message, {
|
|
item: item,
|
|
info: treeHierarchy,
|
|
})) {
|
|
Alertify.pgNotifier(
|
|
error, xhr,
|
|
gettext('Error retrieving the information - %s', message || _label),
|
|
function(alertMsg) {
|
|
if(alertMsg === 'CRYPTKEY_SET') {
|
|
ajaxHook();
|
|
} else {
|
|
console.warn(arguments);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
// show failed message.
|
|
$msgContainer.text(gettext('Failed to retrieve data from the server.'));
|
|
});
|
|
};
|
|
|
|
ajaxHook();
|
|
}
|
|
}
|
|
if (msg != '') {
|
|
// Hide the grid container and show the default message container
|
|
if (!$gridContainer.hasClass('d-none'))
|
|
$gridContainer.addClass('d-none');
|
|
$msgContainer.removeClass('d-none');
|
|
|
|
$msgContainer.text(msg);
|
|
}
|
|
},
|
|
refreshStatistics: function(item, data, node) {
|
|
var that = this,
|
|
cache_flag = {
|
|
node_type: data._type,
|
|
url: node.generate_url(item, 'stats', data, true),
|
|
};
|
|
|
|
if (_.isEqual($(that.panel[0]).data('node-prop'), cache_flag)) {
|
|
// Reset the current item selection
|
|
$(that.panel[0]).data('node-prop', '');
|
|
that.showStatistics(item, data, node);
|
|
}
|
|
},
|
|
showStatistics: function(item, data, node) {
|
|
var self = this;
|
|
if (!node) {
|
|
return;
|
|
}
|
|
/**
|
|
* We can't start fetching the statistics immediately, it is possible -
|
|
* the user is just using keyboards to select the node, and just
|
|
* traversing through.
|
|
*
|
|
* We will wait for some time before fetching the statistics for the
|
|
* selected node.
|
|
**/
|
|
if (self.timeout) {
|
|
clearTimeout(self.timeout);
|
|
}
|
|
self.timeout = setTimeout(
|
|
function() {
|
|
self.__updateCollection.call(
|
|
self, node.generate_url(item, 'stats', data, true), node, item, data._type
|
|
);
|
|
}, 400);
|
|
},
|
|
|
|
__createMultiLineStatistics: function(data, prettifyFields) {
|
|
var rows = data['rows'],
|
|
columns = data['columns'];
|
|
|
|
this.columns = [];
|
|
for (var idx in columns) {
|
|
var rawColumn = columns[idx],
|
|
cell_type = typeCellMapper[rawColumn['type_code']] || 'string';
|
|
|
|
// Don't show PID comma separated
|
|
if (rawColumn['name'] == 'PID') {
|
|
cell_type = cell_type.extend({
|
|
orderSeparator: '',
|
|
});
|
|
}
|
|
|
|
var col = {
|
|
editable: false,
|
|
name: rawColumn['name'],
|
|
cell: cell_type,
|
|
};
|
|
if (_.indexOf(prettifyFields, rawColumn['name']) != -1) {
|
|
col['formatter'] = SizeFormatter;
|
|
}
|
|
this.columns.push(col);
|
|
|
|
}
|
|
|
|
this.collection.reset(rows.splice(0, 50));
|
|
},
|
|
|
|
__loadMoreRows: function() {
|
|
let elem = $('.pg-panel-statistics-container').closest('.wcFrameCenter')[0];
|
|
if ((elem.scrollHeight - 10) < elem.scrollTop + elem.offsetHeight) {
|
|
var rows = this.data['rows'];
|
|
if (rows.length > 0) {
|
|
this.collection.add(rows.splice(0, 50));
|
|
}
|
|
}
|
|
},
|
|
|
|
__createSingleLineStatistics: function(data, prettifyFields) {
|
|
var row = data['rows'][0],
|
|
columns = data['columns'],
|
|
res = [],
|
|
name;
|
|
|
|
this.columns = this.statistic_columns;
|
|
for (var idx in columns) {
|
|
name = (columns[idx])['name'];
|
|
res.push({
|
|
'statistics': name,
|
|
// Check if row is undefined?
|
|
'value': row && row[name] ?
|
|
((_.indexOf(prettifyFields, name) != -1) ?
|
|
sizePrettify(row[name]) : row[name]) : null,
|
|
});
|
|
}
|
|
|
|
this.collection.reset(res);
|
|
},
|
|
});
|
|
|
|
return pgBrowser.NodeStatistics;
|
|
});
|