Skip to content

Commit

Permalink
Introduce custom React Hook useSchemaState to simplify SchemaView com…
Browse files Browse the repository at this point in the history
…ponent. pgadmin-org#7776

Changes include: 
- Simplify current SchemaView code
- Add ability to reuse the schema data & state management implementation outside the SchemaDialogView component.
- Further split components in small and manageable separate files.
- Removed the 'DepListenerContext' context as there was no need for separate context.
- Added a reload functionality in the 'useSchemaState'
- Changes in feature tests.
  • Loading branch information
asheshv authored Aug 2, 2024
1 parent 546806c commit 52af8d3
Show file tree
Hide file tree
Showing 20 changed files with 2,196 additions and 1,576 deletions.
2 changes: 1 addition & 1 deletion web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@
"pep8": "pycodestyle --config=../.pycodestyle ../docs && pycodestyle --config=../.pycodestyle ../pkg && pycodestyle --config=../.pycodestyle ../tools && pycodestyle --config=../.pycodestyle ../web",
"auditjs-html": "yarn audit --json | yarn run yarn-audit-html --output ../auditjs.html",
"auditjs": "yarn audit --groups dependencies",
"auditpy": "safety check --full-report -i 51668 -i 52495yarn npm audit",
"auditpy": "safety check --full-report -i 51668 -i 52495",
"audit-all": "yarn run auditjs && yarn run auditpy"
},
"packageManager": "yarn@3.8.3",
Expand Down
13 changes: 12 additions & 1 deletion web/pgadmin/misc/properties/ObjectNodeProperties.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,18 @@ export default function ObjectNodeProperties({panelId, node, treeNodeInfo, nodeD
let warnOnCloseFlag = true;
const confirmOnCloseReset = usePreferences().getPreferencesForModule('browser').confirm_on_properties_close;
let updatedData = ['table', 'partition'].includes(nodeType) && !_.isEmpty(nodeData.rows_cnt) ? {rows_cnt: nodeData.rows_cnt} : undefined;
let schema = node.getSchema(treeNodeInfo, nodeData);

const objToString = (obj) => (
(obj && typeof obj === 'object') ? Object.keys(obj).sort().reduce(
(acc, key) => (acc + `${key}=` + objToString(obj[key])), ''
) : String(obj)
);

const treeNodeId = objToString(treeNodeInfo);

let schema = useMemo(
() => node.getSchema(treeNodeInfo, nodeData), [treeNodeId, isActive]
);

// We only have two actionTypes, 'create' and 'edit' to initiate the dialog,
// so if isActionTypeCopy is true, we should revert back to "create" since
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,9 @@ export default function PreferencesComponent({ ...props }) {
function savePreferences(data, initVal) {
let _data = [];
for (const [key, value] of Object.entries(data.current)) {
let _metadata = prefSchema.current.schemaFields.filter((el) => { return el.id == key; });
let _metadata = prefSchema.current.schemaFields.filter(
(el) => { return el.id == key; }
);
if (_metadata.length > 0) {
let val = getCollectionValue(_metadata, value, initVal);
_data.push({
Expand Down Expand Up @@ -525,7 +527,11 @@ export default function PreferencesComponent({ ...props }) {
data: save_data,
}).then(() => {
let requiresTreeRefresh = save_data.some((s)=>{
return s.name=='show_system_objects'||s.name=='show_empty_coll_nodes'||s.name.startsWith('show_node_')||s.name=='hide_shared_server'||s.name=='show_user_defined_templates';
return (
s.name=='show_system_objects' || s.name=='show_empty_coll_nodes' ||
s.name.startsWith('show_node_') || s.name=='hide_shared_server' ||
s.name=='show_user_defined_templates'
);
});
let requires_refresh = false;
for (const [key] of Object.entries(data.current)) {
Expand All @@ -536,11 +542,15 @@ export default function PreferencesComponent({ ...props }) {
if (requiresTreeRefresh) {
pgAdmin.Browser.notifier.confirm(
gettext('Object explorer refresh required'),
gettext('An object explorer refresh is required. Do you wish to refresh it now?'),
gettext(
'An object explorer refresh is required. Do you wish to refresh it now?'
),
function () {
pgAdmin.Browser.tree.destroy().then(
() => {
pgAdmin.Browser.Events.trigger('pgadmin-browser:tree:destroyed', undefined, undefined);
pgAdmin.Browser.Events.trigger(
'pgadmin-browser:tree:destroyed', undefined, undefined
);
return true;
}
);
Expand Down
191 changes: 70 additions & 121 deletions web/pgadmin/static/js/SchemaView/DataGridView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,8 @@
/* The DataGridView component is based on react-table component */

import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { styled } from '@mui/material/styles';
import { Box } from '@mui/material';
import { PgIconButton } from '../components/Buttons';
import AddIcon from '@mui/icons-material/AddOutlined';
import { MappedCellControl } from './MappedControl';

import {
useReactTable,
getCoreRowModel,
Expand All @@ -24,103 +20,41 @@ import {
getExpandedRowModel,
flexRender,
} from '@tanstack/react-table';
import { useVirtualizer } from '@tanstack/react-virtual';
import PropTypes from 'prop-types';
import _ from 'lodash';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import {HTML5Backend} from 'react-dnd-html5-backend';

import gettext from 'sources/gettext';
import { SCHEMA_STATE_ACTIONS, StateUtilsContext } from '.';
import FormView, { getFieldMetaData } from './FormView';
import CustomPropTypes from 'sources/custom_prop_types';
import { evalFunc } from 'sources/utils';
import { DepListenerContext } from './DepListener';
import { useIsMounted } from '../custom_hooks';
import { InputText } from '../components/FormComponents';
import { usePgAdmin } from '../BrowserComponent';
import { requestAnimationAndFocus } from '../utils';
import { PgReactTable, PgReactTableBody, PgReactTableCell, PgReactTableHeader,
import { usePgAdmin } from 'sources/BrowserComponent';
import { PgIconButton } from 'sources/components/Buttons';
import {
PgReactTable, PgReactTableBody, PgReactTableCell, PgReactTableHeader,
PgReactTableRow, PgReactTableRowContent, PgReactTableRowExpandContent,
getDeleteCell, getEditCell, getReorderCell } from '../components/PgReactTableStyled';
import { useVirtualizer } from '@tanstack/react-virtual';
getDeleteCell, getEditCell, getReorderCell
} from 'sources/components/PgReactTableStyled';
import CustomPropTypes from 'sources/custom_prop_types';
import { useIsMounted } from 'sources/custom_hooks';
import { InputText } from 'sources/components/FormComponents';
import gettext from 'sources/gettext';
import { evalFunc, requestAnimationAndFocus } from 'sources/utils';

const StyledBox = styled(Box)(({theme}) => ({
'& .DataGridView-grid': {
...theme.mixins.panelBorder,
backgroundColor: theme.palette.background.default,
display: 'flex',
flexDirection: 'column',
minHeight: 0,
height: '100%',
'& .DataGridView-gridHeader': {
display: 'flex',
...theme.mixins.panelBorder.bottom,
backgroundColor: theme.otherVars.headerBg,
'& .DataGridView-gridHeaderText': {
padding: theme.spacing(0.5, 1),
fontWeight: theme.typography.fontWeightBold,
},
'& .DataGridView-gridControls': {
marginLeft: 'auto',
'& .DataGridView-gridControlsButton': {
border: 0,
borderRadius: 0,
...theme.mixins.panelBorder.left,
},
},
},
'& .DataGridView-table': {
'&.pgrt-table': {
'& .pgrt-body':{
'& .pgrt-row': {
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'
},
}
},
}
}
}
},
},
'& .DataGridView-tableRowHovered': {
position: 'relative',
'& .hover-overlay': {
backgroundColor: theme.palette.primary.light,
position: 'absolute',
inset: 0,
opacity: 0.75,
}
},
'& .DataGridView-resizer': {
display: 'inline-block',
width: '5px',
height: '100%',
position: 'absolute',
right: 0,
top: 0,
transform: 'translateX(50%)',
zIndex: 1,
touchAction: 'none',
},
'& .DataGridView-expandedForm': {
border: '1px solid '+theme.palette.grey[400],
},
'& .DataGridView-expandedIconCell': {
backgroundColor: theme.palette.grey[400],
borderBottom: 'none',
}
}));
import FormView from './FormView';
import { MappedCellControl } from './MappedControl';
import {
SCHEMA_STATE_ACTIONS, SchemaStateContext, getFieldMetaData,
isModeSupportedByField
} from './common';
import { StyleDataGridBox } from './StyledComponents';

function DataTableRow({index, row, totalRows, isResizing, isHovered, schema, schemaRef, accessPath, moveRow, setHoverIndex, viewHelperProps}) {

function DataTableRow({
index, row, totalRows, isResizing, isHovered, schema, schemaRef, accessPath,
moveRow, setHoverIndex, viewHelperProps
}) {

const [key, setKey] = useState(false);
const depListener = useContext(DepListenerContext);
const schemaState = useContext(SchemaStateContext);
const rowRef = useRef(null);
const dragHandleRef = useRef(null);

Expand Down Expand Up @@ -150,22 +84,22 @@ function DataTableRow({index, row, totalRows, isResizing, isHovered, schema, sch
schemaRef.current.fields.forEach((field)=>{
/* Self change is also dep change */
if(field.depChange || field.deferredDepChange) {
depListener?.addDepListener(accessPath.concat(field.id), accessPath.concat(field.id), field.depChange, field.deferredDepChange);
schemaState?.addDepListener(accessPath.concat(field.id), accessPath.concat(field.id), field.depChange, field.deferredDepChange);
}
(evalFunc(null, field.deps) || []).forEach((dep)=>{
let source = accessPath.concat(dep);
if(_.isArray(dep)) {
source = dep;
}
if(field.depChange) {
depListener?.addDepListener(source, accessPath.concat(field.id), field.depChange);
schemaState?.addDepListener(source, accessPath.concat(field.id), field.depChange);
}
});
});

return ()=>{
/* Cleanup the listeners when unmounting */
depListener?.removeDepListener(accessPath);
schemaState?.removeDepListener(accessPath);
};
}, []);

Expand Down Expand Up @@ -227,18 +161,24 @@ function DataTableRow({index, row, totalRows, isResizing, isHovered, schema, sch
drop(rowRef);

return useMemo(()=>
<PgReactTableRowContent ref={rowRef} data-handler-id={handlerId} className={isHovered ? 'DataGridView-tableRowHovered' : null} data-test='data-table-row' style={{position: 'initial'}}>
<PgReactTableRowContent ref={rowRef} data-handler-id={handlerId}
className={isHovered ? 'DataGridView-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's not render the cell, which are not supported in this mode.
if (cell.column.field && !isModeSupportedByField(
cell.column.field, viewHelperProps
)) return;

const content = flexRender(cell.column.columnDef.cell, {
key: cell.column.columnDef.cell?.type ?? cell.column.columnDef.id,
...cell.getContext(),
reRenderRow: ()=>{setKey((currKey)=>!currKey);}
});

return (modeSupported &&
<PgReactTableCell cell={cell} row={row} key={cell.id} ref={cell.column.id == 'btn-reorder' ? dragHandleRef : null}>
return (
<PgReactTableCell cell={cell} row={row} key={cell.id}
ref={cell.column.id == 'btn-reorder' ? dragHandleRef : null}>
{content}
</PgReactTableCell>
);
Expand Down Expand Up @@ -337,9 +277,10 @@ function getMappedCell({

export default function DataGridView({
value, viewHelperProps, schema, accessPath, dataDispatch, containerClassName,
fixedRows, ...props}) {
fixedRows, ...props
}) {

const stateUtils = useContext(StateUtilsContext);
const schemaState = useContext(SchemaStateContext);
const checkIsMounted = useIsMounted();
const [hoverIndex, setHoverIndex] = useState();
const newRowIndex = useRef();
Expand Down Expand Up @@ -439,14 +380,14 @@ export default function DataGridView({
}

cols = cols.concat(
schemaRef.current.fields.filter((f)=>{
return _.isArray(props.columns) ? props.columns.indexOf(f.id) > -1 : true;
}).sort((firstF, secondF)=>{
if(_.isArray(props.columns)) {
return props.columns.indexOf(firstF.id) < props.columns.indexOf(secondF.id) ? -1 : 1;
}
return 0;
}).map((field)=>{
schemaRef.current.fields.filter((f) => (
_.isArray(props.columns) ? props.columns.indexOf(f.id) > -1 : true
)).sort((firstF, secondF) => (
_.isArray(props.columns) ? ((
props.columns.indexOf(firstF.id) <
props.columns.indexOf(secondF.id)
) ? -1 : 1) : 0
)).map((field) => {
let widthParms = {};
if(field.width) {
widthParms.size = field.width;
Expand All @@ -461,7 +402,10 @@ export default function DataGridView({
if(field.maxWidth) {
widthParms.maxSize = field.maxWidth;
}
widthParms.enableResizing = _.isUndefined(field.enableResizing) ? true : Boolean(field.enableResizing);
widthParms.enableResizing =
_.isUndefined(field.enableResizing) ? true : Boolean(
field.enableResizing
);

let colInfo = {
header: field.label||<>&nbsp;</>,
Expand Down Expand Up @@ -490,8 +434,7 @@ export default function DataGridView({
const ret = {};

columns.forEach(column => {
let {modeSupported} = column.field ? getFieldMetaData(column.field, schemaRef.current, {}, viewHelperProps) : {modeSupported: true};
ret[column.id] = modeSupported;
ret[column.id] = isModeSupportedByField(column.field, viewHelperProps);
});

return ret;
Expand Down Expand Up @@ -535,19 +478,20 @@ export default function DataGridView({
});
}, [props.canAddRow, rows?.length]);

useEffect(()=>{
useEffect(() => {
let rowsPromise = fixedRows;

/* If fixedRows is defined, fetch the details */
// If fixedRows is defined, fetch the details.
if(typeof rowsPromise === 'function') {
rowsPromise = rowsPromise();
}

if(rowsPromise) {
Promise.resolve(rowsPromise)
.then((res)=>{
.then((res) => {
/* If component unmounted, dont update state */
if(checkIsMounted()) {
stateUtils.initOrigData(accessPath, res);
schemaState.setUnpreparedData(accessPath, res);
}
});
}
Expand All @@ -558,12 +502,17 @@ export default function DataGridView({
virtualizer.scrollToIndex(newRowIndex.current);

// Try autofocus on newly added row.
setTimeout(()=>{
const rowInput = tableRef.current?.querySelector(`.pgrt-row[data-index="${newRowIndex.current}"] input`);
setTimeout(() => {
const rowInput = tableRef.current?.querySelector(
`.pgrt-row[data-index="${newRowIndex.current}"] input`
);
if(!rowInput) return;

requestAnimationAndFocus(tableRef.current.querySelector(`.pgrt-row[data-index="${newRowIndex.current}"] input`));
props.expandEditOnAdd && props.canEdit && rows[newRowIndex.current]?.toggleExpanded(true);
requestAnimationAndFocus(tableRef.current.querySelector(
`.pgrt-row[data-index="${newRowIndex.current}"] input`
));
props.expandEditOnAdd && props.canEdit &&
rows[newRowIndex.current]?.toggleExpanded(true);
newRowIndex.current = undefined;
}, 50);
}
Expand Down Expand Up @@ -599,7 +548,7 @@ export default function DataGridView({
}

return (
<StyledBox className={containerClassName}>
<StyleDataGridBox className={containerClassName}>
<Box className='DataGridView-grid'>
{(props.label || props.canAdd) && <DataGridHeader label={props.label} canAdd={props.canAdd} onAddClick={onAddClick}
canSearch={props.canSearch}
Expand Down Expand Up @@ -639,7 +588,7 @@ export default function DataGridView({
</PgReactTable>
</DndProvider>
</Box>
</StyledBox>
</StyleDataGridBox>
);
}

Expand Down
Loading

0 comments on commit 52af8d3

Please sign in to comment.