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

+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,