diff --git a/web/pgadmin/misc/statistics/__init__.py b/web/pgadmin/misc/statistics/__init__.py new file mode 100644 index 000000000..c26441713 --- /dev/null +++ b/web/pgadmin/misc/statistics/__init__.py @@ -0,0 +1,37 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""A blueprint module providing utility functions for the application.""" + +from flask import url_for +from pgadmin.utils import PgAdminModule + +MODULE_NAME = 'statistics' + + +class StatisticsModule(PgAdminModule): + """ + StatisticsModule + + This module will render the statistics of the browser nodes on selection + when statistics panel is active. + """ + + def get_own_javascripts(self): + return [{ + 'name': 'pgadmin.browser.object_statistics', + 'path': url_for('statistics.static', filename='js/statistics'), + 'when': None + }] + + +# Initialise the module +blueprint = StatisticsModule( + MODULE_NAME, __name__, url_prefix='/misc/statistics' + ) diff --git a/web/pgadmin/misc/statistics/static/js/statistics.js b/web/pgadmin/misc/statistics/static/js/statistics.js new file mode 100644 index 000000000..235dbe48a --- /dev/null +++ b/web/pgadmin/misc/statistics/static/js/statistics.js @@ -0,0 +1,301 @@ +define( + ['underscore', 'jquery', 'pgadmin.browser', 'backgrid', 'wcdocker', 'pgadmin.backgrid'], +function(_, $, pgBrowser, Backgrid) { + + if (pgBrowser.NodeStatistics) + return pgBrowser.NodeStatistics; + + pgBrowser.NodeStatistics = pgBrowser.NodeStatistics || {}; + + if (pgBrowser.NodeStatistics.initialized) { + return pgBrowser.NodeStatistics; + } + + 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 backgrid-striped table-bordered table-hover", + wcDocker = window.wcDocker; + + _.extend( + PGBooleanCell.prototype.defaults.options, { + onText: pgBrowser.messages.TRUE, + offText: pgBrowser.messages.FALSE, + onColor: 'success', + offColor: 'primary', + size: 'mini' + } + ); + + _.extend(pgBrowser.NodeStatistics, { + init: function() { + if (this.initialized) { + return; + } + this.initialized = true; + _.bindAll( + this, + 'showStatistics', 'panelVisibilityChanged', + '__createMultiLineStatistics', '__createSingleLineStatistics'); + + _.extend( + this, { + initialized: true, + collection: new (Backbone.Collection)(null), + statistic_columns: [{ + editable: false, + name: 'statistics', + label: pgBrowser.messages.STATISTICS_LABEL, + cell: 'string', + headerCell: Backgrid.Extension.CustomHeaderCell, + cellHeaderClasses: 'width_percent_25' + },{ + editable: false, + name: 'value', + label: pgBrowser.messages.STATISTICS_VALUE_LABEL, + cell: 'string' + }], + panel: pgBrowser.docker.findPanels('statistics'), + columns: null, + grid: null + }); + + var self = this; + + // We will listen to the visibility change of the statistics panel + pgBrowser.Events.on( + 'pgadmin-browser:panel-statistics:' + + wcDocker.EVENT.VISIBILITY_CHANGED, + this.panelVisibilityChanged); + + // Hmm.. Did we find the statistics panel, and is it visible (openned)? + // If that is the case - we need to listen the browser tree selection + // events. + if (this.panel.length == 0) { + pgBrowser.Events.on( + 'pgadmin-browser:panel-statistics:' + wcDocker.EVENT.INIT, + function() { + self.panel = pgBrowser.docker.findPanels('statistics'); + if (self.panel[0].isVisible() || + self.panel.length != 1) { + pgBrowser.Events.on( + 'pgadmin-browser:tree:selected', this.showStatistics + ); + } + }.bind(this) + ); + } else { + if (self.panel[0].isVisible() || + self.panel.length != 1) { + pgBrowser.Events.on( + 'pgadmin-browser:tree:selected', this.showStatistics + ); + } + } + if (self.panel.length > 0 && self.panel[0].isVisible()) { + pgBrowser.Events.on( + 'pgadmin-browser:tree:selected', this.showStatistics + ); + } + }, + + // Fetch the actual data and update the collection + __updateCollection: function(url, node) { + 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'), + collection = this.collection, + panel = this.panel, + self = this, + msg = ''; + + if (node) { + msg = pgBrowser.messages.NODE_HAS_NO_STATISTICS; + /* We fetch the statistics only for those node who set the parameter + * showStatistics function. + */ + if (node.hasStatistics) { + /* Set the message because ajax request may take time to + * fetch the information from the server. + */ + msg = ''; + $msgContainer.text(msg); + + // Set the url, fetch the data and update the collection + $.ajax({ + url: url, + type:'GET', + success: function(res) { + if (res.data) { + var data = res.data; + if (node.hasCollectiveStatistics || data['rows'].length > 1) { + self.__createMultiLineStatistics.call(self, data); + } else { + self.__createSingleLineStatistics.call(self, data); + } + + if (self.grid) { + delete self.grid; + self.grid = null; + } + + self.grid = new Backgrid.Grid({ + columns: self.columns, + collection: self.collection, + className: GRID_CLASSES + }); + self.grid.render(); + $gridContainer.empty(); + $gridContainer.append(self.grid.$el); + + if (!$msgContainer.hasClass('hidden')) { + $msgContainer.addClass('hidden') + } + $gridContainer.removeClass('hidden'); + + } else if (res.info) { + if (!$gridContainer.hasClass('hidden')) { + $gridContainer.addClass('hidden') + } + $msgContainer.text(res.info); + $msgContainer.removeClass('hidden'); + } + }, + error: function() { + // TODO:: Report this error. + } + }); + } + } + if (msg != '') { + // Hide the grid container and show the default message container + if (!$gridContainer.hasClass('hidden')) + $gridContainer.addClass('hidden'); + $msgContainer.removeClass('hidden'); + + $msgContainer.text(msg); + } + }, + + showStatistics: function(item, data, node) { + var self = this; + /** + * 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 + ); + }, 400); + }, + + __createMultiLineStatistics: function(data) { + var rows = data['rows'], + columns = data['columns']; + + this.columns = []; + for (var idx in columns) { + var c = columns[idx]; + this.columns.push({ + editable: false, + name: c['name'], + cell: typeCellMapper[c['type_code']] || 'string' + }); + } + + this.collection.reset(rows); + }, + + __createSingleLineStatistics: function(data) { + var row = data['rows'][0], + columns = data['columns'] + res = []; + + this.columns = this.statistic_columns; + for (var idx in columns) { + name = (columns[idx])['name']; + res.push({ + 'statistics': name, + 'value': row[name] + }); + } + + this.collection.reset(res); + }, + + panelVisibilityChanged: function(panel) { + if (panel.isVisible()) { + 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 + ); + } else { + // We don't need to listen the tree item selection event. + pgBrowser.Events.off( + 'pgadmin-browser:tree:selected', + pgBrowser.NodeStatistics.showStatistics + ); + } + } + }); + + return pgBrowser.NodeStatistics; +}); diff --git a/web/pgadmin/static/css/overrides.css b/web/pgadmin/static/css/overrides.css index 3d289f410..e69a5b162 100755 --- a/web/pgadmin/static/css/overrides.css +++ b/web/pgadmin/static/css/overrides.css @@ -483,7 +483,15 @@ fieldset[disabled] .form-control { width:25px; } -.backgrid td.renderable:not(.editable):not(.delete-cell) { +.backgrid.presentation { + background-color: #EEEEEE; +} + +.backgrid.presentation td.renderable { + padding: 3px; +} + +.backgrid:not(.presentation) td.renderable:not(.editable):not(.delete-cell) { background-color: #F1F1F1; } diff --git a/web/pgadmin/static/js/backgrid/backgrid.js b/web/pgadmin/static/js/backgrid/backgrid.js index 6f8da79e5..b03587492 100644 --- a/web/pgadmin/static/js/backgrid/backgrid.js +++ b/web/pgadmin/static/js/backgrid/backgrid.js @@ -2880,4 +2880,4 @@ var Grid = Backgrid.Grid = Backbone.View.extend({ }); return Backgrid; -})); \ No newline at end of file +}));