Port Dependent, dependencies, statistics panel to React. Fixes #7016

This commit is contained in:
Pradip Parkale 2022-01-24 14:13:02 +05:30 committed by Akshay Joshi
parent 3e86ed5d21
commit 094129e2be
14 changed files with 779 additions and 1232 deletions

View File

@ -13,6 +13,7 @@ New features
Housekeeping
************
| `Issue #7016 <https://redmine.postgresql.org/issues/7016>`_ - Port Dependent, dependencies, statistics panel to React.
| `Issue #7017 <https://redmine.postgresql.org/issues/7017>`_ - Port Import/Export dialog to React.
Bug fixes

View File

@ -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;

View File

@ -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 = {

View File

@ -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
);
}
}
}
});

View File

@ -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(
<Theme>
<Statistics
treeNodeInfo={treeNodeInfo}
pgBrowser={pgBrowser}
nodeData={nodeData}
node={node}
item={item}
/>
</Theme>,
container
);
}
if (panelType == 'dependencies') {
ReactDOM.render(
<Theme>
<Dependencies
treeNodeInfo={treeNodeInfo}
pgBrowser={pgBrowser}
nodeData={nodeData}
node={node}
item={item}
/>
</Theme>,
container
);
}
if (panelType == 'dependents') {
ReactDOM.render(
<Theme>
<Dependents
treeNodeInfo={treeNodeInfo}
pgBrowser={pgBrowser}
nodeData={nodeData}
node={node}
item={item}
/>
</Theme>,
container
);
}
}
/* When switching from normal node to collection node, clean up the React mounted DOM */
export function removePanelView(container) {
ReactDOM.unmountComponentAtNode(container);
}

View File

@ -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 ? (
<PgTable
className={classes.autoResizer}
columns={columns}
data={tableData}
msg={msg}
type={gettext('panel')}
></PgTable>
) : (
<div className={classes.emptyPanel}>
<div className={classes.panelIcon}>
<i className="fa fa-exclamation-circle"></i>
<span className={classes.panelMessage}>{gettext(msg)}</span>
</div>
</div>
)}
</>
);
}
Dependencies.propTypes = {
res: PropTypes.array,
nodeData: PropTypes.object,
treeNodeInfo: PropTypes.object,
node: PropTypes.func,
};

View File

@ -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($('<i>', {
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;
});

View File

@ -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 ? (
<PgTable
className={classes.autoResizer}
columns={columns}
data={tableData}
msg={msg}
type={gettext('panel')}
></PgTable>
) : (
<div className={classes.emptyPanel}>
<div className={classes.panelIcon}>
<i className="fa fa-exclamation-circle"></i>
<span className={classes.panelMessage}>{gettext(msg)}</span>
</div>
</div>
)}
</>
);
}
Dependents.propTypes = {
res: PropTypes.array,
nodeData: PropTypes.object,
treeNodeInfo: PropTypes.object,
node: PropTypes.func,
};

View File

@ -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($('<i>', {
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;
});

View File

@ -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 ? (
<PgTable
className={classes.autoResizer}
columns={columns}
data={tableData}
msg={msg}
type={'panel'}
></PgTable>
) : (
<div className={classes.emptyPanel}>
<div className={classes.panelIcon}>
<i className="fa fa-exclamation-circle"></i>
<span className={classes.panelMessage}>{gettext(msg)}</span>
</div>
</div>
)}
</>
);
}
Statistics.propTypes = {
res: PropTypes.array,
nodeData: PropTypes.object,
item: PropTypes.object,
treeNodeInfo: PropTypes.object,
node: PropTypes.func,
};

View File

@ -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;
});

View File

@ -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 (
<AutoSizer className={classes.autoResizer}>
{({ height }) => (
<AutoSizer className={(props.type ==='panel' ? props.className: classes.autoResizer)}>
{({ height}) => (
<div {...getTableProps()} className={classes.table}>
<div>
{headerGroups.map(headerGroup => (
{headerGroups.map((headerGroup) => (
<div key={''} {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<div key={column.id} {...column.getHeaderProps()} className={clsx(classes.tableCellHeader, column.className)}>
<div {...(column.sortble ? column.getSortByToggleProps() : {})}>
{headerGroup.headers.map((column) => (
<div
key={column.id}
{...column.getHeaderProps()}
className={clsx(
classes.tableCellHeader,
column.className
)}
>
<div
{...(column.sortble
? column.getSortByToggleProps()
: {})}
>
{column.render('Header')}
<span>
{column.isSorted
@ -252,21 +281,23 @@ export default function PgTable({ columns, data, isSelectRow, ...props }) {
: ' 🔼'
: ''}
</span>
{column.resizable &&
{column.resizable && (
<div
{...column.getResizerProps()}
className={classes.resizer}
/>}
/>
)}
</div>
</div>
))}
{/* <span className={classes.extraTable}></span> */}
</div>
))}
</div>
<div {...getTableBodyProps()}>
<div {...getTableBodyProps()} className={classes}>
<FixedSizeList
className={classes.fixedSizeList}
height={height - 75}
itemCount={rows.length}
itemSize={35}
@ -274,7 +305,6 @@ export default function PgTable({ columns, data, isSelectRow, ...props }) {
>
{RenderRow}
</FixedSizeList>
</div>
</div>
)}

View File

@ -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',

View File

@ -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'),