mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Fixes for the preferences dialog
1) Add server mode validation in the binary path. 2) Updated preferences tree rendering to avoid using the ReactDOM render. 3) Updated CSS for keyboard shortcuts checkbox border makes it consistent with input box border. 4) Fixed jasmine test case and improved code coverage. 5) Fixed SonarQube issues. 6) Added validation to disable "Maximum column with" option if "Column sized by" option is set to "Column name" in Query Tool -> Result grid. 7) Updated documentation with the latest screenshots. 8) Correct typo in the documentation. Fixes #7261 refs #7149
This commit is contained in:
committed by
Akshay Joshi
parent
1711834229
commit
2f37f0ca51
@@ -8,8 +8,9 @@
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import _ from 'lodash';
|
||||
import url_for from 'sources/url_for';
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { FileType } from 'react-aspen';
|
||||
import { Box } from '@material-ui/core';
|
||||
import PropTypes from 'prop-types';
|
||||
@@ -209,100 +210,8 @@ export default function PreferencesComponent({ ...props }) {
|
||||
'expanded': false,
|
||||
};
|
||||
|
||||
if (subNode.label == 'Nodes' && node.label == 'Browser') {
|
||||
//Add Note for Nodes
|
||||
preferencesData.push(
|
||||
{
|
||||
id: 'note_' + subNode.id,
|
||||
type: 'note', text: [gettext('This settings is to Show/Hide nodes in the browser tree.')].join(''),
|
||||
visible: false,
|
||||
'parentId': nodeData['id']
|
||||
},
|
||||
);
|
||||
}
|
||||
subNode.preferences.forEach((element) => {
|
||||
let addNote = false;
|
||||
let note = '';
|
||||
let type = getControlMappedForType(element.type);
|
||||
|
||||
if (type === 'file') {
|
||||
addNote = true;
|
||||
note = gettext('Enter the directory in which the psql, pg_dump, pg_dumpall, and pg_restore utilities can be found for the corresponding database server version. The default path will be used for server versions that do not have a path specified.');
|
||||
element.type = 'collection';
|
||||
element.schema = getBinaryPathSchema();
|
||||
element.canAdd = false;
|
||||
element.canDelete = false;
|
||||
element.canEdit = false;
|
||||
element.editable = false;
|
||||
element.disabled = true;
|
||||
preferencesValues[element.id] = JSON.parse(element.value);
|
||||
}
|
||||
else if (type == 'select') {
|
||||
if (element.control_props !== undefined) {
|
||||
element.controlProps = element.control_props;
|
||||
} else {
|
||||
element.controlProps = {};
|
||||
}
|
||||
|
||||
element.type = type;
|
||||
preferencesValues[element.id] = element.value;
|
||||
|
||||
if (element.name == 'theme') {
|
||||
element.type = 'theme';
|
||||
|
||||
element.options.forEach((opt) => {
|
||||
if (opt.value == element.value) {
|
||||
opt.selected = true;
|
||||
} else {
|
||||
opt.selected = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (type === 'keyboardShortcut') {
|
||||
element.type = 'keyboardShortcut';
|
||||
element.canAdd = false;
|
||||
element.canDelete = false;
|
||||
element.canEdit = false;
|
||||
element.editable = false;
|
||||
if (pgAdmin.Browser.get_preference(node.label.toLowerCase(), element.name)?.value) {
|
||||
let temp = pgAdmin.Browser.get_preference(node.label.toLowerCase(), element.name).value;
|
||||
preferencesValues[element.id] = temp;
|
||||
} else {
|
||||
preferencesValues[element.id] = element.value;
|
||||
}
|
||||
delete element.value;
|
||||
} else if (type === 'threshold') {
|
||||
element.type = 'threshold';
|
||||
|
||||
let _val = element.value.split('|');
|
||||
preferencesValues[element.id] = { 'warning': _val[0], 'alert': _val[1] };
|
||||
} else {
|
||||
element.type = type;
|
||||
preferencesValues[element.id] = element.value;
|
||||
}
|
||||
|
||||
delete element.value;
|
||||
element.visible = false;
|
||||
element.helpMessage = element?.help_str ? element.help_str : null;
|
||||
preferencesData.push(element);
|
||||
|
||||
if (addNote) {
|
||||
preferencesData.push(
|
||||
{
|
||||
id: 'note_' + element.id,
|
||||
type: 'note', text: [
|
||||
'<ul><li>',
|
||||
gettext(note),
|
||||
'</li></ul>',
|
||||
].join(''),
|
||||
visible: false,
|
||||
'parentId': nodeData['id']
|
||||
},
|
||||
);
|
||||
}
|
||||
element.parentId = nodeData['id'];
|
||||
});
|
||||
addNote(node, subNode, nodeData, preferencesData);
|
||||
setPreferences(node, subNode, nodeData, preferencesValues, preferencesData);
|
||||
tdata['childrenNodes'].push(nodeData);
|
||||
});
|
||||
|
||||
@@ -318,11 +227,128 @@ export default function PreferencesComponent({ ...props }) {
|
||||
Notify.alert(err);
|
||||
});
|
||||
}, []);
|
||||
function setPreferences(node, subNode, nodeData, preferencesValues, preferencesData) {
|
||||
subNode.preferences.forEach((element) => {
|
||||
let note = '';
|
||||
let type = getControlMappedForType(element.type);
|
||||
|
||||
if (type === 'file') {
|
||||
note = gettext('Enter the directory in which the psql, pg_dump, pg_dumpall, and pg_restore utilities can be found for the corresponding database server version. The default path will be used for server versions that do not have a path specified.');
|
||||
element.type = 'collection';
|
||||
element.schema = getBinaryPathSchema();
|
||||
element.canAdd = false;
|
||||
element.canDelete = false;
|
||||
element.canEdit = false;
|
||||
element.editable = false;
|
||||
element.disabled = true;
|
||||
preferencesValues[element.id] = JSON.parse(element.value);
|
||||
addNote(node, subNode, nodeData, preferencesData, note);
|
||||
}
|
||||
else if (type == 'select') {
|
||||
setControlProps(element);
|
||||
element.type = type;
|
||||
preferencesValues[element.id] = element.value;
|
||||
|
||||
setThemesOptions(element);
|
||||
}
|
||||
else if (type === 'keyboardShortcut') {
|
||||
getKeyboardShortcuts(element, preferencesValues, node);
|
||||
} else if (type === 'threshold') {
|
||||
element.type = 'threshold';
|
||||
|
||||
let _val = element.value.split('|');
|
||||
preferencesValues[element.id] = { 'warning': _val[0], 'alert': _val[1] };
|
||||
} else if (subNode.label == 'Results grid' && node.label == 'Query Tool') {
|
||||
setResultsOptions(element, subNode, preferencesValues, type);
|
||||
} else {
|
||||
element.type = type;
|
||||
preferencesValues[element.id] = element.value;
|
||||
}
|
||||
|
||||
delete element.value;
|
||||
element.visible = false;
|
||||
element.helpMessage = element?.help_str ? element.help_str : null;
|
||||
preferencesData.push(element);
|
||||
element.parentId = nodeData['id'];
|
||||
});
|
||||
}
|
||||
|
||||
function setResultsOptions(element, subNode, preferencesValues, type) {
|
||||
if (element.name== 'column_data_max_width') {
|
||||
let size_control_id = null;
|
||||
subNode.preferences.forEach((_el) => {
|
||||
if(_el.name == 'column_data_auto_resize') {
|
||||
size_control_id = _el.id;
|
||||
}
|
||||
|
||||
});
|
||||
element.disabled = (state) => {
|
||||
return state[size_control_id] != 'by_data';
|
||||
};
|
||||
}
|
||||
element.type = type;
|
||||
preferencesValues[element.id] = element.value;
|
||||
}
|
||||
|
||||
function setThemesOptions(element) {
|
||||
if (element.name == 'theme') {
|
||||
element.type = 'theme';
|
||||
|
||||
element.options.forEach((opt) => {
|
||||
if (opt.value == element.value) {
|
||||
opt.selected = true;
|
||||
} else {
|
||||
opt.selected = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
function setControlProps(element) {
|
||||
if (element.control_props !== undefined) {
|
||||
element.controlProps = element.control_props;
|
||||
} else {
|
||||
element.controlProps = {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getKeyboardShortcuts(element, preferencesValues, node) {
|
||||
element.type = 'keyboardShortcut';
|
||||
element.canAdd = false;
|
||||
element.canDelete = false;
|
||||
element.canEdit = false;
|
||||
element.editable = false;
|
||||
if (pgAdmin.Browser.get_preference(node.label.toLowerCase(), element.name)?.value) {
|
||||
let temp = pgAdmin.Browser.get_preference(node.label.toLowerCase(), element.name).value;
|
||||
preferencesValues[element.id] = temp;
|
||||
} else {
|
||||
preferencesValues[element.id] = element.value;
|
||||
}
|
||||
}
|
||||
function addNote(node, subNode, nodeData, preferencesData, note = '') {
|
||||
// Check and add the note for the element.
|
||||
if (subNode.label == 'Nodes' && node.label == 'Browser') {
|
||||
note = [gettext('This settings is to Show/Hide nodes in the browser tree.')].join('');
|
||||
} else {
|
||||
note = [gettext(note)].join('');
|
||||
}
|
||||
|
||||
if (note && note.length > 0) {
|
||||
//Add Note for Nodes
|
||||
preferencesData.push(
|
||||
{
|
||||
id: _.uniqueId('note') + subNode.id,
|
||||
type: 'note', text: note,
|
||||
visible: false,
|
||||
'parentId': nodeData['id']
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
props.renderTree(prefTreeData);
|
||||
let initTreeTimeout = null;
|
||||
|
||||
// Listen selected preferences tree node event and show the appropriate components in right panel.
|
||||
pgAdmin.Browser.Events.on('preferences:tree:selected', (item) => {
|
||||
if (item.type == FileType.File) {
|
||||
@@ -330,12 +356,12 @@ export default function PreferencesComponent({ ...props }) {
|
||||
field.visible = field.parentId === item._metadata.data.id;
|
||||
});
|
||||
setLoadTree(Math.floor(Math.random() * 1000));
|
||||
initTreeTimeout = setTimeout(()=> {
|
||||
initTreeTimeout = setTimeout(() => {
|
||||
prefTreeInit.current = true;
|
||||
}, 10);
|
||||
}
|
||||
else {
|
||||
if(item.isExpanded && item._children && item._children.length > 0 && prefTreeInit.current) {
|
||||
if (item.isExpanded && item._children && item._children.length > 0 && prefTreeInit.current) {
|
||||
pgAdmin.Browser.ptree.tree.setActiveFile(item._children[0], true);
|
||||
}
|
||||
}
|
||||
@@ -343,29 +369,24 @@ export default function PreferencesComponent({ ...props }) {
|
||||
|
||||
// Listen open preferences tree node event to default select first child node on parent node selection.
|
||||
pgAdmin.Browser.Events.on('preferences:tree:opened', (item) => {
|
||||
if (item._fileName == 'Browser' && item.type == 2 && item.isExpanded && item._children && item._children.length > 0 && !prefTreeInit.current) {
|
||||
pgAdmin.Browser.ptree.tree.setActiveFile(item._children[0], false);
|
||||
}
|
||||
else if(prefTreeInit.current) {
|
||||
pgAdmin.Browser.ptree.tree.setActiveFile(item._children[0], true);
|
||||
}
|
||||
pgAdmin.Browser.ptree.tree.setActiveFile(item._children[0], true);
|
||||
});
|
||||
|
||||
// Listen added preferences tree node event to expand the newly added node on tree load.
|
||||
pgAdmin.Browser.Events.on('preferences:tree:added', (item) => {
|
||||
// Check the if newely added node is Directoy call toggle to expand the node.
|
||||
if (item.type == FileType.Directory) {
|
||||
if (item._parent._fileName == 'Browser' && item._parent.isExpanded && !prefTreeInit.current) {
|
||||
pgAdmin.Browser.ptree.tree.setActiveFile(item._parent._children[0], false);
|
||||
}
|
||||
else if (item.type == FileType.Directory) {
|
||||
// Check the if newely added node is Directoy and call toggle to expand the node.
|
||||
pgAdmin.Browser.ptree.tree.toggleDirectory(item);
|
||||
}
|
||||
});
|
||||
|
||||
/* Clear the initTreeTimeout timeout if unmounted */
|
||||
return ()=>{
|
||||
return () => {
|
||||
clearTimeout(initTreeTimeout);
|
||||
};
|
||||
}, [prefTreeData]);
|
||||
|
||||
|
||||
}, []);
|
||||
|
||||
function getControlMappedForType(type) {
|
||||
switch (type) {
|
||||
@@ -414,7 +435,7 @@ export default function PreferencesComponent({ ...props }) {
|
||||
}
|
||||
}
|
||||
|
||||
function getCollectionValue(_metadata, value, initValues) {
|
||||
function getCollectionValue(_metadata, value, initVals) {
|
||||
let val = value;
|
||||
if (typeof (value) == 'object') {
|
||||
if (_metadata[0].type == 'collection' && _metadata[0].schema) {
|
||||
@@ -424,14 +445,7 @@ export default function PreferencesComponent({ ...props }) {
|
||||
value.changed.forEach((chValue) => {
|
||||
pathVersions.push(chValue.version);
|
||||
});
|
||||
initValues[_metadata[0].id].forEach((initVal) => {
|
||||
if (pathVersions.includes(initVal.version)) {
|
||||
pathData.push(value.changed[pathVersions.indexOf(initVal.version)]);
|
||||
}
|
||||
else {
|
||||
pathData.push(initVal);
|
||||
}
|
||||
});
|
||||
getPathData(initVals, pathData, _metadata, value, pathVersions);
|
||||
val = JSON.stringify(pathData);
|
||||
} else {
|
||||
let key_val = {
|
||||
@@ -450,12 +464,23 @@ export default function PreferencesComponent({ ...props }) {
|
||||
return val;
|
||||
}
|
||||
|
||||
function savePreferences(data, initValues) {
|
||||
function getPathData(initVals, pathData, _metadata, value, pathVersions) {
|
||||
initVals[_metadata[0].id].forEach((initVal) => {
|
||||
if (pathVersions.includes(initVal.version)) {
|
||||
pathData.push(value.changed[pathVersions.indexOf(initVal.version)]);
|
||||
}
|
||||
else {
|
||||
pathData.push(initVal);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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; });
|
||||
if (_metadata.length > 0) {
|
||||
let val = getCollectionValue(_metadata, value, initValues);
|
||||
let val = getCollectionValue(_metadata, value, initVal);
|
||||
_data.push({
|
||||
'category_id': _metadata[0]['cid'],
|
||||
'id': parseInt(key),
|
||||
@@ -536,14 +561,14 @@ export default function PreferencesComponent({ ...props }) {
|
||||
location.reload();
|
||||
return true;
|
||||
},
|
||||
function () { props.closeModal(); /*props.panel.close()*/ },
|
||||
function () { props.closeModal();},
|
||||
gettext('Refresh'),
|
||||
gettext('Later')
|
||||
);
|
||||
}
|
||||
// Refresh preferences cache
|
||||
pgAdmin.Browser.cache_preferences(modulesChanged);
|
||||
props.closeModal(); /*props.panel.close()*/
|
||||
props.closeModal();
|
||||
}).catch((err) => {
|
||||
Notify.alert(err.response.data);
|
||||
});
|
||||
@@ -558,7 +583,12 @@ export default function PreferencesComponent({ ...props }) {
|
||||
<Box className={classes.root}>
|
||||
<Box className={clsx(classes.preferences)}>
|
||||
<Box className={clsx(classes.treeContainer)} >
|
||||
<Box className={clsx('aciTree', classes.tree)} id={'treeContainer'}></Box>
|
||||
|
||||
<Box className={clsx('aciTree', classes.tree)} id={'treeContainer'}>
|
||||
{
|
||||
useMemo(() => (prefTreeData && props.renderTree(prefTreeData)), [prefTreeData])
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box className={clsx(classes.preferencesContainer)}>
|
||||
{
|
||||
@@ -576,7 +606,7 @@ export default function PreferencesComponent({ ...props }) {
|
||||
<PgIconButton data-test="dialog-help" onClick={onDialogHelp} icon={<HelpIcon />} title={gettext('Help for this dialog.')} />
|
||||
</Box>
|
||||
<Box className={classes.actionBtn} marginLeft="auto">
|
||||
<DefaultButton className={classes.buttonMargin} onClick={() => { props.closeModal(); /*props.panel.close()*/ }} startIcon={<CloseSharpIcon onClick={() => { props.closeModal(); /*props.panel.close()*/ }} />}>
|
||||
<DefaultButton className={classes.buttonMargin} onClick={() => { props.closeModal();}} startIcon={<CloseSharpIcon onClick={() => { props.closeModal();}} />}>
|
||||
{gettext('Cancel')}
|
||||
</DefaultButton>
|
||||
<PrimaryButton className={classes.buttonMargin} startIcon={<SaveSharpIcon />} disabled={disableSave} onClick={() => { savePreferences(prefChangedData, initValues); }}>
|
||||
@@ -584,8 +614,6 @@ export default function PreferencesComponent({ ...props }) {
|
||||
</PrimaryButton>
|
||||
</Box>
|
||||
</Box>
|
||||
{/* </Box> */}
|
||||
|
||||
</Box >
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -7,62 +7,79 @@
|
||||
// //
|
||||
// //////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import * as React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { Directory } from 'react-aspen';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Directory} from 'react-aspen';
|
||||
import { FileTreeX, TreeModelX } from 'pgadmin4-tree';
|
||||
import {Tree} from '../../../../static/js/tree/tree';
|
||||
import { Tree } from '../../../../static/js/tree/tree';
|
||||
import { ManagePreferenceTreeNodes } from '../../../../static/js/tree/preference_nodes';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
|
||||
var initPreferencesTree = async (pgBrowser, containerElement, data) => {
|
||||
|
||||
export default function PreferencesTree({ pgBrowser, data }) {
|
||||
const pTreeModelX = React.useRef();
|
||||
const onReadyRef = React.useRef();
|
||||
const [loaded, setLoaded] = React.useState(false);
|
||||
|
||||
const MOUNT_POINT = '/preferences';
|
||||
|
||||
// Setup host
|
||||
let ptree = new ManagePreferenceTreeNodes(data);
|
||||
React.useEffect(() => {
|
||||
setLoaded(false);
|
||||
|
||||
// Init Tree with the Tree Parent node '/browser'
|
||||
ptree.init(MOUNT_POINT);
|
||||
// Setup host
|
||||
let ptree = new ManagePreferenceTreeNodes(data);
|
||||
// Init Tree with the Tree Parent node '/browser'
|
||||
ptree.init(MOUNT_POINT);
|
||||
|
||||
const host = {
|
||||
pathStyle: 'unix',
|
||||
getItems: async (path) => {
|
||||
return ptree.readNode(path);
|
||||
},
|
||||
sortComparator: (a, b) => {
|
||||
// No nee to sort Query tool options.
|
||||
if (a._parent && a._parent._fileName == 'Query Tool') return 0;
|
||||
// Sort alphabetically
|
||||
if (a.constructor === b.constructor) {
|
||||
return pgAdmin.natural_sort(a.fileName, b.fileName);
|
||||
}
|
||||
let retval = 0;
|
||||
if (a.constructor === Directory) {
|
||||
retval = -1;
|
||||
} else if (b.constructor === Directory) {
|
||||
retval = 1;
|
||||
}
|
||||
return retval;
|
||||
},
|
||||
};
|
||||
const host = {
|
||||
pathStyle: 'unix',
|
||||
getItems: (path) => {
|
||||
return ptree.readNode(path);
|
||||
|
||||
},
|
||||
sortComparator: (a, b) => {
|
||||
// No nee to sort Query tool options.
|
||||
if (a._parent && a._parent._fileName == 'Query Tool') return 0;
|
||||
// Sort alphabetically
|
||||
if (a.constructor === b.constructor) {
|
||||
return pgAdmin.natural_sort(a.fileName, b.fileName);
|
||||
}
|
||||
let retval = 0;
|
||||
if (a.constructor === Directory) {
|
||||
retval = -1;
|
||||
} else if (b.constructor === Directory) {
|
||||
retval = 1;
|
||||
}
|
||||
return retval;
|
||||
},
|
||||
};
|
||||
|
||||
const pTreeModelX = new TreeModelX(host, MOUNT_POINT);
|
||||
pTreeModelX.current = new TreeModelX(host, MOUNT_POINT);
|
||||
onReadyRef.current = function onReady(handler) {
|
||||
// Initialize preferences Tree
|
||||
pgBrowser.ptree = new Tree(handler, ptree, pgBrowser, 'preferences');
|
||||
// Expand directoy on loading.
|
||||
pTreeModelX.current.root._children.forEach((_d)=> {
|
||||
_d.root.expandDirectory(_d);
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const itemHandle = function onReady(handler) {
|
||||
// Initialize preferences Tree
|
||||
pgBrowser.ptree = new Tree(handler, ptree, pgBrowser, 'preferences');
|
||||
return true;
|
||||
};
|
||||
pTreeModelX.current.root.ensureLoaded().then(() => {
|
||||
setLoaded(true);
|
||||
});
|
||||
}, [data]);
|
||||
|
||||
await pTreeModelX.root.ensureLoaded();
|
||||
if (!loaded || _.isUndefined(pTreeModelX.current) || _.isUndefined(onReadyRef.current)) {
|
||||
return (gettext('Loading...'));
|
||||
}
|
||||
return (<FileTreeX model={pTreeModelX.current} height={'100%'} onReady={onReadyRef.current} />);
|
||||
}
|
||||
|
||||
// Render Browser Tree
|
||||
await render(
|
||||
<FileTreeX model={pTreeModelX} height={'100%'}
|
||||
onReady={itemHandle} />
|
||||
, containerElement);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
initPreferencesTree: initPreferencesTree,
|
||||
PreferencesTree.propTypes = {
|
||||
pgBrowser: PropTypes.any,
|
||||
data: PropTypes.array,
|
||||
ptree: PropTypes.any,
|
||||
};
|
||||
Reference in New Issue
Block a user