Monitor connection and transaction status in the query tool. Fixes #2475

This commit is contained in:
Murtuza Zabuawala
2018-01-12 14:34:39 +00:00
committed by Dave Page
parent 82aa8035c2
commit 8520871bc6
12 changed files with 470 additions and 52 deletions

View File

@@ -21,7 +21,7 @@ from pgadmin.tools.sqleditor.command import QueryToolCommand
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
success_return, internal_server_error, unauthorized
from pgadmin.utils.driver import get_driver
from pgadmin.utils.sqlautocomplete.autocomplete import SQLAutoComplete
from pgadmin.misc.file_manager import Filemanager
@@ -50,6 +50,14 @@ TX_STATUS__ACTIVE = 1
TX_STATUS_INTRANS = 2
TX_STATUS_INERROR = 3
# Connection status codes mapping
CONNECTION_STATUS_MESSAGE_MAPPING = dict({
0: 'The session is idle and there is no current transaction.',
1: 'A command is currently in progress.',
2: 'The session is idle in a valid transaction block.',
3: 'The session is idle in a failed transaction block.',
4: 'The connection with the server is bad.'
})
class SqlEditorModule(PgAdminModule):
"""
@@ -107,7 +115,8 @@ class SqlEditorModule(PgAdminModule):
'sqleditor.autocomplete',
'sqleditor.load_file',
'sqleditor.save_file',
'sqleditor.query_tool_download'
'sqleditor.query_tool_download',
'sqleditor.connection_status'
]
def register_preferences(self):
@@ -333,6 +342,24 @@ class SqlEditorModule(PgAdminModule):
}
)
self.display_connection_status = self.preference.register(
'display', 'connection_status',
gettext("Connection status"), 'boolean', True,
category_label=gettext('Display'),
help_str=gettext('If set to True, the Query Tool '
'will monitor and display the connection and '
'transaction status.')
)
self.connection_status = self.preference.register(
'display', 'connection_status_fetch_time',
gettext("Connection status refresh rate"), 'integer', 2,
min_val=1, max_val=600,
category_label=gettext('Display'),
help_str=gettext('The number of seconds between connection/transaction '
'status polls.')
)
blueprint = SqlEditorModule(MODULE_NAME, __name__, static_url_path='/static')
@@ -345,10 +372,10 @@ def index():
def update_session_grid_transaction(trans_id, data):
grid_data = session['gridData']
grid_data[str(trans_id)] = data
session['gridData'] = grid_data
if 'gridData' in session:
grid_data = session['gridData']
grid_data[str(trans_id)] = data
session['gridData'] = grid_data
def check_transaction_status(trans_id):
@@ -363,6 +390,10 @@ def check_transaction_status(trans_id):
Returns: status and connection object
"""
if 'gridData' not in session:
return False, unauthorized(gettext("Unauthorized request.")), \
None, None, None
grid_data = session['gridData']
# Return from the function if transaction id not found
@@ -497,13 +528,23 @@ def start_query_tool(trans_id):
else:
sql = request.args or request.form
if 'gridData' not in session:
return make_json_response(
data={
'status': False,
'result': gettext('Transaction ID not found in the session.'),
'can_edit': False, 'can_filter': False
}
)
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': False, 'result': gettext('Transaction ID not found in the session.'),
'status': False,
'result': gettext('Transaction ID not found in the session.'),
'can_edit': False, 'can_filter': False
}
)
@@ -1809,3 +1850,50 @@ def start_query_download_tool(trans_id):
return internal_server_error(
errormsg=gettext("Transaction status check failed.")
)
@blueprint.route(
'/status/<int:trans_id>',
methods=["GET"],
endpoint='connection_status'
)
@login_required
def query_tool_status(trans_id):
"""
The task of this function to return the status of the current connection
used in query tool instance with given transaction ID.
Args:
trans_id: Transaction ID
Returns:
Response with the connection status
Psycopg2 Status Code Mapping:
-----------------------------
TRANSACTION_STATUS_IDLE = 0
TRANSACTION_STATUS_ACTIVE = 1
TRANSACTION_STATUS_INTRANS = 2
TRANSACTION_STATUS_INERROR = 3
TRANSACTION_STATUS_UNKNOWN = 4
"""
status, error_msg, conn, trans_obj, \
session_obj = check_transaction_status(trans_id)
if not status and error_msg and type(error_msg) == str:
return internal_server_error(
errormsg=error_msg
)
if conn and trans_obj and session_obj:
status = conn.conn.get_transaction_status()
return make_json_response(
data={
'status': status,
'message': gettext(
CONNECTION_STATUS_MESSAGE_MAPPING.get(status)
)
}
)
else:
return internal_server_error(
errormsg=gettext("Transaction status check failed.")
)

View File

@@ -14,7 +14,7 @@
.sql-editor-busy-fetching {
position:absolute;
left: 0;
top: 41px;
top: 65px;
bottom: 0;
right: 0;
margin:0;
@@ -33,11 +33,6 @@
z-index: 0;
}
.editor-title {
padding: 4px 5px;
font-size: 13px;
}
.sql-editor-grid-container {
height: 100%;
overflow: auto;
@@ -524,3 +519,56 @@ input.editor-checkbox:focus {
#datagrid .slick-row .slick-cell {
white-space: pre;
}
/* CSS for connection status icon */
#output-panel .CodeMirror {
border-top: 1px solid #ddd;
}
.connection_status_wrapper {
width: 100%;
background-color: #f7f7f7;
}
.editor-title {
padding: 4px 5px;
font-size: 13px;
display: inline-block;
}
.connection_status {
width: 11px;
position: relative;
display: inline-block;
margin-top: 2px;
margin-left: 12px;
margin-right: 16px;
}
.connection_status .fa {
font-size: 16px;
margin-left: 2px;
}
.connection_status .fa-clock-o,
.connection_status .fa-hourglass-half {
color: #e8a735;
}
.connection_status .fa-exclamation-circle,
.connection_status .fa-exclamation-triangle {
color: #d0021b;
}
.connection_status .fa-custom {
height: 18px;
margin-bottom: -4px;
}
.connection_status .fa-query-tool-connected {
content: url('../img/connect.svg');
}
.connection_status .fa-query-tool-disconnected {
content: url('../img/disconnect.svg');
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs><style>.cls-1{fill:#2c76b4;}</style></defs><title>connect</title><path class="cls-1" d="M21.27,4.25c.12-.12.12-.18,0-.29-.32-.3-.63-.61-.92-.93-.12-.13-.19-.13-.31,0-.69.71-1.4,1.4-2.1,2.11L17.7,4.9a4.22,4.22,0,0,0-5.26-.82,9.75,9.75,0,0,0-1.69,1.46l-.58.55c-.18-.18-.36-.35-.53-.53s-.14-.1-.23,0c-.31.33-.63.65-1,1-.12.12-.13.18,0,.3.69.67,1.36,1.37,2.05,2,.15.14.13.21,0,.34l-2.05,2c-.12.12-.12.18,0,.29.32.3.63.61.92.93.12.13.19.13.31,0l2-2.05c.12-.13.19-.15.33,0,.61.63,1.24,1.25,1.87,1.87.13.12.12.19,0,.31l-2.07,2.06c-.11.11-.13.17,0,.29q.47.44.91.91c.13.14.2.16.35,0,.67-.69,1.36-1.36,2-2.05.12-.13.19-.12.31,0l2.08,2.09c.09.1.15.12.25,0,.32-.34.66-.67,1-1,.1-.1.07-.15,0-.23l-.61-.6c.48-.45,1-.86,1.41-1.32a4.13,4.13,0,0,0,1.2-3.67,4,4,0,0,0-1.42-2.55c-.14-.12-.14-.19,0-.32C20,5.56,20.61,4.9,21.27,4.25Zm-3.05,7.41c-.43.39-.81.83-1.2,1.23L11.37,7.25l1.42-1.34a2.52,2.52,0,0,1,3.48-.06C17,6.55,17.74,7.27,18.43,8A2.51,2.51,0,0,1,18.22,11.67Z"/><path class="cls-1" d="M15.52,17.16c-2-1.95-8-8-9-9C6.4,8,6.34,8,6.22,8.14c-.3.32-.62.64-.94.94-.11.11-.14.17,0,.28s.35.33.6.57c-.49.45-1,.88-1.42,1.34a4.16,4.16,0,0,0-1.12,4,4.37,4.37,0,0,0,1.33,2.22c.13.13.13.2,0,.33-.65.63-1.28,1.28-1.93,1.91-.13.13-.12.19,0,.31.32.3.62.61.92.92.11.12.18.12.29,0,.53-.55,1.07-1.08,1.62-1.62.15-.15.29-.42.47-.41s.31.25.47.39a4.21,4.21,0,0,0,5.81,0c.5-.47.94-1,1.43-1.53.25.26.43.44.6.63s.14.11.24,0c.3-.32.61-.63.93-.92C15.67,17.38,15.68,17.31,15.52,17.16Zm-3-.38c-.43.43-.84.87-1.27,1.29a2.53,2.53,0,0,1-3.74-.17c-.53-.57-1.1-1.11-1.66-1.64a2.58,2.58,0,0,1,0-3.93c.41-.41.79-.84,1.19-1.27l5.52,5.52C12.57,16.66,12.58,16.7,12.49,16.78Z"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs><style>.cls-1{fill:#d0021b;}</style></defs><title>disconnect</title><path class="cls-1" d="M22.49,2.73c.12-.11.12-.18,0-.29-.31-.3-.62-.6-.91-.91-.12-.13-.18-.13-.31,0-.68.69-1.37,1.38-2.07,2.07L19,3.36a4.15,4.15,0,0,0-5.18-.81A9.59,9.59,0,0,0,12.14,4l-.57.54c-.18-.17-.36-.34-.53-.52s-.13-.09-.22,0c-.31.32-.62.64-.95.94-.12.11-.13.18,0,.3.68.66,1.34,1.34,2,2,.14.14.12.21,0,.34l-2,2c-.12.11-.12.18,0,.29.31.29.62.6.91.91.12.13.18.12.3,0l2-2c.12-.13.19-.15.32,0,.6.62,1.22,1.23,1.84,1.84.12.12.12.18,0,.3l-2,2c-.11.11-.13.17,0,.29q.46.43.89.89c.13.13.2.15.34,0,.66-.68,1.34-1.34,2-2,.12-.13.18-.12.3,0l2,2.05c.09.09.14.11.24,0,.32-.33.65-.66,1-1,.1-.09.07-.14,0-.22l-.6-.59c.47-.44.95-.85,1.38-1.3A4.06,4.06,0,0,0,22,7.47,4,4,0,0,0,20.56,5c-.14-.12-.14-.18,0-.31C21.2,4,21.84,3.36,22.49,2.73Zm-3,7.29c-.42.38-.8.81-1.18,1.21L12.75,5.68l1.39-1.32a2.48,2.48,0,0,1,3.42-.06C18.3,5,19,5.7,19.69,6.44A2.47,2.47,0,0,1,19.48,10Z"/><path class="cls-1" d="M14.1,18.75c-1.93-1.92-7.84-7.83-8.87-8.87-.11-.11-.16-.13-.28,0-.3.32-.61.63-.93.93-.11.1-.14.17,0,.28s.35.33.59.56c-.48.45-1,.86-1.4,1.32a4.09,4.09,0,0,0-1.1,3.93,4.3,4.3,0,0,0,1.31,2.19c.13.13.13.2,0,.33-.64.62-1.26,1.26-1.9,1.88-.13.12-.12.19,0,.3.31.29.61.6.91.91.11.12.17.12.29,0,.52-.54,1.06-1.07,1.59-1.59.15-.15.28-.41.46-.41s.31.24.46.38a4.14,4.14,0,0,0,5.72,0c.5-.46.92-1,1.4-1.51.25.26.42.43.59.62s.14.11.24,0c.3-.31.6-.62.91-.91C14.24,19,14.25,18.9,14.1,18.75Zm-3-.37c-.42.42-.82.86-1.25,1.27a2.49,2.49,0,0,1-3.68-.17c-.52-.56-1.08-1.09-1.64-1.62a2.54,2.54,0,0,1,0-3.87c.4-.4.78-.83,1.17-1.25l5.43,5.43C11.2,18.25,11.21,18.29,11.12,18.38Z"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -107,6 +107,8 @@ define('tools.querytool', [
filter = self.$el.find('#sql_filter');
$('.editor-title').text(_.unescape(self.editor_title));
self.checkConnectionStatus();
self.filter_obj = CodeMirror.fromTextArea(filter.get(0), {
lineNumbers: true,
mode: self.handler.server_type === 'gpdb' ? 'text/x-gpsql' : 'text/x-pgsql',
@@ -114,8 +116,12 @@ define('tools.querytool', [
widget: '\u2026',
},
foldGutter: {
rangeFinder: CodeMirror.fold.combine(CodeMirror.pgadminBeginRangeFinder, CodeMirror.pgadminIfRangeFinder,
CodeMirror.pgadminLoopRangeFinder, CodeMirror.pgadminCaseRangeFinder),
rangeFinder: CodeMirror.fold.combine(
CodeMirror.pgadminBeginRangeFinder,
CodeMirror.pgadminIfRangeFinder,
CodeMirror.pgadminLoopRangeFinder,
CodeMirror.pgadminCaseRangeFinder
),
},
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
extraKeys: pgBrowser.editor_shortcut_keys,
@@ -127,6 +133,20 @@ define('tools.querytool', [
matchBrackets: pgAdmin.Browser.editor_options.brace_matching,
});
// Updates connection status flag
self.gain_focus = function() {
setTimeout(function() {
SqlEditorUtils.updateConnectionStatusFlag('visible');
}, 100);
};
// Updates connection status flag
self.lost_focus = function() {
setTimeout(function() {
SqlEditorUtils.updateConnectionStatusFlag('hidden');
}, 100);
};
// Create main wcDocker instance
var main_docker = new wcDocker(
'#editor-panel', {
@@ -136,7 +156,7 @@ define('tools.querytool', [
'filename': 'css',
}),
theme: 'webcabin.overrides.css',
});
});
var sql_panel = new pgAdmin.Browser.Panel({
name: 'sql_panel',
@@ -162,8 +182,12 @@ define('tools.querytool', [
widget: '\u2026',
},
foldGutter: {
rangeFinder: CodeMirror.fold.combine(CodeMirror.pgadminBeginRangeFinder, CodeMirror.pgadminIfRangeFinder,
CodeMirror.pgadminLoopRangeFinder, CodeMirror.pgadminCaseRangeFinder),
rangeFinder: CodeMirror.fold.combine(
CodeMirror.pgadminBeginRangeFinder,
CodeMirror.pgadminIfRangeFinder,
CodeMirror.pgadminLoopRangeFinder,
CodeMirror.pgadminCaseRangeFinder
),
},
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
extraKeys: pgBrowser.editor_shortcut_keys,
@@ -270,14 +294,47 @@ define('tools.querytool', [
// Set focus on query tool of active panel
p.on(wcDocker.EVENT.GAIN_FOCUS, function() {
if (!$(p.$container).hasClass('wcPanelTabContentHidden')) {
setTimeout(function() {
self.handler.gridView.query_tool_obj.focus();
}, 200);
var $container = $(this.$container),
transId = self.handler.transId;
// If transaction id is already set
if($container.data('trans-id') != transId) {
$container.data('trans-id', transId)
}
if (!$container.hasClass('wcPanelTabContentHidden')) {
setTimeout(function () {
self.handler.gridView.query_tool_obj.focus();
}, 200);
// Trigger an event to update connection status flag
pgBrowser.Events.trigger(
'pgadmin:query_tool:panel:gain_focus:' + transId
);
}
});
}
});
// When any query tool panel lost it focus then
p.on(wcDocker.EVENT.LOST_FOCUS, function () {
var $container = $(this.$container),
transId = $container.data('trans-id');
// Trigger an event to update connection status flag
if ($container.hasClass('wcPanelTabContentHidden')) {
pgBrowser.Events.trigger(
'pgadmin:query_tool:panel:lost_focus:' + transId
);
}
});
}
});
// Code to update connection status polling flag
pgBrowser.Events.on(
'pgadmin:query_tool:panel:gain_focus:' + self.handler.transId,
self.gain_focus, self
);
pgBrowser.Events.on(
'pgadmin:query_tool:panel:lost_focus:' + self.handler.transId,
self.lost_focus, self
);
}
// set focus on query tool once loaded
@@ -422,6 +479,34 @@ define('tools.querytool', [
});
},
// This function will check the connection status at specific
// interval defined by the user in preference
checkConnectionStatus: function() {
var self = this,
preference = window.top.pgAdmin.Browser.get_preference(
'sqleditor', 'connection_status_fetch_time'
),
display_status = window.top.pgAdmin.Browser.get_preference(
'sqleditor', 'connection_status'
);
if(!preference && self.handler.is_new_browser_tab) {
preference = window.opener.pgAdmin.Browser.get_preference(
'sqleditor', 'connection_status_fetch_time'
);
display_status = window.opener.pgAdmin.Browser.get_preference(
'sqleditor', 'connection_status'
);
}
// Only enable pooling if it is enabled
if (display_status && display_status.value) {
SqlEditorUtils.updateConnectionStatus(
url_for('sqleditor.connection_status', {'trans_id': self.transId}),
preference.value
);
}
},
/* To prompt user for unsaved changes */
user_confirmation: function(panel, msg) {
// If there is anything to save then prompt user