mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2026-10-26 06:30:06 -05:00
Added support to select/deselect objects in the Backup dialog. #642
This commit is contained in:
1 parent
8c91d40932
commit
aa973fc8ae
18 files changed
+715
-34
No files matched your search
@@ -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
|
||||
)
|
||||
@@ -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):
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user