From 094129e2be62cc039d51d9a59a41921e88f9504c Mon Sep 17 00:00:00 2001 From: Pradip Parkale Date: Mon, 24 Jan 2022 14:13:02 +0530 Subject: [PATCH] Port Dependent, dependencies, statistics panel to React. Fixes #7016 --- docs/en_US/release_notes_6_5.rst | 1 + .../tables/rules/templates/rules/css/rule.css | 2 - web/pgadmin/browser/static/js/node_ajax.js | 19 + web/pgadmin/browser/static/js/panel.js | 62 ++- web/pgadmin/browser/static/js/panel_view.jsx | 77 +++ .../dependencies/static/js/Dependencies.jsx | 175 +++++++ .../dependencies/static/js/dependencies.js | 437 ------------------ .../misc/dependents/static/js/Dependents.jsx | 176 +++++++ .../misc/dependents/static/js/dependents.js | 321 ------------- .../misc/statistics/static/js/Statistics.jsx | 235 ++++++++++ .../misc/statistics/static/js/statistics.js | 432 ----------------- web/pgadmin/static/js/components/PgTable.jsx | 68 ++- web/webpack.config.js | 3 - web/webpack.shim.js | 3 - 14 files changed, 779 insertions(+), 1232 deletions(-) create mode 100644 web/pgadmin/browser/static/js/panel_view.jsx create mode 100644 web/pgadmin/misc/dependencies/static/js/Dependencies.jsx delete mode 100644 web/pgadmin/misc/dependencies/static/js/dependencies.js create mode 100644 web/pgadmin/misc/dependents/static/js/Dependents.jsx delete mode 100644 web/pgadmin/misc/dependents/static/js/dependents.js create mode 100644 web/pgadmin/misc/statistics/static/js/Statistics.jsx delete mode 100644 web/pgadmin/misc/statistics/static/js/statistics.js diff --git a/docs/en_US/release_notes_6_5.rst b/docs/en_US/release_notes_6_5.rst index 0dcfa9733..00897028a 100644 --- a/docs/en_US/release_notes_6_5.rst +++ b/docs/en_US/release_notes_6_5.rst @@ -13,6 +13,7 @@ New features Housekeeping ************ +| `Issue #7016 `_ - Port Dependent, dependencies, statistics panel to React. | `Issue #7017 `_ - Port Import/Export dialog to React. Bug fixes diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/templates/rules/css/rule.css b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/templates/rules/css/rule.css index 63f4ecbce..f6de648ba 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/templates/rules/css/rule.css +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/templates/rules/css/rule.css @@ -1,6 +1,5 @@ .icon-rule{ background-image: url('{{ url_for('NODE-rule.static', filename='img/rule.svg') }}') !important; - border-radius: 10px; background-size: 20px !important; background-repeat: no-repeat; align-content: center; @@ -11,7 +10,6 @@ .icon-rule-bad{ background-image: url('{{ url_for('NODE-rule.static', filename='img/rule-bad.svg') }}') !important; - border-radius: 10px; background-size: 20px !important; background-repeat: no-repeat; align-content: center; diff --git a/web/pgadmin/browser/static/js/node_ajax.js b/web/pgadmin/browser/static/js/node_ajax.js index 2ed51663c..14a9afa3c 100644 --- a/web/pgadmin/browser/static/js/node_ajax.js +++ b/web/pgadmin/browser/static/js/node_ajax.js @@ -12,6 +12,25 @@ import getApiInstance from '../../../static/js/api_instance'; import {generate_url} from 'sources/browser/generate_url'; import pgAdmin from 'sources/pgadmin'; +/* It generates the URL based on collection node selected */ +export function generateCollectionURL(item, type) { + var opURL = { + 'properties': 'obj', + 'children': 'nodes', + 'drop': 'obj', + }; + let nodeObj= this; + var collectionPickFunction = function (treeInfoValue, treeInfoKey) { + return (treeInfoKey != nodeObj.type); + }; + var treeInfo = pgAdmin.Browser.tree.getTreeNodeHierarchy(item); + var actionType = type in opURL ? opURL[type] : type; + return generate_url( + pgAdmin.Browser.URL, treeInfo, actionType, nodeObj.node, + collectionPickFunction + ); +} + /* It generates the URL based on tree node selected */ export function generateNodeUrl(treeNodeInfo, actionType, itemNodeData, withId, jumpAfterNode) { let opURL = { diff --git a/web/pgadmin/browser/static/js/panel.js b/web/pgadmin/browser/static/js/panel.js index 7f408d63b..ff1444f3d 100644 --- a/web/pgadmin/browser/static/js/panel.js +++ b/web/pgadmin/browser/static/js/panel.js @@ -7,6 +7,8 @@ // ////////////////////////////////////////////////////////////// +import { getPanelView, removePanelView } from './panel_view'; + define( ['underscore', 'sources/pgadmin', 'jquery', 'wcdocker'], function(_, pgAdmin, $) { @@ -127,6 +129,19 @@ define( myPanel.on(ev, that.handleVisibility.bind(myPanel, ev)); }); } + + pgBrowser.Events.on('pgadmin-browser:tree:selected', () => { + + if(myPanel.isVisible()) { + removePanelView($container[0]); + getPanelView( + pgBrowser.tree, + $container[0], + pgBrowser, + myPanel._type + ); + } + }); }, }); } @@ -193,26 +208,43 @@ define( handleVisibility: function(eventName) { // Supported modules let type_module = { - 'dashboard': pgAdmin.Dashboard, - 'statistics': pgBrowser.NodeStatistics, - 'dependencies': pgBrowser.NodeDependencies, - 'dependents': pgBrowser.NodeDependents, + dashboard: pgAdmin.Dashboard, }; - let module = type_module[this._type]; - if(_.isUndefined(module)) - return; + if (_.isNull(pgBrowser.tree)) return; - if(_.isUndefined(module.toggleVisibility)) - return; + let selectedPanel = pgBrowser.docker.findPanels(this._type)[0]; + let isPanelVisible = selectedPanel.isVisible(); + var $container = selectedPanel + .layout() + .scene() + .find('.pg-panel-content'); - if (eventName == 'panelClosed') { - /* Pass the closed flag also */ - module.toggleVisibility.call(module, false, true); - } else if (eventName == 'panelVisibilityChanged') { - module.toggleVisibility.call(module, pgBrowser.docker.findPanels(this._type)[0].isVisible(), false); + if (this._type === 'dashboard') { + if (eventName == 'panelClosed') { + module.toggleVisibility.call(module, false, true); + } else if (eventName == 'panelVisibilityChanged') { + module.toggleVisibility.call( + module, + pgBrowser.docker.findPanels(this._type)[0].isVisible(), + false + ); + } } - }, + + if (isPanelVisible) { + if (eventName == 'panelClosed') { + removePanelView($container[0]); + } else if (eventName == 'panelVisibilityChanged') { + getPanelView( + pgBrowser.tree, + $container[0], + pgBrowser, + this._type + ); + } + } + } }); diff --git a/web/pgadmin/browser/static/js/panel_view.jsx b/web/pgadmin/browser/static/js/panel_view.jsx new file mode 100644 index 000000000..cf0a5a07e --- /dev/null +++ b/web/pgadmin/browser/static/js/panel_view.jsx @@ -0,0 +1,77 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import React from 'react'; +import ReactDOM from 'react-dom'; +import Theme from 'sources/Theme'; +import Dependencies from '../../../misc/dependencies/static/js/Dependencies'; +import Dependents from '../../../misc/dependents/static/js/Dependents'; +import Statistics from '../../../misc/statistics/static/js/Statistics'; + +/* The entry point for rendering React based view in properties, called in node.js */ +export function getPanelView( + tree, + container, + pgBrowser, + panelType +) { + let item = tree.selected(), + nodeData = item && tree.itemData(item), + node = item && nodeData && pgBrowser.Nodes[nodeData._type], + treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item); + + + if (panelType == 'statistics') { + ReactDOM.render( + + + , + container + ); + } + if (panelType == 'dependencies') { + ReactDOM.render( + + + , + container + ); + } + if (panelType == 'dependents') { + ReactDOM.render( + + + , + container + ); + } +} + +/* When switching from normal node to collection node, clean up the React mounted DOM */ +export function removePanelView(container) { + ReactDOM.unmountComponentAtNode(container); +} diff --git a/web/pgadmin/misc/dependencies/static/js/Dependencies.jsx b/web/pgadmin/misc/dependencies/static/js/Dependencies.jsx new file mode 100644 index 000000000..94266ad32 --- /dev/null +++ b/web/pgadmin/misc/dependencies/static/js/Dependencies.jsx @@ -0,0 +1,175 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import _ from 'lodash'; +import React, { useEffect } from 'react'; +import { generateNodeUrl } from '../../../../browser/static/js/node_ajax'; +import PgTable from 'sources/components/PgTable'; +import gettext from 'sources/gettext'; +import PropTypes from 'prop-types'; +import Notify from '../../../../static/js/helpers/Notifier'; +import getApiInstance from 'sources/api_instance'; +import { makeStyles } from '@material-ui/core/styles'; + +const useStyles = makeStyles((theme) => ({ + emptyPanel: { + minHeight: '100%', + minWidth: '100%', + background: theme.palette.grey[400], + overflow: 'auto', + padding: '7.5px', + }, + panelIcon: { + width: '80%', + margin: '0 auto', + marginTop: '25px !important', + position: 'relative', + textAlign: 'center', + }, + panelMessage: { + marginLeft: '0.5rem', + fontSize: '0.875rem', + }, + autoResizer: { + height: '100% !important', + width: '100% !important', + background: theme.palette.grey[400], + padding: '7.5px', + overflow: 'auto !important', + minHeight: '100%', + minWidth: '100%', + }, +})); + +function parseData(data, node) { + // Update the icon + data.forEach((element) => { + if (element.icon == null || element.icon == '') { + if (node) { + element.icon = _.isFunction(node['node_image']) + ? node['node_image'].apply(node, [null, null]) + : node['node_image'] || 'icon-' + element.type; + } else { + element.icon = 'icon-' + element.type; + } + } + if (element.icon) { + element['icon'] = { + type: element.icon, + }; + } + }); + return data; +} + +export default function Dependencies({ nodeData, node, ...props }) { + const classes = useStyles(); + const [tableData, setTableData] = React.useState([]); + + const [msg, setMsg] = React.useState(''); + var columns = [ + { + Header: 'Type', + accessor: 'type', + sortble: true, + resizable: false, + disableGlobalFilter: true, + }, + { + Header: 'Name', + accessor: 'name', + sortble: true, + resizable: false, + disableGlobalFilter: true, + }, + { + Header: 'Restriction', + accessor: 'field', + sortble: true, + resizable: true, + disableGlobalFilter: false, + minWidth: 280, + }, + ]; + + useEffect(() => { + let message = gettext('Please select an object in the tree view.'); + if (node) { + let url = generateNodeUrl.call( + node, + props.treeNodeInfo, + 'dependency', + nodeData, + true, + node.url_jump_after_node + ); + message = gettext( + 'No dependency information is available for the selected object.' + ); + if (node.hasDepends) { + const api = getApiInstance(); + + api({ + url: url, + type: 'GET', + }) + .then((res) => { + if (res.data.length > 0) { + let data = parseData(res.data, node); + setTableData(data); + } else { + setMsg(message); + } + }) + .catch((e) => { + Notify.alert( + gettext('Failed to retrieve data from the server.'), + gettext(e.message) + ); + // show failed message. + setMsg(gettext('Failed to retrieve data from the server.')); + }); + } + } + if (message != '') { + setMsg(message); + } + return () => { + setTableData([]); + }; + }, [nodeData]); + + return ( + <> + {tableData.length > 0 ? ( + + ) : ( +
+
+ + {gettext(msg)} +
+
+ )} + + ); +} + +Dependencies.propTypes = { + res: PropTypes.array, + nodeData: PropTypes.object, + treeNodeInfo: PropTypes.object, + node: PropTypes.func, +}; diff --git a/web/pgadmin/misc/dependencies/static/js/dependencies.js b/web/pgadmin/misc/dependencies/static/js/dependencies.js deleted file mode 100644 index e655db28a..000000000 --- a/web/pgadmin/misc/dependencies/static/js/dependencies.js +++ /dev/null @@ -1,437 +0,0 @@ -///////////////////////////////////////////////////////////// -// -// pgAdmin 4 - PostgreSQL Tools -// -// Copyright (C) 2013 - 2022, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -////////////////////////////////////////////////////////////// - -import Notify from '../../../../static/js/helpers/Notifier'; - -define('misc.dependencies', [ - 'sources/gettext', 'underscore', 'jquery', 'backbone', - 'pgadmin', 'pgadmin.browser', 'pgadmin.alertifyjs', 'pgadmin.backgrid', - 'sources/utils', -], function(gettext, _, $, Backbone, pgAdmin, pgBrowser, Alertify, Backgrid, pgadminUtils) { - - if (pgBrowser.NodeDependencies) - return pgBrowser.NodeDependencies; - - var wcDocker = window.wcDocker; - - pgBrowser.NodeDependencies = pgBrowser.NodeDependencies || {}; - - _.extend(pgBrowser.NodeDependencies, { - init: function() { - if (this.initialized) { - return; - } - - this.initialized = true; - this.dependenciesPanel = pgBrowser.docker.findPanels('dependencies')[0]; - /* Parameter is used to set the proper label of the - * backgrid header cell. - */ - _.bindAll(this, 'showDependencies', '__updateCollection', '__loadMoreRows', - '__appendGridToPanel', 'toggleVisibility'); - - // Defining Backbone Model for Dependencies. - var Model = Backbone.Model.extend({ - defaults: { - icon: 'icon-unknown', - type: undefined, - name: undefined, - /* field contains 'Database Name' for 'Tablespace and Role node', - * for other node it contains 'Restriction'. - */ - field: undefined, - }, - // This function is used to fetch/set the icon for the type(Function, Role, Database, ....) - parse: function(res) { - var node = pgBrowser.Nodes[res.type]; - if(res.icon == null || res.icon == '') { - if (node) { - res.icon = _.isFunction(node['node_image']) ? - (node['node_image']).apply(node, [null, null]) : - (node['node_image'] || ('icon-' + res.type)); - } else { - res.icon = ('icon-' + res.type); - } - } - res.type = pgadminUtils.titleize(res.type.replace(/_/g, ' '), true); - return res; - }, - }); - - // Defining Backbone Collection for Dependencies. - this.dependenciesCollection = new(Backbone.Collection.extend({ - model: Model, - }))(null); - - if(this.dependenciesPanel) this.toggleVisibility(this.dependenciesPanel.isVisible()); - }, - - toggleVisibility: function(visible, closed=false) { - if (visible) { - this.dependenciesPanel = pgBrowser.docker.findPanels('dependencies')[0]; - var t = pgBrowser.tree, - i = t.selected(), - d = i && t.itemData(i), - n = i && d && pgBrowser.Nodes[d._type]; - - this.showDependencies(i, d, n); - - // We will start listening the tree selection event. - pgBrowser.Events.on('pgadmin-browser:tree:selected', this.showDependencies); - } else { - if(closed) { - $(this.dependenciesPanel).data('node-prop', ''); - } - // We don't need to listen the tree item selection event. - pgBrowser.Events.off('pgadmin-browser:tree:selected', this.showDependencies); - } - }, - - /* Function is used to create and render backgrid with - * empty collection. We just want to add backgrid into the - * panel only once. - */ - __appendGridToPanel: function() { - var $container = this.dependenciesPanel.layout().scene().find('.pg-panel-content'), - $gridContainer = $container.find('.pg-panel-dependencies-container'), - grid = new Backgrid.Grid({ - emptyText: gettext('No data found'), - columns: [{ - name: 'type', - label: gettext('Type'), - // Extend it to render the icon as per the type. - cell: Backgrid.Cell.extend({ - render: function() { - Backgrid.Cell.prototype.render.apply(this, arguments); - this.$el.prepend($('', { - class: 'wcTabIcon ' + this.model.get('icon'), - })); - return this; - }, - }), - editable: false, - }, - { - name: 'name', - label: gettext('Name'), - cell: 'string', - editable: false, - }, - { - name: 'field', - label: gettext('field'), - cell: 'string', - editable: false, - }, - ], - - collection: this.dependenciesCollection, - className: 'backgrid table presentation table-bordered table-noouter-border table-hover', - }); - - // Condition is used to save grid object to change the label of the header. - this.dependenciesGrid = grid; - - $gridContainer.empty(); - $gridContainer.append(grid.render().el); - - return true; - }, - - // Fetch the actual data and update the collection - showDependencies: function(item, data, node) { - 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 - **/ - if (this.timeout) { - clearTimeout(this.timeout); - } - this.timeout = setTimeout(() => { - this.__updateCollection(node.generate_url(item, 'dependency', data, true), node, item, data._type); - }, 400); - }, - - // Fetch the actual data and update the collection - __updateCollection: function(url, node, item, node_type) { - let self = this, - msg = gettext('Please select an object in the tree view.'), - panel = this.dependenciesPanel, - $container = panel.layout().scene().find('.pg-panel-content'), - $msgContainer = $container.find('.pg-panel-depends-message'), - $gridContainer = $container.find('.pg-panel-dependencies-container'), - treeHierarchy = pgBrowser.tree.getTreeNodeHierarchy(item); - - if (node) { - /* We fetch the Dependencies and Dependencies tab only for - * those node who set the parameter hasDepends to true. - */ - msg = gettext('No dependency information is available for the selected object.'); - if (node.hasDepends) { - // Avoid unnecessary reloads - var cache_flag = { - node_type: node_type, - url: url, - }; - if (_.isEqual($(panel).data('node-prop'), cache_flag)) { - return; - } - // Cache the current IDs for next time - $(panel).data('node-prop', cache_flag); - - /* Updating the label for the 'field' type of the backbone model. - * Label should be "Database" if the node type is tablespace or role - * and dependencies tab is selected. For other nodes and dependencies tab - * it should be 'Restriction'. - */ - - self.__appendGridToPanel(); - this.dependenciesGrid.columns.models[2].set({ - 'label': gettext('Restriction'), - }); - - // Hide message container and show grid container. - $msgContainer.addClass('d-none'); - $gridContainer.removeClass('d-none'); - - var timer = ''; - 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('Fetching dependency information from the server...')); - $msgContainer.removeClass('d-none'); - msg = ''; - - }, 1000); - }, - }) - .done(function(res) { - clearTimeout(timer); - - if (res.length > 0) { - - if (!$msgContainer.hasClass('d-none')) { - $msgContainer.addClass('d-none'); - } - $gridContainer.removeClass('d-none'); - - self.dependenciesData = res; - - // Load only 100 rows - self.dependenciesCollection.reset(self.dependenciesData.splice(0, 100), {parse: true}); - - // Load more rows on scroll down - pgBrowser.Events.on( - 'pgadmin-browser:panel-dependencies:' + - wcDocker.EVENT.SCROLLED, - self.__loadMoreRows - ); - } else { - // Do not listen the scroll event - pgBrowser.Events.off( - 'pgadmin-browser:panel-dependencies:' + - wcDocker.EVENT.SCROLLED - ); - - self.dependenciesCollection.reset({silent: true}); - $msgContainer.text(msg); - $msgContainer.removeClass('d-none'); - - if (!$gridContainer.hasClass('d-none')) { - $gridContainer.addClass('d-none'); - } - } - - - }) - .fail(function(xhr, error, message) { - var _label = treeHierarchy[node_type].label; - pgBrowser.Events.trigger( - 'pgadmin:node:retrieval:error', 'depends', xhr, error, message - ); - if (!Alertify.pgHandleItemError(xhr, error, message, { - item: item, - info: treeHierarchy, - })) { - Notify.pgNotifier( - error, xhr, - gettext('Error retrieving data from the server: %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 != '') { - $msgContainer.text(msg); - $msgContainer.removeClass('d-none'); - if (!$gridContainer.hasClass('d-none')) { - $gridContainer.addClass('d-none'); - } - } - }, - showReactDependencies: function(item, data, node) { - let self = this, - msg = gettext('Please select an object in the tree view.'), - panel = this.dependenciesPanel, - $container = panel.layout().scene().find('.pg-panel-content'), - $msgContainer = $container.find('.pg-panel-depends-message'), - $gridContainer = $container.find('.pg-panel-dependencies-container'), - treeHierarchy = pgBrowser.tree.getTreeNodeHierarchy(item), - n_type = data._type, - url = node.generate_url_react(item, 'dependency'); - - if (node) { - /* We fetch the Dependencies and Dependencies tab only for - * those node who set the parameter hasDepends to true. - */ - msg = gettext('No dependency information is available for the selected object.'); - if (node.hasDepends) { - /* Updating the label for the 'field' type of the backbone model. - * Label should be "Database" if the node type is tablespace or role - * and dependencies tab is selected. For other nodes and dependencies tab - * it should be 'Restriction'. - */ - - this.dependenciesGrid.columns.models[2].set({ - 'label': gettext('Restriction'), - }); - - // Hide message container and show grid container. - $msgContainer.addClass('d-none'); - $gridContainer.removeClass('d-none'); - - var timer = ''; - $.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('Fetching dependency information from the server...')); - $msgContainer.removeClass('d-none'); - msg = ''; - - }, 1000); - }, - }) - .done(function(res) { - clearTimeout(timer); - - if (res.length > 0) { - - if (!$msgContainer.hasClass('d-none')) { - $msgContainer.addClass('d-none'); - } - $gridContainer.removeClass('d-none'); - - self.dependenciesData = res; - - // Load only 100 rows - self.dependenciesCollection.reset(self.dependenciesData.splice(0, 100), {parse: true}); - - // Load more rows on scroll down - pgBrowser.Events.on( - 'pgadmin-browser:panel-dependencies:' + - wcDocker.EVENT.SCROLLED, - self.__loadMoreRows - ); - - } else { - // Do not listen the scroll event - pgBrowser.Events.off( - 'pgadmin-browser:panel-dependencies:' + - wcDocker.EVENT.SCROLLED - ); - - self.dependenciesCollection.reset({silent: true}); - $msgContainer.text(msg); - $msgContainer.removeClass('d-none'); - - if (!$gridContainer.hasClass('d-none')) { - $gridContainer.addClass('d-none'); - } - } - - - }) - .fail(function(xhr, error, message) { - var _label = treeHierarchy[n_type].label; - pgBrowser.Events.trigger( - 'pgadmin:node:retrieval:error', 'depends', xhr, error, message - ); - if (!Alertify.pgHandleItemError(xhr, error, message, { - item: item, - info: treeHierarchy, - })) { - Notify.pgNotifier( - error, xhr, - gettext('Error retrieving data from the server: %s', message || _label), - function(alertMsg) { - if(alertMsg === 'CRYPTKEY_SET') { - self.showDependencies(item, data, node); - } else { - console.warn(arguments); - } - }); - } - // show failed message. - $msgContainer.text(gettext('Failed to retrieve data from the server.')); - }); - } - } - if (msg != '') { - $msgContainer.text(msg); - $msgContainer.removeClass('d-none'); - if (!$gridContainer.hasClass('d-none')) { - $gridContainer.addClass('d-none'); - } - } - }, - - __loadMoreRows: function() { - if (this.dependenciesPanel.length < 1) return ; - - let elem = this.dependenciesPanel.$container.find('.pg-panel-dependencies-container').closest('.wcFrameCenter')[0]; - if ((elem.scrollHeight - 10) < elem.scrollTop + elem.offsetHeight) { - if (this.dependenciesData.length > 0) { - this.dependenciesCollection.add(this.dependenciesData.splice(0, 100), {parse: true}); - } - } - }, - }); - - return pgBrowser.NodeDependencies; -}); diff --git a/web/pgadmin/misc/dependents/static/js/Dependents.jsx b/web/pgadmin/misc/dependents/static/js/Dependents.jsx new file mode 100644 index 000000000..704ca9ef1 --- /dev/null +++ b/web/pgadmin/misc/dependents/static/js/Dependents.jsx @@ -0,0 +1,176 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import _ from 'lodash'; +import React, { useEffect } from 'react'; +import { generateNodeUrl } from '../../../../browser/static/js/node_ajax'; +import PgTable from 'sources/components/PgTable'; +import gettext from 'sources/gettext'; +import PropTypes from 'prop-types'; +import Notify from '../../../../static/js/helpers/Notifier'; +import getApiInstance from 'sources/api_instance'; +import { makeStyles } from '@material-ui/core/styles'; + +const useStyles = makeStyles((theme) => ({ + emptyPanel: { + minHeight: '100%', + minWidth: '100%', + background: theme.palette.grey[400], + overflow: 'auto', + padding: '7.5px', + }, + panelIcon: { + width: '80%', + margin: '0 auto', + marginTop: '25px !important', + position: 'relative', + textAlign: 'center', + }, + panelMessage: { + marginLeft: '0.5rem', + fontSize: '0.875rem', + }, + autoResizer: { + height: '100% !important', + width: '100% !important', + background: theme.palette.grey[400], + padding: '7.5px', + overflow: 'auto !important', + minHeight: '100%', + minWidth: '100%', + }, +})); + +function parseData(data, node) { + // Update the icon + data.forEach((element) => { + if (element.icon == null || element.icon == '') { + if (node) { + element.icon = _.isFunction(node['node_image']) + ? node['node_image'].apply(node, [null, null]) + : node['node_image'] || 'icon-' + element.type; + } else { + element.icon = 'icon-' + element.type; + } + } + if (element.icon) { + element['icon'] = { + type: element.icon, + }; + } + }); + return data; +} + +export default function Dependents({ nodeData, node, ...props }) { + const classes = useStyles(); + const [tableData, setTableData] = React.useState([]); + + const [msg, setMsg] = React.useState(''); + + var columns = [ + { + Header: 'Type', + accessor: 'type', + sortble: true, + resizable: false, + disableGlobalFilter: true, + }, + { + Header: 'Name', + accessor: 'name', + sortble: true, + resizable: false, + disableGlobalFilter: true, + }, + { + Header: 'Restriction', + accessor: 'field', + sortble: true, + resizable: true, + disableGlobalFilter: false, + minWidth: 280, + }, + ]; + + useEffect(() => { + let message = gettext('Please select an object in the tree view.'); + if (node) { + let url = generateNodeUrl.call( + node, + props.treeNodeInfo, + 'dependent', + nodeData, + true, + node.url_jump_after_node + ); + message = gettext( + 'No dependant information is available for the selected object.' + ); + if (node.hasDepends && !nodeData.is_collection) { + const api = getApiInstance(); + api({ + url: url, + type: 'GET', + }) + .then((res) => { + if (res.data.length > 0) { + let data = parseData(res.data, node); + setTableData(data); + } else { + setMsg(message); + } + }) + .catch((e) => { + Notify.alert( + gettext('Failed to retrieve data from the server.'), + gettext(e.message) + ); + // show failed message. + setMsg(gettext('Failed to retrieve data from the server.')); + }); + } + } + if (message != '') { + setMsg(message); + } + + return () => { + setTableData([]); + }; + }, [nodeData]); + + return ( + <> + {tableData.length > 0 ? ( + + ) : ( +
+
+ + {gettext(msg)} +
+
+ )} + + ); +} + +Dependents.propTypes = { + res: PropTypes.array, + nodeData: PropTypes.object, + treeNodeInfo: PropTypes.object, + node: PropTypes.func, +}; diff --git a/web/pgadmin/misc/dependents/static/js/dependents.js b/web/pgadmin/misc/dependents/static/js/dependents.js deleted file mode 100644 index 717ca1e4a..000000000 --- a/web/pgadmin/misc/dependents/static/js/dependents.js +++ /dev/null @@ -1,321 +0,0 @@ -///////////////////////////////////////////////////////////// -// -// pgAdmin 4 - PostgreSQL Tools -// -// Copyright (C) 2013 - 2022, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -////////////////////////////////////////////////////////////// - -import Notify from '../../../../static/js/helpers/Notifier'; - -define('misc.dependents', [ - 'sources/gettext', 'underscore', 'jquery', 'backbone', - 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.alertifyjs', 'pgadmin.backgrid', - 'sources/utils', -], function(gettext, _, $, Backbone, pgAdmin, pgBrowser, Alertify, Backgrid, pgadminUtils) { - - if (pgBrowser.NodeDependents) - return pgBrowser.NodeDependents; - - var wcDocker = window.wcDocker; - - pgBrowser.NodeDependents = pgBrowser.NodeDependents || {}; - - _.extend(pgBrowser.NodeDependents, { - init: function() { - if (this.initialized) { - return; - } - - this.initialized = true; - this.dependentsPanel = pgBrowser.docker.findPanels('dependents')[0]; - /* Parameter is used to set the proper label of the - * backgrid header cell. - */ - _.bindAll(this, 'showDependents', '__updateCollection', '__loadMoreRows', - '__appendGridToPanel', 'toggleVisibility'); - - // Defining Backbone Model for Dependents. - var Model = Backbone.Model.extend({ - defaults: { - icon: 'icon-unknown', - type: undefined, - name: undefined, - /* field contains 'Database Name' for 'Tablespace and Role node', - * for other node it contains 'Restriction'. - */ - field: undefined, - }, - // This function is used to fetch/set the icon for the type(Function, Role, Database, ....) - parse: function(res) { - var node = pgBrowser.Nodes[res.type]; - if(res.icon == null || res.icon == '') { - if (node) { - res.icon = _.isFunction(node['node_image']) ? - (node['node_image']).apply(node, [null, null]) : - (node['node_image'] || ('icon-' + res.type)); - } else { - res.icon = ('icon-' + res.type); - } - } - res.type = pgadminUtils.titleize(res.type.replace(/_/g, ' '), true); - return res; - }, - }); - - // Defining Backbone Collection for Dependents. - this.dependentCollection = new(Backbone.Collection.extend({ - model: Model, - }))(null); - - if(this.dependentsPanel) this.toggleVisibility(this.dependentsPanel.isVisible()); - }, - - toggleVisibility: function(visible, closed=false) { - if (visible) { - this.dependentsPanel = pgBrowser.docker.findPanels('dependents')[0]; - var t = pgBrowser.tree, - i = t && t.selected(), - d = i && t.itemData(i), - n = i && d && pgBrowser.Nodes[d._type]; - - this.showDependents(i, d, n); - - // We will start listening the tree selection event. - pgBrowser.Events.on('pgadmin-browser:tree:selected', this.showDependents); - } else { - if(closed) { - $(this.dependentsPanel).data('node-prop', ''); - } - // We don't need to listen the tree item selection event. - pgBrowser.Events.off('pgadmin-browser:tree:selected', this.showDependents); - } - }, - - /* Function is used to create and render backgrid with - * empty collection. We just want to add backgrid into the - * panel only once. - */ - __appendGridToPanel: function() { - var $container = this.dependentsPanel.layout().scene().find('.pg-panel-content'), - $gridContainer = $container.find('.pg-panel-dependents-container'), - grid = new Backgrid.Grid({ - emptyText: gettext('No data found'), - columns: [{ - name: 'type', - label: gettext('Type'), - // Extend it to render the icon as per the type. - cell: Backgrid.Cell.extend({ - render: function() { - Backgrid.Cell.prototype.render.apply(this, arguments); - this.$el.prepend($('', { - class: 'wcTabIcon ' + this.model.get('icon'), - })); - return this; - }, - }), - editable: false, - }, - { - name: 'name', - label: gettext('Name'), - cell: 'string', - editable: false, - }, - { - name: 'field', - label: gettext('field'), - cell: 'string', - editable: false, - }, - ], - - collection: this.dependentCollection, - className: 'backgrid table presentation table-bordered table-noouter-border table-hover', - }); - - // Condition is used to save grid object to change the label of the header. - this.dependentGrid = grid; - - $gridContainer.empty(); - $gridContainer.append(grid.render().el); - - return true; - }, - - // Fetch the actual data and update the collection - showDependents: function(item, data, node) { - 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 - **/ - if (this.timeout) { - clearTimeout(this.timeout); - } - this.timeout = setTimeout(() => { - this.__updateCollection(node.generate_url(item, 'dependent', data, true), node, item, data._type); - }, 400); - }, - - // Fetch the actual data and update the collection - __updateCollection: function(url, node, item, node_type) { - let self = this, - msg = gettext('Please select an object in the tree view.'), - panel = this.dependentsPanel, - $container = panel.layout().scene().find('.pg-panel-content'), - $msgContainer = $container.find('.pg-panel-depends-message'), - $gridContainer = $container.find('.pg-panel-dependents-container'), - treeHierarchy = pgBrowser.tree.getTreeNodeHierarchy(item); - - if (node) { - /* We fetch the Dependencies and Dependents tab only for - * those node who set the parameter hasDepends to true. - */ - msg = gettext('No dependent information is available for the selected object.'); - if (node.hasDepends) { - // Avoid unnecessary reloads - var cache_flag = { - node_type: node_type, - url: url, - }; - if (_.isEqual($(panel).data('node-prop'), cache_flag)) { - return; - } - // Cache the current IDs for next time - $(panel).data('node-prop', cache_flag); - - /* Updating the label for the 'field' type of the backbone model. - * Label should be "Database" if the node type is tablespace or role - * and dependent tab is selected. For other nodes and dependencies tab - * it should be 'Restriction'. - */ - this.__appendGridToPanel(); - if (node.type == 'tablespace' || node.type == 'role') { - this.dependentGrid.columns.models[2].set({ - 'label': gettext('Database'), - }); - } else { - this.dependentGrid.columns.models[2].set({ - 'label': gettext('Restriction'), - }); - } - - // Hide message container and show grid container. - $msgContainer.addClass('d-none'); - $gridContainer.removeClass('d-none'); - - var timer = ''; - 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('Fetching dependent information from the server...')); - $msgContainer.removeClass('d-none'); - msg = ''; - - }, 1000); - }, - }) - .done(function(res) { - clearTimeout(timer); - - if (res.length > 0) { - - if (!$msgContainer.hasClass('d-none')) { - $msgContainer.addClass('d-none'); - } - $gridContainer.removeClass('d-none'); - - self.dependentData = res; - - // Load only 100 rows - self.dependentCollection.reset(self.dependentData.splice(0, 100), {parse: true}); - - // Load more rows on scroll down - pgBrowser.Events.on( - 'pgadmin-browser:panel-dependents:' + - wcDocker.EVENT.SCROLLED, - self.__loadMoreRows - ); - - } else { - // Do not listen the scroll event - pgBrowser.Events.off( - 'pgadmin-browser:panel-dependents:' + - wcDocker.EVENT.SCROLLED - ); - - self.dependentCollection.reset({silent: true}); - $msgContainer.text(msg); - $msgContainer.removeClass('d-none'); - - if (!$gridContainer.hasClass('d-none')) { - $gridContainer.addClass('d-none'); - } - } - - - }) - .fail(function(xhr, error, message) { - var _label = treeHierarchy[node_type].label; - pgBrowser.Events.trigger( - 'pgadmin:node:retrieval:error', 'depends', xhr, error, message - ); - if (!Alertify.pgHandleItemError(xhr, error, message, { - item: item, - info: treeHierarchy, - })) { - Notify.pgNotifier( - error, xhr, - gettext('Error retrieving data from the server: %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 != '') { - $msgContainer.text(msg); - $msgContainer.removeClass('d-none'); - if (!$gridContainer.hasClass('d-none')) { - $gridContainer.addClass('d-none'); - } - } - }, - __loadMoreRows: function() { - if (this.dependentsPanel.length < 1) return ; - - let elem = this.dependentsPanel.$container.find('.pg-panel-dependents-container').closest('.wcFrameCenter')[0]; - if ((elem.scrollHeight - 10) < elem.scrollTop + elem.offsetHeight) { - if (this.dependentData.length > 0) { - this.dependentCollection.add(this.dependentData.splice(0, 100), {parse: true}); - } - } - }, - }); - - return pgBrowser.NodeDependents; -}); diff --git a/web/pgadmin/misc/statistics/static/js/Statistics.jsx b/web/pgadmin/misc/statistics/static/js/Statistics.jsx new file mode 100644 index 000000000..f2fb316f0 --- /dev/null +++ b/web/pgadmin/misc/statistics/static/js/Statistics.jsx @@ -0,0 +1,235 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import _ from 'lodash'; +import React, { useEffect } from 'react'; +import { + generateNodeUrl, + generateCollectionURL, +} from '../../../../browser/static/js/node_ajax'; +import PgTable from 'sources/components/PgTable'; +import gettext from 'sources/gettext'; +import PropTypes from 'prop-types'; +import Notify from '../../../../static/js/helpers/Notifier'; +import getApiInstance from 'sources/api_instance'; +import { makeStyles } from '@material-ui/core/styles'; +import sizePrettify from 'sources/size_prettify'; + +const useStyles = makeStyles((theme) => ({ + emptyPanel: { + minHeight: '100%', + minWidth: '100%', + background: theme.palette.grey[400], + overflow: 'auto', + padding: '7.5px', + }, + panelIcon: { + width: '80%', + margin: '0 auto', + marginTop: '25px !important', + position: 'relative', + textAlign: 'center', + }, + panelMessage: { + marginLeft: '0.5rem', + fontSize: '0.875rem', + }, + autoResizer: { + height: '100% !important', + width: '100% !important', + background: theme.palette.grey[400], + padding: '7.5px', + overflow: 'auto !important', + minHeight: '100%', + minWidth: '100%', + }, +})); + +function getColumn(data, singleLineStatistics) { + let columns = []; + if (!singleLineStatistics) { + if (!_.isUndefined(data)) { + data.forEach((row) => { + var column = { + Header: row.name, + accessor: row.name, + sortble: true, + resizable: false, + disableGlobalFilter: true, + }; + columns.push(column); + }); + } + } else { + columns = [ + { + Header: 'Statictics', + accessor: 'name', + sortble: true, + resizable: false, + disableGlobalFilter: true, + }, + { + Header: 'Value', + accessor: 'value', + sortble: true, + resizable: false, + disableGlobalFilter: true, + }, + ]; + } + return columns; +} + +function getTableData(res, node) { + let nodeStats = [], + colData; + if (res.data.data) { + let data = res.data.data; + if (node.hasCollectiveStatistics || data['rows'].length > 1) { + data.rows.forEach((row) => { + nodeStats.push({ ...row, icon: '' }); + }); + colData = getColumn(data.columns, false); + } else { + nodeStats = createSingleLineStatistics(data, node.statsPrettifyFields); + colData = getColumn(data.columns, true); + } + } + return [nodeStats, colData]; +} +function createSingleLineStatistics(data, prettifyFields) { + var row = data['rows'][0], + columns = data['columns'], + res = [], + name, + value; + + for (var idx in columns) { + name = columns[idx]['name']; + if (row && row[name]) { + value = + _.indexOf(prettifyFields, name) != -1 + ? sizePrettify(row[name]) + : row[name]; + } else { + value = null; + } + + res.push({ + name: name, + value: value, + icon: '', + }); + } + + return res; +} + +export default function Statistics({ nodeData, item, node, ...props }) { + const classes = useStyles(); + const [tableData, setTableData] = React.useState([]); + + const [msg, setMsg] = React.useState(''); + const [columns, setColumns] = React.useState([ + { + Header: 'Statictics', + accessor: 'name', + sortble: true, + resizable: false, + disableGlobalFilter: true, + }, + { + Header: 'Value', + accessor: 'value', + sortble: true, + resizable: false, + disableGlobalFilter: true, + }, + ]); + + useEffect(() => { + let url, + message = gettext('Please select an object in the tree view.'); + if (node) { + if (nodeData.is_collection) { + url = generateCollectionURL.call(node, item, 'stats'); + } else { + url = generateNodeUrl.call( + node, + props.treeNodeInfo, + 'stats', + nodeData, + true, + node.url_jump_after_node + ); + } + + message = gettext('No statistics are available for the selected object.'); + + const api = getApiInstance(); + if (node.hasStatistics) { + api({ + url: url, + type: 'GET', + }) + .then((res) => { + let [nodeStats, colData] = getTableData(res, node); + setTableData(nodeStats); + setColumns(colData); + }) + .catch((e) => { + Notify.alert( + gettext('Failed to retrieve data from the server.'), + gettext(e.message) + ); + // show failed message. + setMsg(gettext('Failed to retrieve data from the server.')); + }); + } else { + setMsg(message); + } + } + if (message != '') { + setMsg(message); + } + return () => { + setTableData([]); + }; + }, [nodeData]); + + return ( + <> + {tableData.length > 0 ? ( + + ) : ( +
+
+ + {gettext(msg)} +
+
+ )} + + ); +} + +Statistics.propTypes = { + res: PropTypes.array, + nodeData: PropTypes.object, + item: PropTypes.object, + treeNodeInfo: PropTypes.object, + node: PropTypes.func, +}; diff --git a/web/pgadmin/misc/statistics/static/js/statistics.js b/web/pgadmin/misc/statistics/static/js/statistics.js deleted file mode 100644 index 744e36dbf..000000000 --- a/web/pgadmin/misc/statistics/static/js/statistics.js +++ /dev/null @@ -1,432 +0,0 @@ -///////////////////////////////////////////////////////////// -// -// pgAdmin 4 - PostgreSQL Tools -// -// Copyright (C) 2013 - 2022, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -////////////////////////////////////////////////////////////// - -import Notify from '../../../../static/js/helpers/Notifier'; - -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() {/*This is intentional (SonarQube)*/}; - _.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 = pgBrowser.tree.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(pgBrowser, 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, - })) { - Notify.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']; - let val = null; - if (row && row[name]) { - val = _.indexOf(prettifyFields, name) != -1 ? sizePrettify(row[name]) : row[name]; - } - res.push({ - 'statistics': name, - // Check if row is undefined? - 'value': val, - }); - } - - this.collection.reset(res); - }, - }); - - return pgBrowser.NodeStatistics; -}); diff --git a/web/pgadmin/static/js/components/PgTable.jsx b/web/pgadmin/static/js/components/PgTable.jsx index 7a56ab83d..8a433b08b 100644 --- a/web/pgadmin/static/js/components/PgTable.jsx +++ b/web/pgadmin/static/js/components/PgTable.jsx @@ -30,14 +30,23 @@ const useStyles = makeStyles((theme) => ({ height: '100% !important', width: '100% !important', }, + fixedSizeList: { + position: 'relative', + direction: 'ltr', + overflowX: 'hidden !important', + overflow: 'overlay !important' + }, table: { + flexGrow:1, + minHeight:0, borderSpacing: 0, width: '100%', overflow: 'hidden', - height: '100%', - backgroundColor: theme.otherVars.tableBg, - ...theme.mixins.panelBorder, - //backgroundColor: theme.palette.background.default, + borderRadius: theme.shape.borderRadius, + }, + extraTable:{ + backgroundColor: theme.palette.grey[400], + flexGrow:1, }, tableCell: { @@ -49,6 +58,10 @@ const useStyles = makeStyles((theme) => ({ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', + backgroundColor: theme.otherVars.tableBg, + // ...theme.mixins.panelBorder.top, + ...theme.mixins.panelBorder.left, + }, selectCell: { textAlign: 'center' @@ -60,8 +73,11 @@ const useStyles = makeStyles((theme) => ({ overflowY: 'auto', overflowX: 'hidden', alignContent: 'center', + backgroundColor: theme.otherVars.tableBg, ...theme.mixins.panelBorder.bottom, ...theme.mixins.panelBorder.right, + ...theme.mixins.panelBorder.top, + ...theme.mixins.panelBorder.left, }, resizer: { display: 'inline-block', @@ -75,8 +91,10 @@ const useStyles = makeStyles((theme) => ({ touchAction: 'none', }, cellIcon: { - paddingLeft: '1.5em', - height: 35 + paddingLeft: '1.8em', + paddingTop: '0.35em', + height: 35, + backgroundPosition: '1%', } }), ); @@ -171,9 +189,9 @@ export default function PgTable({ columns, data, isSelectRow, ...props }) { return [...CLOUMNS]; } }); - hooks.useInstanceBeforeDimensions.push(({ tmpHeaderGroups }) => { + hooks.useInstanceBeforeDimensions.push(({ headerGroups }) => { // fix the parent group of the selection button to not be resizable - const selectionGroupHeader = tmpHeaderGroups[0].headers[0]; + const selectionGroupHeader = headerGroups[0].headers[0]; selectionGroupHeader.resizable = false; }); } @@ -235,15 +253,26 @@ export default function PgTable({ columns, data, isSelectRow, ...props }) { ); // Render the UI for your table return ( - - {({ height }) => ( + + {({ height}) => (
- {headerGroups.map(headerGroup => ( + {headerGroups.map((headerGroup) => (
- {headerGroup.headers.map(column => ( -
-
+ {headerGroup.headers.map((column) => ( +
+
{column.render('Header')} {column.isSorted @@ -252,21 +281,23 @@ export default function PgTable({ columns, data, isSelectRow, ...props }) { : ' 🔼' : ''} - {column.resizable && + {column.resizable && (
} + /> + )}
))} + {/* */}
))}
-
- +
{RenderRow} -
)} diff --git a/web/webpack.config.js b/web/webpack.config.js index bf7293c68..3181ecfe3 100644 --- a/web/webpack.config.js +++ b/web/webpack.config.js @@ -455,9 +455,6 @@ module.exports = [{ 'pure|pgadmin.dashboard', 'pure|pgadmin.browser.quick_search', 'pure|pgadmin.tools.user_management', - 'pure|pgadmin.browser.object_statistics', - 'pure|pgadmin.browser.dependencies', - 'pure|pgadmin.browser.dependents', 'pure|pgadmin.browser.object_sql', 'pure|pgadmin.browser.bgprocess', 'pure|pgadmin.node.server_group', diff --git a/web/webpack.shim.js b/web/webpack.shim.js index 6896ade63..0430047c0 100644 --- a/web/webpack.shim.js +++ b/web/webpack.shim.js @@ -207,10 +207,7 @@ var webpackShimConfig = { 'pgadmin.browser.dialog': path.join(__dirname, './pgadmin/browser/static/js/dialog'), 'pgadmin.browser.node': path.join(__dirname, './pgadmin/browser/static/js/node'), 'pgadmin.browser.node.ui': path.join(__dirname, './pgadmin/browser/static/js/node.ui'), - 'pgadmin.browser.dependencies': path.join(__dirname, './pgadmin/misc/dependencies/static/js/dependencies'), - 'pgadmin.browser.dependents': path.join(__dirname, './pgadmin/misc/dependents/static/js/dependents'), 'pgadmin.browser.object_sql': path.join(__dirname, './pgadmin/misc/sql/static/js/sql'), - 'pgadmin.browser.object_statistics': path.join(__dirname, './pgadmin/misc/statistics/static/js/statistics'), 'pgadmin.browser.panel': path.join(__dirname, './pgadmin/browser/static/js/panel'), 'pgadmin.browser.toolbar': path.join(__dirname, './pgadmin/browser/static/js/toolbar'), 'pgadmin.browser.server.privilege': path.join(__dirname, './pgadmin/browser/server_groups/servers/static/js/privilege'),