public inbox for [email protected]help / color / mirror / Atom feed
[pgAdmin][RM3794]:Allow User to Change Database Connection from an Open Query Tool Tab 18+ messages / 4 participants [nested] [flat]
* [pgAdmin][RM3794]:Allow User to Change Database Connection from an Open Query Tool Tab @ 2020-09-17 10:45 Nikhil Mohite <[email protected]> 0 siblings, 1 reply; 18+ messages in thread From: Nikhil Mohite @ 2020-09-17 10:45 UTC (permalink / raw) To: pgadmin-hackers Hi Team, Regarding RM-3794 <https://redmine.postgresql.org/issues/3794; allow the user to change the database connection from an open query tool: I have implemented the feature and also added documentation for it. PFA patch. -- *Thanks & Regards,* *Nikhil Mohite* *Software Engineer.* *EDB Postgres* <https://www.enterprisedb.com/; *Mob.No: +91-7798364578.* Attachments: [application/octet-stream] RM_3794_v1.patch (197.4K, 3-RM_3794_v1.patch) download | inline diff: diff --git a/docs/en_US/images/new_connection_dialog.png b/docs/en_US/images/new_connection_dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..c2f877dc9771b8b697c925438e4abb4f7436db35 GIT binary patch literal 44942 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 F{{cYfFA@L% literal 0 HcmV?d00001 diff --git a/docs/en_US/images/new_connection_options.png b/docs/en_US/images/new_connection_options.png new file mode 100644 index 0000000000000000000000000000000000000000..3de8fc4ee9a50d1c93c35b274cc21fec784c1138 GIT binary patch literal 47015 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 O-}%!Hr)q!oyY_$PkK4om literal 0 HcmV?d00001 diff --git a/docs/en_US/query_tool.rst b/docs/en_US/query_tool.rst index 83933f0..545fb95 100644 --- a/docs/en_US/query_tool.rst +++ b/docs/en_US/query_tool.rst @@ -300,3 +300,24 @@ transaction status by clicking on the status icon in the Query Tool: .. image:: images/query_tool_connection_status.png :alt: Query tool connection and transaction statuses :align: center + +Change connection +***************** + +User can connect to another server or database from existing open session of query tool. + +* Click on the connection link next to connection status. +* Now click on the *<New Connection>* option from the dropdown. + +.. image:: images/new_connection_options.png + :alt: Query tool connection options + :align: center + +* Now select server, database, user, and password to connect and click OK. + +.. image:: images/new_connection_dialog.png + :alt: Query tool connection dialog + :align: center + +* A newly created connection will now get listed in the options. +* To connect, select the newly created connection from the dropdown list. diff --git a/web/pgadmin/browser/server_groups/servers/roles/tests/utils.py b/web/pgadmin/browser/server_groups/servers/roles/tests/utils.py index 3a7ee58..028ee64 100644 --- a/web/pgadmin/browser/server_groups/servers/roles/tests/utils.py +++ b/web/pgadmin/browser/server_groups/servers/roles/tests/utils.py @@ -152,3 +152,38 @@ def delete_role(connection, role_names): exception = "Error while deleting role: %s: line:%s %s" % ( file_name, sys.exc_traceback.tb_lineno, exception) print(exception, file=sys.stderr) + + +def create_role_with_password(server, role_name, role_password): + """ + This function create the role. + :param server: + :param role_name: + :param role_password: + :return: + """ + try: + connection = utils.get_db_connection(server['db'], + server['username'], + server['db_password'], + server['host'], + server['port'], + server['sslmode']) + pg_cursor = connection.cursor() + pg_cursor.execute( + "CREATE ROLE %s LOGIN PASSWORD '%s'" % (role_name, role_password)) + connection.commit() + # Get 'oid' from newly created tablespace + pg_cursor.execute( + "SELECT pr.oid from pg_catalog.pg_roles pr WHERE pr.rolname='%s'" % + role_name) + oid = pg_cursor.fetchone() + role_id = '' + if oid: + role_id = oid[0] + connection.close() + return role_id + except Exception as exception: + exception = "Error while deleting role: %s: line:%s %s" % ( + file_name, sys.exc_traceback.tb_lineno, exception) + print(exception, file=sys.stderr) diff --git a/web/pgadmin/model/__init__.py b/web/pgadmin/model/__init__.py index b33adc0..d24a350 100644 --- a/web/pgadmin/model/__init__.py +++ b/web/pgadmin/model/__init__.py @@ -94,6 +94,15 @@ class ServerGroup(db.Model): name = db.Column(db.String(128), nullable=False) __table_args__ = (db.UniqueConstraint('user_id', 'name'),) + @property + def serialize(self): + """Return object data in easily serializable format""" + return { + 'id': self.id, + 'user_id': self.user_id, + 'name': self.name, + } + class Server(db.Model): """Define a registered Postgres server""" @@ -175,6 +184,44 @@ class Server(db.Model): tunnel_password = db.Column(db.String(64), nullable=True) shared = db.Column(db.Boolean(), nullable=False) + @property + def serialize(self): + """Return object data in easily serializable format""" + return { + "id": self.id, + "user_id": self.user_id, + "servergroup_id": self.servergroup_id, + "name": self.name, + "host": self.host, + "hostaddr": self.hostaddr, + "port": self.port, + "maintenance_db": self.maintenance_db, + "username": self.username, + "password": self.password, + "save_password": self.save_password, + "role": self.role, + "ssl_mode": self.ssl_mode, + "comment": self.comment, + "discovery_id": self.discovery_id, + "db_res": self.db_res, + "passfile": self.passfile, + "sslcert": self.sslcert, + "sslkey": self.sslkey, + "sslrootcert": self.sslrootcert, + "sslcrl": self.sslcrl, + "sslcompression": self.sslcompression, + "bgcolor": self.bgcolor, + "fgcolor": self.fgcolor, + "service": self.service, + "connect_timeout": self.connect_timeout, + "use_ssh_tunnel": self.use_ssh_tunnel, + "tunnel_host": self.tunnel_host, + "tunnel_port": self.tunnel_port, + "tunnel_authentication": self.tunnel_authentication, + "tunnel_identity_file": self.tunnel_identity_file, + "tunnel_password": self.tunnel_password + } + class ModulePreference(db.Model): """Define a preferences table for any modules.""" diff --git a/web/pgadmin/static/js/sqleditor/new_connection_dialog.js b/web/pgadmin/static/js/sqleditor/new_connection_dialog.js new file mode 100644 index 0000000..a56d736 --- /dev/null +++ b/web/pgadmin/static/js/sqleditor/new_connection_dialog.js @@ -0,0 +1,252 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import gettext from 'sources/gettext'; +import url_for from 'sources/url_for'; +import $ from 'jquery'; +import Alertify from 'pgadmin.alertifyjs'; +import pgAdmin from 'sources/pgadmin'; +import Backform from 'pgadmin.backform'; +import newConnectionDialogModel from 'sources/sqleditor/new_connection_dialog_model'; + + +let NewConnectionDialog = { + 'dialog': function(handler, reconnect) { + let url = url_for('sqleditor.get_new_connection_data', { + 'sid': handler.url_params.sid, + 'sgid': handler.url_params.sgid, + }); + + if(reconnect) { + url += '?connect=1'; + } + + let title = gettext('Connect to server'); + + $.ajax({ + url: url, + headers: { + 'Cache-Control' : 'no-cache', + }, + }).done(function (res) { + let response = res.data.result; + response.database_list = []; + response.user_list = []; + if (Alertify.newConnectionDialog) { + delete Alertify.newConnectionDialog; + } + + // Create Dialog + Alertify.dialog('newConnectionDialog', function factory() { + let $container = $('<div class=\'new-connection-dialog\'></div>'); + return { + main: function(message) { + this.msg = message; + }, + build: function() { + this.elements.content.appendChild($container.get(0)); + Alertify.pgDialogBuild.apply(this); + }, + setup: function(){ + return { + buttons: [ + { + text: '', + key: 112, + className: 'btn btn-primary-icon pull-left fa fa-question pg-alertify-icon-button', + attrs: { + name: 'dialog_help', + type: 'button', + label: gettext('Help'), + 'aria-label': gettext('Help'), + url: url_for('help.static', { + 'filename': 'query_tool.html', + }), + }, + }, + { + text: gettext('Cancel'), + key: 27, + className: 'btn btn-secondary fa fa-times pg-alertify-button', + 'data-btn-name': 'cancel', + }, { + text: gettext('OK'), + key: 13, + className: 'btn btn-primary fa fa-check pg-alertify-button', + 'data-btn-name': 'ok', + }, + ], + // Set options for dialog + options: { + title: title, + //disable both padding and overflow control. + padding: !1, + overflow: !1, + model: 0, + resizable: true, + maximizable: false, + pinnable: false, + closableByDimmer: false, + modal: false, + autoReset: false, + closable: true, + }, + }; + }, + prepare: function() { + let self = this; + $container.html(''); + // Disable Ok button + this.__internal.buttons[2].element.disabled = true; + + // Status bar + this.statusBar = $( + '<div class=\'pg-prop-status-bar pg-el-xs-12 d-none\'>' + + ' <div class="error-in-footer"> ' + + ' <div class="d-flex px-2 py-1"> ' + + ' <div class="pr-2"> ' + + ' <i class="fa fa-exclamation-triangle text-danger" aria-hidden="true"></i> ' + + ' </div> ' + + ' <div class="alert-text" role="alert"></div> ' + + ' </div> ' + + ' </div> ' + + '</div>').appendTo($container); + + // To show progress on filter Saving/Updating on AJAX + this.showNewConnectionProgress = $( + `<div id="show_filter_progress" class="pg-sp-container sql-editor-busy-fetching d-none"> + <div class="pg-sp-content"> + <div class="row"><div class="col-12 pg-sp-icon sql-editor-busy-icon"></div></div> + <div class="row"><div class="col-12 pg-sp-text sql-editor-busy-text">` + gettext('Loading data...') + `</div></div> + </div> + </div>` + ).appendTo($container); + $( + self.showNewConnectionProgress[0] + ).removeClass('d-none'); + + self.newConnCollectionModel = newConnectionDialogModel(response, handler.url_params.sgid, handler.url_params.sid); + let fields = Backform.generateViewSchema(null, self.newConnCollectionModel, 'create', null, null, true); + + let view = this.view = new Backform.Dialog({ + el: '<div></div>', + model: self.newConnCollectionModel, + schema: fields, + }); + + $(this.elements.body.childNodes[0]).addClass( + 'alertify_tools_dialog_properties obj_properties' + ); + + $container.append(view.render().$el); + + // Enable/disable save button and show/hide statusbar based on session + view.listenTo(view.model, 'pgadmin-session:start', function() { + view.listenTo(view.model, 'pgadmin-session:invalid', function(msg) { + self.statusBar.removeClass('d-none'); + $(self.statusBar.find('.alert-text')).html(msg); + // Disable Okay button + self.__internal.buttons[2].element.disabled = true; + }); + + view.listenTo(view.model, 'pgadmin-session:valid', function() { + self.statusBar.addClass('d-none'); + $(self.statusBar.find('.alert-text')).html(''); + // Enable Okay button + self.__internal.buttons[2].element.disabled = false; + }); + }); + + view.listenTo(view.model, 'pgadmin-session:stop', function() { + view.stopListening(view.model, 'pgadmin-session:invalid'); + view.stopListening(view.model, 'pgadmin-session:valid'); + }); + + // Starts monitoring changes to model + view.model.startNewSession(); + + // Hide Progress ... + $( + self.showNewConnectionProgress[0] + ).addClass('d-none'); + }, + callback: function(e) { + let self = this; + if (e.button.element.name == 'dialog_help') { + e.cancel = true; + pgAdmin.Browser.showHelp(e.button.element.name, e.button.element.getAttribute('url'), + null, null); + return; + } else if (e.button['data-btn-name'] === 'ok') { + e.cancel = true; // Do not close dialog + let newConnCollectionModel = this.newConnCollectionModel.toJSON(); + let selected_database_name = null; + response.database_list.forEach(function(data){ + if(newConnCollectionModel['database'] == data['value']) { + selected_database_name = data['label']; + return false; + } + }); + let is_create_connection = true; + let title = selected_database_name + '/' + newConnCollectionModel['user'] + '@' + response.server_name; + handler.gridView.connection_list.forEach(function(connection_data){ + if(parseInt(connection_data['server']) == newConnCollectionModel['server'] + && parseInt(connection_data['database']) == newConnCollectionModel['database'] + && connection_data['user'] == newConnCollectionModel['user']) { + is_create_connection = false; + // break for loop by return false. + return false; + } + + if(title == connection_data['title']) { + is_create_connection = false; + return false; + } + }); + if(!is_create_connection) { + let errmsg = 'Connection with this configuration already present.'; + Alertify.error(errmsg); + }else { + let connection_details = { + 'server_group': handler.gridView.handler.url_params.sgid, + 'server': newConnCollectionModel['server'], + 'database': newConnCollectionModel['database'], + 'title': title, + 'user': newConnCollectionModel['user'], + 'password': newConnCollectionModel['password'], + }; + handler.gridView.on_change_connection(connection_details, self); + } + } else { + self.close(); + } + }, + }; + }); + setTimeout(function(){ + Alertify.newConnectionDialog('Connect to server.').resizeTo(pgAdmin.Browser.stdW.md,pgAdmin.Browser.stdH.md); + }, 500); + }).fail(function(error) { + Alertify.alert().setting({ + 'title': gettext('Connection lost'), + 'label':gettext('Ok'), + 'message': gettext('Connection to the server has been lost.'), + 'onok': function(){ + alert(error); + //Close the window after connection is lost + window.close(); + }, + }).show(); + }); + + }, + +}; + +module.exports = NewConnectionDialog; diff --git a/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js b/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js new file mode 100644 index 0000000..7f309cd --- /dev/null +++ b/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js @@ -0,0 +1,228 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import gettext from 'sources/gettext'; +import _ from 'underscore'; +import $ from 'jquery'; +import pgAdmin from 'sources/pgadmin'; +import Backform from 'pgadmin.backform'; +import url_for from 'sources/url_for'; +import alertify from 'pgadmin.alertifyjs'; + +export default function newConnectionDialogModel(response, sgid, sid) { + + let server_name = ''; + let database_name = ''; + + let NewConnectionSelect2Control = Backform.Select2Control.extend({ + fetchData: function(){ + let self = this; + url = self.field.get('url'); + + let url = url_for(url, { + 'sid': self.model.attributes.server, + 'sgid': sgid, + }); + + $.ajax({ + async: false, + url: url, + headers: { + 'Cache-Control' : 'no-cache', + }, + }).done(function (res) { + var transform = self.field.get('transform'); + if(res.data.status){ + let data = res.data.result.data; + + if (transform && _.isFunction(transform)) { + self.field.set('options', transform.bind(self, data)); + } else { + self.field.set('options', data); + } + } else { + if (transform && _.isFunction(transform)) { + self.field.set('options', transform.bind(self, [])); + } else { + self.field.set('options', []); + } + alertify.error(res.data.msg); + } + }).fail(function(e){ + let msg = ''; + if(e.status == 404) { + msg = 'Unable to find url.'; + } else { + msg = e.responseJSON.errormsg; + } + alertify.error(msg); + }); + }, + render: function() { + this.fetchData(); + return Backform.Select2Control.prototype.render.apply(this, arguments); + }, + onChange: function() { + Backform.Select2Control.prototype.onChange.apply(this, arguments); + }, + }); + + let newConnectionModel = pgAdmin.Browser.DataModel.extend({ + idAttribute: 'name', + defaults: { + server: parseInt(sid), + database: null, + user: null, + password: null, + server_name: server_name, + database_name: database_name, + }, + schema: [{ + id: 'server', + name: 'server', + label: gettext('Server'), + type: 'text', + editable: true, + disabled: false, + select2: { + allowClear: false, + }, + control: Backform.Select2Control.extend({ + onChange: function() { + this.model.attributes.database = null; + this.model.attributes.user = null; + let self = this; + Backform.Select2Control.prototype.onChange.apply(this, arguments); + + response.server_list.forEach(function(obj){ + if(obj.id==self.model.changed.server) { + response.server_name = obj.name; + } + }); + }, + }), + options: function() { + return _.map(response.server_list, (obj) => { + if (obj.id == parseInt(sid)) + response.server_name = obj.name; + + return { + value: obj.id, + label: obj.name, + }; + }); + }, + }, + { + id: 'database', + name: 'database', + label: gettext('Database'), + type: 'text', + editable: true, + disabled: function(m) { + let self_local = this; + if (!_.isUndefined(m.get('server')) && !_.isNull(m.get('server')) + && m.get('server') !== '') { + setTimeout(function() { + if(self_local.options.length) { + m.set('database', self_local.options[0].value); + } + }, 10); + return false; + } + + return true; + }, + deps: ['server'], + url: 'sqleditor.get_new_connection_database', + select2: { + allowClear: false, + width: '100%', + first_empty: true, + select_first: false, + }, + extraClasses:['new-connection-dialog-style'], + control: NewConnectionSelect2Control, + transform: function(data) { + response.database_list = data; + return data; + }, + }, + { + id: 'user', + name: 'user', + label: gettext('User'), + type: 'text', + editable: true, + deps: ['server'], + select2: { + allowClear: false, + width: '100%', + }, + control: NewConnectionSelect2Control, + url: 'sqleditor.get_new_connection_user', + disabled: function(m) { + let self_local = this; + if (!_.isUndefined(m.get('server')) && !_.isNull(m.get('server')) + && m.get('server') !== '') { + setTimeout(function() { + if(self_local.options.length) { + m.set('user', self_local.options[0].value); + } + }, 10); + return false; + } + return true; + }, + }, + { + id: 'password', + name: 'password', + label: gettext('Password'), + type: 'password', + editable: true, + disabled: false, + deps: ['user'], + control: Backform.InputControl.extend({ + render: function() { + let self = this; + self.model.attributes.password = null; + Backform.InputControl.prototype.render.apply(self, arguments); + return self; + }, + onChange: function() { + let self = this; + Backform.InputControl.prototype.onChange.apply(self, arguments); + }, + }), + }, + ], + validate: function() { + let msg = null; + this.errorModel.clear(); + if(_.isUndefined(this.get('database')) || _.isNull(this.get('database'))){ + msg = gettext('Please select database'); + this.errorModel.set('database', msg); + return msg; + } else if(_.isUndefined(this.get('database')) || _.isUndefined(this.get('user'))|| _.isNull(this.get('user'))) { + msg = gettext('Please select user'); + this.errorModel.set('user', msg); + return msg; + } else if((this.attributes.password == '' || _.isUndefined(this.get('password')) || _.isNull(this.get('password')))) { + msg = gettext('Please enter password'); + this.errorModel.set('password', msg); + return msg; + } + return null; + }, + }); + + let model = new newConnectionModel(); + return model; +} diff --git a/web/pgadmin/static/scss/_alert.scss b/web/pgadmin/static/scss/_alert.scss index dac552b..836f0af 100644 --- a/web/pgadmin/static/scss/_alert.scss +++ b/web/pgadmin/static/scss/_alert.scss @@ -92,6 +92,7 @@ right: 0; left: 0; bottom: 0; + z-index: 1; } .pg-prop-status-bar { diff --git a/web/pgadmin/tools/datagrid/__init__.py b/web/pgadmin/tools/datagrid/__init__.py index f5fc78c..5f32e74 100644 --- a/web/pgadmin/tools/datagrid/__init__.py +++ b/web/pgadmin/tools/datagrid/__init__.py @@ -18,21 +18,22 @@ from flask import Response, url_for, session, request, make_response from werkzeug.useragents import UserAgent from flask import current_app as app, render_template from flask_babelex import gettext -from flask_security import login_required +from flask_security import login_required, current_user from pgadmin.tools.sqleditor.command import ObjectRegistry, SQLFilter +from pgadmin.tools.sqleditor import check_transaction_status from pgadmin.utils import PgAdminModule from pgadmin.utils.ajax import make_json_response, bad_request, \ - internal_server_error + internal_server_error, unauthorized from config import PG_DEFAULT_DRIVER -from pgadmin.model import Server +from pgadmin.model import Server, User from pgadmin.utils.driver import get_driver from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost from pgadmin.utils.preferences import Preferences from pgadmin.settings import get_setting from pgadmin.browser.utils import underscore_unescape from pgadmin.utils.exception import ObjectGone -from pgadmin.utils.constants import MIMETYPE_APP_JS +from pgadmin.utils.constants import MIMETYPE_APP_JS, UNAUTH_REQ MODULE_NAME = 'datagrid' @@ -73,7 +74,8 @@ class DataGridModule(PgAdminModule): 'datagrid.filter_validate', 'datagrid.filter', 'datagrid.panel', - 'datagrid.close' + 'datagrid.close', + 'datagrid.update_query_tool_connection' ] def on_logout(self, user): @@ -320,10 +322,23 @@ def initialize_query_tool(trans_id, sgid, sid, did=None): req_args['recreate'] == '1'): connect = False + is_error, errmsg, conn_id, version = _init_query_tool(trans_id, connect, + sgid, sid, did) + if is_error: + return errmsg + + return make_json_response( + data={ + 'connId': str(conn_id), + 'serverVersion': version, + } + ) + + +def _init_query_tool(trans_id, connect, sgid, sid, did, **kwargs): # Create asynchronous connection using random connection id. conn_id = str(random.randint(1, 9999999)) - # Use Maintenance database OID manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) if did is None: @@ -334,7 +349,7 @@ def initialize_query_tool(trans_id, sgid, sid, did=None): ) except Exception as e: app.logger.error(e) - return internal_server_error(errormsg=str(e)) + return True, internal_server_error(errormsg=str(e)), '', '' try: conn = manager.connection(did=did, conn_id=conn_id, @@ -342,16 +357,26 @@ def initialize_query_tool(trans_id, sgid, sid, did=None): use_binary_placeholder=True, array_to_string=True) if connect: - status, msg = conn.connect() + user = None + password = None + + if 'user' in kwargs and 'password' in kwargs: + user = kwargs['user'] + password = kwargs['password'] + + if user: + status, msg = conn.connect(user=user, password=password) + else: + status, msg = conn.connect() if not status: app.logger.error(msg) - return internal_server_error(errormsg=str(msg)) + return True, internal_server_error(errormsg=str(msg)), '', '' except (ConnectionLost, SSHTunnelConnectionLost) as e: app.logger.error(e) raise except Exception as e: app.logger.error(e) - return internal_server_error(errormsg=str(e)) + return True, internal_server_error(errormsg=str(e)), '', '' if 'gridData' not in session: sql_grid_data = dict() @@ -373,10 +398,79 @@ def initialize_query_tool(trans_id, sgid, sid, did=None): # Store the grid dictionary into the session variable session['gridData'] = sql_grid_data + return False, '', conn_id, manager.version + + [email protected]( + '/initialize/query_tool/update_connection/<int:trans_id>/' + '<int:sgid>/<int:sid>/<int:did>', + methods=["POST"], endpoint='update_query_tool_connection' +) +def update_query_tool_connection(trans_id, sgid, sid, did): + # Remove transaction Id. + with query_tool_close_session_lock: + data = dict() + if request.data: + data = json.loads(request.data, encoding='utf-8') + + if 'gridData' not in session: + return make_json_response(data={'status': True}) + + grid_data = session['gridData'] + + # Return from the function if transaction id not found + if str(trans_id) not in grid_data: + return make_json_response(data={'status': True}) + + connect = True + + req_args = request.args + if ('recreate' in req_args and + req_args['recreate'] == '1'): + connect = False + + new_trans_id = str(random.randint(1, 9999999)) + kwargs = { + 'user': data['user'], + 'password': data['password'] + } + + is_error, errmsg, conn_id, version = _init_query_tool( + new_trans_id, connect, sgid, sid, did, **kwargs) + + if is_error: + return errmsg + else: + try: + # Check the transaction and connection status + status, error_msg, conn, trans_obj, session_obj = \ + check_transaction_status(trans_id) + + status, error_msg, new_conn, new_trans_obj, new_session_obj = \ + check_transaction_status(new_trans_id) + + new_session_obj['primary_keys'] = session_obj[ + 'primary_keys'] if 'primary_keys' in session_obj else None + new_session_obj['columns_info'] = session_obj[ + 'columns_info'] if 'columns_info' in session_obj else None + new_session_obj['client_primary_key'] = session_obj[ + 'client_primary_key'] if 'client_primary_key'\ + in session_obj else None + + close_query_tool_session(trans_id) + # Remove the information of unique transaction id from the + # session variable. + grid_data.pop(str(trans_id), None) + session['gridData'] = grid_data + except Exception as e: + app.logger.error(e) + # return internal_server_error(errormsg=str(e)) + return make_json_response( data={ 'connId': str(conn_id), - 'serverVersion': manager.version, + 'serverVersion': version, + 'tran_id': new_trans_id } ) diff --git a/web/pgadmin/tools/datagrid/templates/datagrid/index.html b/web/pgadmin/tools/datagrid/templates/datagrid/index.html index e81a42a..fb2526f 100644 --- a/web/pgadmin/tools/datagrid/templates/datagrid/index.html +++ b/web/pgadmin/tools/datagrid/templates/datagrid/index.html @@ -391,8 +391,17 @@ title="" role="img"> </i> </div> - <div class="editor-title" - style="background-color: {% if fgcolor %}{{ bgcolor or '#FFFFFF' }}{% endif %}; color: {% if fgcolor %}{{ fgcolor }}{% endif %};"> </div> + <div class="connection-info btn-group mr-1" role="group" aria-label=""> + <div class="editor-title" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" + style="background-color: {% if fgcolor %}{{ bgcolor or '#FFFFFF' }}{% endif %}; color: {% if fgcolor %}{{ fgcolor }}{% endif %};"> + </div> + <span class="conn-info-dd dropdown-toggle dropdown-toggle-split" + data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span> + <ul class="dropdown-menu" id="connections-list"> + </ul> + </div> + + </div> <div id="editor-panel" tabindex="0"> <div id="fetching_data" class="pg-sp-container sql-editor-busy-fetching"> @@ -455,6 +464,7 @@ require(['sources/generated/browser_nodes', 'sources/generated/codemirror', 'sou var script_type_url = ''; {% endif %} // Start the query tool. + sqlEditorController.start( {{ uniqueId }}, {{ url_params|safe}}, diff --git a/web/pgadmin/tools/datagrid/tests/__init__.py b/web/pgadmin/tools/datagrid/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web/pgadmin/tools/datagrid/tests/datagrid_test_data.json b/web/pgadmin/tools/datagrid/tests/datagrid_test_data.json new file mode 100644 index 0000000..a50992f --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/datagrid_test_data.json @@ -0,0 +1,134 @@ +{ + "data_grid_init_query_tool": [ + { + "name": "Datagrid init query tool", + "url": "/datagrid/initialize/query_tool/", + "is_positive_test": true, + "mocking_required": false, + "test_data": {}, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + } + ], + "data_grid_query_tool_close": [ + { + "name": "Datagrid query tool close", + "url": "/datagrid/close/", + "is_positive_test": true, + "mocking_required": false, + "test_data": {}, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + } + ], + "data_grid_validate_filter": [ + { + "name": "Datagrid validate filter", + "url": "/datagrid/filter/validate/", + "is_positive_test": true, + "mocking_required": false, + "test_data": "id = 1", + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Datagrid validate filter", + "url": "/datagrid/filter/validate/", + "is_positive_test": false, + "mocking_required": true, + "test_data": "id = 1", + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_scalar", + "return_value": "(False, 'Mocked Internal Server Error while validate filter')" + }, + "expected_data": { + "status_code": 200 + } + } + ], + "data_grid_update_connection": [ + { + "name": "Datagrid update connection positive", + "url": "/datagrid/initialize/query_tool/update_connection/", + "is_positive_test": true, + "mocking_required": false, + "is_create_role": false, + "test_data": {}, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Datagrid update connection with new user", + "url": "/datagrid/initialize/query_tool/update_connection/", + "is_positive_test": true, + "mocking_required": false, + "is_create_role": true, + "test_data": {}, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + } + ], + "data_grid_panel": [ + { + "name": "Datagrid Panel", + "url": "/datagrid/panel/", + "is_positive_test": true, + "mocking_required": false, + "test_data": {}, + "mock_data": { + }, + "expected_data": { + "status_code": 500 + } + } + ], + "data_grid_initialize": [ + { + "name": "Datagrid Initialize", + "url": "/datagrid/initialize/datagrid/", + "is_positive_test": true, + "mocking_required": false, + "test_data": "id=1", + "mock_data": { + + }, + "expected_data": { + "status_code": 200 + } + },{ + "name": "Datagrid Initialize", + "url": "/datagrid/initialize/datagrid/", + "is_positive_test": true, + "mocking_required": false, + "test_data": null, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Datagrid Initialize", + "url": "/datagrid/initialize/datagrid/", + "is_positive_test": false, + "mocking_required": true, + "test_data": "id=1", + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict", + "return_value": "(False, 'Mocked Internal Server Error while initialize datagrid.')" + }, + "expected_data": { + "status_code": 500 + } + } + ] +} diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_init_query_tool.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_init_query_tool.py new file mode 100644 index 0000000..6ecf5de --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_init_query_tool.py @@ -0,0 +1,72 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from . import utils as data_grid_utils + + +class DatagridInitQueryToolTestCase(BaseTestGenerator): + """ + This will init query-tool connection. + """ + + scenarios = utils.generate_scenarios( + 'data_grid_init_query_tool', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + + self.trans_id = str(random.randint(1, 9999999)) + + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + + def init_query_tool(self): + response = self.tester.post( + self.url + str(self.trans_id) + '/' + str(self.sgid) + '/' + str( + self.sid) + '/' + str(self.did), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will init query tool connection.""" + + if self.is_positive_test: + response = self.init_query_tool() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_panel.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_panel.py new file mode 100644 index 0000000..5ab6a6b --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_panel.py @@ -0,0 +1,92 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from . import utils as data_grid_utils + + +class DatagridPanelTestCase(BaseTestGenerator): + """ + This will data grid panel. + """ + + scenarios = utils.generate_scenarios( + 'data_grid_panel', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + + self.trans_id = str(random.randint(1, 9999999)) + qt_init = data_grid_utils._init_query_tool(self, self.trans_id, + self.sgid, self.sid, + self.did) + + if not qt_init['success']: + raise Exception("Could not initialize querty tool.") + + def panel(self): + query_param = \ + '?is_query_tool={0}&sgid={1}&sid={2}&server_type={3}' \ + '&did={4}'.format(True, self.sgid, self.sid, + self.server_information['type'], self.did) + + response = self.tester.post( + self.url + str(self.trans_id) + query_param, + data=json.dumps(self.test_data), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will update query tool connection.""" + + if self.is_positive_test: + response = self.panel() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + with patch(self.mock_data["function_name"], + return_value=eval(self.mock_data["return_value"])): + response = self.panel() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_query_tool_close.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_query_tool_close.py new file mode 100644 index 0000000..d1a0a97 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_query_tool_close.py @@ -0,0 +1,90 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from . import utils as data_grid_utils + + +class DatagridQueryToolCloseTestCase(BaseTestGenerator): + """ + This will close query-tool connection. + """ + + scenarios = utils.generate_scenarios( + 'data_grid_query_tool_close', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + + self.trans_id = str(random.randint(1, 9999999)) + qt_init = data_grid_utils._init_query_tool(self, self.trans_id, + self.sgid, self.sid, + self.did) + + if not qt_init['success']: + raise Exception("Could not initialize querty tool.") + + self.test_data = { + "database": self.did, + "server": self.sid + } + + if self.server_information['type'] == 'ppas': + self.test_data['password'] = 'enterprisedb' + self.test_data['user'] = 'enterprisedb' + else: + self.test_data['password'] = 'root' + self.test_data['user'] = 'postgres' + + def close_connection(self): + response = self.tester.delete( + self.url + str(self.trans_id), + + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will update query tool connection.""" + + if self.is_positive_test: + response = self.close_connection() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_update_connection.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_update_connection.py new file mode 100644 index 0000000..a21c38e --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_update_connection.py @@ -0,0 +1,119 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from pgadmin.browser.server_groups.servers.roles.tests import \ + utils as roles_utils +from . import utils as data_grid_utils + + +class DatagridUpdateConnectionTestCase(BaseTestGenerator): + """ + This will update query-tool connection. + """ + + scenarios = utils.generate_scenarios( + 'data_grid_update_connection', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + + self.trans_id = str(random.randint(1, 9999999)) + self.roles = None + + if self.is_create_role: + data = roles_utils.get_role_data(self.server['db_password']) + self.role_name = data['rolname'] + self.role_password = data['rolpassword'] + roles_utils.create_role_with_password( + self.server, self.role_name, self.role_password) + + if not self.is_positive_test or self.is_create_role: + qt_init = data_grid_utils._init_query_tool(self, self.trans_id, + self.sgid, self.sid, + self.did) + + if not qt_init['success']: + raise Exception("Could not initialize querty tool.") + + self.test_data = { + "database": self.did, + "server": self.sid + } + + if self.server_information['type'] == 'ppas': + self.test_data['password'] = 'enterprisedb' + self.test_data['user'] = 'enterprisedb' + else: + self.test_data['password'] = 'root' + self.test_data['user'] = 'postgres' + + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + + def update_connection(self, user_data=None): + if user_data: + response = self.tester.post( + self.url + str(self.trans_id) + '/' + str(self.sgid) + + '/' + str(self.sid) + '/' + str(self.did), + data=json.dumps(user_data), + content_type='html/json' + ) + else: + response = self.tester.post( + self.url + str(self.trans_id) + '/' + str(self.sgid) + '/' + + str(self.sid) + '/' + str(self.did), + data=json.dumps(self.test_data), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will update query tool connection.""" + + if self.is_positive_test: + user_data = dict() + if self.is_create_role: + user_data['user'] = self.role_name + user_data['password'] = self.role_password + response = self.update_connection(user_data=user_data) + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + response = self.update_connection() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_validate_filter.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_validate_filter.py new file mode 100644 index 0000000..0aba5d8 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_validate_filter.py @@ -0,0 +1,91 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from . import utils as data_grid_utils + + +class DatagridValidateFilterTestCase(BaseTestGenerator): + """ + This will validate filter connection. + """ + + scenarios = utils.generate_scenarios( + 'data_grid_validate_filter', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + self.schema_id = parent_node_dict['schema'][-1]["schema_id"] + self.schema_name = parent_node_dict['schema'][-1]["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception("Could not find the schema to add a table.") + self.table_name = "table_for_wizard%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + + def validate_filter(self): + response = self.tester.post( + self.url + str(self.sid) + '/' + str(self.did) + '/' + + str(self.table_id), + data=json.dumps(self.test_data), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will update query tool connection.""" + + if self.is_positive_test: + response = self.validate_filter() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + with patch(self.mock_data["function_name"], + return_value=eval(self.mock_data["return_value"])): + response = self.validate_filter() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/test_initialize_data_grid.py b/web/pgadmin/tools/datagrid/tests/test_initialize_data_grid.py new file mode 100644 index 0000000..130a1d6 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_initialize_data_grid.py @@ -0,0 +1,108 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from . import utils as data_grid_utils + + +class DatagridInitializeTestCase(BaseTestGenerator): + """ + This will Initialize datagrid + """ + + scenarios = utils.generate_scenarios( + 'data_grid_initialize', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + + self.schema_id = parent_node_dict['schema'][-1]["schema_id"] + self.schema_name = parent_node_dict['schema'][-1]["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception("Could not find the schema to add a table.") + self.table_name = "table_for_wizard%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + self.trans_id = str(random.randint(1, 9999999)) + qt_init = data_grid_utils._init_query_tool(self, self.trans_id, + self.sgid, self.sid, + self.did) + + if not qt_init['success']: + raise Exception("Could not initialize query tool.") + + def initialize_datagrid(self): + if self.test_data: + response = self.tester.post( + self.url + str(self.trans_id) + '/4/table/' + + str(self.sgid) + '/' + str(self.sid) + '/' + + str(self.did) + '/' + str(self.table_id), + data=json.dumps(self.test_data), + content_type='html/json' + ) + else: + response = self.tester.post( + self.url + str(self.trans_id) + '/4/table/' + + str(self.sgid) + '/' + str(self.sid) + '/' + + str(self.did) + '/' + str(self.table_id), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will update query tool connection.""" + + if self.is_positive_test: + response = self.initialize_datagrid() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + with patch(self.mock_data["function_name"], + return_value=eval(self.mock_data["return_value"])): + response = self.initialize_datagrid() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/utils.py b/web/pgadmin/tools/datagrid/tests/utils.py new file mode 100644 index 0000000..82f9427 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/utils.py @@ -0,0 +1,33 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## +import os +import json + +file_name = os.path.basename(__file__) +CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) +with open(CURRENT_PATH + "/datagrid_test_data.json") as data_file: + test_cases = json.load(data_file) + + +def _init_query_tool(self, trans_id, server_group, server_id, db_id): + QUERY_TOOL_INIT_URL = '/datagrid/initialize/query_tool/' + + qt_init = self.tester.post( + '{0}/{1}/{2}/{3}/{4}'.format( + QUERY_TOOL_INIT_URL, + trans_id, + server_group, + server_id, + db_id + ), + follow_redirects=True + ) + assert qt_init.status_code == 200 + qt_init = json.loads(qt_init.data.decode('utf-8')) + return qt_init diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py index c62bc7c..434ba88 100644 --- a/web/pgadmin/tools/sqleditor/__init__.py +++ b/web/pgadmin/tools/sqleditor/__init__.py @@ -10,33 +10,38 @@ """A blueprint module implementing the sqleditor frame.""" import os import pickle -import sys import re +from urllib.parse import unquote import simplejson as json -from flask import Response, url_for, render_template, session, request, \ - current_app +from config import PG_DEFAULT_DRIVER, ON_DEMAND_RECORD_COUNT +from flask import Response, url_for, render_template, session, current_app +from flask import request, jsonify from flask_babelex import gettext from flask_security import login_required, current_user -from urllib.parse import unquote - -from config import PG_DEFAULT_DRIVER, ON_DEMAND_RECORD_COUNT from pgadmin.misc.file_manager import Filemanager from pgadmin.tools.sqleditor.command import QueryToolCommand from pgadmin.tools.sqleditor.utils.constant_definition import ASYNC_OK, \ ASYNC_EXECUTION_ABORTED, \ CONNECTION_STATUS_MESSAGE_MAPPING, TX_STATUS_INERROR +from pgadmin.tools.sqleditor.utils.filter_dialog import FilterDialog +from pgadmin.tools.sqleditor.utils.query_history import QueryHistory +from pgadmin.tools.sqleditor.utils.query_tool_fs_utils import \ + read_file_generator +from pgadmin.tools.sqleditor.utils.query_tool_preferences import \ + register_query_tool_preferences from pgadmin.tools.sqleditor.utils.start_running_query import StartRunningQuery from pgadmin.tools.sqleditor.utils.update_session_grid_transaction import \ update_session_grid_transaction from pgadmin.utils import PgAdminModule from pgadmin.utils import get_storage_directory from pgadmin.utils.ajax import make_json_response, bad_request, \ - success_return, internal_server_error + internal_server_error +from pgadmin.utils.constants import MIMETYPE_APP_JS from pgadmin.utils.driver import get_driver -from pgadmin.utils.menu import MenuItem -from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost,\ +from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost, \ CryptKeyMissing +from pgadmin.utils.menu import MenuItem from pgadmin.utils.sqlautocomplete.autocomplete import SQLAutoComplete from pgadmin.tools.sqleditor.utils.query_tool_preferences import \ register_query_tool_preferences @@ -46,6 +51,8 @@ from pgadmin.tools.sqleditor.utils.filter_dialog import FilterDialog from pgadmin.tools.sqleditor.utils.query_history import QueryHistory from pgadmin.utils.constants import MIMETYPE_APP_JS, SERVER_CONNECTION_CLOSED,\ ERROR_MSG_TRANS_ID_NOT_FOUND +from pgadmin.model import Server + MODULE_NAME = 'sqleditor' @@ -109,6 +116,9 @@ class SqlEditorModule(PgAdminModule): 'sqleditor.get_query_history', 'sqleditor.add_query_history', 'sqleditor.clear_query_history', + 'sqleditor.get_new_connection_data', + 'sqleditor.get_new_connection_database', + 'sqleditor.get_new_connection_user', ] def register_preferences(self): @@ -1465,6 +1475,188 @@ def get_filter_data(trans_id): return FilterDialog.get(status, error_msg, conn, trans_obj, session_ob) [email protected]( + '/new_connection_dialog/<int:sgid>/<int:sid>', + methods=["GET"], endpoint='get_new_connection_data' +) +@login_required +def get_new_connection_data(sgid, sid=None): + """ + This method is used to get required data for get new connection. + :extract_sql_from_network_parameters, + """ + try: + # if sid and not did: + servers = Server.query.all() + server_list = [ + {'name': server.serialize['name'], "id": server.serialize['id']} + for server in servers] + + msg = "Success" + return make_json_response( + data={ + 'status': True, + 'msg': msg, + 'result': { + 'server_list': server_list + } + } + ) + + except Exception as e: + return make_json_response( + data={ + 'status': False, + 'msg': 'Unable to fetch data.', + 'result': { + 'server_list': [] + } + } + ) + + [email protected]( + '/new_connection_database/<int:sgid>/<int:sid>', + methods=["GET"], endpoint='get_new_connection_database' +) +@login_required +def get_new_connection_database(sgid, sid=None): + """ + This method is used to get required data for get new connection. + :extract_sql_from_network_parameters, + """ + try: + database_list = [] + from pgadmin.utils.driver import get_driver + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) + conn = manager.connection() + if conn.connected(): + is_connected = True + else: + # connection = conn.connect() + is_connected = False + if is_connected: + if sid: + template_path = 'databases/sql/#{0}#'.format(manager.version) + last_system_oid = 0 + server_node_res = manager + + db_disp_res = None + params = None + if server_node_res and server_node_res.db_res: + db_disp_res = ", ".join( + ['%s'] * len(server_node_res.db_res.split(',')) + ) + params = tuple(server_node_res.db_res.split(',')) + sql = render_template( + "/".join([template_path, 'nodes.sql']), + last_system_oid=last_system_oid, + db_restrictions=db_disp_res + ) + status, databases = conn.execute_dict(sql, params) + database_list = [ + {'label': database['name'], 'value': database['did']} for + database in databases['rows']] + else: + status = False + + msg = "Success" + return make_json_response( + data={ + 'status': status, + 'msg': msg, + 'result': { + 'data': database_list, + } + } + ) + else: + return make_json_response( + data={ + 'status': False, + 'msg': 'Server not connected, ' + 'Please connect with server and try again.', + 'result': { + 'database_list': [], + } + } + ) + except Exception as e: + return make_json_response( + data={ + 'status': False, + 'msg': 'Unable to fetch data.', + 'result': { + 'database_list': [], + } + } + ) + + [email protected]( + '/new_connection_user/<int:sgid>/<int:sid>', + methods=["GET"], endpoint='get_new_connection_user' +) +@login_required +def get_new_connection_user(sgid, sid=None): + """ + This method is used to get required data for get new connection. + :extract_sql_from_network_parameters, + """ + try: + from pgadmin.utils.driver import get_driver + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) + conn = manager.connection() + user_list = [] + if conn.connected(): + is_connected = True + else: + is_connected = False + if is_connected: + if sid: + sql_path = 'roles/sql/#{0}#'.format(manager.version) + status, users = conn.execute_2darray( + render_template(sql_path + 'nodes.sql') + ) + user_list = [ + {'value': user['rolname'], 'label': user['rolname']} for + user in users['rows'] if user['rolcanlogin']] + else: + status = False + + msg = "Success" + return make_json_response( + data={ + 'status': status, + 'msg': msg, + 'result': { + 'data': user_list, + } + } + ) + else: + return make_json_response( + data={ + 'status': False, + 'msg': 'Server not connected, ' + 'Please connect with server and try again.', + 'result': { + 'user_list': [], + } + } + ) + except Exception as e: + return make_json_response( + data={ + 'status': False, + 'msg': 'Unable to fetch data.', + 'result': { + 'user_list': [], + } + } + ) + + @blueprint.route( '/filter_dialog/<int:trans_id>', methods=["PUT"], endpoint='set_filter_data' diff --git a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css index 00543ff..b8c2d2e 100644 --- a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css +++ b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css @@ -315,10 +315,6 @@ input.editor-checkbox:focus { padding: 10px 0px; } -.editor-title { - width:100%; -} - .connection-status-hide { display: none !important; } @@ -395,3 +391,7 @@ input.editor-checkbox:focus { .hide-vertical-scrollbar { overflow-y: hidden; } + +.new-connection-dialog-style { + width: 100% !important; +} diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js index ce02a53..9720d36 100644 --- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js +++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js @@ -14,6 +14,7 @@ define('tools.querytool', [ 'jqueryui.position', 'underscore', 'pgadmin.alertifyjs', 'sources/pgadmin', 'backbone', 'bundled_codemirror', 'sources/utils', 'pgadmin.misc.explain', + 'pgadmin.user_management.current_user', 'sources/selection/grid_selector', 'sources/selection/active_cell_capture', 'sources/selection/clipboard', @@ -26,6 +27,7 @@ define('tools.querytool', [ 'sources/sqleditor/execute_query', 'sources/sqleditor/query_tool_http_error_handler', 'sources/sqleditor/filter_dialog', + 'sources/sqleditor/new_connection_dialog', 'sources/sqleditor/geometry_viewer', 'sources/sqleditor/history/history_collection.js', 'sources/sqleditor/history/query_history', @@ -52,8 +54,8 @@ define('tools.querytool', [ 'pgadmin.tools.user_management', ], function( gettext, url_for, $, jqueryui, jqueryui_position, _, alertify, pgAdmin, Backbone, codemirror, pgadminUtils, - pgExplain, GridSelector, ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent, - XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler, FilterHandler, + pgExplain, current_user, GridSelector, ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent, + XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler, FilterHandler, newConnectionHandler, GeometryViewer, historyColl, queryHist, querySources, keyboardShortcuts, queryToolActions, queryToolNotifications, Datagrid, modifyAnimation, calculateQueryRunTime, callRenderAfterPoll, queryToolPref, queryTxnStatus, csrfToken, panelTitleFunc, @@ -89,6 +91,9 @@ define('tools.querytool', [ this.layout = opts.layout; this.set_server_version(opts.server_ver); this.trigger('pgadmin-sqleditor:view:initialised'); + this.connection_list = [ + {'server_group': null,'server': null, 'database': null, 'user': null, 'password': null, 'title': '<New Connection>'}, + ]; }, // Bind all the events @@ -151,6 +156,30 @@ define('tools.querytool', [ 'click #btn-rollback': 'on_rollback_transaction', }, + render_connection: function(data_list) { + var dropdownElement = document.getElementById('connections-list'); + dropdownElement.innerHTML = ''; + data_list.forEach((option, index) => { + $('#connections-list').append('<li class="connection-list-item" data-index='+ index +'><a class="dropdown-item" href="#" tabindex="0">'+ option.title +'</a></li>'); + + }); + var self = this; + $('.connection-list-item').click(function() { + self.get_connection_data(this); + }); + }, + + get_connection_data: function(event){ + var index = $(event).attr('data-index'); + var connection_details = this.connection_list[index]; + if(connection_details.server_group) { + this.on_change_connection(connection_details); + } else { + this.on_new_connection(); + } + + }, + reflectPreferences: function() { let self = this, browser = pgWindow.default.pgAdmin.Browser, @@ -199,8 +228,15 @@ define('tools.querytool', [ }); }, - set_editor_title: function(title) { - this.$el.find('.editor-title').text(title); + set_editor_title: function(title, is_connected) { + if(is_connected) { + this.$el.find('.editor-title').text(title); + this.render_connection(this.connection_list); + } else { + this.$el.find('.editor-title').text(title); + this.render_connection(this.connection_list); + } + }, // This function is used to render the template. @@ -684,6 +720,8 @@ define('tools.querytool', [ pgBrowser.register_to_activity_listener(document, ()=>{ alertify.alert(gettext('Timeout'), gettext('Your session has timed out due to inactivity. Please close the window and login again.')); }); + + self.render_connection(self.connection_list); }, /* Regarding SlickGrid usage in render_grid function. @@ -1595,6 +1633,17 @@ define('tools.querytool', [ ); }, + on_new_connection: function() { + var self = this; + + // Trigger the show_filter signal to the SqlEditorController class + self.handler.trigger( + 'pgadmin-sqleditor:button:show_new_connection', + self, + self.handler + ); + }, + // Callback function for include filter button click. on_include_filter: function(ev) { var self = this; @@ -2038,6 +2087,55 @@ define('tools.querytool', [ queryToolActions.executeRollback(this.handler); }, + on_change_connection: function(connection_details, ref) { + var self = this; + $.ajax({ + url: url_for('datagrid.update_query_tool_connection', { + 'trans_id': self.transId, + 'sgid': connection_details['server_group'], + 'sid': connection_details['server'], + 'did': connection_details['database'], + }), + method: 'POST', + contentType: 'application/json', + data: JSON.stringify(connection_details), + }) + .done(function(res) { + if(res.success) { + self.transId = res.data.tran_id; + self.handler.transId = res.data.tran_id; + self.handler.url_params = { + 'did': connection_details['database'], + 'is_query_tool': self.handler.url_params.is_query_tool, + 'server_type': self.handler.url_params.server_type, + 'sgid': connection_details['server_group'], + 'sid': connection_details['server'], + 'title': connection_details['title'], + }; + self.set_editor_title(self.handler.url_params.title); + self.handler.setTitle(self.handler.url_params.title); + alertify.success('connected successfully'); + if(ref){ + let connection_data = { + 'server_group': self.handler.url_params.sgid, + 'server': connection_details['server'], + 'database': connection_details['database'], + 'user': connection_details['user'], + 'password': connection_details['password'], + 'title': connection_details['title'], + 'is_allow_new_connection': true, + }; + self.connection_list.unshift(connection_data); + self.render_connection(self.connection_list); + ref.close(); + } + } + + }) + .fail(function(xhr) { + alertify.error(xhr.responseJSON['errormsg']); + }); + }, }); /* Defining controller class for data grid, which actually @@ -2357,7 +2455,18 @@ define('tools.querytool', [ }); $('#btn-conn-status i').removeClass('obtaining-conn'); - self.gridView.set_editor_title(_.unescape(url_params.title)); + self.gridView.set_editor_title(_.unescape(url_params.title), true); + let connection_data = { + 'server_group': self.gridView.handler.url_params.sgid, + 'server': self.gridView.handler.url_params.sid, + 'database': self.gridView.handler.url_params.did, + 'user': null, + 'password': null, + 'title': _.unescape(url_params.title), + 'is_allow_new_connection': false, + }; + self.gridView.connection_list.unshift(connection_data); + self.gridView.render_connection(self.gridView.connection_list); }; pgBrowser.Events.on('pgadmin:query_tool:connected:' + transId, afterConn); @@ -2452,6 +2561,7 @@ define('tools.querytool', [ self.on('pgadmin-sqleditor:button:save_file', self._save_file, self); self.on('pgadmin-sqleditor:button:deleterow', self._delete, self); self.on('pgadmin-sqleditor:button:show_filter', self._show_filter, self); + self.on('pgadmin-sqleditor:button:show_new_connection', self._show_new_connection, self); self.on('pgadmin-sqleditor:button:include_filter', self._include_filter, self); self.on('pgadmin-sqleditor:button:exclude_filter', self._exclude_filter, self); self.on('pgadmin-sqleditor:button:remove_filter', self._remove_filter, self); @@ -3622,7 +3732,6 @@ define('tools.querytool', [ } }; }, - // This function will show the filter in the text area. _show_filter: function() { let self = this, @@ -3637,7 +3746,19 @@ define('tools.querytool', [ } FilterHandler.dialog(self, reconnect); }, + // This function will show the new connection. + _show_new_connection: function() { + let self = this, + reconnect = false; + /* When server is disconnected and connected, connection is lost, + * To reconnect pass true + */ + if (arguments.length > 0 && arguments[arguments.length - 1] == 'connect') { + reconnect = true; + } + newConnectionHandler.dialog(self, reconnect); + }, // This function will include the filter by selection. _include_filter: function() { var self = this, diff --git a/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss b/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss index fd1e5d3..53f2449 100644 --- a/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss +++ b/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss @@ -30,6 +30,19 @@ color: $sql-title-fg; } +.connection-info { + background: $sql-title-bg; + color: $sql-title-fg; + width:100%; + display: inherit; +} + +.conn-info-dd { + padding-top: 0.3em; + padding-left: 0.2em; + cursor: pointer; +} + #editor-panel { z-index: 0; diff --git a/web/pgadmin/tools/sqleditor/tests/test_new_connection_database.py b/web/pgadmin/tools/sqleditor/tests/test_new_connection_database.py new file mode 100644 index 0000000..20fe3e3 --- /dev/null +++ b/web/pgadmin/tools/sqleditor/tests/test_new_connection_database.py @@ -0,0 +1,98 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.test_setup import config_data +from regression.python_test_utils import test_utils as utils + + +class TestNewConnectionDatabase(BaseTestGenerator): + """ This class will test new connection database. """ + scenarios = [ + ('New connection dialog', + dict( + url="/sqleditor/new_connection_database/", + is_positive_test=True, + mocking_required=False, + is_server_conn_required=False, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ('New connection dialog connect server', + dict( + url="/sqleditor/new_connection_database/", + is_positive_test=True, + mocking_required=False, + is_server_conn_required=True, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ('New connection dialog negative', + dict( + url="/sqleditor/new_connection_database/", + is_positive_test=False, + mocking_required=False, + is_server_conn_required=True, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ] + + def setUp(self): + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + def get_database(self): + response = self.tester.get( + self.url + str(self.sgid) + '/' + str(self.sid), + content_type='html/json' + ) + + return response + + def runTest(self): + if self.is_positive_test: + if self.is_server_conn_required: + self.server['password'] = self.server['db_password'] + server_response = self.tester.post( + '/browser/server/connect/{0}/{1}'.format( + utils.SERVER_GROUP, + self.sid), + data=json.dumps(self.server), + content_type='html/json' + ) + response = self.get_database() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + if self.is_server_conn_required: + self.server['password'] = self.server['db_password'] + server_response = self.tester.post( + '/browser/server/connect/{0}/{1}'.format( + utils.SERVER_GROUP, + self.sid), + data=json.dumps(self.server), + content_type='html/json' + ) + self.sid = 0 + response = self.get_database() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + self.assertEqual(actual_response_code, expected_response_code) diff --git a/web/pgadmin/tools/sqleditor/tests/test_new_connection_dialog.py b/web/pgadmin/tools/sqleditor/tests/test_new_connection_dialog.py new file mode 100644 index 0000000..75a47ef --- /dev/null +++ b/web/pgadmin/tools/sqleditor/tests/test_new_connection_dialog.py @@ -0,0 +1,50 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## +import json +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.test_setup import config_data +from regression.python_test_utils import test_utils as utils + + +class TestNewConnectionDialog(BaseTestGenerator): + """ This class will test new connection dialog. """ + scenarios = [ + ('New connection dialog', + dict( + url="/sqleditor/new_connection_dialog/", + is_positive_test=True, + mocking_required=False, + is_connect_server=False, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ] + + def setUp(self): + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + def new_connection(self): + response = self.tester.get( + self.url + str(self.sgid) + '/' + str(self.sgid), + content_type='html/json' + ) + + return response + + def runTest(self): + if self.is_positive_test: + response = self.new_connection() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + self.assertEqual(actual_response_code, expected_response_code) diff --git a/web/pgadmin/tools/sqleditor/tests/test_new_connection_user.py b/web/pgadmin/tools/sqleditor/tests/test_new_connection_user.py new file mode 100644 index 0000000..7b6c12e --- /dev/null +++ b/web/pgadmin/tools/sqleditor/tests/test_new_connection_user.py @@ -0,0 +1,98 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.test_setup import config_data +from regression.python_test_utils import test_utils as utils + + +class TestNewConnectionUser(BaseTestGenerator): + """ This class will test new connection user. """ + scenarios = [ + ('New connection dialog', + dict( + url="/sqleditor/new_connection_user/", + is_positive_test=True, + mocking_required=False, + is_server_conn_required=False, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ('New connection dialog connect server', + dict( + url="/sqleditor/new_connection_user/", + is_positive_test=True, + mocking_required=False, + is_server_conn_required=True, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ('New connection dialog negative', + dict( + url="/sqleditor/new_connection_user/", + is_positive_test=False, + mocking_required=False, + is_server_conn_required=True, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ] + + def setUp(self): + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + def get_use(self): + response = self.tester.get( + self.url + str(self.sgid) + '/' + str(self.sid), + content_type='html/json' + ) + + return response + + def runTest(self): + if self.is_positive_test: + if self.is_server_conn_required: + self.server['password'] = self.server['db_password'] + server_response = self.tester.post( + '/browser/server/connect/{0}/{1}'.format( + utils.SERVER_GROUP, + self.sid), + data=json.dumps(self.server), + content_type='html/json' + ) + response = self.get_use() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + if self.is_server_conn_required: + self.server['password'] = self.server['db_password'] + server_response = self.tester.post( + '/browser/server/connect/{0}/{1}'.format( + utils.SERVER_GROUP, + self.sid), + data=json.dumps(self.server), + content_type='html/json' + ) + self.sid = 0 + response = self.get_use() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + self.assertEqual(actual_response_code, expected_response_code) diff --git a/web/pgadmin/utils/driver/psycopg2/connection.py b/web/pgadmin/utils/driver/psycopg2/connection.py index 9cb65bc..115598a 100644 --- a/web/pgadmin/utils/driver/psycopg2/connection.py +++ b/web/pgadmin/utils/driver/psycopg2/connection.py @@ -21,7 +21,7 @@ import psycopg2 from flask import g, current_app from flask_babelex import gettext from flask_security import current_user -from pgadmin.utils.crypto import decrypt +from pgadmin.utils.crypto import decrypt, encrypt from psycopg2.extensions import encodings import config @@ -211,8 +211,23 @@ class Connection(BaseConnection): password = None passfile = None manager = self.manager + encpass = None + is_update_password = True + crypt_key_present, crypt_key = get_crypt_key() + + if 'user' in kwargs and kwargs['password']: + password = kwargs['password'] + # kwargs['password'] = encrypt(password, crypt_key) + ''' + Remove the password from kwargs as we don't want to update + password in manager when user is connecting through query toll. + If we update password in manager will affect existing connections. + ''' + kwargs.pop('password') + is_update_password = False + else: + encpass = kwargs['password'] if 'password' in kwargs else None - encpass = kwargs['password'] if 'password' in kwargs else None passfile = kwargs['passfile'] if 'passfile' in kwargs else None tunnel_password = kwargs['tunnel_password'] if 'tunnel_password' in \ kwargs else '' @@ -227,16 +242,16 @@ class Connection(BaseConnection): if manager.use_ssh_tunnel == 1: manager.check_ssh_tunnel_alive() - if encpass is None: - encpass = self.password or getattr(manager, 'password', None) + if is_update_password: + if encpass is None: + encpass = self.password or getattr(manager, 'password', None) - self.password = encpass + self.password = encpass # Reset the existing connection password if self.reconnecting is not False: self.password = None - crypt_key_present, crypt_key = get_crypt_key() if not crypt_key_present: raise CryptKeyMissing() @@ -269,7 +284,10 @@ class Connection(BaseConnection): try: database = self.db - user = manager.user + if 'user' in kwargs and kwargs['user']: + user = kwargs['user'] + else: + user = manager.user conn_id = self.conn_id import os @@ -338,10 +356,10 @@ class Connection(BaseConnection): self.wasConnected = False raise e - if status: + if status and is_update_password: manager._update_password(encpass) else: - if not self.reconnecting: + if not self.reconnecting and is_update_password: self.wasConnected = False return status, msg @@ -491,7 +509,7 @@ WHERE db.datname = current_database()""") if len(manager.db_info) == 1: manager.did = res['did'] - self._set_user_info(cur, manager) + self._set_user_info(cur, manager, **kwargs) self._set_server_type_and_password(kwargs, manager) @@ -499,7 +517,7 @@ WHERE db.datname = current_database()""") return True, None - def _set_user_info(self, cur, manager): + def _set_user_info(self, cur, manager, **kwargs): """ Set user info. :param cur: @@ -517,7 +535,7 @@ WHERE db.datname = current_database()""") WHERE rolname = current_user""") - if status is None: + if status is None and 'user' not in kwargs: manager.user_info = dict() if cur.rowcount > 0: manager.user_info = cur.fetchmany(1)[0] ^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: [pgAdmin][RM3794]:Allow User to Change Database Connection from an Open Query Tool Tab @ 2020-09-21 11:54 Akshay Joshi <[email protected]> parent: Nikhil Mohite <[email protected]> 0 siblings, 1 reply; 18+ messages in thread From: Akshay Joshi @ 2020-09-21 11:54 UTC (permalink / raw) To: Nikhil Mohite <[email protected]>; +Cc: pgadmin-hackers Hi Nikhil Following are the initial review comments: - Open View/Edit data on any table and click on the same database connection and then click on the Execute button. Got "get_primary_keys() takes 1 positional argument but 2 were given" error. - In my opinion, we should hide the option to change the database connection for View/Edit Data. - If the user clicks on the same database connection multiple times then no need to change the backend connection and transaction id. Add validation at the backend, no action required in this case. - The role option is missing from the "connect to server" dialog. - The Password field should not be there on the "connect to server" dialog. Sometimes we saved the password so asking a password every time is not correct. Check the pgAdmin 3 behavior. Code review still remains. On Thu, Sep 17, 2020 at 4:15 PM Nikhil Mohite < [email protected]> wrote: > Hi Team, > > Regarding RM-3794 <https://redmine.postgresql.org/issues/3794; allow > the user to change the database connection from an open query tool: > I have implemented the feature and also added documentation for it. > > PFA patch. > > -- > *Thanks & Regards,* > *Nikhil Mohite* > *Software Engineer.* > *EDB Postgres* <https://www.enterprisedb.com/; > *Mob.No: +91-7798364578.* > -- *Thanks & Regards* *Akshay Joshi* *pgAdmin Hacker | Sr. Software Architect* *EDB Postgres <http://edbpostgres.com>* *Mobile: +91 976-788-8246* ^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: [pgAdmin][RM3794]:Allow User to Change Database Connection from an Open Query Tool Tab @ 2020-09-28 05:50 Nikhil Mohite <[email protected]> parent: Akshay Joshi <[email protected]> 0 siblings, 1 reply; 18+ messages in thread From: Nikhil Mohite @ 2020-09-28 05:50 UTC (permalink / raw) To: Akshay Joshi <[email protected]>; +Cc: pgadmin-hackers Hi Akshay, I have resolved all the review comments and also updated the test cases as per the new implementation. PFA updated patch. On Mon, Sep 21, 2020 at 5:24 PM Akshay Joshi <[email protected]> wrote: > Hi Nikhil > > Following are the initial review comments: > > - Open View/Edit data on any table and click on the same database > connection and then click on the Execute button. Got "get_primary_keys() > takes 1 positional argument but 2 were given" error. > - In my opinion, we should hide the option to change the database > connection for View/Edit Data. > - If the user clicks on the same database connection multiple times > then no need to change the backend connection and transaction id. Add > validation at the backend, no action required in this case. > - The role option is missing from the "connect to server" dialog. > - The Password field should not be there on the "connect to server" > dialog. Sometimes we saved the password so asking a password every time is > not correct. Check the pgAdmin 3 behavior. > > Code review still remains. > > On Thu, Sep 17, 2020 at 4:15 PM Nikhil Mohite < > [email protected]> wrote: > >> Hi Team, >> >> Regarding RM-3794 <https://redmine.postgresql.org/issues/3794; allow >> the user to change the database connection from an open query tool: >> I have implemented the feature and also added documentation for it. >> >> PFA patch. >> >> -- >> *Thanks & Regards,* >> *Nikhil Mohite* >> *Software Engineer.* >> *EDB Postgres* <https://www.enterprisedb.com/; >> *Mob.No: +91-7798364578.* >> > > > -- > *Thanks & Regards* > *Akshay Joshi* > *pgAdmin Hacker | Sr. Software Architect* > *EDB Postgres <http://edbpostgres.com>* > > *Mobile: +91 976-788-8246* > Attachments: [application/octet-stream] RM_3794_V2.patch (220.0K, 3-RM_3794_V2.patch) download | inline diff: diff --git a/docs/en_US/images/new_connection_dialog.png b/docs/en_US/images/new_connection_dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf7ae6aa750360ae7710af61bf8e89f9d520284 GIT binary patch literal 49691 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	%0^c>O%*OkVmcSQwgzI6WP2m3$pEbze=t& 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 NIVmN{VsYb-{|8bOF{}Up literal 0 HcmV?d00001 diff --git a/docs/en_US/images/new_connection_options.png b/docs/en_US/images/new_connection_options.png new file mode 100644 index 0000000000000000000000000000000000000000..7ca94374baf0eb9a8a0e49cbd9d4d426895ff9d0 GIT binary patch literal 45739 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 literal 0 HcmV?d00001 diff --git a/docs/en_US/query_tool.rst b/docs/en_US/query_tool.rst index 83933f0..80f27a5 100644 --- a/docs/en_US/query_tool.rst +++ b/docs/en_US/query_tool.rst @@ -300,3 +300,24 @@ transaction status by clicking on the status icon in the Query Tool: .. image:: images/query_tool_connection_status.png :alt: Query tool connection and transaction statuses :align: center + +Change connection +***************** + +User can connect to another server or database from existing open session of query tool. + +* Click on the connection link next to connection status. +* Now click on the *<New Connection>* option from the dropdown. + +.. image:: images/new_connection_options.png + :alt: Query tool connection options + :align: center + +* Now select server, database, user, and role to connect and click OK. + +.. image:: images/new_connection_dialog.png + :alt: Query tool connection dialog + :align: center + +* A newly created connection will now get listed in the options. +* To connect, select the newly created connection from the dropdown list. diff --git a/web/pgadmin/browser/server_groups/servers/roles/tests/utils.py b/web/pgadmin/browser/server_groups/servers/roles/tests/utils.py index 3a7ee58..028ee64 100644 --- a/web/pgadmin/browser/server_groups/servers/roles/tests/utils.py +++ b/web/pgadmin/browser/server_groups/servers/roles/tests/utils.py @@ -152,3 +152,38 @@ def delete_role(connection, role_names): exception = "Error while deleting role: %s: line:%s %s" % ( file_name, sys.exc_traceback.tb_lineno, exception) print(exception, file=sys.stderr) + + +def create_role_with_password(server, role_name, role_password): + """ + This function create the role. + :param server: + :param role_name: + :param role_password: + :return: + """ + try: + connection = utils.get_db_connection(server['db'], + server['username'], + server['db_password'], + server['host'], + server['port'], + server['sslmode']) + pg_cursor = connection.cursor() + pg_cursor.execute( + "CREATE ROLE %s LOGIN PASSWORD '%s'" % (role_name, role_password)) + connection.commit() + # Get 'oid' from newly created tablespace + pg_cursor.execute( + "SELECT pr.oid from pg_catalog.pg_roles pr WHERE pr.rolname='%s'" % + role_name) + oid = pg_cursor.fetchone() + role_id = '' + if oid: + role_id = oid[0] + connection.close() + return role_id + except Exception as exception: + exception = "Error while deleting role: %s: line:%s %s" % ( + file_name, sys.exc_traceback.tb_lineno, exception) + print(exception, file=sys.stderr) diff --git a/web/pgadmin/model/__init__.py b/web/pgadmin/model/__init__.py index b33adc0..d24a350 100644 --- a/web/pgadmin/model/__init__.py +++ b/web/pgadmin/model/__init__.py @@ -94,6 +94,15 @@ class ServerGroup(db.Model): name = db.Column(db.String(128), nullable=False) __table_args__ = (db.UniqueConstraint('user_id', 'name'),) + @property + def serialize(self): + """Return object data in easily serializable format""" + return { + 'id': self.id, + 'user_id': self.user_id, + 'name': self.name, + } + class Server(db.Model): """Define a registered Postgres server""" @@ -175,6 +184,44 @@ class Server(db.Model): tunnel_password = db.Column(db.String(64), nullable=True) shared = db.Column(db.Boolean(), nullable=False) + @property + def serialize(self): + """Return object data in easily serializable format""" + return { + "id": self.id, + "user_id": self.user_id, + "servergroup_id": self.servergroup_id, + "name": self.name, + "host": self.host, + "hostaddr": self.hostaddr, + "port": self.port, + "maintenance_db": self.maintenance_db, + "username": self.username, + "password": self.password, + "save_password": self.save_password, + "role": self.role, + "ssl_mode": self.ssl_mode, + "comment": self.comment, + "discovery_id": self.discovery_id, + "db_res": self.db_res, + "passfile": self.passfile, + "sslcert": self.sslcert, + "sslkey": self.sslkey, + "sslrootcert": self.sslrootcert, + "sslcrl": self.sslcrl, + "sslcompression": self.sslcompression, + "bgcolor": self.bgcolor, + "fgcolor": self.fgcolor, + "service": self.service, + "connect_timeout": self.connect_timeout, + "use_ssh_tunnel": self.use_ssh_tunnel, + "tunnel_host": self.tunnel_host, + "tunnel_port": self.tunnel_port, + "tunnel_authentication": self.tunnel_authentication, + "tunnel_identity_file": self.tunnel_identity_file, + "tunnel_password": self.tunnel_password + } + class ModulePreference(db.Model): """Define a preferences table for any modules.""" diff --git a/web/pgadmin/static/js/sqleditor/new_connection_dialog.js b/web/pgadmin/static/js/sqleditor/new_connection_dialog.js new file mode 100644 index 0000000..dc1c064 --- /dev/null +++ b/web/pgadmin/static/js/sqleditor/new_connection_dialog.js @@ -0,0 +1,262 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import gettext from 'sources/gettext'; +import url_for from 'sources/url_for'; +import $ from 'jquery'; +import Alertify from 'pgadmin.alertifyjs'; +import pgAdmin from 'sources/pgadmin'; +import Backform from 'pgadmin.backform'; +import newConnectionDialogModel from 'sources/sqleditor/new_connection_dialog_model'; + + +let NewConnectionDialog = { + 'dialog': function(handler, reconnect) { + let url = url_for('sqleditor.get_new_connection_data', { + 'sid': handler.url_params.sid, + 'sgid': handler.url_params.sgid, + }); + + if(reconnect) { + url += '?connect=1'; + } + + let title = gettext('Connect to server'); + + $.ajax({ + url: url, + headers: { + 'Cache-Control' : 'no-cache', + }, + }).done(function (res) { + let response = res.data.result; + response.database_list = []; + response.user_list = []; + if (Alertify.newConnectionDialog) { + delete Alertify.newConnectionDialog; + } + + // Create Dialog + Alertify.dialog('newConnectionDialog', function factory() { + let $container = $('<div class=\'new-connection-dialog\'></div>'); + return { + main: function(message) { + this.msg = message; + }, + build: function() { + this.elements.content.appendChild($container.get(0)); + Alertify.pgDialogBuild.apply(this); + }, + setup: function(){ + return { + buttons: [ + { + text: '', + key: 112, + className: 'btn btn-primary-icon pull-left fa fa-question pg-alertify-icon-button', + attrs: { + name: 'dialog_help', + type: 'button', + label: gettext('Help'), + 'aria-label': gettext('Help'), + url: url_for('help.static', { + 'filename': 'query_tool.html', + }), + }, + }, + { + text: gettext('Cancel'), + key: 27, + className: 'btn btn-secondary fa fa-times pg-alertify-button', + 'data-btn-name': 'cancel', + }, { + text: gettext('OK'), + key: 13, + className: 'btn btn-primary fa fa-check pg-alertify-button', + 'data-btn-name': 'ok', + }, + ], + // Set options for dialog + options: { + title: title, + //disable both padding and overflow control. + padding: !1, + overflow: !1, + model: 0, + resizable: true, + maximizable: false, + pinnable: false, + closableByDimmer: false, + modal: false, + autoReset: false, + closable: true, + }, + }; + }, + prepare: function() { + let self = this; + $container.html(''); + // Disable Ok button + this.__internal.buttons[2].element.disabled = true; + + // Status bar + this.statusBar = $( + '<div class=\'pg-prop-status-bar pg-el-xs-12 d-none\'>' + + ' <div class="error-in-footer"> ' + + ' <div class="d-flex px-2 py-1"> ' + + ' <div class="pr-2"> ' + + ' <i class="fa fa-exclamation-triangle text-danger" aria-hidden="true"></i> ' + + ' </div> ' + + ' <div class="alert-text" role="alert"></div> ' + + ' </div> ' + + ' </div> ' + + '</div>').appendTo($container); + + // To show progress on filter Saving/Updating on AJAX + this.showNewConnectionProgress = $( + `<div id="show_filter_progress" class="pg-sp-container sql-editor-busy-fetching d-none"> + <div class="pg-sp-content"> + <div class="row"><div class="col-12 pg-sp-icon sql-editor-busy-icon"></div></div> + <div class="row"><div class="col-12 pg-sp-text sql-editor-busy-text">` + gettext('Loading data...') + `</div></div> + </div> + </div>` + ).appendTo($container); + $( + self.showNewConnectionProgress[0] + ).removeClass('d-none'); + + self.newConnCollectionModel = newConnectionDialogModel(response, handler.url_params.sgid, handler.url_params.sid); + let fields = Backform.generateViewSchema(null, self.newConnCollectionModel, 'create', null, null, true); + + let view = this.view = new Backform.Dialog({ + el: '<div></div>', + model: self.newConnCollectionModel, + schema: fields, + }); + + $(this.elements.body.childNodes[0]).addClass( + 'alertify_tools_dialog_properties obj_properties' + ); + + $container.append(view.render().$el); + + // Enable/disable save button and show/hide statusbar based on session + view.listenTo(view.model, 'pgadmin-session:start', function() { + view.listenTo(view.model, 'pgadmin-session:invalid', function(msg) { + self.statusBar.removeClass('d-none'); + $(self.statusBar.find('.alert-text')).html(msg); + // Disable Okay button + self.__internal.buttons[2].element.disabled = true; + }); + + view.listenTo(view.model, 'pgadmin-session:valid', function() { + self.statusBar.addClass('d-none'); + $(self.statusBar.find('.alert-text')).html(''); + // Enable Okay button + self.__internal.buttons[2].element.disabled = false; + }); + }); + + view.listenTo(view.model, 'pgadmin-session:stop', function() { + view.stopListening(view.model, 'pgadmin-session:invalid'); + view.stopListening(view.model, 'pgadmin-session:valid'); + }); + + // Starts monitoring changes to model + view.model.startNewSession(); + + // Hide Progress ... + $( + self.showNewConnectionProgress[0] + ).addClass('d-none'); + }, + callback: function(e) { + let self = this; + if (e.button.element.name == 'dialog_help') { + e.cancel = true; + pgAdmin.Browser.showHelp(e.button.element.name, e.button.element.getAttribute('url'), + null, null); + return; + } else if (e.button['data-btn-name'] === 'ok') { + e.cancel = true; // Do not close dialog + let newConnCollectionModel = this.newConnCollectionModel.toJSON(); + + let selected_database_name = null; + response.database_list.forEach(function(data){ + if(newConnCollectionModel['database'] == data['value']) { + selected_database_name = data['label']; + return false; + } + }); + let title = ''; + if(newConnCollectionModel['role']) { + title = selected_database_name + '/' + newConnCollectionModel['role'] + '@' + response.server_name; + } else { + title = selected_database_name + '/' + newConnCollectionModel['user'] + '@' + response.server_name; + newConnCollectionModel['role'] = null; + } + + let is_create_connection = true; + + handler.gridView.connection_list.forEach(function(connection_data){ + if(parseInt(connection_data['server']) == newConnCollectionModel['server'] + && parseInt(connection_data['database']) == newConnCollectionModel['database'] + && connection_data['user'] == newConnCollectionModel['user'] && connection_data['role'] == newConnCollectionModel['role']) { + is_create_connection = false; + // break for loop by return false. + return false; + } + + if(title == connection_data['title']) { + is_create_connection = false; + return false; + } + }); + if(!is_create_connection) { + let errmsg = 'Connection with this configuration already present.'; + Alertify.info(errmsg); + }else { + let connection_details = { + 'server_group': handler.gridView.handler.url_params.sgid, + 'server': newConnCollectionModel['server'], + 'database': newConnCollectionModel['database'], + 'title': title, + 'user': newConnCollectionModel['user'], + 'role': newConnCollectionModel['role'], + 'password': response.password, + }; + handler.gridView.on_change_connection(connection_details, self); + } + } else { + self.close(); + } + }, + }; + }); + setTimeout(function(){ + Alertify.newConnectionDialog('Connect to server.').resizeTo(pgAdmin.Browser.stdW.md,pgAdmin.Browser.stdH.md); + }, 500); + }).fail(function(error) { + Alertify.alert().setting({ + 'title': gettext('Connection lost'), + 'label':gettext('Ok'), + 'message': gettext('Connection to the server has been lost.'), + 'onok': function(){ + alert(error); + //Close the window after connection is lost + window.close(); + }, + }).show(); + }); + + }, + +}; + +module.exports = NewConnectionDialog; diff --git a/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js b/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js new file mode 100644 index 0000000..1cba6e6 --- /dev/null +++ b/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js @@ -0,0 +1,339 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import gettext from 'sources/gettext'; +import _ from 'underscore'; +import $ from 'jquery'; +import pgAdmin from 'sources/pgadmin'; +import Backform from 'pgadmin.backform'; +import url_for from 'sources/url_for'; +import alertify from 'pgadmin.alertifyjs'; + +export default function newConnectionDialogModel(response, sgid, sid) { + + let server_name = ''; + let database_name = ''; + + let NewConnectionSelect2Control = Backform.Select2Control.extend({ + fetchData: function(){ + let self = this; + url = self.field.get('url'); + + let url = url_for(url, { + 'sid': self.model.attributes.server, + 'sgid': sgid, + }); + + $.ajax({ + async: false, + url: url, + headers: { + 'Cache-Control' : 'no-cache', + }, + }).done(function (res) { + var transform = self.field.get('transform'); + if(res.data.status){ + let data = res.data.result.data; + + if (transform && _.isFunction(transform)) { + self.field.set('options', transform.bind(self, data)); + } else { + self.field.set('options', data); + } + } else { + if (transform && _.isFunction(transform)) { + self.field.set('options', transform.bind(self, [])); + } else { + self.field.set('options', []); + } + //alertify.error(res.data.msg); + } + }).fail(function(e){ + let msg = ''; + if(e.status == 404) { + msg = 'Unable to find url.'; + } else { + msg = e.responseJSON.errormsg; + } + alertify.error(msg); + }); + }, + render: function() { + this.fetchData(); + return Backform.Select2Control.prototype.render.apply(this, arguments); + }, + onChange: function() { + Backform.Select2Control.prototype.onChange.apply(this, arguments); + }, + }); + + let newConnectionModel = pgAdmin.Browser.DataModel.extend({ + idAttribute: 'name', + defaults: { + server: parseInt(sid), + database: null, + user: null, + password: null, + server_name: server_name, + database_name: database_name, + }, + schema: [{ + id: 'server', + name: 'server', + label: gettext('Server'), + type: 'text', + editable: true, + disabled: false, + select2: { + allowClear: false, + }, + control: Backform.Select2Control.extend({ + connect: function(self) { + /*if (alertify.connectServer) { + delete alertify.connectServer; + }*/ + if(!alertify.connectServer){ + alertify.dialog('connectServer', function factory() { + return { + main: function( + title, message, sid, submit_password=true + ) { + this.set('title', title); + this.message = message; + this.server_id = sid; + this.submit_password = submit_password; + }, + setup:function() { + return { + buttons:[{ + text: gettext('Cancel'), className: 'btn btn-secondary fa fa-times pg-alertify-button', + key: 27, + },{ + text: gettext('OK'), key: 13, className: 'btn btn-primary fa fa-check pg-alertify-button', + }], + focus: {element: '#password', select: true}, + options: { + modal: 0, resizable: false, maximizable: false, pinnable: false, + }, + }; + }, + build:function() { + }, + prepare:function() { + this.setContent(this.message); + }, + callback: function(closeEvent) { + + if (closeEvent.button.text == gettext('OK')) { + if(this.submit_password) { + var _url = url_for('schema_diff.connect_server', {'sid': this.server_id}); + + $.ajax({ + type: 'POST', + timeout: 30000, + url: _url, + data: $('#frmPassword').serialize(), + }) + .done(function() { + self.model.attributes.database = null; + self.model.attributes.user = null; + self.model.attributes.role = null; + Backform.Select2Control.prototype.onChange.apply(self, arguments); + response.server_list.forEach(function(obj){ + if(obj.id==self.model.changed.server) { + response.server_name = obj.name; + } + }); + }) + .fail(function(xhr) { + alertify.connectServer('Connect to server', xhr.responseJSON.result, self.getValueFromDOM()); + }); + } else { + response.password = $('#password').val(); + } + } else { + self.model.attributes.database = null; + self.model.attributes.user = null; + self.model.attributes.role = null; + Backform.Select2Control.prototype.onChange.apply(self, arguments); + } + closeEvent.close = true; + }, + }; + }); + } + }, + render: function() { + let self = this; + self.connect(self); + return Backform.Select2Control.prototype.render.apply(self, arguments); + }, + onChange: function() { + this.model.attributes.database = null; + this.model.attributes.user = null; + let self = this; + self.connect(self); + + let url = url_for('sqleditor.connect_server', { + 'sid': self.getValueFromDOM(), + }); + $.ajax({ + async: false, + url: url, + headers: { + 'Cache-Control' : 'no-cache', + }, + }).done(function () { + Backform.Select2Control.prototype.onChange.apply(self, arguments); + response.server_list.forEach(function(obj){ + if(obj.id==self.model.changed.server) { + response.server_name = obj.name; + } + }); + }).fail(function(xhr){ + alertify.connectServer('Connect to server', xhr.responseJSON.result, self.getValueFromDOM()); + }); + + }, + }), + options: function() { + return _.map(response.server_list, (obj) => { + if (obj.id == parseInt(sid)) + response.server_name = obj.name; + + return { + value: obj.id, + label: obj.name, + }; + }); + }, + }, + { + id: 'database', + name: 'database', + label: gettext('Database'), + type: 'text', + editable: true, + disabled: function(m) { + let self_local = this; + if (!_.isUndefined(m.get('server')) && !_.isNull(m.get('server')) + && m.get('server') !== '') { + setTimeout(function() { + if(self_local.options.length) { + m.set('database', self_local.options[0].value); + } + }, 10); + return false; + } + + return true; + }, + deps: ['server'], + url: 'sqleditor.get_new_connection_database', + select2: { + allowClear: false, + width: '100%', + first_empty: true, + select_first: false, + }, + extraClasses:['new-connection-dialog-style'], + control: NewConnectionSelect2Control, + transform: function(data) { + response.database_list = data; + return data; + }, + }, + { + id: 'user', + name: 'user', + label: gettext('User'), + type: 'text', + editable: true, + deps: ['server'], + select2: { + allowClear: false, + width: '100%', + }, + control: NewConnectionSelect2Control, + url: 'sqleditor.get_new_connection_user', + disabled: function(m) { + let self_local = this; + if (!_.isUndefined(m.get('server')) && !_.isNull(m.get('server')) + && m.get('server') !== '') { + setTimeout(function() { + if(self_local.options.length) { + m.set('user', self_local.options[0].value); + } + }, 10); + return false; + } + return true; + }, + },{ + id: 'role', + name: 'role', + label: gettext('Role'), + type: 'text', + editable: true, + deps: ['server'], + select2: { + allowClear: false, + width: '100%', + first_empty: true, + }, + control: NewConnectionSelect2Control, + url: 'sqleditor.get_new_connection_role', + disabled: false, + }, + /*{ + id: 'password', + name: 'password', + label: gettext('Password'tools/sqleditor/__init__.py), + type: 'password', + editable: true, + disabled: true, + deps: ['user'], + control: Backform.InputControl.extend({ + render: function() { + let self = this; + self.model.attributes.password = null; + Backform.InputControl.prototype.render.apply(self, arguments); + return self; + }, + onChange: function() { + let self = this; + Backform.InputControl.prototype.onChange.apply(self, arguments); + }, + }), + },*/ + ], + validate: function() { + let msg = null; + this.errorModel.clear(); + if(_.isUndefined(this.get('database')) || _.isNull(this.get('database'))){ + msg = gettext('Please select database'); + this.errorModel.set('database', msg); + return msg; + } else if(_.isUndefined(this.get('database')) || _.isUndefined(this.get('user'))|| _.isNull(this.get('user'))) { + msg = gettext('Please select user'); + this.errorModel.set('user', msg); + return msg; + } + /*else if((this.attributes.password == '' || _.isUndefined(this.get('password')) || _.isNull(this.get('password')))) { + msg = gettext('Please enter password'); + this.errorModel.set('password', msg); + return msg; + }*/ + return null; + }, + }); + + let model = new newConnectionModel(); + return model; +} diff --git a/web/pgadmin/static/scss/_alert.scss b/web/pgadmin/static/scss/_alert.scss index dac552b..836f0af 100644 --- a/web/pgadmin/static/scss/_alert.scss +++ b/web/pgadmin/static/scss/_alert.scss @@ -92,6 +92,7 @@ right: 0; left: 0; bottom: 0; + z-index: 1; } .pg-prop-status-bar { diff --git a/web/pgadmin/tools/datagrid/__init__.py b/web/pgadmin/tools/datagrid/__init__.py index f5fc78c..07459be 100644 --- a/web/pgadmin/tools/datagrid/__init__.py +++ b/web/pgadmin/tools/datagrid/__init__.py @@ -18,21 +18,22 @@ from flask import Response, url_for, session, request, make_response from werkzeug.useragents import UserAgent from flask import current_app as app, render_template from flask_babelex import gettext -from flask_security import login_required +from flask_security import login_required, current_user from pgadmin.tools.sqleditor.command import ObjectRegistry, SQLFilter +from pgadmin.tools.sqleditor import check_transaction_status from pgadmin.utils import PgAdminModule from pgadmin.utils.ajax import make_json_response, bad_request, \ - internal_server_error + internal_server_error, unauthorized from config import PG_DEFAULT_DRIVER -from pgadmin.model import Server +from pgadmin.model import Server, User from pgadmin.utils.driver import get_driver from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost from pgadmin.utils.preferences import Preferences from pgadmin.settings import get_setting from pgadmin.browser.utils import underscore_unescape from pgadmin.utils.exception import ObjectGone -from pgadmin.utils.constants import MIMETYPE_APP_JS +from pgadmin.utils.constants import MIMETYPE_APP_JS, UNAUTH_REQ MODULE_NAME = 'datagrid' @@ -73,7 +74,8 @@ class DataGridModule(PgAdminModule): 'datagrid.filter_validate', 'datagrid.filter', 'datagrid.panel', - 'datagrid.close' + 'datagrid.close', + 'datagrid.update_query_tool_connection' ] def on_logout(self, user): @@ -320,10 +322,23 @@ def initialize_query_tool(trans_id, sgid, sid, did=None): req_args['recreate'] == '1'): connect = False + is_error, errmsg, conn_id, version = _init_query_tool(trans_id, connect, + sgid, sid, did) + if is_error: + return errmsg + + return make_json_response( + data={ + 'connId': str(conn_id), + 'serverVersion': version, + } + ) + + +def _init_query_tool(trans_id, connect, sgid, sid, did, **kwargs): # Create asynchronous connection using random connection id. conn_id = str(random.randint(1, 9999999)) - # Use Maintenance database OID manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) if did is None: @@ -334,24 +349,53 @@ def initialize_query_tool(trans_id, sgid, sid, did=None): ) except Exception as e: app.logger.error(e) - return internal_server_error(errormsg=str(e)) + return True, internal_server_error(errormsg=str(e)), '', '' try: conn = manager.connection(did=did, conn_id=conn_id, auto_reconnect=False, use_binary_placeholder=True, array_to_string=True) + is_ask_password = False if connect: - status, msg = conn.connect() + user = None + role = None + password = None + + if 'user' in kwargs and 'role' in kwargs: + user = kwargs['user'] + role = kwargs['role'] if kwargs['role'] else None + password = kwargs['password'] if kwargs['password'] else None + is_ask_password = True + if user: + status, msg = conn.connect(user=user, role=role, + password=password) + else: + status, msg = conn.connect() if not status: app.logger.error(msg) - return internal_server_error(errormsg=str(msg)) + if is_ask_password: + server = Server.query.filter_by(id=sid).first() + return True, make_json_response( + success=0, + status=428, + result=render_template( + 'servers/password.html', + server_label=server.name, + username=user, + errmsg=msg, + _=gettext, + ) + ), '', '' + else: + return True, internal_server_error( + errormsg=str(msg)), '', '' except (ConnectionLost, SSHTunnelConnectionLost) as e: app.logger.error(e) raise except Exception as e: app.logger.error(e) - return internal_server_error(errormsg=str(e)) + return True, internal_server_error(errormsg=str(e)), '', '' if 'gridData' not in session: sql_grid_data = dict() @@ -373,10 +417,80 @@ def initialize_query_tool(trans_id, sgid, sid, did=None): # Store the grid dictionary into the session variable session['gridData'] = sql_grid_data + return False, '', conn_id, manager.version + + [email protected]( + '/initialize/query_tool/update_connection/<int:trans_id>/' + '<int:sgid>/<int:sid>/<int:did>', + methods=["POST"], endpoint='update_query_tool_connection' +) +def update_query_tool_connection(trans_id, sgid, sid, did): + # Remove transaction Id. + with query_tool_close_session_lock: + data = dict() + if request.data: + data = json.loads(request.data, encoding='utf-8') + + if 'gridData' not in session: + return make_json_response(data={'status': True}) + + grid_data = session['gridData'] + + # Return from the function if transaction id not found + if str(trans_id) not in grid_data: + return make_json_response(data={'status': True}) + + connect = True + + req_args = request.args + if ('recreate' in req_args and + req_args['recreate'] == '1'): + connect = False + + new_trans_id = str(random.randint(1, 9999999)) + kwargs = { + 'user': data['user'], + 'role': data['role'], + 'password': data['password'] if 'password' in data else None + } + + is_error, errmsg, conn_id, version = _init_query_tool( + new_trans_id, connect, sgid, sid, did, **kwargs) + + if is_error: + return errmsg + else: + try: + # Check the transaction and connection status + status, error_msg, conn, trans_obj, session_obj = \ + check_transaction_status(trans_id) + + status, error_msg, new_conn, new_trans_obj, new_session_obj = \ + check_transaction_status(new_trans_id) + + new_session_obj['primary_keys'] = session_obj[ + 'primary_keys'] if 'primary_keys' in session_obj else None + new_session_obj['columns_info'] = session_obj[ + 'columns_info'] if 'columns_info' in session_obj else None + new_session_obj['client_primary_key'] = session_obj[ + 'client_primary_key'] if 'client_primary_key'\ + in session_obj else None + + close_query_tool_session(trans_id) + # Remove the information of unique transaction id from the + # session variable. + grid_data.pop(str(trans_id), None) + session['gridData'] = grid_data + except Exception as e: + app.logger.error(e) + # return internal_server_error(errormsg=str(e)) + return make_json_response( data={ 'connId': str(conn_id), - 'serverVersion': manager.version, + 'serverVersion': version, + 'tran_id': new_trans_id } ) diff --git a/web/pgadmin/tools/datagrid/templates/datagrid/index.html b/web/pgadmin/tools/datagrid/templates/datagrid/index.html index 097a42e..16a209f 100644 --- a/web/pgadmin/tools/datagrid/templates/datagrid/index.html +++ b/web/pgadmin/tools/datagrid/templates/datagrid/index.html @@ -392,8 +392,17 @@ title="" role="img"> </i> </div> - <div class="editor-title" - style="background-color: {% if fgcolor %}{{ bgcolor or '#FFFFFF' }}{% endif %}; color: {% if fgcolor %}{{ fgcolor }}{% endif %};"> </div> + <div class="connection-info btn-group mr-1" role="group" aria-label=""> + <div class="editor-title" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" + style="background-color: {% if fgcolor %}{{ bgcolor or '#FFFFFF' }}{% endif %}; color: {% if fgcolor %}{{ fgcolor }}{% endif %};"> + </div> + <span class="conn-info-dd dropdown-toggle dropdown-toggle-split" + data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span> + <ul class="dropdown-menu" id="connections-list"> + </ul> + </div> + + </div> <div id="editor-panel" tabindex="0"> <div id="fetching_data" class="pg-sp-container sql-editor-busy-fetching"> @@ -456,6 +465,7 @@ require(['sources/generated/browser_nodes', 'sources/generated/codemirror', 'sou var script_type_url = ''; {% endif %} // Start the query tool. + sqlEditorController.start( {{ uniqueId }}, {{ url_params|safe}}, diff --git a/web/pgadmin/tools/datagrid/tests/__init__.py b/web/pgadmin/tools/datagrid/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web/pgadmin/tools/datagrid/tests/datagrid_test_data.json b/web/pgadmin/tools/datagrid/tests/datagrid_test_data.json new file mode 100644 index 0000000..0075f35 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/datagrid_test_data.json @@ -0,0 +1,134 @@ +{ + "data_grid_init_query_tool": [ + { + "name": "Datagrid init query tool", + "url": "/datagrid/initialize/query_tool/", + "is_positive_test": true, + "mocking_required": false, + "test_data": {}, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + } + ], + "data_grid_query_tool_close": [ + { + "name": "Datagrid query tool close", + "url": "/datagrid/close/", + "is_positive_test": true, + "mocking_required": false, + "test_data": {}, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + } + ], + "data_grid_validate_filter": [ + { + "name": "Datagrid validate filter", + "url": "/datagrid/filter/validate/", + "is_positive_test": true, + "mocking_required": false, + "test_data": "id = 1", + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Datagrid validate filter", + "url": "/datagrid/filter/validate/", + "is_positive_test": false, + "mocking_required": true, + "test_data": "id = 1", + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_scalar", + "return_value": "(False, 'Mocked Internal Server Error while validate filter')" + }, + "expected_data": { + "status_code": 200 + } + } + ], + "data_grid_update_connection": [ + { + "name": "Datagrid update connection positive", + "url": "/datagrid/initialize/query_tool/update_connection/", + "is_positive_test": true, + "mocking_required": false, + "is_create_role": false, + "test_data": {}, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Datagrid update connection with new user", + "url": "/datagrid/initialize/query_tool/update_connection/", + "is_positive_test": true, + "mocking_required": false, + "is_create_role": true, + "test_data": {}, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + } + ], + "data_grid_panel": [ + { + "name": "Datagrid Panel", + "url": "/datagrid/panel/", + "is_positive_test": true, + "mocking_required": false, + "test_data": {}, + "mock_data": { + }, + "expected_data": { + "status_code": 200 + } + } + ], + "data_grid_initialize": [ + { + "name": "Datagrid Initialize", + "url": "/datagrid/initialize/datagrid/", + "is_positive_test": true, + "mocking_required": false, + "test_data": "id=1", + "mock_data": { + + }, + "expected_data": { + "status_code": 200 + } + },{ + "name": "Datagrid Initialize", + "url": "/datagrid/initialize/datagrid/", + "is_positive_test": true, + "mocking_required": false, + "test_data": null, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Datagrid Initialize", + "url": "/datagrid/initialize/datagrid/", + "is_positive_test": false, + "mocking_required": true, + "test_data": "id=1", + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict", + "return_value": "(False, 'Mocked Internal Server Error while initialize datagrid.')" + }, + "expected_data": { + "status_code": 500 + } + } + ] +} diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_init_query_tool.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_init_query_tool.py new file mode 100644 index 0000000..6ecf5de --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_init_query_tool.py @@ -0,0 +1,72 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from . import utils as data_grid_utils + + +class DatagridInitQueryToolTestCase(BaseTestGenerator): + """ + This will init query-tool connection. + """ + + scenarios = utils.generate_scenarios( + 'data_grid_init_query_tool', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + + self.trans_id = str(random.randint(1, 9999999)) + + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + + def init_query_tool(self): + response = self.tester.post( + self.url + str(self.trans_id) + '/' + str(self.sgid) + '/' + str( + self.sid) + '/' + str(self.did), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will init query tool connection.""" + + if self.is_positive_test: + response = self.init_query_tool() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_panel.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_panel.py new file mode 100644 index 0000000..ae8ec10 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_panel.py @@ -0,0 +1,93 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from . import utils as data_grid_utils + + +class DatagridPanelTestCase(BaseTestGenerator): + """ + This will data grid panel. + """ + + scenarios = utils.generate_scenarios( + 'data_grid_panel', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + + self.trans_id = str(random.randint(1, 9999999)) + qt_init = data_grid_utils._init_query_tool(self, self.trans_id, + self.sgid, self.sid, + self.did) + + if not qt_init['success']: + raise Exception("Could not initialize querty tool.") + + def panel(self): + query_param = \ + '?is_query_tool={0}&sgid={1}&sid={2}&server_type={3}' \ + '&did={4}&title={5}'.format(True, self.sgid, self.sid, + self.server_information['type'], + self.did, 'Query panel') + + response = self.tester.post( + self.url + str(self.trans_id) + query_param, + data=json.dumps(self.test_data), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will update query tool connection.""" + + if self.is_positive_test: + response = self.panel() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + with patch(self.mock_data["function_name"], + return_value=eval(self.mock_data["return_value"])): + response = self.panel() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_query_tool_close.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_query_tool_close.py new file mode 100644 index 0000000..822c2e1 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_query_tool_close.py @@ -0,0 +1,77 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from . import utils as data_grid_utils + + +class DatagridQueryToolCloseTestCase(BaseTestGenerator): + """ + This will close query-tool connection. + """ + + scenarios = utils.generate_scenarios( + 'data_grid_query_tool_close', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + + self.trans_id = str(random.randint(1, 9999999)) + qt_init = data_grid_utils._init_query_tool(self, self.trans_id, + self.sgid, self.sid, + self.did) + + if not qt_init['success']: + raise Exception("Could not initialize querty tool.") + + def close_connection(self): + response = self.tester.delete( + self.url + str(self.trans_id), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will update query tool connection.""" + + if self.is_positive_test: + response = self.close_connection() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_update_connection.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_update_connection.py new file mode 100644 index 0000000..5d2b14a --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_update_connection.py @@ -0,0 +1,120 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from pgadmin.browser.server_groups.servers.roles.tests import \ + utils as roles_utils +from . import utils as data_grid_utils + + +class DatagridUpdateConnectionTestCase(BaseTestGenerator): + """ + This will update query-tool connection. + """ + + scenarios = utils.generate_scenarios( + 'data_grid_update_connection', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + + self.trans_id = str(random.randint(1, 9999999)) + self.roles = None + + if self.is_create_role: + data = roles_utils.get_role_data(self.server['db_password']) + self.role_name = data['rolname'] + self.role_password = data['rolpassword'] + roles_utils.create_role_with_password( + self.server, self.role_name, self.role_password) + + if not self.is_positive_test or self.is_create_role: + qt_init = data_grid_utils._init_query_tool(self, self.trans_id, + self.sgid, self.sid, + self.did) + + if not qt_init['success']: + raise Exception("Could not initialize querty tool.") + + self.test_data = { + "database": self.did, + "server": self.sid, + } + + if self.server_information['type'] == 'ppas': + self.test_data['password'] = 'enterprisedb' + self.test_data['user'] = 'enterprisedb' + else: + self.test_data['password'] = 'postgres' + self.test_data['user'] = 'postgres' + + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + + def update_connection(self, user_data=None): + if user_data: + response = self.tester.post( + self.url + str(self.trans_id) + '/' + str(self.sgid) + + '/' + str(self.sid) + '/' + str(self.did), + data=json.dumps(user_data), + content_type='html/json' + ) + else: + response = self.tester.post( + self.url + str(self.trans_id) + '/' + str(self.sgid) + '/' + + str(self.sid) + '/' + str(self.did), + data=json.dumps(self.test_data), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will update query tool connection.""" + + if self.is_positive_test: + user_data = dict() + if self.is_create_role: + user_data['user'] = self.role_name + user_data['password'] = self.role_password + user_data['role'] = None + response = self.update_connection(user_data=user_data) + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + response = self.update_connection() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_validate_filter.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_validate_filter.py new file mode 100644 index 0000000..0aba5d8 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_validate_filter.py @@ -0,0 +1,91 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from . import utils as data_grid_utils + + +class DatagridValidateFilterTestCase(BaseTestGenerator): + """ + This will validate filter connection. + """ + + scenarios = utils.generate_scenarios( + 'data_grid_validate_filter', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + self.schema_id = parent_node_dict['schema'][-1]["schema_id"] + self.schema_name = parent_node_dict['schema'][-1]["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception("Could not find the schema to add a table.") + self.table_name = "table_for_wizard%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + + def validate_filter(self): + response = self.tester.post( + self.url + str(self.sid) + '/' + str(self.did) + '/' + + str(self.table_id), + data=json.dumps(self.test_data), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will update query tool connection.""" + + if self.is_positive_test: + response = self.validate_filter() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + with patch(self.mock_data["function_name"], + return_value=eval(self.mock_data["return_value"])): + response = self.validate_filter() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/test_initialize_data_grid.py b/web/pgadmin/tools/datagrid/tests/test_initialize_data_grid.py new file mode 100644 index 0000000..130a1d6 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_initialize_data_grid.py @@ -0,0 +1,108 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from . import utils as data_grid_utils + + +class DatagridInitializeTestCase(BaseTestGenerator): + """ + This will Initialize datagrid + """ + + scenarios = utils.generate_scenarios( + 'data_grid_initialize', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + + self.schema_id = parent_node_dict['schema'][-1]["schema_id"] + self.schema_name = parent_node_dict['schema'][-1]["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception("Could not find the schema to add a table.") + self.table_name = "table_for_wizard%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + self.trans_id = str(random.randint(1, 9999999)) + qt_init = data_grid_utils._init_query_tool(self, self.trans_id, + self.sgid, self.sid, + self.did) + + if not qt_init['success']: + raise Exception("Could not initialize query tool.") + + def initialize_datagrid(self): + if self.test_data: + response = self.tester.post( + self.url + str(self.trans_id) + '/4/table/' + + str(self.sgid) + '/' + str(self.sid) + '/' + + str(self.did) + '/' + str(self.table_id), + data=json.dumps(self.test_data), + content_type='html/json' + ) + else: + response = self.tester.post( + self.url + str(self.trans_id) + '/4/table/' + + str(self.sgid) + '/' + str(self.sid) + '/' + + str(self.did) + '/' + str(self.table_id), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will update query tool connection.""" + + if self.is_positive_test: + response = self.initialize_datagrid() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + with patch(self.mock_data["function_name"], + return_value=eval(self.mock_data["return_value"])): + response = self.initialize_datagrid() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/utils.py b/web/pgadmin/tools/datagrid/tests/utils.py new file mode 100644 index 0000000..c3d4bb5 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/utils.py @@ -0,0 +1,33 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## +import os +import json + +file_name = os.path.basename(__file__) +CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) +with open(CURRENT_PATH + "/datagrid_test_data.json") as data_file: + test_cases = json.load(data_file) + + +def _init_query_tool(self, trans_id, server_group, server_id, db_id): + QUERY_TOOL_INIT_URL = '/datagrid/initialize/query_tool' + + qt_init = self.tester.post( + '{0}/{1}/{2}/{3}/{4}'.format( + QUERY_TOOL_INIT_URL, + trans_id, + server_group, + server_id, + db_id + ), + follow_redirects=True + ) + assert qt_init.status_code == 200 + qt_init = json.loads(qt_init.data.decode('utf-8')) + return qt_init diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py index c62bc7c..a02f6b9 100644 --- a/web/pgadmin/tools/sqleditor/__init__.py +++ b/web/pgadmin/tools/sqleditor/__init__.py @@ -10,33 +10,38 @@ """A blueprint module implementing the sqleditor frame.""" import os import pickle -import sys import re +from urllib.parse import unquote import simplejson as json -from flask import Response, url_for, render_template, session, request, \ - current_app +from config import PG_DEFAULT_DRIVER, ON_DEMAND_RECORD_COUNT +from flask import Response, url_for, render_template, session, current_app +from flask import request, jsonify from flask_babelex import gettext from flask_security import login_required, current_user -from urllib.parse import unquote - -from config import PG_DEFAULT_DRIVER, ON_DEMAND_RECORD_COUNT from pgadmin.misc.file_manager import Filemanager from pgadmin.tools.sqleditor.command import QueryToolCommand from pgadmin.tools.sqleditor.utils.constant_definition import ASYNC_OK, \ ASYNC_EXECUTION_ABORTED, \ CONNECTION_STATUS_MESSAGE_MAPPING, TX_STATUS_INERROR +from pgadmin.tools.sqleditor.utils.filter_dialog import FilterDialog +from pgadmin.tools.sqleditor.utils.query_history import QueryHistory +from pgadmin.tools.sqleditor.utils.query_tool_fs_utils import \ + read_file_generator +from pgadmin.tools.sqleditor.utils.query_tool_preferences import \ + register_query_tool_preferences from pgadmin.tools.sqleditor.utils.start_running_query import StartRunningQuery from pgadmin.tools.sqleditor.utils.update_session_grid_transaction import \ update_session_grid_transaction from pgadmin.utils import PgAdminModule from pgadmin.utils import get_storage_directory from pgadmin.utils.ajax import make_json_response, bad_request, \ - success_return, internal_server_error + internal_server_error +from pgadmin.utils.constants import MIMETYPE_APP_JS from pgadmin.utils.driver import get_driver -from pgadmin.utils.menu import MenuItem -from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost,\ +from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost, \ CryptKeyMissing +from pgadmin.utils.menu import MenuItem from pgadmin.utils.sqlautocomplete.autocomplete import SQLAutoComplete from pgadmin.tools.sqleditor.utils.query_tool_preferences import \ register_query_tool_preferences @@ -44,8 +49,10 @@ from pgadmin.tools.sqleditor.utils.query_tool_fs_utils import \ read_file_generator from pgadmin.tools.sqleditor.utils.filter_dialog import FilterDialog from pgadmin.tools.sqleditor.utils.query_history import QueryHistory -from pgadmin.utils.constants import MIMETYPE_APP_JS, SERVER_CONNECTION_CLOSED,\ - ERROR_MSG_TRANS_ID_NOT_FOUND +from pgadmin.utils.constants import MIMETYPE_APP_JS, \ + SERVER_CONNECTION_CLOSED, ERROR_MSG_TRANS_ID_NOT_FOUND +from pgadmin.model import Server +from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry MODULE_NAME = 'sqleditor' @@ -109,6 +116,12 @@ class SqlEditorModule(PgAdminModule): 'sqleditor.get_query_history', 'sqleditor.add_query_history', 'sqleditor.clear_query_history', + 'sqleditor.get_new_connection_data', + 'sqleditor.get_new_connection_database', + 'sqleditor.get_new_connection_user', + 'sqleditor.get_new_connection_role', + 'sqleditor.connect_server', + 'sqleditor.connect_server_with_user', ] def register_preferences(self): @@ -224,7 +237,7 @@ def start_view_data(trans_id): ) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: # set fetched row count to 0 as we are executing query again. trans_obj.update_fetched_row_cnt(0) @@ -370,7 +383,7 @@ def poll(trans_id): if isinstance(trans_obj, QueryToolCommand): trans_status = conn.transaction_status() if trans_status == TX_STATUS_INERROR and \ - trans_obj.auto_rollback: + trans_obj.auto_rollback: conn.execute_void("ROLLBACK;") st, result = conn.async_fetchmany_2darray(ON_DEMAND_RECORD_COUNT) @@ -680,13 +693,12 @@ def save(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: # If there is no primary key found then return from the function. if ('primary_keys' not in session_obj or - len(session_obj['primary_keys']) <= 0 or - len(changed_data) <= 0) and \ - 'has_oids' not in session_obj: + len(session_obj['primary_keys']) <= 0 or + len(changed_data) <= 0) and 'has_oids' not in session_obj: return make_json_response( data={ 'status': False, @@ -753,7 +765,7 @@ def append_filter_inclusive(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = None filter_sql = '' @@ -807,7 +819,7 @@ def append_filter_exclusive(trans_id): info='DATAGRID_TRANSACTION_REQUIRED', status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = None filter_sql = '' @@ -860,7 +872,7 @@ def remove_filter(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = None @@ -904,7 +916,7 @@ def set_limit(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = None @@ -1046,7 +1058,7 @@ def get_object_name(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = trans_obj.object_name else: status = False @@ -1082,7 +1094,7 @@ def set_auto_commit(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = None @@ -1127,7 +1139,7 @@ def set_auto_rollback(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = None @@ -1179,7 +1191,7 @@ def auto_complete(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: # Create object of SQLAutoComplete class and pass connection object auto_complete_obj = SQLAutoComplete( @@ -1465,6 +1477,284 @@ def get_filter_data(trans_id): return FilterDialog.get(status, error_msg, conn, trans_obj, session_ob) [email protected]( + '/new_connection_dialog/<int:sgid>/<int:sid>', + methods=["GET"], endpoint='get_new_connection_data' +) +@login_required +def get_new_connection_data(sgid, sid=None): + """ + This method is used to get required data for get new connection. + :extract_sql_from_network_parameters, + """ + try: + # if sid and not did: + servers = Server.query.all() + server_list = [ + {'name': server.serialize['name'], "id": server.serialize['id']} + for server in servers] + + msg = "Success" + return make_json_response( + data={ + 'status': True, + 'msg': msg, + 'result': { + 'server_list': server_list + } + } + ) + + except Exception as e: + return make_json_response( + data={ + 'status': False, + 'msg': 'Unable to fetch data.', + 'result': { + 'server_list': [] + } + } + ) + + [email protected]( + '/new_connection_database/<int:sgid>/<int:sid>', + methods=["GET"], endpoint='get_new_connection_database' +) +@login_required +def get_new_connection_database(sgid, sid=None): + """ + This method is used to get required data for get new connection. + :extract_sql_from_network_parameters, + """ + try: + database_list = [] + from pgadmin.utils.driver import get_driver + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) + conn = manager.connection() + if conn.connected(): + is_connected = True + else: + # connection = conn.connect() + is_connected = False + if is_connected: + if sid: + template_path = 'databases/sql/#{0}#'.format(manager.version) + last_system_oid = 0 + server_node_res = manager + + db_disp_res = None + params = None + if server_node_res and server_node_res.db_res: + db_disp_res = ", ".join( + ['%s'] * len(server_node_res.db_res.split(',')) + ) + params = tuple(server_node_res.db_res.split(',')) + sql = render_template( + "/".join([template_path, 'nodes.sql']), + last_system_oid=last_system_oid, + db_restrictions=db_disp_res + ) + status, databases = conn.execute_dict(sql, params) + database_list = [ + {'label': database['name'], 'value': database['did']} for + database in databases['rows']] + else: + status = False + + msg = "Success" + return make_json_response( + data={ + 'status': status, + 'msg': msg, + 'result': { + 'data': database_list, + } + } + ) + else: + return make_json_response( + data={ + 'status': False, + 'msg': 'Server not connected, ' + 'Please connect with server and try again.', + 'result': { + 'database_list': [], + } + } + ) + except Exception as e: + return make_json_response( + data={ + 'status': False, + 'msg': 'Unable to fetch data.', + 'result': { + 'database_list': [], + } + } + ) + + [email protected]( + '/new_connection_user/<int:sgid>/<int:sid>', + methods=["GET"], endpoint='get_new_connection_user' +) +@login_required +def get_new_connection_user(sgid, sid=None): + """ + This method is used to get required data for get new connection. + :extract_sql_from_network_parameters, + """ + try: + from pgadmin.utils.driver import get_driver + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) + conn = manager.connection() + user_list = [] + if conn.connected(): + is_connected = True + else: + is_connected = False + if is_connected: + if sid: + sql_path = 'roles/sql/#{0}#'.format(manager.version) + status, users = conn.execute_2darray( + render_template(sql_path + 'nodes.sql') + ) + user_list = [ + {'value': user['rolname'], 'label': user['rolname']} for + user in users['rows'] if user['rolcanlogin']] + else: + status = False + + msg = "Success" + return make_json_response( + data={ + 'status': status, + 'msg': msg, + 'result': { + 'data': user_list, + } + } + ) + else: + return make_json_response( + data={ + 'status': False, + 'msg': 'Server not connected, ' + 'Please connect with server and try again.', + 'result': { + 'user_list': [], + } + } + ) + except Exception as e: + return make_json_response( + data={ + 'status': False, + 'msg': 'Unable to fetch data.', + 'result': { + 'user_list': [], + } + } + ) + + [email protected]( + '/new_connection_role/<int:sgid>/<int:sid>', + methods=["GET"], endpoint='get_new_connection_role' +) +@login_required +def get_new_connection_role(sgid, sid=None): + """ + This method is used to get required data for get new connection. + :extract_sql_from_network_parameters, + """ + try: + from pgadmin.utils.driver import get_driver + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) + conn = manager.connection() + role_list = [] + if conn.connected(): + is_connected = True + else: + is_connected = False + if is_connected: + if sid: + sql_path = 'roles/sql/#{0}#'.format(manager.version) + status, roles = conn.execute_2darray( + render_template(sql_path + 'nodes.sql') + ) + role_list = [ + {'value': role['rolname'], 'label': role['rolname']} for + role in roles['rows']] + else: + status = False + + msg = "Success" + return make_json_response( + data={ + 'status': status, + 'msg': msg, + 'result': { + 'data': role_list, + } + } + ) + else: + return make_json_response( + data={ + 'status': False, + 'msg': 'Server not connected, ' + 'Please connect with server and try again.', + 'result': { + 'user_list': [], + } + } + ) + except Exception as e: + return make_json_response( + data={ + 'status': False, + 'msg': 'Unable to fetch data.', + 'result': { + 'user_list': [], + } + } + ) + + [email protected]( + '/connect_server/<int:sid>/<usr>', + methods=["GET"], + endpoint="connect_server_with_user" +) [email protected]( + '/connect_server/<int:sid>', + methods=["GET"], + endpoint="connect_server" +) +@login_required +def connect_server(sid, usr=None): + # Check if server is already connected then no need to reconnect again. + server = Server.query.filter_by(id=sid).first() + driver = get_driver(PG_DEFAULT_DRIVER) + manager = driver.connection_manager(sid) + conn = manager.connection() + user = None + if usr and manager.user != usr: + user = usr + else: + if conn.connected(): + return make_json_response( + success=1, + info=gettext("Server connected."), + data={} + ) + + view = SchemaDiffRegistry.get_node_view('server') + return view.connect(server.servergroup_id, sid, user=user) + + @blueprint.route( '/filter_dialog/<int:trans_id>', methods=["PUT"], endpoint='set_filter_data' diff --git a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css index 00543ff..b8c2d2e 100644 --- a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css +++ b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css @@ -315,10 +315,6 @@ input.editor-checkbox:focus { padding: 10px 0px; } -.editor-title { - width:100%; -} - .connection-status-hide { display: none !important; } @@ -395,3 +391,7 @@ input.editor-checkbox:focus { .hide-vertical-scrollbar { overflow-y: hidden; } + +.new-connection-dialog-style { + width: 100% !important; +} diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js index ddd9ad7..9389db5 100644 --- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js +++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js @@ -14,6 +14,7 @@ define('tools.querytool', [ 'jqueryui.position', 'underscore', 'pgadmin.alertifyjs', 'sources/pgadmin', 'backbone', 'bundled_codemirror', 'sources/utils', 'pgadmin.misc.explain', + 'pgadmin.user_management.current_user', 'sources/selection/grid_selector', 'sources/selection/active_cell_capture', 'sources/selection/clipboard', @@ -26,6 +27,7 @@ define('tools.querytool', [ 'sources/sqleditor/execute_query', 'sources/sqleditor/query_tool_http_error_handler', 'sources/sqleditor/filter_dialog', + 'sources/sqleditor/new_connection_dialog', 'sources/sqleditor/geometry_viewer', 'sources/sqleditor/history/history_collection.js', 'sources/sqleditor/history/query_history', @@ -52,8 +54,8 @@ define('tools.querytool', [ 'pgadmin.tools.user_management', ], function( gettext, url_for, $, jqueryui, jqueryui_position, _, alertify, pgAdmin, Backbone, codemirror, pgadminUtils, - pgExplain, GridSelector, ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent, - XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler, FilterHandler, + pgExplain, current_user, GridSelector, ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent, + XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler, FilterHandler, newConnectionHandler, GeometryViewer, historyColl, queryHist, querySources, keyboardShortcuts, queryToolActions, queryToolNotifications, Datagrid, modifyAnimation, calculateQueryRunTime, callRenderAfterPoll, queryToolPref, queryTxnStatus, csrfToken, panelTitleFunc, @@ -89,6 +91,9 @@ define('tools.querytool', [ this.layout = opts.layout; this.set_server_version(opts.server_ver); this.trigger('pgadmin-sqleditor:view:initialised'); + this.connection_list = [ + {'server_group': null,'server': null, 'database': null, 'user': null, 'role': null, 'title': '<New Connection>'}, + ]; }, // Bind all the events @@ -151,6 +156,35 @@ define('tools.querytool', [ 'click #btn-rollback': 'on_rollback_transaction', }, + render_connection: function(data_list) { + if(this.handler.is_query_tool) { + var dropdownElement = document.getElementById('connections-list'); + dropdownElement.innerHTML = ''; + data_list.forEach((option, index) => { + $('#connections-list').append('<li class="connection-list-item" data-index='+ index +'><a class="dropdown-item" href="#" tabindex="0">'+ option.title +'</a></li>'); + + }); + var self = this; + $('.connection-list-item').click(function() { + self.get_connection_data(this); + }); + } else { + $('.conn-info-dd').hide(); + $('.editor-title').css({pointerEvents: 'none'}); + } + }, + + get_connection_data: function(event){ + var index = $(event).attr('data-index'); + var connection_details = this.connection_list[index]; + if(connection_details.server_group) { + this.on_change_connection(connection_details); + } else { + this.on_new_connection(); + } + + }, + reflectPreferences: function() { let self = this, browser = pgWindow.default.pgAdmin.Browser, @@ -199,8 +233,15 @@ define('tools.querytool', [ }); }, - set_editor_title: function(title) { - this.$el.find('.editor-title').text(title); + set_editor_title: function(title, is_connected) { + if(is_connected) { + this.$el.find('.editor-title').text(title); + this.render_connection(this.connection_list); + } else { + this.$el.find('.editor-title').text(title); + this.render_connection(this.connection_list); + } + }, // This function is used to render the template. @@ -684,6 +725,8 @@ define('tools.querytool', [ pgBrowser.register_to_activity_listener(document, ()=>{ alertify.alert(gettext('Timeout'), gettext('Your session has timed out due to inactivity. Please close the window and login again.')); }); + + self.render_connection(self.connection_list); }, /* Regarding SlickGrid usage in render_grid function. @@ -1595,6 +1638,17 @@ define('tools.querytool', [ ); }, + on_new_connection: function() { + var self = this; + + // Trigger the show_filter signal to the SqlEditorController class + self.handler.trigger( + 'pgadmin-sqleditor:button:show_new_connection', + self, + self.handler + ); + }, + // Callback function for include filter button click. on_include_filter: function(ev) { var self = this; @@ -2038,6 +2092,83 @@ define('tools.querytool', [ queryToolActions.executeRollback(this.handler); }, + on_change_connection: function(connection_details, ref) { + let title = this.$el.find('.editor-title').html(); + if(connection_details['title'] != title) { + var self = this; + $.ajax({ + async: false, + url: url_for('datagrid.update_query_tool_connection', { + 'trans_id': self.transId, + 'sgid': connection_details['server_group'], + 'sid': connection_details['server'], + 'did': connection_details['database'], + }), + method: 'POST', + contentType: 'application/json', + data: JSON.stringify(connection_details), + }) + .done(function(res) { + if(res.success) { + self.transId = res.data.tran_id; + self.handler.transId = res.data.tran_id; + self.handler.url_params = { + 'did': connection_details['database'], + 'is_query_tool': self.handler.url_params.is_query_tool, + 'server_type': self.handler.url_params.server_type, + 'sgid': connection_details['server_group'], + 'sid': connection_details['server'], + 'title': connection_details['title'], + }; + self.set_editor_title(self.handler.url_params.title); + self.handler.setTitle(self.handler.url_params.title); + alertify.success('connected successfully'); + if(ref){ + let connection_data = { + 'server_group': self.handler.url_params.sgid, + 'server': connection_details['server'], + 'database': connection_details['database'], + 'user': connection_details['user'], + 'title': connection_details['title'], + 'role': connection_details['role'], + 'password': connection_details['password'], + 'is_allow_new_connection': true, + }; + self.connection_list.unshift(connection_data); + self.render_connection(self.connection_list); + ref.close(); + } + } + return true; + }) + .fail(function(xhr) { + if(xhr.status == 428) { + alertify.connectServer('Connect to server', xhr.responseJSON.result, connection_details['server'], false); + } else { + alertify.error(xhr.responseJSON['errormsg']); + } + /*let url = url_for('sqleditor.connect_server_with_user', { + 'sid': newConnCollectionModel['server'], + 'usr': newConnCollectionModel['user'] + }); + $.ajax({ + async: false, + url: url, + headers: { + 'Cache-Control' : 'no-cache', + }, + }).done(function () { + Backform.Select2Control.prototype.onChange.apply(self, arguments); + response.server_list.forEach(function(obj){ + if(obj.id==self.model.changed.server) { + response.server_name = obj.name; + } + }); + }).fail(function(xhr){});*/ + + }); + } + }, }); /* Defining controller class for data grid, which actually @@ -2357,7 +2488,18 @@ define('tools.querytool', [ }); $('#btn-conn-status i').removeClass('obtaining-conn'); - self.gridView.set_editor_title(_.unescape(url_params.title)); + self.gridView.set_editor_title(_.unescape(url_params.title), true); + let connection_data = { + 'server_group': self.gridView.handler.url_params.sgid, + 'server': self.gridView.handler.url_params.sid, + 'database': self.gridView.handler.url_params.did, + 'user': null, + 'role': null, + 'title': _.unescape(url_params.title), + 'is_allow_new_connection': false, + }; + self.gridView.connection_list.unshift(connection_data); + self.gridView.render_connection(self.gridView.connection_list); }; pgBrowser.Events.on('pgadmin:query_tool:connected:' + transId, afterConn); @@ -2452,6 +2594,7 @@ define('tools.querytool', [ self.on('pgadmin-sqleditor:button:save_file', self._save_file, self); self.on('pgadmin-sqleditor:button:deleterow', self._delete, self); self.on('pgadmin-sqleditor:button:show_filter', self._show_filter, self); + self.on('pgadmin-sqleditor:button:show_new_connection', self._show_new_connection, self); self.on('pgadmin-sqleditor:button:include_filter', self._include_filter, self); self.on('pgadmin-sqleditor:button:exclude_filter', self._exclude_filter, self); self.on('pgadmin-sqleditor:button:remove_filter', self._remove_filter, self); @@ -3622,7 +3765,6 @@ define('tools.querytool', [ } }; }, - // This function will show the filter in the text area. _show_filter: function() { let self = this, @@ -3637,7 +3779,19 @@ define('tools.querytool', [ } FilterHandler.dialog(self, reconnect); }, + // This function will show the new connection. + _show_new_connection: function() { + let self = this, + reconnect = false; + /* When server is disconnected and connected, connection is lost, + * To reconnect pass true + */ + if (arguments.length > 0 && arguments[arguments.length - 1] == 'connect') { + reconnect = true; + } + newConnectionHandler.dialog(self, reconnect); + }, // This function will include the filter by selection. _include_filter: function() { var self = this, diff --git a/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss b/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss index fd1e5d3..53f2449 100644 --- a/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss +++ b/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss @@ -30,6 +30,19 @@ color: $sql-title-fg; } +.connection-info { + background: $sql-title-bg; + color: $sql-title-fg; + width:100%; + display: inherit; +} + +.conn-info-dd { + padding-top: 0.3em; + padding-left: 0.2em; + cursor: pointer; +} + #editor-panel { z-index: 0; diff --git a/web/pgadmin/tools/sqleditor/tests/test_new_connection_database.py b/web/pgadmin/tools/sqleditor/tests/test_new_connection_database.py new file mode 100644 index 0000000..20fe3e3 --- /dev/null +++ b/web/pgadmin/tools/sqleditor/tests/test_new_connection_database.py @@ -0,0 +1,98 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.test_setup import config_data +from regression.python_test_utils import test_utils as utils + + +class TestNewConnectionDatabase(BaseTestGenerator): + """ This class will test new connection database. """ + scenarios = [ + ('New connection dialog', + dict( + url="/sqleditor/new_connection_database/", + is_positive_test=True, + mocking_required=False, + is_server_conn_required=False, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ('New connection dialog connect server', + dict( + url="/sqleditor/new_connection_database/", + is_positive_test=True, + mocking_required=False, + is_server_conn_required=True, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ('New connection dialog negative', + dict( + url="/sqleditor/new_connection_database/", + is_positive_test=False, + mocking_required=False, + is_server_conn_required=True, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ] + + def setUp(self): + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + def get_database(self): + response = self.tester.get( + self.url + str(self.sgid) + '/' + str(self.sid), + content_type='html/json' + ) + + return response + + def runTest(self): + if self.is_positive_test: + if self.is_server_conn_required: + self.server['password'] = self.server['db_password'] + server_response = self.tester.post( + '/browser/server/connect/{0}/{1}'.format( + utils.SERVER_GROUP, + self.sid), + data=json.dumps(self.server), + content_type='html/json' + ) + response = self.get_database() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + if self.is_server_conn_required: + self.server['password'] = self.server['db_password'] + server_response = self.tester.post( + '/browser/server/connect/{0}/{1}'.format( + utils.SERVER_GROUP, + self.sid), + data=json.dumps(self.server), + content_type='html/json' + ) + self.sid = 0 + response = self.get_database() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + self.assertEqual(actual_response_code, expected_response_code) diff --git a/web/pgadmin/tools/sqleditor/tests/test_new_connection_dialog.py b/web/pgadmin/tools/sqleditor/tests/test_new_connection_dialog.py new file mode 100644 index 0000000..75a47ef --- /dev/null +++ b/web/pgadmin/tools/sqleditor/tests/test_new_connection_dialog.py @@ -0,0 +1,50 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## +import json +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.test_setup import config_data +from regression.python_test_utils import test_utils as utils + + +class TestNewConnectionDialog(BaseTestGenerator): + """ This class will test new connection dialog. """ + scenarios = [ + ('New connection dialog', + dict( + url="/sqleditor/new_connection_dialog/", + is_positive_test=True, + mocking_required=False, + is_connect_server=False, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ] + + def setUp(self): + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + def new_connection(self): + response = self.tester.get( + self.url + str(self.sgid) + '/' + str(self.sgid), + content_type='html/json' + ) + + return response + + def runTest(self): + if self.is_positive_test: + response = self.new_connection() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + self.assertEqual(actual_response_code, expected_response_code) diff --git a/web/pgadmin/tools/sqleditor/tests/test_new_connection_user.py b/web/pgadmin/tools/sqleditor/tests/test_new_connection_user.py new file mode 100644 index 0000000..7b6c12e --- /dev/null +++ b/web/pgadmin/tools/sqleditor/tests/test_new_connection_user.py @@ -0,0 +1,98 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.test_setup import config_data +from regression.python_test_utils import test_utils as utils + + +class TestNewConnectionUser(BaseTestGenerator): + """ This class will test new connection user. """ + scenarios = [ + ('New connection dialog', + dict( + url="/sqleditor/new_connection_user/", + is_positive_test=True, + mocking_required=False, + is_server_conn_required=False, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ('New connection dialog connect server', + dict( + url="/sqleditor/new_connection_user/", + is_positive_test=True, + mocking_required=False, + is_server_conn_required=True, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ('New connection dialog negative', + dict( + url="/sqleditor/new_connection_user/", + is_positive_test=False, + mocking_required=False, + is_server_conn_required=True, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ] + + def setUp(self): + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + def get_use(self): + response = self.tester.get( + self.url + str(self.sgid) + '/' + str(self.sid), + content_type='html/json' + ) + + return response + + def runTest(self): + if self.is_positive_test: + if self.is_server_conn_required: + self.server['password'] = self.server['db_password'] + server_response = self.tester.post( + '/browser/server/connect/{0}/{1}'.format( + utils.SERVER_GROUP, + self.sid), + data=json.dumps(self.server), + content_type='html/json' + ) + response = self.get_use() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + if self.is_server_conn_required: + self.server['password'] = self.server['db_password'] + server_response = self.tester.post( + '/browser/server/connect/{0}/{1}'.format( + utils.SERVER_GROUP, + self.sid), + data=json.dumps(self.server), + content_type='html/json' + ) + self.sid = 0 + response = self.get_use() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + self.assertEqual(actual_response_code, expected_response_code) diff --git a/web/pgadmin/utils/driver/psycopg2/connection.py b/web/pgadmin/utils/driver/psycopg2/connection.py index 9cb65bc..83c2c17 100644 --- a/web/pgadmin/utils/driver/psycopg2/connection.py +++ b/web/pgadmin/utils/driver/psycopg2/connection.py @@ -21,7 +21,7 @@ import psycopg2 from flask import g, current_app from flask_babelex import gettext from flask_security import current_user -from pgadmin.utils.crypto import decrypt +from pgadmin.utils.crypto import decrypt, encrypt from psycopg2.extensions import encodings import config @@ -211,8 +211,17 @@ class Connection(BaseConnection): password = None passfile = None manager = self.manager + encpass = None + is_update_password = True + crypt_key_present, crypt_key = get_crypt_key() + + if 'user' in kwargs and kwargs['password']: + password = kwargs['password'] + kwargs.pop('password') + is_update_password = False + else: + encpass = kwargs['password'] if 'password' in kwargs else None - encpass = kwargs['password'] if 'password' in kwargs else None passfile = kwargs['passfile'] if 'passfile' in kwargs else None tunnel_password = kwargs['tunnel_password'] if 'tunnel_password' in \ kwargs else '' @@ -227,16 +236,16 @@ class Connection(BaseConnection): if manager.use_ssh_tunnel == 1: manager.check_ssh_tunnel_alive() - if encpass is None: - encpass = self.password or getattr(manager, 'password', None) + if is_update_password: + if encpass is None: + encpass = self.password or getattr(manager, 'password', None) - self.password = encpass + self.password = encpass # Reset the existing connection password if self.reconnecting is not False: self.password = None - crypt_key_present, crypt_key = get_crypt_key() if not crypt_key_present: raise CryptKeyMissing() @@ -269,7 +278,10 @@ class Connection(BaseConnection): try: database = self.db - user = manager.user + if 'user' in kwargs and kwargs['user']: + user = kwargs['user'] + else: + user = manager.user conn_id = self.conn_id import os @@ -338,10 +350,10 @@ class Connection(BaseConnection): self.wasConnected = False raise e - if status: + if status and is_update_password: manager._update_password(encpass) else: - if not self.reconnecting: + if not self.reconnecting and is_update_password: self.wasConnected = False return status, msg @@ -359,7 +371,7 @@ class Connection(BaseConnection): else: self.conn.autocommit = True - def _set_role(self, manager, cur, conn_id): + def _set_role(self, manager, cur, conn_id, **kwargs): """ Set role :param manager: @@ -367,8 +379,18 @@ class Connection(BaseConnection): :param conn_id: :return: """ - if manager.role: - status = self._execute(cur, "SET ROLE TO %s", [manager.role]) + is_set_role = False + role = None + + if 'role' in kwargs and kwargs['role']: + is_set_role = True + role = kwargs['role'] + elif manager.role: + is_set_role = True + role = manager.role + + if is_set_role: + status = self._execute(cur, "SET ROLE TO %s", [role]) if status is not None: self.conn.close() @@ -382,7 +404,7 @@ class Connection(BaseConnection): msg=status ) ) - return False, \ + return True, \ _( "Failed to setup the role with error message:\n{0}" ).format(status) @@ -445,7 +467,7 @@ class Connection(BaseConnection): return False, status - is_error, errmsg = self._set_role(manager, cur, conn_id) + is_error, errmsg = self._set_role(manager, cur, conn_id, **kwargs) if is_error: return False, errmsg @@ -491,7 +513,7 @@ WHERE db.datname = current_database()""") if len(manager.db_info) == 1: manager.did = res['did'] - self._set_user_info(cur, manager) + self._set_user_info(cur, manager, **kwargs) self._set_server_type_and_password(kwargs, manager) @@ -499,7 +521,7 @@ WHERE db.datname = current_database()""") return True, None - def _set_user_info(self, cur, manager): + def _set_user_info(self, cur, manager, **kwargs): """ Set user info. :param cur: @@ -517,7 +539,7 @@ WHERE db.datname = current_database()""") WHERE rolname = current_user""") - if status is None: + if status is None and 'user' not in kwargs: manager.user_info = dict() if cur.rowcount > 0: manager.user_info = cur.fetchmany(1)[0] ^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: [pgAdmin][RM3794]:Allow User to Change Database Connection from an Open Query Tool Tab @ 2020-09-28 12:28 Akshay Joshi <[email protected]> parent: Nikhil Mohite <[email protected]> 0 siblings, 1 reply; 18+ messages in thread From: Akshay Joshi @ 2020-09-28 12:28 UTC (permalink / raw) To: Nikhil Mohite <[email protected]>; +Cc: pgadmin-hackers Hi Nikhil The patch is not applying, rebase, and send it again. Please check your code should not create any new SonarQube issues. On Mon, Sep 28, 2020 at 11:20 AM Nikhil Mohite < [email protected]> wrote: > Hi Akshay, > > I have resolved all the review comments and also updated the test cases as > per the new implementation. > > PFA updated patch. > > > > On Mon, Sep 21, 2020 at 5:24 PM Akshay Joshi < > [email protected]> wrote: > >> Hi Nikhil >> >> Following are the initial review comments: >> >> - Open View/Edit data on any table and click on the same database >> connection and then click on the Execute button. Got "get_primary_keys() >> takes 1 positional argument but 2 were given" error. >> - In my opinion, we should hide the option to change the database >> connection for View/Edit Data. >> - If the user clicks on the same database connection multiple times >> then no need to change the backend connection and transaction id. Add >> validation at the backend, no action required in this case. >> - The role option is missing from the "connect to server" dialog. >> - The Password field should not be there on the "connect to server" >> dialog. Sometimes we saved the password so asking a password every time is >> not correct. Check the pgAdmin 3 behavior. >> >> Code review still remains. >> >> On Thu, Sep 17, 2020 at 4:15 PM Nikhil Mohite < >> [email protected]> wrote: >> >>> Hi Team, >>> >>> Regarding RM-3794 <https://redmine.postgresql.org/issues/3794; allow >>> the user to change the database connection from an open query tool: >>> I have implemented the feature and also added documentation for it. >>> >>> PFA patch. >>> >>> -- >>> *Thanks & Regards,* >>> *Nikhil Mohite* >>> *Software Engineer.* >>> *EDB Postgres* <https://www.enterprisedb.com/; >>> *Mob.No: +91-7798364578.* >>> >> >> >> -- >> *Thanks & Regards* >> *Akshay Joshi* >> *pgAdmin Hacker | Sr. Software Architect* >> *EDB Postgres <http://edbpostgres.com>* >> >> *Mobile: +91 976-788-8246* >> > -- *Thanks & Regards* *Akshay Joshi* *pgAdmin Hacker | Sr. Software Architect* *EDB Postgres <http://edbpostgres.com>* *Mobile: +91 976-788-8246* ^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: [pgAdmin][RM3794]:Allow User to Change Database Connection from an Open Query Tool Tab @ 2020-09-28 14:01 Nikhil Mohite <[email protected]> parent: Akshay Joshi <[email protected]> 0 siblings, 1 reply; 18+ messages in thread From: Nikhil Mohite @ 2020-09-28 14:01 UTC (permalink / raw) To: Akshay Joshi <[email protected]>; +Cc: pgadmin-hackers Hi Akshay, I have resolved code conflict issues and sonarqube issues. PFA updated patch. Regards, Nikhil Mohite. On Mon, Sep 28, 2020 at 5:58 PM Akshay Joshi <[email protected]> wrote: > Hi Nikhil > > The patch is not applying, rebase, and send it again. Please check your > code should not create any new SonarQube issues. > > On Mon, Sep 28, 2020 at 11:20 AM Nikhil Mohite < > [email protected]> wrote: > >> Hi Akshay, >> >> I have resolved all the review comments and also updated the test cases >> as per the new implementation. >> >> PFA updated patch. >> >> >> >> On Mon, Sep 21, 2020 at 5:24 PM Akshay Joshi < >> [email protected]> wrote: >> >>> Hi Nikhil >>> >>> Following are the initial review comments: >>> >>> - Open View/Edit data on any table and click on the same database >>> connection and then click on the Execute button. Got "get_primary_keys() >>> takes 1 positional argument but 2 were given" error. >>> - In my opinion, we should hide the option to change the database >>> connection for View/Edit Data. >>> - If the user clicks on the same database connection multiple times >>> then no need to change the backend connection and transaction id. Add >>> validation at the backend, no action required in this case. >>> - The role option is missing from the "connect to server" dialog. >>> - The Password field should not be there on the "connect to server" >>> dialog. Sometimes we saved the password so asking a password every time is >>> not correct. Check the pgAdmin 3 behavior. >>> >>> Code review still remains. >>> >>> On Thu, Sep 17, 2020 at 4:15 PM Nikhil Mohite < >>> [email protected]> wrote: >>> >>>> Hi Team, >>>> >>>> Regarding RM-3794 <https://redmine.postgresql.org/issues/3794; allow >>>> the user to change the database connection from an open query tool: >>>> I have implemented the feature and also added documentation for it. >>>> >>>> PFA patch. >>>> >>>> -- >>>> *Thanks & Regards,* >>>> *Nikhil Mohite* >>>> *Software Engineer.* >>>> *EDB Postgres* <https://www.enterprisedb.com/; >>>> *Mob.No: +91-7798364578.* >>>> >>> >>> >>> -- >>> *Thanks & Regards* >>> *Akshay Joshi* >>> *pgAdmin Hacker | Sr. Software Architect* >>> *EDB Postgres <http://edbpostgres.com>* >>> >>> *Mobile: +91 976-788-8246* >>> >> > > -- > *Thanks & Regards* > *Akshay Joshi* > *pgAdmin Hacker | Sr. Software Architect* > *EDB Postgres <http://edbpostgres.com>* > > *Mobile: +91 976-788-8246* > Attachments: [application/octet-stream] RM_3794_V3.patch (219.9K, 3-RM_3794_V3.patch) download | inline diff: diff --git a/docs/en_US/images/new_connection_dialog.png b/docs/en_US/images/new_connection_dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf7ae6aa750360ae7710af61bf8e89f9d520284 GIT binary patch literal 49691 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	%0^c>O%*OkVmcSQwgzI6WP2m3$pEbze=t& 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 NIVmN{VsYb-{|8bOF{}Up literal 0 HcmV?d00001 diff --git a/docs/en_US/images/new_connection_options.png b/docs/en_US/images/new_connection_options.png new file mode 100644 index 0000000000000000000000000000000000000000..7ca94374baf0eb9a8a0e49cbd9d4d426895ff9d0 GIT binary patch literal 45739 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 literal 0 HcmV?d00001 diff --git a/docs/en_US/query_tool.rst b/docs/en_US/query_tool.rst index 83933f0..80f27a5 100644 --- a/docs/en_US/query_tool.rst +++ b/docs/en_US/query_tool.rst @@ -300,3 +300,24 @@ transaction status by clicking on the status icon in the Query Tool: .. image:: images/query_tool_connection_status.png :alt: Query tool connection and transaction statuses :align: center + +Change connection +***************** + +User can connect to another server or database from existing open session of query tool. + +* Click on the connection link next to connection status. +* Now click on the *<New Connection>* option from the dropdown. + +.. image:: images/new_connection_options.png + :alt: Query tool connection options + :align: center + +* Now select server, database, user, and role to connect and click OK. + +.. image:: images/new_connection_dialog.png + :alt: Query tool connection dialog + :align: center + +* A newly created connection will now get listed in the options. +* To connect, select the newly created connection from the dropdown list. diff --git a/web/pgadmin/browser/server_groups/servers/roles/tests/utils.py b/web/pgadmin/browser/server_groups/servers/roles/tests/utils.py index 3a7ee58..028ee64 100644 --- a/web/pgadmin/browser/server_groups/servers/roles/tests/utils.py +++ b/web/pgadmin/browser/server_groups/servers/roles/tests/utils.py @@ -152,3 +152,38 @@ def delete_role(connection, role_names): exception = "Error while deleting role: %s: line:%s %s" % ( file_name, sys.exc_traceback.tb_lineno, exception) print(exception, file=sys.stderr) + + +def create_role_with_password(server, role_name, role_password): + """ + This function create the role. + :param server: + :param role_name: + :param role_password: + :return: + """ + try: + connection = utils.get_db_connection(server['db'], + server['username'], + server['db_password'], + server['host'], + server['port'], + server['sslmode']) + pg_cursor = connection.cursor() + pg_cursor.execute( + "CREATE ROLE %s LOGIN PASSWORD '%s'" % (role_name, role_password)) + connection.commit() + # Get 'oid' from newly created tablespace + pg_cursor.execute( + "SELECT pr.oid from pg_catalog.pg_roles pr WHERE pr.rolname='%s'" % + role_name) + oid = pg_cursor.fetchone() + role_id = '' + if oid: + role_id = oid[0] + connection.close() + return role_id + except Exception as exception: + exception = "Error while deleting role: %s: line:%s %s" % ( + file_name, sys.exc_traceback.tb_lineno, exception) + print(exception, file=sys.stderr) diff --git a/web/pgadmin/model/__init__.py b/web/pgadmin/model/__init__.py index 425c9e6..1d30092 100644 --- a/web/pgadmin/model/__init__.py +++ b/web/pgadmin/model/__init__.py @@ -94,6 +94,15 @@ class ServerGroup(db.Model): name = db.Column(db.String(128), nullable=False) __table_args__ = (db.UniqueConstraint('user_id', 'name'),) + @property + def serialize(self): + """Return object data in easily serializable format""" + return { + 'id': self.id, + 'user_id': self.user_id, + 'name': self.name, + } + class Server(db.Model): """Define a registered Postgres server""" @@ -175,6 +184,44 @@ class Server(db.Model): tunnel_password = db.Column(db.String(64), nullable=True) shared = db.Column(db.Boolean(), nullable=False) + @property + def serialize(self): + """Return object data in easily serializable format""" + return { + "id": self.id, + "user_id": self.user_id, + "servergroup_id": self.servergroup_id, + "name": self.name, + "host": self.host, + "hostaddr": self.hostaddr, + "port": self.port, + "maintenance_db": self.maintenance_db, + "username": self.username, + "password": self.password, + "save_password": self.save_password, + "role": self.role, + "ssl_mode": self.ssl_mode, + "comment": self.comment, + "discovery_id": self.discovery_id, + "db_res": self.db_res, + "passfile": self.passfile, + "sslcert": self.sslcert, + "sslkey": self.sslkey, + "sslrootcert": self.sslrootcert, + "sslcrl": self.sslcrl, + "sslcompression": self.sslcompression, + "bgcolor": self.bgcolor, + "fgcolor": self.fgcolor, + "service": self.service, + "connect_timeout": self.connect_timeout, + "use_ssh_tunnel": self.use_ssh_tunnel, + "tunnel_host": self.tunnel_host, + "tunnel_port": self.tunnel_port, + "tunnel_authentication": self.tunnel_authentication, + "tunnel_identity_file": self.tunnel_identity_file, + "tunnel_password": self.tunnel_password + } + class ModulePreference(db.Model): """Define a preferences table for any modules.""" diff --git a/web/pgadmin/static/js/sqleditor/new_connection_dialog.js b/web/pgadmin/static/js/sqleditor/new_connection_dialog.js new file mode 100644 index 0000000..dc1c064 --- /dev/null +++ b/web/pgadmin/static/js/sqleditor/new_connection_dialog.js @@ -0,0 +1,262 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import gettext from 'sources/gettext'; +import url_for from 'sources/url_for'; +import $ from 'jquery'; +import Alertify from 'pgadmin.alertifyjs'; +import pgAdmin from 'sources/pgadmin'; +import Backform from 'pgadmin.backform'; +import newConnectionDialogModel from 'sources/sqleditor/new_connection_dialog_model'; + + +let NewConnectionDialog = { + 'dialog': function(handler, reconnect) { + let url = url_for('sqleditor.get_new_connection_data', { + 'sid': handler.url_params.sid, + 'sgid': handler.url_params.sgid, + }); + + if(reconnect) { + url += '?connect=1'; + } + + let title = gettext('Connect to server'); + + $.ajax({ + url: url, + headers: { + 'Cache-Control' : 'no-cache', + }, + }).done(function (res) { + let response = res.data.result; + response.database_list = []; + response.user_list = []; + if (Alertify.newConnectionDialog) { + delete Alertify.newConnectionDialog; + } + + // Create Dialog + Alertify.dialog('newConnectionDialog', function factory() { + let $container = $('<div class=\'new-connection-dialog\'></div>'); + return { + main: function(message) { + this.msg = message; + }, + build: function() { + this.elements.content.appendChild($container.get(0)); + Alertify.pgDialogBuild.apply(this); + }, + setup: function(){ + return { + buttons: [ + { + text: '', + key: 112, + className: 'btn btn-primary-icon pull-left fa fa-question pg-alertify-icon-button', + attrs: { + name: 'dialog_help', + type: 'button', + label: gettext('Help'), + 'aria-label': gettext('Help'), + url: url_for('help.static', { + 'filename': 'query_tool.html', + }), + }, + }, + { + text: gettext('Cancel'), + key: 27, + className: 'btn btn-secondary fa fa-times pg-alertify-button', + 'data-btn-name': 'cancel', + }, { + text: gettext('OK'), + key: 13, + className: 'btn btn-primary fa fa-check pg-alertify-button', + 'data-btn-name': 'ok', + }, + ], + // Set options for dialog + options: { + title: title, + //disable both padding and overflow control. + padding: !1, + overflow: !1, + model: 0, + resizable: true, + maximizable: false, + pinnable: false, + closableByDimmer: false, + modal: false, + autoReset: false, + closable: true, + }, + }; + }, + prepare: function() { + let self = this; + $container.html(''); + // Disable Ok button + this.__internal.buttons[2].element.disabled = true; + + // Status bar + this.statusBar = $( + '<div class=\'pg-prop-status-bar pg-el-xs-12 d-none\'>' + + ' <div class="error-in-footer"> ' + + ' <div class="d-flex px-2 py-1"> ' + + ' <div class="pr-2"> ' + + ' <i class="fa fa-exclamation-triangle text-danger" aria-hidden="true"></i> ' + + ' </div> ' + + ' <div class="alert-text" role="alert"></div> ' + + ' </div> ' + + ' </div> ' + + '</div>').appendTo($container); + + // To show progress on filter Saving/Updating on AJAX + this.showNewConnectionProgress = $( + `<div id="show_filter_progress" class="pg-sp-container sql-editor-busy-fetching d-none"> + <div class="pg-sp-content"> + <div class="row"><div class="col-12 pg-sp-icon sql-editor-busy-icon"></div></div> + <div class="row"><div class="col-12 pg-sp-text sql-editor-busy-text">` + gettext('Loading data...') + `</div></div> + </div> + </div>` + ).appendTo($container); + $( + self.showNewConnectionProgress[0] + ).removeClass('d-none'); + + self.newConnCollectionModel = newConnectionDialogModel(response, handler.url_params.sgid, handler.url_params.sid); + let fields = Backform.generateViewSchema(null, self.newConnCollectionModel, 'create', null, null, true); + + let view = this.view = new Backform.Dialog({ + el: '<div></div>', + model: self.newConnCollectionModel, + schema: fields, + }); + + $(this.elements.body.childNodes[0]).addClass( + 'alertify_tools_dialog_properties obj_properties' + ); + + $container.append(view.render().$el); + + // Enable/disable save button and show/hide statusbar based on session + view.listenTo(view.model, 'pgadmin-session:start', function() { + view.listenTo(view.model, 'pgadmin-session:invalid', function(msg) { + self.statusBar.removeClass('d-none'); + $(self.statusBar.find('.alert-text')).html(msg); + // Disable Okay button + self.__internal.buttons[2].element.disabled = true; + }); + + view.listenTo(view.model, 'pgadmin-session:valid', function() { + self.statusBar.addClass('d-none'); + $(self.statusBar.find('.alert-text')).html(''); + // Enable Okay button + self.__internal.buttons[2].element.disabled = false; + }); + }); + + view.listenTo(view.model, 'pgadmin-session:stop', function() { + view.stopListening(view.model, 'pgadmin-session:invalid'); + view.stopListening(view.model, 'pgadmin-session:valid'); + }); + + // Starts monitoring changes to model + view.model.startNewSession(); + + // Hide Progress ... + $( + self.showNewConnectionProgress[0] + ).addClass('d-none'); + }, + callback: function(e) { + let self = this; + if (e.button.element.name == 'dialog_help') { + e.cancel = true; + pgAdmin.Browser.showHelp(e.button.element.name, e.button.element.getAttribute('url'), + null, null); + return; + } else if (e.button['data-btn-name'] === 'ok') { + e.cancel = true; // Do not close dialog + let newConnCollectionModel = this.newConnCollectionModel.toJSON(); + + let selected_database_name = null; + response.database_list.forEach(function(data){ + if(newConnCollectionModel['database'] == data['value']) { + selected_database_name = data['label']; + return false; + } + }); + let title = ''; + if(newConnCollectionModel['role']) { + title = selected_database_name + '/' + newConnCollectionModel['role'] + '@' + response.server_name; + } else { + title = selected_database_name + '/' + newConnCollectionModel['user'] + '@' + response.server_name; + newConnCollectionModel['role'] = null; + } + + let is_create_connection = true; + + handler.gridView.connection_list.forEach(function(connection_data){ + if(parseInt(connection_data['server']) == newConnCollectionModel['server'] + && parseInt(connection_data['database']) == newConnCollectionModel['database'] + && connection_data['user'] == newConnCollectionModel['user'] && connection_data['role'] == newConnCollectionModel['role']) { + is_create_connection = false; + // break for loop by return false. + return false; + } + + if(title == connection_data['title']) { + is_create_connection = false; + return false; + } + }); + if(!is_create_connection) { + let errmsg = 'Connection with this configuration already present.'; + Alertify.info(errmsg); + }else { + let connection_details = { + 'server_group': handler.gridView.handler.url_params.sgid, + 'server': newConnCollectionModel['server'], + 'database': newConnCollectionModel['database'], + 'title': title, + 'user': newConnCollectionModel['user'], + 'role': newConnCollectionModel['role'], + 'password': response.password, + }; + handler.gridView.on_change_connection(connection_details, self); + } + } else { + self.close(); + } + }, + }; + }); + setTimeout(function(){ + Alertify.newConnectionDialog('Connect to server.').resizeTo(pgAdmin.Browser.stdW.md,pgAdmin.Browser.stdH.md); + }, 500); + }).fail(function(error) { + Alertify.alert().setting({ + 'title': gettext('Connection lost'), + 'label':gettext('Ok'), + 'message': gettext('Connection to the server has been lost.'), + 'onok': function(){ + alert(error); + //Close the window after connection is lost + window.close(); + }, + }).show(); + }); + + }, + +}; + +module.exports = NewConnectionDialog; diff --git a/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js b/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js new file mode 100644 index 0000000..1cba6e6 --- /dev/null +++ b/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js @@ -0,0 +1,339 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import gettext from 'sources/gettext'; +import _ from 'underscore'; +import $ from 'jquery'; +import pgAdmin from 'sources/pgadmin'; +import Backform from 'pgadmin.backform'; +import url_for from 'sources/url_for'; +import alertify from 'pgadmin.alertifyjs'; + +export default function newConnectionDialogModel(response, sgid, sid) { + + let server_name = ''; + let database_name = ''; + + let NewConnectionSelect2Control = Backform.Select2Control.extend({ + fetchData: function(){ + let self = this; + url = self.field.get('url'); + + let url = url_for(url, { + 'sid': self.model.attributes.server, + 'sgid': sgid, + }); + + $.ajax({ + async: false, + url: url, + headers: { + 'Cache-Control' : 'no-cache', + }, + }).done(function (res) { + var transform = self.field.get('transform'); + if(res.data.status){ + let data = res.data.result.data; + + if (transform && _.isFunction(transform)) { + self.field.set('options', transform.bind(self, data)); + } else { + self.field.set('options', data); + } + } else { + if (transform && _.isFunction(transform)) { + self.field.set('options', transform.bind(self, [])); + } else { + self.field.set('options', []); + } + //alertify.error(res.data.msg); + } + }).fail(function(e){ + let msg = ''; + if(e.status == 404) { + msg = 'Unable to find url.'; + } else { + msg = e.responseJSON.errormsg; + } + alertify.error(msg); + }); + }, + render: function() { + this.fetchData(); + return Backform.Select2Control.prototype.render.apply(this, arguments); + }, + onChange: function() { + Backform.Select2Control.prototype.onChange.apply(this, arguments); + }, + }); + + let newConnectionModel = pgAdmin.Browser.DataModel.extend({ + idAttribute: 'name', + defaults: { + server: parseInt(sid), + database: null, + user: null, + password: null, + server_name: server_name, + database_name: database_name, + }, + schema: [{ + id: 'server', + name: 'server', + label: gettext('Server'), + type: 'text', + editable: true, + disabled: false, + select2: { + allowClear: false, + }, + control: Backform.Select2Control.extend({ + connect: function(self) { + /*if (alertify.connectServer) { + delete alertify.connectServer; + }*/ + if(!alertify.connectServer){ + alertify.dialog('connectServer', function factory() { + return { + main: function( + title, message, sid, submit_password=true + ) { + this.set('title', title); + this.message = message; + this.server_id = sid; + this.submit_password = submit_password; + }, + setup:function() { + return { + buttons:[{ + text: gettext('Cancel'), className: 'btn btn-secondary fa fa-times pg-alertify-button', + key: 27, + },{ + text: gettext('OK'), key: 13, className: 'btn btn-primary fa fa-check pg-alertify-button', + }], + focus: {element: '#password', select: true}, + options: { + modal: 0, resizable: false, maximizable: false, pinnable: false, + }, + }; + }, + build:function() { + }, + prepare:function() { + this.setContent(this.message); + }, + callback: function(closeEvent) { + + if (closeEvent.button.text == gettext('OK')) { + if(this.submit_password) { + var _url = url_for('schema_diff.connect_server', {'sid': this.server_id}); + + $.ajax({ + type: 'POST', + timeout: 30000, + url: _url, + data: $('#frmPassword').serialize(), + }) + .done(function() { + self.model.attributes.database = null; + self.model.attributes.user = null; + self.model.attributes.role = null; + Backform.Select2Control.prototype.onChange.apply(self, arguments); + response.server_list.forEach(function(obj){ + if(obj.id==self.model.changed.server) { + response.server_name = obj.name; + } + }); + }) + .fail(function(xhr) { + alertify.connectServer('Connect to server', xhr.responseJSON.result, self.getValueFromDOM()); + }); + } else { + response.password = $('#password').val(); + } + } else { + self.model.attributes.database = null; + self.model.attributes.user = null; + self.model.attributes.role = null; + Backform.Select2Control.prototype.onChange.apply(self, arguments); + } + closeEvent.close = true; + }, + }; + }); + } + }, + render: function() { + let self = this; + self.connect(self); + return Backform.Select2Control.prototype.render.apply(self, arguments); + }, + onChange: function() { + this.model.attributes.database = null; + this.model.attributes.user = null; + let self = this; + self.connect(self); + + let url = url_for('sqleditor.connect_server', { + 'sid': self.getValueFromDOM(), + }); + $.ajax({ + async: false, + url: url, + headers: { + 'Cache-Control' : 'no-cache', + }, + }).done(function () { + Backform.Select2Control.prototype.onChange.apply(self, arguments); + response.server_list.forEach(function(obj){ + if(obj.id==self.model.changed.server) { + response.server_name = obj.name; + } + }); + }).fail(function(xhr){ + alertify.connectServer('Connect to server', xhr.responseJSON.result, self.getValueFromDOM()); + }); + + }, + }), + options: function() { + return _.map(response.server_list, (obj) => { + if (obj.id == parseInt(sid)) + response.server_name = obj.name; + + return { + value: obj.id, + label: obj.name, + }; + }); + }, + }, + { + id: 'database', + name: 'database', + label: gettext('Database'), + type: 'text', + editable: true, + disabled: function(m) { + let self_local = this; + if (!_.isUndefined(m.get('server')) && !_.isNull(m.get('server')) + && m.get('server') !== '') { + setTimeout(function() { + if(self_local.options.length) { + m.set('database', self_local.options[0].value); + } + }, 10); + return false; + } + + return true; + }, + deps: ['server'], + url: 'sqleditor.get_new_connection_database', + select2: { + allowClear: false, + width: '100%', + first_empty: true, + select_first: false, + }, + extraClasses:['new-connection-dialog-style'], + control: NewConnectionSelect2Control, + transform: function(data) { + response.database_list = data; + return data; + }, + }, + { + id: 'user', + name: 'user', + label: gettext('User'), + type: 'text', + editable: true, + deps: ['server'], + select2: { + allowClear: false, + width: '100%', + }, + control: NewConnectionSelect2Control, + url: 'sqleditor.get_new_connection_user', + disabled: function(m) { + let self_local = this; + if (!_.isUndefined(m.get('server')) && !_.isNull(m.get('server')) + && m.get('server') !== '') { + setTimeout(function() { + if(self_local.options.length) { + m.set('user', self_local.options[0].value); + } + }, 10); + return false; + } + return true; + }, + },{ + id: 'role', + name: 'role', + label: gettext('Role'), + type: 'text', + editable: true, + deps: ['server'], + select2: { + allowClear: false, + width: '100%', + first_empty: true, + }, + control: NewConnectionSelect2Control, + url: 'sqleditor.get_new_connection_role', + disabled: false, + }, + /*{ + id: 'password', + name: 'password', + label: gettext('Password'tools/sqleditor/__init__.py), + type: 'password', + editable: true, + disabled: true, + deps: ['user'], + control: Backform.InputControl.extend({ + render: function() { + let self = this; + self.model.attributes.password = null; + Backform.InputControl.prototype.render.apply(self, arguments); + return self; + }, + onChange: function() { + let self = this; + Backform.InputControl.prototype.onChange.apply(self, arguments); + }, + }), + },*/ + ], + validate: function() { + let msg = null; + this.errorModel.clear(); + if(_.isUndefined(this.get('database')) || _.isNull(this.get('database'))){ + msg = gettext('Please select database'); + this.errorModel.set('database', msg); + return msg; + } else if(_.isUndefined(this.get('database')) || _.isUndefined(this.get('user'))|| _.isNull(this.get('user'))) { + msg = gettext('Please select user'); + this.errorModel.set('user', msg); + return msg; + } + /*else if((this.attributes.password == '' || _.isUndefined(this.get('password')) || _.isNull(this.get('password')))) { + msg = gettext('Please enter password'); + this.errorModel.set('password', msg); + return msg; + }*/ + return null; + }, + }); + + let model = new newConnectionModel(); + return model; +} diff --git a/web/pgadmin/static/scss/_alert.scss b/web/pgadmin/static/scss/_alert.scss index dac552b..836f0af 100644 --- a/web/pgadmin/static/scss/_alert.scss +++ b/web/pgadmin/static/scss/_alert.scss @@ -92,6 +92,7 @@ right: 0; left: 0; bottom: 0; + z-index: 1; } .pg-prop-status-bar { diff --git a/web/pgadmin/tools/datagrid/__init__.py b/web/pgadmin/tools/datagrid/__init__.py index 1bd841f..f7b836a 100644 --- a/web/pgadmin/tools/datagrid/__init__.py +++ b/web/pgadmin/tools/datagrid/__init__.py @@ -18,22 +18,23 @@ from flask import Response, url_for, session, request, make_response from werkzeug.useragents import UserAgent from flask import current_app as app, render_template from flask_babelex import gettext -from flask_security import login_required +from flask_security import login_required, current_user from pgadmin.tools.sqleditor.command import ObjectRegistry, SQLFilter +from pgadmin.tools.sqleditor import check_transaction_status from pgadmin.utils import PgAdminModule from pgadmin.utils.ajax import make_json_response, bad_request, \ - internal_server_error + internal_server_error, unauthorized from config import PG_DEFAULT_DRIVER -from pgadmin.model import Server +from pgadmin.model import Server, User from pgadmin.utils.driver import get_driver from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost from pgadmin.utils.preferences import Preferences from pgadmin.settings import get_setting from pgadmin.browser.utils import underscore_unescape from pgadmin.utils.exception import ObjectGone -from pgadmin.utils.constants import MIMETYPE_APP_JS from pgadmin.tools.sqleditor.utils.macros import get_user_macros +from pgadmin.utils.constants import MIMETYPE_APP_JS, UNAUTH_REQ MODULE_NAME = 'datagrid' @@ -74,7 +75,8 @@ class DataGridModule(PgAdminModule): 'datagrid.filter_validate', 'datagrid.filter', 'datagrid.panel', - 'datagrid.close' + 'datagrid.close', + 'datagrid.update_query_tool_connection' ] def on_logout(self, user): @@ -324,10 +326,48 @@ def initialize_query_tool(trans_id, sgid, sid, did=None): req_args['recreate'] == '1'): connect = False + is_error, errmsg, conn_id, version = _init_query_tool(trans_id, connect, + sgid, sid, did) + if is_error: + return errmsg + + return make_json_response( + data={ + 'connId': str(conn_id), + 'serverVersion': version, + } + ) + + +def _connect(conn, **kwargs): + """ + Connect the database. + :param conn: Connection instance. + :param kwargs: user, role and password data from user. + :return: + """ + user = None + role = None + password = None + is_ask_password = False + if 'user' in kwargs and 'role' in kwargs: + user = kwargs['user'] + role = kwargs['role'] if kwargs['role'] else None + password = kwargs['password'] if kwargs['password'] else None + is_ask_password = True + if user: + status, msg = conn.connect(user=user, role=role, + password=password) + else: + status, msg = conn.connect() + + return status, msg, is_ask_password, user, role, password + + +def _init_query_tool(trans_id, connect, sgid, sid, did, **kwargs): # Create asynchronous connection using random connection id. conn_id = str(random.randint(1, 9999999)) - # Use Maintenance database OID manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) if did is None: @@ -338,24 +378,41 @@ def initialize_query_tool(trans_id, sgid, sid, did=None): ) except Exception as e: app.logger.error(e) - return internal_server_error(errormsg=str(e)) + return True, internal_server_error(errormsg=str(e)), '', '' try: conn = manager.connection(did=did, conn_id=conn_id, auto_reconnect=False, use_binary_placeholder=True, array_to_string=True) + if connect: - status, msg = conn.connect() + status, msg, is_ask_password, user, role, password = _connect( + conn, **kwargs) if not status: app.logger.error(msg) - return internal_server_error(errormsg=str(msg)) + if is_ask_password: + server = Server.query.filter_by(id=sid).first() + return True, make_json_response( + success=0, + status=428, + result=render_template( + 'servers/password.html', + server_label=server.name, + username=user, + errmsg=msg, + _=gettext, + ) + ), '', '' + else: + return True, internal_server_error( + errormsg=str(msg)), '', '' except (ConnectionLost, SSHTunnelConnectionLost) as e: app.logger.error(e) raise except Exception as e: app.logger.error(e) - return internal_server_error(errormsg=str(e)) + return True, internal_server_error(errormsg=str(e)), '', '' if 'gridData' not in session: sql_grid_data = dict() @@ -377,10 +434,77 @@ def initialize_query_tool(trans_id, sgid, sid, did=None): # Store the grid dictionary into the session variable session['gridData'] = sql_grid_data + return False, '', conn_id, manager.version + + [email protected]( + '/initialize/query_tool/update_connection/<int:trans_id>/' + '<int:sgid>/<int:sid>/<int:did>', + methods=["POST"], endpoint='update_query_tool_connection' +) +def update_query_tool_connection(trans_id, sgid, sid, did): + # Remove transaction Id. + with query_tool_close_session_lock: + data = json.loads(request.data, encoding='utf-8') + + if 'gridData' not in session: + return make_json_response(data={'status': True}) + + grid_data = session['gridData'] + + # Return from the function if transaction id not found + if str(trans_id) not in grid_data: + return make_json_response(data={'status': True}) + + connect = True + + req_args = request.args + if ('recreate' in req_args and + req_args['recreate'] == '1'): + connect = False + + new_trans_id = str(random.randint(1, 9999999)) + kwargs = { + 'user': data['user'], + 'role': data['role'], + 'password': data['password'] if 'password' in data else None + } + + is_error, errmsg, conn_id, version = _init_query_tool( + new_trans_id, connect, sgid, sid, did, **kwargs) + + if is_error: + return errmsg + else: + try: + # Check the transaction and connection status + status, error_msg, conn, trans_obj, session_obj = \ + check_transaction_status(trans_id) + + status, error_msg, new_conn, new_trans_obj, new_session_obj = \ + check_transaction_status(new_trans_id) + + new_session_obj['primary_keys'] = session_obj[ + 'primary_keys'] if 'primary_keys' in session_obj else None + new_session_obj['columns_info'] = session_obj[ + 'columns_info'] if 'columns_info' in session_obj else None + new_session_obj['client_primary_key'] = session_obj[ + 'client_primary_key'] if 'client_primary_key'\ + in session_obj else None + + close_query_tool_session(trans_id) + # Remove the information of unique transaction id from the + # session variable. + grid_data.pop(str(trans_id), None) + session['gridData'] = grid_data + except Exception as e: + app.logger.error(e) + return make_json_response( data={ 'connId': str(conn_id), - 'serverVersion': manager.version, + 'serverVersion': version, + 'tran_id': new_trans_id } ) diff --git a/web/pgadmin/tools/datagrid/templates/datagrid/index.html b/web/pgadmin/tools/datagrid/templates/datagrid/index.html index a0eebc8..4970027 100644 --- a/web/pgadmin/tools/datagrid/templates/datagrid/index.html +++ b/web/pgadmin/tools/datagrid/templates/datagrid/index.html @@ -417,8 +417,17 @@ title="" role="img"> </i> </div> - <div class="editor-title" - style="background-color: {% if fgcolor %}{{ bgcolor or '#FFFFFF' }}{% endif %}; color: {% if fgcolor %}{{ fgcolor }}{% endif %};"> </div> + <div class="connection-info btn-group mr-1" role="group" aria-label=""> + <div class="editor-title" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" + style="background-color: {% if fgcolor %}{{ bgcolor or '#FFFFFF' }}{% endif %}; color: {% if fgcolor %}{{ fgcolor }}{% endif %};"> + </div> + <span class="conn-info-dd dropdown-toggle dropdown-toggle-split" + data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span> + <ul class="dropdown-menu" id="connections-list"> + </ul> + </div> + + </div> <div id="editor-panel" tabindex="0"> <div id="fetching_data" class="pg-sp-container sql-editor-busy-fetching"> @@ -481,6 +490,7 @@ require(['sources/generated/browser_nodes', 'sources/generated/codemirror', 'sou var script_type_url = ''; {% endif %} // Start the query tool. + sqlEditorController.start( {{ uniqueId }}, {{ url_params|safe}}, diff --git a/web/pgadmin/tools/datagrid/tests/__init__.py b/web/pgadmin/tools/datagrid/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web/pgadmin/tools/datagrid/tests/datagrid_test_data.json b/web/pgadmin/tools/datagrid/tests/datagrid_test_data.json new file mode 100644 index 0000000..0075f35 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/datagrid_test_data.json @@ -0,0 +1,134 @@ +{ + "data_grid_init_query_tool": [ + { + "name": "Datagrid init query tool", + "url": "/datagrid/initialize/query_tool/", + "is_positive_test": true, + "mocking_required": false, + "test_data": {}, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + } + ], + "data_grid_query_tool_close": [ + { + "name": "Datagrid query tool close", + "url": "/datagrid/close/", + "is_positive_test": true, + "mocking_required": false, + "test_data": {}, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + } + ], + "data_grid_validate_filter": [ + { + "name": "Datagrid validate filter", + "url": "/datagrid/filter/validate/", + "is_positive_test": true, + "mocking_required": false, + "test_data": "id = 1", + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Datagrid validate filter", + "url": "/datagrid/filter/validate/", + "is_positive_test": false, + "mocking_required": true, + "test_data": "id = 1", + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_scalar", + "return_value": "(False, 'Mocked Internal Server Error while validate filter')" + }, + "expected_data": { + "status_code": 200 + } + } + ], + "data_grid_update_connection": [ + { + "name": "Datagrid update connection positive", + "url": "/datagrid/initialize/query_tool/update_connection/", + "is_positive_test": true, + "mocking_required": false, + "is_create_role": false, + "test_data": {}, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Datagrid update connection with new user", + "url": "/datagrid/initialize/query_tool/update_connection/", + "is_positive_test": true, + "mocking_required": false, + "is_create_role": true, + "test_data": {}, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + } + ], + "data_grid_panel": [ + { + "name": "Datagrid Panel", + "url": "/datagrid/panel/", + "is_positive_test": true, + "mocking_required": false, + "test_data": {}, + "mock_data": { + }, + "expected_data": { + "status_code": 200 + } + } + ], + "data_grid_initialize": [ + { + "name": "Datagrid Initialize", + "url": "/datagrid/initialize/datagrid/", + "is_positive_test": true, + "mocking_required": false, + "test_data": "id=1", + "mock_data": { + + }, + "expected_data": { + "status_code": 200 + } + },{ + "name": "Datagrid Initialize", + "url": "/datagrid/initialize/datagrid/", + "is_positive_test": true, + "mocking_required": false, + "test_data": null, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Datagrid Initialize", + "url": "/datagrid/initialize/datagrid/", + "is_positive_test": false, + "mocking_required": true, + "test_data": "id=1", + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict", + "return_value": "(False, 'Mocked Internal Server Error while initialize datagrid.')" + }, + "expected_data": { + "status_code": 500 + } + } + ] +} diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_init_query_tool.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_init_query_tool.py new file mode 100644 index 0000000..6ecf5de --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_init_query_tool.py @@ -0,0 +1,72 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from . import utils as data_grid_utils + + +class DatagridInitQueryToolTestCase(BaseTestGenerator): + """ + This will init query-tool connection. + """ + + scenarios = utils.generate_scenarios( + 'data_grid_init_query_tool', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + + self.trans_id = str(random.randint(1, 9999999)) + + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + + def init_query_tool(self): + response = self.tester.post( + self.url + str(self.trans_id) + '/' + str(self.sgid) + '/' + str( + self.sid) + '/' + str(self.did), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will init query tool connection.""" + + if self.is_positive_test: + response = self.init_query_tool() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_panel.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_panel.py new file mode 100644 index 0000000..ae8ec10 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_panel.py @@ -0,0 +1,93 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from . import utils as data_grid_utils + + +class DatagridPanelTestCase(BaseTestGenerator): + """ + This will data grid panel. + """ + + scenarios = utils.generate_scenarios( + 'data_grid_panel', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + + self.trans_id = str(random.randint(1, 9999999)) + qt_init = data_grid_utils._init_query_tool(self, self.trans_id, + self.sgid, self.sid, + self.did) + + if not qt_init['success']: + raise Exception("Could not initialize querty tool.") + + def panel(self): + query_param = \ + '?is_query_tool={0}&sgid={1}&sid={2}&server_type={3}' \ + '&did={4}&title={5}'.format(True, self.sgid, self.sid, + self.server_information['type'], + self.did, 'Query panel') + + response = self.tester.post( + self.url + str(self.trans_id) + query_param, + data=json.dumps(self.test_data), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will update query tool connection.""" + + if self.is_positive_test: + response = self.panel() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + with patch(self.mock_data["function_name"], + return_value=eval(self.mock_data["return_value"])): + response = self.panel() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_query_tool_close.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_query_tool_close.py new file mode 100644 index 0000000..822c2e1 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_query_tool_close.py @@ -0,0 +1,77 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from . import utils as data_grid_utils + + +class DatagridQueryToolCloseTestCase(BaseTestGenerator): + """ + This will close query-tool connection. + """ + + scenarios = utils.generate_scenarios( + 'data_grid_query_tool_close', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + + self.trans_id = str(random.randint(1, 9999999)) + qt_init = data_grid_utils._init_query_tool(self, self.trans_id, + self.sgid, self.sid, + self.did) + + if not qt_init['success']: + raise Exception("Could not initialize querty tool.") + + def close_connection(self): + response = self.tester.delete( + self.url + str(self.trans_id), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will update query tool connection.""" + + if self.is_positive_test: + response = self.close_connection() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_update_connection.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_update_connection.py new file mode 100644 index 0000000..5d2b14a --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_update_connection.py @@ -0,0 +1,120 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from pgadmin.browser.server_groups.servers.roles.tests import \ + utils as roles_utils +from . import utils as data_grid_utils + + +class DatagridUpdateConnectionTestCase(BaseTestGenerator): + """ + This will update query-tool connection. + """ + + scenarios = utils.generate_scenarios( + 'data_grid_update_connection', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + + self.trans_id = str(random.randint(1, 9999999)) + self.roles = None + + if self.is_create_role: + data = roles_utils.get_role_data(self.server['db_password']) + self.role_name = data['rolname'] + self.role_password = data['rolpassword'] + roles_utils.create_role_with_password( + self.server, self.role_name, self.role_password) + + if not self.is_positive_test or self.is_create_role: + qt_init = data_grid_utils._init_query_tool(self, self.trans_id, + self.sgid, self.sid, + self.did) + + if not qt_init['success']: + raise Exception("Could not initialize querty tool.") + + self.test_data = { + "database": self.did, + "server": self.sid, + } + + if self.server_information['type'] == 'ppas': + self.test_data['password'] = 'enterprisedb' + self.test_data['user'] = 'enterprisedb' + else: + self.test_data['password'] = 'postgres' + self.test_data['user'] = 'postgres' + + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + + def update_connection(self, user_data=None): + if user_data: + response = self.tester.post( + self.url + str(self.trans_id) + '/' + str(self.sgid) + + '/' + str(self.sid) + '/' + str(self.did), + data=json.dumps(user_data), + content_type='html/json' + ) + else: + response = self.tester.post( + self.url + str(self.trans_id) + '/' + str(self.sgid) + '/' + + str(self.sid) + '/' + str(self.did), + data=json.dumps(self.test_data), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will update query tool connection.""" + + if self.is_positive_test: + user_data = dict() + if self.is_create_role: + user_data['user'] = self.role_name + user_data['password'] = self.role_password + user_data['role'] = None + response = self.update_connection(user_data=user_data) + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + response = self.update_connection() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_validate_filter.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_validate_filter.py new file mode 100644 index 0000000..0aba5d8 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_validate_filter.py @@ -0,0 +1,91 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from . import utils as data_grid_utils + + +class DatagridValidateFilterTestCase(BaseTestGenerator): + """ + This will validate filter connection. + """ + + scenarios = utils.generate_scenarios( + 'data_grid_validate_filter', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + self.schema_id = parent_node_dict['schema'][-1]["schema_id"] + self.schema_name = parent_node_dict['schema'][-1]["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception("Could not find the schema to add a table.") + self.table_name = "table_for_wizard%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + + def validate_filter(self): + response = self.tester.post( + self.url + str(self.sid) + '/' + str(self.did) + '/' + + str(self.table_id), + data=json.dumps(self.test_data), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will update query tool connection.""" + + if self.is_positive_test: + response = self.validate_filter() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + with patch(self.mock_data["function_name"], + return_value=eval(self.mock_data["return_value"])): + response = self.validate_filter() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/test_initialize_data_grid.py b/web/pgadmin/tools/datagrid/tests/test_initialize_data_grid.py new file mode 100644 index 0000000..130a1d6 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_initialize_data_grid.py @@ -0,0 +1,108 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from . import utils as data_grid_utils + + +class DatagridInitializeTestCase(BaseTestGenerator): + """ + This will Initialize datagrid + """ + + scenarios = utils.generate_scenarios( + 'data_grid_initialize', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a table.") + + self.schema_id = parent_node_dict['schema'][-1]["schema_id"] + self.schema_name = parent_node_dict['schema'][-1]["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception("Could not find the schema to add a table.") + self.table_name = "table_for_wizard%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + self.trans_id = str(random.randint(1, 9999999)) + qt_init = data_grid_utils._init_query_tool(self, self.trans_id, + self.sgid, self.sid, + self.did) + + if not qt_init['success']: + raise Exception("Could not initialize query tool.") + + def initialize_datagrid(self): + if self.test_data: + response = self.tester.post( + self.url + str(self.trans_id) + '/4/table/' + + str(self.sgid) + '/' + str(self.sid) + '/' + + str(self.did) + '/' + str(self.table_id), + data=json.dumps(self.test_data), + content_type='html/json' + ) + else: + response = self.tester.post( + self.url + str(self.trans_id) + '/4/table/' + + str(self.sgid) + '/' + str(self.sid) + '/' + + str(self.did) + '/' + str(self.table_id), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will update query tool connection.""" + + if self.is_positive_test: + response = self.initialize_datagrid() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + with patch(self.mock_data["function_name"], + return_value=eval(self.mock_data["return_value"])): + response = self.initialize_datagrid() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/utils.py b/web/pgadmin/tools/datagrid/tests/utils.py new file mode 100644 index 0000000..c3d4bb5 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/utils.py @@ -0,0 +1,33 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## +import os +import json + +file_name = os.path.basename(__file__) +CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) +with open(CURRENT_PATH + "/datagrid_test_data.json") as data_file: + test_cases = json.load(data_file) + + +def _init_query_tool(self, trans_id, server_group, server_id, db_id): + QUERY_TOOL_INIT_URL = '/datagrid/initialize/query_tool' + + qt_init = self.tester.post( + '{0}/{1}/{2}/{3}/{4}'.format( + QUERY_TOOL_INIT_URL, + trans_id, + server_group, + server_id, + db_id + ), + follow_redirects=True + ) + assert qt_init.status_code == 200 + qt_init = json.loads(qt_init.data.decode('utf-8')) + return qt_init diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py index 9d0903b..ca98ec4 100644 --- a/web/pgadmin/tools/sqleditor/__init__.py +++ b/web/pgadmin/tools/sqleditor/__init__.py @@ -10,17 +10,15 @@ """A blueprint module implementing the sqleditor frame.""" import os import pickle -import sys import re +from urllib.parse import unquote import simplejson as json -from flask import Response, url_for, render_template, session, request, \ - current_app +from config import PG_DEFAULT_DRIVER, ON_DEMAND_RECORD_COUNT +from flask import Response, url_for, render_template, session, current_app +from flask import request, jsonify from flask_babelex import gettext from flask_security import login_required, current_user -from urllib.parse import unquote - -from config import PG_DEFAULT_DRIVER, ON_DEMAND_RECORD_COUNT from pgadmin.misc.file_manager import Filemanager from pgadmin.tools.sqleditor.command import QueryToolCommand from pgadmin.tools.sqleditor.utils.constant_definition import ASYNC_OK, \ @@ -32,11 +30,11 @@ from pgadmin.tools.sqleditor.utils.update_session_grid_transaction import \ from pgadmin.utils import PgAdminModule from pgadmin.utils import get_storage_directory from pgadmin.utils.ajax import make_json_response, bad_request, \ - success_return, internal_server_error, make_response as ajax_response + success_return, internal_server_error from pgadmin.utils.driver import get_driver -from pgadmin.utils.menu import MenuItem -from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost,\ +from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost, \ CryptKeyMissing +from pgadmin.utils.menu import MenuItem from pgadmin.utils.sqlautocomplete.autocomplete import SQLAutoComplete from pgadmin.tools.sqleditor.utils.query_tool_preferences import \ register_query_tool_preferences @@ -44,10 +42,12 @@ from pgadmin.tools.sqleditor.utils.query_tool_fs_utils import \ read_file_generator from pgadmin.tools.sqleditor.utils.filter_dialog import FilterDialog from pgadmin.tools.sqleditor.utils.query_history import QueryHistory -from pgadmin.utils.constants import MIMETYPE_APP_JS, SERVER_CONNECTION_CLOSED,\ - ERROR_MSG_TRANS_ID_NOT_FOUND from pgadmin.tools.sqleditor.utils.macros import get_macros,\ get_user_macros, set_macros +from pgadmin.utils.constants import MIMETYPE_APP_JS, \ + SERVER_CONNECTION_CLOSED, ERROR_MSG_TRANS_ID_NOT_FOUND +from pgadmin.model import Server +from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry MODULE_NAME = 'sqleditor' @@ -113,7 +113,13 @@ class SqlEditorModule(PgAdminModule): 'sqleditor.clear_query_history', 'sqleditor.get_macro', 'sqleditor.get_macros', - 'sqleditor.set_macros' + 'sqleditor.set_macros', + 'sqleditor.get_new_connection_data', + 'sqleditor.get_new_connection_database', + 'sqleditor.get_new_connection_user', + 'sqleditor.get_new_connection_role', + 'sqleditor.connect_server', + 'sqleditor.connect_server_with_user', ] def register_preferences(self): @@ -229,7 +235,7 @@ def start_view_data(trans_id): ) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: # set fetched row count to 0 as we are executing query again. trans_obj.update_fetched_row_cnt(0) @@ -375,7 +381,7 @@ def poll(trans_id): if isinstance(trans_obj, QueryToolCommand): trans_status = conn.transaction_status() if trans_status == TX_STATUS_INERROR and \ - trans_obj.auto_rollback: + trans_obj.auto_rollback: conn.execute_void("ROLLBACK;") st, result = conn.async_fetchmany_2darray(ON_DEMAND_RECORD_COUNT) @@ -685,13 +691,12 @@ def save(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: # If there is no primary key found then return from the function. if ('primary_keys' not in session_obj or - len(session_obj['primary_keys']) <= 0 or - len(changed_data) <= 0) and \ - 'has_oids' not in session_obj: + len(session_obj['primary_keys']) <= 0 or + len(changed_data) <= 0) and 'has_oids' not in session_obj: return make_json_response( data={ 'status': False, @@ -758,7 +763,7 @@ def append_filter_inclusive(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = None filter_sql = '' @@ -812,7 +817,7 @@ def append_filter_exclusive(trans_id): info='DATAGRID_TRANSACTION_REQUIRED', status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = None filter_sql = '' @@ -865,7 +870,7 @@ def remove_filter(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = None @@ -909,7 +914,7 @@ def set_limit(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = None @@ -1051,7 +1056,7 @@ def get_object_name(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = trans_obj.object_name else: status = False @@ -1087,7 +1092,7 @@ def set_auto_commit(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = None @@ -1132,7 +1137,7 @@ def set_auto_rollback(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = None @@ -1184,7 +1189,7 @@ def auto_complete(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: # Create object of SQLAutoComplete class and pass connection object auto_complete_obj = SQLAutoComplete( @@ -1470,6 +1475,284 @@ def get_filter_data(trans_id): return FilterDialog.get(status, error_msg, conn, trans_obj, session_ob) [email protected]( + '/new_connection_dialog/<int:sgid>/<int:sid>', + methods=["GET"], endpoint='get_new_connection_data' +) +@login_required +def get_new_connection_data(sgid, sid=None): + """ + This method is used to get required data for get new connection. + :extract_sql_from_network_parameters, + """ + try: + # if sid and not did: + servers = Server.query.all() + server_list = [ + {'name': server.serialize['name'], "id": server.serialize['id']} + for server in servers] + + msg = "Success" + return make_json_response( + data={ + 'status': True, + 'msg': msg, + 'result': { + 'server_list': server_list + } + } + ) + + except Exception as e: + return make_json_response( + data={ + 'status': False, + 'msg': 'Unable to fetch data.', + 'result': { + 'server_list': [] + } + } + ) + + [email protected]( + '/new_connection_database/<int:sgid>/<int:sid>', + methods=["GET"], endpoint='get_new_connection_database' +) +@login_required +def get_new_connection_database(sgid, sid=None): + """ + This method is used to get required data for get new connection. + :extract_sql_from_network_parameters, + """ + try: + database_list = [] + from pgadmin.utils.driver import get_driver + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) + conn = manager.connection() + if conn.connected(): + is_connected = True + else: + # connection = conn.connect() + is_connected = False + if is_connected: + if sid: + template_path = 'databases/sql/#{0}#'.format(manager.version) + last_system_oid = 0 + server_node_res = manager + + db_disp_res = None + params = None + if server_node_res and server_node_res.db_res: + db_disp_res = ", ".join( + ['%s'] * len(server_node_res.db_res.split(',')) + ) + params = tuple(server_node_res.db_res.split(',')) + sql = render_template( + "/".join([template_path, 'nodes.sql']), + last_system_oid=last_system_oid, + db_restrictions=db_disp_res + ) + status, databases = conn.execute_dict(sql, params) + database_list = [ + {'label': database['name'], 'value': database['did']} for + database in databases['rows']] + else: + status = False + + msg = "Success" + return make_json_response( + data={ + 'status': status, + 'msg': msg, + 'result': { + 'data': database_list, + } + } + ) + else: + return make_json_response( + data={ + 'status': False, + 'msg': 'Server not connected, ' + 'Please connect with server and try again.', + 'result': { + 'database_list': [], + } + } + ) + except Exception as e: + return make_json_response( + data={ + 'status': False, + 'msg': 'Unable to fetch data.', + 'result': { + 'database_list': [], + } + } + ) + + [email protected]( + '/new_connection_user/<int:sgid>/<int:sid>', + methods=["GET"], endpoint='get_new_connection_user' +) +@login_required +def get_new_connection_user(sgid, sid=None): + """ + This method is used to get required data for get new connection. + :extract_sql_from_network_parameters, + """ + try: + from pgadmin.utils.driver import get_driver + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) + conn = manager.connection() + user_list = [] + if conn.connected(): + is_connected = True + else: + is_connected = False + if is_connected: + if sid: + sql_path = 'roles/sql/#{0}#'.format(manager.version) + status, users = conn.execute_2darray( + render_template(sql_path + 'nodes.sql') + ) + user_list = [ + {'value': user['rolname'], 'label': user['rolname']} for + user in users['rows'] if user['rolcanlogin']] + else: + status = False + + msg = "Success" + return make_json_response( + data={ + 'status': status, + 'msg': msg, + 'result': { + 'data': user_list, + } + } + ) + else: + return make_json_response( + data={ + 'status': False, + 'msg': 'Server not connected, ' + 'Please connect with server and try again.', + 'result': { + 'user_list': [], + } + } + ) + except Exception as e: + return make_json_response( + data={ + 'status': False, + 'msg': 'Unable to fetch data.', + 'result': { + 'user_list': [], + } + } + ) + + [email protected]( + '/new_connection_role/<int:sgid>/<int:sid>', + methods=["GET"], endpoint='get_new_connection_role' +) +@login_required +def get_new_connection_role(sgid, sid=None): + """ + This method is used to get required data for get new connection. + :extract_sql_from_network_parameters, + """ + try: + from pgadmin.utils.driver import get_driver + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) + conn = manager.connection() + role_list = [] + if conn.connected(): + is_connected = True + else: + is_connected = False + if is_connected: + if sid: + sql_path = 'roles/sql/#{0}#'.format(manager.version) + status, roles = conn.execute_2darray( + render_template(sql_path + 'nodes.sql') + ) + role_list = [ + {'value': role['rolname'], 'label': role['rolname']} for + role in roles['rows']] + else: + status = False + + msg = "Success" + return make_json_response( + data={ + 'status': status, + 'msg': msg, + 'result': { + 'data': role_list, + } + } + ) + else: + return make_json_response( + data={ + 'status': False, + 'msg': 'Server not connected, ' + 'Please connect with server and try again.', + 'result': { + 'user_list': [], + } + } + ) + except Exception as e: + return make_json_response( + data={ + 'status': False, + 'msg': 'Unable to fetch data.', + 'result': { + 'user_list': [], + } + } + ) + + [email protected]( + '/connect_server/<int:sid>/<usr>', + methods=["GET"], + endpoint="connect_server_with_user" +) [email protected]( + '/connect_server/<int:sid>', + methods=["GET"], + endpoint="connect_server" +) +@login_required +def connect_server(sid, usr=None): + # Check if server is already connected then no need to reconnect again. + server = Server.query.filter_by(id=sid).first() + driver = get_driver(PG_DEFAULT_DRIVER) + manager = driver.connection_manager(sid) + conn = manager.connection() + user = None + if usr and manager.user != usr: + user = usr + else: + if conn.connected(): + return make_json_response( + success=1, + info=gettext("Server connected."), + data={} + ) + + view = SchemaDiffRegistry.get_node_view('server') + return view.connect(server.servergroup_id, sid, user=user) + + @blueprint.route( '/filter_dialog/<int:trans_id>', methods=["PUT"], endpoint='set_filter_data' diff --git a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css index c281d53..20590d3 100644 --- a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css +++ b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css @@ -315,10 +315,6 @@ input.editor-checkbox:focus { padding: 10px 0px; } -.editor-title { - width:100%; -} - .connection-status-hide { display: none !important; } @@ -396,7 +392,6 @@ input.editor-checkbox:focus { overflow-y: hidden; } - /* Macros */ .macro-tab { @@ -424,3 +419,7 @@ input.editor-checkbox:focus { .macro_dialog .pg-prop-status-bar { z-index: 1; } + +.new-connection-dialog-style { + width: 100% !important; +} diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js index ed3bd59..6abcf1d 100644 --- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js +++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js @@ -14,6 +14,7 @@ define('tools.querytool', [ 'jqueryui.position', 'underscore', 'pgadmin.alertifyjs', 'sources/pgadmin', 'backbone', 'bundled_codemirror', 'sources/utils', 'pgadmin.misc.explain', + 'pgadmin.user_management.current_user', 'sources/selection/grid_selector', 'sources/selection/active_cell_capture', 'sources/selection/clipboard', @@ -26,6 +27,7 @@ define('tools.querytool', [ 'sources/sqleditor/execute_query', 'sources/sqleditor/query_tool_http_error_handler', 'sources/sqleditor/filter_dialog', + 'sources/sqleditor/new_connection_dialog', 'sources/sqleditor/geometry_viewer', 'sources/sqleditor/history/history_collection.js', 'sources/sqleditor/history/query_history', @@ -53,8 +55,8 @@ define('tools.querytool', [ 'pgadmin.tools.user_management', ], function( gettext, url_for, $, jqueryui, jqueryui_position, _, alertify, pgAdmin, Backbone, codemirror, pgadminUtils, - pgExplain, GridSelector, ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent, - XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler, FilterHandler, + pgExplain, current_user, GridSelector, ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent, + XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler, FilterHandler, newConnectionHandler, GeometryViewer, historyColl, queryHist, querySources, keyboardShortcuts, queryToolActions, queryToolNotifications, Datagrid, modifyAnimation, calculateQueryRunTime, callRenderAfterPoll, queryToolPref, queryTxnStatus, csrfToken, panelTitleFunc, @@ -98,6 +100,9 @@ define('tools.querytool', [ this.layout = opts.layout; this.set_server_version(opts.server_ver); this.trigger('pgadmin-sqleditor:view:initialised'); + this.connection_list = [ + {'server_group': null,'server': null, 'database': null, 'user': null, 'role': null, 'title': '<New Connection>'}, + ]; }, // Bind all the events @@ -163,6 +168,35 @@ define('tools.querytool', [ 'click .btn-macro': 'on_execute_macro', }, + render_connection: function(data_list) { + if(this.handler.is_query_tool) { + var dropdownElement = document.getElementById('connections-list'); + dropdownElement.innerHTML = ''; + data_list.forEach((option, index) => { + $('#connections-list').append('<li class="connection-list-item" data-index='+ index +'><a class="dropdown-item" href="#" tabindex="0">'+ option.title +'</a></li>'); + + }); + var self = this; + $('.connection-list-item').click(function() { + self.get_connection_data(this); + }); + } else { + $('.conn-info-dd').hide(); + $('.editor-title').css({pointerEvents: 'none'}); + } + }, + + get_connection_data: function(event){ + var index = $(event).attr('data-index'); + var connection_details = this.connection_list[index]; + if(connection_details.server_group) { + this.on_change_connection(connection_details); + } else { + this.on_new_connection(); + } + + }, + reflectPreferences: function() { let self = this, browser = pgWindow.default.pgAdmin.Browser, @@ -211,8 +245,15 @@ define('tools.querytool', [ }); }, - set_editor_title: function(title) { - this.$el.find('.editor-title').text(title); + set_editor_title: function(title, is_connected) { + if(is_connected) { + this.$el.find('.editor-title').text(title); + this.render_connection(this.connection_list); + } else { + this.$el.find('.editor-title').text(title); + this.render_connection(this.connection_list); + } + }, // This function is used to render the template. @@ -696,6 +737,8 @@ define('tools.querytool', [ pgBrowser.register_to_activity_listener(document, ()=>{ alertify.alert(gettext('Timeout'), gettext('Your session has timed out due to inactivity. Please close the window and login again.')); }); + + self.render_connection(self.connection_list); }, /* Regarding SlickGrid usage in render_grid function. @@ -1607,6 +1650,17 @@ define('tools.querytool', [ ); }, + on_new_connection: function() { + var self = this; + + // Trigger the show_filter signal to the SqlEditorController class + self.handler.trigger( + 'pgadmin-sqleditor:button:show_new_connection', + self, + self.handler + ); + }, + // Callback function for include filter button click. on_include_filter: function(ev) { var self = this; @@ -2070,6 +2124,83 @@ define('tools.querytool', [ queryToolActions.executeMacro(this.handler, macroId); }, + on_change_connection: function(connection_details, ref) { + let title = this.$el.find('.editor-title').html(); + if(connection_details['title'] != title) { + var self = this; + $.ajax({ + async: false, + url: url_for('datagrid.update_query_tool_connection', { + 'trans_id': self.transId, + 'sgid': connection_details['server_group'], + 'sid': connection_details['server'], + 'did': connection_details['database'], + }), + method: 'POST', + contentType: 'application/json', + data: JSON.stringify(connection_details), + }) + .done(function(res) { + if(res.success) { + self.transId = res.data.tran_id; + self.handler.transId = res.data.tran_id; + self.handler.url_params = { + 'did': connection_details['database'], + 'is_query_tool': self.handler.url_params.is_query_tool, + 'server_type': self.handler.url_params.server_type, + 'sgid': connection_details['server_group'], + 'sid': connection_details['server'], + 'title': connection_details['title'], + }; + self.set_editor_title(self.handler.url_params.title); + self.handler.setTitle(self.handler.url_params.title); + alertify.success('connected successfully'); + if(ref){ + let connection_data = { + 'server_group': self.handler.url_params.sgid, + 'server': connection_details['server'], + 'database': connection_details['database'], + 'user': connection_details['user'], + 'title': connection_details['title'], + 'role': connection_details['role'], + 'password': connection_details['password'], + 'is_allow_new_connection': true, + }; + self.connection_list.unshift(connection_data); + self.render_connection(self.connection_list); + ref.close(); + } + } + return true; + }) + .fail(function(xhr) { + if(xhr.status == 428) { + alertify.connectServer('Connect to server', xhr.responseJSON.result, connection_details['server'], false); + } else { + alertify.error(xhr.responseJSON['errormsg']); + } + /*let url = url_for('sqleditor.connect_server_with_user', { + 'sid': newConnCollectionModel['server'], + 'usr': newConnCollectionModel['user'] + }); + $.ajax({ + async: false, + url: url, + headers: { + 'Cache-Control' : 'no-cache', + }, + }).done(function () { + Backform.Select2Control.prototype.onChange.apply(self, arguments); + response.server_list.forEach(function(obj){ + if(obj.id==self.model.changed.server) { + response.server_name = obj.name; + } + }); + }).fail(function(xhr){});*/ + + }); + } + }, }); @@ -2392,7 +2523,18 @@ define('tools.querytool', [ }); $('#btn-conn-status i').removeClass('obtaining-conn'); - self.gridView.set_editor_title(_.unescape(url_params.title)); + self.gridView.set_editor_title(_.unescape(url_params.title), true); + let connection_data = { + 'server_group': self.gridView.handler.url_params.sgid, + 'server': self.gridView.handler.url_params.sid, + 'database': self.gridView.handler.url_params.did, + 'user': null, + 'role': null, + 'title': _.unescape(url_params.title), + 'is_allow_new_connection': false, + }; + self.gridView.connection_list.unshift(connection_data); + self.gridView.render_connection(self.gridView.connection_list); }; pgBrowser.Events.on('pgadmin:query_tool:connected:' + transId, afterConn); @@ -2487,6 +2629,7 @@ define('tools.querytool', [ self.on('pgadmin-sqleditor:button:save_file', self._save_file, self); self.on('pgadmin-sqleditor:button:deleterow', self._delete, self); self.on('pgadmin-sqleditor:button:show_filter', self._show_filter, self); + self.on('pgadmin-sqleditor:button:show_new_connection', self._show_new_connection, self); self.on('pgadmin-sqleditor:button:include_filter', self._include_filter, self); self.on('pgadmin-sqleditor:button:exclude_filter', self._exclude_filter, self); self.on('pgadmin-sqleditor:button:remove_filter', self._remove_filter, self); @@ -3696,7 +3839,6 @@ define('tools.querytool', [ } }; }, - // This function will show the filter in the text area. _show_filter: function() { let self = this, @@ -3711,7 +3853,19 @@ define('tools.querytool', [ } FilterHandler.dialog(self, reconnect); }, + // This function will show the new connection. + _show_new_connection: function() { + let self = this, + reconnect = false; + /* When server is disconnected and connected, connection is lost, + * To reconnect pass true + */ + if (arguments.length > 0 && arguments[arguments.length - 1] == 'connect') { + reconnect = true; + } + newConnectionHandler.dialog(self, reconnect); + }, // This function will include the filter by selection. _include_filter: function() { var self = this, diff --git a/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss b/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss index fd1e5d3..53f2449 100644 --- a/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss +++ b/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss @@ -30,6 +30,19 @@ color: $sql-title-fg; } +.connection-info { + background: $sql-title-bg; + color: $sql-title-fg; + width:100%; + display: inherit; +} + +.conn-info-dd { + padding-top: 0.3em; + padding-left: 0.2em; + cursor: pointer; +} + #editor-panel { z-index: 0; diff --git a/web/pgadmin/tools/sqleditor/tests/test_new_connection_database.py b/web/pgadmin/tools/sqleditor/tests/test_new_connection_database.py new file mode 100644 index 0000000..20fe3e3 --- /dev/null +++ b/web/pgadmin/tools/sqleditor/tests/test_new_connection_database.py @@ -0,0 +1,98 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.test_setup import config_data +from regression.python_test_utils import test_utils as utils + + +class TestNewConnectionDatabase(BaseTestGenerator): + """ This class will test new connection database. """ + scenarios = [ + ('New connection dialog', + dict( + url="/sqleditor/new_connection_database/", + is_positive_test=True, + mocking_required=False, + is_server_conn_required=False, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ('New connection dialog connect server', + dict( + url="/sqleditor/new_connection_database/", + is_positive_test=True, + mocking_required=False, + is_server_conn_required=True, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ('New connection dialog negative', + dict( + url="/sqleditor/new_connection_database/", + is_positive_test=False, + mocking_required=False, + is_server_conn_required=True, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ] + + def setUp(self): + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + def get_database(self): + response = self.tester.get( + self.url + str(self.sgid) + '/' + str(self.sid), + content_type='html/json' + ) + + return response + + def runTest(self): + if self.is_positive_test: + if self.is_server_conn_required: + self.server['password'] = self.server['db_password'] + server_response = self.tester.post( + '/browser/server/connect/{0}/{1}'.format( + utils.SERVER_GROUP, + self.sid), + data=json.dumps(self.server), + content_type='html/json' + ) + response = self.get_database() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + if self.is_server_conn_required: + self.server['password'] = self.server['db_password'] + server_response = self.tester.post( + '/browser/server/connect/{0}/{1}'.format( + utils.SERVER_GROUP, + self.sid), + data=json.dumps(self.server), + content_type='html/json' + ) + self.sid = 0 + response = self.get_database() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + self.assertEqual(actual_response_code, expected_response_code) diff --git a/web/pgadmin/tools/sqleditor/tests/test_new_connection_dialog.py b/web/pgadmin/tools/sqleditor/tests/test_new_connection_dialog.py new file mode 100644 index 0000000..75a47ef --- /dev/null +++ b/web/pgadmin/tools/sqleditor/tests/test_new_connection_dialog.py @@ -0,0 +1,50 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## +import json +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.test_setup import config_data +from regression.python_test_utils import test_utils as utils + + +class TestNewConnectionDialog(BaseTestGenerator): + """ This class will test new connection dialog. """ + scenarios = [ + ('New connection dialog', + dict( + url="/sqleditor/new_connection_dialog/", + is_positive_test=True, + mocking_required=False, + is_connect_server=False, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ] + + def setUp(self): + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + def new_connection(self): + response = self.tester.get( + self.url + str(self.sgid) + '/' + str(self.sgid), + content_type='html/json' + ) + + return response + + def runTest(self): + if self.is_positive_test: + response = self.new_connection() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + self.assertEqual(actual_response_code, expected_response_code) diff --git a/web/pgadmin/tools/sqleditor/tests/test_new_connection_user.py b/web/pgadmin/tools/sqleditor/tests/test_new_connection_user.py new file mode 100644 index 0000000..7b6c12e --- /dev/null +++ b/web/pgadmin/tools/sqleditor/tests/test_new_connection_user.py @@ -0,0 +1,98 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.test_setup import config_data +from regression.python_test_utils import test_utils as utils + + +class TestNewConnectionUser(BaseTestGenerator): + """ This class will test new connection user. """ + scenarios = [ + ('New connection dialog', + dict( + url="/sqleditor/new_connection_user/", + is_positive_test=True, + mocking_required=False, + is_server_conn_required=False, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ('New connection dialog connect server', + dict( + url="/sqleditor/new_connection_user/", + is_positive_test=True, + mocking_required=False, + is_server_conn_required=True, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ('New connection dialog negative', + dict( + url="/sqleditor/new_connection_user/", + is_positive_test=False, + mocking_required=False, + is_server_conn_required=True, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ] + + def setUp(self): + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + def get_use(self): + response = self.tester.get( + self.url + str(self.sgid) + '/' + str(self.sid), + content_type='html/json' + ) + + return response + + def runTest(self): + if self.is_positive_test: + if self.is_server_conn_required: + self.server['password'] = self.server['db_password'] + server_response = self.tester.post( + '/browser/server/connect/{0}/{1}'.format( + utils.SERVER_GROUP, + self.sid), + data=json.dumps(self.server), + content_type='html/json' + ) + response = self.get_use() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + if self.is_server_conn_required: + self.server['password'] = self.server['db_password'] + server_response = self.tester.post( + '/browser/server/connect/{0}/{1}'.format( + utils.SERVER_GROUP, + self.sid), + data=json.dumps(self.server), + content_type='html/json' + ) + self.sid = 0 + response = self.get_use() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + self.assertEqual(actual_response_code, expected_response_code) diff --git a/web/pgadmin/utils/driver/psycopg2/connection.py b/web/pgadmin/utils/driver/psycopg2/connection.py index 9cb65bc..83c2c17 100644 --- a/web/pgadmin/utils/driver/psycopg2/connection.py +++ b/web/pgadmin/utils/driver/psycopg2/connection.py @@ -21,7 +21,7 @@ import psycopg2 from flask import g, current_app from flask_babelex import gettext from flask_security import current_user -from pgadmin.utils.crypto import decrypt +from pgadmin.utils.crypto import decrypt, encrypt from psycopg2.extensions import encodings import config @@ -211,8 +211,17 @@ class Connection(BaseConnection): password = None passfile = None manager = self.manager + encpass = None + is_update_password = True + crypt_key_present, crypt_key = get_crypt_key() + + if 'user' in kwargs and kwargs['password']: + password = kwargs['password'] + kwargs.pop('password') + is_update_password = False + else: + encpass = kwargs['password'] if 'password' in kwargs else None - encpass = kwargs['password'] if 'password' in kwargs else None passfile = kwargs['passfile'] if 'passfile' in kwargs else None tunnel_password = kwargs['tunnel_password'] if 'tunnel_password' in \ kwargs else '' @@ -227,16 +236,16 @@ class Connection(BaseConnection): if manager.use_ssh_tunnel == 1: manager.check_ssh_tunnel_alive() - if encpass is None: - encpass = self.password or getattr(manager, 'password', None) + if is_update_password: + if encpass is None: + encpass = self.password or getattr(manager, 'password', None) - self.password = encpass + self.password = encpass # Reset the existing connection password if self.reconnecting is not False: self.password = None - crypt_key_present, crypt_key = get_crypt_key() if not crypt_key_present: raise CryptKeyMissing() @@ -269,7 +278,10 @@ class Connection(BaseConnection): try: database = self.db - user = manager.user + if 'user' in kwargs and kwargs['user']: + user = kwargs['user'] + else: + user = manager.user conn_id = self.conn_id import os @@ -338,10 +350,10 @@ class Connection(BaseConnection): self.wasConnected = False raise e - if status: + if status and is_update_password: manager._update_password(encpass) else: - if not self.reconnecting: + if not self.reconnecting and is_update_password: self.wasConnected = False return status, msg @@ -359,7 +371,7 @@ class Connection(BaseConnection): else: self.conn.autocommit = True - def _set_role(self, manager, cur, conn_id): + def _set_role(self, manager, cur, conn_id, **kwargs): """ Set role :param manager: @@ -367,8 +379,18 @@ class Connection(BaseConnection): :param conn_id: :return: """ - if manager.role: - status = self._execute(cur, "SET ROLE TO %s", [manager.role]) + is_set_role = False + role = None + + if 'role' in kwargs and kwargs['role']: + is_set_role = True + role = kwargs['role'] + elif manager.role: + is_set_role = True + role = manager.role + + if is_set_role: + status = self._execute(cur, "SET ROLE TO %s", [role]) if status is not None: self.conn.close() @@ -382,7 +404,7 @@ class Connection(BaseConnection): msg=status ) ) - return False, \ + return True, \ _( "Failed to setup the role with error message:\n{0}" ).format(status) @@ -445,7 +467,7 @@ class Connection(BaseConnection): return False, status - is_error, errmsg = self._set_role(manager, cur, conn_id) + is_error, errmsg = self._set_role(manager, cur, conn_id, **kwargs) if is_error: return False, errmsg @@ -491,7 +513,7 @@ WHERE db.datname = current_database()""") if len(manager.db_info) == 1: manager.did = res['did'] - self._set_user_info(cur, manager) + self._set_user_info(cur, manager, **kwargs) self._set_server_type_and_password(kwargs, manager) @@ -499,7 +521,7 @@ WHERE db.datname = current_database()""") return True, None - def _set_user_info(self, cur, manager): + def _set_user_info(self, cur, manager, **kwargs): """ Set user info. :param cur: @@ -517,7 +539,7 @@ WHERE db.datname = current_database()""") WHERE rolname = current_user""") - if status is None: + if status is None and 'user' not in kwargs: manager.user_info = dict() if cur.rowcount > 0: manager.user_info = cur.fetchmany(1)[0] ^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: [pgAdmin][RM3794]:Allow User to Change Database Connection from an Open Query Tool Tab @ 2020-09-29 06:01 Akshay Joshi <[email protected]> parent: Nikhil Mohite <[email protected]> 0 siblings, 1 reply; 18+ messages in thread From: Akshay Joshi @ 2020-09-29 06:01 UTC (permalink / raw) To: Nikhil Mohite <[email protected]>; +Cc: pgadmin-hackers Hi Nikhil Your patch introduces 1 new Bug and 13 new code smells, please fix those and resend the patch. On Mon, Sep 28, 2020 at 7:31 PM Nikhil Mohite < [email protected]> wrote: > Hi Akshay, > > I have resolved code conflict issues and sonarqube issues. > PFA updated patch. > > Regards, > Nikhil Mohite. > > On Mon, Sep 28, 2020 at 5:58 PM Akshay Joshi < > [email protected]> wrote: > >> Hi Nikhil >> >> The patch is not applying, rebase, and send it again. Please check your >> code should not create any new SonarQube issues. >> >> On Mon, Sep 28, 2020 at 11:20 AM Nikhil Mohite < >> [email protected]> wrote: >> >>> Hi Akshay, >>> >>> I have resolved all the review comments and also updated the test cases >>> as per the new implementation. >>> >>> PFA updated patch. >>> >>> >>> >>> On Mon, Sep 21, 2020 at 5:24 PM Akshay Joshi < >>> [email protected]> wrote: >>> >>>> Hi Nikhil >>>> >>>> Following are the initial review comments: >>>> >>>> - Open View/Edit data on any table and click on the same database >>>> connection and then click on the Execute button. Got "get_primary_keys() >>>> takes 1 positional argument but 2 were given" error. >>>> - In my opinion, we should hide the option to change the database >>>> connection for View/Edit Data. >>>> - If the user clicks on the same database connection multiple times >>>> then no need to change the backend connection and transaction id. Add >>>> validation at the backend, no action required in this case. >>>> - The role option is missing from the "connect to server" dialog. >>>> - The Password field should not be there on the "connect to server" >>>> dialog. Sometimes we saved the password so asking a password every time is >>>> not correct. Check the pgAdmin 3 behavior. >>>> >>>> Code review still remains. >>>> >>>> On Thu, Sep 17, 2020 at 4:15 PM Nikhil Mohite < >>>> [email protected]> wrote: >>>> >>>>> Hi Team, >>>>> >>>>> Regarding RM-3794 <https://redmine.postgresql.org/issues/3794; allow >>>>> the user to change the database connection from an open query tool: >>>>> I have implemented the feature and also added documentation for it. >>>>> >>>>> PFA patch. >>>>> >>>>> -- >>>>> *Thanks & Regards,* >>>>> *Nikhil Mohite* >>>>> *Software Engineer.* >>>>> *EDB Postgres* <https://www.enterprisedb.com/; >>>>> *Mob.No: +91-7798364578.* >>>>> >>>> >>>> >>>> -- >>>> *Thanks & Regards* >>>> *Akshay Joshi* >>>> *pgAdmin Hacker | Sr. Software Architect* >>>> *EDB Postgres <http://edbpostgres.com>* >>>> >>>> *Mobile: +91 976-788-8246* >>>> >>> >> >> -- >> *Thanks & Regards* >> *Akshay Joshi* >> *pgAdmin Hacker | Sr. Software Architect* >> *EDB Postgres <http://edbpostgres.com>* >> >> *Mobile: +91 976-788-8246* >> > -- *Thanks & Regards* *Akshay Joshi* *pgAdmin Hacker | Sr. Software Architect* *EDB Postgres <http://edbpostgres.com>* *Mobile: +91 976-788-8246* ^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: [pgAdmin][RM3794]:Allow User to Change Database Connection from an Open Query Tool Tab @ 2020-10-01 05:12 Nikhil Mohite <[email protected]> parent: Akshay Joshi <[email protected]> 0 siblings, 1 reply; 18+ messages in thread From: Nikhil Mohite @ 2020-10-01 05:12 UTC (permalink / raw) To: Akshay Joshi <[email protected]>; +Cc: pgadmin-hackers Hi Akshay, I have resolved the sonarQube issues, PFA updated patch for the same. Regards, Nikhil Mohite. On Tue, Sep 29, 2020 at 11:31 AM Akshay Joshi <[email protected]> wrote: > Hi Nikhil > > Your patch introduces 1 new Bug and 13 new code smells, please fix those > and resend the patch. > > On Mon, Sep 28, 2020 at 7:31 PM Nikhil Mohite < > [email protected]> wrote: > >> Hi Akshay, >> >> I have resolved code conflict issues and sonarqube issues. >> PFA updated patch. >> >> Regards, >> Nikhil Mohite. >> >> On Mon, Sep 28, 2020 at 5:58 PM Akshay Joshi < >> [email protected]> wrote: >> >>> Hi Nikhil >>> >>> The patch is not applying, rebase, and send it again. Please check your >>> code should not create any new SonarQube issues. >>> >>> On Mon, Sep 28, 2020 at 11:20 AM Nikhil Mohite < >>> [email protected]> wrote: >>> >>>> Hi Akshay, >>>> >>>> I have resolved all the review comments and also updated the test cases >>>> as per the new implementation. >>>> >>>> PFA updated patch. >>>> >>>> >>>> >>>> On Mon, Sep 21, 2020 at 5:24 PM Akshay Joshi < >>>> [email protected]> wrote: >>>> >>>>> Hi Nikhil >>>>> >>>>> Following are the initial review comments: >>>>> >>>>> - Open View/Edit data on any table and click on the same database >>>>> connection and then click on the Execute button. Got "get_primary_keys() >>>>> takes 1 positional argument but 2 were given" error. >>>>> - In my opinion, we should hide the option to change the database >>>>> connection for View/Edit Data. >>>>> - If the user clicks on the same database connection multiple >>>>> times then no need to change the backend connection and transaction id. Add >>>>> validation at the backend, no action required in this case. >>>>> - The role option is missing from the "connect to server" dialog. >>>>> - The Password field should not be there on the "connect to >>>>> server" dialog. Sometimes we saved the password so asking a password every >>>>> time is not correct. Check the pgAdmin 3 behavior. >>>>> >>>>> Code review still remains. >>>>> >>>>> On Thu, Sep 17, 2020 at 4:15 PM Nikhil Mohite < >>>>> [email protected]> wrote: >>>>> >>>>>> Hi Team, >>>>>> >>>>>> Regarding RM-3794 <https://redmine.postgresql.org/issues/3794; allow >>>>>> the user to change the database connection from an open query tool: >>>>>> I have implemented the feature and also added documentation for it. >>>>>> >>>>>> PFA patch. >>>>>> >>>>>> -- >>>>>> *Thanks & Regards,* >>>>>> *Nikhil Mohite* >>>>>> *Software Engineer.* >>>>>> *EDB Postgres* <https://www.enterprisedb.com/; >>>>>> *Mob.No: +91-7798364578.* >>>>>> >>>>> >>>>> >>>>> -- >>>>> *Thanks & Regards* >>>>> *Akshay Joshi* >>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>> *EDB Postgres <http://edbpostgres.com>* >>>>> >>>>> *Mobile: +91 976-788-8246* >>>>> >>>> >>> >>> -- >>> *Thanks & Regards* >>> *Akshay Joshi* >>> *pgAdmin Hacker | Sr. Software Architect* >>> *EDB Postgres <http://edbpostgres.com>* >>> >>> *Mobile: +91 976-788-8246* >>> >> > > -- > *Thanks & Regards* > *Akshay Joshi* > *pgAdmin Hacker | Sr. Software Architect* > *EDB Postgres <http://edbpostgres.com>* > > *Mobile: +91 976-788-8246* > Attachments: [application/octet-stream] RM_3794_V5.patch (224.5K, 3-RM_3794_V5.patch) download | inline diff: diff --git a/docs/en_US/images/new_connection_dialog.png b/docs/en_US/images/new_connection_dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf7ae6aa750360ae7710af61bf8e89f9d520284 GIT binary patch literal 49691 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	%0^c>O%*OkVmcSQwgzI6WP2m3$pEbze=t& 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 NIVmN{VsYb-{|8bOF{}Up literal 0 HcmV?d00001 diff --git a/docs/en_US/images/new_connection_options.png b/docs/en_US/images/new_connection_options.png new file mode 100644 index 0000000000000000000000000000000000000000..7ca94374baf0eb9a8a0e49cbd9d4d426895ff9d0 GIT binary patch literal 45739 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 literal 0 HcmV?d00001 diff --git a/docs/en_US/query_tool.rst b/docs/en_US/query_tool.rst index 83933f0..80f27a5 100644 --- a/docs/en_US/query_tool.rst +++ b/docs/en_US/query_tool.rst @@ -300,3 +300,24 @@ transaction status by clicking on the status icon in the Query Tool: .. image:: images/query_tool_connection_status.png :alt: Query tool connection and transaction statuses :align: center + +Change connection +***************** + +User can connect to another server or database from existing open session of query tool. + +* Click on the connection link next to connection status. +* Now click on the *<New Connection>* option from the dropdown. + +.. image:: images/new_connection_options.png + :alt: Query tool connection options + :align: center + +* Now select server, database, user, and role to connect and click OK. + +.. image:: images/new_connection_dialog.png + :alt: Query tool connection dialog + :align: center + +* A newly created connection will now get listed in the options. +* To connect, select the newly created connection from the dropdown list. diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py index af845e5..34680be 100644 --- a/web/pgadmin/browser/server_groups/servers/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/__init__.py @@ -1241,7 +1241,7 @@ class ServerNode(PGChildNodeView): } ) - def connect(self, gid, sid): + def connect(self, gid, sid, user_name=None): """ Connect the Server and return the connection object. Verification Process before Connection: @@ -1363,7 +1363,8 @@ class ServerNode(PGChildNodeView): # not provided, or password has not been saved earlier. if prompt_password or prompt_tunnel_password: return self.get_response_for_password(server, 428, prompt_password, - prompt_tunnel_password) + prompt_tunnel_password, + user=user_name) status = True try: @@ -1797,7 +1798,8 @@ class ServerNode(PGChildNodeView): return internal_server_error(errormsg=str(e)) def get_response_for_password(self, server, status, prompt_password=False, - prompt_tunnel_password=False, errmsg=None): + prompt_tunnel_password=False, errmsg=None, + user=None): if server.use_ssh_tunnel: return make_json_response( @@ -1824,7 +1826,7 @@ class ServerNode(PGChildNodeView): result=render_template( 'servers/password.html', server_label=server.name, - username=server.username, + username=user if user else server.username, errmsg=errmsg, service=server.service, _=gettext, diff --git a/web/pgadmin/browser/server_groups/servers/roles/tests/utils.py b/web/pgadmin/browser/server_groups/servers/roles/tests/utils.py index 3a7ee58..028ee64 100644 --- a/web/pgadmin/browser/server_groups/servers/roles/tests/utils.py +++ b/web/pgadmin/browser/server_groups/servers/roles/tests/utils.py @@ -152,3 +152,38 @@ def delete_role(connection, role_names): exception = "Error while deleting role: %s: line:%s %s" % ( file_name, sys.exc_traceback.tb_lineno, exception) print(exception, file=sys.stderr) + + +def create_role_with_password(server, role_name, role_password): + """ + This function create the role. + :param server: + :param role_name: + :param role_password: + :return: + """ + try: + connection = utils.get_db_connection(server['db'], + server['username'], + server['db_password'], + server['host'], + server['port'], + server['sslmode']) + pg_cursor = connection.cursor() + pg_cursor.execute( + "CREATE ROLE %s LOGIN PASSWORD '%s'" % (role_name, role_password)) + connection.commit() + # Get 'oid' from newly created tablespace + pg_cursor.execute( + "SELECT pr.oid from pg_catalog.pg_roles pr WHERE pr.rolname='%s'" % + role_name) + oid = pg_cursor.fetchone() + role_id = '' + if oid: + role_id = oid[0] + connection.close() + return role_id + except Exception as exception: + exception = "Error while deleting role: %s: line:%s %s" % ( + file_name, sys.exc_traceback.tb_lineno, exception) + print(exception, file=sys.stderr) diff --git a/web/pgadmin/model/__init__.py b/web/pgadmin/model/__init__.py index 976e962..c3fdaf3 100644 --- a/web/pgadmin/model/__init__.py +++ b/web/pgadmin/model/__init__.py @@ -95,6 +95,15 @@ class ServerGroup(db.Model): name = db.Column(db.String(128), nullable=False) __table_args__ = (db.UniqueConstraint('user_id', 'name'),) + @property + def serialize(self): + """Return object data in easily serializable format""" + return { + 'id': self.id, + 'user_id': self.user_id, + 'name': self.name, + } + class Server(db.Model): """Define a registered Postgres server""" @@ -176,6 +185,44 @@ class Server(db.Model): tunnel_password = db.Column(db.String(64), nullable=True) shared = db.Column(db.Boolean(), nullable=False) + @property + def serialize(self): + """Return object data in easily serializable format""" + return { + "id": self.id, + "user_id": self.user_id, + "servergroup_id": self.servergroup_id, + "name": self.name, + "host": self.host, + "hostaddr": self.hostaddr, + "port": self.port, + "maintenance_db": self.maintenance_db, + "username": self.username, + "password": self.password, + "save_password": self.save_password, + "role": self.role, + "ssl_mode": self.ssl_mode, + "comment": self.comment, + "discovery_id": self.discovery_id, + "db_res": self.db_res, + "passfile": self.passfile, + "sslcert": self.sslcert, + "sslkey": self.sslkey, + "sslrootcert": self.sslrootcert, + "sslcrl": self.sslcrl, + "sslcompression": self.sslcompression, + "bgcolor": self.bgcolor, + "fgcolor": self.fgcolor, + "service": self.service, + "connect_timeout": self.connect_timeout, + "use_ssh_tunnel": self.use_ssh_tunnel, + "tunnel_host": self.tunnel_host, + "tunnel_port": self.tunnel_port, + "tunnel_authentication": self.tunnel_authentication, + "tunnel_identity_file": self.tunnel_identity_file, + "tunnel_password": self.tunnel_password + } + class ModulePreference(db.Model): """Define a preferences table for any modules.""" diff --git a/web/pgadmin/static/js/sqleditor/new_connection_dialog.js b/web/pgadmin/static/js/sqleditor/new_connection_dialog.js new file mode 100644 index 0000000..3fcd374 --- /dev/null +++ b/web/pgadmin/static/js/sqleditor/new_connection_dialog.js @@ -0,0 +1,262 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import gettext from 'sources/gettext'; +import url_for from 'sources/url_for'; +import $ from 'jquery'; +import Alertify from 'pgadmin.alertifyjs'; +import pgAdmin from 'sources/pgadmin'; +import Backform from 'pgadmin.backform'; +import newConnectionDialogModel from 'sources/sqleditor/new_connection_dialog_model'; + + +let NewConnectionDialog = { + 'dialog': function(handler, reconnect) { + let url = url_for('sqleditor.get_new_connection_data', { + 'sid': handler.url_params.sid, + 'sgid': handler.url_params.sgid, + }); + + if(reconnect) { + url += '?connect=1'; + } + + let title = gettext('Connect to server'); + + $.ajax({ + url: url, + headers: { + 'Cache-Control' : 'no-cache', + }, + }).done(function (res) { + let response = res.data.result; + response.database_list = []; + response.user_list = []; + if (Alertify.newConnectionDialog) { + delete Alertify.newConnectionDialog; + } + + // Create Dialog + Alertify.dialog('newConnectionDialog', function factory() { + let $container = $('<div class=\'new-connection-dialog\'></div>'); + return { + main: function(message) { + this.msg = message; + }, + build: function() { + this.elements.content.appendChild($container.get(0)); + Alertify.pgDialogBuild.apply(this); + }, + setup: function(){ + return { + buttons: [ + { + text: '', + key: 112, + className: 'btn btn-primary-icon pull-left fa fa-question pg-alertify-icon-button', + attrs: { + name: 'dialog_help', + type: 'button', + label: gettext('Help'), + 'aria-label': gettext('Help'), + url: url_for('help.static', { + 'filename': 'query_tool.html', + }), + }, + }, + { + text: gettext('Cancel'), + key: 27, + className: 'btn btn-secondary fa fa-times pg-alertify-button', + 'data-btn-name': 'cancel', + }, { + text: gettext('OK'), + key: 13, + className: 'btn btn-primary fa fa-check pg-alertify-button', + 'data-btn-name': 'ok', + }, + ], + // Set options for dialog + options: { + title: title, + //disable both padding and overflow control. + padding: !1, + overflow: !1, + model: 0, + resizable: true, + maximizable: false, + pinnable: false, + closableByDimmer: false, + modal: false, + autoReset: false, + closable: true, + }, + }; + }, + prepare: function() { + let self = this; + $container.html(''); + // Disable Ok button + this.__internal.buttons[2].element.disabled = true; + + // Status bar + this.statusBar = $( + '<div class=\'pg-prop-status-bar pg-el-xs-12 d-none\'>' + + ' <div class="error-in-footer"> ' + + ' <div class="d-flex px-2 py-1"> ' + + ' <div class="pr-2"> ' + + ' <i class="fa fa-exclamation-triangle text-danger" aria-hidden="true"></i> ' + + ' </div> ' + + ' <div class="alert-text" role="alert"></div> ' + + ' </div> ' + + ' </div> ' + + '</div>').appendTo($container); + + // To show progress on filter Saving/Updating on AJAX + this.showNewConnectionProgress = $( + `<div id="show_filter_progress" class="pg-sp-container sql-editor-busy-fetching d-none"> + <div class="pg-sp-content"> + <div class="row"><div class="col-12 pg-sp-icon sql-editor-busy-icon"></div></div> + <div class="row"><div class="col-12 pg-sp-text sql-editor-busy-text">` + gettext('Loading data...') + `</div></div> + </div> + </div>` + ).appendTo($container); + $( + self.showNewConnectionProgress[0] + ).removeClass('d-none'); + + self.newConnCollectionModel = newConnectionDialogModel(response, handler.url_params.sgid, handler.url_params.sid); + let fields = Backform.generateViewSchema(null, self.newConnCollectionModel, 'create', null, null, true); + + let view = this.view = new Backform.Dialog({ + el: '<div></div>', + model: self.newConnCollectionModel, + schema: fields, + }); + + $(this.elements.body.childNodes[0]).addClass( + 'alertify_tools_dialog_properties obj_properties' + ); + + $container.append(view.render().$el); + + // Enable/disable save button and show/hide statusbar based on session + view.listenTo(view.model, 'pgadmin-session:start', function() { + view.listenTo(view.model, 'pgadmin-session:invalid', function(msg) { + self.statusBar.removeClass('d-none'); + $(self.statusBar.find('.alert-text')).html(msg); + // Disable Okay button + self.__internal.buttons[2].element.disabled = true; + }); + + view.listenTo(view.model, 'pgadmin-session:valid', function() { + self.statusBar.addClass('d-none'); + $(self.statusBar.find('.alert-text')).html(''); + // Enable Okay button + self.__internal.buttons[2].element.disabled = false; + }); + }); + + view.listenTo(view.model, 'pgadmin-session:stop', function() { + view.stopListening(view.model, 'pgadmin-session:invalid'); + view.stopListening(view.model, 'pgadmin-session:valid'); + }); + + // Starts monitoring changes to model + view.model.startNewSession(); + + // Hide Progress ... + $( + self.showNewConnectionProgress[0] + ).addClass('d-none'); + }, + callback: function(e) { + let self = this; + if (e.button.element.name == 'dialog_help') { + e.cancel = true; + pgAdmin.Browser.showHelp(e.button.element.name, e.button.element.getAttribute('url'), + null, null); + return; + } else if (e.button['data-btn-name'] === 'ok') { + e.cancel = true; // Do not close dialog + let newConnCollectionModel = this.newConnCollectionModel.toJSON(); + + let selected_database_name = null; + response.database_list.forEach(function(data){ + if(newConnCollectionModel['database'] == data['value']) { + selected_database_name = data['label']; + return false; + } + }); + let tab_title = ''; + if(newConnCollectionModel['role']) { + tab_title = selected_database_name + '/' + newConnCollectionModel['role'] + '@' + response.server_name; + } else { + tab_title = selected_database_name + '/' + newConnCollectionModel['user'] + '@' + response.server_name; + newConnCollectionModel['role'] = null; + } + + let is_create_connection = true; + + handler.gridView.connection_list.forEach(function(connection_data){ + if(parseInt(connection_data['server']) == newConnCollectionModel['server'] + && parseInt(connection_data['database']) == newConnCollectionModel['database'] + && connection_data['user'] == newConnCollectionModel['user'] && connection_data['role'] == newConnCollectionModel['role']) { + is_create_connection = false; + // break for loop by return false. + return false; + } + + if(tab_title == connection_data['title']) { + is_create_connection = false; + return false; + } + }); + if(!is_create_connection) { + let errmsg = 'Connection with this configuration already present.'; + Alertify.info(errmsg); + }else { + let connection_details = { + 'server_group': handler.gridView.handler.url_params.sgid, + 'server': newConnCollectionModel['server'], + 'database': newConnCollectionModel['database'], + 'title': tab_title, + 'user': newConnCollectionModel['user'], + 'role': newConnCollectionModel['role'], + 'password': response.password, + }; + handler.gridView.on_change_connection(connection_details, self); + } + } else { + self.close(); + } + }, + }; + }); + setTimeout(function(){ + Alertify.newConnectionDialog('Connect to server.').resizeTo(pgAdmin.Browser.stdW.md,pgAdmin.Browser.stdH.md); + }, 500); + }).fail(function(error) { + Alertify.alert().setting({ + 'title': gettext('Connection lost'), + 'label':gettext('Ok'), + 'message': gettext('Connection to the server has been lost.'), + 'onok': function(){ + alert(error); + //Close the window after connection is lost + window.close(); + }, + }).show(); + }); + + }, + +}; + +module.exports = NewConnectionDialog; diff --git a/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js b/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js new file mode 100644 index 0000000..e262d0e --- /dev/null +++ b/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js @@ -0,0 +1,339 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import gettext from 'sources/gettext'; +import _ from 'underscore'; +import $ from 'jquery'; +import pgAdmin from 'sources/pgadmin'; +import Backform from 'pgadmin.backform'; +import url_for from 'sources/url_for'; +import alertify from 'pgadmin.alertifyjs'; + +export default function newConnectionDialogModel(response, sgid, sid) { + + let server_name = ''; + let database_name = ''; + + let NewConnectionSelect2Control = Backform.Select2Control.extend({ + fetchData: function(){ + let self = this; + let url = self.field.get('url'); + + url = url_for(url, { + 'sid': self.model.attributes.server, + 'sgid': sgid, + }); + + $.ajax({ + async: false, + url: url, + headers: { + 'Cache-Control' : 'no-cache', + }, + }).done(function (res) { + var transform = self.field.get('transform'); + if(res.data.status){ + let data = res.data.result.data; + + if (transform && _.isFunction(transform)) { + self.field.set('options', transform.bind(self, data)); + } else { + self.field.set('options', data); + } + } else { + if (transform && _.isFunction(transform)) { + self.field.set('options', transform.bind(self, [])); + } else { + self.field.set('options', []); + } + //alertify.error(res.data.msg); + } + }).fail(function(e){ + let msg = ''; + if(e.status == 404) { + msg = 'Unable to find url.'; + } else { + msg = e.responseJSON.errormsg; + } + alertify.error(msg); + }); + }, + render: function() { + this.fetchData(); + return Backform.Select2Control.prototype.render.apply(this, arguments); + }, + onChange: function() { + Backform.Select2Control.prototype.onChange.apply(this, arguments); + }, + }); + + let newConnectionModel = pgAdmin.Browser.DataModel.extend({ + idAttribute: 'name', + defaults: { + server: parseInt(sid), + database: null, + user: null, + password: null, + server_name: server_name, + database_name: database_name, + }, + schema: [{ + id: 'server', + name: 'server', + label: gettext('Server'), + type: 'text', + editable: true, + disabled: false, + select2: { + allowClear: false, + }, + control: Backform.Select2Control.extend({ + connect: function(self) { + let local_self = self; + if(!alertify.connectServer){ + alertify.dialog('connectServer', function factory() { + return { + main: function( + title, message, server_id, submit_password=true + ) { + this.set('title', title); + this.message = message; + this.server_id = server_id; + this.submit_password = submit_password; + }, + setup:function() { + return { + buttons:[{ + text: gettext('Cancel'), className: 'btn btn-secondary fa fa-times pg-alertify-button', + key: 27, + },{ + text: gettext('OK'), key: 13, className: 'btn btn-primary fa fa-check pg-alertify-button', + }], + focus: {element: '#password', select: true}, + options: { + modal: 0, resizable: false, maximizable: false, pinnable: false, + }, + }; + }, + build:function() { + }, + prepare:function() { + this.setContent(this.message); + }, + callback: function(closeEvent) { + + if (closeEvent.button.text == gettext('OK')) { + if(this.submit_password) { + var _url = url_for('sqleditor.connect_server', {'sid': this.server_id}); + + $.ajax({ + type: 'POST', + timeout: 30000, + url: _url, + data: $('#frmPassword').serialize(), + }) + .done(function() { + local_self.model.attributes.database = null; + local_self.model.attributes.user = null; + local_self.model.attributes.role = null; + Backform.Select2Control.prototype.onChange.apply(local_self, arguments); + response.server_list.forEach(function(obj){ + if(obj.id==self.model.changed.server) { + response.server_name = obj.name; + } + }); + }) + .fail(function(xhr) { + alertify.connectServer('Connect to server', xhr.responseJSON.result, local_self.getValueFromDOM()); + }); + } else { + response.password = $('#password').val(); + } + } else { + local_self.model.attributes.database = null; + local_self.model.attributes.user = null; + local_self.model.attributes.role = null; + Backform.Select2Control.prototype.onChange.apply(local_self, arguments); + } + closeEvent.close = true; + }, + }; + }); + } + }, + render: function() { + let self = this; + self.connect(self); + return Backform.Select2Control.prototype.render.apply(self, arguments); + }, + onChange: function() { + this.model.attributes.database = null; + this.model.attributes.user = null; + let self = this; + self.connect(self); + + let url = url_for('sqleditor.connect_server', { + 'sid': self.getValueFromDOM(), + 'usr': self.model.attributes.user, + }); + $.ajax({ + async: false, + url: url, + type: 'POST', + headers: { + 'Cache-Control' : 'no-cache', + }, + }).done(function () { + Backform.Select2Control.prototype.onChange.apply(self, arguments); + response.server_list.forEach(function(obj){ + if(obj.id==self.model.changed.server) { + response.server_name = obj.name; + } + }); + }).fail(function(xhr){ + alertify.connectServer('Connect to server', xhr.responseJSON.result, self.getValueFromDOM()); + }); + + }, + }), + options: function() { + return _.map(response.server_list, (obj) => { + if (obj.id == parseInt(sid)) + response.server_name = obj.name; + + return { + value: obj.id, + label: obj.name, + }; + }); + }, + }, + { + id: 'database', + name: 'database', + label: gettext('Database'), + type: 'text', + editable: true, + disabled: function(m) { + let self_local = this; + if (!_.isUndefined(m.get('server')) && !_.isNull(m.get('server')) + && m.get('server') !== '') { + setTimeout(function() { + if(self_local.options.length) { + m.set('database', self_local.options[0].value); + } + }, 10); + return false; + } + + return true; + }, + deps: ['server'], + url: 'sqleditor.get_new_connection_database', + select2: { + allowClear: false, + width: '100%', + first_empty: true, + select_first: false, + }, + extraClasses:['new-connection-dialog-style'], + control: NewConnectionSelect2Control, + transform: function(data) { + response.database_list = data; + return data; + }, + }, + { + id: 'user', + name: 'user', + label: gettext('User'), + type: 'text', + editable: true, + deps: ['server'], + select2: { + allowClear: false, + width: '100%', + }, + control: NewConnectionSelect2Control, + url: 'sqleditor.get_new_connection_user', + disabled: function(m) { + let self_local = this; + if (!_.isUndefined(m.get('server')) && !_.isNull(m.get('server')) + && m.get('server') !== '') { + setTimeout(function() { + if(self_local.options.length) { + m.set('user', self_local.options[0].value); + } + }, 10); + return false; + } + return true; + }, + },{ + id: 'role', + name: 'role', + label: gettext('Role'), + type: 'text', + editable: true, + deps: ['server'], + select2: { + allowClear: false, + width: '100%', + first_empty: true, + }, + control: NewConnectionSelect2Control, + url: 'sqleditor.get_new_connection_role', + disabled: false, + }, + /*{ + id: 'password', + name: 'password', + label: gettext('Password'tools/sqleditor/__init__.py), + type: 'password', + editable: true, + disabled: true, + deps: ['user'], + control: Backform.InputControl.extend({ + render: function() { + let self = this; + self.model.attributes.password = null; + Backform.InputControl.prototype.render.apply(self, arguments); + return self; + }, + onChange: function() { + let self = this; + Backform.InputControl.prototype.onChange.apply(self, arguments); + }, + }), + },*/ + ], + validate: function() { + let msg = null; + this.errorModel.clear(); + if(_.isUndefined(this.get('database')) || _.isNull(this.get('database'))){ + msg = gettext('Please select database'); + this.errorModel.set('database', msg); + return msg; + } else if(_.isUndefined(this.get('database')) || _.isUndefined(this.get('user'))|| _.isNull(this.get('user'))) { + msg = gettext('Please select user'); + this.errorModel.set('user', msg); + return msg; + } + /*else if((this.attributes.password == '' || _.isUndefined(this.get('password')) || _.isNull(this.get('password')))) { + msg = gettext('Please enter password'); + this.errorModel.set('password', msg); + return msg; + }*/ + return null; + }, + }); + + let model = new newConnectionModel(); + return model; +} diff --git a/web/pgadmin/static/scss/_alert.scss b/web/pgadmin/static/scss/_alert.scss index dac552b..836f0af 100644 --- a/web/pgadmin/static/scss/_alert.scss +++ b/web/pgadmin/static/scss/_alert.scss @@ -92,6 +92,7 @@ right: 0; left: 0; bottom: 0; + z-index: 1; } .pg-prop-status-bar { diff --git a/web/pgadmin/tools/datagrid/__init__.py b/web/pgadmin/tools/datagrid/__init__.py index 1bd841f..f7b836a 100644 --- a/web/pgadmin/tools/datagrid/__init__.py +++ b/web/pgadmin/tools/datagrid/__init__.py @@ -18,22 +18,23 @@ from flask import Response, url_for, session, request, make_response from werkzeug.useragents import UserAgent from flask import current_app as app, render_template from flask_babelex import gettext -from flask_security import login_required +from flask_security import login_required, current_user from pgadmin.tools.sqleditor.command import ObjectRegistry, SQLFilter +from pgadmin.tools.sqleditor import check_transaction_status from pgadmin.utils import PgAdminModule from pgadmin.utils.ajax import make_json_response, bad_request, \ - internal_server_error + internal_server_error, unauthorized from config import PG_DEFAULT_DRIVER -from pgadmin.model import Server +from pgadmin.model import Server, User from pgadmin.utils.driver import get_driver from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost from pgadmin.utils.preferences import Preferences from pgadmin.settings import get_setting from pgadmin.browser.utils import underscore_unescape from pgadmin.utils.exception import ObjectGone -from pgadmin.utils.constants import MIMETYPE_APP_JS from pgadmin.tools.sqleditor.utils.macros import get_user_macros +from pgadmin.utils.constants import MIMETYPE_APP_JS, UNAUTH_REQ MODULE_NAME = 'datagrid' @@ -74,7 +75,8 @@ class DataGridModule(PgAdminModule): 'datagrid.filter_validate', 'datagrid.filter', 'datagrid.panel', - 'datagrid.close' + 'datagrid.close', + 'datagrid.update_query_tool_connection' ] def on_logout(self, user): @@ -324,10 +326,48 @@ def initialize_query_tool(trans_id, sgid, sid, did=None): req_args['recreate'] == '1'): connect = False + is_error, errmsg, conn_id, version = _init_query_tool(trans_id, connect, + sgid, sid, did) + if is_error: + return errmsg + + return make_json_response( + data={ + 'connId': str(conn_id), + 'serverVersion': version, + } + ) + + +def _connect(conn, **kwargs): + """ + Connect the database. + :param conn: Connection instance. + :param kwargs: user, role and password data from user. + :return: + """ + user = None + role = None + password = None + is_ask_password = False + if 'user' in kwargs and 'role' in kwargs: + user = kwargs['user'] + role = kwargs['role'] if kwargs['role'] else None + password = kwargs['password'] if kwargs['password'] else None + is_ask_password = True + if user: + status, msg = conn.connect(user=user, role=role, + password=password) + else: + status, msg = conn.connect() + + return status, msg, is_ask_password, user, role, password + + +def _init_query_tool(trans_id, connect, sgid, sid, did, **kwargs): # Create asynchronous connection using random connection id. conn_id = str(random.randint(1, 9999999)) - # Use Maintenance database OID manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) if did is None: @@ -338,24 +378,41 @@ def initialize_query_tool(trans_id, sgid, sid, did=None): ) except Exception as e: app.logger.error(e) - return internal_server_error(errormsg=str(e)) + return True, internal_server_error(errormsg=str(e)), '', '' try: conn = manager.connection(did=did, conn_id=conn_id, auto_reconnect=False, use_binary_placeholder=True, array_to_string=True) + if connect: - status, msg = conn.connect() + status, msg, is_ask_password, user, role, password = _connect( + conn, **kwargs) if not status: app.logger.error(msg) - return internal_server_error(errormsg=str(msg)) + if is_ask_password: + server = Server.query.filter_by(id=sid).first() + return True, make_json_response( + success=0, + status=428, + result=render_template( + 'servers/password.html', + server_label=server.name, + username=user, + errmsg=msg, + _=gettext, + ) + ), '', '' + else: + return True, internal_server_error( + errormsg=str(msg)), '', '' except (ConnectionLost, SSHTunnelConnectionLost) as e: app.logger.error(e) raise except Exception as e: app.logger.error(e) - return internal_server_error(errormsg=str(e)) + return True, internal_server_error(errormsg=str(e)), '', '' if 'gridData' not in session: sql_grid_data = dict() @@ -377,10 +434,77 @@ def initialize_query_tool(trans_id, sgid, sid, did=None): # Store the grid dictionary into the session variable session['gridData'] = sql_grid_data + return False, '', conn_id, manager.version + + [email protected]( + '/initialize/query_tool/update_connection/<int:trans_id>/' + '<int:sgid>/<int:sid>/<int:did>', + methods=["POST"], endpoint='update_query_tool_connection' +) +def update_query_tool_connection(trans_id, sgid, sid, did): + # Remove transaction Id. + with query_tool_close_session_lock: + data = json.loads(request.data, encoding='utf-8') + + if 'gridData' not in session: + return make_json_response(data={'status': True}) + + grid_data = session['gridData'] + + # Return from the function if transaction id not found + if str(trans_id) not in grid_data: + return make_json_response(data={'status': True}) + + connect = True + + req_args = request.args + if ('recreate' in req_args and + req_args['recreate'] == '1'): + connect = False + + new_trans_id = str(random.randint(1, 9999999)) + kwargs = { + 'user': data['user'], + 'role': data['role'], + 'password': data['password'] if 'password' in data else None + } + + is_error, errmsg, conn_id, version = _init_query_tool( + new_trans_id, connect, sgid, sid, did, **kwargs) + + if is_error: + return errmsg + else: + try: + # Check the transaction and connection status + status, error_msg, conn, trans_obj, session_obj = \ + check_transaction_status(trans_id) + + status, error_msg, new_conn, new_trans_obj, new_session_obj = \ + check_transaction_status(new_trans_id) + + new_session_obj['primary_keys'] = session_obj[ + 'primary_keys'] if 'primary_keys' in session_obj else None + new_session_obj['columns_info'] = session_obj[ + 'columns_info'] if 'columns_info' in session_obj else None + new_session_obj['client_primary_key'] = session_obj[ + 'client_primary_key'] if 'client_primary_key'\ + in session_obj else None + + close_query_tool_session(trans_id) + # Remove the information of unique transaction id from the + # session variable. + grid_data.pop(str(trans_id), None) + session['gridData'] = grid_data + except Exception as e: + app.logger.error(e) + return make_json_response( data={ 'connId': str(conn_id), - 'serverVersion': manager.version, + 'serverVersion': version, + 'tran_id': new_trans_id } ) diff --git a/web/pgadmin/tools/datagrid/templates/datagrid/index.html b/web/pgadmin/tools/datagrid/templates/datagrid/index.html index a0eebc8..4970027 100644 --- a/web/pgadmin/tools/datagrid/templates/datagrid/index.html +++ b/web/pgadmin/tools/datagrid/templates/datagrid/index.html @@ -417,8 +417,17 @@ title="" role="img"> </i> </div> - <div class="editor-title" - style="background-color: {% if fgcolor %}{{ bgcolor or '#FFFFFF' }}{% endif %}; color: {% if fgcolor %}{{ fgcolor }}{% endif %};"> </div> + <div class="connection-info btn-group mr-1" role="group" aria-label=""> + <div class="editor-title" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" + style="background-color: {% if fgcolor %}{{ bgcolor or '#FFFFFF' }}{% endif %}; color: {% if fgcolor %}{{ fgcolor }}{% endif %};"> + </div> + <span class="conn-info-dd dropdown-toggle dropdown-toggle-split" + data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span> + <ul class="dropdown-menu" id="connections-list"> + </ul> + </div> + + </div> <div id="editor-panel" tabindex="0"> <div id="fetching_data" class="pg-sp-container sql-editor-busy-fetching"> @@ -481,6 +490,7 @@ require(['sources/generated/browser_nodes', 'sources/generated/codemirror', 'sou var script_type_url = ''; {% endif %} // Start the query tool. + sqlEditorController.start( {{ uniqueId }}, {{ url_params|safe}}, diff --git a/web/pgadmin/tools/datagrid/tests/__init__.py b/web/pgadmin/tools/datagrid/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web/pgadmin/tools/datagrid/tests/datagrid_test_data.json b/web/pgadmin/tools/datagrid/tests/datagrid_test_data.json new file mode 100644 index 0000000..0075f35 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/datagrid_test_data.json @@ -0,0 +1,134 @@ +{ + "data_grid_init_query_tool": [ + { + "name": "Datagrid init query tool", + "url": "/datagrid/initialize/query_tool/", + "is_positive_test": true, + "mocking_required": false, + "test_data": {}, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + } + ], + "data_grid_query_tool_close": [ + { + "name": "Datagrid query tool close", + "url": "/datagrid/close/", + "is_positive_test": true, + "mocking_required": false, + "test_data": {}, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + } + ], + "data_grid_validate_filter": [ + { + "name": "Datagrid validate filter", + "url": "/datagrid/filter/validate/", + "is_positive_test": true, + "mocking_required": false, + "test_data": "id = 1", + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Datagrid validate filter", + "url": "/datagrid/filter/validate/", + "is_positive_test": false, + "mocking_required": true, + "test_data": "id = 1", + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_scalar", + "return_value": "(False, 'Mocked Internal Server Error while validate filter')" + }, + "expected_data": { + "status_code": 200 + } + } + ], + "data_grid_update_connection": [ + { + "name": "Datagrid update connection positive", + "url": "/datagrid/initialize/query_tool/update_connection/", + "is_positive_test": true, + "mocking_required": false, + "is_create_role": false, + "test_data": {}, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Datagrid update connection with new user", + "url": "/datagrid/initialize/query_tool/update_connection/", + "is_positive_test": true, + "mocking_required": false, + "is_create_role": true, + "test_data": {}, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + } + ], + "data_grid_panel": [ + { + "name": "Datagrid Panel", + "url": "/datagrid/panel/", + "is_positive_test": true, + "mocking_required": false, + "test_data": {}, + "mock_data": { + }, + "expected_data": { + "status_code": 200 + } + } + ], + "data_grid_initialize": [ + { + "name": "Datagrid Initialize", + "url": "/datagrid/initialize/datagrid/", + "is_positive_test": true, + "mocking_required": false, + "test_data": "id=1", + "mock_data": { + + }, + "expected_data": { + "status_code": 200 + } + },{ + "name": "Datagrid Initialize", + "url": "/datagrid/initialize/datagrid/", + "is_positive_test": true, + "mocking_required": false, + "test_data": null, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Datagrid Initialize", + "url": "/datagrid/initialize/datagrid/", + "is_positive_test": false, + "mocking_required": true, + "test_data": "id=1", + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict", + "return_value": "(False, 'Mocked Internal Server Error while initialize datagrid.')" + }, + "expected_data": { + "status_code": 500 + } + } + ] +} diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_init_query_tool.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_init_query_tool.py new file mode 100644 index 0000000..f64e561 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_init_query_tool.py @@ -0,0 +1,73 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from pgadmin.utils.exception import ExecuteError +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from . import utils as data_grid_utils + + +class DatagridInitQueryToolTestCase(BaseTestGenerator): + """ + This will init query-tool connection. + """ + + scenarios = utils.generate_scenarios( + 'data_grid_init_query_tool', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + + self.trans_id = str(random.randint(1, 9999999)) + + if not db_con['data']["connected"]: + raise ExecuteError("Could not connect to database to add a table.") + + def init_query_tool(self): + response = self.tester.post( + self.url + str(self.trans_id) + '/' + str(self.sgid) + '/' + str( + self.sid) + '/' + str(self.did), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will init query tool connection.""" + + if self.is_positive_test: + response = self.init_query_tool() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_panel.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_panel.py new file mode 100644 index 0000000..54bbe31 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_panel.py @@ -0,0 +1,90 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from pgadmin.utils.exception import ExecuteError +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from . import utils as data_grid_utils + + +class DatagridPanelTestCase(BaseTestGenerator): + """ + This will data grid panel. + """ + + scenarios = utils.generate_scenarios( + 'data_grid_panel', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + if not db_con['data']["connected"]: + raise ExecuteError("Could not connect to database to add a table.") + + self.trans_id = str(random.randint(1, 9999999)) + qt_init = data_grid_utils._init_query_tool(self, self.trans_id, + self.sgid, self.sid, + self.did) + + if not qt_init['success']: + raise ExecuteError("Could not initialize querty tool.") + + def panel(self): + query_param = \ + '?is_query_tool={0}&sgid={1}&sid={2}&server_type={3}' \ + '&did={4}&title={5}'.format(True, self.sgid, self.sid, + self.server_information['type'], + self.did, 'Query panel') + + response = self.tester.post( + self.url + str(self.trans_id) + query_param, + data=json.dumps(self.test_data), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will update query tool connection.""" + + if self.is_positive_test: + response = self.panel() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + with patch(self.mock_data["function_name"], + return_value=eval(self.mock_data["return_value"])): + response = self.panel() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_query_tool_close.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_query_tool_close.py new file mode 100644 index 0000000..7e4b56c --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_query_tool_close.py @@ -0,0 +1,78 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from . import utils as data_grid_utils +from pgadmin.utils.exception import ExecuteError + + +class DatagridQueryToolCloseTestCase(BaseTestGenerator): + """ + This will close query-tool connection. + """ + + scenarios = utils.generate_scenarios( + 'data_grid_query_tool_close', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + + if not db_con['data']["connected"]: + raise ExecuteError("Could not connect to database to add a table.") + + self.trans_id = str(random.randint(1, 9999999)) + qt_init = data_grid_utils._init_query_tool(self, self.trans_id, + self.sgid, self.sid, + self.did) + + if not qt_init['success']: + raise ExecuteError("Could not initialize querty tool.") + + def close_connection(self): + response = self.tester.delete( + self.url + str(self.trans_id), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will update query tool connection.""" + + if self.is_positive_test: + response = self.close_connection() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_update_connection.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_update_connection.py new file mode 100644 index 0000000..a15702c --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_update_connection.py @@ -0,0 +1,121 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from pgadmin.browser.server_groups.servers.roles.tests import \ + utils as roles_utils +from . import utils as data_grid_utils +from pgadmin.utils.exception import ExecuteError + + +class DatagridUpdateConnectionTestCase(BaseTestGenerator): + """ + This will update query-tool connection. + """ + + scenarios = utils.generate_scenarios( + 'data_grid_update_connection', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + + self.trans_id = str(random.randint(1, 9999999)) + self.roles = None + + if self.is_create_role: + data = roles_utils.get_role_data(self.server['db_password']) + self.role_name = data['rolname'] + self.role_password = data['rolpassword'] + roles_utils.create_role_with_password( + self.server, self.role_name, self.role_password) + + if not self.is_positive_test or self.is_create_role: + qt_init = data_grid_utils._init_query_tool(self, self.trans_id, + self.sgid, self.sid, + self.did) + + if not qt_init['success']: + raise ExecuteError("Could not initialize querty tool.") + + self.test_data = { + "database": self.did, + "server": self.sid, + } + + if self.server_information['type'] == 'ppas': + self.test_data['password'] = 'enterprisedb' + self.test_data['user'] = 'enterprisedb' + else: + self.test_data['password'] = 'postgres' + self.test_data['user'] = 'postgres' + + if not db_con['data']["connected"]: + raise ExecuteError("Could not connect to database to add a table.") + + def update_connection(self, user_data=None): + if user_data: + response = self.tester.post( + self.url + str(self.trans_id) + '/' + str(self.sgid) + + '/' + str(self.sid) + '/' + str(self.did), + data=json.dumps(user_data), + content_type='html/json' + ) + else: + response = self.tester.post( + self.url + str(self.trans_id) + '/' + str(self.sgid) + '/' + + str(self.sid) + '/' + str(self.did), + data=json.dumps(self.test_data), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will update query tool connection.""" + + if self.is_positive_test: + user_data = dict() + if self.is_create_role: + user_data['user'] = self.role_name + user_data['password'] = self.role_password + user_data['role'] = None + response = self.update_connection(user_data=user_data) + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + response = self.update_connection() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_validate_filter.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_validate_filter.py new file mode 100644 index 0000000..b467b6d --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_validate_filter.py @@ -0,0 +1,92 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from . import utils as data_grid_utils +from pgadmin.utils.exception import ExecuteError + + +class DatagridValidateFilterTestCase(BaseTestGenerator): + """ + This will validate filter connection. + """ + + scenarios = utils.generate_scenarios( + 'data_grid_validate_filter', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + if not db_con['data']["connected"]: + raise ExecuteError("Could not connect to database to add a table.") + self.schema_id = parent_node_dict['schema'][-1]["schema_id"] + self.schema_name = parent_node_dict['schema'][-1]["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise ExecuteError("Could not find the schema to add a table.") + self.table_name = "table_for_wizard%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + + def validate_filter(self): + response = self.tester.post( + self.url + str(self.sid) + '/' + str(self.did) + '/' + + str(self.table_id), + data=json.dumps(self.test_data), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will update query tool connection.""" + + if self.is_positive_test: + response = self.validate_filter() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + with patch(self.mock_data["function_name"], + return_value=eval(self.mock_data["return_value"])): + response = self.validate_filter() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/test_initialize_data_grid.py b/web/pgadmin/tools/datagrid/tests/test_initialize_data_grid.py new file mode 100644 index 0000000..8d07139 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_initialize_data_grid.py @@ -0,0 +1,109 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import json +import uuid +import random + +from unittest.mock import patch +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from regression.test_setup import config_data +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from . import utils as data_grid_utils +from pgadmin.utils.exception import ExecuteError + + +class DatagridInitializeTestCase(BaseTestGenerator): + """ + This will Initialize datagrid + """ + + scenarios = utils.generate_scenarios( + 'data_grid_initialize', + data_grid_utils.test_cases + ) + + def setUp(self): + self.database_info = parent_node_dict["database"][-1] + self.db_name = self.database_info["db_name"] + self.did = self.database_info["db_id"] + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.sid, self.did) + if not db_con['data']["connected"]: + raise ExecuteError("Could not connect to database to add a table.") + + self.schema_id = parent_node_dict['schema'][-1]["schema_id"] + self.schema_name = parent_node_dict['schema'][-1]["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise ExecuteError("Could not find the schema to add a table.") + self.table_name = "table_for_wizard%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + self.trans_id = str(random.randint(1, 9999999)) + qt_init = data_grid_utils._init_query_tool(self, self.trans_id, + self.sgid, self.sid, + self.did) + + if not qt_init['success']: + raise ExecuteError("Could not initialize query tool.") + + def initialize_datagrid(self): + if self.test_data: + response = self.tester.post( + self.url + str(self.trans_id) + '/4/table/' + + str(self.sgid) + '/' + str(self.sid) + '/' + + str(self.did) + '/' + str(self.table_id), + data=json.dumps(self.test_data), + content_type='html/json' + ) + else: + response = self.tester.post( + self.url + str(self.trans_id) + '/4/table/' + + str(self.sgid) + '/' + str(self.sid) + '/' + + str(self.did) + '/' + str(self.table_id), + content_type='html/json' + ) + return response + + def runTest(self): + """ This function will update query tool connection.""" + + if self.is_positive_test: + response = self.initialize_datagrid() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + with patch(self.mock_data["function_name"], + return_value=eval(self.mock_data["return_value"])): + response = self.initialize_datagrid() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + + self.assertEqual(actual_response_code, expected_response_code) + + def tearDown(self): + """This function disconnect database.""" + database_utils.disconnect_database(self, self.sid, + self.did) diff --git a/web/pgadmin/tools/datagrid/tests/utils.py b/web/pgadmin/tools/datagrid/tests/utils.py new file mode 100644 index 0000000..c3d4bb5 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/utils.py @@ -0,0 +1,33 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## +import os +import json + +file_name = os.path.basename(__file__) +CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) +with open(CURRENT_PATH + "/datagrid_test_data.json") as data_file: + test_cases = json.load(data_file) + + +def _init_query_tool(self, trans_id, server_group, server_id, db_id): + QUERY_TOOL_INIT_URL = '/datagrid/initialize/query_tool' + + qt_init = self.tester.post( + '{0}/{1}/{2}/{3}/{4}'.format( + QUERY_TOOL_INIT_URL, + trans_id, + server_group, + server_id, + db_id + ), + follow_redirects=True + ) + assert qt_init.status_code == 200 + qt_init = json.loads(qt_init.data.decode('utf-8')) + return qt_init diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py index 0f56b60..9da8842 100644 --- a/web/pgadmin/tools/sqleditor/__init__.py +++ b/web/pgadmin/tools/sqleditor/__init__.py @@ -10,17 +10,15 @@ """A blueprint module implementing the sqleditor frame.""" import os import pickle -import sys import re +from urllib.parse import unquote import simplejson as json -from flask import Response, url_for, render_template, session, request, \ - current_app +from config import PG_DEFAULT_DRIVER, ON_DEMAND_RECORD_COUNT +from flask import Response, url_for, render_template, session, current_app +from flask import request, jsonify from flask_babelex import gettext from flask_security import login_required, current_user -from urllib.parse import unquote - -from config import PG_DEFAULT_DRIVER, ON_DEMAND_RECORD_COUNT from pgadmin.misc.file_manager import Filemanager from pgadmin.tools.sqleditor.command import QueryToolCommand from pgadmin.tools.sqleditor.utils.constant_definition import ASYNC_OK, \ @@ -32,11 +30,11 @@ from pgadmin.tools.sqleditor.utils.update_session_grid_transaction import \ from pgadmin.utils import PgAdminModule from pgadmin.utils import get_storage_directory from pgadmin.utils.ajax import make_json_response, bad_request, \ - success_return, internal_server_error, make_response as ajax_response + success_return, internal_server_error from pgadmin.utils.driver import get_driver -from pgadmin.utils.menu import MenuItem -from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost,\ +from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost, \ CryptKeyMissing +from pgadmin.utils.menu import MenuItem from pgadmin.utils.sqlautocomplete.autocomplete import SQLAutoComplete from pgadmin.tools.sqleditor.utils.query_tool_preferences import \ register_query_tool_preferences @@ -44,13 +42,16 @@ from pgadmin.tools.sqleditor.utils.query_tool_fs_utils import \ read_file_generator from pgadmin.tools.sqleditor.utils.filter_dialog import FilterDialog from pgadmin.tools.sqleditor.utils.query_history import QueryHistory -from pgadmin.utils.constants import MIMETYPE_APP_JS, SERVER_CONNECTION_CLOSED,\ - ERROR_MSG_TRANS_ID_NOT_FOUND from pgadmin.tools.sqleditor.utils.macros import get_macros,\ get_user_macros, set_macros +from pgadmin.utils.constants import MIMETYPE_APP_JS, \ + SERVER_CONNECTION_CLOSED, ERROR_MSG_TRANS_ID_NOT_FOUND, ERROR_FETCHING_DATA +from pgadmin.model import Server +from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry MODULE_NAME = 'sqleditor' TRANSACTION_STATUS_CHECK_FAILED = gettext("Transaction status check failed.") +_NODES_SQL = 'nodes.sql' class SqlEditorModule(PgAdminModule): @@ -114,7 +115,13 @@ class SqlEditorModule(PgAdminModule): 'sqleditor.clear_query_history', 'sqleditor.get_macro', 'sqleditor.get_macros', - 'sqleditor.set_macros' + 'sqleditor.set_macros', + 'sqleditor.get_new_connection_data', + 'sqleditor.get_new_connection_database', + 'sqleditor.get_new_connection_user', + 'sqleditor.get_new_connection_role', + 'sqleditor.connect_server', + 'sqleditor.connect_server_with_user', ] def register_preferences(self): @@ -230,7 +237,7 @@ def start_view_data(trans_id): ) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: # set fetched row count to 0 as we are executing query again. trans_obj.update_fetched_row_cnt(0) @@ -376,7 +383,7 @@ def poll(trans_id): if isinstance(trans_obj, QueryToolCommand): trans_status = conn.transaction_status() if trans_status == TX_STATUS_INERROR and \ - trans_obj.auto_rollback: + trans_obj.auto_rollback: conn.execute_void("ROLLBACK;") st, result = conn.async_fetchmany_2darray(ON_DEMAND_RECORD_COUNT) @@ -686,13 +693,12 @@ def save(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: # If there is no primary key found then return from the function. if ('primary_keys' not in session_obj or - len(session_obj['primary_keys']) <= 0 or - len(changed_data) <= 0) and \ - 'has_oids' not in session_obj: + len(session_obj['primary_keys']) <= 0 or + len(changed_data) <= 0) and 'has_oids' not in session_obj: return make_json_response( data={ 'status': False, @@ -759,7 +765,7 @@ def append_filter_inclusive(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = None filter_sql = '' @@ -813,7 +819,7 @@ def append_filter_exclusive(trans_id): info='DATAGRID_TRANSACTION_REQUIRED', status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = None filter_sql = '' @@ -866,7 +872,7 @@ def remove_filter(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = None @@ -910,7 +916,7 @@ def set_limit(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = None @@ -1052,7 +1058,7 @@ def get_object_name(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = trans_obj.object_name else: status = False @@ -1088,7 +1094,7 @@ def set_auto_commit(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = None @@ -1133,7 +1139,7 @@ def set_auto_rollback(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: res = None @@ -1185,7 +1191,7 @@ def auto_complete(trans_id): status=404) if status and conn is not None and \ - trans_obj is not None and session_obj is not None: + trans_obj is not None and session_obj is not None: # Create object of SQLAutoComplete class and pass connection object auto_complete_obj = SQLAutoComplete( @@ -1472,6 +1478,282 @@ def get_filter_data(trans_id): return FilterDialog.get(status, error_msg, conn, trans_obj, session_ob) [email protected]( + '/new_connection_dialog/<int:sgid>/<int:sid>', + methods=["GET"], endpoint='get_new_connection_data' +) +@login_required +def get_new_connection_data(sgid, sid=None): + """ + This method is used to get required data for get new connection. + :extract_sql_from_network_parameters, + """ + try: + # if sid and not did: + servers = Server.query.all() + server_list = [ + {'name': server.serialize['name'], "id": server.serialize['id']} + for server in servers] + + msg = "Success" + return make_json_response( + data={ + 'status': True, + 'msg': msg, + 'result': { + 'server_list': server_list + } + } + ) + + except Exception: + return make_json_response( + data={ + 'status': False, + 'msg': ERROR_FETCHING_DATA, + 'result': { + 'server_list': [] + } + } + ) + + [email protected]( + '/new_connection_database/<int:sgid>/<int:sid>', + methods=["GET"], endpoint='get_new_connection_database' +) +@login_required +def get_new_connection_database(sgid, sid=None): + """ + This method is used to get required data for get new connection. + :extract_sql_from_network_parameters, + """ + try: + database_list = [] + from pgadmin.utils.driver import get_driver + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) + conn = manager.connection() + if conn.connected(): + is_connected = True + else: + is_connected = False + if is_connected: + if sid: + template_path = 'databases/sql/#{0}#'.format(manager.version) + last_system_oid = 0 + server_node_res = manager + + db_disp_res = None + params = None + if server_node_res and server_node_res.db_res: + db_disp_res = ", ".join( + ['%s'] * len(server_node_res.db_res.split(',')) + ) + params = tuple(server_node_res.db_res.split(',')) + sql = render_template( + "/".join([template_path, _NODES_SQL]), + last_system_oid=last_system_oid, + db_restrictions=db_disp_res + ) + status, databases = conn.execute_dict(sql, params) + database_list = [ + {'label': database['name'], 'value': database['did']} for + database in databases['rows']] + else: + status = False + + msg = "Success" + return make_json_response( + data={ + 'status': status, + 'msg': msg, + 'result': { + 'data': database_list, + } + } + ) + else: + return make_json_response( + data={ + 'status': False, + 'msg': SERVER_CONNECTION_CLOSED, + 'result': { + 'database_list': [], + } + } + ) + except Exception: + return make_json_response( + data={ + 'status': False, + 'msg': ERROR_FETCHING_DATA, + 'result': { + 'database_list': [], + } + } + ) + + [email protected]( + '/new_connection_user/<int:sgid>/<int:sid>', + methods=["GET"], endpoint='get_new_connection_user' +) +@login_required +def get_new_connection_user(sgid, sid=None): + """ + This method is used to get required data for get new connection. + :extract_sql_from_network_parameters, + """ + try: + from pgadmin.utils.driver import get_driver + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) + conn = manager.connection() + user_list = [] + if conn.connected(): + is_connected = True + else: + is_connected = False + if is_connected: + if sid: + sql_path = 'roles/sql/#{0}#'.format(manager.version) + status, users = conn.execute_2darray( + render_template(sql_path + _NODES_SQL) + ) + user_list = [ + {'value': user['rolname'], 'label': user['rolname']} for + user in users['rows'] if user['rolcanlogin']] + else: + status = False + + msg = "Success" + return make_json_response( + data={ + 'status': status, + 'msg': msg, + 'result': { + 'data': user_list, + } + } + ) + else: + return make_json_response( + data={ + 'status': False, + 'msg': SERVER_CONNECTION_CLOSED, + 'result': { + 'user_list': [], + } + } + ) + except Exception: + return make_json_response( + data={ + 'status': False, + 'msg': 'Unable to fetch data.', + 'result': { + 'user_list': [], + } + } + ) + + [email protected]( + '/new_connection_role/<int:sgid>/<int:sid>', + methods=["GET"], endpoint='get_new_connection_role' +) +@login_required +def get_new_connection_role(sgid, sid=None): + """ + This method is used to get required data for get new connection. + :extract_sql_from_network_parameters, + """ + try: + from pgadmin.utils.driver import get_driver + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) + conn = manager.connection() + role_list = [] + if conn.connected(): + is_connected = True + else: + is_connected = False + if is_connected: + if sid: + sql_path = 'roles/sql/#{0}#'.format(manager.version) + status, roles = conn.execute_2darray( + render_template(sql_path + _NODES_SQL) + ) + role_list = [ + {'value': role['rolname'], 'label': role['rolname']} for + role in roles['rows']] + else: + status = False + + msg = "Success" + return make_json_response( + data={ + 'status': status, + 'msg': msg, + 'result': { + 'data': role_list, + } + } + ) + else: + return make_json_response( + data={ + 'status': False, + 'msg': SERVER_CONNECTION_CLOSED, + 'result': { + 'user_list': [], + } + } + ) + except Exception: + return make_json_response( + data={ + 'status': False, + 'msg': 'Unable to fetch data.', + 'result': { + 'user_list': [], + } + } + ) + + [email protected]( + '/connect_server/<int:sid>/<usr>', + methods=["POST"], + endpoint="connect_server_with_user" +) [email protected]( + '/connect_server/<int:sid>', + methods=["POST"], + endpoint="connect_server" +) +@login_required +def connect_server(sid, usr=None): + # Check if server is already connected then no need to reconnect again. + server = Server.query.filter_by(id=sid).first() + driver = get_driver(PG_DEFAULT_DRIVER) + manager = driver.connection_manager(sid) + conn = manager.connection() + user = None + + if usr and manager.user != usr: + user = usr + else: + user = manager.user + if conn.connected(): + return make_json_response( + success=1, + info=gettext("Server connected."), + data={} + ) + + view = SchemaDiffRegistry.get_node_view('server') + return view.connect(server.servergroup_id, sid, user_name=user) + + @blueprint.route( '/filter_dialog/<int:trans_id>', methods=["PUT"], endpoint='set_filter_data' diff --git a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css index c281d53..20590d3 100644 --- a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css +++ b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css @@ -315,10 +315,6 @@ input.editor-checkbox:focus { padding: 10px 0px; } -.editor-title { - width:100%; -} - .connection-status-hide { display: none !important; } @@ -396,7 +392,6 @@ input.editor-checkbox:focus { overflow-y: hidden; } - /* Macros */ .macro-tab { @@ -424,3 +419,7 @@ input.editor-checkbox:focus { .macro_dialog .pg-prop-status-bar { z-index: 1; } + +.new-connection-dialog-style { + width: 100% !important; +} diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js index ed3bd59..4ef4b8f 100644 --- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js +++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js @@ -14,6 +14,7 @@ define('tools.querytool', [ 'jqueryui.position', 'underscore', 'pgadmin.alertifyjs', 'sources/pgadmin', 'backbone', 'bundled_codemirror', 'sources/utils', 'pgadmin.misc.explain', + 'pgadmin.user_management.current_user', 'sources/selection/grid_selector', 'sources/selection/active_cell_capture', 'sources/selection/clipboard', @@ -26,6 +27,7 @@ define('tools.querytool', [ 'sources/sqleditor/execute_query', 'sources/sqleditor/query_tool_http_error_handler', 'sources/sqleditor/filter_dialog', + 'sources/sqleditor/new_connection_dialog', 'sources/sqleditor/geometry_viewer', 'sources/sqleditor/history/history_collection.js', 'sources/sqleditor/history/query_history', @@ -53,8 +55,8 @@ define('tools.querytool', [ 'pgadmin.tools.user_management', ], function( gettext, url_for, $, jqueryui, jqueryui_position, _, alertify, pgAdmin, Backbone, codemirror, pgadminUtils, - pgExplain, GridSelector, ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent, - XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler, FilterHandler, + pgExplain, current_user, GridSelector, ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent, + XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler, FilterHandler, newConnectionHandler, GeometryViewer, historyColl, queryHist, querySources, keyboardShortcuts, queryToolActions, queryToolNotifications, Datagrid, modifyAnimation, calculateQueryRunTime, callRenderAfterPoll, queryToolPref, queryTxnStatus, csrfToken, panelTitleFunc, @@ -98,6 +100,9 @@ define('tools.querytool', [ this.layout = opts.layout; this.set_server_version(opts.server_ver); this.trigger('pgadmin-sqleditor:view:initialised'); + this.connection_list = [ + {'server_group': null,'server': null, 'database': null, 'user': null, 'role': null, 'title': '<New Connection>'}, + ]; }, // Bind all the events @@ -163,6 +168,35 @@ define('tools.querytool', [ 'click .btn-macro': 'on_execute_macro', }, + render_connection: function(data_list) { + if(this.handler.is_query_tool) { + var dropdownElement = document.getElementById('connections-list'); + dropdownElement.innerHTML = ''; + data_list.forEach((option, index) => { + $('#connections-list').append('<li class="connection-list-item" data-index='+ index +'><a class="dropdown-item" href="#" tabindex="0">'+ option.title +'</a></li>'); + + }); + var self = this; + $('.connection-list-item').click(function() { + self.get_connection_data(this); + }); + } else { + $('.conn-info-dd').hide(); + $('.editor-title').css({pointerEvents: 'none'}); + } + }, + + get_connection_data: function(event){ + var index = $(event).attr('data-index'); + var connection_details = this.connection_list[index]; + if(connection_details.server_group) { + this.on_change_connection(connection_details); + } else { + this.on_new_connection(); + } + + }, + reflectPreferences: function() { let self = this, browser = pgWindow.default.pgAdmin.Browser, @@ -213,6 +247,7 @@ define('tools.querytool', [ set_editor_title: function(title) { this.$el.find('.editor-title').text(title); + this.render_connection(this.connection_list); }, // This function is used to render the template. @@ -696,6 +731,8 @@ define('tools.querytool', [ pgBrowser.register_to_activity_listener(document, ()=>{ alertify.alert(gettext('Timeout'), gettext('Your session has timed out due to inactivity. Please close the window and login again.')); }); + + self.render_connection(self.connection_list); }, /* Regarding SlickGrid usage in render_grid function. @@ -1607,6 +1644,17 @@ define('tools.querytool', [ ); }, + on_new_connection: function() { + var self = this; + + // Trigger the show_filter signal to the SqlEditorController class + self.handler.trigger( + 'pgadmin-sqleditor:button:show_new_connection', + self, + self.handler + ); + }, + // Callback function for include filter button click. on_include_filter: function(ev) { var self = this; @@ -2070,6 +2118,83 @@ define('tools.querytool', [ queryToolActions.executeMacro(this.handler, macroId); }, + on_change_connection: function(connection_details, ref) { + let title = this.$el.find('.editor-title').html(); + if(connection_details['title'] != title) { + var self = this; + $.ajax({ + async: false, + url: url_for('datagrid.update_query_tool_connection', { + 'trans_id': self.transId, + 'sgid': connection_details['server_group'], + 'sid': connection_details['server'], + 'did': connection_details['database'], + }), + method: 'POST', + contentType: 'application/json', + data: JSON.stringify(connection_details), + }) + .done(function(res) { + if(res.success) { + self.transId = res.data.tran_id; + self.handler.transId = res.data.tran_id; + self.handler.url_params = { + 'did': connection_details['database'], + 'is_query_tool': self.handler.url_params.is_query_tool, + 'server_type': self.handler.url_params.server_type, + 'sgid': connection_details['server_group'], + 'sid': connection_details['server'], + 'title': connection_details['title'], + }; + self.set_editor_title(self.handler.url_params.title); + self.handler.setTitle(self.handler.url_params.title); + alertify.success('connected successfully'); + if(ref){ + let connection_data = { + 'server_group': self.handler.url_params.sgid, + 'server': connection_details['server'], + 'database': connection_details['database'], + 'user': connection_details['user'], + 'title': connection_details['title'], + 'role': connection_details['role'], + 'password': connection_details['password'], + 'is_allow_new_connection': true, + }; + self.connection_list.unshift(connection_data); + self.render_connection(self.connection_list); + ref.close(); + } + } + return true; + }) + .fail(function(xhr) { + if(xhr.status == 428) { + alertify.connectServer('Connect to server', xhr.responseJSON.result, connection_details['server'], false); + } else { + alertify.error(xhr.responseJSON['errormsg']); + } + /*let url = url_for('sqleditor.connect_server_with_user', { + 'sid': newConnCollectionModel['server'], + 'usr': newConnCollectionModel['user'] + }); + $.ajax({ + async: false, + url: url, + headers: { + 'Cache-Control' : 'no-cache', + }, + }).done(function () { + Backform.Select2Control.prototype.onChange.apply(self, arguments); + response.server_list.forEach(function(obj){ + if(obj.id==self.model.changed.server) { + response.server_name = obj.name; + } + }); + }).fail(function(xhr){});*/ + + }); + } + }, }); @@ -2393,6 +2518,17 @@ define('tools.querytool', [ $('#btn-conn-status i').removeClass('obtaining-conn'); self.gridView.set_editor_title(_.unescape(url_params.title)); + let connection_data = { + 'server_group': self.gridView.handler.url_params.sgid, + 'server': self.gridView.handler.url_params.sid, + 'database': self.gridView.handler.url_params.did, + 'user': null, + 'role': null, + 'title': _.unescape(url_params.title), + 'is_allow_new_connection': false, + }; + self.gridView.connection_list.unshift(connection_data); + self.gridView.render_connection(self.gridView.connection_list); }; pgBrowser.Events.on('pgadmin:query_tool:connected:' + transId, afterConn); @@ -2487,6 +2623,7 @@ define('tools.querytool', [ self.on('pgadmin-sqleditor:button:save_file', self._save_file, self); self.on('pgadmin-sqleditor:button:deleterow', self._delete, self); self.on('pgadmin-sqleditor:button:show_filter', self._show_filter, self); + self.on('pgadmin-sqleditor:button:show_new_connection', self._show_new_connection, self); self.on('pgadmin-sqleditor:button:include_filter', self._include_filter, self); self.on('pgadmin-sqleditor:button:exclude_filter', self._exclude_filter, self); self.on('pgadmin-sqleditor:button:remove_filter', self._remove_filter, self); @@ -3696,7 +3833,6 @@ define('tools.querytool', [ } }; }, - // This function will show the filter in the text area. _show_filter: function() { let self = this, @@ -3711,7 +3847,19 @@ define('tools.querytool', [ } FilterHandler.dialog(self, reconnect); }, + // This function will show the new connection. + _show_new_connection: function() { + let self = this, + reconnect = false; + /* When server is disconnected and connected, connection is lost, + * To reconnect pass true + */ + if (arguments.length > 0 && arguments[arguments.length - 1] == 'connect') { + reconnect = true; + } + newConnectionHandler.dialog(self, reconnect); + }, // This function will include the filter by selection. _include_filter: function() { var self = this, diff --git a/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss b/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss index fd1e5d3..53f2449 100644 --- a/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss +++ b/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss @@ -30,6 +30,19 @@ color: $sql-title-fg; } +.connection-info { + background: $sql-title-bg; + color: $sql-title-fg; + width:100%; + display: inherit; +} + +.conn-info-dd { + padding-top: 0.3em; + padding-left: 0.2em; + cursor: pointer; +} + #editor-panel { z-index: 0; diff --git a/web/pgadmin/tools/sqleditor/tests/test_new_connection_database.py b/web/pgadmin/tools/sqleditor/tests/test_new_connection_database.py new file mode 100644 index 0000000..e799095 --- /dev/null +++ b/web/pgadmin/tools/sqleditor/tests/test_new_connection_database.py @@ -0,0 +1,100 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.test_setup import config_data +from regression.python_test_utils import test_utils as utils + + +class TestNewConnectionDatabase(BaseTestGenerator): + """ This class will test new connection database. """ + API_URL = "/sqleditor/new_connection_database/" + scenarios = [ + ('New connection dialog', + dict( + url=API_URL, + is_positive_test=True, + mocking_required=False, + is_server_conn_required=False, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ('New connection dialog connect server', + dict( + url=API_URL, + is_positive_test=True, + mocking_required=False, + is_server_conn_required=True, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ('New connection dialog negative', + dict( + url=API_URL, + is_positive_test=False, + mocking_required=False, + is_server_conn_required=True, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ] + + def setUp(self): + self.content_type = 'html/json' + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + def get_database(self): + response = self.tester.get( + self.url + str(self.sgid) + '/' + str(self.sid), + content_type=self.content_type + ) + + return response + + def runTest(self): + if self.is_positive_test: + if self.is_server_conn_required: + self.server['password'] = self.server['db_password'] + self.tester.post( + '/browser/server/connect/{0}/{1}'.format( + utils.SERVER_GROUP, + self.sid), + data=json.dumps(self.server), + content_type=self.content_type + ) + response = self.get_database() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + if self.is_server_conn_required: + self.server['password'] = self.server['db_password'] + self.tester.post( + '/browser/server/connect/{0}/{1}'.format( + utils.SERVER_GROUP, + self.sid), + data=json.dumps(self.server), + content_type=self.content_type + ) + self.sid = 0 + response = self.get_database() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + self.assertEqual(actual_response_code, expected_response_code) diff --git a/web/pgadmin/tools/sqleditor/tests/test_new_connection_dialog.py b/web/pgadmin/tools/sqleditor/tests/test_new_connection_dialog.py new file mode 100644 index 0000000..75a47ef --- /dev/null +++ b/web/pgadmin/tools/sqleditor/tests/test_new_connection_dialog.py @@ -0,0 +1,50 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## +import json +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.test_setup import config_data +from regression.python_test_utils import test_utils as utils + + +class TestNewConnectionDialog(BaseTestGenerator): + """ This class will test new connection dialog. """ + scenarios = [ + ('New connection dialog', + dict( + url="/sqleditor/new_connection_dialog/", + is_positive_test=True, + mocking_required=False, + is_connect_server=False, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ] + + def setUp(self): + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + def new_connection(self): + response = self.tester.get( + self.url + str(self.sgid) + '/' + str(self.sgid), + content_type='html/json' + ) + + return response + + def runTest(self): + if self.is_positive_test: + response = self.new_connection() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + self.assertEqual(actual_response_code, expected_response_code) diff --git a/web/pgadmin/tools/sqleditor/tests/test_new_connection_user.py b/web/pgadmin/tools/sqleditor/tests/test_new_connection_user.py new file mode 100644 index 0000000..7c69bbd --- /dev/null +++ b/web/pgadmin/tools/sqleditor/tests/test_new_connection_user.py @@ -0,0 +1,100 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.test_setup import config_data +from regression.python_test_utils import test_utils as utils + + +class TestNewConnectionUser(BaseTestGenerator): + """ This class will test new connection user. """ + API_URL = '/sqleditor/new_connection_user/' + scenarios = [ + ('New connection dialog', + dict( + url=API_URL, + is_positive_test=True, + mocking_required=False, + is_server_conn_required=False, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ('New connection dialog connect server', + dict( + url=API_URL, + is_positive_test=True, + mocking_required=False, + is_server_conn_required=True, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ('New connection dialog negative', + dict( + url=API_URL, + is_positive_test=False, + mocking_required=False, + is_server_conn_required=True, + test_data={}, + mock_data={}, + expected_data={ + "status_code": 200 + } + )), + ] + + def setUp(self): + self.content_type = 'html/json' + self.sid = parent_node_dict["server"][-1]["server_id"] + self.sgid = config_data['server_group'] + + def get_use(self): + response = self.tester.get( + self.url + str(self.sgid) + '/' + str(self.sid), + content_type=self.content_type + ) + + return response + + def runTest(self): + if self.is_positive_test: + if self.is_server_conn_required: + self.server['password'] = self.server['db_password'] + self.tester.post( + '/browser/server/connect/{0}/{1}'.format( + utils.SERVER_GROUP, + self.sid), + data=json.dumps(self.server), + content_type=self.content_type + ) + response = self.get_use() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + else: + if self.is_server_conn_required: + self.server['password'] = self.server['db_password'] + self.tester.post( + '/browser/server/connect/{0}/{1}'.format( + utils.SERVER_GROUP, + self.sid), + data=json.dumps(self.server), + content_type='html/json' + ) + self.sid = 0 + response = self.get_use() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + self.assertEqual(actual_response_code, expected_response_code) diff --git a/web/pgadmin/utils/constants.py b/web/pgadmin/utils/constants.py index 010185f..278881b 100644 --- a/web/pgadmin/utils/constants.py +++ b/web/pgadmin/utils/constants.py @@ -44,3 +44,5 @@ ERROR_MSG_TRANS_ID_NOT_FOUND = gettext( # Role module constant ERROR_FETCHING_ROLE_INFORMATION = gettext( 'Error fetching role information from the database server.') + +ERROR_FETCHING_DATA = gettext('Unable to fetch data.') diff --git a/web/pgadmin/utils/driver/psycopg2/connection.py b/web/pgadmin/utils/driver/psycopg2/connection.py index 5295c51..4f40e65 100644 --- a/web/pgadmin/utils/driver/psycopg2/connection.py +++ b/web/pgadmin/utils/driver/psycopg2/connection.py @@ -21,7 +21,7 @@ import psycopg2 from flask import g, current_app from flask_babelex import gettext from flask_security import current_user -from pgadmin.utils.crypto import decrypt +from pgadmin.utils.crypto import decrypt, encrypt from psycopg2.extensions import encodings import config @@ -204,6 +204,45 @@ class Connection(BaseConnection): def __str__(self): return self.__repr__() + def _check_user_password(self, kwargs): + """ + Check user and password. + """ + password = None + encpass = None + is_update_password = True + + if 'user' in kwargs and kwargs['password']: + password = kwargs['password'] + kwargs.pop('password') + is_update_password = False + else: + encpass = kwargs['password'] if 'password' in kwargs else None + + return password, encpass, is_update_password + + def _decode_password(self, encpass, manager, password, crypt_key): + if encpass: + # Fetch Logged in User Details. + user = User.query.filter_by(id=current_user.id).first() + + if user is None: + return True, self.UNAUTHORIZED_REQUEST, password + + try: + password = decrypt(encpass, crypt_key) + # password is in bytes, for python3 we need it in string + if isinstance(password, bytes): + password = password.decode() + except Exception as e: + manager.stop_ssh_tunnel() + current_app.logger.exception(e) + return True, \ + _( + "Failed to decrypt the saved password.\nError: {0}" + ).format(str(e)) + return False, '', password + def connect(self, **kwargs): if self.conn: if self.conn.closed: @@ -212,11 +251,13 @@ class Connection(BaseConnection): return True, None pg_conn = None - password = None passfile = None manager = self.manager + crypt_key_present, crypt_key = get_crypt_key() + + password, encpass, is_update_password = self._check_user_password( + kwargs) - encpass = kwargs['password'] if 'password' in kwargs else None passfile = kwargs['passfile'] if 'passfile' in kwargs else None tunnel_password = kwargs['tunnel_password'] if 'tunnel_password' in \ kwargs else '' @@ -231,38 +272,23 @@ class Connection(BaseConnection): if manager.use_ssh_tunnel == 1: manager.check_ssh_tunnel_alive() - if encpass is None: - encpass = self.password or getattr(manager, 'password', None) + if is_update_password: + if encpass is None: + encpass = self.password or getattr(manager, 'password', None) - self.password = encpass + self.password = encpass # Reset the existing connection password if self.reconnecting is not False: self.password = None - crypt_key_present, crypt_key = get_crypt_key() if not crypt_key_present: raise CryptKeyMissing() - if encpass: - # Fetch Logged in User Details. - user = User.query.filter_by(id=current_user.id).first() - - if user is None: - return False, self.UNAUTHORIZED_REQUEST - - try: - password = decrypt(encpass, crypt_key) - # password is in bytes, for python3 we need it in string - if isinstance(password, bytes): - password = password.decode() - except Exception as e: - manager.stop_ssh_tunnel() - current_app.logger.exception(e) - return False, \ - _( - "Failed to decrypt the saved password.\nError: {0}" - ).format(str(e)) + is_error, errmsg, password = self._decode_password(encpass, manager, + password, crypt_key) + if is_error: + return False, errmsg # If no password credential is found then connect request might # come from Query tool, ViewData grid, debugger etc tools. @@ -273,7 +299,10 @@ class Connection(BaseConnection): try: database = self.db - user = manager.user + if 'user' in kwargs and kwargs['user']: + user = kwargs['user'] + else: + user = manager.user conn_id = self.conn_id import os @@ -342,10 +371,10 @@ class Connection(BaseConnection): self.wasConnected = False raise e - if status: + if status and is_update_password: manager._update_password(encpass) else: - if not self.reconnecting: + if not self.reconnecting and is_update_password: self.wasConnected = False return status, msg @@ -363,7 +392,7 @@ class Connection(BaseConnection): else: self.conn.autocommit = True - def _set_role(self, manager, cur, conn_id): + def _set_role(self, manager, cur, conn_id, **kwargs): """ Set role :param manager: @@ -371,8 +400,18 @@ class Connection(BaseConnection): :param conn_id: :return: """ - if manager.role: - status = self._execute(cur, "SET ROLE TO %s", [manager.role]) + is_set_role = False + role = None + + if 'role' in kwargs and kwargs['role']: + is_set_role = True + role = kwargs['role'] + elif manager.role: + is_set_role = True + role = manager.role + + if is_set_role: + status = self._execute(cur, "SET ROLE TO %s", [role]) if status is not None: self.conn.close() @@ -386,7 +425,7 @@ class Connection(BaseConnection): msg=status ) ) - return False, \ + return True, \ _( "Failed to setup the role with error message:\n{0}" ).format(status) @@ -449,7 +488,7 @@ class Connection(BaseConnection): return False, status - is_error, errmsg = self._set_role(manager, cur, conn_id) + is_error, errmsg = self._set_role(manager, cur, conn_id, **kwargs) if is_error: return False, errmsg @@ -495,7 +534,7 @@ WHERE db.datname = current_database()""") if len(manager.db_info) == 1: manager.did = res['did'] - self._set_user_info(cur, manager) + self._set_user_info(cur, manager, **kwargs) self._set_server_type_and_password(kwargs, manager) @@ -503,7 +542,7 @@ WHERE db.datname = current_database()""") return True, None - def _set_user_info(self, cur, manager): + def _set_user_info(self, cur, manager, **kwargs): """ Set user info. :param cur: @@ -521,7 +560,7 @@ WHERE db.datname = current_database()""") WHERE rolname = current_user""") - if status is None: + if status is None and 'user' not in kwargs: manager.user_info = dict() if cur.rowcount > 0: manager.user_info = cur.fetchmany(1)[0] ^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: [pgAdmin][RM3794]:Allow User to Change Database Connection from an Open Query Tool Tab @ 2020-10-01 08:01 Akshay Joshi <[email protected]> parent: Nikhil Mohite <[email protected]> 0 siblings, 2 replies; 18+ messages in thread From: Akshay Joshi @ 2020-10-01 08:01 UTC (permalink / raw) To: Nikhil Mohite <[email protected]>; +Cc: pgadmin-hackers Thanks, patch applied. On Thu, Oct 1, 2020 at 10:42 AM Nikhil Mohite < [email protected]> wrote: > Hi Akshay, > > I have resolved the sonarQube issues, PFA updated patch for the same. > > > Regards, > Nikhil Mohite. > > > On Tue, Sep 29, 2020 at 11:31 AM Akshay Joshi < > [email protected]> wrote: > >> Hi Nikhil >> >> Your patch introduces 1 new Bug and 13 new code smells, please fix those >> and resend the patch. >> >> On Mon, Sep 28, 2020 at 7:31 PM Nikhil Mohite < >> [email protected]> wrote: >> >>> Hi Akshay, >>> >>> I have resolved code conflict issues and sonarqube issues. >>> PFA updated patch. >>> >>> Regards, >>> Nikhil Mohite. >>> >>> On Mon, Sep 28, 2020 at 5:58 PM Akshay Joshi < >>> [email protected]> wrote: >>> >>>> Hi Nikhil >>>> >>>> The patch is not applying, rebase, and send it again. Please check your >>>> code should not create any new SonarQube issues. >>>> >>>> On Mon, Sep 28, 2020 at 11:20 AM Nikhil Mohite < >>>> [email protected]> wrote: >>>> >>>>> Hi Akshay, >>>>> >>>>> I have resolved all the review comments and also updated the test >>>>> cases as per the new implementation. >>>>> >>>>> PFA updated patch. >>>>> >>>>> >>>>> >>>>> On Mon, Sep 21, 2020 at 5:24 PM Akshay Joshi < >>>>> [email protected]> wrote: >>>>> >>>>>> Hi Nikhil >>>>>> >>>>>> Following are the initial review comments: >>>>>> >>>>>> - Open View/Edit data on any table and click on the same database >>>>>> connection and then click on the Execute button. Got "get_primary_keys() >>>>>> takes 1 positional argument but 2 were given" error. >>>>>> - In my opinion, we should hide the option to change the database >>>>>> connection for View/Edit Data. >>>>>> - If the user clicks on the same database connection multiple >>>>>> times then no need to change the backend connection and transaction id. Add >>>>>> validation at the backend, no action required in this case. >>>>>> - The role option is missing from the "connect to server" dialog. >>>>>> - The Password field should not be there on the "connect to >>>>>> server" dialog. Sometimes we saved the password so asking a password every >>>>>> time is not correct. Check the pgAdmin 3 behavior. >>>>>> >>>>>> Code review still remains. >>>>>> >>>>>> On Thu, Sep 17, 2020 at 4:15 PM Nikhil Mohite < >>>>>> [email protected]> wrote: >>>>>> >>>>>>> Hi Team, >>>>>>> >>>>>>> Regarding RM-3794 <https://redmine.postgresql.org/issues/3794; >>>>>>> allow the user to change the database connection from an open query tool: >>>>>>> I have implemented the feature and also added documentation for it. >>>>>>> >>>>>>> PFA patch. >>>>>>> >>>>>>> -- >>>>>>> *Thanks & Regards,* >>>>>>> *Nikhil Mohite* >>>>>>> *Software Engineer.* >>>>>>> *EDB Postgres* <https://www.enterprisedb.com/; >>>>>>> *Mob.No: +91-7798364578.* >>>>>>> >>>>>> >>>>>> >>>>>> -- >>>>>> *Thanks & Regards* >>>>>> *Akshay Joshi* >>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>> >>>>>> *Mobile: +91 976-788-8246* >>>>>> >>>>> >>>> >>>> -- >>>> *Thanks & Regards* >>>> *Akshay Joshi* >>>> *pgAdmin Hacker | Sr. Software Architect* >>>> *EDB Postgres <http://edbpostgres.com>* >>>> >>>> *Mobile: +91 976-788-8246* >>>> >>> >> >> -- >> *Thanks & Regards* >> *Akshay Joshi* >> *pgAdmin Hacker | Sr. Software Architect* >> *EDB Postgres <http://edbpostgres.com>* >> >> *Mobile: +91 976-788-8246* >> > -- *Thanks & Regards* *Akshay Joshi* *pgAdmin Hacker | Sr. Software Architect* *EDB Postgres <http://edbpostgres.com>* *Mobile: +91 976-788-8246* ^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: [pgAdmin][RM3794]:Allow User to Change Database Connection from an Open Query Tool Tab @ 2020-10-06 08:51 Nikhil Mohite <[email protected]> parent: Akshay Joshi <[email protected]> 1 sibling, 0 replies; 18+ messages in thread From: Nikhil Mohite @ 2020-10-06 08:51 UTC (permalink / raw) To: Akshay Joshi <[email protected]>; +Cc: pgadmin-hackers Hi Akshay, I have updated the following points as per suggestions: 1. Updated connection success message. (Added database name in the success message.) 2. Resolve issue of the mouse pointer and dropdown will show below the connection string only. 3. Added loader for both new connections and load existing connections. 4. Removed async: false for update connection. Regards, Nikhil Mohite. On Thu, Oct 1, 2020 at 1:31 PM Akshay Joshi <[email protected]> wrote: > Thanks, patch applied. > > On Thu, Oct 1, 2020 at 10:42 AM Nikhil Mohite < > [email protected]> wrote: > >> Hi Akshay, >> >> I have resolved the sonarQube issues, PFA updated patch for the same. >> >> >> Regards, >> Nikhil Mohite. >> >> >> On Tue, Sep 29, 2020 at 11:31 AM Akshay Joshi < >> [email protected]> wrote: >> >>> Hi Nikhil >>> >>> Your patch introduces 1 new Bug and 13 new code smells, please fix those >>> and resend the patch. >>> >>> On Mon, Sep 28, 2020 at 7:31 PM Nikhil Mohite < >>> [email protected]> wrote: >>> >>>> Hi Akshay, >>>> >>>> I have resolved code conflict issues and sonarqube issues. >>>> PFA updated patch. >>>> >>>> Regards, >>>> Nikhil Mohite. >>>> >>>> On Mon, Sep 28, 2020 at 5:58 PM Akshay Joshi < >>>> [email protected]> wrote: >>>> >>>>> Hi Nikhil >>>>> >>>>> The patch is not applying, rebase, and send it again. Please check >>>>> your code should not create any new SonarQube issues. >>>>> >>>>> On Mon, Sep 28, 2020 at 11:20 AM Nikhil Mohite < >>>>> [email protected]> wrote: >>>>> >>>>>> Hi Akshay, >>>>>> >>>>>> I have resolved all the review comments and also updated the test >>>>>> cases as per the new implementation. >>>>>> >>>>>> PFA updated patch. >>>>>> >>>>>> >>>>>> >>>>>> On Mon, Sep 21, 2020 at 5:24 PM Akshay Joshi < >>>>>> [email protected]> wrote: >>>>>> >>>>>>> Hi Nikhil >>>>>>> >>>>>>> Following are the initial review comments: >>>>>>> >>>>>>> - Open View/Edit data on any table and click on the same >>>>>>> database connection and then click on the Execute button. Got >>>>>>> "get_primary_keys() takes 1 positional argument but 2 were given" error. >>>>>>> - In my opinion, we should hide the option to change the >>>>>>> database connection for View/Edit Data. >>>>>>> - If the user clicks on the same database connection multiple >>>>>>> times then no need to change the backend connection and transaction id. Add >>>>>>> validation at the backend, no action required in this case. >>>>>>> - The role option is missing from the "connect to server" dialog. >>>>>>> - The Password field should not be there on the "connect to >>>>>>> server" dialog. Sometimes we saved the password so asking a password every >>>>>>> time is not correct. Check the pgAdmin 3 behavior. >>>>>>> >>>>>>> Code review still remains. >>>>>>> >>>>>>> On Thu, Sep 17, 2020 at 4:15 PM Nikhil Mohite < >>>>>>> [email protected]> wrote: >>>>>>> >>>>>>>> Hi Team, >>>>>>>> >>>>>>>> Regarding RM-3794 <https://redmine.postgresql.org/issues/3794; >>>>>>>> allow the user to change the database connection from an open query tool: >>>>>>>> I have implemented the feature and also added documentation for it. >>>>>>>> >>>>>>>> PFA patch. >>>>>>>> >>>>>>>> -- >>>>>>>> *Thanks & Regards,* >>>>>>>> *Nikhil Mohite* >>>>>>>> *Software Engineer.* >>>>>>>> *EDB Postgres* <https://www.enterprisedb.com/; >>>>>>>> *Mob.No: +91-7798364578.* >>>>>>>> >>>>>>> >>>>>>> >>>>>>> -- >>>>>>> *Thanks & Regards* >>>>>>> *Akshay Joshi* >>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>> >>>>>>> *Mobile: +91 976-788-8246* >>>>>>> >>>>>> >>>>> >>>>> -- >>>>> *Thanks & Regards* >>>>> *Akshay Joshi* >>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>> *EDB Postgres <http://edbpostgres.com>* >>>>> >>>>> *Mobile: +91 976-788-8246* >>>>> >>>> >>> >>> -- >>> *Thanks & Regards* >>> *Akshay Joshi* >>> *pgAdmin Hacker | Sr. Software Architect* >>> *EDB Postgres <http://edbpostgres.com>* >>> >>> *Mobile: +91 976-788-8246* >>> >> > > -- > *Thanks & Regards* > *Akshay Joshi* > *pgAdmin Hacker | Sr. Software Architect* > *EDB Postgres <http://edbpostgres.com>* > > *Mobile: +91 976-788-8246* > Attachments: [application/octet-stream] RM_3794_V6.patch (6.0K, 3-RM_3794_V6.patch) download | inline diff: diff --git a/web/pgadmin/static/js/sqleditor/new_connection_dialog.js b/web/pgadmin/static/js/sqleditor/new_connection_dialog.js index 3fcd374..a674f2c 100644 --- a/web/pgadmin/static/js/sqleditor/new_connection_dialog.js +++ b/web/pgadmin/static/js/sqleditor/new_connection_dialog.js @@ -230,6 +230,8 @@ let NewConnectionDialog = { 'user': newConnCollectionModel['user'], 'role': newConnCollectionModel['role'], 'password': response.password, + 'server_name': response.server_name, + 'database_name': selected_database_name, }; handler.gridView.on_change_connection(connection_details, self); } diff --git a/web/pgadmin/tools/datagrid/templates/datagrid/index.html b/web/pgadmin/tools/datagrid/templates/datagrid/index.html index 4970027..feedc63 100644 --- a/web/pgadmin/tools/datagrid/templates/datagrid/index.html +++ b/web/pgadmin/tools/datagrid/templates/datagrid/index.html @@ -418,11 +418,13 @@ </i> </div> <div class="connection-info btn-group mr-1" role="group" aria-label=""> - <div class="editor-title" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" - style="background-color: {% if fgcolor %}{{ bgcolor or '#FFFFFF' }}{% endif %}; color: {% if fgcolor %}{{ fgcolor }}{% endif %};"> + <div class="connection-data" data-toggle="dropdown"> + <div class="editor-title" aria-haspopup="true" aria-expanded="false" + style="background-color: {% if fgcolor %}{{ bgcolor or '#FFFFFF' }}{% endif %}; color: {% if fgcolor %}{{ fgcolor }}{% endif %};"> + </div> + <span class="conn-info-dd dropdown-toggle dropdown-toggle-split" + aria-haspopup="true" aria-expanded="false"></span> </div> - <span class="conn-info-dd dropdown-toggle dropdown-toggle-split" - data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span> <ul class="dropdown-menu" id="connections-list"> </ul> </div> diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js index 4ef4b8f..600a644 100644 --- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js +++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js @@ -182,7 +182,7 @@ define('tools.querytool', [ }); } else { $('.conn-info-dd').hide(); - $('.editor-title').css({pointerEvents: 'none'}); + $('.connection-data').css({pointerEvents: 'none', cursor: 'arrow'}); } }, @@ -2122,8 +2122,20 @@ define('tools.querytool', [ let title = this.$el.find('.editor-title').html(); if(connection_details['title'] != title) { var self = this; + var loadingDiv = null; + var msgDiv = null; + if(ref){ + loadingDiv = $('#show_filter_progress'); + loadingDiv.removeClass('d-none'); + msgDiv = loadingDiv.find('.sql-editor-busy-text'); + msgDiv.text('Connecting to database...'); + } else{ + loadingDiv = $('#fetching_data'); + loadingDiv.removeClass('d-none'); + msgDiv = loadingDiv.find('.sql-editor-busy-text'); + } + $.ajax({ - async: false, url: url_for('datagrid.update_query_tool_connection', { 'trans_id': self.transId, 'sgid': connection_details['server_group'], @@ -2148,7 +2160,8 @@ define('tools.querytool', [ }; self.set_editor_title(self.handler.url_params.title); self.handler.setTitle(self.handler.url_params.title); - alertify.success('connected successfully'); + let success_msg = connection_details['server_name'] + '/' + connection_details['database_name']+ '- Database connected'; + alertify.success(success_msg); if(ref){ let connection_data = { 'server_group': self.handler.url_params.sgid, @@ -2159,15 +2172,21 @@ define('tools.querytool', [ 'role': connection_details['role'], 'password': connection_details['password'], 'is_allow_new_connection': true, + 'database_name': connection_details['database_name'], + 'server_name': connection_details['server_name'], }; self.connection_list.unshift(connection_data); self.render_connection(self.connection_list); + loadingDiv.addClass('d-none'); ref.close(); + } else { + loadingDiv.addClass('d-none'); } } return true; }) .fail(function(xhr) { + loadingDiv.addClass('d-none'); if(xhr.status == 428) { alertify.connectServer('Connect to server', xhr.responseJSON.result, connection_details['server'], false); } else { @@ -2526,6 +2545,8 @@ define('tools.querytool', [ 'role': null, 'title': _.unescape(url_params.title), 'is_allow_new_connection': false, + 'database_name': url_params.title.split('/')[0], + 'server_name': url_params.title.split('@')[1], }; self.gridView.connection_list.unshift(connection_data); self.gridView.render_connection(self.gridView.connection_list); diff --git a/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss b/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss index 53f2449..7fc576a 100644 --- a/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss +++ b/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss @@ -43,6 +43,12 @@ cursor: pointer; } +.connection-data { + display: inherit; + cursor: pointer; + width: auto; +} + #editor-panel { z-index: 0; ^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: [pgAdmin][RM3794]:Allow User to Change Database Connection from an Open Query Tool Tab @ 2020-10-06 11:51 Murtuza Zabuawala <[email protected]> parent: Akshay Joshi <[email protected]> 1 sibling, 1 reply; 18+ messages in thread From: Murtuza Zabuawala @ 2020-10-06 11:51 UTC (permalink / raw) To: Akshay Joshi <[email protected]>; Dave Page <[email protected]>; +Cc: pgadmin-hackers Hi Akshay, We have used aysnc=False in most ajax calls with this feature, It is causing UI hang in case of slow server response. You can try adding a time.sleep() call at the python side response and check the UI hang, I think we should avoid sync calls as much as possible. -- Regards, Murtuza Zabuawala *EDB* *POWER TO POSTGRES* https://www.edbpostgres.com On Thu, Oct 1, 2020 at 1:31 PM Akshay Joshi <[email protected]> wrote: > Thanks, patch applied. > > On Thu, Oct 1, 2020 at 10:42 AM Nikhil Mohite < > [email protected]> wrote: > >> Hi Akshay, >> >> I have resolved the sonarQube issues, PFA updated patch for the same. >> >> >> Regards, >> Nikhil Mohite. >> >> >> On Tue, Sep 29, 2020 at 11:31 AM Akshay Joshi < >> [email protected]> wrote: >> >>> Hi Nikhil >>> >>> Your patch introduces 1 new Bug and 13 new code smells, please fix those >>> and resend the patch. >>> >>> On Mon, Sep 28, 2020 at 7:31 PM Nikhil Mohite < >>> [email protected]> wrote: >>> >>>> Hi Akshay, >>>> >>>> I have resolved code conflict issues and sonarqube issues. >>>> PFA updated patch. >>>> >>>> Regards, >>>> Nikhil Mohite. >>>> >>>> On Mon, Sep 28, 2020 at 5:58 PM Akshay Joshi < >>>> [email protected]> wrote: >>>> >>>>> Hi Nikhil >>>>> >>>>> The patch is not applying, rebase, and send it again. Please check >>>>> your code should not create any new SonarQube issues. >>>>> >>>>> On Mon, Sep 28, 2020 at 11:20 AM Nikhil Mohite < >>>>> [email protected]> wrote: >>>>> >>>>>> Hi Akshay, >>>>>> >>>>>> I have resolved all the review comments and also updated the test >>>>>> cases as per the new implementation. >>>>>> >>>>>> PFA updated patch. >>>>>> >>>>>> >>>>>> >>>>>> On Mon, Sep 21, 2020 at 5:24 PM Akshay Joshi < >>>>>> [email protected]> wrote: >>>>>> >>>>>>> Hi Nikhil >>>>>>> >>>>>>> Following are the initial review comments: >>>>>>> >>>>>>> - Open View/Edit data on any table and click on the same >>>>>>> database connection and then click on the Execute button. Got >>>>>>> "get_primary_keys() takes 1 positional argument but 2 were given" error. >>>>>>> - In my opinion, we should hide the option to change the >>>>>>> database connection for View/Edit Data. >>>>>>> - If the user clicks on the same database connection multiple >>>>>>> times then no need to change the backend connection and transaction id. Add >>>>>>> validation at the backend, no action required in this case. >>>>>>> - The role option is missing from the "connect to server" dialog. >>>>>>> - The Password field should not be there on the "connect to >>>>>>> server" dialog. Sometimes we saved the password so asking a password every >>>>>>> time is not correct. Check the pgAdmin 3 behavior. >>>>>>> >>>>>>> Code review still remains. >>>>>>> >>>>>>> On Thu, Sep 17, 2020 at 4:15 PM Nikhil Mohite < >>>>>>> [email protected]> wrote: >>>>>>> >>>>>>>> Hi Team, >>>>>>>> >>>>>>>> Regarding RM-3794 <https://redmine.postgresql.org/issues/3794; >>>>>>>> allow the user to change the database connection from an open query tool: >>>>>>>> I have implemented the feature and also added documentation for it. >>>>>>>> >>>>>>>> PFA patch. >>>>>>>> >>>>>>>> -- >>>>>>>> *Thanks & Regards,* >>>>>>>> *Nikhil Mohite* >>>>>>>> *Software Engineer.* >>>>>>>> *EDB Postgres* <https://www.enterprisedb.com/; >>>>>>>> *Mob.No: +91-7798364578.* >>>>>>>> >>>>>>> >>>>>>> >>>>>>> -- >>>>>>> *Thanks & Regards* >>>>>>> *Akshay Joshi* >>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>> >>>>>>> *Mobile: +91 976-788-8246* >>>>>>> >>>>>> >>>>> >>>>> -- >>>>> *Thanks & Regards* >>>>> *Akshay Joshi* >>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>> *EDB Postgres <http://edbpostgres.com>* >>>>> >>>>> *Mobile: +91 976-788-8246* >>>>> >>>> >>> >>> -- >>> *Thanks & Regards* >>> *Akshay Joshi* >>> *pgAdmin Hacker | Sr. Software Architect* >>> *EDB Postgres <http://edbpostgres.com>* >>> >>> *Mobile: +91 976-788-8246* >>> >> > > -- > *Thanks & Regards* > *Akshay Joshi* > *pgAdmin Hacker | Sr. Software Architect* > *EDB Postgres <http://edbpostgres.com>* > > *Mobile: +91 976-788-8246* > ^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: [pgAdmin][RM3794]:Allow User to Change Database Connection from an Open Query Tool Tab @ 2020-10-06 11:54 Dave Page <[email protected]> parent: Murtuza Zabuawala <[email protected]> 0 siblings, 1 reply; 18+ messages in thread From: Dave Page @ 2020-10-06 11:54 UTC (permalink / raw) To: Murtuza Zabuawala <[email protected]>; +Cc: Akshay Joshi <[email protected]>; pgadmin-hackers On Tue, Oct 6, 2020 at 12:51 PM Murtuza Zabuawala < [email protected]> wrote: > Hi Akshay, > > We have used aysnc=False in most ajax calls with this feature, It is > causing UI hang in case of slow server response. > You can try adding a time.sleep() call at the python side response and > check the UI hang, I think we should avoid sync calls as much as possible. > I consider a sync ajax call to be a bug. > > > -- > Regards, > Murtuza Zabuawala > *EDB* > *POWER TO POSTGRES* > https://www.edbpostgres.com > > > On Thu, Oct 1, 2020 at 1:31 PM Akshay Joshi <[email protected]> > wrote: > >> Thanks, patch applied. >> >> On Thu, Oct 1, 2020 at 10:42 AM Nikhil Mohite < >> [email protected]> wrote: >> >>> Hi Akshay, >>> >>> I have resolved the sonarQube issues, PFA updated patch for the same. >>> >>> >>> Regards, >>> Nikhil Mohite. >>> >>> >>> On Tue, Sep 29, 2020 at 11:31 AM Akshay Joshi < >>> [email protected]> wrote: >>> >>>> Hi Nikhil >>>> >>>> Your patch introduces 1 new Bug and 13 new code smells, please fix >>>> those and resend the patch. >>>> >>>> On Mon, Sep 28, 2020 at 7:31 PM Nikhil Mohite < >>>> [email protected]> wrote: >>>> >>>>> Hi Akshay, >>>>> >>>>> I have resolved code conflict issues and sonarqube issues. >>>>> PFA updated patch. >>>>> >>>>> Regards, >>>>> Nikhil Mohite. >>>>> >>>>> On Mon, Sep 28, 2020 at 5:58 PM Akshay Joshi < >>>>> [email protected]> wrote: >>>>> >>>>>> Hi Nikhil >>>>>> >>>>>> The patch is not applying, rebase, and send it again. Please check >>>>>> your code should not create any new SonarQube issues. >>>>>> >>>>>> On Mon, Sep 28, 2020 at 11:20 AM Nikhil Mohite < >>>>>> [email protected]> wrote: >>>>>> >>>>>>> Hi Akshay, >>>>>>> >>>>>>> I have resolved all the review comments and also updated the test >>>>>>> cases as per the new implementation. >>>>>>> >>>>>>> PFA updated patch. >>>>>>> >>>>>>> >>>>>>> >>>>>>> On Mon, Sep 21, 2020 at 5:24 PM Akshay Joshi < >>>>>>> [email protected]> wrote: >>>>>>> >>>>>>>> Hi Nikhil >>>>>>>> >>>>>>>> Following are the initial review comments: >>>>>>>> >>>>>>>> - Open View/Edit data on any table and click on the same >>>>>>>> database connection and then click on the Execute button. Got >>>>>>>> "get_primary_keys() takes 1 positional argument but 2 were given" error. >>>>>>>> - In my opinion, we should hide the option to change the >>>>>>>> database connection for View/Edit Data. >>>>>>>> - If the user clicks on the same database connection multiple >>>>>>>> times then no need to change the backend connection and transaction id. Add >>>>>>>> validation at the backend, no action required in this case. >>>>>>>> - The role option is missing from the "connect to server" >>>>>>>> dialog. >>>>>>>> - The Password field should not be there on the "connect to >>>>>>>> server" dialog. Sometimes we saved the password so asking a password every >>>>>>>> time is not correct. Check the pgAdmin 3 behavior. >>>>>>>> >>>>>>>> Code review still remains. >>>>>>>> >>>>>>>> On Thu, Sep 17, 2020 at 4:15 PM Nikhil Mohite < >>>>>>>> [email protected]> wrote: >>>>>>>> >>>>>>>>> Hi Team, >>>>>>>>> >>>>>>>>> Regarding RM-3794 <https://redmine.postgresql.org/issues/3794; >>>>>>>>> allow the user to change the database connection from an open query tool: >>>>>>>>> I have implemented the feature and also added documentation for it. >>>>>>>>> >>>>>>>>> PFA patch. >>>>>>>>> >>>>>>>>> -- >>>>>>>>> *Thanks & Regards,* >>>>>>>>> *Nikhil Mohite* >>>>>>>>> *Software Engineer.* >>>>>>>>> *EDB Postgres* <https://www.enterprisedb.com/; >>>>>>>>> *Mob.No: +91-7798364578.* >>>>>>>>> >>>>>>>> >>>>>>>> >>>>>>>> -- >>>>>>>> *Thanks & Regards* >>>>>>>> *Akshay Joshi* >>>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>>> >>>>>>>> *Mobile: +91 976-788-8246* >>>>>>>> >>>>>>> >>>>>> >>>>>> -- >>>>>> *Thanks & Regards* >>>>>> *Akshay Joshi* >>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>> >>>>>> *Mobile: +91 976-788-8246* >>>>>> >>>>> >>>> >>>> -- >>>> *Thanks & Regards* >>>> *Akshay Joshi* >>>> *pgAdmin Hacker | Sr. Software Architect* >>>> *EDB Postgres <http://edbpostgres.com>* >>>> >>>> *Mobile: +91 976-788-8246* >>>> >>> >> >> -- >> *Thanks & Regards* >> *Akshay Joshi* >> *pgAdmin Hacker | Sr. Software Architect* >> *EDB Postgres <http://edbpostgres.com>* >> >> *Mobile: +91 976-788-8246* >> > -- Dave Page VP & Chief Architect, Database Infrastructure EDB: http://www.enterprisedb.com Blog: http://pgsnake.blogspot.com Twitter: @pgsnake ^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: [pgAdmin][RM3794]:Allow User to Change Database Connection from an Open Query Tool Tab @ 2020-10-06 12:49 Akshay Joshi <[email protected]> parent: Dave Page <[email protected]> 0 siblings, 1 reply; 18+ messages in thread From: Akshay Joshi @ 2020-10-06 12:49 UTC (permalink / raw) To: Nikhil Mohite <[email protected]>; +Cc: Murtuza Zabuawala <[email protected]>; pgadmin-hackers; Dave Page <[email protected]> Hi Nikhil Please verify and remove async = false wherever possible. On Tue, Oct 6, 2020 at 5:24 PM Dave Page <[email protected]> wrote: > > > On Tue, Oct 6, 2020 at 12:51 PM Murtuza Zabuawala < > [email protected]> wrote: > >> Hi Akshay, >> >> We have used aysnc=False in most ajax calls with this feature, It is >> causing UI hang in case of slow server response. >> You can try adding a time.sleep() call at the python side response and >> check the UI hang, I think we should avoid sync calls as much as possible. >> > > I consider a sync ajax call to be a bug. > > >> >> >> -- >> Regards, >> Murtuza Zabuawala >> *EDB* >> *POWER TO POSTGRES* >> https://www.edbpostgres.com >> >> >> On Thu, Oct 1, 2020 at 1:31 PM Akshay Joshi < >> [email protected]> wrote: >> >>> Thanks, patch applied. >>> >>> On Thu, Oct 1, 2020 at 10:42 AM Nikhil Mohite < >>> [email protected]> wrote: >>> >>>> Hi Akshay, >>>> >>>> I have resolved the sonarQube issues, PFA updated patch for the same. >>>> >>>> >>>> Regards, >>>> Nikhil Mohite. >>>> >>>> >>>> On Tue, Sep 29, 2020 at 11:31 AM Akshay Joshi < >>>> [email protected]> wrote: >>>> >>>>> Hi Nikhil >>>>> >>>>> Your patch introduces 1 new Bug and 13 new code smells, please fix >>>>> those and resend the patch. >>>>> >>>>> On Mon, Sep 28, 2020 at 7:31 PM Nikhil Mohite < >>>>> [email protected]> wrote: >>>>> >>>>>> Hi Akshay, >>>>>> >>>>>> I have resolved code conflict issues and sonarqube issues. >>>>>> PFA updated patch. >>>>>> >>>>>> Regards, >>>>>> Nikhil Mohite. >>>>>> >>>>>> On Mon, Sep 28, 2020 at 5:58 PM Akshay Joshi < >>>>>> [email protected]> wrote: >>>>>> >>>>>>> Hi Nikhil >>>>>>> >>>>>>> The patch is not applying, rebase, and send it again. Please check >>>>>>> your code should not create any new SonarQube issues. >>>>>>> >>>>>>> On Mon, Sep 28, 2020 at 11:20 AM Nikhil Mohite < >>>>>>> [email protected]> wrote: >>>>>>> >>>>>>>> Hi Akshay, >>>>>>>> >>>>>>>> I have resolved all the review comments and also updated the test >>>>>>>> cases as per the new implementation. >>>>>>>> >>>>>>>> PFA updated patch. >>>>>>>> >>>>>>>> >>>>>>>> >>>>>>>> On Mon, Sep 21, 2020 at 5:24 PM Akshay Joshi < >>>>>>>> [email protected]> wrote: >>>>>>>> >>>>>>>>> Hi Nikhil >>>>>>>>> >>>>>>>>> Following are the initial review comments: >>>>>>>>> >>>>>>>>> - Open View/Edit data on any table and click on the same >>>>>>>>> database connection and then click on the Execute button. Got >>>>>>>>> "get_primary_keys() takes 1 positional argument but 2 were given" error. >>>>>>>>> - In my opinion, we should hide the option to change the >>>>>>>>> database connection for View/Edit Data. >>>>>>>>> - If the user clicks on the same database connection multiple >>>>>>>>> times then no need to change the backend connection and transaction id. Add >>>>>>>>> validation at the backend, no action required in this case. >>>>>>>>> - The role option is missing from the "connect to server" >>>>>>>>> dialog. >>>>>>>>> - The Password field should not be there on the "connect to >>>>>>>>> server" dialog. Sometimes we saved the password so asking a password every >>>>>>>>> time is not correct. Check the pgAdmin 3 behavior. >>>>>>>>> >>>>>>>>> Code review still remains. >>>>>>>>> >>>>>>>>> On Thu, Sep 17, 2020 at 4:15 PM Nikhil Mohite < >>>>>>>>> [email protected]> wrote: >>>>>>>>> >>>>>>>>>> Hi Team, >>>>>>>>>> >>>>>>>>>> Regarding RM-3794 <https://redmine.postgresql.org/issues/3794; >>>>>>>>>> allow the user to change the database connection from an open query tool: >>>>>>>>>> I have implemented the feature and also added documentation for >>>>>>>>>> it. >>>>>>>>>> >>>>>>>>>> PFA patch. >>>>>>>>>> >>>>>>>>>> -- >>>>>>>>>> *Thanks & Regards,* >>>>>>>>>> *Nikhil Mohite* >>>>>>>>>> *Software Engineer.* >>>>>>>>>> *EDB Postgres* <https://www.enterprisedb.com/; >>>>>>>>>> *Mob.No: +91-7798364578.* >>>>>>>>>> >>>>>>>>> >>>>>>>>> >>>>>>>>> -- >>>>>>>>> *Thanks & Regards* >>>>>>>>> *Akshay Joshi* >>>>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>>>> >>>>>>>>> *Mobile: +91 976-788-8246* >>>>>>>>> >>>>>>>> >>>>>>> >>>>>>> -- >>>>>>> *Thanks & Regards* >>>>>>> *Akshay Joshi* >>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>> >>>>>>> *Mobile: +91 976-788-8246* >>>>>>> >>>>>> >>>>> >>>>> -- >>>>> *Thanks & Regards* >>>>> *Akshay Joshi* >>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>> *EDB Postgres <http://edbpostgres.com>* >>>>> >>>>> *Mobile: +91 976-788-8246* >>>>> >>>> >>> >>> -- >>> *Thanks & Regards* >>> *Akshay Joshi* >>> *pgAdmin Hacker | Sr. Software Architect* >>> *EDB Postgres <http://edbpostgres.com>* >>> >>> *Mobile: +91 976-788-8246* >>> >> > > -- > Dave Page > VP & Chief Architect, Database Infrastructure > EDB: http://www.enterprisedb.com > > Blog: http://pgsnake.blogspot.com > Twitter: @pgsnake > -- *Thanks & Regards* *Akshay Joshi* *pgAdmin Hacker | Sr. Software Architect* *EDB Postgres <http://edbpostgres.com>* *Mobile: +91 976-788-8246* ^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: [pgAdmin][RM3794]:Allow User to Change Database Connection from an Open Query Tool Tab @ 2020-10-07 06:41 Nikhil Mohite <[email protected]> parent: Akshay Joshi <[email protected]> 0 siblings, 1 reply; 18+ messages in thread From: Nikhil Mohite @ 2020-10-07 06:41 UTC (permalink / raw) To: Akshay Joshi <[email protected]>; +Cc: pgadmin-hackers; Murtuza Zabuawala <[email protected]> Hi Akshay, I checked the implementation and found 2 locations which I missed in the last patch to remove async: False. I have removed all occurrences of async: False now also added missing loader in required places. PFA updated the patch for the same. Regards, Nikhil Mohite. On Tue, Oct 6, 2020 at 6:19 PM Akshay Joshi <[email protected]> wrote: > Hi Nikhil > > Please verify and remove async = false wherever possible. > > On Tue, Oct 6, 2020 at 5:24 PM Dave Page <[email protected]> > wrote: > >> >> >> On Tue, Oct 6, 2020 at 12:51 PM Murtuza Zabuawala < >> [email protected]> wrote: >> >>> Hi Akshay, >>> >>> We have used aysnc=False in most ajax calls with this feature, It is >>> causing UI hang in case of slow server response. >>> You can try adding a time.sleep() call at the python side response and >>> check the UI hang, I think we should avoid sync calls as much as possible. >>> >> >> I consider a sync ajax call to be a bug. >> >> >>> >>> >>> -- >>> Regards, >>> Murtuza Zabuawala >>> *EDB* >>> *POWER TO POSTGRES* >>> https://www.edbpostgres.com >>> >>> >>> On Thu, Oct 1, 2020 at 1:31 PM Akshay Joshi < >>> [email protected]> wrote: >>> >>>> Thanks, patch applied. >>>> >>>> On Thu, Oct 1, 2020 at 10:42 AM Nikhil Mohite < >>>> [email protected]> wrote: >>>> >>>>> Hi Akshay, >>>>> >>>>> I have resolved the sonarQube issues, PFA updated patch for the same. >>>>> >>>>> >>>>> Regards, >>>>> Nikhil Mohite. >>>>> >>>>> >>>>> On Tue, Sep 29, 2020 at 11:31 AM Akshay Joshi < >>>>> [email protected]> wrote: >>>>> >>>>>> Hi Nikhil >>>>>> >>>>>> Your patch introduces 1 new Bug and 13 new code smells, please fix >>>>>> those and resend the patch. >>>>>> >>>>>> On Mon, Sep 28, 2020 at 7:31 PM Nikhil Mohite < >>>>>> [email protected]> wrote: >>>>>> >>>>>>> Hi Akshay, >>>>>>> >>>>>>> I have resolved code conflict issues and sonarqube issues. >>>>>>> PFA updated patch. >>>>>>> >>>>>>> Regards, >>>>>>> Nikhil Mohite. >>>>>>> >>>>>>> On Mon, Sep 28, 2020 at 5:58 PM Akshay Joshi < >>>>>>> [email protected]> wrote: >>>>>>> >>>>>>>> Hi Nikhil >>>>>>>> >>>>>>>> The patch is not applying, rebase, and send it again. Please check >>>>>>>> your code should not create any new SonarQube issues. >>>>>>>> >>>>>>>> On Mon, Sep 28, 2020 at 11:20 AM Nikhil Mohite < >>>>>>>> [email protected]> wrote: >>>>>>>> >>>>>>>>> Hi Akshay, >>>>>>>>> >>>>>>>>> I have resolved all the review comments and also updated the test >>>>>>>>> cases as per the new implementation. >>>>>>>>> >>>>>>>>> PFA updated patch. >>>>>>>>> >>>>>>>>> >>>>>>>>> >>>>>>>>> On Mon, Sep 21, 2020 at 5:24 PM Akshay Joshi < >>>>>>>>> [email protected]> wrote: >>>>>>>>> >>>>>>>>>> Hi Nikhil >>>>>>>>>> >>>>>>>>>> Following are the initial review comments: >>>>>>>>>> >>>>>>>>>> - Open View/Edit data on any table and click on the same >>>>>>>>>> database connection and then click on the Execute button. Got >>>>>>>>>> "get_primary_keys() takes 1 positional argument but 2 were given" error. >>>>>>>>>> - In my opinion, we should hide the option to change the >>>>>>>>>> database connection for View/Edit Data. >>>>>>>>>> - If the user clicks on the same database connection multiple >>>>>>>>>> times then no need to change the backend connection and transaction id. Add >>>>>>>>>> validation at the backend, no action required in this case. >>>>>>>>>> - The role option is missing from the "connect to server" >>>>>>>>>> dialog. >>>>>>>>>> - The Password field should not be there on the "connect to >>>>>>>>>> server" dialog. Sometimes we saved the password so asking a password every >>>>>>>>>> time is not correct. Check the pgAdmin 3 behavior. >>>>>>>>>> >>>>>>>>>> Code review still remains. >>>>>>>>>> >>>>>>>>>> On Thu, Sep 17, 2020 at 4:15 PM Nikhil Mohite < >>>>>>>>>> [email protected]> wrote: >>>>>>>>>> >>>>>>>>>>> Hi Team, >>>>>>>>>>> >>>>>>>>>>> Regarding RM-3794 <https://redmine.postgresql.org/issues/3794; >>>>>>>>>>> allow the user to change the database connection from an open query tool: >>>>>>>>>>> I have implemented the feature and also added documentation for >>>>>>>>>>> it. >>>>>>>>>>> >>>>>>>>>>> PFA patch. >>>>>>>>>>> >>>>>>>>>>> -- >>>>>>>>>>> *Thanks & Regards,* >>>>>>>>>>> *Nikhil Mohite* >>>>>>>>>>> *Software Engineer.* >>>>>>>>>>> *EDB Postgres* <https://www.enterprisedb.com/; >>>>>>>>>>> *Mob.No: +91-7798364578.* >>>>>>>>>>> >>>>>>>>>> >>>>>>>>>> >>>>>>>>>> -- >>>>>>>>>> *Thanks & Regards* >>>>>>>>>> *Akshay Joshi* >>>>>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>>>>> >>>>>>>>>> *Mobile: +91 976-788-8246* >>>>>>>>>> >>>>>>>>> >>>>>>>> >>>>>>>> -- >>>>>>>> *Thanks & Regards* >>>>>>>> *Akshay Joshi* >>>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>>> >>>>>>>> *Mobile: +91 976-788-8246* >>>>>>>> >>>>>>> >>>>>> >>>>>> -- >>>>>> *Thanks & Regards* >>>>>> *Akshay Joshi* >>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>> >>>>>> *Mobile: +91 976-788-8246* >>>>>> >>>>> >>>> >>>> -- >>>> *Thanks & Regards* >>>> *Akshay Joshi* >>>> *pgAdmin Hacker | Sr. Software Architect* >>>> *EDB Postgres <http://edbpostgres.com>* >>>> >>>> *Mobile: +91 976-788-8246* >>>> >>> >> >> -- >> Dave Page >> VP & Chief Architect, Database Infrastructure >> EDB: http://www.enterprisedb.com >> >> Blog: http://pgsnake.blogspot.com >> Twitter: @pgsnake >> > > > -- > *Thanks & Regards* > *Akshay Joshi* > *pgAdmin Hacker | Sr. Software Architect* > *EDB Postgres <http://edbpostgres.com>* > > *Mobile: +91 976-788-8246* > Attachments: [application/octet-stream] RM_3794_V7.patch (11.1K, 3-RM_3794_V7.patch) download | inline diff: diff --git a/web/pgadmin/static/js/sqleditor/new_connection_dialog.js b/web/pgadmin/static/js/sqleditor/new_connection_dialog.js index 3fcd374..13267ab 100644 --- a/web/pgadmin/static/js/sqleditor/new_connection_dialog.js +++ b/web/pgadmin/static/js/sqleditor/new_connection_dialog.js @@ -95,7 +95,7 @@ let NewConnectionDialog = { closableByDimmer: false, modal: false, autoReset: false, - closable: true, + closable: false, }, }; }, @@ -152,14 +152,18 @@ let NewConnectionDialog = { self.statusBar.removeClass('d-none'); $(self.statusBar.find('.alert-text')).html(msg); // Disable Okay button - self.__internal.buttons[2].element.disabled = true; + if(self.__internal){ + self.__internal.buttons[2].element.disabled = true; + } }); view.listenTo(view.model, 'pgadmin-session:valid', function() { self.statusBar.addClass('d-none'); $(self.statusBar.find('.alert-text')).html(''); // Enable Okay button - self.__internal.buttons[2].element.disabled = false; + if(self.__internal) { + self.__internal.buttons[2].element.disabled = false; + } }); }); @@ -230,15 +234,18 @@ let NewConnectionDialog = { 'user': newConnCollectionModel['user'], 'role': newConnCollectionModel['role'], 'password': response.password, + 'server_name': response.server_name, + 'database_name': selected_database_name, }; handler.gridView.on_change_connection(connection_details, self); } } else { - self.close(); + Alertify.newConnectionDialog().destroy(); } }, }; }); + setTimeout(function(){ Alertify.newConnectionDialog('Connect to server.').resizeTo(pgAdmin.Browser.stdW.md,pgAdmin.Browser.stdH.md); }, 500); diff --git a/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js b/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js index e262d0e..9e03051 100644 --- a/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js +++ b/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js @@ -31,7 +31,6 @@ export default function newConnectionDialogModel(response, sgid, sid) { }); $.ajax({ - async: false, url: url, headers: { 'Cache-Control' : 'no-cache', @@ -52,8 +51,8 @@ export default function newConnectionDialogModel(response, sgid, sid) { } else { self.field.set('options', []); } - //alertify.error(res.data.msg); } + Backform.Select2Control.prototype.render.apply(self, arguments); }).fail(function(e){ let msg = ''; if(e.status == 404) { @@ -131,7 +130,8 @@ export default function newConnectionDialogModel(response, sgid, sid) { if (closeEvent.button.text == gettext('OK')) { if(this.submit_password) { var _url = url_for('sqleditor.connect_server', {'sid': this.server_id}); - + var loadingDiv = $('#show_filter_progress'); + loadingDiv.removeClass('d-none'); $.ajax({ type: 'POST', timeout: 30000, @@ -148,8 +148,10 @@ export default function newConnectionDialogModel(response, sgid, sid) { response.server_name = obj.name; } }); + loadingDiv.addClass('d-none'); }) .fail(function(xhr) { + loadingDiv.addClass('d-none'); alertify.connectServer('Connect to server', xhr.responseJSON.result, local_self.getValueFromDOM()); }); } else { @@ -182,8 +184,9 @@ export default function newConnectionDialogModel(response, sgid, sid) { 'sid': self.getValueFromDOM(), 'usr': self.model.attributes.user, }); + var loadingDiv = $('#show_filter_progress'); + loadingDiv.removeClass('d-none'); $.ajax({ - async: false, url: url, type: 'POST', headers: { @@ -196,7 +199,9 @@ export default function newConnectionDialogModel(response, sgid, sid) { response.server_name = obj.name; } }); + loadingDiv.addClass('d-none'); }).fail(function(xhr){ + loadingDiv.addClass('d-none'); alertify.connectServer('Connect to server', xhr.responseJSON.result, self.getValueFromDOM()); }); diff --git a/web/pgadmin/tools/datagrid/templates/datagrid/index.html b/web/pgadmin/tools/datagrid/templates/datagrid/index.html index 4970027..feedc63 100644 --- a/web/pgadmin/tools/datagrid/templates/datagrid/index.html +++ b/web/pgadmin/tools/datagrid/templates/datagrid/index.html @@ -418,11 +418,13 @@ </i> </div> <div class="connection-info btn-group mr-1" role="group" aria-label=""> - <div class="editor-title" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" - style="background-color: {% if fgcolor %}{{ bgcolor or '#FFFFFF' }}{% endif %}; color: {% if fgcolor %}{{ fgcolor }}{% endif %};"> + <div class="connection-data" data-toggle="dropdown"> + <div class="editor-title" aria-haspopup="true" aria-expanded="false" + style="background-color: {% if fgcolor %}{{ bgcolor or '#FFFFFF' }}{% endif %}; color: {% if fgcolor %}{{ fgcolor }}{% endif %};"> + </div> + <span class="conn-info-dd dropdown-toggle dropdown-toggle-split" + aria-haspopup="true" aria-expanded="false"></span> </div> - <span class="conn-info-dd dropdown-toggle dropdown-toggle-split" - data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span> <ul class="dropdown-menu" id="connections-list"> </ul> </div> diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js index 4ef4b8f..cb1f8a2 100644 --- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js +++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js @@ -182,7 +182,7 @@ define('tools.querytool', [ }); } else { $('.conn-info-dd').hide(); - $('.editor-title').css({pointerEvents: 'none'}); + $('.connection-data').css({pointerEvents: 'none', cursor: 'arrow'}); } }, @@ -2122,8 +2122,20 @@ define('tools.querytool', [ let title = this.$el.find('.editor-title').html(); if(connection_details['title'] != title) { var self = this; + var loadingDiv = null; + var msgDiv = null; + if(ref){ + loadingDiv = $('#show_filter_progress'); + loadingDiv.removeClass('d-none'); + msgDiv = loadingDiv.find('.sql-editor-busy-text'); + msgDiv.text('Connecting to database...'); + } else{ + loadingDiv = $('#fetching_data'); + loadingDiv.removeClass('d-none'); + msgDiv = loadingDiv.find('.sql-editor-busy-text'); + } + $.ajax({ - async: false, url: url_for('datagrid.update_query_tool_connection', { 'trans_id': self.transId, 'sgid': connection_details['server_group'], @@ -2148,7 +2160,8 @@ define('tools.querytool', [ }; self.set_editor_title(self.handler.url_params.title); self.handler.setTitle(self.handler.url_params.title); - alertify.success('connected successfully'); + let success_msg = connection_details['server_name'] + '/' + connection_details['database_name']+ '- Database connected'; + alertify.success(success_msg); if(ref){ let connection_data = { 'server_group': self.handler.url_params.sgid, @@ -2159,39 +2172,27 @@ define('tools.querytool', [ 'role': connection_details['role'], 'password': connection_details['password'], 'is_allow_new_connection': true, + 'database_name': connection_details['database_name'], + 'server_name': connection_details['server_name'], }; self.connection_list.unshift(connection_data); self.render_connection(self.connection_list); + loadingDiv.addClass('d-none'); + alertify.newConnectionDialog().destroy(); ref.close(); + } else { + loadingDiv.addClass('d-none'); } } return true; }) .fail(function(xhr) { + loadingDiv.addClass('d-none'); if(xhr.status == 428) { alertify.connectServer('Connect to server', xhr.responseJSON.result, connection_details['server'], false); } else { alertify.error(xhr.responseJSON['errormsg']); } - /*let url = url_for('sqleditor.connect_server_with_user', { - 'sid': newConnCollectionModel['server'], - 'usr': newConnCollectionModel['user'] - }); - $.ajax({ - async: false, - url: url, - headers: { - 'Cache-Control' : 'no-cache', - }, - }).done(function () { - Backform.Select2Control.prototype.onChange.apply(self, arguments); - response.server_list.forEach(function(obj){ - if(obj.id==self.model.changed.server) { - response.server_name = obj.name; - } - }); - }).fail(function(xhr){});*/ - }); } }, @@ -2526,6 +2527,8 @@ define('tools.querytool', [ 'role': null, 'title': _.unescape(url_params.title), 'is_allow_new_connection': false, + 'database_name': url_params.title.split('/')[0], + 'server_name': url_params.title.split('@')[1], }; self.gridView.connection_list.unshift(connection_data); self.gridView.render_connection(self.gridView.connection_list); diff --git a/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss b/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss index 53f2449..7fc576a 100644 --- a/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss +++ b/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss @@ -43,6 +43,12 @@ cursor: pointer; } +.connection-data { + display: inherit; + cursor: pointer; + width: auto; +} + #editor-panel { z-index: 0; ^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: [pgAdmin][RM3794]:Allow User to Change Database Connection from an Open Query Tool Tab @ 2020-10-08 06:09 Akshay Joshi <[email protected]> parent: Nikhil Mohite <[email protected]> 0 siblings, 1 reply; 18+ messages in thread From: Akshay Joshi @ 2020-10-08 06:09 UTC (permalink / raw) To: Nikhil Mohite <[email protected]>; +Cc: pgadmin-hackers; Murtuza Zabuawala <[email protected]> Thanks, patch applied. On Wed, Oct 7, 2020 at 12:11 PM Nikhil Mohite < [email protected]> wrote: > Hi Akshay, > > I checked the implementation and found 2 locations which I missed in the > last patch to remove async: False. > I have removed all occurrences of async: False now also added missing > loader in required places. > > PFA updated the patch for the same. > > Regards, > Nikhil Mohite. > > On Tue, Oct 6, 2020 at 6:19 PM Akshay Joshi <[email protected]> > wrote: > >> Hi Nikhil >> >> Please verify and remove async = false wherever possible. >> >> On Tue, Oct 6, 2020 at 5:24 PM Dave Page <[email protected]> >> wrote: >> >>> >>> >>> On Tue, Oct 6, 2020 at 12:51 PM Murtuza Zabuawala < >>> [email protected]> wrote: >>> >>>> Hi Akshay, >>>> >>>> We have used aysnc=False in most ajax calls with this feature, It is >>>> causing UI hang in case of slow server response. >>>> You can try adding a time.sleep() call at the python side response and >>>> check the UI hang, I think we should avoid sync calls as much as possible. >>>> >>> >>> I consider a sync ajax call to be a bug. >>> >>> >>>> >>>> >>>> -- >>>> Regards, >>>> Murtuza Zabuawala >>>> *EDB* >>>> *POWER TO POSTGRES* >>>> https://www.edbpostgres.com >>>> >>>> >>>> On Thu, Oct 1, 2020 at 1:31 PM Akshay Joshi < >>>> [email protected]> wrote: >>>> >>>>> Thanks, patch applied. >>>>> >>>>> On Thu, Oct 1, 2020 at 10:42 AM Nikhil Mohite < >>>>> [email protected]> wrote: >>>>> >>>>>> Hi Akshay, >>>>>> >>>>>> I have resolved the sonarQube issues, PFA updated patch for the same. >>>>>> >>>>>> >>>>>> Regards, >>>>>> Nikhil Mohite. >>>>>> >>>>>> >>>>>> On Tue, Sep 29, 2020 at 11:31 AM Akshay Joshi < >>>>>> [email protected]> wrote: >>>>>> >>>>>>> Hi Nikhil >>>>>>> >>>>>>> Your patch introduces 1 new Bug and 13 new code smells, please fix >>>>>>> those and resend the patch. >>>>>>> >>>>>>> On Mon, Sep 28, 2020 at 7:31 PM Nikhil Mohite < >>>>>>> [email protected]> wrote: >>>>>>> >>>>>>>> Hi Akshay, >>>>>>>> >>>>>>>> I have resolved code conflict issues and sonarqube issues. >>>>>>>> PFA updated patch. >>>>>>>> >>>>>>>> Regards, >>>>>>>> Nikhil Mohite. >>>>>>>> >>>>>>>> On Mon, Sep 28, 2020 at 5:58 PM Akshay Joshi < >>>>>>>> [email protected]> wrote: >>>>>>>> >>>>>>>>> Hi Nikhil >>>>>>>>> >>>>>>>>> The patch is not applying, rebase, and send it again. Please check >>>>>>>>> your code should not create any new SonarQube issues. >>>>>>>>> >>>>>>>>> On Mon, Sep 28, 2020 at 11:20 AM Nikhil Mohite < >>>>>>>>> [email protected]> wrote: >>>>>>>>> >>>>>>>>>> Hi Akshay, >>>>>>>>>> >>>>>>>>>> I have resolved all the review comments and also updated the test >>>>>>>>>> cases as per the new implementation. >>>>>>>>>> >>>>>>>>>> PFA updated patch. >>>>>>>>>> >>>>>>>>>> >>>>>>>>>> >>>>>>>>>> On Mon, Sep 21, 2020 at 5:24 PM Akshay Joshi < >>>>>>>>>> [email protected]> wrote: >>>>>>>>>> >>>>>>>>>>> Hi Nikhil >>>>>>>>>>> >>>>>>>>>>> Following are the initial review comments: >>>>>>>>>>> >>>>>>>>>>> - Open View/Edit data on any table and click on the same >>>>>>>>>>> database connection and then click on the Execute button. Got >>>>>>>>>>> "get_primary_keys() takes 1 positional argument but 2 were given" error. >>>>>>>>>>> - In my opinion, we should hide the option to change the >>>>>>>>>>> database connection for View/Edit Data. >>>>>>>>>>> - If the user clicks on the same database connection >>>>>>>>>>> multiple times then no need to change the backend connection and >>>>>>>>>>> transaction id. Add validation at the backend, no action required in this >>>>>>>>>>> case. >>>>>>>>>>> - The role option is missing from the "connect to server" >>>>>>>>>>> dialog. >>>>>>>>>>> - The Password field should not be there on the "connect to >>>>>>>>>>> server" dialog. Sometimes we saved the password so asking a password every >>>>>>>>>>> time is not correct. Check the pgAdmin 3 behavior. >>>>>>>>>>> >>>>>>>>>>> Code review still remains. >>>>>>>>>>> >>>>>>>>>>> On Thu, Sep 17, 2020 at 4:15 PM Nikhil Mohite < >>>>>>>>>>> [email protected]> wrote: >>>>>>>>>>> >>>>>>>>>>>> Hi Team, >>>>>>>>>>>> >>>>>>>>>>>> Regarding RM-3794 <https://redmine.postgresql.org/issues/3794; >>>>>>>>>>>> allow the user to change the database connection from an open query tool: >>>>>>>>>>>> I have implemented the feature and also added documentation for >>>>>>>>>>>> it. >>>>>>>>>>>> >>>>>>>>>>>> PFA patch. >>>>>>>>>>>> >>>>>>>>>>>> -- >>>>>>>>>>>> *Thanks & Regards,* >>>>>>>>>>>> *Nikhil Mohite* >>>>>>>>>>>> *Software Engineer.* >>>>>>>>>>>> *EDB Postgres* <https://www.enterprisedb.com/; >>>>>>>>>>>> *Mob.No: +91-7798364578.* >>>>>>>>>>>> >>>>>>>>>>> >>>>>>>>>>> >>>>>>>>>>> -- >>>>>>>>>>> *Thanks & Regards* >>>>>>>>>>> *Akshay Joshi* >>>>>>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>>>>>> >>>>>>>>>>> *Mobile: +91 976-788-8246* >>>>>>>>>>> >>>>>>>>>> >>>>>>>>> >>>>>>>>> -- >>>>>>>>> *Thanks & Regards* >>>>>>>>> *Akshay Joshi* >>>>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>>>> >>>>>>>>> *Mobile: +91 976-788-8246* >>>>>>>>> >>>>>>>> >>>>>>> >>>>>>> -- >>>>>>> *Thanks & Regards* >>>>>>> *Akshay Joshi* >>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>> >>>>>>> *Mobile: +91 976-788-8246* >>>>>>> >>>>>> >>>>> >>>>> -- >>>>> *Thanks & Regards* >>>>> *Akshay Joshi* >>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>> *EDB Postgres <http://edbpostgres.com>* >>>>> >>>>> *Mobile: +91 976-788-8246* >>>>> >>>> >>> >>> -- >>> Dave Page >>> VP & Chief Architect, Database Infrastructure >>> EDB: http://www.enterprisedb.com >>> >>> Blog: http://pgsnake.blogspot.com >>> Twitter: @pgsnake >>> >> >> >> -- >> *Thanks & Regards* >> *Akshay Joshi* >> *pgAdmin Hacker | Sr. Software Architect* >> *EDB Postgres <http://edbpostgres.com>* >> >> *Mobile: +91 976-788-8246* >> > -- *Thanks & Regards* *Akshay Joshi* *pgAdmin Hacker | Sr. Software Architect* *EDB Postgres <http://edbpostgres.com>* *Mobile: +91 976-788-8246* ^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: [pgAdmin][RM3794]:Allow User to Change Database Connection from an Open Query Tool Tab @ 2020-10-21 05:38 Nikhil Mohite <[email protected]> parent: Akshay Joshi <[email protected]> 0 siblings, 1 reply; 18+ messages in thread From: Nikhil Mohite @ 2020-10-21 05:38 UTC (permalink / raw) To: Akshay Joshi <[email protected]>; +Cc: pgadmin-hackers Hi Akshay, I have updated the existing implementation as per suggestions. 1. Show servers in server groups in the dropdown. 2. Current selected connection in the new connection dropdown is now highlighted as selected. 3. Notification to the user before the change connection action. 4. If we connect to the server through a new connection dialog, the tree will use the same connection and it will not create a new connection. (In earlier implementation it was asking for the password even we have connected from a new connection dialog.) PFA patch Regards, Nikhil Mohite. On Thu, Oct 8, 2020 at 11:39 AM Akshay Joshi <[email protected]> wrote: > Thanks, patch applied. > > On Wed, Oct 7, 2020 at 12:11 PM Nikhil Mohite < > [email protected]> wrote: > >> Hi Akshay, >> >> I checked the implementation and found 2 locations which I missed in the >> last patch to remove async: False. >> I have removed all occurrences of async: False now also added missing >> loader in required places. >> >> PFA updated the patch for the same. >> >> Regards, >> Nikhil Mohite. >> >> On Tue, Oct 6, 2020 at 6:19 PM Akshay Joshi < >> [email protected]> wrote: >> >>> Hi Nikhil >>> >>> Please verify and remove async = false wherever possible. >>> >>> On Tue, Oct 6, 2020 at 5:24 PM Dave Page <[email protected]> >>> wrote: >>> >>>> >>>> >>>> On Tue, Oct 6, 2020 at 12:51 PM Murtuza Zabuawala < >>>> [email protected]> wrote: >>>> >>>>> Hi Akshay, >>>>> >>>>> We have used aysnc=False in most ajax calls with this feature, It is >>>>> causing UI hang in case of slow server response. >>>>> You can try adding a time.sleep() call at the python side response and >>>>> check the UI hang, I think we should avoid sync calls as much as possible. >>>>> >>>> >>>> I consider a sync ajax call to be a bug. >>>> >>>> >>>>> >>>>> >>>>> -- >>>>> Regards, >>>>> Murtuza Zabuawala >>>>> *EDB* >>>>> *POWER TO POSTGRES* >>>>> https://www.edbpostgres.com >>>>> >>>>> >>>>> On Thu, Oct 1, 2020 at 1:31 PM Akshay Joshi < >>>>> [email protected]> wrote: >>>>> >>>>>> Thanks, patch applied. >>>>>> >>>>>> On Thu, Oct 1, 2020 at 10:42 AM Nikhil Mohite < >>>>>> [email protected]> wrote: >>>>>> >>>>>>> Hi Akshay, >>>>>>> >>>>>>> I have resolved the sonarQube issues, PFA updated patch for the same. >>>>>>> >>>>>>> >>>>>>> Regards, >>>>>>> Nikhil Mohite. >>>>>>> >>>>>>> >>>>>>> On Tue, Sep 29, 2020 at 11:31 AM Akshay Joshi < >>>>>>> [email protected]> wrote: >>>>>>> >>>>>>>> Hi Nikhil >>>>>>>> >>>>>>>> Your patch introduces 1 new Bug and 13 new code smells, please fix >>>>>>>> those and resend the patch. >>>>>>>> >>>>>>>> On Mon, Sep 28, 2020 at 7:31 PM Nikhil Mohite < >>>>>>>> [email protected]> wrote: >>>>>>>> >>>>>>>>> Hi Akshay, >>>>>>>>> >>>>>>>>> I have resolved code conflict issues and sonarqube issues. >>>>>>>>> PFA updated patch. >>>>>>>>> >>>>>>>>> Regards, >>>>>>>>> Nikhil Mohite. >>>>>>>>> >>>>>>>>> On Mon, Sep 28, 2020 at 5:58 PM Akshay Joshi < >>>>>>>>> [email protected]> wrote: >>>>>>>>> >>>>>>>>>> Hi Nikhil >>>>>>>>>> >>>>>>>>>> The patch is not applying, rebase, and send it again. Please >>>>>>>>>> check your code should not create any new SonarQube issues. >>>>>>>>>> >>>>>>>>>> On Mon, Sep 28, 2020 at 11:20 AM Nikhil Mohite < >>>>>>>>>> [email protected]> wrote: >>>>>>>>>> >>>>>>>>>>> Hi Akshay, >>>>>>>>>>> >>>>>>>>>>> I have resolved all the review comments and also updated the >>>>>>>>>>> test cases as per the new implementation. >>>>>>>>>>> >>>>>>>>>>> PFA updated patch. >>>>>>>>>>> >>>>>>>>>>> >>>>>>>>>>> >>>>>>>>>>> On Mon, Sep 21, 2020 at 5:24 PM Akshay Joshi < >>>>>>>>>>> [email protected]> wrote: >>>>>>>>>>> >>>>>>>>>>>> Hi Nikhil >>>>>>>>>>>> >>>>>>>>>>>> Following are the initial review comments: >>>>>>>>>>>> >>>>>>>>>>>> - Open View/Edit data on any table and click on the same >>>>>>>>>>>> database connection and then click on the Execute button. Got >>>>>>>>>>>> "get_primary_keys() takes 1 positional argument but 2 were given" error. >>>>>>>>>>>> - In my opinion, we should hide the option to change the >>>>>>>>>>>> database connection for View/Edit Data. >>>>>>>>>>>> - If the user clicks on the same database connection >>>>>>>>>>>> multiple times then no need to change the backend connection and >>>>>>>>>>>> transaction id. Add validation at the backend, no action required in this >>>>>>>>>>>> case. >>>>>>>>>>>> - The role option is missing from the "connect to server" >>>>>>>>>>>> dialog. >>>>>>>>>>>> - The Password field should not be there on the "connect to >>>>>>>>>>>> server" dialog. Sometimes we saved the password so asking a password every >>>>>>>>>>>> time is not correct. Check the pgAdmin 3 behavior. >>>>>>>>>>>> >>>>>>>>>>>> Code review still remains. >>>>>>>>>>>> >>>>>>>>>>>> On Thu, Sep 17, 2020 at 4:15 PM Nikhil Mohite < >>>>>>>>>>>> [email protected]> wrote: >>>>>>>>>>>> >>>>>>>>>>>>> Hi Team, >>>>>>>>>>>>> >>>>>>>>>>>>> Regarding RM-3794 <https://redmine.postgresql.org/issues/3794; >>>>>>>>>>>>> allow the user to change the database connection from an open query tool: >>>>>>>>>>>>> I have implemented the feature and also added documentation >>>>>>>>>>>>> for it. >>>>>>>>>>>>> >>>>>>>>>>>>> PFA patch. >>>>>>>>>>>>> >>>>>>>>>>>>> -- >>>>>>>>>>>>> *Thanks & Regards,* >>>>>>>>>>>>> *Nikhil Mohite* >>>>>>>>>>>>> *Software Engineer.* >>>>>>>>>>>>> *EDB Postgres* <https://www.enterprisedb.com/; >>>>>>>>>>>>> *Mob.No: +91-7798364578.* >>>>>>>>>>>>> >>>>>>>>>>>> >>>>>>>>>>>> >>>>>>>>>>>> -- >>>>>>>>>>>> *Thanks & Regards* >>>>>>>>>>>> *Akshay Joshi* >>>>>>>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>>>>>>> >>>>>>>>>>>> *Mobile: +91 976-788-8246* >>>>>>>>>>>> >>>>>>>>>>> >>>>>>>>>> >>>>>>>>>> -- >>>>>>>>>> *Thanks & Regards* >>>>>>>>>> *Akshay Joshi* >>>>>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>>>>> >>>>>>>>>> *Mobile: +91 976-788-8246* >>>>>>>>>> >>>>>>>>> >>>>>>>> >>>>>>>> -- >>>>>>>> *Thanks & Regards* >>>>>>>> *Akshay Joshi* >>>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>>> >>>>>>>> *Mobile: +91 976-788-8246* >>>>>>>> >>>>>>> >>>>>> >>>>>> -- >>>>>> *Thanks & Regards* >>>>>> *Akshay Joshi* >>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>> >>>>>> *Mobile: +91 976-788-8246* >>>>>> >>>>> >>>> >>>> -- >>>> Dave Page >>>> VP & Chief Architect, Database Infrastructure >>>> EDB: http://www.enterprisedb.com >>>> >>>> Blog: http://pgsnake.blogspot.com >>>> Twitter: @pgsnake >>>> >>> >>> >>> -- >>> *Thanks & Regards* >>> *Akshay Joshi* >>> *pgAdmin Hacker | Sr. Software Architect* >>> *EDB Postgres <http://edbpostgres.com>* >>> >>> *Mobile: +91 976-788-8246* >>> >> > > -- > *Thanks & Regards* > *Akshay Joshi* > *pgAdmin Hacker | Sr. Software Architect* > *EDB Postgres <http://edbpostgres.com>* > > *Mobile: +91 976-788-8246* > Attachments: [application/octet-stream] RM_3794_review_comments.patch (23.1K, 3-RM_3794_review_comments.patch) download | inline diff: diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py index 2630d1e..ab4bea4 100644 --- a/web/pgadmin/browser/server_groups/servers/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/__init__.py @@ -1310,7 +1310,8 @@ class ServerNode(PGChildNodeView): # Connect the Server manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) - manager.update(server) + if not manager.connection().connected(): + manager.update(server) conn = manager.connection() # Get enc key diff --git a/web/pgadmin/static/js/sqleditor/new_connection_dialog.js b/web/pgadmin/static/js/sqleditor/new_connection_dialog.js index 10850e1..b19a50a 100644 --- a/web/pgadmin/static/js/sqleditor/new_connection_dialog.js +++ b/web/pgadmin/static/js/sqleditor/new_connection_dialog.js @@ -243,7 +243,6 @@ let NewConnectionDialog = { 'title': _.escape(tab_title), 'user': newConnCollectionModel['user'], 'role': newConnCollectionModel['role'], - 'password': response.password, 'server_name': _.escape(response.server_name), 'database_name': _.escape(selected_database_name), 'is_selected': false, @@ -260,13 +259,12 @@ let NewConnectionDialog = { setTimeout(function(){ Alertify.newConnectionDialog('Connect to server.').resizeTo(pgAdmin.Browser.stdW.md,pgAdmin.Browser.stdH.md); }, 500); - }).fail(function(error) { + }).fail(function() { Alertify.alert().setting({ 'title': gettext('Connection lost'), 'label':gettext('Ok'), 'message': gettext('Connection to the server has been lost.'), 'onok': function(){ - alert(error); //Close the window after connection is lost window.close(); }, diff --git a/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js b/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js index 09b2979..1dd32b7 100644 --- a/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js +++ b/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js @@ -78,7 +78,6 @@ export default function newConnectionDialogModel(response, sgid, sid) { server: parseInt(sid), database: null, user: null, - password: null, server_name: server_name, database_name: database_name, }, @@ -92,7 +91,63 @@ export default function newConnectionDialogModel(response, sgid, sid) { select2: { allowClear: false, }, + transform: function(data) { + let group_template_options = []; + for (let key in data) { + if (data.hasOwnProperty(key)) { + group_template_options.push({'group': key, 'optval': data[key]}); + } + } + return group_template_options; + }, control: Backform.Select2Control.extend({ + template: _.template([ + '<% if(label == false) {} else {%>', + ' <label class="<%=Backform.controlLabelClassName%>"><%=label%></label>', + '<% }%>', + '<div class="<%=controlsClassName%>">', + ' <select class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>"', + ' name="<%=name%>" value="<%-value%>" <%=disabled ? "disabled" : ""%>', + ' <%=required ? "required" : ""%><%= select2.multiple ? " multiple>" : ">" %>', + ' <%=select2.first_empty ? " <option></option>" : ""%>', + ' <% for (var i=0; i < options.length; i++) {%>', + ' <% if (options[i].group) { %>', + ' <% var group = options[i].group; %>', + ' <% if (options[i].optval) { %> <% var option_length = options[i].optval.length; %>', + ' <optgroup label="<%=group%>">', + ' <% for (var subindex=0; subindex < option_length; subindex++) {%>', + ' <% var option = options[i].optval[subindex]; %>', + ' <option ', + ' <% if (option.image) { %> data-image=<%=option.image%> <%}%>', + ' <% if (option.connected) { %> data-connected=connected <%}%>', + ' value=<%- formatter.fromRaw(option.value) %>', + ' <% if (option.selected) {%>selected="selected"<%} else {%>', + ' <% if (!select2.multiple && option.value === rawValue) {%>selected="selected"<%}%>', + ' <% if (select2.multiple && rawValue && rawValue.indexOf(option.value) != -1){%>selected="selected" data-index="rawValue.indexOf(option.value)"<%}%>', + ' <%}%>', + ' <%= disabled ? "disabled" : ""%>><%-option.label%></option>', + ' <%}%>', + ' </optgroup>', + ' <%}%>', + ' <%} else {%>', + ' <% var option = options[i]; %>', + ' <option ', + ' <% if (option.image) { %> data-image=<%=option.image%> <%}%>', + ' <% if (option.connected) { %> data-connected=connected <%}%>', + ' value=<%- formatter.fromRaw(option.value) %>', + ' <% if (option.selected) {%>selected="selected"<%} else {%>', + ' <% if (!select2.multiple && option.value === rawValue) {%>selected="selected"<%}%>', + ' <% if (select2.multiple && rawValue && rawValue.indexOf(option.value) != -1){%>selected="selected" data-index="rawValue.indexOf(option.value)"<%}%>', + ' <%}%>', + ' <%= disabled ? "disabled" : ""%>><%-option.label%></option>', + ' <%}%>', + ' <%}%>', + ' </select>', + ' <% if (helpMessage && helpMessage.length) { %>', + ' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>', + ' <% } %>', + '</div>', + ].join('\n')), connect: function(self) { let local_self = self; @@ -147,11 +202,14 @@ export default function newConnectionDialogModel(response, sgid, sid) { local_self.model.attributes.user = null; local_self.model.attributes.role = null; Backform.Select2Control.prototype.onChange.apply(local_self, arguments); - response.server_list.forEach(function(obj){ - if(obj.id==self.model.changed.server) { - response.server_name = obj.name; - } + Object.keys(response.server_list).forEach(key => { + response.server_list[key].forEach(option => { + if (option.value == parseInt(sid)) { + response.server_name = option.label; + } + }); }); + loadingDiv.addClass('d-none'); alertify.connectServer().destroy(); }) @@ -160,8 +218,6 @@ export default function newConnectionDialogModel(response, sgid, sid) { alertify.connectServer().destroy(); alertify.connectServer('Connect to server', xhr.responseJSON.result, local_self.getValueFromDOM()); }); - } else { - response.password = $('#password').val(); } } else { local_self.model.attributes.database = null; @@ -178,6 +234,19 @@ export default function newConnectionDialogModel(response, sgid, sid) { render: function() { let self = this; self.connect(self); + Object.keys(response.server_list).forEach(key => { + response.server_list[key].forEach(option => { + if (option.value == parseInt(sid)) { + response.server_name = option.label; + } + }); + }); + var transform = self.field.get('transform') || self.defaults.transform; + if (transform && _.isFunction(transform)) { + self.field.set('options', transform.bind(self, response.server_list)); + } else { + self.field.set('options', response.server_list); + } return Backform.Select2Control.prototype.render.apply(self, arguments); }, onChange: function() { @@ -200,10 +269,12 @@ export default function newConnectionDialogModel(response, sgid, sid) { }, }).done(function () { Backform.Select2Control.prototype.onChange.apply(self, arguments); - response.server_list.forEach(function(obj){ - if(obj.id==self.model.changed.server) { - response.server_name = obj.name; - } + Object.keys(response.server_list).forEach(key => { + response.server_list[key].forEach(option => { + if (option.value == parseInt(sid)) { + response.server_name = option.label; + } + }); }); loadingDiv.addClass('d-none'); }).fail(function(xhr){ @@ -213,17 +284,6 @@ export default function newConnectionDialogModel(response, sgid, sid) { }, }), - options: function() { - return _.map(response.server_list, (obj) => { - if (obj.id == parseInt(sid)) - response.server_name = obj.name; - - return { - value: obj.id, - label: obj.name, - }; - }); - }, }, { id: 'database', @@ -302,27 +362,6 @@ export default function newConnectionDialogModel(response, sgid, sid) { url: 'sqleditor.get_new_connection_role', disabled: false, }, - /*{ - id: 'password', - name: 'password', - label: gettext('Password'tools/sqleditor/__init__.py), - type: 'password', - editable: true, - disabled: true, - deps: ['user'], - control: Backform.InputControl.extend({ - render: function() { - let self = this; - self.model.attributes.password = null; - Backform.InputControl.prototype.render.apply(self, arguments); - return self; - }, - onChange: function() { - let self = this; - Backform.InputControl.prototype.onChange.apply(self, arguments); - }, - }), - },*/ ], validate: function() { let msg = null; @@ -336,11 +375,6 @@ export default function newConnectionDialogModel(response, sgid, sid) { this.errorModel.set('user', msg); return msg; } - /*else if((this.attributes.password == '' || _.isUndefined(this.get('password')) || _.isNull(this.get('password')))) { - msg = gettext('Please enter password'); - this.errorModel.set('password', msg); - return msg; - }*/ return null; }, }); diff --git a/web/pgadmin/tools/debugger/__init__.py b/web/pgadmin/tools/debugger/__init__.py index 1949709..422a8ae 100644 --- a/web/pgadmin/tools/debugger/__init__.py +++ b/web/pgadmin/tools/debugger/__init__.py @@ -98,6 +98,18 @@ class DebuggerModule(PgAdminModule): ) ) + self.tab_title = self.preference.register( + 'display', 'debugger_tab_title_placeholder', + gettext("Debugger tab title placeholder"), + 'text', '%FUNCTION%/%SCHEMA%/%DATABASE%', + category_label=PREF_LABEL_DISPLAY, + help_str=gettext( + 'Supported placeholders: FUNCTION, SCHEMA and DATABASE. ' + 'You can also provide any string with or ' + 'without placeholders' + ) + ) + self.preference.register( 'keyboard_shortcuts', 'btn_start', gettext('Accesskey (Continue/Start)'), 'keyboardshortcut', diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py index 9da8842..f9fb26f 100644 --- a/web/pgadmin/tools/sqleditor/__init__.py +++ b/web/pgadmin/tools/sqleditor/__init__.py @@ -46,7 +46,7 @@ from pgadmin.tools.sqleditor.utils.macros import get_macros,\ get_user_macros, set_macros from pgadmin.utils.constants import MIMETYPE_APP_JS, \ SERVER_CONNECTION_CLOSED, ERROR_MSG_TRANS_ID_NOT_FOUND, ERROR_FETCHING_DATA -from pgadmin.model import Server +from pgadmin.model import Server, ServerGroup from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry MODULE_NAME = 'sqleditor' @@ -1489,10 +1489,14 @@ def get_new_connection_data(sgid, sid=None): :extract_sql_from_network_parameters, """ try: - # if sid and not did: + server_groups = ServerGroup.query.all() + server_group_data = {server_group.name: [] for server_group in + server_groups} servers = Server.query.all() - server_list = [ - {'name': server.serialize['name'], "id": server.serialize['id']} + + [server_group_data[server.servers.name].append( + {'label': server.serialize['name'], + "value": server.serialize['id']}) for server in servers] msg = "Success" @@ -1501,7 +1505,7 @@ def get_new_connection_data(sgid, sid=None): 'status': True, 'msg': msg, 'result': { - 'server_list': server_list + 'server_list': server_group_data } } ) diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js index d3a2d25..905d782 100644 --- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js +++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js @@ -173,8 +173,13 @@ define('tools.querytool', [ var dropdownElement = document.getElementById('connections-list'); dropdownElement.innerHTML = ''; data_list.forEach((option, index) => { - $('#connections-list').append('<li class="connection-list-item" data-index='+ index +'><a class="dropdown-item" href="#" tabindex="0">'+ option.title +'</a></li>'); - + var opt = ''; + if ('is_selected' in option && option['is_selected']) { + opt = '<li class="connection-list-item selected-connection" data-index='+ index +'><a class="dropdown-item" href="#" tabindex="0">'+ option.title +'</a></li>'; + } else { + opt = '<li class="connection-list-item" data-index='+ index +'><a class="dropdown-item" href="#" tabindex="0">'+ option.title +'</a></li>'; + } + $('#connections-list').append(opt); }); var self = this; $('.connection-list-item').click(function() { @@ -2131,7 +2136,6 @@ define('tools.querytool', [ on_change_connection: function(connection_details, ref) { if(!connection_details['is_selected']) { var self = this; - self.set_selected_option(connection_details); var loadingDiv = null; var msgDiv = null; if(ref){ @@ -2145,66 +2149,78 @@ define('tools.querytool', [ msgDiv = loadingDiv.find('.sql-editor-busy-text'); } - $.ajax({ - url: url_for('datagrid.update_query_tool_connection', { - 'trans_id': self.transId, - 'sgid': connection_details['server_group'], - 'sid': connection_details['server'], - 'did': connection_details['database'], - }), - method: 'POST', - contentType: 'application/json', - data: JSON.stringify(connection_details), - }) - .done(function(res) { - if(res.success) { - self.transId = res.data.tran_id; - self.handler.transId = res.data.tran_id; - self.handler.url_params = { - 'did': connection_details['database'], - 'is_query_tool': self.handler.url_params.is_query_tool, - 'server_type': self.handler.url_params.server_type, + alertify.confirm(gettext('Change connection.'), + gettext('Change connection will lose all non committed changes for current connection, do you want to continue?'), + function() { + self.set_selected_option(connection_details); + $.ajax({ + url: url_for('datagrid.update_query_tool_connection', { + 'trans_id': self.transId, 'sgid': connection_details['server_group'], 'sid': connection_details['server'], - 'title': connection_details['title'], - }; - self.set_editor_title(_.unescape(self.handler.url_params.title)); - self.handler.setTitle(_.unescape(self.handler.url_params.title)); - let success_msg = connection_details['server_name'] + '/' + connection_details['database_name'] + '- Database connected'; - alertify.success(success_msg); - if(ref){ - let connection_data = { - 'server_group': self.handler.url_params.sgid, - 'server': connection_details['server'], - 'database': connection_details['database'], - 'user': connection_details['user'], - 'title': connection_details['title'], - 'role': connection_details['role'], - 'password': connection_details['password'], - 'is_allow_new_connection': true, - 'database_name': connection_details['database_name'], - 'server_name': connection_details['server_name'], - 'is_selected': true, - }; - self.connection_list.unshift(connection_data); - self.render_connection(self.connection_list); - loadingDiv.addClass('d-none'); - alertify.newConnectionDialog().destroy(); - ref.close(); - } else { - loadingDiv.addClass('d-none'); - } - } - return true; - }) - .fail(function(xhr) { + 'did': connection_details['database'], + }), + method: 'POST', + contentType: 'application/json', + data: JSON.stringify(connection_details), + }) + .done(function(res) { + if(res.success) { + self.transId = res.data.tran_id; + self.handler.transId = res.data.tran_id; + self.handler.url_params = { + 'did': connection_details['database'], + 'is_query_tool': self.handler.url_params.is_query_tool, + 'server_type': self.handler.url_params.server_type, + 'sgid': connection_details['server_group'], + 'sid': connection_details['server'], + 'title': connection_details['title'], + }; + self.set_editor_title(_.unescape(self.handler.url_params.title)); + self.handler.setTitle(_.unescape(self.handler.url_params.title)); + let success_msg = connection_details['server_name'] + '/' + connection_details['database_name'] + '- Database connected'; + alertify.success(success_msg); + if(ref){ + let connection_data = { + 'server_group': self.handler.url_params.sgid, + 'server': connection_details['server'], + 'database': connection_details['database'], + 'user': connection_details['user'], + 'title': connection_details['title'], + 'role': connection_details['role'], + 'is_allow_new_connection': true, + 'database_name': connection_details['database_name'], + 'server_name': connection_details['server_name'], + 'is_selected': true, + }; + self.connection_list.unshift(connection_data); + self.render_connection(self.connection_list); + loadingDiv.addClass('d-none'); + alertify.newConnectionDialog().destroy(); + ref.close(); + } else { + loadingDiv.addClass('d-none'); + } + } + return true; + }) + .fail(function(xhr) { + if(xhr.status == 428) { + alertify.connectServer('Connect to server', xhr.responseJSON.result, connection_details['server'], false); + } else { + alertify.error(xhr.responseJSON['errormsg']); + } + }); + }, + function() { loadingDiv.addClass('d-none'); - if(xhr.status == 428) { - alertify.connectServer('Connect to server', xhr.responseJSON.result, connection_details['server'], false); - } else { - alertify.error(xhr.responseJSON['errormsg']); - } - }); + alertify.newConnectionDialog().destroy(); + return true; + } + ).set('labels', { + ok: gettext('Yes'), + cancel: gettext('No'), + }); } }, }); diff --git a/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss b/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss index 7fc576a..9d5ba4d 100644 --- a/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss +++ b/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss @@ -371,6 +371,9 @@ div.strikeout:after { height: 100%; } +.selected-connection { + background-color: $color-primary-light; +} /* Setting it to hardcoded white as the SVG generated is having white bg * Need to check what can be done. diff --git a/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py b/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py index 76523b1..642a1eb 100644 --- a/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py +++ b/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py @@ -326,6 +326,29 @@ def register_query_tool_preferences(self): ) ) + self.qt_tab_title = self.preference.register( + 'display', 'qt_tab_title_placeholder', + gettext("Query tool tab title placeholder"), + 'text', '%DATABASE%/%USERNAME%@%SERVER%', + category_label=PREF_LABEL_DISPLAY, + help_str=gettext( + 'Supported placeholders: DATABASE, USERNAME and SERVER. ' + 'You can also provide any string with or without placeholders.' + ) + ) + + self.ve_edt_tab_title = self.preference.register( + 'display', 'vw_edt_tab_title_placeholder', + gettext("View/Edit tab title placeholder"), + 'text', '%SCHEMA%.%TABLE%/%DATABASE%/%USERNAME%@%SERVER%', + category_label=PREF_LABEL_DISPLAY, + help_str=gettext( + 'Supported placeholders: SCHEMA, TABLE, DATABASE, USERNAME and ' + 'SERVER. You can also provide any string with or ' + 'without placeholders.' + ) + ) + self.connection_status = self.preference.register( 'display', 'connection_status_fetch_time', gettext("Connection status refresh rate"), 'integer', 2, ^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: [pgAdmin][RM3794]:Allow User to Change Database Connection from an Open Query Tool Tab @ 2020-10-21 09:24 Akshay Joshi <[email protected]> parent: Nikhil Mohite <[email protected]> 0 siblings, 1 reply; 18+ messages in thread From: Akshay Joshi @ 2020-10-21 09:24 UTC (permalink / raw) To: Nikhil Mohite <[email protected]>; +Cc: pgadmin-hackers Hi Nikhil Following are the review comments: - Connect to any server from the browser tree. Open the query tool and then open the new connection dialog. Click on the "OK" button without changing any field. It shows the popup for "Change connection" which should not be raised because the server is the same. - In the above scenario, if you click on the Yes button it is showing a duplicate entry for the same server. - The server name is not getting changed when we connect to any other server from the new connection. Changes needed in alertify message, tab title, and a combo box. - Remove the "." from the "Change connection." title. - Change the string "Change connection will lose all non committed changes for current connection, do you want to continue?" to "*By changing the connection you will lose all your unsaved data for the current connection.* *Do you want to continue?*" Please fix the above changes and send the patch again. On Wed, Oct 21, 2020 at 11:08 AM Nikhil Mohite < [email protected]> wrote: > Hi Akshay, > > I have updated the existing implementation as per suggestions. > 1. Show servers in server groups in the dropdown. > 2. Current selected connection in the new connection dropdown is now > highlighted as selected. > 3. Notification to the user before the change connection action. > 4. If we connect to the server through a new connection dialog, the tree > will use the same connection and it will not create a new connection. > (In earlier implementation it was asking for the password even we have > connected from a new connection dialog.) > > PFA patch > > Regards, > Nikhil Mohite. > > On Thu, Oct 8, 2020 at 11:39 AM Akshay Joshi < > [email protected]> wrote: > >> Thanks, patch applied. >> >> On Wed, Oct 7, 2020 at 12:11 PM Nikhil Mohite < >> [email protected]> wrote: >> >>> Hi Akshay, >>> >>> I checked the implementation and found 2 locations which I missed in the >>> last patch to remove async: False. >>> I have removed all occurrences of async: False now also added missing >>> loader in required places. >>> >>> PFA updated the patch for the same. >>> >>> Regards, >>> Nikhil Mohite. >>> >>> On Tue, Oct 6, 2020 at 6:19 PM Akshay Joshi < >>> [email protected]> wrote: >>> >>>> Hi Nikhil >>>> >>>> Please verify and remove async = false wherever possible. >>>> >>>> On Tue, Oct 6, 2020 at 5:24 PM Dave Page <[email protected]> >>>> wrote: >>>> >>>>> >>>>> >>>>> On Tue, Oct 6, 2020 at 12:51 PM Murtuza Zabuawala < >>>>> [email protected]> wrote: >>>>> >>>>>> Hi Akshay, >>>>>> >>>>>> We have used aysnc=False in most ajax calls with this feature, It is >>>>>> causing UI hang in case of slow server response. >>>>>> You can try adding a time.sleep() call at the python side response >>>>>> and check the UI hang, I think we should avoid sync calls as much as >>>>>> possible. >>>>>> >>>>> >>>>> I consider a sync ajax call to be a bug. >>>>> >>>>> >>>>>> >>>>>> >>>>>> -- >>>>>> Regards, >>>>>> Murtuza Zabuawala >>>>>> *EDB* >>>>>> *POWER TO POSTGRES* >>>>>> https://www.edbpostgres.com >>>>>> >>>>>> >>>>>> On Thu, Oct 1, 2020 at 1:31 PM Akshay Joshi < >>>>>> [email protected]> wrote: >>>>>> >>>>>>> Thanks, patch applied. >>>>>>> >>>>>>> On Thu, Oct 1, 2020 at 10:42 AM Nikhil Mohite < >>>>>>> [email protected]> wrote: >>>>>>> >>>>>>>> Hi Akshay, >>>>>>>> >>>>>>>> I have resolved the sonarQube issues, PFA updated patch for the >>>>>>>> same. >>>>>>>> >>>>>>>> >>>>>>>> Regards, >>>>>>>> Nikhil Mohite. >>>>>>>> >>>>>>>> >>>>>>>> On Tue, Sep 29, 2020 at 11:31 AM Akshay Joshi < >>>>>>>> [email protected]> wrote: >>>>>>>> >>>>>>>>> Hi Nikhil >>>>>>>>> >>>>>>>>> Your patch introduces 1 new Bug and 13 new code smells, please fix >>>>>>>>> those and resend the patch. >>>>>>>>> >>>>>>>>> On Mon, Sep 28, 2020 at 7:31 PM Nikhil Mohite < >>>>>>>>> [email protected]> wrote: >>>>>>>>> >>>>>>>>>> Hi Akshay, >>>>>>>>>> >>>>>>>>>> I have resolved code conflict issues and sonarqube issues. >>>>>>>>>> PFA updated patch. >>>>>>>>>> >>>>>>>>>> Regards, >>>>>>>>>> Nikhil Mohite. >>>>>>>>>> >>>>>>>>>> On Mon, Sep 28, 2020 at 5:58 PM Akshay Joshi < >>>>>>>>>> [email protected]> wrote: >>>>>>>>>> >>>>>>>>>>> Hi Nikhil >>>>>>>>>>> >>>>>>>>>>> The patch is not applying, rebase, and send it again. Please >>>>>>>>>>> check your code should not create any new SonarQube issues. >>>>>>>>>>> >>>>>>>>>>> On Mon, Sep 28, 2020 at 11:20 AM Nikhil Mohite < >>>>>>>>>>> [email protected]> wrote: >>>>>>>>>>> >>>>>>>>>>>> Hi Akshay, >>>>>>>>>>>> >>>>>>>>>>>> I have resolved all the review comments and also updated the >>>>>>>>>>>> test cases as per the new implementation. >>>>>>>>>>>> >>>>>>>>>>>> PFA updated patch. >>>>>>>>>>>> >>>>>>>>>>>> >>>>>>>>>>>> >>>>>>>>>>>> On Mon, Sep 21, 2020 at 5:24 PM Akshay Joshi < >>>>>>>>>>>> [email protected]> wrote: >>>>>>>>>>>> >>>>>>>>>>>>> Hi Nikhil >>>>>>>>>>>>> >>>>>>>>>>>>> Following are the initial review comments: >>>>>>>>>>>>> >>>>>>>>>>>>> - Open View/Edit data on any table and click on the same >>>>>>>>>>>>> database connection and then click on the Execute button. Got >>>>>>>>>>>>> "get_primary_keys() takes 1 positional argument but 2 were given" error. >>>>>>>>>>>>> - In my opinion, we should hide the option to change the >>>>>>>>>>>>> database connection for View/Edit Data. >>>>>>>>>>>>> - If the user clicks on the same database connection >>>>>>>>>>>>> multiple times then no need to change the backend connection and >>>>>>>>>>>>> transaction id. Add validation at the backend, no action required in this >>>>>>>>>>>>> case. >>>>>>>>>>>>> - The role option is missing from the "connect to server" >>>>>>>>>>>>> dialog. >>>>>>>>>>>>> - The Password field should not be there on the "connect >>>>>>>>>>>>> to server" dialog. Sometimes we saved the password so asking a password >>>>>>>>>>>>> every time is not correct. Check the pgAdmin 3 behavior. >>>>>>>>>>>>> >>>>>>>>>>>>> Code review still remains. >>>>>>>>>>>>> >>>>>>>>>>>>> On Thu, Sep 17, 2020 at 4:15 PM Nikhil Mohite < >>>>>>>>>>>>> [email protected]> wrote: >>>>>>>>>>>>> >>>>>>>>>>>>>> Hi Team, >>>>>>>>>>>>>> >>>>>>>>>>>>>> Regarding RM-3794 >>>>>>>>>>>>>> <https://redmine.postgresql.org/issues/3794; allow the user >>>>>>>>>>>>>> to change the database connection from an open query tool: >>>>>>>>>>>>>> I have implemented the feature and also added documentation >>>>>>>>>>>>>> for it. >>>>>>>>>>>>>> >>>>>>>>>>>>>> PFA patch. >>>>>>>>>>>>>> >>>>>>>>>>>>>> -- >>>>>>>>>>>>>> *Thanks & Regards,* >>>>>>>>>>>>>> *Nikhil Mohite* >>>>>>>>>>>>>> *Software Engineer.* >>>>>>>>>>>>>> *EDB Postgres* <https://www.enterprisedb.com/; >>>>>>>>>>>>>> *Mob.No: +91-7798364578.* >>>>>>>>>>>>>> >>>>>>>>>>>>> >>>>>>>>>>>>> >>>>>>>>>>>>> -- >>>>>>>>>>>>> *Thanks & Regards* >>>>>>>>>>>>> *Akshay Joshi* >>>>>>>>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>>>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>>>>>>>> >>>>>>>>>>>>> *Mobile: +91 976-788-8246* >>>>>>>>>>>>> >>>>>>>>>>>> >>>>>>>>>>> >>>>>>>>>>> -- >>>>>>>>>>> *Thanks & Regards* >>>>>>>>>>> *Akshay Joshi* >>>>>>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>>>>>> >>>>>>>>>>> *Mobile: +91 976-788-8246* >>>>>>>>>>> >>>>>>>>>> >>>>>>>>> >>>>>>>>> -- >>>>>>>>> *Thanks & Regards* >>>>>>>>> *Akshay Joshi* >>>>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>>>> >>>>>>>>> *Mobile: +91 976-788-8246* >>>>>>>>> >>>>>>>> >>>>>>> >>>>>>> -- >>>>>>> *Thanks & Regards* >>>>>>> *Akshay Joshi* >>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>> >>>>>>> *Mobile: +91 976-788-8246* >>>>>>> >>>>>> >>>>> >>>>> -- >>>>> Dave Page >>>>> VP & Chief Architect, Database Infrastructure >>>>> EDB: http://www.enterprisedb.com >>>>> >>>>> Blog: http://pgsnake.blogspot.com >>>>> Twitter: @pgsnake >>>>> >>>> >>>> >>>> -- >>>> *Thanks & Regards* >>>> *Akshay Joshi* >>>> *pgAdmin Hacker | Sr. Software Architect* >>>> *EDB Postgres <http://edbpostgres.com>* >>>> >>>> *Mobile: +91 976-788-8246* >>>> >>> >> >> -- >> *Thanks & Regards* >> *Akshay Joshi* >> *pgAdmin Hacker | Sr. Software Architect* >> *EDB Postgres <http://edbpostgres.com>* >> >> *Mobile: +91 976-788-8246* >> > -- *Thanks & Regards* *Akshay Joshi* *pgAdmin Hacker | Sr. Software Architect* *EDB Postgres <http://edbpostgres.com>* *Mobile: +91 976-788-8246* ^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: [pgAdmin][RM3794]:Allow User to Change Database Connection from an Open Query Tool Tab @ 2020-10-21 11:15 Nikhil Mohite <[email protected]> parent: Akshay Joshi <[email protected]> 0 siblings, 1 reply; 18+ messages in thread From: Nikhil Mohite @ 2020-10-21 11:15 UTC (permalink / raw) To: Akshay Joshi <[email protected]>; +Cc: pgadmin-hackers Hi Akshay, I have fixed the review comments, PFA the updated patch for the same. Regards, Nikhil Mohite. On Wed, Oct 21, 2020 at 2:55 PM Akshay Joshi <[email protected]> wrote: > Hi Nikhil > > Following are the review comments: > > - Connect to any server from the browser tree. Open the query tool and > then open the new connection dialog. Click on the "OK" button without > changing any field. It shows the popup for "Change connection" which should > not be raised because the server is the same. > - In the above scenario, if you click on the Yes button it is showing > a duplicate entry for the same server. > - The server name is not getting changed when we connect to any > other server from the new connection. Changes needed in alertify message, > tab title, and a combo box. > - Remove the "." from the "Change connection." title. > - Change the string "Change connection will lose all non committed > changes for current connection, do you want to continue?" to "*By > changing the connection you will lose all your unsaved data for the current > connection.* > *Do you want to continue?*" > > Please fix the above changes and send the patch again. > > On Wed, Oct 21, 2020 at 11:08 AM Nikhil Mohite < > [email protected]> wrote: > >> Hi Akshay, >> >> I have updated the existing implementation as per suggestions. >> 1. Show servers in server groups in the dropdown. >> 2. Current selected connection in the new connection dropdown is now >> highlighted as selected. >> 3. Notification to the user before the change connection action. >> 4. If we connect to the server through a new connection dialog, the tree >> will use the same connection and it will not create a new connection. >> (In earlier implementation it was asking for the password even we have >> connected from a new connection dialog.) >> >> PFA patch >> >> Regards, >> Nikhil Mohite. >> >> On Thu, Oct 8, 2020 at 11:39 AM Akshay Joshi < >> [email protected]> wrote: >> >>> Thanks, patch applied. >>> >>> On Wed, Oct 7, 2020 at 12:11 PM Nikhil Mohite < >>> [email protected]> wrote: >>> >>>> Hi Akshay, >>>> >>>> I checked the implementation and found 2 locations which I missed in >>>> the last patch to remove async: False. >>>> I have removed all occurrences of async: False now also added missing >>>> loader in required places. >>>> >>>> PFA updated the patch for the same. >>>> >>>> Regards, >>>> Nikhil Mohite. >>>> >>>> On Tue, Oct 6, 2020 at 6:19 PM Akshay Joshi < >>>> [email protected]> wrote: >>>> >>>>> Hi Nikhil >>>>> >>>>> Please verify and remove async = false wherever possible. >>>>> >>>>> On Tue, Oct 6, 2020 at 5:24 PM Dave Page <[email protected]> >>>>> wrote: >>>>> >>>>>> >>>>>> >>>>>> On Tue, Oct 6, 2020 at 12:51 PM Murtuza Zabuawala < >>>>>> [email protected]> wrote: >>>>>> >>>>>>> Hi Akshay, >>>>>>> >>>>>>> We have used aysnc=False in most ajax calls with this feature, It is >>>>>>> causing UI hang in case of slow server response. >>>>>>> You can try adding a time.sleep() call at the python side response >>>>>>> and check the UI hang, I think we should avoid sync calls as much as >>>>>>> possible. >>>>>>> >>>>>> >>>>>> I consider a sync ajax call to be a bug. >>>>>> >>>>>> >>>>>>> >>>>>>> >>>>>>> -- >>>>>>> Regards, >>>>>>> Murtuza Zabuawala >>>>>>> *EDB* >>>>>>> *POWER TO POSTGRES* >>>>>>> https://www.edbpostgres.com >>>>>>> >>>>>>> >>>>>>> On Thu, Oct 1, 2020 at 1:31 PM Akshay Joshi < >>>>>>> [email protected]> wrote: >>>>>>> >>>>>>>> Thanks, patch applied. >>>>>>>> >>>>>>>> On Thu, Oct 1, 2020 at 10:42 AM Nikhil Mohite < >>>>>>>> [email protected]> wrote: >>>>>>>> >>>>>>>>> Hi Akshay, >>>>>>>>> >>>>>>>>> I have resolved the sonarQube issues, PFA updated patch for the >>>>>>>>> same. >>>>>>>>> >>>>>>>>> >>>>>>>>> Regards, >>>>>>>>> Nikhil Mohite. >>>>>>>>> >>>>>>>>> >>>>>>>>> On Tue, Sep 29, 2020 at 11:31 AM Akshay Joshi < >>>>>>>>> [email protected]> wrote: >>>>>>>>> >>>>>>>>>> Hi Nikhil >>>>>>>>>> >>>>>>>>>> Your patch introduces 1 new Bug and 13 new code smells, please >>>>>>>>>> fix those and resend the patch. >>>>>>>>>> >>>>>>>>>> On Mon, Sep 28, 2020 at 7:31 PM Nikhil Mohite < >>>>>>>>>> [email protected]> wrote: >>>>>>>>>> >>>>>>>>>>> Hi Akshay, >>>>>>>>>>> >>>>>>>>>>> I have resolved code conflict issues and sonarqube issues. >>>>>>>>>>> PFA updated patch. >>>>>>>>>>> >>>>>>>>>>> Regards, >>>>>>>>>>> Nikhil Mohite. >>>>>>>>>>> >>>>>>>>>>> On Mon, Sep 28, 2020 at 5:58 PM Akshay Joshi < >>>>>>>>>>> [email protected]> wrote: >>>>>>>>>>> >>>>>>>>>>>> Hi Nikhil >>>>>>>>>>>> >>>>>>>>>>>> The patch is not applying, rebase, and send it again. Please >>>>>>>>>>>> check your code should not create any new SonarQube issues. >>>>>>>>>>>> >>>>>>>>>>>> On Mon, Sep 28, 2020 at 11:20 AM Nikhil Mohite < >>>>>>>>>>>> [email protected]> wrote: >>>>>>>>>>>> >>>>>>>>>>>>> Hi Akshay, >>>>>>>>>>>>> >>>>>>>>>>>>> I have resolved all the review comments and also updated the >>>>>>>>>>>>> test cases as per the new implementation. >>>>>>>>>>>>> >>>>>>>>>>>>> PFA updated patch. >>>>>>>>>>>>> >>>>>>>>>>>>> >>>>>>>>>>>>> >>>>>>>>>>>>> On Mon, Sep 21, 2020 at 5:24 PM Akshay Joshi < >>>>>>>>>>>>> [email protected]> wrote: >>>>>>>>>>>>> >>>>>>>>>>>>>> Hi Nikhil >>>>>>>>>>>>>> >>>>>>>>>>>>>> Following are the initial review comments: >>>>>>>>>>>>>> >>>>>>>>>>>>>> - Open View/Edit data on any table and click on the same >>>>>>>>>>>>>> database connection and then click on the Execute button. Got >>>>>>>>>>>>>> "get_primary_keys() takes 1 positional argument but 2 were given" error. >>>>>>>>>>>>>> - In my opinion, we should hide the option to change the >>>>>>>>>>>>>> database connection for View/Edit Data. >>>>>>>>>>>>>> - If the user clicks on the same database connection >>>>>>>>>>>>>> multiple times then no need to change the backend connection and >>>>>>>>>>>>>> transaction id. Add validation at the backend, no action required in this >>>>>>>>>>>>>> case. >>>>>>>>>>>>>> - The role option is missing from the "connect to server" >>>>>>>>>>>>>> dialog. >>>>>>>>>>>>>> - The Password field should not be there on the "connect >>>>>>>>>>>>>> to server" dialog. Sometimes we saved the password so asking a password >>>>>>>>>>>>>> every time is not correct. Check the pgAdmin 3 behavior. >>>>>>>>>>>>>> >>>>>>>>>>>>>> Code review still remains. >>>>>>>>>>>>>> >>>>>>>>>>>>>> On Thu, Sep 17, 2020 at 4:15 PM Nikhil Mohite < >>>>>>>>>>>>>> [email protected]> wrote: >>>>>>>>>>>>>> >>>>>>>>>>>>>>> Hi Team, >>>>>>>>>>>>>>> >>>>>>>>>>>>>>> Regarding RM-3794 >>>>>>>>>>>>>>> <https://redmine.postgresql.org/issues/3794; allow the user >>>>>>>>>>>>>>> to change the database connection from an open query tool: >>>>>>>>>>>>>>> I have implemented the feature and also added documentation >>>>>>>>>>>>>>> for it. >>>>>>>>>>>>>>> >>>>>>>>>>>>>>> PFA patch. >>>>>>>>>>>>>>> >>>>>>>>>>>>>>> -- >>>>>>>>>>>>>>> *Thanks & Regards,* >>>>>>>>>>>>>>> *Nikhil Mohite* >>>>>>>>>>>>>>> *Software Engineer.* >>>>>>>>>>>>>>> *EDB Postgres* <https://www.enterprisedb.com/; >>>>>>>>>>>>>>> *Mob.No: +91-7798364578.* >>>>>>>>>>>>>>> >>>>>>>>>>>>>> >>>>>>>>>>>>>> >>>>>>>>>>>>>> -- >>>>>>>>>>>>>> *Thanks & Regards* >>>>>>>>>>>>>> *Akshay Joshi* >>>>>>>>>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>>>>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>>>>>>>>> >>>>>>>>>>>>>> *Mobile: +91 976-788-8246* >>>>>>>>>>>>>> >>>>>>>>>>>>> >>>>>>>>>>>> >>>>>>>>>>>> -- >>>>>>>>>>>> *Thanks & Regards* >>>>>>>>>>>> *Akshay Joshi* >>>>>>>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>>>>>>> >>>>>>>>>>>> *Mobile: +91 976-788-8246* >>>>>>>>>>>> >>>>>>>>>>> >>>>>>>>>> >>>>>>>>>> -- >>>>>>>>>> *Thanks & Regards* >>>>>>>>>> *Akshay Joshi* >>>>>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>>>>> >>>>>>>>>> *Mobile: +91 976-788-8246* >>>>>>>>>> >>>>>>>>> >>>>>>>> >>>>>>>> -- >>>>>>>> *Thanks & Regards* >>>>>>>> *Akshay Joshi* >>>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>>> >>>>>>>> *Mobile: +91 976-788-8246* >>>>>>>> >>>>>>> >>>>>> >>>>>> -- >>>>>> Dave Page >>>>>> VP & Chief Architect, Database Infrastructure >>>>>> EDB: http://www.enterprisedb.com >>>>>> >>>>>> Blog: http://pgsnake.blogspot.com >>>>>> Twitter: @pgsnake >>>>>> >>>>> >>>>> >>>>> -- >>>>> *Thanks & Regards* >>>>> *Akshay Joshi* >>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>> *EDB Postgres <http://edbpostgres.com>* >>>>> >>>>> *Mobile: +91 976-788-8246* >>>>> >>>> >>> >>> -- >>> *Thanks & Regards* >>> *Akshay Joshi* >>> *pgAdmin Hacker | Sr. Software Architect* >>> *EDB Postgres <http://edbpostgres.com>* >>> >>> *Mobile: +91 976-788-8246* >>> >> > > -- > *Thanks & Regards* > *Akshay Joshi* > *pgAdmin Hacker | Sr. Software Architect* > *EDB Postgres <http://edbpostgres.com>* > > *Mobile: +91 976-788-8246* > Attachments: [application/octet-stream] RM_3794_review_comments_v2.patch (23.6K, 3-RM_3794_review_comments_v2.patch) download | inline diff: diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py index 2630d1e..ab4bea4 100644 --- a/web/pgadmin/browser/server_groups/servers/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/__init__.py @@ -1310,7 +1310,8 @@ class ServerNode(PGChildNodeView): # Connect the Server manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) - manager.update(server) + if not manager.connection().connected(): + manager.update(server) conn = manager.connection() # Get enc key diff --git a/web/pgadmin/static/js/sqleditor/new_connection_dialog.js b/web/pgadmin/static/js/sqleditor/new_connection_dialog.js index 10850e1..b19a50a 100644 --- a/web/pgadmin/static/js/sqleditor/new_connection_dialog.js +++ b/web/pgadmin/static/js/sqleditor/new_connection_dialog.js @@ -243,7 +243,6 @@ let NewConnectionDialog = { 'title': _.escape(tab_title), 'user': newConnCollectionModel['user'], 'role': newConnCollectionModel['role'], - 'password': response.password, 'server_name': _.escape(response.server_name), 'database_name': _.escape(selected_database_name), 'is_selected': false, @@ -260,13 +259,12 @@ let NewConnectionDialog = { setTimeout(function(){ Alertify.newConnectionDialog('Connect to server.').resizeTo(pgAdmin.Browser.stdW.md,pgAdmin.Browser.stdH.md); }, 500); - }).fail(function(error) { + }).fail(function() { Alertify.alert().setting({ 'title': gettext('Connection lost'), 'label':gettext('Ok'), 'message': gettext('Connection to the server has been lost.'), 'onok': function(){ - alert(error); //Close the window after connection is lost window.close(); }, diff --git a/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js b/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js index 09b2979..0bbda60 100644 --- a/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js +++ b/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js @@ -78,7 +78,6 @@ export default function newConnectionDialogModel(response, sgid, sid) { server: parseInt(sid), database: null, user: null, - password: null, server_name: server_name, database_name: database_name, }, @@ -92,7 +91,63 @@ export default function newConnectionDialogModel(response, sgid, sid) { select2: { allowClear: false, }, + transform: function(data) { + let group_template_options = []; + for (let key in data) { + if (data.hasOwnProperty(key)) { + group_template_options.push({'group': key, 'optval': data[key]}); + } + } + return group_template_options; + }, control: Backform.Select2Control.extend({ + template: _.template([ + '<% if(label == false) {} else {%>', + ' <label class="<%=Backform.controlLabelClassName%>"><%=label%></label>', + '<% }%>', + '<div class="<%=controlsClassName%>">', + ' <select class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>"', + ' name="<%=name%>" value="<%-value%>" <%=disabled ? "disabled" : ""%>', + ' <%=required ? "required" : ""%><%= select2.multiple ? " multiple>" : ">" %>', + ' <%=select2.first_empty ? " <option></option>" : ""%>', + ' <% for (var i=0; i < options.length; i++) {%>', + ' <% if (options[i].group) { %>', + ' <% var group = options[i].group; %>', + ' <% if (options[i].optval) { %> <% var option_length = options[i].optval.length; %>', + ' <optgroup label="<%=group%>">', + ' <% for (var subindex=0; subindex < option_length; subindex++) {%>', + ' <% var option = options[i].optval[subindex]; %>', + ' <option ', + ' <% if (option.image) { %> data-image=<%=option.image%> <%}%>', + ' <% if (option.connected) { %> data-connected=connected <%}%>', + ' value=<%- formatter.fromRaw(option.value) %>', + ' <% if (option.selected) {%>selected="selected"<%} else {%>', + ' <% if (!select2.multiple && option.value === rawValue) {%>selected="selected"<%}%>', + ' <% if (select2.multiple && rawValue && rawValue.indexOf(option.value) != -1){%>selected="selected" data-index="rawValue.indexOf(option.value)"<%}%>', + ' <%}%>', + ' <%= disabled ? "disabled" : ""%>><%-option.label%></option>', + ' <%}%>', + ' </optgroup>', + ' <%}%>', + ' <%} else {%>', + ' <% var option = options[i]; %>', + ' <option ', + ' <% if (option.image) { %> data-image=<%=option.image%> <%}%>', + ' <% if (option.connected) { %> data-connected=connected <%}%>', + ' value=<%- formatter.fromRaw(option.value) %>', + ' <% if (option.selected) {%>selected="selected"<%} else {%>', + ' <% if (!select2.multiple && option.value === rawValue) {%>selected="selected"<%}%>', + ' <% if (select2.multiple && rawValue && rawValue.indexOf(option.value) != -1){%>selected="selected" data-index="rawValue.indexOf(option.value)"<%}%>', + ' <%}%>', + ' <%= disabled ? "disabled" : ""%>><%-option.label%></option>', + ' <%}%>', + ' <%}%>', + ' </select>', + ' <% if (helpMessage && helpMessage.length) { %>', + ' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>', + ' <% } %>', + '</div>', + ].join('\n')), connect: function(self) { let local_self = self; @@ -147,11 +202,14 @@ export default function newConnectionDialogModel(response, sgid, sid) { local_self.model.attributes.user = null; local_self.model.attributes.role = null; Backform.Select2Control.prototype.onChange.apply(local_self, arguments); - response.server_list.forEach(function(obj){ - if(obj.id==self.model.changed.server) { - response.server_name = obj.name; - } + Object.keys(response.server_list).forEach(key => { + response.server_list[key].forEach(option => { + if (option.value == local_self.getValueFromDOM()) { + response.server_name = option.label; + } + }); }); + loadingDiv.addClass('d-none'); alertify.connectServer().destroy(); }) @@ -160,8 +218,6 @@ export default function newConnectionDialogModel(response, sgid, sid) { alertify.connectServer().destroy(); alertify.connectServer('Connect to server', xhr.responseJSON.result, local_self.getValueFromDOM()); }); - } else { - response.password = $('#password').val(); } } else { local_self.model.attributes.database = null; @@ -178,6 +234,19 @@ export default function newConnectionDialogModel(response, sgid, sid) { render: function() { let self = this; self.connect(self); + Object.keys(response.server_list).forEach(key => { + response.server_list[key].forEach(option => { + if (option.value == parseInt(sid)) { + response.server_name = option.label; + } + }); + }); + var transform = self.field.get('transform') || self.defaults.transform; + if (transform && _.isFunction(transform)) { + self.field.set('options', transform.bind(self, response.server_list)); + } else { + self.field.set('options', response.server_list); + } return Backform.Select2Control.prototype.render.apply(self, arguments); }, onChange: function() { @@ -200,10 +269,12 @@ export default function newConnectionDialogModel(response, sgid, sid) { }, }).done(function () { Backform.Select2Control.prototype.onChange.apply(self, arguments); - response.server_list.forEach(function(obj){ - if(obj.id==self.model.changed.server) { - response.server_name = obj.name; - } + Object.keys(response.server_list).forEach(key => { + response.server_list[key].forEach(option => { + if (option.value == self.getValueFromDOM()) { + response.server_name = option.label; + } + }); }); loadingDiv.addClass('d-none'); }).fail(function(xhr){ @@ -213,17 +284,6 @@ export default function newConnectionDialogModel(response, sgid, sid) { }, }), - options: function() { - return _.map(response.server_list, (obj) => { - if (obj.id == parseInt(sid)) - response.server_name = obj.name; - - return { - value: obj.id, - label: obj.name, - }; - }); - }, }, { id: 'database', @@ -302,27 +362,6 @@ export default function newConnectionDialogModel(response, sgid, sid) { url: 'sqleditor.get_new_connection_role', disabled: false, }, - /*{ - id: 'password', - name: 'password', - label: gettext('Password'tools/sqleditor/__init__.py), - type: 'password', - editable: true, - disabled: true, - deps: ['user'], - control: Backform.InputControl.extend({ - render: function() { - let self = this; - self.model.attributes.password = null; - Backform.InputControl.prototype.render.apply(self, arguments); - return self; - }, - onChange: function() { - let self = this; - Backform.InputControl.prototype.onChange.apply(self, arguments); - }, - }), - },*/ ], validate: function() { let msg = null; @@ -336,11 +375,6 @@ export default function newConnectionDialogModel(response, sgid, sid) { this.errorModel.set('user', msg); return msg; } - /*else if((this.attributes.password == '' || _.isUndefined(this.get('password')) || _.isNull(this.get('password')))) { - msg = gettext('Please enter password'); - this.errorModel.set('password', msg); - return msg; - }*/ return null; }, }); diff --git a/web/pgadmin/tools/debugger/__init__.py b/web/pgadmin/tools/debugger/__init__.py index 1949709..422a8ae 100644 --- a/web/pgadmin/tools/debugger/__init__.py +++ b/web/pgadmin/tools/debugger/__init__.py @@ -98,6 +98,18 @@ class DebuggerModule(PgAdminModule): ) ) + self.tab_title = self.preference.register( + 'display', 'debugger_tab_title_placeholder', + gettext("Debugger tab title placeholder"), + 'text', '%FUNCTION%/%SCHEMA%/%DATABASE%', + category_label=PREF_LABEL_DISPLAY, + help_str=gettext( + 'Supported placeholders: FUNCTION, SCHEMA and DATABASE. ' + 'You can also provide any string with or ' + 'without placeholders' + ) + ) + self.preference.register( 'keyboard_shortcuts', 'btn_start', gettext('Accesskey (Continue/Start)'), 'keyboardshortcut', diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py index 9da8842..f9fb26f 100644 --- a/web/pgadmin/tools/sqleditor/__init__.py +++ b/web/pgadmin/tools/sqleditor/__init__.py @@ -46,7 +46,7 @@ from pgadmin.tools.sqleditor.utils.macros import get_macros,\ get_user_macros, set_macros from pgadmin.utils.constants import MIMETYPE_APP_JS, \ SERVER_CONNECTION_CLOSED, ERROR_MSG_TRANS_ID_NOT_FOUND, ERROR_FETCHING_DATA -from pgadmin.model import Server +from pgadmin.model import Server, ServerGroup from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry MODULE_NAME = 'sqleditor' @@ -1489,10 +1489,14 @@ def get_new_connection_data(sgid, sid=None): :extract_sql_from_network_parameters, """ try: - # if sid and not did: + server_groups = ServerGroup.query.all() + server_group_data = {server_group.name: [] for server_group in + server_groups} servers = Server.query.all() - server_list = [ - {'name': server.serialize['name'], "id": server.serialize['id']} + + [server_group_data[server.servers.name].append( + {'label': server.serialize['name'], + "value": server.serialize['id']}) for server in servers] msg = "Success" @@ -1501,7 +1505,7 @@ def get_new_connection_data(sgid, sid=None): 'status': True, 'msg': msg, 'result': { - 'server_list': server_list + 'server_list': server_group_data } } ) diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js index d3a2d25..26c88bc 100644 --- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js +++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js @@ -173,8 +173,13 @@ define('tools.querytool', [ var dropdownElement = document.getElementById('connections-list'); dropdownElement.innerHTML = ''; data_list.forEach((option, index) => { - $('#connections-list').append('<li class="connection-list-item" data-index='+ index +'><a class="dropdown-item" href="#" tabindex="0">'+ option.title +'</a></li>'); - + var opt = ''; + if ('is_selected' in option && option['is_selected']) { + opt = '<li class="connection-list-item selected-connection" data-index='+ index +'><a class="dropdown-item" href="#" tabindex="0">'+ option.title +'</a></li>'; + } else { + opt = '<li class="connection-list-item" data-index='+ index +'><a class="dropdown-item" href="#" tabindex="0">'+ option.title +'</a></li>'; + } + $('#connections-list').append(opt); }); var self = this; $('.connection-list-item').click(function() { @@ -2131,7 +2136,6 @@ define('tools.querytool', [ on_change_connection: function(connection_details, ref) { if(!connection_details['is_selected']) { var self = this; - self.set_selected_option(connection_details); var loadingDiv = null; var msgDiv = null; if(ref){ @@ -2145,66 +2149,78 @@ define('tools.querytool', [ msgDiv = loadingDiv.find('.sql-editor-busy-text'); } - $.ajax({ - url: url_for('datagrid.update_query_tool_connection', { - 'trans_id': self.transId, - 'sgid': connection_details['server_group'], - 'sid': connection_details['server'], - 'did': connection_details['database'], - }), - method: 'POST', - contentType: 'application/json', - data: JSON.stringify(connection_details), - }) - .done(function(res) { - if(res.success) { - self.transId = res.data.tran_id; - self.handler.transId = res.data.tran_id; - self.handler.url_params = { - 'did': connection_details['database'], - 'is_query_tool': self.handler.url_params.is_query_tool, - 'server_type': self.handler.url_params.server_type, + alertify.confirm(gettext('Change connection'), + gettext('By changing the connection you will lose all your unsaved data for the current connection. <br> Do you want to continue?'), + function() { + self.set_selected_option(connection_details); + $.ajax({ + url: url_for('datagrid.update_query_tool_connection', { + 'trans_id': self.transId, 'sgid': connection_details['server_group'], 'sid': connection_details['server'], - 'title': connection_details['title'], - }; - self.set_editor_title(_.unescape(self.handler.url_params.title)); - self.handler.setTitle(_.unescape(self.handler.url_params.title)); - let success_msg = connection_details['server_name'] + '/' + connection_details['database_name'] + '- Database connected'; - alertify.success(success_msg); - if(ref){ - let connection_data = { - 'server_group': self.handler.url_params.sgid, - 'server': connection_details['server'], - 'database': connection_details['database'], - 'user': connection_details['user'], - 'title': connection_details['title'], - 'role': connection_details['role'], - 'password': connection_details['password'], - 'is_allow_new_connection': true, - 'database_name': connection_details['database_name'], - 'server_name': connection_details['server_name'], - 'is_selected': true, - }; - self.connection_list.unshift(connection_data); - self.render_connection(self.connection_list); - loadingDiv.addClass('d-none'); - alertify.newConnectionDialog().destroy(); - ref.close(); - } else { - loadingDiv.addClass('d-none'); - } - } - return true; - }) - .fail(function(xhr) { + 'did': connection_details['database'], + }), + method: 'POST', + contentType: 'application/json', + data: JSON.stringify(connection_details), + }) + .done(function(res) { + if(res.success) { + self.transId = res.data.tran_id; + self.handler.transId = res.data.tran_id; + self.handler.url_params = { + 'did': connection_details['database'], + 'is_query_tool': self.handler.url_params.is_query_tool, + 'server_type': self.handler.url_params.server_type, + 'sgid': connection_details['server_group'], + 'sid': connection_details['server'], + 'title': connection_details['title'], + }; + self.set_editor_title(_.unescape(self.handler.url_params.title)); + self.handler.setTitle(_.unescape(self.handler.url_params.title)); + let success_msg = connection_details['server_name'] + '/' + connection_details['database_name'] + '- Database connected'; + alertify.success(success_msg); + if(ref){ + let connection_data = { + 'server_group': self.handler.url_params.sgid, + 'server': connection_details['server'], + 'database': connection_details['database'], + 'user': connection_details['user'], + 'title': connection_details['title'], + 'role': connection_details['role'], + 'is_allow_new_connection': true, + 'database_name': connection_details['database_name'], + 'server_name': connection_details['server_name'], + 'is_selected': true, + }; + self.connection_list.unshift(connection_data); + self.render_connection(self.connection_list); + loadingDiv.addClass('d-none'); + alertify.newConnectionDialog().destroy(); + ref.close(); + } else { + loadingDiv.addClass('d-none'); + } + } + return true; + }) + .fail(function(xhr) { + if(xhr.status == 428) { + alertify.connectServer('Connect to server', xhr.responseJSON.result, connection_details['server'], false); + } else { + alertify.error(xhr.responseJSON['errormsg']); + } + }); + }, + function() { loadingDiv.addClass('d-none'); - if(xhr.status == 428) { - alertify.connectServer('Connect to server', xhr.responseJSON.result, connection_details['server'], false); - } else { - alertify.error(xhr.responseJSON['errormsg']); - } - }); + alertify.newConnectionDialog().destroy(); + return true; + } + ).set('labels', { + ok: gettext('Yes'), + cancel: gettext('No'), + }); } }, }); @@ -2542,7 +2558,7 @@ define('tools.querytool', [ 'server_group': self.gridView.handler.url_params.sgid, 'server': self.gridView.handler.url_params.sid, 'database': self.gridView.handler.url_params.did, - 'user': null, + 'user': server_data.data.user.name, 'role': null, 'title': _.unescape(url_params.title), 'is_allow_new_connection': false, diff --git a/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss b/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss index 7fc576a..9d5ba4d 100644 --- a/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss +++ b/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss @@ -371,6 +371,9 @@ div.strikeout:after { height: 100%; } +.selected-connection { + background-color: $color-primary-light; +} /* Setting it to hardcoded white as the SVG generated is having white bg * Need to check what can be done. diff --git a/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py b/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py index 76523b1..642a1eb 100644 --- a/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py +++ b/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py @@ -326,6 +326,29 @@ def register_query_tool_preferences(self): ) ) + self.qt_tab_title = self.preference.register( + 'display', 'qt_tab_title_placeholder', + gettext("Query tool tab title placeholder"), + 'text', '%DATABASE%/%USERNAME%@%SERVER%', + category_label=PREF_LABEL_DISPLAY, + help_str=gettext( + 'Supported placeholders: DATABASE, USERNAME and SERVER. ' + 'You can also provide any string with or without placeholders.' + ) + ) + + self.ve_edt_tab_title = self.preference.register( + 'display', 'vw_edt_tab_title_placeholder', + gettext("View/Edit tab title placeholder"), + 'text', '%SCHEMA%.%TABLE%/%DATABASE%/%USERNAME%@%SERVER%', + category_label=PREF_LABEL_DISPLAY, + help_str=gettext( + 'Supported placeholders: SCHEMA, TABLE, DATABASE, USERNAME and ' + 'SERVER. You can also provide any string with or ' + 'without placeholders.' + ) + ) + self.connection_status = self.preference.register( 'display', 'connection_status_fetch_time', gettext("Connection status refresh rate"), 'integer', 2, ^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: [pgAdmin][RM3794]:Allow User to Change Database Connection from an Open Query Tool Tab @ 2020-10-21 11:49 Akshay Joshi <[email protected]> parent: Nikhil Mohite <[email protected]> 0 siblings, 0 replies; 18+ messages in thread From: Akshay Joshi @ 2020-10-21 11:49 UTC (permalink / raw) To: Nikhil Mohite <[email protected]>; +Cc: pgadmin-hackers Thanks, patch applied. On Wed, Oct 21, 2020 at 4:45 PM Nikhil Mohite < [email protected]> wrote: > Hi Akshay, > > I have fixed the review comments, PFA the updated patch for the same. > > > Regards, > Nikhil Mohite. > > On Wed, Oct 21, 2020 at 2:55 PM Akshay Joshi < > [email protected]> wrote: > >> Hi Nikhil >> >> Following are the review comments: >> >> - Connect to any server from the browser tree. Open the query tool >> and then open the new connection dialog. Click on the "OK" button without >> changing any field. It shows the popup for "Change connection" which should >> not be raised because the server is the same. >> - In the above scenario, if you click on the Yes button it is showing >> a duplicate entry for the same server. >> - The server name is not getting changed when we connect to any >> other server from the new connection. Changes needed in alertify message, >> tab title, and a combo box. >> - Remove the "." from the "Change connection." title. >> - Change the string "Change connection will lose all non committed >> changes for current connection, do you want to continue?" to "*By >> changing the connection you will lose all your unsaved data for the current >> connection.* >> *Do you want to continue?*" >> >> Please fix the above changes and send the patch again. >> >> On Wed, Oct 21, 2020 at 11:08 AM Nikhil Mohite < >> [email protected]> wrote: >> >>> Hi Akshay, >>> >>> I have updated the existing implementation as per suggestions. >>> 1. Show servers in server groups in the dropdown. >>> 2. Current selected connection in the new connection dropdown is now >>> highlighted as selected. >>> 3. Notification to the user before the change connection action. >>> 4. If we connect to the server through a new connection dialog, the tree >>> will use the same connection and it will not create a new connection. >>> (In earlier implementation it was asking for the password even we have >>> connected from a new connection dialog.) >>> >>> PFA patch >>> >>> Regards, >>> Nikhil Mohite. >>> >>> On Thu, Oct 8, 2020 at 11:39 AM Akshay Joshi < >>> [email protected]> wrote: >>> >>>> Thanks, patch applied. >>>> >>>> On Wed, Oct 7, 2020 at 12:11 PM Nikhil Mohite < >>>> [email protected]> wrote: >>>> >>>>> Hi Akshay, >>>>> >>>>> I checked the implementation and found 2 locations which I missed in >>>>> the last patch to remove async: False. >>>>> I have removed all occurrences of async: False now also added missing >>>>> loader in required places. >>>>> >>>>> PFA updated the patch for the same. >>>>> >>>>> Regards, >>>>> Nikhil Mohite. >>>>> >>>>> On Tue, Oct 6, 2020 at 6:19 PM Akshay Joshi < >>>>> [email protected]> wrote: >>>>> >>>>>> Hi Nikhil >>>>>> >>>>>> Please verify and remove async = false wherever possible. >>>>>> >>>>>> On Tue, Oct 6, 2020 at 5:24 PM Dave Page <[email protected]> >>>>>> wrote: >>>>>> >>>>>>> >>>>>>> >>>>>>> On Tue, Oct 6, 2020 at 12:51 PM Murtuza Zabuawala < >>>>>>> [email protected]> wrote: >>>>>>> >>>>>>>> Hi Akshay, >>>>>>>> >>>>>>>> We have used aysnc=False in most ajax calls with this feature, It >>>>>>>> is causing UI hang in case of slow server response. >>>>>>>> You can try adding a time.sleep() call at the python side response >>>>>>>> and check the UI hang, I think we should avoid sync calls as much as >>>>>>>> possible. >>>>>>>> >>>>>>> >>>>>>> I consider a sync ajax call to be a bug. >>>>>>> >>>>>>> >>>>>>>> >>>>>>>> >>>>>>>> -- >>>>>>>> Regards, >>>>>>>> Murtuza Zabuawala >>>>>>>> *EDB* >>>>>>>> *POWER TO POSTGRES* >>>>>>>> https://www.edbpostgres.com >>>>>>>> >>>>>>>> >>>>>>>> On Thu, Oct 1, 2020 at 1:31 PM Akshay Joshi < >>>>>>>> [email protected]> wrote: >>>>>>>> >>>>>>>>> Thanks, patch applied. >>>>>>>>> >>>>>>>>> On Thu, Oct 1, 2020 at 10:42 AM Nikhil Mohite < >>>>>>>>> [email protected]> wrote: >>>>>>>>> >>>>>>>>>> Hi Akshay, >>>>>>>>>> >>>>>>>>>> I have resolved the sonarQube issues, PFA updated patch for the >>>>>>>>>> same. >>>>>>>>>> >>>>>>>>>> >>>>>>>>>> Regards, >>>>>>>>>> Nikhil Mohite. >>>>>>>>>> >>>>>>>>>> >>>>>>>>>> On Tue, Sep 29, 2020 at 11:31 AM Akshay Joshi < >>>>>>>>>> [email protected]> wrote: >>>>>>>>>> >>>>>>>>>>> Hi Nikhil >>>>>>>>>>> >>>>>>>>>>> Your patch introduces 1 new Bug and 13 new code smells, please >>>>>>>>>>> fix those and resend the patch. >>>>>>>>>>> >>>>>>>>>>> On Mon, Sep 28, 2020 at 7:31 PM Nikhil Mohite < >>>>>>>>>>> [email protected]> wrote: >>>>>>>>>>> >>>>>>>>>>>> Hi Akshay, >>>>>>>>>>>> >>>>>>>>>>>> I have resolved code conflict issues and sonarqube issues. >>>>>>>>>>>> PFA updated patch. >>>>>>>>>>>> >>>>>>>>>>>> Regards, >>>>>>>>>>>> Nikhil Mohite. >>>>>>>>>>>> >>>>>>>>>>>> On Mon, Sep 28, 2020 at 5:58 PM Akshay Joshi < >>>>>>>>>>>> [email protected]> wrote: >>>>>>>>>>>> >>>>>>>>>>>>> Hi Nikhil >>>>>>>>>>>>> >>>>>>>>>>>>> The patch is not applying, rebase, and send it again. Please >>>>>>>>>>>>> check your code should not create any new SonarQube issues. >>>>>>>>>>>>> >>>>>>>>>>>>> On Mon, Sep 28, 2020 at 11:20 AM Nikhil Mohite < >>>>>>>>>>>>> [email protected]> wrote: >>>>>>>>>>>>> >>>>>>>>>>>>>> Hi Akshay, >>>>>>>>>>>>>> >>>>>>>>>>>>>> I have resolved all the review comments and also updated the >>>>>>>>>>>>>> test cases as per the new implementation. >>>>>>>>>>>>>> >>>>>>>>>>>>>> PFA updated patch. >>>>>>>>>>>>>> >>>>>>>>>>>>>> >>>>>>>>>>>>>> >>>>>>>>>>>>>> On Mon, Sep 21, 2020 at 5:24 PM Akshay Joshi < >>>>>>>>>>>>>> [email protected]> wrote: >>>>>>>>>>>>>> >>>>>>>>>>>>>>> Hi Nikhil >>>>>>>>>>>>>>> >>>>>>>>>>>>>>> Following are the initial review comments: >>>>>>>>>>>>>>> >>>>>>>>>>>>>>> - Open View/Edit data on any table and click on the same >>>>>>>>>>>>>>> database connection and then click on the Execute button. Got >>>>>>>>>>>>>>> "get_primary_keys() takes 1 positional argument but 2 were given" error. >>>>>>>>>>>>>>> - In my opinion, we should hide the option to change the >>>>>>>>>>>>>>> database connection for View/Edit Data. >>>>>>>>>>>>>>> - If the user clicks on the same database connection >>>>>>>>>>>>>>> multiple times then no need to change the backend connection and >>>>>>>>>>>>>>> transaction id. Add validation at the backend, no action required in this >>>>>>>>>>>>>>> case. >>>>>>>>>>>>>>> - The role option is missing from the "connect to >>>>>>>>>>>>>>> server" dialog. >>>>>>>>>>>>>>> - The Password field should not be there on the "connect >>>>>>>>>>>>>>> to server" dialog. Sometimes we saved the password so asking a password >>>>>>>>>>>>>>> every time is not correct. Check the pgAdmin 3 behavior. >>>>>>>>>>>>>>> >>>>>>>>>>>>>>> Code review still remains. >>>>>>>>>>>>>>> >>>>>>>>>>>>>>> On Thu, Sep 17, 2020 at 4:15 PM Nikhil Mohite < >>>>>>>>>>>>>>> [email protected]> wrote: >>>>>>>>>>>>>>> >>>>>>>>>>>>>>>> Hi Team, >>>>>>>>>>>>>>>> >>>>>>>>>>>>>>>> Regarding RM-3794 >>>>>>>>>>>>>>>> <https://redmine.postgresql.org/issues/3794; allow >>>>>>>>>>>>>>>> the user to change the database connection from an open query tool: >>>>>>>>>>>>>>>> I have implemented the feature and also added documentation >>>>>>>>>>>>>>>> for it. >>>>>>>>>>>>>>>> >>>>>>>>>>>>>>>> PFA patch. >>>>>>>>>>>>>>>> >>>>>>>>>>>>>>>> -- >>>>>>>>>>>>>>>> *Thanks & Regards,* >>>>>>>>>>>>>>>> *Nikhil Mohite* >>>>>>>>>>>>>>>> *Software Engineer.* >>>>>>>>>>>>>>>> *EDB Postgres* <https://www.enterprisedb.com/; >>>>>>>>>>>>>>>> *Mob.No: +91-7798364578.* >>>>>>>>>>>>>>>> >>>>>>>>>>>>>>> >>>>>>>>>>>>>>> >>>>>>>>>>>>>>> -- >>>>>>>>>>>>>>> *Thanks & Regards* >>>>>>>>>>>>>>> *Akshay Joshi* >>>>>>>>>>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>>>>>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>>>>>>>>>> >>>>>>>>>>>>>>> *Mobile: +91 976-788-8246* >>>>>>>>>>>>>>> >>>>>>>>>>>>>> >>>>>>>>>>>>> >>>>>>>>>>>>> -- >>>>>>>>>>>>> *Thanks & Regards* >>>>>>>>>>>>> *Akshay Joshi* >>>>>>>>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>>>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>>>>>>>> >>>>>>>>>>>>> *Mobile: +91 976-788-8246* >>>>>>>>>>>>> >>>>>>>>>>>> >>>>>>>>>>> >>>>>>>>>>> -- >>>>>>>>>>> *Thanks & Regards* >>>>>>>>>>> *Akshay Joshi* >>>>>>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>>>>>> >>>>>>>>>>> *Mobile: +91 976-788-8246* >>>>>>>>>>> >>>>>>>>>> >>>>>>>>> >>>>>>>>> -- >>>>>>>>> *Thanks & Regards* >>>>>>>>> *Akshay Joshi* >>>>>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>>>>> >>>>>>>>> *Mobile: +91 976-788-8246* >>>>>>>>> >>>>>>>> >>>>>>> >>>>>>> -- >>>>>>> Dave Page >>>>>>> VP & Chief Architect, Database Infrastructure >>>>>>> EDB: http://www.enterprisedb.com >>>>>>> >>>>>>> Blog: http://pgsnake.blogspot.com >>>>>>> Twitter: @pgsnake >>>>>>> >>>>>> >>>>>> >>>>>> -- >>>>>> *Thanks & Regards* >>>>>> *Akshay Joshi* >>>>>> *pgAdmin Hacker | Sr. Software Architect* >>>>>> *EDB Postgres <http://edbpostgres.com>* >>>>>> >>>>>> *Mobile: +91 976-788-8246* >>>>>> >>>>> >>>> >>>> -- >>>> *Thanks & Regards* >>>> *Akshay Joshi* >>>> *pgAdmin Hacker | Sr. Software Architect* >>>> *EDB Postgres <http://edbpostgres.com>* >>>> >>>> *Mobile: +91 976-788-8246* >>>> >>> >> >> -- >> *Thanks & Regards* >> *Akshay Joshi* >> *pgAdmin Hacker | Sr. Software Architect* >> *EDB Postgres <http://edbpostgres.com>* >> >> *Mobile: +91 976-788-8246* >> > -- *Thanks & Regards* *Akshay Joshi* *pgAdmin Hacker | Sr. Software Architect* *EDB Postgres <http://edbpostgres.com>* *Mobile: +91 976-788-8246* ^ permalink raw reply [nested|flat] 18+ messages in thread
end of thread, other threads:[~2020-10-21 11:49 UTC | newest] Thread overview: 18+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2020-09-17 10:45 [pgAdmin][RM3794]:Allow User to Change Database Connection from an Open Query Tool Tab Nikhil Mohite <[email protected]> 2020-09-21 11:54 ` Akshay Joshi <[email protected]> 2020-09-28 05:50 ` Nikhil Mohite <[email protected]> 2020-09-28 12:28 ` Akshay Joshi <[email protected]> 2020-09-28 14:01 ` Nikhil Mohite <[email protected]> 2020-09-29 06:01 ` Akshay Joshi <[email protected]> 2020-10-01 05:12 ` Nikhil Mohite <[email protected]> 2020-10-01 08:01 ` Akshay Joshi <[email protected]> 2020-10-06 08:51 ` Nikhil Mohite <[email protected]> 2020-10-06 11:51 ` Murtuza Zabuawala <[email protected]> 2020-10-06 11:54 ` Dave Page <[email protected]> 2020-10-06 12:49 ` Akshay Joshi <[email protected]> 2020-10-07 06:41 ` Nikhil Mohite <[email protected]> 2020-10-08 06:09 ` Akshay Joshi <[email protected]> 2020-10-21 05:38 ` Nikhil Mohite <[email protected]> 2020-10-21 09:24 ` Akshay Joshi <[email protected]> 2020-10-21 11:15 ` Nikhil Mohite <[email protected]> 2020-10-21 11:49 ` Akshay Joshi <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox