Infrastructure and changes to the Query Tool for realtime preference handling. Refs #3294

Highlights of this patch include:
- Changes will affect SQL Editors in Create dialog boxes, SQL tab of the main screen, Query tool, History entries in the query tool, Query tool opened in New Tab/Window
- All the components of SQL editor will refer to single source of preferences which is cached in the Browser object. All other redundant ajax get preference calls are removed.
- SQL editor will not refer template JS variables anymore, once all the references are removed the template variables will also be removed.
- Code refactoring wherever possible.
- Covered JS test cases wherever possible.
This commit is contained in:
Aditya Toshniwal
2018-07-05 11:38:43 +01:00
committed by Dave Page
parent 82d77c4608
commit bdb7e3fde2
24 changed files with 527 additions and 849 deletions

View File

@@ -24,12 +24,9 @@ from pgadmin.utils.ajax import make_json_response, bad_request, \
internal_server_error
from config import PG_DEFAULT_DRIVER
from pgadmin.utils.preferences import Preferences
from pgadmin.model import Server
from pgadmin.utils.driver import get_driver
from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost
from pgadmin.tools.sqleditor.utils.query_tool_preferences import \
get_query_tool_keyboard_shortcuts, get_text_representation_of_shortcut
class DataGridModule(PgAdminModule):
@@ -184,13 +181,9 @@ def initialize_datagrid(cmd_type, obj_type, sgid, sid, did, obj_id):
# Store the grid dictionary into the session variable
session['gridData'] = sql_grid_data
pref = Preferences.module('sqleditor')
new_browser_tab = pref.preference('new_browser_tab').get()
return make_json_response(
data={
'gridTransId': trans_id,
'newBrowserTab': new_browser_tab
'gridTransId': trans_id
}
)
@@ -246,12 +239,6 @@ def panel(trans_id, is_query_tool, editor_title):
if "linux" in _platform:
is_linux_platform = True
pref = Preferences.module('sqleditor')
if pref.preference('new_browser_tab').get():
new_browser_tab = 'true'
else:
new_browser_tab = 'false'
# Fetch the server details
bgcolor = None
fgcolor = None
@@ -271,16 +258,10 @@ def panel(trans_id, is_query_tool, editor_title):
url_params = dict()
if is_query_tool == 'true':
prompt_save_changes = pref.preference(
'prompt_save_query_changes'
).get()
url_params['sgid'] = trans_obj.sgid
url_params['sid'] = trans_obj.sid
url_params['did'] = trans_obj.did
else:
prompt_save_changes = pref.preference(
'prompt_save_data_changes'
).get()
url_params['cmd_type'] = trans_obj.cmd_type
url_params['obj_type'] = trans_obj.object_type
url_params['sgid'] = trans_obj.sgid
@@ -288,9 +269,6 @@ def panel(trans_id, is_query_tool, editor_title):
url_params['did'] = trans_obj.did
url_params['obj_id'] = trans_obj.obj_id
display_connection_status = pref.preference('connection_status').get()
queryToolShortcuts = get_query_tool_keyboard_shortcuts()
return render_template(
"datagrid/index.html",
_=gettext,
@@ -300,19 +278,11 @@ def panel(trans_id, is_query_tool, editor_title):
script_type_url=sURL,
is_desktop_mode=app.PGADMIN_RUNTIME,
is_linux=is_linux_platform,
is_new_browser_tab=new_browser_tab,
server_type=server_type,
client_platform=user_agent.platform,
bgcolor=bgcolor,
fgcolor=fgcolor,
# convert python boolean value to equivalent js boolean literal
# before passing it to html template.
prompt_save_changes='true' if prompt_save_changes else 'false',
display_connection_status=display_connection_status,
url_params=json.dumps(url_params),
key=queryToolShortcuts.get('keys'),
shortcuts=queryToolShortcuts.get('shortcuts'),
get_shortcut_text=get_text_representation_of_shortcut
url_params=json.dumps(url_params)
)
@@ -387,13 +357,9 @@ def initialize_query_tool(sgid, sid, did=None):
# Store the grid dictionary into the session variable
session['gridData'] = sql_grid_data
pref = Preferences.module('sqleditor')
new_browser_tab = pref.preference('new_browser_tab').get()
return make_json_response(
data={
'gridTransId': trans_id,
'newBrowserTab': new_browser_tab
'gridTransId': trans_id
}
)

View File

@@ -29,6 +29,23 @@ define('pgadmin.datagrid', [
this.initialized = true;
this.title_index = 1;
let self = this;
/* Cache may take time to load for the first time
* Keep trying till available
*/
let cacheIntervalId = setInterval(function() {
if(pgBrowser.preference_version() > 0) {
self.preferences = pgBrowser.get_preferences_for_module('sqleditor');
clearInterval(cacheIntervalId);
}
},0);
pgBrowser.onPreferencesChange('sqleditor', function() {
self.preferences = pgBrowser.get_preferences_for_module('sqleditor');
});
this.spinner_el = '<div class="wcLoadingContainer">'+
'<div class="wcLoadingBackground"></div>'+
'<div class="wcLoadingIconContainer">'+
@@ -279,12 +296,12 @@ define('pgadmin.datagrid', [
lineNumbers: true,
mode: 'text/x-pgsql',
extraKeys: pgBrowser.editor_shortcut_keys,
indentWithTabs: pgAdmin.Browser.editor_options.indent_with_tabs,
indentUnit: pgAdmin.Browser.editor_options.tabSize,
tabSize: pgBrowser.editor_options.tabSize,
lineWrapping: pgAdmin.Browser.editor_options.wrapCode,
autoCloseBrackets: pgAdmin.Browser.editor_options.insert_pair_brackets,
matchBrackets: pgAdmin.Browser.editor_options.brace_matching,
indentWithTabs: !this.preferences.use_spaces,
indentUnit: this.preferences.tab_size,
tabSize: this.preferences.tab_size,
lineWrapping: this.preferences.wrap_code,
autoCloseBrackets: this.preferences.insert_pair_brackets,
matchBrackets: this.preferences.brace_matching,
});
setTimeout(function() {
@@ -415,13 +432,20 @@ define('pgadmin.datagrid', [
}
}
if (trans_obj.newBrowserTab) {
if (self.preferences.new_browser_tab) {
var newWin = window.open(baseUrl, '_blank');
// add a load listener to the window so that the title gets changed on page load
newWin.addEventListener('load', function() {
newWin.document.title = panel_title;
/* Set the initial version of pref cache the new window is having
* This will be used by the poller to compare with window openers
* pref cache version
*/
//newWin.pgAdmin.Browser.preference_version(pgBrowser.preference_version());
});
} else {
/* On successfully initialization find the dashboard panel,
* create new panel and add it to the dashboard panel.

View File

@@ -10,13 +10,6 @@
.alertify .ajs-dialog.ajs-shake{-webkit-animation-name: none;}
.sql-editor-busy-icon.fa-pulse{-webkit-animation: none;}
{% endif %}
{# Note: If we will display connection status then we have to provide some
space to display status icon else we can use all the space available #}
.editor-title {
width:{% if display_connection_status -%} calc(100% - 43px)
{% else %} 100% {%- endif %};
}
</style>
<div id="main-editor_panel">
<div id="fetching_data" class="wcLoadingIconContainer sql-editor-busy-fetching hide">
@@ -28,14 +21,14 @@
<div id="btn-toolbar" class="pg-prop-btn-group bg-gray-2 border-gray-3" role="toolbar" aria-label="">
<div class="btn-group" role="group" aria-label="">
<button id="btn-load-file" type="button" class="btn btn-default btn-load-file"
title="{{ _('Open File') }}{{ _(' (accesskey+{0})'.format(key.open_file.upper())) }}"
accesskey="{{key.open_file}}"
title=""
accesskey=""
tabindex="0">
<i class="fa fa-folder-open-o" aria-hidden="true"></i>
</button>
<button id="btn-save" type="button" class="btn btn-default"
title="{{ _('Save') }}{{ _(' (accesskey+{0})'.format(key.save_file.upper())) }}"
accesskey="{{key.save_file}}"
title=""
accesskey=""
disabled>
<i class="fa fa-floppy-o" aria-hidden="true" tabindex="0"></i>
</button>
@@ -63,8 +56,8 @@
</button>
<button id="btn-find-menu-dropdown" type="button" class="btn btn-default dropdown-toggle"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
title="{{ _('Find options') }}{{ _(' (accesskey+{0})'.format(key.find_options.upper())) }}"
accesskey="{{key.find_options}}"
title=""
accesskey=""
tabindex="0">
<span class="caret"></span> <span class="sr-only">Toggle Dropdown</span>
</button>
@@ -122,22 +115,22 @@
</div>
<div class="btn-group" role="group" aria-label="">
<button id="btn-copy-row" type="button" class="btn btn-default"
title="{{ _('Copy') }}{{ _(' (accesskey+{0})'.format(key.copy_row.upper())) }}"
accesskey="{{key.copy_row}}"
title=""
accesskey=""
tabindex="0" disabled>
<i class="fa fa-files-o" aria-hidden="true"></i>
</button>
<button id="btn-paste-row" type="button" class="btn btn-default"
title="{{ _('Paste') }}{{ _(' (accesskey+{0})'.format(key.paste_row.upper())) }}"
accesskey="{{key.paste_row}}"
title=""
accesskey=""
tabindex="0" disabled>
<i class="fa fa-clipboard" aria-hidden="true"></i>
</button>
</div>
<div class="btn-group" role="group" aria-label="">
<button id="btn-delete-row" type="button" class="btn btn-default"
title="{{ _('Delete') }}{{ _(' (accesskey+{0})'.format(key.delete_row.upper())) }}"
accesskey="{{key.delete_row}}"
title=""
accesskey=""
tabindex="0" disabled>
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
@@ -188,15 +181,15 @@
</div>
<div class="btn-group" role="group" aria-label="">
<button id="btn-filter" type="button" class="btn btn-default"
title="{{ _('Filter') }}{{ _(' (accesskey+{0})'.format(key.filter_dialog.upper())) }}"
accesskey="{{key.filter_dialog}}"
title=""
accesskey=""
tabindex="0" disabled>
<i class="fa fa-filter" aria-hidden="true"></i>
</button>
<button id="btn-filter-dropdown" type="button" class="btn btn-default dropdown-toggle"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
title="{{ _('Filter options') }}{{ _(' (accesskey+{0})'.format(key.filter_options.upper())) }}"
accesskey="{{key.filter_options}}"
title=""
accesskey=""
disabled tabindex="0">
<span class="caret"></span> <span class="sr-only">{{ _('Toggle Dropdown') }}</span>
</button>
@@ -210,9 +203,9 @@
</ul>
</div>
<div class="btn-group" role="group" aria-label="">
<select class="limit" style="height: 30px; width: 90px;" disabled
title="{{ _('Rows limit') }}{{ _(' (accesskey+{0})'.format(key.rows_limit.upper())) }}"
accesskey="{{key.rows_limit}}"
<select id="btn-rows-limit" class="limit" style="height: 30px; width: 90px;" disabled
title=""
accesskey=""
tabindex="0">
<option value="-1">{{ _('No limit') }}</option>
<option value="1000">{{ _('1000 rows') }}</option>
@@ -223,31 +216,31 @@
<div class="btn-group" role="group" aria-label="">
<button id="btn-flash" data-test-selector="execute-refresh-button" type="button" class="btn btn-default" style="width: 40px;"
title="{{ _('Execute/Refresh') }}{{ _(' ({0})'.format(get_shortcut_text(shortcuts.execute_query))) }}"
title=""
tabindex="0">
<i class="fa fa-bolt" aria-hidden="true"></i>
</button>
<button id="btn-query-dropdown" type="button" class="btn btn-default dropdown-toggle"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
accesskey="{{key.execute_options}}"
title="{{ _('Execute options') }}{{ _(' (accesskey+{0})'.format(key.execute_options.upper())) }}"
accesskey=""
title=""
tabindex="0">
<span class="caret"></span> <span class="sr-only">{{ _('Toggle Dropdown') }}</span>
</button>
<ul class="dropdown-menu" role="menu">
<li>
<a id="btn-flash-menu" href="#" tabindex="0">
<span>{{ _('Execute/Refresh') }}{{ _(' ({0})'.format(get_shortcut_text(shortcuts.execute_query))) }}</span>
<span></span>
</a>
</li>
<li>
<a id="btn-explain" href="#" tabindex="0">
<span>{{ _('Explain') }}{{ _(' ({0})'.format(get_shortcut_text(shortcuts.explain_query))) }}</span>
<span></span>
</a>
</li>
<li>
<a id="btn-explain-analyze" href="#" tabindex="0">
<span>{{ _('Explain Analyze') }}{{ _(' ({0})'.format(get_shortcut_text(shortcuts.explain_analyze_query))) }}</span>
<span></span>
</a>
</li>
<li class="divider"></li>
@@ -295,8 +288,8 @@
</li>
</ul>
<button id="btn-cancel-query" type="button" class="btn btn-default"
title="{{ _('Cancel query') }}{{ _(' (accesskey+{0})'.format(key.cancel_query.upper())) }}"
accesskey="{{key.cancel_query}}"
title=""
accesskey=""
tabindex="0" disabled >
<i class="fa fa-stop" aria-hidden="true"></i>
</button>
@@ -304,8 +297,8 @@
<div class="btn-group" role="group" aria-label="">
<button id="btn-clear-dropdown" type="button" class="btn btn-default dropdown-toggle"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
title="{{ _('Clear') }}{{ _(' (accesskey+{0})'.format(key.clear_options.upper())) }}"
accesskey="{{key.clear_options}}"
title=""
accesskey=""
tabindex="0">
<i class="fa fa-eraser" aria-hidden="true"></i>
<span class="caret"></span> <span class="sr-only">{{ _('Toggle Dropdown') }}</span>
@@ -325,7 +318,7 @@
</div>
<div class="btn-group" role="group" aria-label="">
<button id="btn-download" type="button" class="btn btn-default"
title="{{ _('Download as CSV') }}{{ _(' ({0})'.format(get_shortcut_text(shortcuts.download_csv))) }}"
title=""
tabindex="0">
<i class="fa fa-download" aria-hidden="true"></i>
</button>
@@ -333,21 +326,17 @@
</div>
<div class="connection_status_wrapper">
{% if display_connection_status %}
<div style="display: inline-block;"
title="{{ _('Connection status (click for details) (accesskey+{0})'.format(key.conn_status.upper())) }}">
<div class="connection_status" data-container="body"
data-toggle="popover" data-placement="bottom"
data-content=""
data-panel-visible="visible"
accesskey="{{key.conn_status}}"
tabindex="0">
<i class="fa-custom fa-query-tool-disconnected" aria-hidden="true"
title="{{ _('Connection status (click for details) (accesskey+{0})'.format(key.conn_status.upper())) }}">
</i>
</div>
<div id="btn-conn-status"
class="connection_status connection-status-show" data-container="body"
data-toggle="popover" data-placement="bottom"
data-content=""
data-panel-visible="visible"
accesskey=""
tabindex="0">
<i class="fa-custom fa-query-tool-disconnected" aria-hidden="true"
title="">
</i>
</div>
{% endif %}
<div class="editor-title"
style="background-color: {% if fgcolor %}{{ bgcolor or '#FFFFFF' }}{% else %}{{ bgcolor or '#2C76B4' }}{% endif %}; color: {{ fgcolor or 'white' }};"></div>
</div>
@@ -398,9 +387,7 @@
{{ is_query_tool }},
"{{ editor_title }}",
script_type_url,
{{ is_new_browser_tab }},
"{{ server_type }}",
{{ prompt_save_changes }},
{{ url_params|safe}}
);
});

View File

@@ -625,3 +625,19 @@ input.editor-checkbox:focus {
.data_sorting_dialog .data_sorting {
padding: 10px 0px;
}
.editor-title {
width:100%;
}
.editor-title-connection {
width:calc(100% - 43px);
}
.connection-status-show {
display: inline-block;
}
.connection-status-hide {
display: none;
}

View File

@@ -16,7 +16,7 @@ define('tools.querytool', [
'sources/sqleditor/query_tool_http_error_handler',
'sources/sqleditor/filter_dialog',
'sources/history/index.js',
'sources/../jsx/history/query_history',
'sourcesjsx/history/query_history',
'react', 'react-dom',
'sources/keyboard_shortcuts',
'sources/sqleditor/query_tool_actions',
@@ -25,6 +25,7 @@ define('tools.querytool', [
'sources/modify_animation',
'sources/sqleditor/calculate_query_run_time',
'sources/sqleditor/call_render_after_poll',
'sources/sqleditor/query_tool_preferences',
'sources/../bundle/slickgrid',
'pgadmin.file_manager',
'backgrid.sizeable.columns',
@@ -38,7 +39,7 @@ define('tools.querytool', [
XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler, FilterHandler,
HistoryBundle, queryHistory, React, ReactDOM,
keyboardShortcuts, queryToolActions, queryToolNotifications, Datagrid,
modifyAnimation, calculateQueryRunTime, callRenderAfterPoll) {
modifyAnimation, calculateQueryRunTime, callRenderAfterPoll, queryToolPref) {
/* Return back, this has been called more than once */
if (pgAdmin.SqlEditor)
return pgAdmin.SqlEditor;
@@ -58,6 +59,11 @@ define('tools.querytool', [
this.$el = opts.el;
this.handler = opts.handler;
this.handler['col_size'] = {};
let browser = window.opener ?
window.opener.pgAdmin.Browser : window.top.pgAdmin.Browser;
this.preferences = browser.get_preferences_for_module('sqleditor');
this.handler.preferences = this.preferences;
this.connIntervalId = null;
},
// Bind all the events
@@ -112,15 +118,30 @@ define('tools.querytool', [
'click #btn-unindent-code': 'on_unindent_code',
},
reflectPreferences: function() {
let self = this,
browser = window.opener ?
window.opener.pgAdmin.Browser : window.top.pgAdmin.Browser;
/* pgBrowser is different obj from window.top.pgAdmin.Browser
* Make sure to get only the latest update. Older versions will be discarded
* if function is called by older events.
* This works for new tab sql editor also as it polls if latest version available
* This is required because sql editor can update preferences directly
*/
if(pgBrowser.preference_version() < browser.preference_version()){
pgBrowser.preference_version(browser.preference_version());
self.preferences = browser.get_preferences_for_module('sqleditor');
self.handler.preferences = self.preferences;
queryToolPref.updateUIPreferences(self);
}
},
// This function is used to render the template.
render: function() {
var self = this;
$('.editor-title').text(_.unescape(self.editor_title));
self.checkConnectionStatus();
// Fetch and assign the shortcuts to current instance
self.keyboardShortcutConfig = queryToolActions.getKeyboardShortcuts(self);
// Updates connection status flag
self.gain_focus = function() {
@@ -183,13 +204,7 @@ define('tools.querytool', [
},
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
extraKeys: pgBrowser.editor_shortcut_keys,
indentWithTabs: pgAdmin.Browser.editor_options.indent_with_tabs,
indentUnit: pgAdmin.Browser.editor_options.tabSize,
tabSize: pgAdmin.Browser.editor_options.tabSize,
lineWrapping: pgAdmin.Browser.editor_options.wrapCode,
scrollbarStyle: 'simple',
autoCloseBrackets: pgAdmin.Browser.editor_options.insert_pair_brackets,
matchBrackets: pgAdmin.Browser.editor_options.brace_matching,
});
// Refresh Code mirror on SQL panel resize to
@@ -270,32 +285,32 @@ define('tools.querytool', [
self.render_history_grid();
queryToolNotifications.renderNotificationsGrid(self.notifications_panel);
if (!self.handler.is_new_browser_tab) {
if (!self.preferences.new_browser_tab) {
// Listen on the panel closed event and notify user to save modifications.
_.each(window.top.pgAdmin.Browser.docker.findPanels('frm_datagrid'), function(p) {
if (p.isVisible()) {
if (self.handler.prompt_save_changes) {
p.on(wcDocker.EVENT.CLOSING, function() {
// Only if we can edit data then perform this check
var notify = false,
msg;
if (self.handler.can_edit) {
var data_store = self.handler.data_store;
if (data_store && (_.size(data_store.added) ||
_.size(data_store.updated))) {
msg = gettext('The data has changed. Do you want to save changes?');
notify = true;
}
} else if (self.handler.is_query_tool && self.handler.is_query_changed) {
msg = gettext('The text has changed. Do you want to save changes?');
p.on(wcDocker.EVENT.CLOSING, function() {
// Only if we can edit data then perform this check
var notify = false,
msg;
if (self.handler.can_edit
&& self.preferences.prompt_save_data_changes) {
var data_store = self.handler.data_store;
if (data_store && (_.size(data_store.added) ||
_.size(data_store.updated))) {
msg = gettext('The data has changed. Do you want to save changes?');
notify = true;
}
if (notify) {
return self.user_confirmation(p, msg);
}
return true;
});
}
} else if (self.handler.is_query_tool && self.handler.is_query_changed
&& self.preferences.prompt_save_query_changes) {
msg = gettext('The text has changed. Do you want to save changes?');
notify = true;
}
if (notify) {
return self.user_confirmation(p, msg);
}
return true;
});
// Set focus on query tool of active panel
p.on(wcDocker.EVENT.GAIN_FOCUS, function() {
@@ -483,33 +498,27 @@ define('tools.querytool', [
}.bind(ctx),
};
});
},
// 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(
self.handler,
preference.value
);
self.reflectPreferences();
/* Register for preference changed event broadcasted in parent
* to reload the shorcuts. As sqleditor is in iFrame of wcDocker
* window parent is referred
*/
pgBrowser.onPreferencesChange('sqleditor', function() {
self.reflectPreferences();
});
/* If sql editor is in a new tab, event fired is not available
* instead, a poller is set up who will check
*/
if(self.preferences.new_browser_tab) {
setInterval(()=>{
if(window.opener.pgAdmin) {
self.reflectPreferences();
}
}, 1000);
}
},
@@ -805,6 +814,11 @@ define('tools.querytool', [
};
self.handler.slickgrid = grid;
self.handler.slickgrid.CSVOptions = {
quoting: self.preferences.results_grid_quoting,
quote_char: self.preferences.results_grid_quote_char,
field_separator: self.preferences.results_grid_field_separator,
};
// Listener function to watch selected rows from grid
if (editor_data.selection) {
@@ -903,29 +917,6 @@ define('tools.querytool', [
handleQueryOutputKeyboardEvent(event, args);
});
} else {
var pref_cache = undefined;
args.grid.CSVOptions = {};
if (self.handler.is_new_browser_tab) {
pref_cache = window.opener.pgAdmin.Browser.preferences_cache;
} else {
pref_cache = window.top.pgAdmin.Browser.preferences_cache;
}
// Get CSV options from preferences cache
args.grid.CSVOptions.quoting = _.findWhere(pref_cache, {
'module': 'sqleditor',
'name': 'results_grid_quoting',
}).value;
args.grid.CSVOptions.quote_char = _.findWhere(pref_cache, {
'module': 'sqleditor',
'name': 'results_grid_quote_char',
}).value;
args.grid.CSVOptions.field_separator = _.findWhere(pref_cache, {
'module': 'sqleditor',
'name': 'results_grid_field_separator',
}).value;
handleQueryOutputKeyboardEvent(event, args);
}
});
@@ -1196,7 +1187,10 @@ define('tools.querytool', [
render_history_grid: function() {
var self = this;
self.history_collection = new HistoryBundle.HistoryCollection([]);
/* Should not reset if function called again */
if(!self.history_collection) {
self.history_collection = new HistoryBundle.HistoryCollection([]);
}
var historyComponent;
var historyCollectionReactElement = React.createElement(
@@ -1205,9 +1199,13 @@ define('tools.querytool', [
ref: function(component) {
historyComponent = component;
},
sqlEditorPref: {
sql_font_size: SqlEditorUtils.calcFontSize(this.preferences.sql_font_size),
},
});
ReactDOM.render(historyCollectionReactElement, $('#history_grid')[0]);
self.history_panel.off(wcDocker.EVENT.VISIBILITY_CHANGED);
self.history_panel.on(wcDocker.EVENT.VISIBILITY_CHANGED, function() {
historyComponent.refocus();
});
@@ -1416,29 +1414,7 @@ define('tools.querytool', [
// Callback function for copy button click.
on_copy_row: function() {
var self = this,
pref_cache = undefined;
self.grid.CSVOptions = {};
if (self.handler.is_new_browser_tab) {
pref_cache = window.opener.pgAdmin.Browser.preferences_cache;
} else {
pref_cache = window.top.pgAdmin.Browser.preferences_cache;
}
// Get CSV options from preferences cache
self.grid.CSVOptions.quoting = _.findWhere(pref_cache, {
'module': 'sqleditor',
'name': 'results_grid_quoting',
}).value;
self.grid.CSVOptions.quote_char = _.findWhere(pref_cache, {
'module': 'sqleditor',
'name': 'results_grid_quote_char',
}).value;
self.grid.CSVOptions.field_separator = _.findWhere(pref_cache, {
'module': 'sqleditor',
'name': 'results_grid_field_separator',
}).value;
var self = this;
// Trigger the copy signal to the SqlEditorController class
self.handler.trigger(
@@ -1708,7 +1684,7 @@ define('tools.querytool', [
keyAction: function(event) {
var panel_id, self = this;
panel_id = keyboardShortcuts.processEventQueryTool(
this.handler, this.keyboardShortcutConfig, queryToolActions, event
this.handler, queryToolActions, event
);
// If it return panel id then focus it
@@ -1933,23 +1909,17 @@ define('tools.querytool', [
* header and loading icon and start execution of the sql query.
*/
start: function(transId, is_query_tool, editor_title, script_type_url,
is_new_browser_tab, server_type, prompt_save_changes, url_params
server_type, url_params
) {
var self = this;
self.is_query_tool = is_query_tool;
self.rows_affected = 0;
self.marked_line_no = 0;
self.explain_verbose = false;
self.explain_costs = false;
self.explain_buffers = false;
self.explain_timing = false;
self.is_new_browser_tab = is_new_browser_tab;
self.has_more_rows = false;
self.fetching_rows = false;
self.close_on_save = false;
self.server_type = server_type;
self.prompt_save_changes = prompt_save_changes;
self.url_params = url_params;
self.script_type_url = script_type_url;
@@ -2022,7 +1992,6 @@ define('tools.querytool', [
// Listen to the codemirror on text change event
// only in query editor tool
if (self.is_query_tool) {
self.get_preferences();
self.gridView.query_tool_obj.on('change', self._on_query_change.bind(self));
}
@@ -2830,7 +2799,7 @@ define('tools.querytool', [
setTitle: function(title, unsafe) {
var self = this;
if (self.is_new_browser_tab) {
if (self.preferences.new_browser_tab) {
window.document.title = title;
} else {
_.each(window.top.pgAdmin.Browser.docker.findPanels('frm_datagrid'), function(p) {
@@ -2992,7 +2961,7 @@ define('tools.querytool', [
var title = self.gridView.current_file.replace(/^.*[\\\/]/g, '') + ' *';
self.setTitle(title, true);
} else {
if (self.is_new_browser_tab) {
if (self.preferences.new_browser_tab) {
title = window.document.title + ' *';
} else {
// Find the title of the visible panel
@@ -3505,6 +3474,15 @@ define('tools.querytool', [
link.attr('src', url);
},
call_cache_preferences: function() {
let browser = window.opener ?
window.opener.pgAdmin.Browser : window.top.pgAdmin.Browser;
browser.cache_preferences('sqleditor');
/* This will make sure to get latest updates only and not older events */
pgBrowser.preference_version(pgBrowser.generate_preference_version());
},
_auto_rollback: function() {
var self = this,
auto_rollback = true;
@@ -3527,6 +3505,8 @@ define('tools.querytool', [
success: function(res) {
if (!res.data.status)
alertify.alert(gettext('Auto Rollback Error'), res.data.result);
else
self.call_cache_preferences();
},
error: function(e) {
@@ -3560,6 +3540,8 @@ define('tools.querytool', [
success: function(res) {
if (!res.data.status)
alertify.alert(gettext('Auto Commit Error'), res.data.result);
else
self.call_cache_preferences();
},
error: function(e) {
let msg = httpErrorHandler.handleQueryToolAjaxError(
@@ -3568,161 +3550,111 @@ define('tools.querytool', [
alertify.alert(gettext('Auto Commit Error'), msg);
},
});
},
explainPreferenceUpdate: function(subItem, data, caller) {
let self = this;
$.ajax({
url: url_for('sqleditor.query_tool_preferences', {
'trans_id': self.transId,
}),
method: 'PUT',
contentType: 'application/json',
data: JSON.stringify(data),
success: function(res) {
if (res.success == undefined || !res.success) {
alertify.alert(gettext('Explain options error'),
gettext('Error occurred while setting %(subItem)s option in explain.',
{subItem : subItem})
);
}
else
self.call_cache_preferences();
},
error: function(e) {
let msg = httpErrorHandler.handleQueryToolAjaxError(
pgAdmin, self, e, caller, [], true
);
alertify.alert(gettext('Explain options error'), msg);
},
});
},
// This function will toggle "verbose" option in explain
_explain_verbose: function() {
var self = this;
let explain_verbose = false;
if ($('.explain-verbose').hasClass('visibility-hidden') === true) {
$('.explain-verbose').removeClass('visibility-hidden');
self.explain_verbose = true;
explain_verbose = true;
} else {
$('.explain-verbose').addClass('visibility-hidden');
self.explain_verbose = false;
explain_verbose = false;
}
// Set this option in preferences
var data = {
'explain_verbose': self.explain_verbose,
};
$.ajax({
url: url_for('sqleditor.query_tool_preferences', {
'trans_id': self.transId,
}),
method: 'PUT',
contentType: 'application/json',
data: JSON.stringify(data),
success: function(res) {
if (res.success == undefined || !res.success) {
alertify.alert(gettext('Explain options error'),
gettext('Error occurred while setting verbose option in explain.')
);
}
},
error: function(e) {
let msg = httpErrorHandler.handleQueryToolAjaxError(
pgAdmin, self, e, '_explain_verbose', [], true
);
alertify.alert(gettext('Explain options error'), msg);
},
});
self.explainPreferenceUpdate(
'verbose', {
'explain_verbose': explain_verbose,
}, '_explain_verbose'
);
},
// This function will toggle "costs" option in explain
_explain_costs: function() {
var self = this;
let explain_costs = false;
if ($('.explain-costs').hasClass('visibility-hidden') === true) {
$('.explain-costs').removeClass('visibility-hidden');
self.explain_costs = true;
explain_costs = true;
} else {
$('.explain-costs').addClass('visibility-hidden');
self.explain_costs = false;
explain_costs = false;
}
// Set this option in preferences
var data = {
'explain_costs': self.explain_costs,
};
$.ajax({
url: url_for('sqleditor.query_tool_preferences', {
'trans_id': self.transId,
}),
method: 'PUT',
contentType: 'application/json',
data: JSON.stringify(data),
success: function(res) {
if (res.success == undefined || !res.success) {
alertify.alert(gettext('Explain options error'),
gettext('Error occurred while setting costs option in explain.')
);
}
},
error: function(e) {
let msg = httpErrorHandler.handleQueryToolAjaxError(
pgAdmin, self, e, '_explain_costs', [], true
);
alertify.alert(gettext('Explain options error'), msg);
},
});
self.explainPreferenceUpdate(
'costs', {
'explain_costs': explain_costs,
}, '_explain_costs'
);
},
// This function will toggle "buffers" option in explain
_explain_buffers: function() {
var self = this;
let explain_buffers = false;
if ($('.explain-buffers').hasClass('visibility-hidden') === true) {
$('.explain-buffers').removeClass('visibility-hidden');
self.explain_buffers = true;
explain_buffers = true;
} else {
$('.explain-buffers').addClass('visibility-hidden');
self.explain_buffers = false;
explain_buffers = false;
}
// Set this option in preferences
var data = {
'explain_buffers': self.explain_buffers,
};
$.ajax({
url: url_for('sqleditor.query_tool_preferences', {
'trans_id': self.transId,
}),
method: 'PUT',
contentType: 'application/json',
data: JSON.stringify(data),
success: function(res) {
if (res.success == undefined || !res.success) {
alertify.alert(gettext('Explain options error'),
gettext('Error occurred while setting buffers option in explain.')
);
}
},
error: function(e) {
let msg = httpErrorHandler.handleQueryToolAjaxError(
pgAdmin, self, e, '_explain_buffers', [], true
);
alertify.alert(gettext('Explain options error'), msg);
},
});
self.explainPreferenceUpdate(
'buffers', {
'explain_buffers': explain_buffers,
}, '_explain_buffers'
);
},
// This function will toggle "timing" option in explain
_explain_timing: function() {
var self = this;
let explain_timing = false;
if ($('.explain-timing').hasClass('visibility-hidden') === true) {
$('.explain-timing').removeClass('visibility-hidden');
self.explain_timing = true;
explain_timing = true;
} else {
$('.explain-timing').addClass('visibility-hidden');
self.explain_timing = false;
explain_timing = false;
}
// Set this option in preferences
var data = {
'explain_timing': self.explain_timing,
};
$.ajax({
url: url_for('sqleditor.query_tool_preferences', {
'trans_id': self.transId,
}),
method: 'PUT',
contentType: 'application/json',
data: JSON.stringify(data),
success: function(res) {
if (res.success == undefined || !res.success) {
alertify.alert(gettext('Explain options error'),
gettext('Error occurred while setting timing option in explain.')
);
}
},
error: function(e) {
let msg = httpErrorHandler.handleQueryToolAjaxError(
pgAdmin, self, e, '_explain_timing', [], true
);
alertify.alert(gettext('Explain options error'), msg);
},
});
self.explainPreferenceUpdate(
'timing', {
'explain_timing': explain_timing,
}, '_explain_timing'
);
},
/*
@@ -3751,85 +3683,6 @@ define('tools.querytool', [
is_query_running = value;
},
/*
* This function get explain options and auto rollback/auto commit
* values from preferences
*/
get_preferences: function() {
var self = this,
explain_verbose = false,
explain_costs = false,
explain_buffers = false,
explain_timing = false,
auto_commit = true,
auto_rollback = false,
updateUI = function() {
// Set Auto-commit and auto-rollback on query editor
if (auto_commit &&
$('.auto-commit').hasClass('visibility-hidden') === true)
$('.auto-commit').removeClass('visibility-hidden');
else {
$('.auto-commit').addClass('visibility-hidden');
}
if (auto_rollback &&
$('.auto-rollback').hasClass('visibility-hidden') === true)
$('.auto-rollback').removeClass('visibility-hidden');
else {
$('.auto-rollback').addClass('visibility-hidden');
}
// Set explain options on query editor
if (explain_verbose &&
$('.explain-verbose').hasClass('visibility-hidden') === true)
$('.explain-verbose').removeClass('visibility-hidden');
else {
$('.explain-verbose').addClass('visibility-hidden');
}
if (explain_costs &&
$('.explain-costs').hasClass('visibility-hidden') === true)
$('.explain-costs').removeClass('visibility-hidden');
else {
$('.explain-costs').addClass('visibility-hidden');
}
if (explain_buffers &&
$('.explain-buffers').hasClass('visibility-hidden') === true)
$('.explain-buffers').removeClass('visibility-hidden');
else {
$('.explain-buffers').addClass('visibility-hidden');
}
if (explain_timing &&
$('.explain-timing').hasClass('visibility-hidden') === true)
$('.explain-timing').removeClass('visibility-hidden');
else {
$('.explain-timing').addClass('visibility-hidden');
}
};
$.ajax({
url: url_for('sqleditor.query_tool_preferences', {
'trans_id': self.transId,
}),
method: 'GET',
success: function(res) {
if (res.data) {
explain_verbose = res.data.explain_verbose;
explain_costs = res.data.explain_costs;
explain_buffers = res.data.explain_buffers;
explain_timing = res.data.explain_timing;
auto_commit = res.data.auto_commit;
auto_rollback = res.data.auto_rollback;
updateUI();
}
},
error: function(e) {
let msg = httpErrorHandler.handleQueryToolAjaxError(
pgAdmin, self, e, 'get_preferences', [], true
);
updateUI();
alertify.alert(gettext('Get Preferences error'), msg);
},
});
},
close: function() {
var self = this;

View File

@@ -1,100 +0,0 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
from pgadmin.utils.route import BaseTestGenerator
from pgadmin.tools.sqleditor.utils.query_tool_preferences import \
get_text_representation_of_shortcut
class TestQueryToolPreference(BaseTestGenerator):
"""
Ensures that we are able to fetch preferences properly
"""
scenarios = [
('Check text representation of a valid shortcuts', dict(
fetch_pref=True,
sample_shortcut=dict(
alt=False,
shift=False,
control=False,
key=dict(
char='a',
keyCode=65
)
),
expected_result='a'
)),
('Check text representation of a valid shortcuts', dict(
fetch_pref=True,
sample_shortcut=dict(
alt=True,
shift=False,
control=False,
key=dict(
char='a',
keyCode=65
)
),
expected_result='Alt+a'
)),
('Check text representation of a valid shortcuts', dict(
fetch_pref=True,
sample_shortcut=dict(
alt=True,
shift=True,
control=True,
key=dict(
char='a',
keyCode=65
)
),
expected_result='Alt+Shift+Ctrl+a'
)),
('Check text representation of a valid shortcuts', dict(
fetch_pref=True,
sample_shortcut=dict(
alt=False,
shift=True,
control=False,
key=dict(
char='a',
keyCode=65
)
),
expected_result='Shift+a'
)),
('Check text representation of a valid shortcuts', dict(
fetch_pref=True,
sample_shortcut=dict(
alt=True,
shift=True,
control=False,
key=dict(
char='a',
keyCode=65
)
),
expected_result='Alt+Shift+a'
)),
('Check text representation of a invalid shortcuts', dict(
fetch_pref=True,
sample_shortcut=None,
expected_result=''
))
]
def runTest(self):
"""Check correct function is called to handle to run query."""
result = get_text_representation_of_shortcut(self.sample_shortcut)
self.assertEquals(result, self.expected_result)

View File

@@ -560,123 +560,3 @@ def RegisterQueryToolPreferences(self):
help_str=gettext('If set to True, Keywords will be displayed '
'in upper case for auto completion.')
)
def get_query_tool_keyboard_shortcuts():
"""
Fetch all the query tool shortcut preferences
Returns:
List of query tool shortcut preferences
"""
qt_perf = Preferences.module('sqleditor')
conn_status = qt_perf.preference('btn_conn_status').get()
clear_options = qt_perf.preference('btn_clear_options').get()
cancel_query = qt_perf.preference('btn_cancel_query').get()
execute_options = qt_perf.preference('btn_execute_options').get()
filter_options = qt_perf.preference('btn_filter_options').get()
rows_limit = qt_perf.preference('btn_rows_limit').get()
filter_dialog = qt_perf.preference('btn_filter_dialog').get()
delete_row = qt_perf.preference('btn_delete_row').get()
paste_row = qt_perf.preference('btn_paste_row').get()
copy_row = qt_perf.preference('btn_copy_row').get()
save_file = qt_perf.preference('btn_save_file').get()
open_file = qt_perf.preference('btn_open_file').get()
move_next = qt_perf.preference('move_next').get()
move_previous = qt_perf.preference('move_previous').get()
download_csv = qt_perf.preference('download_csv').get()
execute_query = qt_perf.preference('execute_query').get()
explain_query = qt_perf.preference('explain_query').get()
explain_analyze_query = qt_perf.preference('explain_analyze_query').get()
find_options = qt_perf.preference('btn_find_options').get()
toggle_case = qt_perf.preference('toggle_case').get()
return {
'keys': {
'conn_status': conn_status.get('key').get('char'),
'clear_options': clear_options.get('key').get('char'),
'cancel_query': cancel_query.get('key').get('char'),
'execute_options': execute_options.get('key').get('char'),
'filter_options': filter_options.get('key').get('char'),
'rows_limit': rows_limit.get('key').get('char'),
'filter_dialog': filter_dialog.get('key').get('char'),
'delete_row': delete_row.get('key').get('char'),
'paste_row': paste_row.get('key').get('char'),
'copy_row': copy_row.get('key').get('char'),
'save_file': save_file.get('key').get('char'),
'open_file': open_file.get('key').get('char'),
'move_next': move_next.get('key').get('char'),
'move_previous': move_previous.get('key').get('char'),
'download_csv': download_csv.get('key').get('char'),
'execute_query': execute_query.get('key').get('char'),
'explain_query': explain_query.get('key').get('char'),
'explain_analyze_query': explain_analyze_query.get('key').get(
'char'
),
'find_options': find_options.get('key').get('char'),
'toggle_case': toggle_case.get('key').get('char')
},
'shortcuts': {
'conn_status': conn_status,
'clear_options': clear_options,
'cancel_query': cancel_query,
'execute_options': execute_options,
'filter_options': filter_options,
'rows_limit': rows_limit,
'filter_dialog': filter_dialog,
'delete_row': delete_row,
'paste_row': paste_row,
'copy_row': copy_row,
'save_file': save_file,
'open_file': open_file,
'move_next': move_next,
'move_previous': move_previous,
'download_csv': download_csv,
'execute_query': execute_query,
'explain_query': explain_query,
'explain_analyze_query': explain_analyze_query,
'find_options': find_options,
'toggle_case': toggle_case
},
}
def get_text_representation_of_shortcut(shortcut):
"""
Coverts shortcut object to text representation
Args:
shortcut: Shortcut object
Returns:
Text representation of given shortcut
"""
text_representation = ''
is_plus_required = False
if not shortcut:
return text_representation
if shortcut['alt']:
text_representation = gettext('Alt')
is_plus_required = True
if shortcut['shift']:
if is_plus_required:
text_representation += '+'
text_representation += gettext('Shift')
is_plus_required = True
if shortcut['control']:
if is_plus_required:
text_representation += '+'
text_representation += gettext('Ctrl')
is_plus_required = True
if shortcut['key'] and shortcut['key']['char']:
if is_plus_required:
text_representation += '+'
text_representation += '{0}'.format(shortcut['key']['char'])
return text_representation