Upgrade react-table from v7 to v8. #7419

This commit is contained in:
Aditya Toshniwal 2024-05-07 16:31:04 +05:30 committed by GitHub
parent 3e6bd29198
commit 5ec6faff13
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 1468 additions and 1481 deletions

View File

@ -86,6 +86,8 @@
"@projectstorm/react-diagrams": "^6.6.1",
"@simonwep/pickr": "^1.5.1",
"@szhsin/react-menu": "^2.2.0",
"@tanstack/react-table": "^8.16.0",
"@tanstack/react-virtual": "^3.4.0",
"@types/classnames": "^2.2.6",
"@types/react": "^17.0.80",
"@types/react-dom": "^17.0.25",
@ -143,10 +145,8 @@
"react-resize-detector": "^9.1.0",
"react-rnd": "^10.3.5",
"react-select": "^5.7.2",
"react-table": "^7.6.3",
"react-timer-hook": "^3.0.5",
"react-virtualized-auto-sizer": "^1.0.6",
"react-window": "^1.8.5",
"snapsvg-cjs": "^0.0.6",
"socket.io-client": "^4.5.0",
"split.js": "^1.5.10",

View File

@ -453,7 +453,7 @@ export class ColumnSchema extends BaseUISchema {
},{
id: 'max_val_attlen', skipChange: true, visible: false, type: '',
},{
id: 'attprecision', label: gettext('Scale'), width: 60, disableResizing: true,
id: 'attprecision', label: gettext('Scale'), width: 60, enableResizing: false,
deps: ['cltype'], type: 'int', group: gettext('Definition'),
cell: (state)=>{
return obj.attCell(state);

View File

@ -166,7 +166,7 @@ export default class ColumnSchema extends BaseUISchema {
// Need to show this field only when creating new table
// [in SubNode control]
id: 'is_primary_key', label: gettext('Primary key?'),
cell: 'switch', type: 'switch', width: 100, disableResizing: true, deps:['name', ['primary_key']],
cell: 'switch', type: 'switch', width: 100, enableResizing: false, deps:['name', ['primary_key']],
visible: ()=>{
return obj.top?.nodeInfo && _.isUndefined(
obj.top.nodeInfo['table'] || obj.top.nodeInfo['view'] ||
@ -280,7 +280,7 @@ export default class ColumnSchema extends BaseUISchema {
},
},{
id: 'attlen', label: gettext('Length/Precision'),
deps: ['cltype'], type: 'int', group: gettext('Definition'), width: 120, disableResizing: true,
deps: ['cltype'], type: 'int', group: gettext('Definition'), width: 120, enableResizing: false,
cell: (state)=>{
return obj.attCell(state);
},
@ -311,7 +311,7 @@ export default class ColumnSchema extends BaseUISchema {
},{
id: 'max_val_attlen', skipChange: true, visible: false, type: '',
},{
id: 'attprecision', label: gettext('Scale'), width: 60, disableResizing: true,
id: 'attprecision', label: gettext('Scale'), width: 60, enableResizing: false,
deps: ['cltype'], type: 'int', group: gettext('Definition'),
cell: (state)=>{
return obj.attCell(state);
@ -439,7 +439,7 @@ export default class ColumnSchema extends BaseUISchema {
},
},{
id: 'attnotnull', label: gettext('Not NULL?'), cell: 'switch',
type: 'switch', width: 80, disableResizing: true,
type: 'switch', width: 80, enableResizing: false,
group: gettext('Constraints'), editable: this.editableCheckForTable,
deps: ['colconstype'],
readonly: (state) => {

View File

@ -137,7 +137,7 @@ class ExclusionColumnSchema extends BaseUISchema {
let obj = this;
return [{
id: 'is_exp', label: '', type:'', cell: '', editable: false, width: 20,
disableResizing: true,
enableResizing: false,
controlProps: {
formatter: {
fromRaw: function (rawValue) {
@ -162,7 +162,7 @@ class ExclusionColumnSchema extends BaseUISchema {
{label: 'ASC', value: true},
{label: 'DESC', value: false},
],
editable: obj.isEditable, width: 110, disableResizing: true,
editable: obj.isEditable, width: 110, enableResizing: false,
controlProps: {
allowClear: false,
},
@ -172,7 +172,7 @@ class ExclusionColumnSchema extends BaseUISchema {
{label: 'FIRST', value: true},
{label: 'LAST', value: false},
], controlProps: {allowClear: false},
editable: obj.isEditable, width: 110, disableResizing: true,
editable: obj.isEditable, width: 110, enableResizing: false,
},{
id: 'operator', label: gettext('Operator'), type: 'select',
width: 95,

View File

@ -130,7 +130,7 @@ class IndexColumnSchema extends BaseUISchema {
return [
{
id: 'is_exp', label: '', type:'', cell: '', editable: false, width: 20,
disableResizing: true,
enableResizing: false,
controlProps: {
formatter: {
fromRaw: function (rawValue) {
@ -182,7 +182,7 @@ class IndexColumnSchema extends BaseUISchema {
{label: 'ASC', value: false},
{label: 'DESC', value: true},
],
width: 110, disableResizing: true,
width: 110, enableResizing: false,
controlProps: {
allowClear: false,
},
@ -211,7 +211,7 @@ class IndexColumnSchema extends BaseUISchema {
{label: 'FIRST', value: true},
{label: 'LAST', value: false},
], controlProps: {allowClear: false},
width: 110, disableResizing: true,
width: 110, enableResizing: false,
editable: function(state) {
return obj.isEditable(state);
},
@ -243,7 +243,7 @@ export class WithSchema extends BaseUISchema {
super({});
this.node_info = node_info;
}
get baseFields() {
let withSchemaObj = this;
return [
@ -308,7 +308,7 @@ export class WithSchema extends BaseUISchema {
depChange: (state, source) => {
if (state.amname !== 'btree') {
return {deduplicate_items:undefined};
} else if (state.amname === 'btree' && source[0] !== 'deduplicate_items' &&
} else if (state.amname === 'btree' && source[0] !== 'deduplicate_items' &&
withSchemaObj.node_info.server.version >= 130000) {
return {deduplicate_items: true};
}

View File

@ -156,7 +156,7 @@ export class PartitionsSchema extends BaseUISchema {
mode: ['properties'],
},{
id: 'is_attach', label:gettext('Operation'), cell: 'select', type: 'select',
width: 120, disableResizing: true, options: [
width: 120, enableResizing: false, options: [
{label: gettext('Attach'), value: true},
{label: gettext('Create'), value: false},
], controlProps: {allowClear: false},
@ -225,7 +225,7 @@ export class PartitionsSchema extends BaseUISchema {
},
},{
id: 'is_default', label: gettext('Default'), type: 'switch', cell:'switch',
width: 55, disableResizing: true, min_version: 110000,
width: 55, enableResizing: false, min_version: 110000,
editable: function(state) {
return (obj.top && (obj.top.sessData.partition_type == 'range' ||
obj.top.sessData.partition_type == 'list') && obj.isNew(state)

View File

@ -37,7 +37,7 @@ import { parseApiError } from '../../../static/js/api_instance';
import SectionContainer from './components/SectionContainer';
import Replication from './Replication';
import RefreshButton from './components/RefreshButtons';
import {getExpandCell } from '../../../static/js/components/PgTable';
import { getExpandCell } from '../../../static/js/components/PgReactTableStyled';
function parseData(data) {
let res = [];
@ -132,6 +132,8 @@ const useStyles = makeStyles((theme) => ({
}
}));
let activeQSchemaObj = new ActiveQuery();
function Dashboard({
nodeItem, nodeData, node, treeNodeInfo,
...props
@ -150,7 +152,6 @@ function Dashboard({
const [mainTabVal, setMainTabVal] = useState(0);
const [refresh, setRefresh] = useState(false);
const [activeOnly, setActiveOnly] = useState(false);
const [schemaDict, setSchemaDict] = React.useState({});
const [systemStatsTabVal, setSystemStatsTabVal] = useState(0);
const [ldid, setLdid] = useState(0);
@ -183,64 +184,62 @@ function Dashboard({
const serverConfigColumns = [
{
accessor: 'name',
Header: gettext('Name'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 50,
width: 100,
minResizeWidth: 150,
accessorKey: 'name',
header: gettext('Name'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 100,
size: 100,
},
{
accessor: 'category',
Header: gettext('Category'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 50,
accessorKey: 'category',
header: gettext('Category'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 50,
size: 50,
},
{
accessor: 'setting',
Header: gettext('Value'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 50,
width: 100,
accessorKey: 'setting',
header: gettext('Value'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 50,
size: 100,
},
{
accessor: 'unit',
Header: gettext('Unit'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
width: 30,
accessorKey: 'unit',
header: gettext('Unit'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 30,
size: 30,
},
{
accessor: 'short_desc',
Header: gettext('Description'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
accessorKey: 'short_desc',
header: gettext('Description'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
];
const activityColumns = [
{
accessor: 'terminate_query',
Header: () => null,
sortable: true,
resizable: false,
disableGlobalFilter: false,
disableResizing: true,
width: 35,
maxWidth: 35,
minWidth: 35,
header: () => null,
enableSorting: true,
enableResizing: false,
enableFilters: false,
size: 35,
maxSize: 35,
minSize: 35,
id: 'btn-terminate',
// eslint-disable-next-line react/display-name
Cell: ({ row }) => {
cell: ({ row }) => {
let terminate_session_url =
url_for('dashboard.index') + 'terminate_session' + '/' + sid,
title = gettext('Terminate Session?'),
@ -268,7 +267,7 @@ function Dashboard({
!canTakeAction(row, 'terminate')
)
return;
let url = action_url + '/' + row.values.pid;
let url = action_url + '/' + row.original.pid;
pgAdmin.Browser.notifier.confirm(
title,
txtConfirm,
@ -302,15 +301,15 @@ function Dashboard({
},
},
{
accessor: 'cancel_Query',
Header: () => null,
sortable: true,
resizable: false,
disableGlobalFilter: false,
width: 35,
minWidth: 0,
header: () => null,
enableSorting: true,
enableResizing: false,
enableFilters: false,
size: 35,
maxSize: 35,
minSize: 35,
id: 'btn-cancel',
Cell: ({ row }) => {
cell: ({ row }) => {
let cancel_query_url =
url_for('dashboard.index') + 'cancel_query' + '/' + sid,
title = gettext('Cancel Active Query?'),
@ -334,7 +333,7 @@ function Dashboard({
onClick={() => {
if (!canTakeAction(row, 'cancel'))
return;
let url = action_url + '/' + row.values.pid;
let url = action_url + '/' + row.original.pid;
pgAdmin.Browser.notifier.confirm(
title,
txtConfirm,
@ -370,280 +369,272 @@ function Dashboard({
},
},
{
accessor: 'view_active_query',
Header: () => null,
sortable: true,
resizable: false,
disableGlobalFilter: false,
width: 35,
minWidth: 0,
header: () => null,
enableSorting: true,
enableResizing: false,
enableFilters: false,
size: 35,
maxSize: 35,
minSize: 35,
id: 'btn-edit',
Cell: getExpandCell({
onClick: (row) => {
let schema = new ActiveQuery({
query: row.original.query,
backend_type: row.original.backend_type,
state_change: row.original.state_change,
query_start: row.original.query_start,
});
setSchemaDict(prevState => ({
...prevState,
[row.id]: schema
}));
},
cell: getExpandCell({
title: gettext('View the active session details')
}),
},
{
accessor: 'pid',
Header: gettext('PID'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
width: 60,
accessorKey: 'pid',
header: gettext('PID'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 50,
size: 60,
},
{
accessor: 'datname',
Header: gettext('Database'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
width: 80,
isVisible: !did
accessorKey: 'datname',
header: gettext('Database'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
enableVisibility: !did,
minSize: 50,
size: 80,
},
{
accessor: 'usename',
Header: gettext('User'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
width: 60
accessorKey: 'usename',
header: gettext('User'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 50,
size: 60,
},
{
accessor: 'application_name',
Header: gettext('Application'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
accessorKey: 'application_name',
header: gettext('Application'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 50,
},
{
accessor: 'client_addr',
Header: gettext('Client'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
accessorKey: 'client_addr',
header: gettext('Client'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 50,
size: 100
},
{
accessor: 'backend_start',
Header: gettext('Backend start'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 100,
accessorKey: 'backend_start',
header: gettext('Backend start'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 100,
},
{
accessor: 'xact_start',
Header: gettext('Transaction start'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
accessorKey: 'xact_start',
header: gettext('Transaction start'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 100,
},
{
accessor: 'state',
Header: gettext('State'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
width:40
accessorKey: 'state',
header: gettext('State'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 50,
size: 50,
},
{
accessor: 'waiting',
Header: gettext('Waiting'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
isVisible: treeNodeInfo?.server?.version < 90600
accessorKey: 'waiting',
header: gettext('Waiting'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
enableVisibility: treeNodeInfo?.server?.version < 90600
},
{
accessor: 'wait_event',
Header: gettext('Wait event'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
accessorKey: 'wait_event',
header: gettext('Wait event'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
{
accessor: 'blocking_pids',
Header: gettext('Blocking PIDs'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
accessorKey: 'blocking_pids',
header: gettext('Blocking PIDs'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
];
const databaseLocksColumns = [
{
accessor: 'pid',
Header: gettext('PID'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
width: 50,
accessorKey: 'pid',
header: gettext('PID'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 50,
size: 50,
},
{
accessor: 'datname',
Header: gettext('Database'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
isVisible: !did,
width: 80
accessorKey: 'datname',
header: gettext('Database'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
enableVisibility: !did,
minSize: 50,
size: 80,
},
{
accessor: 'locktype',
Header: gettext('Lock type'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
width: 80,
accessorKey: 'locktype',
header: gettext('Lock type'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 50,
size: 80,
},
{
accessor: 'relation',
Header: gettext('Target relation'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
accessorKey: 'relation',
header: gettext('Target relation'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
{
accessor: 'page',
Header: gettext('Page'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
width: 80,
accessorKey: 'page',
header: gettext('Page'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 50,
size: 80,
},
{
accessor: 'tuple',
Header: gettext('Tuple'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
accessorKey: 'tuple',
header: gettext('Tuple'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 50,
size: 50,
},
{
accessor: 'virtualxid',
Header: gettext('vXID (target)'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 50,
width: 80
accessorKey: 'virtualxid',
header: gettext('vXID (target)'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 50,
size: 50,
},
{
accessor: 'transactionid',
Header: gettext('XID (target)'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 50,
width: 80,
accessorKey: 'transactionid',
header: gettext('XID (target)'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 50,
size: 80,
},
{
accessor: 'classid',
Header: gettext('Class'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
width: 80,
accessorKey: 'classid',
header: gettext('Class'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 50,
size: 80,
},
{
accessor: 'objid',
Header: gettext('Object ID'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 50,
width: 80,
accessorKey: 'objid',
header: gettext('Object ID'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 50,
size: 80,
},
{
accessor: 'virtualtransaction',
Header: gettext('vXID (owner)'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 50,
accessorKey: 'virtualtransaction',
header: gettext('vXID (owner)'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 50,
size: 50,
},
{
accessor: 'mode',
Header: gettext('Mode'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
accessorKey: 'mode',
header: gettext('Mode'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 50,
size: 50,
},
{
id: 'granted',
accessor: 'granted',
Header: gettext('Granted?'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 30,
width: 80,
Cell: ({ value }) => String(value)
accessorKey: 'granted',
header: gettext('Granted?'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 50,
size: 80,
cell: ({ value }) => String(value)
},
];
const databasePreparedColumns = [
{
accessor: 'git',
Header: gettext('Name'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
accessorKey: 'git',
header: gettext('Name'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
{
accessor: 'datname',
Header: gettext('Database'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
accessorKey: 'datname',
header: gettext('Database'),
enableSorting: true,
enableResizing: true,
enableVisibility: !did,
enableFilters: true,
minWidth: 26,
width: 80,
isVisible: !did
},
{
accessor: 'Owner',
Header: gettext('Owner'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
accessorKey: 'Owner',
header: gettext('Owner'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
{
accessor: 'transaction',
Header: gettext('XID'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
accessorKey: 'transaction',
header: gettext('XID'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
{
accessor: 'prepared',
Header: gettext('Prepared at'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
accessorKey: 'prepared',
header: gettext('Prepared at'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
];
@ -888,15 +879,17 @@ function Dashboard({
<TabPanel value={tabVal} index={0} classNameRoot={classes.tabPanel}>
<PgTable
caveTable={false}
tableNoBorder={false}
CustomHeader={CustomActiveOnlyHeader}
columns={activityColumns}
data={filteredDashData}
schema={schemaDict}
schema={activeQSchemaObj}
></PgTable>
</TabPanel>
<TabPanel value={tabVal} index={1} classNameRoot={classes.tabPanel}>
<PgTable
caveTable={false}
tableNoBorder={false}
columns={databaseLocksColumns}
data={dashData}
></PgTable>
@ -904,6 +897,7 @@ function Dashboard({
<TabPanel value={tabVal} index={2} classNameRoot={classes.tabPanel}>
<PgTable
caveTable={false}
tableNoBorder={false}
columns={databasePreparedColumns}
data={dashData}
></PgTable>
@ -911,6 +905,7 @@ function Dashboard({
<TabPanel value={tabVal} index={3} classNameRoot={classes.tabPanel}>
<PgTable
caveTable={false}
tableNoBorder={false}
columns={serverConfigColumns}
data={dashData}
></PgTable>

View File

@ -17,134 +17,132 @@ import getApiInstance, { parseApiError } from '../../../../static/js/api_instanc
import SectionContainer from '../components/SectionContainer';
import ReplicationStatsSchema from './replication_stats.ui';
import RefreshButton from '../components/RefreshButtons';
import { getExpandCell, getSwitchCell } from '../../../../static/js/components/PgTable';
import { getExpandCell, getSwitchCell } from '../../../../static/js/components/PgReactTableStyled';
import { usePgAdmin } from '../../../../static/js/BrowserComponent';
import url_for from 'sources/url_for';
import PropTypes from 'prop-types';
const replicationStatsColumns = [{
accessor: 'view_details',
Header: () => null,
sortable: false,
resizable: false,
disableGlobalFilter: false,
disableResizing: true,
width: 35,
maxWidth: 35,
minWidth: 35,
accessorKey: 'view_details',
header: () => null,
enableSorting: false,
enableResizing: false,
enableFilters: true,
size: 35,
maxSize: 35,
minSize: 35,
id: 'btn-edit',
Cell: getExpandCell({
cell: getExpandCell({
title: gettext('View details')
}),
},
{
accessor: 'pid',
Header: gettext('PID'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
width: 40,
accessorKey: 'pid',
header: gettext('PID'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
size: 40,
minSize: 40,
},
{
accessor: 'client_addr',
Header: gettext('Client Addr'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
width: 60,
accessorKey: 'client_addr',
header: gettext('Client Addr'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
size: 100,
minSize: 50,
},
{
accessor:'state',
Header: gettext('State'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
width: 60
accessorKey:'state',
header: gettext('State'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
size: 100,
minSize: 50,
},
{
accessor:'write_lag',
Header: gettext('Write Lag'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
width: 60
accessorKey:'write_lag',
header: gettext('Write Lag'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
size: 100,
minSize: 50,
},
{
accessor:'flush_lag',
Header: gettext('Flush Lag'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
width: 60
accessorKey:'flush_lag',
header: gettext('Flush Lag'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
size: 100,
minSize: 50,
},
{
accessor:'replay_lag',
Header: gettext('Replay Lag'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
width: 60
accessorKey:'replay_lag',
header: gettext('Replay Lag'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
size: 100,
minSize: 50,
},
{
accessor:'reply_time',
Header: gettext('Reply Time'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
width: 80
accessorKey:'reply_time',
header: gettext('Reply Time'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
size: 100,
minSize: 50,
}
];
const replicationSlotsColumns = [{
accessor: 'view_details',
Header: () => null,
sortable: false,
resizable: false,
disableGlobalFilter: false,
disableResizing: true,
width: 35,
maxWidth: 35,
minWidth: 35,
accessorKey: 'view_details',
header: () => null,
enableSorting: false,
enableResizing: false,
enableFilters: true,
size: 35,
maxSize: 35,
minSize: 35,
id: 'btn-details',
Cell: getExpandCell({
cell: getExpandCell({
title: gettext('View details')
}),
},
{
accessor: 'active_pid',
Header: gettext('Active PID'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
width: 50,
accessorKey: 'active_pid',
header: gettext('Active PID'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
size: 50,
minSize: 50,
},
{
accessor: 'slot_name',
Header: gettext('Slot Name'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
width: 200,
accessorKey: 'slot_name',
header: gettext('Slot Name'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
size: 200,
minSize: 50,
},
{
accessor:'active',
Header: gettext('Active'),
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 26,
width: 60,
Cell: getSwitchCell(),
accessorKey:'active',
header: gettext('Active'),
enableSorting: true,
enableResizing: true,
enableFilters: true,
size: 50,
minSize: 50,
cell: getSwitchCell(),
}
];

View File

@ -77,25 +77,25 @@ export default function CPU({preferences, sid, did, pageVisible, enablePoll=true
const tableHeader = [
{
Header: gettext('PID'),
accessor: 'pid',
sortable: true,
resizable: true,
disableGlobalFilter: false,
header: gettext('PID'),
accessorKey: 'pid',
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
{
Header: gettext('Name'),
accessor: 'name',
sortable: true,
resizable: true,
disableGlobalFilter: false,
header: gettext('Name'),
accessorKey: 'name',
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
{
Header: gettext('CPU usage'),
accessor: 'cpu_usage',
sortable: true,
resizable: true,
disableGlobalFilter: false,
header: gettext('CPU usage'),
accessorKey: 'cpu_usage',
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
];

View File

@ -75,32 +75,32 @@ export default function Memory({preferences, sid, did, pageVisible, enablePoll=t
const tableHeader = [
{
Header: gettext('PID'),
accessor: 'pid',
sortable: true,
resizable: true,
disableGlobalFilter: false,
header: gettext('PID'),
accessorKey: 'pid',
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
{
Header: gettext('Name'),
accessor: 'name',
sortable: true,
resizable: true,
disableGlobalFilter: false,
header: gettext('Name'),
accessorKey: 'name',
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
{
Header: gettext('Memory usage'),
accessor: 'memory_usage',
sortable: true,
resizable: true,
disableGlobalFilter: false,
header: gettext('Memory usage'),
accessorKey: 'memory_usage',
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
{
Header: gettext('Memory bytes'),
accessor: 'memory_bytes',
sortable: true,
resizable: true,
disableGlobalFilter: false,
header: gettext('Memory bytes'),
accessorKey: 'memory_bytes',
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
];

View File

@ -178,44 +178,44 @@ export default function Storage({preferences, sid, did, pageVisible, enablePoll=
const tableHeader = [
{
Header: gettext('File system'),
accessor: 'file_system',
header: gettext('File system'),
accessorKey: 'file_system',
},
{
Header: gettext('File system type'),
accessor: 'file_system_type',
header: gettext('File system type'),
accessorKey: 'file_system_type',
},
{
Header: gettext('Mount point'),
accessor: 'mount_point',
header: gettext('Mount point'),
accessorKey: 'mount_point',
},
{
Header: gettext('Drive letter'),
accessor: 'drive_letter',
header: gettext('Drive letter'),
accessorKey: 'drive_letter',
},
{
Header: gettext('Total space'),
accessor: 'total_space',
header: gettext('Total space'),
accessorKey: 'total_space',
},
{
Header: gettext('Used space'),
accessor: 'used_space',
header: gettext('Used space'),
accessorKey: 'used_space',
},
{
Header: gettext('Free space'),
accessor: 'free_space',
header: gettext('Free space'),
accessorKey: 'free_space',
},
{
Header: gettext('Total inodes'),
accessor: 'total_inodes',
header: gettext('Total inodes'),
accessorKey: 'total_inodes',
},
{
Header: gettext('Used inodes'),
accessor: 'used_inodes',
header: gettext('Used inodes'),
accessorKey: 'used_inodes',
},
{
Header: gettext('Free inodes'),
accessor: 'free_inodes',
header: gettext('Free inodes'),
accessorKey: 'free_inodes',
},
];

View File

@ -46,7 +46,7 @@ export default function SectionContainer({title, titleExtras, children, style})
{titleExtras}
</div>
</Box>
<Box height="100%" display="flex" flexDirection="column">
<Box height="100%" display="flex" flexDirection="column" minHeight={0}>
{children}
</Box>
</Box>

View File

@ -99,7 +99,8 @@ export default function Processes() {
const classes = useStyles();
const pgAdmin = usePgAdmin();
const [tableData, setTableData] = React.useState([]);
const [selectedRows, setSelectedRows] = React.useState([]);
const [selectedRows, setSelectedRows] = React.useState({});
const selectedRowIDs = useMemo(()=>Object.keys(selectedRows).filter((k)=>selectedRows[k]), [selectedRows]);
const onViewDetailsClick = useCallback((p)=>{
const panelTitle = gettext('Process Watcher - %s', p.type_desc);
@ -123,7 +124,7 @@ export default function Processes() {
row: PropTypes.any,
};
const CancelCell = ({ row }) => {
const CancelCell = ({row}) => {
return (
<PgIconButton
size="xs"
@ -170,93 +171,91 @@ export default function Processes() {
StatusCell.propTypes = cellPropTypes;
return [{
accessor: 'stop_process',
Header: () => null,
sortable: false,
resizable: false,
disableGlobalFilter: true,
width: 35,
maxWidth: 35,
minWidth: 35,
header: () => null,
enableSorting: false,
enableResizing: false,
enableFilters: false,
size: 35,
maxSize: 35,
minSize: 35,
id: 'btn-stop',
Cell: CancelCell,
cell: CancelCell,
},
{
accessor: 'view_details',
Header: () => null,
sortable: false,
resizable: false,
disableGlobalFilter: true,
width: 35,
maxWidth: 35,
minWidth: 35,
header: () => null,
enableSorting: false,
enableResizing: false,
enableFilters: false,
size: 35,
maxSize: 35,
minSize: 35,
id: 'btn-logs',
Cell: LogsCell,
cell: LogsCell,
},
{
Header: gettext('PID'),
accessor: 'utility_pid',
sortable: true,
resizable: false,
header: gettext('PID'),
accessorKey: 'utility_pid',
enableSorting: true,
enableResizing: false,
width: 70,
minWidth: 70,
disableGlobalFilter: false,
enableFilters: true,
},
{
Header: gettext('Type'),
accessor: (row)=>row.details?.type,
sortable: true,
resizable: true,
header: gettext('Type'),
accessorFn: (row)=>row.details?.type,
enableSorting: true,
enableResizing: true,
width: 100,
minWidth: 70,
disableGlobalFilter: false,
enableFilters: true,
},
{
Header: gettext('Server'),
accessor: (row)=>row.details?.server,
sortable: true,
resizable: true,
header: gettext('Server'),
accessorFn: (row)=>row.details?.server,
enableSorting: true,
enableResizing: true,
width: 200,
minWidth: 120,
disableGlobalFilter: false,
enableFilters: true,
},
{
Header: gettext('Object'),
accessor: (row)=>row.details?.object,
sortable: true,
resizable: true,
header: gettext('Object'),
accessorFn: (row)=>row.details?.object,
enableSorting: true,
enableResizing: true,
width: 200,
minWidth: 120,
disableGlobalFilter: false,
enableFilters: true,
},
{
id: 'stime',
Header: gettext('Start Time'),
sortable: true,
resizable: true,
disableGlobalFilter: true,
header: gettext('Start Time'),
enableSorting: true,
enableResizing: true,
enableFilters: false,
width: 150,
minWidth: 150,
accessor: (row)=>(new Date(row.stime)),
Cell: ({row})=>(new Date(row.original.stime).toLocaleString()),
accessorFn: (row)=>(new Date(row.stime)),
cell: (info)=>(info.getValue().toLocaleString()),
},
{
Header: gettext('Status'),
sortable: true,
resizable: false,
disableGlobalFilter: false,
header: gettext('Status'),
enableSorting: true,
enableResizing: false,
enableFilters: true,
width: 120,
minWidth: 120,
accessor: (row)=>ProcessStateTextAndColor[row.process_state][0],
accessorFn: (row)=>ProcessStateTextAndColor[row.process_state][0],
dataClassName: classes.noPadding,
Cell: StatusCell,
cell: StatusCell,
},
{
Header: gettext('Time Taken (sec)'),
accessor: 'execution_time',
sortable: true,
resizable: true,
disableGlobalFilter: true,
header: gettext('Time Taken (sec)'),
accessorKey: 'execution_time',
enableSorting: true,
enableResizing: true,
enableFilters: false,
}];
}, []);
@ -281,10 +280,10 @@ export default function Processes() {
columns={columns}
data={tableData}
sortOptions={[{id: 'stime', desc: true}]}
getSelectedRows={(rows)=>{setSelectedRows(rows);}}
isSelectRow={true}
selectedRows={selectedRows}
setSelectedRows={setSelectedRows}
hasSelectRow={true}
tableProps={{
autoResetSelectedRows: false,
getRowId: (row)=>{
return row.id;
}
@ -299,10 +298,11 @@ export default function Processes() {
title={gettext('Acknowledge and Remove')}
onClick={() => {
pgAdmin.Browser.notifier.confirm(gettext('Remove Processes'), gettext('Are you sure you want to remove the selected processes?'), ()=>{
pgAdmin.Browser.BgProcessManager.acknowledge(selectedRows.map((p)=>p.original.id));
pgAdmin.Browser.BgProcessManager.acknowledge(selectedRowIDs);
setSelectedRows({});
});
}}
disabled={selectedRows.length <= 0}
disabled={selectedRowIDs.length <= 0}
></PgIconButton>
<PgIconButton
icon={<HelpIcon style={{height: '1.4rem'}}/>}

View File

@ -83,29 +83,30 @@ function Dependencies({ nodeData, nodeItem, node, treeNodeInfo, isActive, isStal
let columns = [
{
Header: 'Type',
accessor: 'type',
sortable: true,
resizable: true,
disableGlobalFilter: false,
Cell: ({row})=>{
return pgAdmin.Browser.Nodes?.[row.original.type]?.label??row.original.type;
header: 'Type',
accessorKey: 'type',
enableSorting: true,
enableResizing: true,
enableFilters: true,
cell: (info)=>{
const type = info.getValue();
return pgAdmin.Browser.Nodes?.[type]?.label ?? type;
}
},
{
Header: 'Name',
accessor: 'name',
sortable: true,
resizable: true,
disableGlobalFilter: false,
header: 'Name',
accessorKey: 'name',
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
{
Header: 'Restriction',
accessor: 'field',
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 280,
header: 'Restriction',
accessorKey: 'field',
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 280,
},
];

View File

@ -83,29 +83,30 @@ function Dependents({ nodeData, nodeItem, node, treeNodeInfo, isActive, isStale,
let columns = [
{
Header: 'Type',
accessor: 'type',
sortable: true,
resizable: true,
disableGlobalFilter: false,
Cell: ({row})=>{
return pgAdmin.Browser.Nodes?.[row.original.type]?.label??row.original.type;
header: 'Type',
accessorKey: 'type',
enableSorting: true,
enableResizing: true,
enableFilters: true,
cell: (info)=>{
const type = info.getValue();
return pgAdmin.Browser.Nodes?.[type]?.label ?? type;
}
},
{
Header: 'Name',
accessor: 'name',
sortable: true,
resizable: true,
disableGlobalFilter: false,
header: 'Name',
accessorKey: 'name',
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
{
Header: 'Restriction',
accessor: 'field',
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 280,
header: 'Restriction',
accessorKey: 'field',
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 280,
},
];

View File

@ -23,7 +23,7 @@ import EmptyPanelMessage from '../../static/js/components/EmptyPanelMessage';
import Loader from 'sources/components/Loader';
import { evalFunc } from '../../static/js/utils';
import { usePgAdmin } from '../../static/js/BrowserComponent';
import { getSwitchCell } from '../../static/js/components/PgTable';
import { getSwitchCell } from '../../static/js/components/PgReactTableStyled';
const useStyles = makeStyles((theme) => ({
emptyPanel: {
@ -81,48 +81,38 @@ export default function CollectionNodeProperties({
const [data, setData] = React.useState([]);
const [infoMsg, setInfoMsg] = React.useState('Please select an object in the tree view.');
const [selectedObject, setSelectedObject] = React.useState([]);
const [selectedObject, setSelectedObject] = React.useState({});
const [loaderText, setLoaderText] = React.useState('');
const schemaRef = React.useRef();
const [pgTableColumns, setPgTableColumns] = React.useState([
{
Header: 'properties',
accessor: 'Properties',
sortable: true,
resizable: true,
disableGlobalFilter: false,
header: 'properties',
accessorKey: 'Properties',
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
{
Header: 'value',
accessor: 'value',
sortable: true,
resizable: true,
disableGlobalFilter: false,
header: 'value',
accessorKey: 'value',
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
]);
const getTableSelectedRows = (selRows) => {
setSelectedObject(selRows);
};
const onDrop = (type) => {
let selRowModels = selectedObject,
selRows = [],
let selRows = [],
selItem = pgAdmin.Browser.tree.selected(),
selectedItemData = selItem ? pgAdmin.Browser.tree.itemData(selItem) : null,
selNode = selectedItemData && pgAdmin.Browser.Nodes[selectedItemData._type],
url, msg, title;
if (selNode?.type == 'coll-constraints') {
// In order to identify the constraint type, the type should be passed to the server
selRows = selRowModels.map((row) => ({
id: row.original.oid,
_type: row.original._type,
}));
} else {
selRows = selRowModels.map((row) => row.original[schemaRef.current.idAttribute]);
}
selRows = Object.keys(selectedObject).map((i)=>(selNode?.type == 'coll-constraints' ? {
id: data[i].oid,
_type: data[i]._type,
} : data[i][schemaRef.current.idAttribute]));
if (selRows.length === 0) {
pgAdmin.Browser.notifier.alert(
@ -166,6 +156,7 @@ export default function CollectionNodeProperties({
}
pgAdmin.Browser.tree.refresh(selItem);
setIsStale(true);
setSelectedObject({});
})
.catch(function (error) {
pgAdmin.Browser.notifier.alert(
@ -209,22 +200,20 @@ export default function CollectionNodeProperties({
if (node.columns.indexOf(field.id) > -1) {
if (field.label.indexOf('?') > -1) {
column = {
Header: field.label,
accessor: field.id,
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 0,
Cell: getSwitchCell()
header: field.label,
accessorKey: field.id,
enableSorting: true,
enableResizing: true,
enableFilters: true,
cell: getSwitchCell()
};
} else {
column = {
Header: field.label,
accessor: field.id,
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 0,
header: field.label,
accessorKey: field.id,
enableSorting: true,
enableResizing: true,
enableFilters: true,
};
}
tableColumns.push(column);
@ -233,12 +222,11 @@ export default function CollectionNodeProperties({
}else{
node.columns.forEach((field) => {
column = {
Header: field,
accessor: field,
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 0,
header: field,
accessorKey: field,
enableSorting: true,
enableResizing: true,
enableFilters: true,
};
tableColumns.push(column);
});
@ -283,7 +271,7 @@ export default function CollectionNodeProperties({
onDrop('drop');
}}
disabled={
(selectedObject.length > 0)
(Object.keys(selectedObject).length > 0)
? !canDrop
: true
}
@ -296,7 +284,7 @@ export default function CollectionNodeProperties({
onDrop('dropCascade');
}}
disabled={
(selectedObject.length > 0)
(Object.keys(selectedObject).length > 0)
? !canDropCascade
: true
}
@ -309,7 +297,7 @@ export default function CollectionNodeProperties({
onDrop('dropForce');
}}
disabled={
(selectedObject.length > 0)
(Object.keys(selectedObject).length > 0)
? !canDropForce
: true
}
@ -325,14 +313,15 @@ export default function CollectionNodeProperties({
{data.length > 0 ?
(
<PgTable
isSelectRow={!('catalog' in treeNodeInfo) && (nodeData.label !== 'Catalogs') && _.isUndefined(node?.canSelect)}
hasSelectRow={!('catalog' in treeNodeInfo) && (nodeData.label !== 'Catalogs') && _.isUndefined(node?.canSelect)}
CustomHeader={CustomHeader}
className={classes.autoResizer}
columns={pgTableColumns}
data={data}
type={'panel'}
isSearch={false}
getSelectedRows={getTableSelectedRows}
selectedRows={selectedObject}
setSelectedRows={setSelectedObject}
/>
)
:

View File

@ -60,29 +60,29 @@ function getColumn(data, singleLineStatistics, prettifyFields=[]) {
if (!_.isUndefined(data)) {
data.forEach((row) => {
columns.push({
Header: row.name,
accessor: row.name,
sortable: true,
resizable: true,
disableGlobalFilter: false,
header: row.name,
accessorKey: row.name,
enableSorting: true,
enableResizing: true,
enableFilters: true,
});
});
}
} else {
columns = [
{
Header: gettext('Statistics'),
accessor: 'name',
sortable: true,
resizable: true,
disableGlobalFilter: false,
header: gettext('Statistics'),
accessorKey: 'name',
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
{
Header: 'Value',
accessor: 'value',
sortable: false,
resizable: true,
disableGlobalFilter: false,
header: 'Value',
accessorKey: 'value',
enableSorting: false,
enableResizing: true,
enableFilters: true,
},
];
}
@ -153,18 +153,18 @@ function Statistics({ nodeData, nodeItem, node, treeNodeInfo, isActive, isStale,
const [loaderText, setLoaderText] = React.useState('');
const [columns, setColumns] = React.useState([
{
Header: 'Statictics',
accessor: 'name',
sortable: true,
resizable: true,
disableGlobalFilter: false,
header: 'Statictics',
accessorKey: 'name',
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
{
Header: 'Value',
accessor: 'value',
sortable: true,
resizable: true,
disableGlobalFilter: false,
header: 'Value',
accessorKey: 'value',
enableSorting: true,
enableResizing: true,
enableFilters: true,
},
]);
const pgAdmin = usePgAdmin();

View File

@ -18,8 +18,15 @@ import { MappedCellControl } from './MappedControl';
import DragIndicatorRoundedIcon from '@mui/icons-material/DragIndicatorRounded';
import EditRoundedIcon from '@mui/icons-material/EditRounded';
import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded';
import { useTable, useFlexLayout, useResizeColumns, useSortBy, useExpanded, useGlobalFilter } from 'react-table';
import clsx from 'clsx';
import {
useReactTable,
getCoreRowModel,
getSortedRowModel,
getFilteredRowModel,
getExpandedRowModel,
flexRender,
} from '@tanstack/react-table';
import PropTypes from 'prop-types';
import _ from 'lodash';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
@ -35,6 +42,7 @@ import { useIsMounted } from '../custom_hooks';
import { InputText } from '../components/FormComponents';
import { usePgAdmin } from '../BrowserComponent';
import { requestAnimationAndFocus } from '../utils';
import { PgReactTable, PgReactTableBody, PgReactTableCell, PgReactTableHeader, PgReactTableRow, PgReactTableRowContent, PgReactTableRowExpandContent } from '../components/PgReactTableStyled';
const useStyles = makeStyles((theme)=>({
grid: {
@ -73,10 +81,25 @@ const useStyles = makeStyles((theme)=>({
width: '100%',
},
table: {
borderSpacing: 0,
width: '100%',
overflow: 'auto',
backgroundColor: theme.otherVars.tableBg,
'&.pgrt-table': {
'& .pgrt-body':{
'& .pgrt-row': {
position: 'unset',
backgroundColor: theme.otherVars.emptySpaceBg,
'& .pgrt-row-content':{
'& .pgrd-row-cell': {
height: 'auto',
padding: theme.spacing(0.5),
'&.btn-cell, &.expanded-icon-cell': {
padding: '2px 0px'
}
}
},
}
}
}
},
tableRowHovered: {
position: 'relative',
@ -87,16 +110,6 @@ const useStyles = makeStyles((theme)=>({
opacity: 0.75,
}
},
tableCell: {
margin: 0,
padding: theme.spacing(0.5),
...theme.mixins.panelBorder.bottom,
...theme.mixins.panelBorder.right,
position: 'relative',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
},
tableCellHeader: {
fontWeight: theme.typography.fontWeightBold,
padding: theme.spacing(1, 0.5),
@ -111,6 +124,7 @@ const useStyles = makeStyles((theme)=>({
},
btnReorder: {
cursor: 'move',
padding: '4px 2px',
},
resizer: {
display: 'inline-block',
@ -124,9 +138,7 @@ const useStyles = makeStyles((theme)=>({
touchAction: 'none',
},
expandedForm: {
borderTopWidth: theme.spacing(0.5),
borderStyle: 'solid ',
borderColor: theme.palette.grey[400],
border: '1px solid '+theme.palette.grey[400],
},
expandedIconCell: {
backgroundColor: theme.palette.grey[400],
@ -134,49 +146,6 @@ const useStyles = makeStyles((theme)=>({
}
}));
function DataTableHeader({headerGroups, viewHelperProps, schema}) {
const classes = useStyles();
/* Using ref so that schema variable is not frozen in columns closure */
const schemaRef = useRef(schema);
const sortIcon = (isDesc) => {
return isDesc ? ' 🔽' : ' 🔼';
};
return (
<div className={classes.tableContentWidth}>
{headerGroups.map((headerGroup, hi) => (
<div key={hi} {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column, ci) => {
let {modeSupported} = column.field ? getFieldMetaData(column.field, schemaRef.current, {}, viewHelperProps) : {modeSupported: true};
return( modeSupported &&
<div key={ci} {...column.getHeaderProps()}>
<div {...(column.sortable ? column.getSortByToggleProps() : {})} className={clsx(classes.tableCell, classes.tableCellHeader)}>
{column.render('Header')}
<span>
{column.isSorted ? sortIcon(column.isSortedDesc) : ''}
</span>
</div>
{!column.disableResizing &&
<div
{...column.getResizerProps()}
className={classes.resizer}
/>}
</div>
);
})}
</div>
))}
</div>
);
}
DataTableHeader.propTypes = {
headerGroups: PropTypes.array.isRequired,
viewHelperProps: PropTypes.object.isRequired,
schema: CustomPropTypes.schemaUI.isRequired,
};
function DataTableRow({index, row, totalRows, isResizing, isHovered, schema, schemaRef, accessPath, moveRow, setHoverIndex, viewHelperProps}) {
const classes = useStyles();
const [key, setKey] = useState(false);
@ -188,7 +157,7 @@ function DataTableRow({index, row, totalRows, isResizing, isHovered, schema, sch
* If table data changes, then react-table re-renders the complete tables
* We can avoid re-render by if row data is not changed
*/
let depsMap = _.values(row.values, Object.keys(row.values).filter((k)=>!k.startsWith('btn')));
let depsMap = _.values(row.original, Object.keys(row.original).filter((k)=>!k.startsWith('btn')));
const externalDeps = useMemo(()=>{
let retVal = [];
/* Calculate the fields which depends on the current field
@ -284,37 +253,30 @@ function DataTableRow({index, row, totalRows, isResizing, isHovered, schema, sch
/* External deps values are from top schema sess data */
depsMap = depsMap.concat(externalDeps.map((source)=>_.get(schemaRef.current.top?.sessData, source)));
depsMap = depsMap.concat([totalRows, row.isExpanded, key, isResizing, isHovered]);
depsMap = depsMap.concat([totalRows, row.getIsExpanded(), key, isResizing, isHovered]);
drag(dragHandleRef);
drop(rowRef);
return useMemo(()=>
<div {...row.getRowProps()} ref={rowRef} data-handler-id={handlerId}
className={isHovered ? classes.tableRowHovered : null}
data-test='data-table-row'
>
{row.cells.map((cell, ci) => {
let classNames = [classes.tableCell];
<PgReactTableRowContent ref={rowRef} row={row} data-handler-id={handlerId} className={isHovered ? classes.tableRowHovered : null} data-test='data-table-row' style={{position: 'initial'}}>
{row.getVisibleCells().map((cell) => {
let {modeSupported} = cell.column.field ? getFieldMetaData(cell.column.field, schemaRef.current, {}, viewHelperProps) : {modeSupported: true};
let {modeSupported} = cell.column.field? getFieldMetaData(cell.column.field, schemaRef.current, {}, viewHelperProps) : {modeSupported: true};
const content = flexRender(cell.column.columnDef.cell, {
key: cell.column.columnDef.cell.type,
...cell.getContext(),
reRenderRow: ()=>{setKey((currKey)=>!currKey);}
});
if(typeof(cell.column.id) == 'string' && cell.column.id.startsWith('btn-')) {
classNames.push(classes.btnCell);
}
if(cell.column.id == 'btn-edit' && row.isExpanded) {
classNames.push(classes.expandedIconCell);
}
return (modeSupported &&
<div ref={cell.column.id == 'btn-reorder' ? dragHandleRef : null} key={ci} {...cell.getCellProps()} className={clsx(classNames)}>
{cell.render('Cell', {
reRenderRow: ()=>{setKey((currKey)=>!currKey);}
})}
</div>
<PgReactTableCell cell={cell} row={row} key={cell.id} ref={cell.column.id == 'btn-reorder' ? dragHandleRef : null}>
{content}
</PgReactTableCell>
);
})}
<div className='hover-overlay'></div>
</div>, depsMap);
</PgReactTableRowContent>, depsMap);
}
export function DataGridHeader({label, canAdd, onAddClick, canSearch, onSearchTextChange}) {
@ -355,7 +317,7 @@ DataGridHeader.propTypes = {
onSearchTextChange: PropTypes.func,
};
export default function DataGridView({
function DataGridView({
value, viewHelperProps, schema, accessPath, dataDispatch, containerClassName,
fixedRows, ...props}) {
const classes = useStyles();
@ -364,76 +326,77 @@ export default function DataGridView({
const [hoverIndex, setHoverIndex] = useState();
const newRowIndex = useRef();
const pgAdmin = usePgAdmin();
const [searchVal, setSearchVal] = useState('');
/* Using ref so that schema variable is not frozen in columns closure */
const schemaRef = useRef(schema);
let columns = useMemo(
const columns = useMemo(
()=>{
let cols = [];
if(props.canReorder) {
let colInfo = {
Header: <>&nbsp;</>,
header: <>&nbsp;</>,
id: 'btn-reorder',
accessor: ()=>{/*This is intentional (SonarQube)*/},
disableResizing: true,
sortable: false,
accessorFn: ()=>{/*This is intentional (SonarQube)*/},
enableResizing: false,
enableSorting: false,
dataType: 'reorder',
width: 26,
minWidth: 26,
maxWidth: 26,
Cell: ()=>{
size: 36,
maxSize: 26,
minSize: 26,
cell: ()=>{
return <div className={classes.btnReorder}>
<DragIndicatorRoundedIcon fontSize="small" />
</div>;
}
};
colInfo.Cell.displayName = 'Cell';
colInfo.cell.displayName = 'Cell';
cols.push(colInfo);
}
if(props.canEdit) {
let colInfo = {
Header: <>&nbsp;</>,
header: <>&nbsp;</>,
id: 'btn-edit',
accessor: ()=>{/*This is intentional (SonarQube)*/},
disableResizing: true,
sortable: false,
accessorFn: ()=>{/*This is intentional (SonarQube)*/},
enableResizing: false,
enableSorting: false,
dataType: 'edit',
width: 26,
minWidth: 26,
maxWidth: 26,
Cell: ({row})=>{
size: 26,
maxSize: 26,
minSize: 26,
cell: ({row})=>{
let canEditRow = true;
if(props.canEditRow) {
canEditRow = evalFunc(schemaRef.current, props.canEditRow, row.original || {});
canEditRow = evalFunc(schemaRef.current, props.canEditRow, row || {});
}
return <PgIconButton data-test="expand-row" title={gettext('Edit row')} icon={<EditRoundedIcon fontSize="small" />} className={classes.gridRowButton}
onClick={()=>{
row.toggleRowExpanded(!row.isExpanded);
row.toggleExpanded();
}} disabled={!canEditRow}
/>;
}
};
colInfo.Cell.displayName = 'Cell';
colInfo.Cell.propTypes = {
colInfo.cell.displayName = 'Cell';
colInfo.cell.propTypes = {
row: PropTypes.object.isRequired,
};
cols.push(colInfo);
}
if(props.canDelete) {
let colInfo = {
Header: <>&nbsp;</>,
header: <>&nbsp;</>,
id: 'btn-delete',
accessor: ()=>{/*This is intentional (SonarQube)*/},
disableResizing: true,
sortable: false,
accessorFn: ()=>{/*This is intentional (SonarQube)*/},
enableResizing: false,
enableSorting: false,
dataType: 'delete',
width: 26,
minWidth: 26,
maxWidth: 26,
Cell: ({row}) => {
size: 26,
maxSize: 26,
minSize: 26,
cell: ({row}) => {
let canDeleteRow = true;
if(props.canDeleteRow) {
canDeleteRow = evalFunc(schemaRef.current, props.canDeleteRow, row.original || {});
canDeleteRow = evalFunc(schemaRef.current, props.canDeleteRow, row || {});
}
return (
@ -449,7 +412,7 @@ export default function DataGridView({
};
if (props.onDelete){
props.onDelete(row.original || {}, deleteRow);
props.onDelete(row || {}, deleteRow);
} else {
pgAdmin.Browser.notifier.confirm(
props.customDeleteTitle || gettext('Delete Row'),
@ -464,8 +427,8 @@ export default function DataGridView({
);
}
};
colInfo.Cell.displayName = 'Cell';
colInfo.Cell.propTypes = {
colInfo.cell.displayName = 'Cell';
colInfo.cell.propTypes = {
row: PropTypes.object.isRequired,
};
cols.push(colInfo);
@ -482,28 +445,29 @@ export default function DataGridView({
}).map((field)=>{
let widthParms = {};
if(field.width) {
widthParms.width = field.width;
widthParms.minWidth = field.width;
widthParms.size = field.width;
widthParms.minSize = field.width;
} else {
widthParms.width = 75;
widthParms.minWidth = 75;
widthParms.size = 75;
widthParms.minSize = 75;
}
if(field.minWidth) {
widthParms.minWidth = field.minWidth;
widthParms.minSize = field.minWidth;
}
if(field.maxWidth) {
widthParms.maxWidth = field.maxWidth;
widthParms.maxSize = field.maxWidth;
}
widthParms.disableResizing = Boolean(field.disableResizing);
widthParms.enableResizing = _.isUndefined(field.enableResizing) ? true : Boolean(field.enableResizing);
let colInfo = {
Header: field.label||<>&nbsp;</>,
accessor: field.id,
header: field.label||<>&nbsp;</>,
accessorKey: field.id,
field: field,
disableResizing: false,
sortable: true,
enableResizing: true,
enableSorting: false,
...widthParms,
Cell: ({value, row, ...other}) => {
cell: ({row, ...other}) => {
const value = other.getValue();
/* Make sure to take the latest field info from schema */
field = _.find(schemaRef.current.fields, (f)=>f.id==field.id) || field;
@ -514,7 +478,7 @@ export default function DataGridView({
}
return modeSupported && <MappedCellControl rowIndex={row.index} value={value}
row={row.original} {...field}
row={row} {...field}
readonly={!editable}
disabled={disabled}
visible={true}
@ -537,8 +501,8 @@ export default function DataGridView({
/>;
},
};
colInfo.Cell.displayName = 'Cell';
colInfo.Cell.propTypes = {
colInfo.cell.displayName = 'Cell';
colInfo.cell.propTypes = {
row: PropTypes.object.isRequired,
value: PropTypes.any,
onCellChange: PropTypes.func,
@ -567,35 +531,33 @@ export default function DataGridView({
});
}, [props.canAddRow, rows?.length]);
const defaultColumn = useMemo(()=>({
}), []);
const columnVisibility = useMemo(()=>{
const ret = {};
let tablePlugins = [
useGlobalFilter,
useFlexLayout,
useResizeColumns,
useSortBy,
useExpanded,
];
columns.forEach(column => {
let {modeSupported} = column.field ? getFieldMetaData(column.field, schemaRef.current, {}, viewHelperProps) : {modeSupported: true};
ret[column.id] = modeSupported;
});
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
setGlobalFilter,
} = useTable(
{
columns,
data: value,
defaultColumn,
manualSortBy: true,
autoResetSortBy: false,
autoResetExpanded: false,
return ret;
}, [columns, viewHelperProps]);
const table = useReactTable({
columns,
data: value,
autoResetAll: false,
state: {
globalFilter: searchVal,
columnVisibility: columnVisibility,
},
...tablePlugins,
);
columnResizeMode: 'onChange',
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getExpandedRowModel: getExpandedRowModel(),
});
const rows = table.getRowModel().rows;
useEffect(()=>{
let rowsPromise = fixedRows;
@ -617,11 +579,13 @@ export default function DataGridView({
useEffect(()=>{
if(newRowIndex.current >= 0) {
rows[newRowIndex.current]?.toggleRowExpanded(true);
rows[newRowIndex.current]?.toggleExpanded(true);
newRowIndex.current = null;
}
}, [rows?.length]);
const tableRef = useRef();
const moveRow = (dragIndex, hoverIndex) => {
dataDispatch({
type: SCHEMA_STATE_ACTIONS.MOVE_ROW,
@ -631,7 +595,7 @@ export default function DataGridView({
});
};
const isResizing = _.flatMap(headerGroups, headerGroup => headerGroup.headers.map(col=>col.isResizing)).includes(true);
const isResizing = _.flatMap(table.getHeaderGroups(), headerGroup => headerGroup.headers.map(header=>header.column.getIsResizing())).includes(true);
if(!props.visible) {
return <></>;
@ -643,36 +607,46 @@ export default function DataGridView({
{(props.label || props.canAdd) && <DataGridHeader label={props.label} canAdd={props.canAdd} onAddClick={onAddClick}
canSearch={props.canSearch}
onSearchTextChange={(value)=>{
setGlobalFilter(value || undefined);
setSearchVal(value || undefined);
}}
/>}
<DndProvider backend={HTML5Backend}>
<div {...getTableProps(()=>({style: {minWidth: 'unset'}}))} className={classes.table} data-test="data-grid-view">
<DataTableHeader headerGroups={headerGroups} viewHelperProps={viewHelperProps} schema={schema} />
<div {...getTableBodyProps()} className={classes.tableContentWidth}>
<PgReactTable ref={tableRef} table={table} data-test="data-grid-view" tableClassName={classes.table}>
<PgReactTableHeader table={table} />
<PgReactTableBody>
{rows.map((row, i) => {
prepareRow(row);
return <React.Fragment key={row.index}>
<DataTableRow index={i} row={row} totalRows={rows.length} isResizing={isResizing}
return <PgReactTableRow key={row.index}>
<DataTableRow index={i} key={i} row={row} totalRows={rows.length} isResizing={isResizing}
schema={schemaRef.current} schemaRef={schemaRef} accessPath={accessPath.concat([row.index])}
moveRow={moveRow} isHovered={i == hoverIndex} setHoverIndex={setHoverIndex} viewHelperProps={viewHelperProps}/>
{props.canEdit && row.isExpanded &&
<FormView value={row.original} viewHelperProps={viewHelperProps} dataDispatch={dataDispatch}
schema={schemaRef.current} accessPath={accessPath.concat([row.index])} isNested={true} className={classes.expandedForm}
isDataGridForm={true} firstEleRef={(ele)=>{
requestAnimationAndFocus(ele);
}}/>
moveRow={moveRow} isHovered={i == hoverIndex} setHoverIndex={setHoverIndex} viewHelperProps={viewHelperProps}
/>
{props.canEdit &&
<PgReactTableRowExpandContent row={row}>
<FormView value={row.original} viewHelperProps={viewHelperProps} dataDispatch={dataDispatch}
schema={schemaRef.current} accessPath={accessPath.concat([row.index])} isNested={true} className={classes.expandedForm}
isDataGridForm={true} firstEleRef={(ele)=>{
requestAnimationAndFocus(ele);
}}/>
</PgReactTableRowExpandContent>
}
</React.Fragment>;
</PgReactTableRow>;
})}
</div>
</div>
</PgReactTableBody>
</PgReactTable>
</DndProvider>
</Box>
</Box>
);
}
export default function DataGridViewMoized({memoDeps, ...props}) {
return useMemo(()=><DataGridView {...props} />, memoDeps??[]);
}
DataGridViewMoized.propTypes = {
memoDeps: PropTypes.array,
};
DataGridView.propTypes = {
label: PropTypes.string,
value: PropTypes.array,

View File

@ -289,10 +289,12 @@ export default function FormView({
canDelete = false;
}
const props = {
key: field.id, value: value[field.id] || [], viewHelperProps: viewHelperProps,
const ctrlProps = {
key: field.id, ...field,
value: value[field.id] || [], viewHelperProps: viewHelperProps,
schema: field.schema, accessPath: accessPath.concat(field.id), dataDispatch: dataDispatch,
containerClassName: classes.controlRow, ...field, canAdd: canAdd, canReorder: canReorder,
containerClassName: classes.controlRow,
canAdd: canAdd, canReorder: canReorder,
canEdit: canEdit, canDelete: canDelete,
visible: visible, canAddRow: canAddRow, onDelete: field.onDelete, canSearch: field.canSearch,
expandEditOnAdd: field.expandEditOnAdd,
@ -301,9 +303,14 @@ export default function FormView({
};
if(CustomControl) {
tabs[group].push(<CustomControl {...props}/>);
tabs[group].push(<CustomControl {...ctrlProps}/>);
} else {
tabs[group].push(<DataGridView {...props}/>);
tabs[group].push(<DataGridView {...ctrlProps} memoDeps={[
JSON.stringify(ctrlProps.value),
ctrlProps.containerClassName,
ctrlProps.visible,
...(evalFunc(null, ctrlProps.deps) || []).map((dep)=>value[dep]),
]} />);
}
} else {
/* Its a form control */

View File

@ -254,7 +254,7 @@ MappedFormControl.propTypes = {
export const MappedCellControl = (props) => {
let newProps = { ...props };
let cellProps = evalFunc(null, newProps.cell, newProps.row);
let cellProps = evalFunc(null, newProps.cell, newProps.row.original);
if (typeof (cellProps) === 'object') {
newProps = {
...newProps,

View File

@ -751,22 +751,23 @@ function SchemaDialogView({
sessDispatch(dispatchPayload);
};
const stateUtils = useMemo(()=>({
dataDispatch: sessDispatchWithListener,
initOrigData: (path, value)=>{
if(path) {
let data = prepareData(value);
_.set(schema.origData, path, data);
sessDispatchWithListener({
type: SCHEMA_STATE_ACTIONS.SET_VALUE,
path: path,
value: data,
});
}
},
formResetKey: formResetKey,
formErr: formErr,
}), [formResetKey, formErr]);
const stateUtils = useMemo(()=>{
return {
dataDispatch: sessDispatchWithListener,
initOrigData: (path, value)=>{
if(path) {
let data = prepareData(value);
_.set(schema.origData, path, data);
sessDispatchWithListener({
type: SCHEMA_STATE_ACTIONS.SET_VALUE,
path: path,
value: data,
});
}
},
formResetKey: formResetKey,
formErr: formErr,
};}, [formResetKey, formErr.name, formErr.message]);
const getButtonIcon = () => {
if(props.customSaveBtnIconType == 'upload') {

View File

@ -347,6 +347,7 @@ function getFinalTheme(baseTheme) {
overflow: 'auto',
backgroundColor: baseTheme.palette.grey[400],
position: 'relative',
flexGrow: 1,
},
fontSourceCode: {
fontFamily: '"Source Code Pro", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',

View File

@ -0,0 +1,364 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2024, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React, { forwardRef } from 'react';
import { flexRender } from '@tanstack/react-table';
import { styled } from '@mui/styles';
import PropTypes from 'prop-types';
import { Switch } from '@mui/material';
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import { PgIconButton } from './Buttons';
import clsx from 'clsx';
import CustomPropTypes from '../custom_prop_types';
const StyledDiv = styled('div')(({theme})=>({
'&.pgrt': {
display: 'grid',
overflow: 'auto',
position: 'relative',
flexGrow: 1,
},
// by default the table has no outer border.
// the parent container has to take care of border.
'& .pgrt-table': {
borderSpacing: 0,
borderRadius: theme.shape.borderRadius,
display: 'grid',
gridAutoRows: 'max-content',
flexGrow: 1,
flexDirection: 'column',
'& .pgrt-header': {
position: 'sticky',
top: 0,
zIndex: 1,
'& .pgrt-header-row': {
height: '34px',
display: 'flex',
'& .pgrt-header-cell': {
position: 'relative',
fontWeight: theme.typography.fontWeightBold,
padding: theme.spacing(0.5),
textAlign: 'left',
alignContent: 'center',
backgroundColor: theme.otherVars.tableBg,
overflow: 'hidden',
...theme.mixins.panelBorder.bottom,
...theme.mixins.panelBorder.right,
'& .pgrt-header-resizer': {
display: 'inline-block',
width: '5px',
height: '100%',
position: 'absolute',
right: 0,
top: 0,
transform: 'translateX(50%)',
zIndex: 1,
cursor: 'col-resize',
}
}
}
},
'& .pgrt-body': {
position: 'relative',
flexGrow: 1,
minHeight: 0,
'& .pgrt-row': {
display: 'flex',
flexDirection: 'column',
position: 'absolute',
width: '100%',
'& .pgrt-row-content': {
display: 'flex',
minHeight: 0,
'& .pgrd-row-cell': {
margin: 0,
padding: theme.spacing(0.25, 0.5),
...theme.mixins.panelBorder.bottom,
...theme.mixins.panelBorder.right,
position: 'relative',
height: '30px',
display: 'flex',
alignItems: 'flex-start',
backgroundColor: theme.otherVars.tableBg,
'&.btn-cell': {
textAlign: 'center',
},
'&.expanded-icon-cell': {
backgroundColor: theme.palette.grey[400],
borderBottom: 'none',
},
'&.row-warning': {
backgroundColor: theme.palette.warning.main + '!important'
},
'&.row-alert': {
backgroundColor: theme.palette.error.main + '!important'
},
'&.cell-with-icon': {
paddingLeft: '1.8em',
borderRadius: 0,
backgroundPosition: '1%',
},
'& .pgrd-row-cell-content': {
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
userSelect: 'text',
width: '100%',
}
}
},
'& .pgrt-expanded-content': {
...theme.mixins.panelBorder.all,
margin: '8px',
flexGrow: 1,
}
}
}
}
}));
export const PgReactTableCell = forwardRef(({row, cell, children, className}, ref)=>{
let classNames = ['pgrd-row-cell'];
if (typeof (cell.column.id) == 'string' && cell.column.id.startsWith('btn-')) {
classNames.push('btn-cell');
}
if (cell.column.id == 'btn-edit' && row.getIsExpanded()) {
classNames.push('expanded-icon-cell');
}
if (row.original.row_type === 'warning') {
classNames.push('row-warning');
}
if (row.original.row_type === 'alert') {
classNames.push('row-alert');
}
if(row.original.icon && row.original.icon[cell.column.id]) {
classNames.push(row.original.icon[cell.column.id], 'cell-with-icon');
}
classNames.push(className);
return (
<div ref={ref} key={cell.id} style={{
flex: `var(--col-${cell.column.id}-size) 0 auto`,
width: `calc(var(--col-${cell.column.id}-size)*1px)`,
...(cell.column.columnDef.maxSize ? { maxWidth: `${cell.column.columnDef.maxSize}px` } : {})
}} role='cell'
className={clsx(...classNames)}
title={String(cell.getValue() ?? '')}>
<div className='pgrd-row-cell-content'>{children}</div>
</div>
);
});
PgReactTableCell.displayName = 'PgReactTableCell';
PgReactTableCell.propTypes = {
row: PropTypes.object,
cell: PropTypes.object,
children: CustomPropTypes.children,
className: PropTypes.any,
};
export const PgReactTableRow = forwardRef(({ children, className, ...props }, ref)=>{
return (
<div className={clsx('pgrt-row', className)} ref={ref} role="row" {...props}>
{children}
</div>
);
});
PgReactTableRow.displayName = 'PgReactTableRow';
PgReactTableRow.propTypes = {
children: CustomPropTypes.children,
className: PropTypes.any,
};
export const PgReactTableRowContent = forwardRef(({children, className, ...props}, ref)=>{
return (
<div className={clsx('pgrt-row-content', className)} ref={ref} {...props}>
{children}
</div>
);
});
PgReactTableRowContent.displayName = 'PgReactTableRowContent';
PgReactTableRowContent.propTypes = {
children: CustomPropTypes.children,
className: PropTypes.any,
};
export function PgReactTableRowExpandContent({row, children}) {
if(!row.getIsExpanded()) {
return <></>;
}
return (
<div className='pgrt-expanded-content' style={{ maxWidth: 'calc(var(--expand-width)*1px)' }}>
{children}
</div>
);
}
PgReactTableRowExpandContent.propTypes = {
row: PropTypes.object,
children: CustomPropTypes.children,
};
export function PgReactTableHeader({table}) {
return (
<div className='pgrt-header'>
{table.getHeaderGroups().map((headerGroup) => (
<div key={''} className='pgrt-header-row' style={{ }}>
{headerGroup.headers.map((header) => (
<div
key={header.id}
className='pgrt-header-cell'
style={{
flex: `var(--header-${header?.id}-size) 0 auto`,
width: `calc(var(--header-${header?.id}-size)*1px)`,
...(header.column.columnDef.maxSize ? { maxWidth: `${header.column.columnDef.maxSize}px` } : {}),
cursor: header.column.getCanSort() ? 'pointer' : 'initial',
}}
onClick={header.column.getCanSort() ? header.column.getToggleSortingHandler() : undefined}
>
<div>
{flexRender(header.column.columnDef.header, header.getContext())}
{header.column.getCanSort() && header.column.getIsSorted() &&
<span>
{header.column.getIsSorted() == 'desc' ?
<KeyboardArrowDownIcon style={{ fontSize: '1.2rem' }} />
: <KeyboardArrowUpIcon style={{ fontSize: '1.2rem' }} />}
</span>}
</div>
{header.column.getCanResize() && (
<div
onDoubleClick={() => header.column.resetSize()}
onMouseDown={header.getResizeHandler()}
onTouchStart={header.getResizeHandler()}
className='pgrt-header-resizer'
/>
)}
</div>
))}
</div>
))}
</div>
);
}
PgReactTableHeader.propTypes = {
table: PropTypes.object,
};
export function PgReactTableBody({children, style}) {
return (
<div className='pgrt-body' style={style}>
{children}
</div>
);
}
PgReactTableBody.propTypes = {
style: PropTypes.object,
children: CustomPropTypes.children,
};
export const PgReactTable = forwardRef(({children, table, rootClassName, tableClassName, ...props}, ref)=>{
const columns = table.getAllColumns();
// Render the UI for your table
const maxExpandWidth = (ref.current?.getBoundingClientRect().width ?? 430) - 30; //margin,scrollbar,etc.
const columnSizeVars = React.useMemo(() => {
const headers = table.getFlatHeaders();
const colSizes = {};
for (let i = 0; i < headers.length; i++) {
const header = headers[i];
colSizes[`--header-${header.id}-size`] = header.getSize();
colSizes[`--col-${header.column.id}-size`] = header.column.getSize();
}
return colSizes;
}, [columns, table.getState().columnSizingInfo]);
return (
<StyledDiv className={clsx('pgrt', rootClassName)} style={{'--expand-width': maxExpandWidth }} ref={ref} >
<div className={clsx('pgrt-table', tableClassName)} style={{ ...columnSizeVars }} {...props}>
{children}
</div>
</StyledDiv>
);
});
PgReactTable.displayName = 'PgReactTable';
PgReactTable.propTypes = {
table: PropTypes.object,
rootClassName: PropTypes.any,
tableClassName: PropTypes.any,
children: CustomPropTypes.children,
};
export function getExpandCell({ onClick, ...props }) {
const Cell = ({ row }) => {
const onClickFinal = (e) => {
e.preventDefault();
row.toggleExpanded();
onClick?.(row, e);
};
return (
<PgIconButton
size="xs"
icon={
row.getIsExpanded() ? (
<KeyboardArrowDownIcon />
) : (
<ChevronRightIcon />
)
}
noBorder
{...props}
onClick={onClickFinal}
aria-label={props.title}
/>
);
};
Cell.displayName = 'ExpandCell';
Cell.propTypes = {
title: PropTypes.string,
row: PropTypes.any,
};
return Cell;
}
const ReadOnlySwitch = styled(Switch)(({theme})=>({
opacity: 0.75,
'& .MuiSwitch-track': {
opacity: theme.palette.action.disabledOpacity,
}
}));
export function getSwitchCell() {
const Cell = ({ value }) => {
return <ReadOnlySwitch color="primary" checked={value} value={value} readOnly title={String(value)} />;
};
Cell.displayName = 'SwitchCell';
Cell.propTypes = {
value: PropTypes.any,
};
return Cell;
}

View File

@ -7,484 +7,244 @@
//
//////////////////////////////////////////////////////////////
import React from 'react';
import React, { useMemo, useRef } from 'react';
import {
useTable,
useRowSelect,
useSortBy,
useResizeColumns,
useFlexLayout,
useGlobalFilter,
useExpanded,
} from 'react-table';
import { VariableSizeList } from 'react-window';
import { makeStyles } from '@mui/styles';
import clsx from 'clsx';
useReactTable,
getCoreRowModel,
getSortedRowModel,
getFilteredRowModel,
getExpandedRowModel,
flexRender,
} from '@tanstack/react-table';
import { useVirtualizer } from '@tanstack/react-virtual';
import { styled } from '@mui/styles';
import PropTypes from 'prop-types';
import AutoSizer from 'react-virtualized-auto-sizer';
import { Checkbox, Box, Switch } from '@mui/material';
import { Checkbox, Box } from '@mui/material';
import { InputText } from './FormComponents';
import _ from 'lodash';
import gettext from 'sources/gettext';
import SchemaView from '../SchemaView';
import EmptyPanelMessage from './EmptyPanelMessage';
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import { PgIconButton } from './Buttons';
import { PgReactTable, PgReactTableBody, PgReactTableCell, PgReactTableHeader, PgReactTableRow, PgReactTableRowContent, PgReactTableRowExpandContent } from './PgReactTableStyled';
/* eslint-disable react/display-name */
const useStyles = makeStyles((theme) => ({
root: {
const ROW_HEIGHT = 30;
function TableRow({ index, style, schema, row, measureElement }) {
const [expandComplete, setExpandComplete] = React.useState(false);
const rowRef = React.useRef();
React.useEffect(() => {
if (rowRef.current) {
if (!expandComplete && rowRef.current.style.height == `${ROW_HEIGHT}px`) {
return;
}
measureElement(rowRef.current);
}
}, [row.getIsExpanded(), expandComplete]);
return (
<PgReactTableRow data-index={index} ref={rowRef} style={style} row={row}>
<PgReactTableRowContent>
{row.getVisibleCells().map((cell) => {
const content = flexRender(cell.column.columnDef.cell, cell.getContext());
return (
<PgReactTableCell cell={cell} row={row} key={cell.id}>
{content}
</PgReactTableCell>
);
})}
</PgReactTableRowContent>
<PgReactTableRowExpandContent row={row}>
<SchemaView
getInitData={() => Promise.resolve(row.original)}
viewHelperProps={{ mode: 'properties' }}
schema={schema}
showFooter={false}
onDataChange={() => { setExpandComplete(true); }}
/>
</PgReactTableRowExpandContent>
</PgReactTableRow>
);
}
TableRow.propTypes = {
index: PropTypes.number,
style: PropTypes.object,
row: PropTypes.object,
schema: PropTypes.object,
measureElement: PropTypes.func,
};
export function Table({ columns, data, hasSelectRow, schema, sortOptions, tableProps, searchVal, ...props }) {
const defaultColumn = React.useMemo(
() => ({
size: 150,
minSize: 100,
maxSize: 1200,
}),
[]
);
const finalColumns = useMemo(() => (hasSelectRow ? [{
id: 'selection',
header: ({ table }) => {
return (
<div style={{textAlign: 'center', minWidth: 20}}>
<Checkbox
color="primary"
checked={table.getIsAllRowsSelected()}
indeterminate={table.getIsSomeRowsSelected()}
onChange={table.getToggleAllRowsSelectedHandler()}
inputProps={{ 'aria-label': gettext('Select All Rows') }}
/>
</div>
);
},
cell: ({ row }) => (
<div style={{textAlign: 'center', minWidth: 20}}>
<Checkbox
color="primary"
checked={row.getIsSelected()}
indeterminate={row.getIsSomeSelected()}
disabled={!row.getCanSelect()}
onChange={row.getToggleSelectedHandler()}
inputProps={{ 'aria-label': gettext('Select Row') }}
/>
</div>
),
enableSorting: false,
enableResizing: false,
maxSize: 35,
}] : []).concat(
columns.filter((c)=>_.isUndefined(c.enableVisibility) ? true : c.enableVisibility)
), [hasSelectRow, columns]);
// Render the UI for your table
const tableRef = useRef();
const table = useReactTable({
columns: finalColumns,
data,
defaultColumn,
autoResetAll: false,
initialState: {
sorting: sortOptions || [],
},
state: {
rowSelection: props.selectedRows ?? {},
globalFilter: searchVal,
},
columnResizeMode: 'onChange',
onRowSelectionChange: props.setSelectedRows,
enableRowSelection: (row) => (hasSelectRow && (_.isUndefined(row.original.canDrop) ? true : row.original.canDrop)),
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getExpandedRowModel: getExpandedRowModel(),
...tableProps,
});
const rows = table.getRowModel().rows;
const virtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => tableRef.current,
estimateSize: () => ROW_HEIGHT,
measureElement:
typeof window !== 'undefined' &&
navigator.userAgent.indexOf('Firefox') === -1
? element => element?.getBoundingClientRect().height
: undefined,
overscan: 20,
});
return (
<PgReactTable ref={tableRef} table={table}>
<PgReactTableHeader table={table} />
{rows.length == 0 ?
<EmptyPanelMessage text={gettext('No rows found')} /> :
<PgReactTableBody style={{ height: virtualizer.getTotalSize() + 'px'}}>
{virtualizer.getVirtualItems().map((virtualRow) => {
const row = rows[virtualRow.index];
return <TableRow index={row.index} key={row.index} row={row} schema={schema}
measureElement={virtualizer.measureElement}
style={{
transform: `translateY(${virtualRow.start}px)`, //this should always be a `style` as it changes on scroll
}}
/>;
})}
</PgReactTableBody>}
</PgReactTable>
);
}
Table.propTypes = {
columns: PropTypes.array,
data: PropTypes.array,
hasSelectRow: PropTypes.bool,
schema: PropTypes.object,
sortOptions: PropTypes.arrayOf(PropTypes.object),
tableProps: PropTypes.object,
selectedRows: PropTypes.object,
setSelectedRows: PropTypes.func,
searchVal: PropTypes.string,
};
const StyledPgTableRoot = styled('div')(({theme})=>({
display: 'flex',
flexGrow: 1,
overflow: 'hidden',
flexDirection: 'column',
height: '100%',
'& .pgtable-header': {
display: 'flex',
flexDirection: 'column',
height: '100%',
...theme.mixins.panelBorder,
backgroundColor: theme.palette.background.default,
background: theme.palette.background.default,
padding: '8px 2px 4px',
'& .pgtable-search-input': {
minWidth: '300px'
},
},
autoResizerContainer: {
flexGrow: 1,
minHeight: 0
},
autoResizer: {
width: '100% !important',
},
fixedSizeList: {
direction: 'ltr',
overflowX: 'hidden !important',
overflow: 'overlay !important',
},
CustomHeader:{
marginTop: '8px',
marginLeft: '4px'
},
warning: {
backgroundColor: theme.palette.warning.main + '!important'
},
alert: {
backgroundColor: theme.palette.error.main + '!important'
},
searchInput: {
minWidth: '300px'
},
tableContainer: {
overflowX: 'auto',
'& .pgtable-body': {
flexGrow: 1,
minHeight: 0,
display: 'flex',
flexDirection: 'column',
backgroundColor: theme.otherVars.emptySpaceBg,
},
table: {
borderSpacing: 0,
overflow: 'hidden',
borderRadius: theme.shape.borderRadius,
border: '1px solid '+theme.otherVars.borderColor,
display: 'flex',
flexDirection: 'column',
height: '100%',
},
pgTableContainer: {
display: 'flex',
flexGrow: 1,
overflow: 'hidden',
flexDirection: 'column',
height: '100%',
},
pgTableHeader: {
display: 'flex',
background: theme.palette.background.default,
padding: '8px 8px 4px',
},
tableRowContent:{
display: 'flex',
flexDirection: 'column',
minHeight: 0,
},
expandedForm: {
...theme.mixins.panelBorder.all,
margin: '8px',
flexGrow: 1,
},
tableCell: {
margin: 0,
padding: theme.spacing(0.5),
...theme.mixins.panelBorder.bottom,
...theme.mixins.panelBorder.right,
position: 'relative',
overflow: 'hidden',
height: '34px',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
backgroundColor: theme.otherVars.tableBg,
userSelect: 'text'
},
selectCell: {
textAlign: 'center',
minWidth: 20
},
tableHeader: {
backgroundColor: theme.otherVars.tableBg,
},
tableCellHeader: {
fontWeight: theme.typography.fontWeightBold,
padding: theme.spacing(1, 0.5),
textAlign: 'left',
alignContent: 'center',
backgroundColor: theme.otherVars.tableBg,
overflow: 'hidden',
...theme.mixins.panelBorder.bottom,
...theme.mixins.panelBorder.right,
...theme.mixins.panelBorder.top,
...theme.mixins.panelBorder.left,
},
resizer: {
display: 'inline-block',
width: '5px',
height: '100%',
position: 'absolute',
right: 0,
top: 0,
transform: 'translateX(50%)',
zIndex: 1,
touchAction: 'none',
},
cellIcon: {
paddingLeft: '1.8em',
paddingTop: '0.35em',
borderRadius: 0,
backgroundPosition: '1%',
},
emptyPanel: {
minHeight: '100%',
minWidth: '100%',
overflow: 'auto',
padding: '8px',
display: 'flex',
},
caveTable: {
margin: '8px',
},
panelIcon: {
width: '80%',
margin: '0 auto',
marginTop: '25px !important',
position: 'relative',
textAlign: 'center',
},
panelMessage: {
marginLeft: '0.5rem',
fontSize: '0.875rem',
},
expandedIconCell: {
backgroundColor: theme.palette.grey[400],
...theme.mixins.panelBorder.top,
borderBottom: 'none',
},
btnCell: {
padding: theme.spacing(0.5, 0),
textAlign: 'center',
},
btnExpanded: {
backgroundColor: theme.palette.grey[400]
},
readOnlySwitch: {
opacity: 0.75,
'& .MuiSwitch-track': {
opacity: theme.palette.action.disabledOpacity,
'&.pgtable-pgrt-border': {
'& .pgrt': {
border: '1px solid ' + theme.otherVars.borderColor,
}
}
},
'&.pgtable-pgrt-cave': {
'& .pgtable-body': {
padding: '8px',
},
'& .pgtable-header': {
padding: '8px 8px 4px',
},
'& .pgrt': {
border: '1px solid ' + theme.otherVars.borderColor,
}
},
}));
const IndeterminateCheckbox = React.forwardRef(
({ indeterminate, label, ...rest }, ref) => {
const defaultRef = React.useRef();
const resolvedRef = ref || defaultRef;
React.useEffect(() => {
resolvedRef.current.indeterminate = indeterminate;
}, [resolvedRef, indeterminate]);
return (
<Checkbox
color="primary"
ref={resolvedRef} {...rest}
inputProps={{'aria-label': label}}
/>
);
},
);
IndeterminateCheckbox.displayName = 'SelectCheckbox';
IndeterminateCheckbox.propTypes = {
indeterminate: PropTypes.bool,
rest: PropTypes.func,
getToggleAllRowsSelectedProps: PropTypes.func,
row: PropTypes.object,
label: PropTypes.string,
};
const ROW_HEIGHT = 34;
function SortIcon ({column}) {
if (column.isSorted) {
return column.isSortedDesc ? <KeyboardArrowDownIcon style={{fontSize: '1.2rem'}} /> : <KeyboardArrowUpIcon style={{fontSize: '1.2rem'}} />;
}
return '';
}
SortIcon.propTypes = {
column: PropTypes.object
};
function RenderRow({ index, style, schema, row, prepareRow, setRowHeight, ExpandedComponent }) {
const [expandComplete, setExpandComplete] = React.useState(false);
const rowRef = React.useRef() ;
const classes = useStyles();
prepareRow(row);
React.useEffect(()=>{
if(rowRef.current) {
if(!expandComplete && rowRef.current.style.height == `${ROW_HEIGHT}px`) {
return;
}
let rowHeight;
rowRef.current.style.height = 'unset';
if(expandComplete) {
rowHeight = rowRef.current.offsetHeight;
} else {
rowHeight = ROW_HEIGHT;
rowRef.current.style.height = ROW_HEIGHT;
}
rowRef.current.style.height = rowHeight + 'px';
setRowHeight(index, rowHeight);
}
}, [expandComplete]);
return (
<div style={style} key={row.id} ref={rowRef} data-test="row-container">
<div className={classes.tableRowContent}>
<div {...row.getRowProps()} className={classes.tr}>
{row.cells.map((cell) => {
let classNames = [classes.tableCell];
if(typeof(cell.column.id) == 'string' && cell.column.id.startsWith('btn-')) {
classNames.push(classes.btnCell);
}
if(cell.column.id == 'btn-edit' && row.isExpanded) {
classNames.push(classes.expandedIconCell);
}
if (row.original.row_type === 'warning'){
classNames.push(classes.warning);
}
if (row.original.row_type === 'alert'){
classNames.push(classes.alert);
}
return (
<div key={cell.column.id} {...cell.getCellProps()} className={clsx(classNames, cell.column?.dataClassName, row.original.icon?.[cell.column.id], row.original.icon?.[cell.column.id] && classes.cellIcon)}
title={_.isUndefined(cell.value) || _.isNull(cell.value) ? '': String(cell.value)}>
{cell.render('Cell')}
</div>
);
})}
</div>
{!_.isUndefined(row) && row.isExpanded && (
<Box key={row.id} className={classes.expandedForm}>
{schema && <SchemaView
getInitData={()=>Promise.resolve(row.original)}
viewHelperProps={{ mode: 'properties' }}
schema={schema[row.id]??schema}
showFooter={false}
onDataChange={()=>{setExpandComplete(true);}}
/>}
{ExpandedComponent && <ExpandedComponent row={row} onExpandComplete={()=>setExpandComplete(true)}/>}
</Box>
)}
</div>
</div>
);
}
RenderRow.propTypes = {
index: PropTypes.number,
style: PropTypes.object,
row: PropTypes.object,
schema: PropTypes.object,
prepareRow: PropTypes.func,
setRowHeight: PropTypes.func,
ExpandedComponent: PropTypes.node,
};
export default function PgTable({ columns, data, isSelectRow, caveTable=true, schema, ExpandedComponent, sortOptions, tableProps, ...props }) {
// Use the state and functions returned from useTable to build your UI
const classes = useStyles();
export default function PgTable({ caveTable = true, tableNoBorder = true, ...props }) {
const [searchVal, setSearchVal] = React.useState('');
const windowTableRef = React.useRef();
const rowHeights = React.useRef({});
// Reset Search value on tab changes.
React.useEffect(()=>{
setSearchVal(prevState => (prevState));
setGlobalFilter(searchVal || undefined);
rowHeights.current = {};
windowTableRef.current?.resetAfterIndex(0);
}, [data]);
function getRowHeight(index) {
return rowHeights.current[index] || ROW_HEIGHT;
}
const setRowHeight = (index, size) => {
if(windowTableRef.current) {
if(size == ROW_HEIGHT) {
delete rowHeights.current[index];
} else {
rowHeights.current[index] = size;
}
windowTableRef.current.resetAfterIndex(index);
}
};
const defaultColumn = React.useMemo(
() => ({
minWidth: 50,
}),
[]
);
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
selectedFlatRows,
state: { selectedRowIds },
setGlobalFilter,
setHiddenColumns,
totalColumnsWidth
} = useTable(
{
columns,
data,
defaultColumn,
isSelectRow,
autoResetSortBy: false,
initialState: {
sortBy: sortOptions || [],
},
...tableProps,
},
useGlobalFilter,
useSortBy,
useExpanded,
useRowSelect,
useResizeColumns,
useFlexLayout,
(hooks) => {
hooks.visibleColumns.push((CLOUMNS) => {
if (isSelectRow) {
return [
// Let's make a column for selection
{
id: 'selection',
resizable: false,
// The header can use the table's getToggleAllRowsSelectedProps method
// to render a checkbox
Header: ({ getToggleAllRowsSelectedProps, toggleRowSelected, isAllRowsSelected, rows }) => {
const modifiedOnChange = (event) => {
rows.forEach((row) => {
//check each row if it is not disabled
!(!_.isUndefined(row.original.canDrop) && !(row.original.canDrop)) && toggleRowSelected(row.id, event.currentTarget.checked);
});
};
let allTableRows = 0;
let selectedTableRows = 0;
rows.forEach((row) => {
row.isSelected && selectedTableRows++;
(_.isUndefined(row.original.canDrop) || row.original.canDrop) && allTableRows++;
});
const disabled = allTableRows === 0;
const checked =
(isAllRowsSelected ||
allTableRows === selectedTableRows) &&
!disabled;
return(
<div className={classes.selectCell}>
<IndeterminateCheckbox {...getToggleAllRowsSelectedProps()
}
onChange={modifiedOnChange}
checked={checked}
label={gettext('Select All Rows')}
/>
</div>
);},
// The cell can use the individual row's getToggleRowSelectedProps method
// to the render a checkbox
Cell: ({ row }) => (
<div className={classes.selectCell}>
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()}
disabled={!_.isUndefined(row.original.canDrop) ? !(row.original.canDrop) : false}
label={gettext('Select Row')}
/>
</div>
),
sortable: false,
disableResizing: true,
width: 35,
maxWidth: 35,
minWidth: 35
},
...CLOUMNS,
];
} else {
return [...CLOUMNS];
}
});
}
);
React.useEffect(() => {
setHiddenColumns(
columns
.filter((column) => {
return !(column.isVisible === undefined || column.isVisible === true);
}
)
.map((column) => column.accessor)
);
}, [setHiddenColumns, columns]);
React.useEffect(() => {
if (props.setSelectedRows) {
props.setSelectedRows(selectedFlatRows);
}
}, [selectedRowIds]);
React.useEffect(() => {
if (props.getSelectedRows) {
props.getSelectedRows(selectedFlatRows);
}
}, [selectedRowIds]);
React.useEffect(() => {
setGlobalFilter(searchVal || undefined);
}, [searchVal]);
// Render the UI for your table
return (
<Box className={classes.pgTableContainer} data-test={props['data-test']}>
<Box className={classes.pgTableHeader}>
{props.CustomHeader && (<Box className={classes.customHeader}> <props.CustomHeader /></Box>)}
<StyledPgTableRoot className={[tableNoBorder ? '' : 'pgtable-pgrt-border', caveTable ? 'pgtable-pgrt-cave' : ''].join(' ')} data-test={props['data-test']}>
<Box className='pgtable-header'>
{props.CustomHeader && (<Box className='pgtable-custom-header-section'> <props.CustomHeader /></Box>)}
<Box marginLeft="auto">
<InputText
placeholder={gettext('Search')}
controlProps={{title: gettext('Search')}}
className={classes.searchInput}
controlProps={{ title: gettext('Search') }}
className='pgtable-search-input'
value={searchVal}
onChange={(val) => {
setSearchVal(val);
@ -492,149 +252,16 @@ export default function PgTable({ columns, data, isSelectRow, caveTable=true, sc
/>
</Box>
</Box>
<div className={classes.tableContainer}>
<div {...getTableProps({style:{minWidth: totalColumnsWidth}})} className={clsx(classes.table, caveTable ? classes.caveTable : '')}>
<div>
{headerGroups.map((headerGroup) => (
<div key={''} {...headerGroup.getHeaderGroupProps((column)=>({
style: {
...column.style,
height: '40px',
}
}))}>
{headerGroup.headers.map((column) => (
<div
key={column.id}
{...column.getHeaderProps()}
className={clsx(classes.tableCellHeader, column.className)}
>
<div
{...(column.sortable ? column.getSortByToggleProps() : {})}
>
{column.render('Header')}
<span>
<SortIcon column={column} />
</span>
</div>
{column.resizable && (
<div
{...column.getResizerProps()}
className={classes.resizer}
/>
)}
</div>
))}
</div>
))}
</div>
{
data.length > 0 ? (
<div {...getTableBodyProps()} className={classes.autoResizerContainer}>
<AutoSizer
className={classes.autoResizer}
>
{({ height }) => (
<VariableSizeList
ref={windowTableRef}
className={classes.fixedSizeList}
height={isNaN(height) ? 100 : height}
itemCount={rows.length}
itemSize={getRowHeight}
itemData={{rows, prepareRow, setRowHeight}}
>
{({index, style})=>(
<RenderRow index={index} style={style} row={rows[index]} schema={schema} prepareRow={prepareRow}
setRowHeight={setRowHeight} ExpandedComponent={ExpandedComponent} />
)}
</VariableSizeList>)}
</AutoSizer>
</div>
) : (
<EmptyPanelMessage text={gettext('No rows found')}/>
)
}
</div>
<div className={'pgtable-body'}>
<Table {...props} searchVal={searchVal} />
</div>
</Box>
</StyledPgTableRoot>
);
}
PgTable.propTypes = {
stepId: PropTypes.number,
height: PropTypes.number,
CustomHeader: PropTypes.func,
className: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
caveTable: PropTypes.bool,
fixedSizeList: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]),
getToggleAllRowsSelectedProps: PropTypes.func,
toggleRowSelected: PropTypes.func,
columns: PropTypes.array,
data: PropTypes.array,
isSelectRow: PropTypes.bool,
isAllRowsSelected: PropTypes.bool,
row: PropTypes.func,
setSelectedRows: PropTypes.func,
getSelectedRows: PropTypes.func,
searchText: PropTypes.string,
sortOptions: PropTypes.array,
schema: PropTypes.object,
rows: PropTypes.object,
ExpandedComponent: PropTypes.node,
tableProps: PropTypes.object,
tableNoBorder: PropTypes.bool,
'data-test': PropTypes.string
};
export function getExpandCell({onClick, ...props}) {
const Cell = ({ row }) => {
const classes = useStyles();
const onClickFinal = (e)=>{
e.preventDefault();
row.toggleRowExpanded(!row.isExpanded);
onClick?.(row, e);
};
return (
<PgIconButton
size="xs"
className={row.isExpanded ? classes.btnExpanded : ''}
icon={
row.isExpanded ? (
<KeyboardArrowDownIcon />
) : (
<ChevronRightIcon />
)
}
noBorder
{...props}
onClick={onClickFinal}
aria-label={props.title}
/>
);
};
Cell.displayName = 'ExpandCell';
Cell.propTypes = {
title: PropTypes.string,
row: PropTypes.any,
};
return Cell;
}
export function getSwitchCell() {
const Cell = ({value})=>{
const classes = useStyles();
return <Switch color="primary" checked={value} className={classes.readOnlySwitch} value={value} readOnly title={String(value)} />;
};
Cell.displayName = 'SwitchCell';
Cell.propTypes = {
value: PropTypes.any,
};
return Cell;
}
};

View File

@ -10,7 +10,7 @@
import gettext from 'sources/gettext';
import _ from 'lodash';
import url_for from 'sources/url_for';
import React from 'react';
import React, { useEffect } from 'react';
import { Box } from '@mui/material';
import { makeStyles } from '@mui/styles';
import Wizard from '../../../../static/js/helpers/wizard/Wizard';
@ -63,63 +63,64 @@ export default function GrantWizard({ sid, did, nodeInfo, nodeData, onClose }) {
let columns = [
{
Header: 'Object Type',
accessor: 'object_type',
sortable: true,
resizable: false,
disableGlobalFilter: true
header: 'Object Type',
accessorKey: 'object_type',
enableSorting: true,
enableResizing: false,
enableFilters: false
},
{
Header: 'Schema',
accessor: 'nspname',
sortable: true,
resizable: false,
disableGlobalFilter: true
header: 'Schema',
accessorKey: 'nspname',
enableSorting: true,
enableResizing: false,
enableFilters: false
},
{
Header: 'Name',
accessor: 'name_with_args',
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 280
header: 'Name',
accessorKey: 'name_with_args',
enableSorting: true,
enableResizing: true,
enableFilters: true,
minSize: 280
},
{
Header: 'parameters',
accessor: 'proargs',
sortable: false,
resizable: false,
disableGlobalFilter: false,
header: 'parameters',
accessorKey: 'proargs',
enableSorting: false,
enableResizing: false,
enableFilters: true,
enableVisibility: false,
minWidth: 280,
isVisible: false
},
{
Header: 'Name',
accessor: 'name',
sortable: false,
resizable: false,
disableGlobalFilter: false,
header: 'Name',
accessorKey: 'name',
enableSorting: false,
enableResizing: false,
enableFilters: true,
enableVisibility: false,
minWidth: 280,
isVisible: false
},
{
Header: 'ID',
accessor: 'oid',
sortable: false,
resizable: false,
disableGlobalFilter: false,
header: 'ID',
accessorKey: 'oid',
enableSorting: false,
enableResizing: false,
enableFilters: true,
enableVisibility: false,
minWidth: 280,
isVisible: false
}
];
let steps = [gettext('Object Selection'), gettext('Privilege Selection'), gettext('Review')];
const [selectedObject, setSelectedObject] = React.useState([]);
const [selectedRows, setSelectedRows] = React.useState({});
const [selectedAcl, setSelectedAcl] = React.useState({});
const [msqlData, setMSQLData] = React.useState('');
const [loaderText, setLoaderText] = React.useState('');
const [tableData, setTableData] = React.useState([]);
const [privOptions, setPrivOptions] = React.useState({});
const [privileges, setPrivileges] = React.useState([]);
const selectedObject = React.useRef([]);
const privileges = React.useRef([]);
const [privSchemaInstance, setPrivSchemaInstance] = React.useState();
const [errMsg, setErrMsg] = React.useState('');
const pgAdmin = usePgAdmin();
@ -135,11 +136,6 @@ export default function GrantWizard({ sid, did, nodeInfo, nodeData, onClose }) {
return !isValid;
};
React.useEffect(() => {
privSchemaInstance?.privilegeRoleSchema.updateSupportedPrivs(privileges);
}, [privileges]);
React.useEffect(() => {
const privSchema = new PrivilegeSchema((privs) => getNodePrivilegeRoleSchema('', nodeInfo, nodeData, privs));
setPrivSchemaInstance(privSchema);
@ -197,7 +193,7 @@ export default function GrantWizard({ sid, did, nodeInfo, nodeData, onClose }) {
});
let post_data = {
acl: selectedAcl.privilege,
objects: selectedObject
objects: selectedObject.current
};
api.post(msql_url, post_data)
.then(res => {
@ -219,7 +215,7 @@ export default function GrantWizard({ sid, did, nodeInfo, nodeData, onClose }) {
});
const post_data = {
acl: selectedAcl.privilege,
objects: selectedObject
objects: selectedObject.current
};
api.post(_url, post_data)
.then(() => {
@ -233,7 +229,7 @@ export default function GrantWizard({ sid, did, nodeInfo, nodeData, onClose }) {
};
const disableNextCheck = (stepId) => {
if (selectedObject.length > 0 && stepId === 0) {
if (Object.keys(selectedRows).length > 0 && stepId === 0) {
return false;
}
@ -244,14 +240,14 @@ export default function GrantWizard({ sid, did, nodeInfo, nodeData, onClose }) {
window.open(url_for('help.static', { 'filename': 'grant_wizard.html' }), 'pgadmin_help');
};
const getTableSelectedRows = (selRows) => {
useEffect(()=>{
let selObj = [];
let objectTypes = new Set();
if (selRows.length > 0) {
selRows.forEach((row) => {
if (Object.keys(selectedRows).length > 0) {
Object.keys(selectedRows).forEach((rowId) => {
const row = tableData[rowId];
let object_type = '';
switch (row.values.object_type) {
switch (row.object_type) {
case 'Function':
object_type = 'function';
break;
@ -284,7 +280,7 @@ export default function GrantWizard({ sid, did, nodeInfo, nodeData, onClose }) {
}
objectTypes.add(object_type);
selObj.push(row.values);
selObj.push(row);
});
}
let privs = new Set();
@ -293,10 +289,11 @@ export default function GrantWizard({ sid, did, nodeInfo, nodeData, onClose }) {
privs.add(priv);
});
});
setPrivileges(Array.from(privs));
setSelectedObject(selObj);
privileges.current = Array.from(privs);
selectedObject.current = selObj;
privSchemaInstance?.privilegeRoleSchema.updateSupportedPrivs(privileges.current);
setErrMsg(selObj.length === 0 ? gettext('Please select any database object.') : '');
};
}, [selectedRows]);
const onErrClose = React.useCallback(()=>{
setErrMsg('');
@ -316,13 +313,15 @@ export default function GrantWizard({ sid, did, nodeInfo, nodeData, onClose }) {
<Box className={classes.panelContent}>
<PgTable
caveTable={false}
tableNoBorder={false}
className={classes.table}
height={window.innerHeight - 450}
columns={columns}
data={tableData}
isSelectRow={true}
getSelectedRows={getTableSelectedRows}>
</PgTable>
hasSelectRow={true}
selectedRows={selectedRows}
setSelectedRows={setSelectedRows}
/>
</Box>
<FormFooterMessage type={MESSAGE_TYPE.ERROR} message={errMsg} onClose={onErrClose} />
</WizardStep>
@ -330,17 +329,17 @@ export default function GrantWizard({ sid, did, nodeInfo, nodeData, onClose }) {
stepId={1}
className={clsx(classes.privilegeStep)}>
{privSchemaInstance &&
<SchemaView
formType={'dialog'}
getInitData={() => {/*This is intentional (SonarQube)*/}}
viewHelperProps={{ mode: 'create' }}
schema={privSchemaInstance}
showFooter={false}
isTabView={false}
onDataChange={(isChanged, changedData) => {
setSelectedAcl(changedData);
}}
/>
<SchemaView
formType={'dialog'}
getInitData={() => {/*This is intentional (SonarQube)*/}}
viewHelperProps={{ mode: 'create' }}
schema={privSchemaInstance}
showFooter={false}
isTabView={false}
onDataChange={(isChanged, changedData) => {
setSelectedAcl(changedData);
}}
/>
}
</WizardStep>
<WizardStep

View File

@ -738,7 +738,7 @@ export function ResultGridComponent({ gridData, allRowIds, filterParams, selecte
treeDepth={2}
enableRowSelect={true}
defaultColumnOptions={{
resizable: true
enableResizing: true
}}
headerRowHeight={28}
rowHeight={28}

View File

@ -135,7 +135,7 @@ const columns = [
},{
key: 'path',
name: gettext('Object path'),
sortable: false,
enableSorting: false,
formatter: TypePathFormatter,
}
];
@ -411,8 +411,8 @@ export default function SearchObjects({nodeData}) {
columns={columns}
rows={sortedItems}
defaultColumnOptions={{
sortable: true,
resizable: true
enableSorting: true,
enableResizing: true
}}
headerRowHeight={28}
rowHeight={28}

View File

@ -270,7 +270,7 @@ function initialiseColumns(columns, rows, totalRowCount, columnWidthBy) {
/* padding 8 on both sides*/
rowNumWidth += 16;
let rowNumCol = {
key: ROWNUM_KEY, name: '', frozen: true, resizable: false,
key: ROWNUM_KEY, name: '', frozen: true, enableResizing: false,
minWidth: 45, width: rowNumWidth,
};
rowNumCol.cellClass = cellClassGetter(rowNumCol);

View File

@ -116,7 +116,7 @@ class UserManagementCollection extends BaseUISchema {
return obj.isEditable(state);
}
}, {
id: 'active', label: gettext('Active'), cell: 'switch', width: 60, disableResizing: true,
id: 'active', label: gettext('Active'), cell: 'switch', width: 60, enableResizing: false,
editable: (state)=> {
return obj.isEditable(state);
}
@ -137,7 +137,7 @@ class UserManagementCollection extends BaseUISchema {
return obj.isEditable(state) && state.auth_source == AUTH_METHODS['INTERNAL'];
}
}, {
id: 'locked', label: gettext('Locked'), cell: 'switch', width: 60, disableResizing: true,
id: 'locked', label: gettext('Locked'), cell: 'switch', width: 60, enableResizing: false,
editable: (state)=> {
return state.locked;
}

View File

@ -294,10 +294,11 @@ class PGUtilitiesBackupFeatureTest(BaseFeatureTest):
if serv == 'pg' and server_version is not None and \
default_binary_path['pg'] != '':
path_input = \
self.page.find_by_xpath(
"//div[span[text()='PostgreSQL {}']]"
"/following-sibling::div//div/input".format(
server_version))
self.page.find_by_css_selector(
"div[class='pgrd-row-cell'][title='PostgreSQL {}']"
"+div[class='pgrd-row-cell'] input"
.format(server_version)
)
existing_path = path_input.get_property("value")
if existing_path != default_binary_path['pg']:
path_already_set = False

View File

@ -54,8 +54,8 @@ def open_process_details(tester):
time.sleep(3)
tester.page.find_by_css_selector(
"div[data-test='processes'] "
"div[data-test='row-container']:nth-child(1) "
"div[role='row'] div[role='cell']:nth-child(3) button").click()
"div[role='row']:nth-child(1) "
"div[role='cell']:nth-child(3) button").click()
tester.page.wait_for_element_to_disappear(
lambda driver: driver.find_element(

View File

@ -3173,6 +3173,44 @@ __metadata:
languageName: node
linkType: hard
"@tanstack/react-table@npm:^8.16.0":
version: 8.16.0
resolution: "@tanstack/react-table@npm:8.16.0"
dependencies:
"@tanstack/table-core": 8.16.0
peerDependencies:
react: ">=16.8"
react-dom: ">=16.8"
checksum: 9a80668ba7531b49425d3c08fe34fbd4bbcdf936fbca120114d2d090013242c3ea1b573c1381719289600bc866f2ded9e3e13c7c4923285d2cf4eee1c1d489e7
languageName: node
linkType: hard
"@tanstack/react-virtual@npm:^3.4.0":
version: 3.4.0
resolution: "@tanstack/react-virtual@npm:3.4.0"
dependencies:
"@tanstack/virtual-core": 3.4.0
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
checksum: 73c272d4a6242d1e49e37e56cec3985d8ff3108e8cd33d2f0c8c3f6ec3d84fece50813e875ee15ee025de4c47f051fcdf8829b1a9c824fd360ae0a569eddb1f5
languageName: node
linkType: hard
"@tanstack/table-core@npm:8.16.0":
version: 8.16.0
resolution: "@tanstack/table-core@npm:8.16.0"
checksum: c2c33c542c60788eb90806feb8f1f0340aa565ef9bb031bc5562d43598394a4089d61007f55ed6ab1fa16bbe93228cb2673b564ecf90b416bf463f42a56f3d85
languageName: node
linkType: hard
"@tanstack/virtual-core@npm:3.4.0":
version: 3.4.0
resolution: "@tanstack/virtual-core@npm:3.4.0"
checksum: dee878179e504bb2d62ffb36a97e1c37e777af1802cadb03566b732099b13d7d053590997e3c17ce098bcb2adf527f53f69795d38cb607e308cde21dc809702e
languageName: node
linkType: hard
"@testing-library/dom@npm:^8.0.0":
version: 8.20.1
resolution: "@testing-library/dom@npm:8.20.1"
@ -13769,15 +13807,6 @@ __metadata:
languageName: node
linkType: hard
"react-table@npm:^7.6.3":
version: 7.8.0
resolution: "react-table@npm:7.8.0"
peerDependencies:
react: ^16.8.3 || ^17.0.0-0 || ^18.0.0
checksum: 44ca0fb848c6869cd793cede8dc33072b38ebb8f8d2833565afe7cf3eac5d1fa455ac5fb9d06838b16fab0523d5d03e3e82f7645032f71245096e67b892313b9
languageName: node
linkType: hard
"react-timer-hook@npm:^3.0.5":
version: 3.0.7
resolution: "react-timer-hook@npm:3.0.7"
@ -13822,7 +13851,7 @@ __metadata:
languageName: node
linkType: hard
"react-window@npm:^1.3.1, react-window@npm:^1.8.10, react-window@npm:^1.8.5":
"react-window@npm:^1.3.1, react-window@npm:^1.8.10":
version: 1.8.10
resolution: "react-window@npm:1.8.10"
dependencies:
@ -14265,6 +14294,8 @@ __metadata:
"@simonwep/pickr": ^1.5.1
"@svgr/webpack": ^8.1.0
"@szhsin/react-menu": ^2.2.0
"@tanstack/react-table": ^8.16.0
"@tanstack/react-virtual": ^3.4.0
"@testing-library/jest-dom": ^6.1.2
"@testing-library/react": 12
"@testing-library/user-event": ^14.4.3
@ -14356,10 +14387,8 @@ __metadata:
react-resize-detector: ^9.1.0
react-rnd: ^10.3.5
react-select: ^5.7.2
react-table: ^7.6.3
react-timer-hook: ^3.0.5
react-virtualized-auto-sizer: ^1.0.6
react-window: ^1.8.5
resize-observer-polyfill: ^1.5.1
shim-loader: ^1.0.1
snapsvg-cjs: ^0.0.6