Added support to select/deselect objects in the Backup dialog. #642

This commit is contained in:
Nikhil Mohite authored and GitHub committed 2023-09-13 11:07:28 +05:30
1 parent 8c91d40932
commit aa973fc8ae
18 files changed
+715 -34

No files matched your search

+1
View File
@@ -141,6 +141,7 @@
"raf": "^3.4.1",
"rc-dock": "^3.2.9",
"react": "^17.0.1",
"react-arborist": "^3.2.0",
"react-aspen": "^1.1.0",
"react-checkbox-tree": "^1.7.2",
"react-data-grid": "https://github.com/pgadmin-org/react-data-grid.git#200d2f5e02de694e3e9ffbe177c279bc40240fb8",
+268
View File
@@ -0,0 +1,268 @@
import { Checkbox, makeStyles } from '@material-ui/core';
import clsx from 'clsx';
import gettext from 'sources/gettext';
import React, { useEffect, useRef } from 'react';
import { Tree } from 'react-arborist';
import AutoSizer from 'react-virtualized-auto-sizer';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import PropTypes from 'prop-types';
import IndeterminateCheckBoxIcon from '@material-ui/icons/IndeterminateCheckBox';
import EmptyPanelMessage from '../components/EmptyPanelMessage';
import CheckBoxIcon from '@material-ui/icons/CheckBox';
const useStyles = makeStyles((theme) => ({
node: {
display: 'inline-block',
paddingLeft: '1.5rem',
height: '1.5rem'
},
checkboxStyle: {
fill: theme.palette.primary.main
},
tree: {
background: theme.palette.background.default,
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
flex: 1,
},
selectedNode: {
background: theme.otherVars.stepBg,
},
focusedNode: {
background: theme.palette.primary.light,
},
leafNode: {
marginLeft: '1.5rem'
},
}));
export const PgTreeSelectionContext = React.createContext();
export default function PgTreeView({ data = [], hasCheckbox = false, selectionChange = null}) {
let classes = useStyles();
let treeData = data;
const treeObj = useRef();
const [selectedCheckBoxNodes, setSelectedCheckBoxNodes] = React.useState();
const onSelectionChange = () => {
if (hasCheckbox) {
let selectedChildNodes = [];
treeObj.current.selectedNodes.forEach((node) => {
selectedChildNodes.push(node.id);
});
setSelectedCheckBoxNodes(selectedChildNodes);
}
selectionChange?.(treeObj.current.selectedNodes);
};
return (<>
{ treeData.length > 0 ?
<PgTreeSelectionContext.Provider value={_.isUndefined(selectedCheckBoxNodes) ? []: selectedCheckBoxNodes}>
<div className={clsx(classes.tree)}>
<AutoSizer>
{({ width, height }) => (
<Tree
ref={(obj) => {
treeObj.current = obj;
}}
width={width}
height={height}
data={treeData}
>
{
(props) => <Node onNodeSelectionChange={onSelectionChange} hasCheckbox={hasCheckbox} {...props}></Node>
}
</Tree>
)}
</AutoSizer>
</div>
</PgTreeSelectionContext.Provider>
:
<EmptyPanelMessage text={gettext('No objects are found to display')}/>
}
</>
);
}
PgTreeView.propTypes = {
data: PropTypes.array,
selectionChange: PropTypes.func,
hasCheckbox: PropTypes.bool,
};
function Node({ node, style, tree, hasCheckbox, onNodeSelectionChange}) {
const classes = useStyles();
const pgTreeSelCtx = React.useContext(PgTreeSelectionContext);
const [isSelected, setIsSelected] = React.useState(pgTreeSelCtx.includes(node.id) ? true : false);
const [isIndeterminate, setIsIndeterminate] = React.useState(node?.parent.level==0? true: false);
useEffect(()=>{
setIsIndeterminate(node.data.isIndeterminate);
}, [node?.data?.isIndeterminate]);
const onCheckboxSelection = (e) => {
if (hasCheckbox) {
setIsSelected(e.currentTarget.checked);
if (e.currentTarget.checked) {
node.selectMulti(node.id);
if (!node.isLeaf && node.isOpen) {
selectAllChild(node, tree);
} else {
if (node?.parent) {
checkAndSelectParent(node);
}
}
if(node?.level == 0) {
node.data.isIndeterminate = false;
}
} else {
node.deselect(node);
if (!node.isLeaf) {
deselectAllChild(node);
}
if(node?.parent){
delectPrentNode(node.parent);
}
}
}
onNodeSelectionChange();
};
return (
<div style={style} className={clsx(node.isFocused ? classes.focusedNode : '', node.isSelected ? classes.selectedNode : '')} onClick={(e) => {
node.focus();
e.stopPropagation();
}}>
<CollectionArrow node={node} tree={tree} />
{
hasCheckbox ? <Checkbox style={{ padding: 0 }} color="primary" className={clsx(!node.isInternal ? classes.leafNode: null)}
checked={isSelected ? true: false}
checkedIcon={isIndeterminate ? <IndeterminateCheckBoxIcon />: <CheckBoxIcon />}
onChange={onCheckboxSelection}/> :
<span className={clsx(node.data.icon)}></span>
}
<div className={clsx(node.data.icon, classes.node)}>{node.data.name}</div>
</div>
);
}
Node.propTypes = {
node: PropTypes.object,
style: PropTypes.any,
tree: PropTypes.object,
hasCheckbox: PropTypes.bool,
onNodeSelectionChange: PropTypes.func
};
function CollectionArrow({ node, tree }) {
const toggleNode = () => {
node.isInternal && node.toggle();
if (node.isSelected && node.isOpen) {
setTimeout(()=>{
selectAllChild(node, tree);
}, 0);
}
};
return (
<span onClick={toggleNode} >
{node.isInternal ? <ToggleArrowIcon node={node} /> : null}
</span>
);
}
CollectionArrow.propTypes = {
node: PropTypes.object,
tree: PropTypes.object
};
function ToggleArrowIcon({node}){
return (<>{node.isOpen ? <ExpandMoreIcon /> : <ChevronRightIcon />}</>);
}
ToggleArrowIcon.propTypes = {
node: PropTypes.object,
};
function checkAndSelectParent(chNode){
let isAllChildSelected = true;
chNode?.parent?.children?.forEach((child) => {
if (!child.isSelected) {
isAllChildSelected = false;
}
});
if (chNode?.parent) {
if (isAllChildSelected) {
if (chNode.parent?.level == 0) {
chNode.parent.data.isIndeterminate = true;
} else {
chNode.parent.data.isIndeterminate = false;
}
chNode.parent.selectMulti(chNode.parent.id);
} else {
chNode.parent.data.isIndeterminate = true;
chNode.parent.selectMulti(chNode.parent.id);
}
checkAndSelectParent(chNode.parent);
}
}
checkAndSelectParent.propTypes = {
chNode: PropTypes.object
};
function delectPrentNode(chNode){
if (chNode) {
let isAnyChildSelected = false;
chNode.children.forEach((childNode)=>{
if(childNode.isSelected && !isAnyChildSelected){
isAnyChildSelected = true;
}
});
if(isAnyChildSelected){
chNode.data.isIndeterminate = true;
} else {
chNode.deselect(chNode);
}
}
if (chNode?.parent) {
delectPrentNode(chNode.parent);
}
}
function selectAllChild(chNode, tree){
chNode?.children?.forEach(child => {
child.selectMulti(child.id);
if (child?.children) {
selectAllChild(child, tree);
}
});
if (chNode?.parent) {
checkAndSelectParent(chNode);
}
}
function deselectAllChild(chNode){
chNode?.children.forEach(child => {
child.deselect(child);
if (child?.children) {
deselectAllChild(child);
}
});
}
+15 -2
View File
@@ -17,7 +17,7 @@ import { MappedFormControl } from './MappedControl';
import TabPanel from '../components/TabPanel';
import DataGridView from './DataGridView';
import { SCHEMA_STATE_ACTIONS, StateUtilsContext } from '.';
import { InputSQL } from '../components/FormComponents';
import { FormNote, InputSQL } from '../components/FormComponents';
import gettext from 'sources/gettext';
import { evalFunc } from 'sources/utils';
import CustomPropTypes from '../custom_prop_types';
@@ -39,6 +39,10 @@ const useStyles = makeStyles((theme)=>({
nestedControl: {
height: 'unset',
},
fullControl: {
display: 'flex',
flexDirection: 'column'
},
errorMargin: {
/* Error footer space */
paddingBottom: '36px !important',
@@ -306,7 +310,7 @@ export default function FormView({
firstEleID.current = field.id;
}
const currentControl = <MappedFormControl
let currentControl = <MappedFormControl
inputRef={(ele)=>{
if(firstEleRef && firstEleID.current === field.id) {
firstEleRef.current = ele;
@@ -344,6 +348,13 @@ export default function FormView({
]}
/>;
if(field.isFullTab && field.helpMessage) {
currentControl = (<React.Fragment key={`coll-${field.id}`}>
<FormNote key={`note-${field.id}`} text={field.helpMessage}/>
{currentControl}
</React.Fragment>);
}
if(field.inlineNext) {
inlineComponents.push(React.cloneElement(currentControl, {
withContainer: false, controlGridBasis: 3
@@ -422,6 +433,8 @@ export default function FormView({
let contentClassName = [stateUtils.formErr.message ? classes.errorMargin : null];
if(fullTabs.indexOf(tabName) == -1) {
contentClassName.push(classes.nestedControl);
} else {
contentClassName.push(classes.fullControl);
}
return (
<TabPanel key={tabName} value={tabValue} index={i} classNameRoot={clsx(tabsClassname[tabName], isNested ? classes.nestedTabPanel : null)}
@@ -12,7 +12,7 @@ import _ from 'lodash';
import {
FormInputText, FormInputSelect, FormInputSwitch, FormInputCheckbox, FormInputColor,
FormInputFileSelect, FormInputToggle, InputSwitch, FormInputSQL, InputSQL, FormNote, FormInputDateTimePicker, PlainString,
InputSelect, InputText, InputCheckbox, InputDateTimePicker, InputFileSelect, FormInputKeyboardShortcut, FormInputQueryThreshold, FormInputSelectThemes, InputRadio, FormButton
InputSelect, InputText, InputCheckbox, InputDateTimePicker, InputFileSelect, FormInputKeyboardShortcut, FormInputQueryThreshold, FormInputSelectThemes, InputRadio, FormButton, InputTree
} from '../components/FormComponents';
import Privilege from '../components/Privilege';
import { evalFunc } from 'sources/utils';
@@ -35,6 +35,10 @@ function MappedFormControlBase({ type, value, id, onChange, className, visible,
onChange && onChange(changedValue);
}, []);
const onTreeSelection = useCallback((selectedValues)=> {
onChange && onChange(selectedValues);
}, []);
if (!visible) {
return <></>;
}
@@ -89,6 +93,8 @@ function MappedFormControlBase({ type, value, id, onChange, className, visible,
return <FormInputSelectThemes name={name} value={value} onChange={onTextChange} {...props}/>;
case 'button':
return <FormButton name={name} value={value} className={className} onClick={onClick} {...props} />;
case 'tree':
return <InputTree name={name} treeData={props.treeData} onChange={onTreeSelection} {...props}/>;
default:
return <PlainString value={value} {...props} />;
}
@@ -109,7 +115,8 @@ MappedFormControlBase.propTypes = {
noLabel: PropTypes.bool,
onClick: PropTypes.func,
withContainer: PropTypes.bool,
controlGridBasis: PropTypes.number
controlGridBasis: PropTypes.number,
treeData: PropTypes.array,
};
/* Control mapping for grid cell view */
@@ -205,7 +212,7 @@ const ALLOWED_PROPS_FIELD_COMMON = [
'label', 'options', 'optionsLoaded', 'controlProps', 'schema', 'inputRef',
'visible', 'autoFocus', 'helpMessage', 'className', 'optionsReloadBasis',
'orientation', 'isvalidate', 'fields', 'radioType', 'hideBrowseButton', 'btnName', 'hidden',
'withContainer', 'controlGridBasis',
'withContainer', 'controlGridBasis', 'hasCheckbox', 'treeData'
];
const ALLOWED_PROPS_FIELD_FORM = [
@@ -12,6 +12,7 @@ const useStyles = makeStyles((theme)=>({
fontSize: '0.8rem',
display: 'flex',
alignItems: 'center',
flexDirection: 'column'
},
}));
@@ -19,8 +20,7 @@ export default function EmptyPanelMessage({text}) {
const classes = useStyles();
return (
<Box className={classes.root}>
<InfoRoundedIcon style={{height: '1.2rem'}}/>
<span style={{marginLeft: '4px'}}>{text}</span>
<span style={{marginLeft: '4px'}}><InfoRoundedIcon style={{height: '1.2rem'}}/>{text}</span>
</Box>
);
}
@@ -43,6 +43,7 @@ import SelectThemes from './SelectThemes';
import { showFileManager } from '../helpers/showFileManager';
import { withColorPicker } from '../helpers/withColorPicker';
import { useWindowSize } from '../custom_hooks';
import PgTreeView from '../PgTreeView';
const useStyles = makeStyles((theme) => ({
@@ -1275,3 +1276,14 @@ FormButton.propTypes = {
disabled: PropTypes.bool,
btnName: PropTypes.string
};
export function InputTree({hasCheckbox, treeData, onChange, ...props}){
return <PgTreeView data={treeData} hasCheckbox={hasCheckbox} selectionChange={onChange} {...props}></PgTreeView>;
}
InputTree.propTypes = {
hasCheckbox: PropTypes.bool,
treeData: PropTypes.array,
onChange: PropTypes.func,
selectionChange: PropTypes.func,
};
+143 -1
View File
@@ -10,6 +10,7 @@
import json
import os
import copy
import functools
import operator
@@ -27,6 +28,8 @@ from config import PG_DEFAULT_DRIVER
from pgadmin.model import Server, SharedServer
from pgadmin.misc.bgprocess import escape_dquotes_process_arg
from pgadmin.utils.constants import MIMETYPE_APP_JS
from pgadmin.tools.grant_wizard import _get_rows_for_type, \
get_node_sql_with_type, properties, get_data
# set template path for sql scripts
MODULE_NAME = 'backup'
@@ -56,7 +59,8 @@ class BackupModule(PgAdminModule):
list: URL endpoints for backup module
"""
return ['backup.create_server_job', 'backup.create_object_job',
'backup.utility_exists']
'backup.utility_exists', 'backup.objects',
'backup.schema_objects']
# Create blueprint for BackupModule class
@@ -355,6 +359,23 @@ def _get_args_params_values(data, conn, backup_obj_type, backup_file, server,
)
)
if 'objects' in data:
selected_objects = data.get('objects', {})
for _key in selected_objects:
param = 'schema' if _key == 'schema' else 'table'
args.extend(
functools.reduce(operator.iconcat, map(
lambda s: [f'--{param}',
r'{0}.{1}'.format(
driver.qtIdent(conn, s['schema']).replace(
'"', '\"'),
driver.qtIdent(conn, s['name']).replace(
'"', '\"')) if type(
s) is dict else driver.qtIdent(
conn, s).replace('"', '\"')],
selected_objects[_key] or []), [])
)
return args
@@ -505,3 +526,124 @@ def check_utility_exists(sid, backup_obj_type):
)
return make_json_response(success=1)
@blueprint.route(
'/objects/<int:sid>/<int:did>', endpoint='objects'
)
@blueprint.route(
'/objects/<int:sid>/<int:did>/<int:scid>', endpoint='schema_objects'
)
@login_required
def objects(sid, did, scid=None):
"""
This function returns backup objects
Args:
sid: Server ID
did: database ID
scid: schema ID
Returns:
list of objects
"""
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
server = get_server(sid)
if server is None:
return make_json_response(
success=0,
errormsg=_("Could not find the specified server.")
)
from pgadmin.utils.driver import get_driver
from flask_babel import gettext
from pgadmin.utils.ajax import precondition_required
server_info = {}
server_info['manager'] = get_driver(PG_DEFAULT_DRIVER) \
.connection_manager(sid)
server_info['conn'] = server_info['manager'].connection(
did=did)
# If DB not connected then return error to browser
if not server_info['conn'].connected():
return precondition_required(
gettext("Connection to the server has been lost.")
)
# Set template path for sql scripts
server_info['server_type'] = server_info['manager'].server_type
server_info['version'] = server_info['manager'].version
if server_info['server_type'] == 'pg':
server_info['template_path'] = 'grant_wizard/pg/#{0}#'.format(
server_info['version'])
elif server_info['server_type'] == 'ppas':
server_info['template_path'] = 'grant_wizard/ppas/#{0}#'.format(
server_info['version'])
res, msg = get_data(sid, did, scid, 'schema' if scid else 'database',
server_info)
tree_data = {
'table': [],
'view': [],
'materialized view': [],
'foreign table': [],
'sequence': []
}
schema_group = {}
for data in res:
obj_type = data['object_type'].lower()
if obj_type in ['table', 'view', 'materialized view', 'foreign table',
'sequence']:
if data['nspname'] not in schema_group:
schema_group[data['nspname']] = {
'id': data['nspname'],
'name': data['nspname'],
'icon': 'icon-schema',
'children': copy.deepcopy(tree_data),
'is_schema': True,
}
icon_data = {
'materialized view': 'icon-mview',
'foreign table': 'icon-foreign_table'
}
icon = icon_data[obj_type] if obj_type in icon_data \
else data['icon']
schema_group[data['nspname']]['children'][obj_type].append({
'id': f'{data["nspname"]}_{data["name"]}',
'name': data['name'],
'icon': icon,
'schema': data['nspname'],
'type': obj_type,
'_name': '{0}.{1}'.format(data['nspname'], data['name'])
})
schema_group = [dt for k, dt in schema_group.items()]
for ch in schema_group:
children = []
for obj_type, data in ch['children'].items():
if data:
icon_data = {
'materialized view': 'icon-coll-mview',
'foreign table': 'icon-coll-foreign_table'
}
icon = icon_data[obj_type] if obj_type in icon_data \
else f'icon-coll-{obj_type.lower()}',
children.append({
'id': f'{ch["id"]}_{obj_type}',
'name': f'{obj_type.title()}s',
'icon': icon,
'children': data,
'type': obj_type,
'is_collection': True,
})
ch['children'] = children
return make_json_response(
data=schema_group,
success=200
)
+36 -9
View File
@@ -180,6 +180,7 @@ define([
gettext(data.errormsg)
);
} else {
pgBrowser.BgProcessManager.startProcess(data.data.job_id, data.data.desc);
}
},
@@ -237,18 +238,43 @@ define([
let panel = pgBrowser.Node.addUtilityPanel(pgBrowser.stdW.md, pgBrowser.stdH.lg),
j = panel.$container.find('.obj_properties').first();
let schema = that.getUISchema(treeItem, 'backup_objects');
panel.title(gettext(`Backup (${pgBrowser.Nodes[data._type].label}: ${data.label})`));
panel.focus();
let backup_obj_url = '';
if (data._type == 'database') {
let did = data._id;
backup_obj_url = url_for('backup.objects', {
'sid': sid,
'did': did
});
} else if(data._type == 'schema') {
let did = data._pid;
let scid = data._id;
backup_obj_url = url_for('backup.schema_objects', {
'sid': sid,
'did': did,
'scid': scid
});
}
let typeOfDialog = 'backup_objects',
serverIdentifier = that.retrieveServerIdentifier(),
extraData = that.setExtraParameters(typeOfDialog);
api({
url: backup_obj_url,
method: 'GET'
}).then((response)=> {
let objects = response.data.data;
let schema = that.getUISchema(treeItem, 'backup_objects', objects);
panel.title(gettext(`Backup (${pgBrowser.Nodes[data._type].label}: ${data.label})`));
panel.focus();
let typeOfDialog = 'backup_objects',
serverIdentifier = that.retrieveServerIdentifier(),
extraData = that.setExtraParameters(typeOfDialog);
that.showBackupDialog(schema, treeItem, j, data, panel, typeOfDialog, serverIdentifier, extraData);
});
that.showBackupDialog(schema, treeItem, j, data, panel, typeOfDialog, serverIdentifier, extraData);
});
},
getUISchema: function(treeItem, backupType) {
getUISchema: function(treeItem, backupType, objects) {
let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(treeItem);
const selectedNode = pgBrowser.tree.selected();
let itemNodeData = pgBrowser.tree.findNodeByDomElement(selectedNode).getData();
@@ -267,7 +293,8 @@ define([
},
treeNodeInfo,
pgBrowser,
backupType
backupType,
objects
);
},
getGlobalUISchema: function(treeItem) {
@@ -416,7 +416,7 @@ export function getMiscellaneousSchema(fieldOptions) {
}
export default class BackupSchema extends BaseUISchema {
constructor(sectionSchema, typeObjSchema, saveOptSchema, disabledOptionSchema, miscellaneousSchema, fieldOptions = {}, treeNodeInfo=[], pgBrowser=null, backupType='server') {
constructor(sectionSchema, typeObjSchema, saveOptSchema, disabledOptionSchema, miscellaneousSchema, fieldOptions = {}, treeNodeInfo=[], pgBrowser=null, backupType='server', objects={}) {
super({
file: undefined,
format: 'custom',
@@ -431,6 +431,7 @@ export default class BackupSchema extends BaseUISchema {
...fieldOptions,
};
this.treeData = objects;
this.treeNodeInfo = treeNodeInfo;
this.pgBrowser = pgBrowser;
this.backupType = backupType;
@@ -699,6 +700,42 @@ export default class BackupSchema extends BaseUISchema {
label: gettext('Miscellaneous'),
group: gettext('Options'),
schema: obj.getMiscellaneousSchema(),
},
{
id: 'object', label: gettext('Objects'), type: 'group',
visible: isVisibleForServerBackup(obj?.backupType)
},
{
id: 'objects',
label: gettext('objects'),
group: gettext('Objects'),
type: 'tree',
helpMessage: gettext('If Schema(s) is selected then it will take the backup of that selected schema(s) only'),
treeData: this.treeData,
visible: () => {
return isVisibleForServerBackup(obj?.backupType);
},
depChange: (state)=> {
let selectedNodeCollection = {
'schema': [],
'table': [],
'view': [],
'sequence': [],
'foreign table': [],
'materialized view': [],
};
state?.objects?.forEach((node)=> {
if(node.data.is_schema && !node.data?.isIndeterminate) {
selectedNodeCollection['schema'].push(node.data.name);
} else if(['table', 'view', 'materialized view', 'foreign table', 'sequence'].includes(node.data.type) &&
!node.data.is_collection && !selectedNodeCollection['schema'].includes(node.data.schema)) {
selectedNodeCollection[node.data.type].push(node.data);
}
});
return {'objects': selectedNodeCollection};
},
hasCheckbox: true,
isFullTab: true,
}];
}
@@ -27,7 +27,7 @@ class BackupJobTest(BaseTestGenerator):
blobs=True,
schemas=[],
tables=[],
database='postgres'
database='postgres',
),
url='/backup/job/{0}/object',
expected_params=dict(
@@ -48,7 +48,7 @@ class BackupJobTest(BaseTestGenerator):
blobs=True,
schemas=[],
tables=[],
database='postgres'
database='postgres',
),
url='/backup/job/{0}/object',
expected_params=dict(
@@ -60,7 +60,35 @@ class BackupJobTest(BaseTestGenerator):
server_min_version=160000,
message='--large-objects is not supported by EPAS/PG server '
'less than 16'
))
)),
('When backup selected objects ',
dict(
params=dict(
file='test_backup',
format='custom',
verbose=True,
blobs=True,
schemas=[],
tables=[],
database='postgres',
objects={
"schema": [],
"table": [
{"id": "public_test", "name": "test",
"icon": "icon-table", "schema": "public",
"type": "table", "_name": "public.test"}
],
"view": [], "sequence": [], "foreign_table": [],
"mview": []
}
),
url='/backup/job/{0}/object',
expected_params=dict(
expected_cmd_opts=['--verbose', '--format=c', '--blobs'],
not_expected_cmd_opts=[],
expected_exit_code=[1]
)
)),
]
def setUp(self):
+12 -7
View File
@@ -270,12 +270,22 @@ def properties(sid, did, node_id, node_type):
and render into selection page of wizard
"""
res_data, msg = get_data(sid, did, node_id, node_type, server_info)
return make_json_response(
result=res_data,
info=msg,
status=200
)
def get_data(sid, did, node_id, node_type, server_data):
get_schema_sql_url = '/sql/get_schemas.sql'
# unquote encoded url parameter
node_type = unquote(node_type)
server_prop = server_info
server_prop = server_data
res_data = []
failed_objects = []
@@ -350,12 +360,7 @@ def properties(sid, did, node_id, node_type):
msg = gettext('Unable to fetch the {} objects'.format(
", ".join(failed_objects))
)
return make_json_response(
result=res_data,
info=msg,
status=200
)
return res_data, msg
def get_req_data():
-1
View File
@@ -854,7 +854,6 @@ def start_query_tool(trans_id):
Args:
trans_id: unique transaction id
"""
sql = extract_sql_from_network_parameters(
request.data, request.args, request.form
)
@@ -109,7 +109,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
fgcolor: params.fgcolor,
bgcolor: params.bgcolor,
conn_title: getTitle(
pgAdmin, null, selectedNodeInfo, true, _.unescape(params.server_name), _.unescape(params.database_name) || getDatabaseLabel(selectedNodeInfo),
pgAdmin, null, selectedNodeInfo, true, _.unescape(params.server_name), _.escape(params.database_name) || getDatabaseLabel(selectedNodeInfo),
_.unescape(params.role) || _.unescape(params.user), params.is_query_tool == 'true' ? true : false),
server_name: _.unescape(params.server_name),
database_name: _.unescape(params.database_name) || getDatabaseLabel(selectedNodeInfo),
@@ -106,6 +106,7 @@ class StartRunningQuery:
status = False
result = gettext(
'Either transaction object or session object not found.')
return make_json_response(
data={
'status': status, 'result': result,
@@ -37,7 +37,8 @@ describe('BackupSchema', ()=>{
},
{server: {version: 11000}},
pgAdmin.pgBrowser,
'backup_objects'
'backup_objects',
[]
);
it('create object backup', ()=>{
@@ -61,6 +62,43 @@ describe('BackupSchema', ()=>{
});
let backupSelectedSchemaObj = new BackupSchema(
()=> getSectionSchema(),
()=> getTypeObjSchema(),
()=> getSaveOptSchema({nodeInfo: {server: {version: 11000}}}),
()=> getDisabledOptionSchema({nodeInfo: {server: {version: 11000}}}),
()=> getMiscellaneousSchema({nodeInfo: {server: {version: 11000}}}),
{
role: ()=>[],
encoding: ()=>[],
},
{server: {version: 11000}},
pgAdmin.pgBrowser,
'backup_objects',
[{'id': 'public','name': 'public','icon': 'icon-schema', 'children': [{'id': 'public_table','name': 'table','icon': 'icon-coll-table','children': [{'id': 'public_test','name': 'test','icon': 'icon-table','schema': 'public','type': 'table','_name': 'public.test'}],'type': 'table','is_collection': true}],'is_schema': true}]
);
it('create selected object backup', ()=>{
mount(<Theme>
<SchemaView
formType='dialog'
schema={backupSelectedSchemaObj}
viewHelperProps={{
mode: 'create',
}}
onSave={()=>{/*This is intentional (SonarQube)*/}}
onClose={()=>{/*This is intentional (SonarQube)*/}}
onHelp={()=>{/*This is intentional (SonarQube)*/}}
onDataChange={()=>{/*This is intentional (SonarQube)*/}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
disableDialogHelp={false}
/>
</Theme>);
});
let backupServerSchemaObj = new BackupSchema(
()=> getSectionSchema(),
()=> getTypeObjSchema(),
@@ -73,7 +111,8 @@ describe('BackupSchema', ()=>{
},
{server: {version: 11000}},
{serverInfo: {}},
'server'
'server',
[]
);
it('create server backup', ()=>{
+94 -2
View File
@@ -2313,6 +2313,13 @@ __metadata:
languageName: node
linkType: hard
"@react-dnd/asap@npm:^4.0.0":
version: 4.0.1
resolution: "@react-dnd/asap@npm:4.0.1"
checksum: 757db3b5c436a95383b74f187f503321092909401ce9665d9cc1999308a44de22809bf8dbe82c9126bd73b72dd6665bbc4a788e864fc3c243c59f65057a4f87f
languageName: node
linkType: hard
"@react-dnd/asap@npm:^5.0.1":
version: 5.0.2
resolution: "@react-dnd/asap@npm:5.0.2"
@@ -2320,6 +2327,13 @@ __metadata:
languageName: node
linkType: hard
"@react-dnd/invariant@npm:^2.0.0":
version: 2.0.0
resolution: "@react-dnd/invariant@npm:2.0.0"
checksum: ef1e989920d70b15c80dccb01af9b598081d76993311aa22d2e9a3ec41d10a88540eeec4b4de7a8b2a2ea52dfc3495ab45e39192c2d27795a9258bd6b79d000e
languageName: node
linkType: hard
"@react-dnd/invariant@npm:^4.0.1":
version: 4.0.2
resolution: "@react-dnd/invariant@npm:4.0.2"
@@ -2327,6 +2341,13 @@ __metadata:
languageName: node
linkType: hard
"@react-dnd/shallowequal@npm:^2.0.0":
version: 2.0.0
resolution: "@react-dnd/shallowequal@npm:2.0.0"
checksum: b5bbdc795d65945bb7ba2322bed5cf8d4c6fe91dced98c3b10e3d16822c438f558751135ff296f8d1aa1eaa9d0037dacab2b522ca5eb812175123b9996966dcb
languageName: node
linkType: hard
"@react-dnd/shallowequal@npm:^4.0.1":
version: 4.0.2
resolution: "@react-dnd/shallowequal@npm:4.0.2"
@@ -5855,6 +5876,17 @@ __metadata:
languageName: node
linkType: hard
"dnd-core@npm:14.0.1":
version: 14.0.1
resolution: "dnd-core@npm:14.0.1"
dependencies:
"@react-dnd/asap": ^4.0.0
"@react-dnd/invariant": ^2.0.0
redux: ^4.1.1
checksum: dbc50727f53baad1cb1e0430a2a1b81c5c291389322f90fdc46edeb2fd49cc206ce4fa30a95afb53d88238945228e34866d7465f7ea49285c296baf883551301
languageName: node
linkType: hard
"dnd-core@npm:^16.0.1":
version: 16.0.1
resolution: "dnd-core@npm:16.0.1"
@@ -12275,6 +12307,22 @@ __metadata:
languageName: node
linkType: hard
"react-arborist@npm:^3.2.0":
version: 3.2.0
resolution: "react-arborist@npm:3.2.0"
dependencies:
react-dnd: ^14.0.3
react-dnd-html5-backend: ^14.0.1
react-window: ^1.8.6
redux: ^4.1.1
use-sync-external-store: ^1.2.0
peerDependencies:
react: ">= 16.14"
react-dom: ">= 16.14"
checksum: 452e8793520b4d69ad897b7bf2584a6ec763f182d6a4e942229e283fe000d579724a3c5c125504a59b66ef68ff898cef41575ccfca5e18a564c1f9e223ef62ca
languageName: node
linkType: hard
"react-aspen@npm:^1.1.0":
version: 1.2.0
resolution: "react-aspen@npm:1.2.0"
@@ -12317,6 +12365,15 @@ __metadata:
languageName: node
linkType: hard
"react-dnd-html5-backend@npm:^14.0.1":
version: 14.1.0
resolution: "react-dnd-html5-backend@npm:14.1.0"
dependencies:
dnd-core: 14.0.1
checksum: 6aa8d62c6b2288893b3f216d476d2f84495b40d33578ba9e3a5051dc093a71dc59700e6927ed7ac596ff8d7aa3b3f29404f7d173f844bd6144ed633403dd8e96
languageName: node
linkType: hard
"react-dnd-html5-backend@npm:^16.0.1":
version: 16.0.1
resolution: "react-dnd-html5-backend@npm:16.0.1"
@@ -12326,6 +12383,31 @@ __metadata:
languageName: node
linkType: hard
"react-dnd@npm:^14.0.3":
version: 14.0.5
resolution: "react-dnd@npm:14.0.5"
dependencies:
"@react-dnd/invariant": ^2.0.0
"@react-dnd/shallowequal": ^2.0.0
dnd-core: 14.0.1
fast-deep-equal: ^3.1.3
hoist-non-react-statics: ^3.3.2
peerDependencies:
"@types/hoist-non-react-statics": ">= 3.3.1"
"@types/node": ">= 12"
"@types/react": ">= 16"
react: ">= 16.14"
peerDependenciesMeta:
"@types/hoist-non-react-statics":
optional: true
"@types/node":
optional: true
"@types/react":
optional: true
checksum: 464e231de8c2b79546049a1600b67b1df0b7f762f23c688d3e9aeddbf334b1e64931ef91d0129df3c8be255f0af76e89426729dbcbefe4bdc09b0f665d2da368
languageName: node
linkType: hard
"react-dnd@npm:^16.0.1":
version: 16.0.1
resolution: "react-dnd@npm:16.0.1"
@@ -12580,7 +12662,7 @@ __metadata:
languageName: node
linkType: hard
"react-window@npm:^1.3.1, react-window@npm:^1.8.5":
"react-window@npm:^1.3.1, react-window@npm:^1.8.5, react-window@npm:^1.8.6":
version: 1.8.9
resolution: "react-window@npm:1.8.9"
dependencies:
@@ -12710,7 +12792,7 @@ __metadata:
languageName: node
linkType: hard
"redux@npm:^4.2.0":
"redux@npm:^4.1.1, redux@npm:^4.2.0":
version: 4.2.1
resolution: "redux@npm:4.2.1"
dependencies:
@@ -13112,6 +13194,7 @@ __metadata:
raf: ^3.4.1
rc-dock: ^3.2.9
react: ^17.0.1
react-arborist: ^3.2.0
react-aspen: ^1.1.0
react-checkbox-tree: ^1.7.2
react-data-grid: "https://github.com/pgadmin-org/react-data-grid.git#200d2f5e02de694e3e9ffbe177c279bc40240fb8"
@@ -14928,6 +15011,15 @@ __metadata:
languageName: node
linkType: hard
"use-sync-external-store@npm:^1.2.0":
version: 1.2.0
resolution: "use-sync-external-store@npm:1.2.0"
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
checksum: 5c639e0f8da3521d605f59ce5be9e094ca772bd44a4ce7322b055a6f58eeed8dda3c94cabd90c7a41fb6fa852210092008afe48f7038792fd47501f33299116a
languageName: node
linkType: hard
"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1":
version: 1.0.2
resolution: "util-deprecate@npm:1.0.2"