From 73530c05aa3fbef4f51ddc06316acbdb46e7133d Mon Sep 17 00:00:00 2001 From: Aditya Toshniwal Date: Thu, 5 Jul 2018 11:41:01 +0100 Subject: [PATCH] Add missing files related to previous commit: Infrastructure and changes to the Query Tool for realtime preference handling. Refs #3294 --- docs/en_US/release_notes_3_2.rst | 1 + web/pgadmin/browser/static/js/preferences.js | 146 ++++++++++++++ .../js/sqleditor/query_tool_preferences.js | 179 ++++++++++++++++++ .../javascript/browser/preferences_spec.js | 152 +++++++++++++++ 4 files changed, 478 insertions(+) create mode 100644 web/pgadmin/browser/static/js/preferences.js create mode 100644 web/pgadmin/static/js/sqleditor/query_tool_preferences.js create mode 100644 web/regression/javascript/browser/preferences_spec.js diff --git a/docs/en_US/release_notes_3_2.rst b/docs/en_US/release_notes_3_2.rst index 8213c3655..e2682cb8e 100644 --- a/docs/en_US/release_notes_3_2.rst +++ b/docs/en_US/release_notes_3_2.rst @@ -16,6 +16,7 @@ Features Bug fixes ********* +| `Bug #3294 `_ - Infrastructure (and changes to the Query Tool) for realtime preference handling. | `Bug #3309 `_ - Fix Directory format support for backups. | `Bug #3319 `_ - Cleanup and fix handling of Query Tool Cancel button status. | `Bug #3363 `_ - Fix restoring of restore options for sections. diff --git a/web/pgadmin/browser/static/js/preferences.js b/web/pgadmin/browser/static/js/preferences.js new file mode 100644 index 000000000..b0c6dc483 --- /dev/null +++ b/web/pgadmin/browser/static/js/preferences.js @@ -0,0 +1,146 @@ +import pgAdmin from 'sources/pgadmin'; +import url_for from 'sources/url_for'; +import * as modifyAnimation from 'sources/modify_animation'; +import $ from 'jquery'; +import * as Alertify from 'pgadmin.alertifyjs'; +import * as SqlEditorUtils from 'sources/sqleditor_utils'; + +const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {}; + +/* Add cache related methods and properties */ +_.extend(pgBrowser, { + /* This will hold preference data (Works as a cache object) + * Here node will be a key and it's preference data will be value + */ + preferences_cache: [], + + /* This will be used by poller of new tabs/windows to check + * if preference cache is updated in parent/window.opener. + */ + prefcache_version: 0, + + /* Generate a unique version number */ + generate_preference_version: function() { + return (new Date()).getTime(); + }, + + preference_version: function(version) { + if(version) { + this.prefcache_version = version; + } + else { + return this.prefcache_version; + } + }, + + /* Get cached preference */ + get_preference: function(module, preference){ + const self = this; + // If cache is not yet loaded then keep checking + if(_.size(self.preferences_cache) == 0) { + var check_preference = function() { + if(self.preferences_cache.length > 0) { + clearInterval(preferenceTimeout); + return _.findWhere( + self.preferences_cache, {'module': module, 'name': preference} + ); + } + }, + preferenceTimeout = setInterval(check_preference, 1000); + } + else { + return _.findWhere( + self.preferences_cache, {'module': module, 'name': preference} + ); + } + }, + + /* Get all the preferences of a module */ + get_preferences_for_module: function(module) { + var self = this; + let preferences = {}; + _.each( + _.where(self.preferences_cache, {'module': module}), + (preference) => { + preferences[preference.name] = preference.value; + } + ); + return preferences; + }, + + /* Get preference of an id, id is numeric */ + get_preference_for_id : function(id) { + var self = this; + return _.findWhere(self.preferences_cache, {'id': id}); + }, + + // Get and cache the preferences + cache_preferences: function (modulesChanged) { + var self = this; + setTimeout(function() { + $.ajax({ + url: url_for('preferences.get_all'), + success: function(res) { + self.preferences_cache = res; + self.preference_version(self.generate_preference_version()); + + pgBrowser.keyboardNavigation.init(); + if(pgBrowser.tree) { + modifyAnimation.modifyAcitreeAnimation(self); + modifyAnimation.modifyAlertifyAnimation(self); + } + + /* Once the cache is loaded after changing the preferences, + * notify the modules of the change + */ + if(modulesChanged) { + if(typeof modulesChanged === 'string'){ + $.event.trigger('prefchange:'+modulesChanged); + } else { + _.each(modulesChanged, (val, key)=> { + $.event.trigger('prefchange:'+key); + }); + } + } + }, + error: function(xhr, status, error) { + Alertify.pgRespErrorNotify(xhr, error); + }, + }); + }, 500); + }, + + reflectPreferences: function(module) { + let obj = this; + + if(module === 'sqleditor' || module === null || typeof module === 'undefined') { + let sqlEditPreferences = obj.get_preferences_for_module('sqleditor'); + + $(obj.editor.getWrapperElement()).css( + 'font-size',SqlEditorUtils.calcFontSize(sqlEditPreferences.sql_font_size) + ); + obj.editor.setOption('tabSize', sqlEditPreferences.tab_size); + obj.editor.setOption('lineWrapping', sqlEditPreferences.wrap_code); + obj.editor.setOption('autoCloseBrackets', sqlEditPreferences.insert_pair_brackets); + obj.editor.setOption('matchBrackets', sqlEditPreferences.brace_matching); + obj.editor.refresh(); + } + }, + + onPreferencesChange: function(module, eventHandler) { + window.parent.$(parent.document).on('prefchange:'+module, function(event) { + /* If a sqleditor is closed, event handler will be called + * but the window.top will be null. Unbind the event handler + */ + if(window.top === null) { + window.$(document).off(event); + } + else { + eventHandler(event); + } + }); + }, + +}); + +export {pgBrowser}; diff --git a/web/pgadmin/static/js/sqleditor/query_tool_preferences.js b/web/pgadmin/static/js/sqleditor/query_tool_preferences.js new file mode 100644 index 000000000..f26a23a24 --- /dev/null +++ b/web/pgadmin/static/js/sqleditor/query_tool_preferences.js @@ -0,0 +1,179 @@ +import {shortcut_key, shortcut_accesskey_title, shortcut_title} + from 'sources/keyboard_shortcuts'; +import * as SqlEditorUtils from 'sources/sqleditor_utils'; +import $ from 'jquery'; + +function updateUIPreferences(sqlEditor) { + let $el = sqlEditor.$el, + preferences = sqlEditor.preferences; + + if(sqlEditor.handler.slickgrid) { + sqlEditor.handler.slickgrid.CSVOptions = { + quoting: sqlEditor.preferences.results_grid_quoting, + quote_char: sqlEditor.preferences.results_grid_quote_char, + field_separator: sqlEditor.preferences.results_grid_field_separator, + }; + } + + /* Accessed using accesskey direct w/o ctrl,atl,shift */ + $el.find('#btn-load-file') + .attr('title', shortcut_accesskey_title('Open File',preferences.btn_open_file)) + .attr('accesskey', shortcut_key(preferences.btn_open_file)); + + $el.find('#btn-save') + .attr('title', shortcut_accesskey_title('Save File',preferences.btn_save_file)) + .attr('accesskey', shortcut_key(preferences.btn_save_file)); + + $el.find('#btn-find-menu-dropdown') + .attr('title', shortcut_accesskey_title('Find',preferences.btn_find_options)) + .attr('accesskey', shortcut_key(preferences.btn_find_options)); + + $el.find('#btn-copy-row') + .attr('title', shortcut_accesskey_title('Copy',preferences.btn_copy_row)) + .attr('accesskey', shortcut_key(preferences.btn_copy_row)); + + $el.find('#btn-paste-row') + .attr('title', shortcut_accesskey_title('Paste',preferences.btn_paste_row)) + .attr('accesskey', shortcut_key(preferences.btn_paste_row)); + + $el.find('#btn-delete-row') + .attr('title', shortcut_accesskey_title('Delete',preferences.btn_delete_row)) + .attr('accesskey', shortcut_key(preferences.btn_delete_row)); + + $el.find('#btn-filter') + .attr('title', shortcut_accesskey_title('Filter',preferences.btn_filter_dialog)) + .attr('accesskey', shortcut_key(preferences.btn_filter_dialog)); + + $el.find('#btn-filter-dropdown') + .attr('title', shortcut_accesskey_title('Filter options',preferences.btn_filter_options)) + .attr('accesskey', shortcut_key(preferences.btn_filter_options)); + + $el.find('#btn-rows-limit') + .attr('title', shortcut_accesskey_title('Rows limit',preferences.btn_rows_limit)) + .attr('accesskey', shortcut_key(preferences.btn_rows_limit)); + + $el.find('#btn-query-dropdown') + .attr('title', shortcut_accesskey_title('Execute options',preferences.btn_execute_options)) + .attr('accesskey', shortcut_key(preferences.btn_execute_options)); + + $el.find('#btn-cancel-query') + .attr('title', shortcut_accesskey_title('Cancel query',preferences.btn_cancel_query)) + .attr('accesskey', shortcut_key(preferences.btn_cancel_query)); + + $el.find('#btn-clear-dropdown') + .attr('title', shortcut_accesskey_title('Clear',preferences.btn_clear_options)) + .attr('accesskey', shortcut_key(preferences.btn_clear_options)); + + $el.find('#btn-conn-status') + .attr('accesskey', shortcut_key(preferences.btn_conn_status)) + .find('i') + .attr('title', + shortcut_accesskey_title('Connection status (click for details)', + preferences.btn_conn_status)); + + /* Accessed using ctrl,atl,shift and key */ + $el.find('#btn-flash') + .attr('title', + shortcut_title('Execute/Refresh',preferences.execute_query)); + + $el.find('#btn-flash-menu span') + .text(shortcut_title('Execute/Refresh',preferences.execute_query)); + + $el.find('#btn-explain span') + .text(shortcut_title('Explain',preferences.explain_query)); + + $el.find('#btn-explain-analyze span') + .text(shortcut_title('Explain Analyze',preferences.explain_analyze_query)); + + $el.find('#btn-download') + .attr('title', + shortcut_title('Download as CSV',preferences.download_csv)); + + /* Set Auto-commit and auto-rollback on query editor */ + if (preferences.auto_commit) { + $el.find('.auto-commit').removeClass('visibility-hidden'); + } + else { + $el.find('.auto-commit').addClass('visibility-hidden'); + } + if (preferences.auto_rollback) { + $el.find('.auto-rollback').removeClass('visibility-hidden'); + } + else { + $el.find('.auto-rollback').addClass('visibility-hidden'); + } + + /* Set explain options on query editor */ + if (preferences.explain_verbose){ + $el.find('.explain-verbose').removeClass('visibility-hidden'); + } + else { + $el.find('.explain-verbose').addClass('visibility-hidden'); + } + + if (preferences.explain_costs){ + $el.find('.explain-costs').removeClass('visibility-hidden'); + } + else { + $el.find('.explain-costs').addClass('visibility-hidden'); + } + + if (preferences.explain_buffers){ + $el.find('.explain-buffers').removeClass('visibility-hidden'); + } + else { + $el.find('.explain-buffers').addClass('visibility-hidden'); + } + + if (preferences.explain_timing) { + $el.find('.explain-timing').removeClass('visibility-hidden'); + } + else { + $el.find('.explain-timing').addClass('visibility-hidden'); + } + + /* Connection status check */ + /* remove the status checker if present */ + if(sqlEditor.connIntervalId != null) { + clearInterval(sqlEditor.connIntervalId); + sqlEditor.connIntervalId = null; + } + if (preferences.connection_status) { + let $conn_status = $el.find('#btn-conn-status'), + $status_el = $conn_status.find('i'); + $conn_status.popover(); + + $conn_status.removeClass('connection-status-hide'); + $el.find('.editor-title').addClass('editor-title-connection'); + + // To set initial connection + SqlEditorUtils.fetchConnectionStatus(sqlEditor.handler, $conn_status, $status_el); + + // Calling it again in specified interval + sqlEditor.connIntervalId = setInterval( + SqlEditorUtils.fetchConnectionStatus.bind(null, sqlEditor.handler, $conn_status, $status_el), + preferences.connection_status_fetch_time * 1000 + ); + } + else { + $el.find('#btn-conn-status').addClass('connection-status-hide'); + $el.find('.editor-title').removeClass('editor-title-connection'); + } + + /* Code Mirror Preferences */ + let sql_font_size = SqlEditorUtils.calcFontSize(preferences.sql_font_size); + $(sqlEditor.query_tool_obj.getWrapperElement()).css('font-size', sql_font_size); + + sqlEditor.query_tool_obj.setOption('indentWithTabs', !preferences.use_spaces); + sqlEditor.query_tool_obj.setOption('indentUnit', preferences.tab_size); + sqlEditor.query_tool_obj.setOption('tabSize', preferences.tab_size); + sqlEditor.query_tool_obj.setOption('lineWrapping', preferences.wrap_code); + sqlEditor.query_tool_obj.setOption('autoCloseBrackets', preferences.insert_pair_brackets); + sqlEditor.query_tool_obj.setOption('matchBrackets', preferences.brace_matching); + sqlEditor.query_tool_obj.refresh(); + + /* Render history to reflect Font size change */ + sqlEditor.render_history_grid(); +} + +export {updateUIPreferences}; diff --git a/web/regression/javascript/browser/preferences_spec.js b/web/regression/javascript/browser/preferences_spec.js new file mode 100644 index 000000000..14bc32ee0 --- /dev/null +++ b/web/regression/javascript/browser/preferences_spec.js @@ -0,0 +1,152 @@ +import {pgBrowser} from 'pgadmin.browser.preferences'; +import $ from 'jquery'; + +var dummy_cache = [ + { + id: 1, + mid: 1, + module:'module1', + name:'pref1', + value:{ + alt: false, + shift: false, + control: false, + key: { + char: 'a', + key_code: 65, + }, + }, + },{ + id: 2, + mid: 1, + module:'module1', + name:'pref2', + value: 123, + },{ + id: 3, + mid: 2, + module:'module2', + name:'pref2', + value: true, + }, +]; + +describe('preferences related functions test', function() { + describe('get preference data related functions', function(){ + beforeEach(function(){ + pgBrowser.preferences_cache = dummy_cache; + }); + + it('generate_preference_version', function() { + pgBrowser.generate_preference_version(); + expect(pgBrowser.generate_preference_version()).toBeGreaterThan(0); + }); + + it('preference_version', function() { + let version = 123; + pgBrowser.preference_version(version); + expect(pgBrowser.prefcache_version).toEqual(version); + expect(pgBrowser.preference_version()).toEqual(version); + }); + + it('get_preference', function(){ + expect(pgBrowser.get_preference('module1','pref1')).toEqual({ + id: 1, + mid: 1, + module:'module1', + name:'pref1', + value:{ + alt: false, + shift: false, + control: false, + key: { + char: 'a', + key_code: 65, + }, + }, + }); + }); + + it('get_preferences_for_module', function() { + expect(pgBrowser.get_preferences_for_module('module1')).toEqual({ + 'pref1':{ + alt: false, + shift: false, + control: false, + key: { + char: 'a', + key_code: 65, + }, + }, + 'pref2': 123, + }); + }); + + it('get_preference_for_id', function() { + expect(pgBrowser.get_preference_for_id(3)).toEqual({ + id: 3, + mid: 2, + module:'module2', + name:'pref2', + value: true, + }); + }); + + it('reflectPreferences', function() { + + let editorOptions = { + 'tabSize':2, + 'lineWrapping':false, + 'autoCloseBrackets':true, + 'matchBrackets':true, + }; + pgBrowser.preferences_cache.push({ + id: 4, mid: 3, module:'sqleditor', name:'sql_font_size', value: 1.456, + }); + pgBrowser.preferences_cache.push({ + id: 4, mid: 3, module:'sqleditor', name:'tab_size', value: editorOptions.tabSize, + }); + pgBrowser.preferences_cache.push({ + id: 4, mid: 3, module:'sqleditor', name:'wrap_code', value: editorOptions.lineWrapping, + }); + pgBrowser.preferences_cache.push({ + id: 4, mid: 3, module:'sqleditor', name:'insert_pair_brackets', value: editorOptions.autoCloseBrackets, + }); + pgBrowser.preferences_cache.push({ + id: 4, mid: 3, module:'sqleditor', name:'brace_matching', value: editorOptions.matchBrackets, + }); + + /* Spies */ + pgBrowser.editor = jasmine.createSpyObj( + 'CodeMirror', ['setOption','refresh','getWrapperElement'] + ); + spyOn($.fn, 'css'); + + /* Call */ + pgBrowser.reflectPreferences(); + + /* Tests */ + expect(pgBrowser.editor.getWrapperElement).toHaveBeenCalled(); + expect($.fn.css).toHaveBeenCalledWith('font-size', '1.46em'); + + let setOptionCalls = pgBrowser.editor.setOption.calls; + expect(setOptionCalls.count()).toBe(Object.keys(editorOptions).length); + + for(let i = 0; i < Object.keys(editorOptions).length; i++) { + let option = Object.keys(editorOptions)[i]; + expect(setOptionCalls.argsFor(i)).toEqual([option, editorOptions[option]]); + } + expect(pgBrowser.editor.refresh).toHaveBeenCalled(); + }); + + it('onPreferencesChange', function() { + + window.parent.$ = $; + spyOn($.fn, 'on'); + + var eventHandler = jasmine.createSpy('eventHandler'); + pgBrowser.onPreferencesChange('somemodule', eventHandler); + expect($.fn.on.calls.mostRecent().args[0]).toEqual('prefchange:somemodule'); + }); + }); +});