mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Save the treeview state periodically, and restore it automatically when reconnecting. Fixes #1253
This commit is contained in:
parent
44ef501283
commit
528ea88dec
@ -9,6 +9,7 @@ for it.
|
|||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
|
|
||||||
|
release_notes_3_5
|
||||||
release_notes_3_4
|
release_notes_3_4
|
||||||
release_notes_3_3
|
release_notes_3_3
|
||||||
release_notes_3_2
|
release_notes_3_2
|
||||||
|
18
docs/en_US/release_notes_3_5.rst
Normal file
18
docs/en_US/release_notes_3_5.rst
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
***********
|
||||||
|
Version 3.5
|
||||||
|
***********
|
||||||
|
|
||||||
|
Release date: 2018-11-01
|
||||||
|
|
||||||
|
This release contains a number of features and fixes reported since the release of pgAdmin4 3.4
|
||||||
|
|
||||||
|
|
||||||
|
Features
|
||||||
|
********
|
||||||
|
|
||||||
|
| `Feature #1253 <https://redmine.postgresql.org/issues/1253>`_ - Save the treeview state periodically, and restore it automatically when reconnecting.
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
*********
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,16 @@ def register_browser_preferences(self):
|
|||||||
True, category_label=gettext('Display')
|
True, category_label=gettext('Display')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.preference.register(
|
||||||
|
'display', 'browser_tree_state_save_interval',
|
||||||
|
gettext("Browser tree state saving interval"), 'integer',
|
||||||
|
30, category_label=gettext('Display'),
|
||||||
|
help_str=gettext(
|
||||||
|
'Browser tree state saving interval in seconds.'
|
||||||
|
'Use -1 to disable the tree saving mechanism.'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.table_row_count_threshold = self.preference.register(
|
self.table_row_count_threshold = self.preference.register(
|
||||||
'properties', 'table_row_count_threshold',
|
'properties', 'table_row_count_threshold',
|
||||||
gettext("Count rows if estimated less than"), 'integer', 2000,
|
gettext("Count rows if estimated less than"), 'integer', 2000,
|
||||||
|
@ -585,7 +585,8 @@ class ServerNode(PGChildNodeView):
|
|||||||
server_icon_and_background(connected, manager, server),
|
server_icon_and_background(connected, manager, server),
|
||||||
True,
|
True,
|
||||||
self.node_type,
|
self.node_type,
|
||||||
connected=False,
|
connected=connected,
|
||||||
|
user=manager.user_info if connected else None,
|
||||||
server_type='pg' # default server type
|
server_type='pg' # default server type
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -9,7 +9,7 @@ define('pgadmin.browser', [
|
|||||||
'pgadmin.browser.error', 'pgadmin.browser.frame',
|
'pgadmin.browser.error', 'pgadmin.browser.frame',
|
||||||
'pgadmin.browser.node', 'pgadmin.browser.collection',
|
'pgadmin.browser.node', 'pgadmin.browser.collection',
|
||||||
'sources/codemirror/addon/fold/pgadmin-sqlfoldcode',
|
'sources/codemirror/addon/fold/pgadmin-sqlfoldcode',
|
||||||
'pgadmin.browser.keyboard',
|
'pgadmin.browser.keyboard', 'sources/tree/pgadmin_tree_save_state',
|
||||||
], function(
|
], function(
|
||||||
tree,
|
tree,
|
||||||
gettext, url_for, require, $, _, S, Bootstrap, pgAdmin, Alertify,
|
gettext, url_for, require, $, _, S, Bootstrap, pgAdmin, Alertify,
|
||||||
@ -513,9 +513,11 @@ define('pgadmin.browser', [
|
|||||||
.done(function() {})
|
.done(function() {})
|
||||||
.fail(function() {});
|
.fail(function() {});
|
||||||
}, 300000);
|
}, 300000);
|
||||||
|
|
||||||
obj.Events.on('pgadmin:browser:tree:add', obj.onAddTreeNode, obj);
|
obj.Events.on('pgadmin:browser:tree:add', obj.onAddTreeNode, obj);
|
||||||
obj.Events.on('pgadmin:browser:tree:update', obj.onUpdateTreeNode, obj);
|
obj.Events.on('pgadmin:browser:tree:update', obj.onUpdateTreeNode, obj);
|
||||||
obj.Events.on('pgadmin:browser:tree:refresh', obj.onRefreshTreeNode, obj);
|
obj.Events.on('pgadmin:browser:tree:refresh', obj.onRefreshTreeNode, obj);
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
add_menu_category: function(
|
add_menu_category: function(
|
||||||
@ -1942,19 +1944,10 @@ define('pgadmin.browser', [
|
|||||||
pgAdmin.Browser.editor_shortcut_keys.Tab = 'insertSoftTab';
|
pgAdmin.Browser.editor_shortcut_keys.Tab = 'insertSoftTab';
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onbeforeunload = function() {
|
$(window).on('beforeunload', function() {
|
||||||
var e = window.event,
|
if (pgBrowser.get_preference('browser', 'browser_tree_state_save_interval').value !== -1)
|
||||||
msg = S(gettext('Are you sure you wish to close the %s browser?')).sprintf(pgBrowser.utils.app_name).value();
|
pgAdmin.Browser.browserTreeState.save_state();
|
||||||
|
});
|
||||||
// For IE and Firefox prior to version 4
|
|
||||||
if (e) {
|
|
||||||
e.returnValue = msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For Safari
|
|
||||||
return msg;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
return pgAdmin.Browser;
|
return pgAdmin.Browser;
|
||||||
});
|
});
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
define('pgadmin.browser.node', [
|
define('pgadmin.browser.node', [
|
||||||
'sources/tree/pgadmin_tree_node',
|
'sources/tree/pgadmin_tree_node', 'sources/url_for',
|
||||||
'sources/gettext', 'jquery', 'underscore', 'underscore.string', 'sources/pgadmin',
|
'sources/gettext', 'jquery', 'underscore', 'underscore.string', 'sources/pgadmin',
|
||||||
'pgadmin.browser.menu', 'backbone', 'pgadmin.alertifyjs', 'pgadmin.browser.datamodel',
|
'pgadmin.browser.menu', 'backbone', 'pgadmin.alertifyjs', 'pgadmin.browser.datamodel',
|
||||||
'backform', 'sources/browser/generate_url', 'sources/utils', 'pgadmin.browser.utils',
|
'backform', 'sources/browser/generate_url', 'sources/utils', 'pgadmin.browser.utils',
|
||||||
'pgadmin.backform',
|
'pgadmin.backform',
|
||||||
], function(
|
], function(
|
||||||
pgadminTreeNode,
|
pgadminTreeNode, url_for,
|
||||||
gettext, $, _, S, pgAdmin, Menu, Backbone, Alertify, pgBrowser, Backform, generateUrl, commonUtils
|
gettext, $, _, S, pgAdmin, Menu, Backbone, Alertify, pgBrowser, Backform, generateUrl, commonUtils
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@ -849,7 +849,6 @@ define('pgadmin.browser.node', [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
added: function(item, data, browser) {
|
added: function(item, data, browser) {
|
||||||
var b = browser || pgBrowser,
|
var b = browser || pgBrowser,
|
||||||
t = b.tree,
|
t = b.tree,
|
||||||
@ -876,6 +875,8 @@ define('pgadmin.browser.node', [
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pgBrowser.Events.trigger('pgadmin:browser:tree:expand-from-previous-tree-state',
|
||||||
|
item);
|
||||||
pgBrowser.Node.callbacks.change_server_background(item, data);
|
pgBrowser.Node.callbacks.change_server_background(item, data);
|
||||||
},
|
},
|
||||||
// Callback called - when a node is selected in browser tree.
|
// Callback called - when a node is selected in browser tree.
|
||||||
@ -905,6 +906,8 @@ define('pgadmin.browser.node', [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pgBrowser.Events.trigger('pgadmin:browser:tree:update-tree-state',
|
||||||
|
item);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
removed: function(item) {
|
removed: function(item) {
|
||||||
@ -958,6 +961,14 @@ define('pgadmin.browser.node', [
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
opened: function(item) {
|
||||||
|
pgBrowser.Events.trigger('pgadmin:browser:tree:update-tree-state',
|
||||||
|
item);
|
||||||
|
},
|
||||||
|
closed: function(item) {
|
||||||
|
pgBrowser.Events.trigger('pgadmin:browser:tree:remove-from-tree-state',
|
||||||
|
item);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* A hook (not a callback) to show object properties in given HTML
|
* A hook (not a callback) to show object properties in given HTML
|
||||||
|
@ -94,6 +94,9 @@ _.extend(pgBrowser, {
|
|||||||
modifyAnimation.modifyAlertifyAnimation(self);
|
modifyAnimation.modifyAlertifyAnimation(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize Tree saving/reloading
|
||||||
|
pgBrowser.browserTreeState.init();
|
||||||
|
|
||||||
/* Once the cache is loaded after changing the preferences,
|
/* Once the cache is loaded after changing the preferences,
|
||||||
* notify the modules of the change
|
* notify the modules of the change
|
||||||
*/
|
*/
|
||||||
|
@ -2,6 +2,7 @@ define('pgadmin.preferences', [
|
|||||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
|
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
|
||||||
'pgadmin.alertifyjs', 'sources/pgadmin', 'pgadmin.backform',
|
'pgadmin.alertifyjs', 'sources/pgadmin', 'pgadmin.backform',
|
||||||
'pgadmin.browser', 'sources/modify_animation',
|
'pgadmin.browser', 'sources/modify_animation',
|
||||||
|
'sources/tree/pgadmin_tree_save_state',
|
||||||
], function(
|
], function(
|
||||||
gettext, url_for, $, _, Backbone, Alertify, pgAdmin, Backform, pgBrowser,
|
gettext, url_for, $, _, Backbone, Alertify, pgAdmin, Backform, pgBrowser,
|
||||||
modifyAnimation
|
modifyAnimation
|
||||||
|
@ -10,13 +10,15 @@
|
|||||||
"""Utility functions for storing and retrieving user configuration settings."""
|
"""Utility functions for storing and retrieving user configuration settings."""
|
||||||
|
|
||||||
import traceback
|
import traceback
|
||||||
|
import json
|
||||||
|
|
||||||
from flask import Response, request, render_template, url_for
|
from flask import Response, request, render_template, url_for, current_app
|
||||||
from flask_babelex import gettext
|
from flask_babelex import gettext
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask_security import login_required
|
from flask_security import login_required
|
||||||
from pgadmin.utils import PgAdminModule
|
from pgadmin.utils import PgAdminModule
|
||||||
from pgadmin.utils.ajax import make_json_response, bad_request
|
from pgadmin.utils.ajax import make_json_response, bad_request,\
|
||||||
|
success_return, internal_server_error
|
||||||
from pgadmin.utils.menu import MenuItem
|
from pgadmin.utils.menu import MenuItem
|
||||||
|
|
||||||
from pgadmin.model import db, Setting
|
from pgadmin.model import db, Setting
|
||||||
@ -52,7 +54,8 @@ class SettingsModule(PgAdminModule):
|
|||||||
list: a list of url endpoints exposed to the client.
|
list: a list of url endpoints exposed to the client.
|
||||||
"""
|
"""
|
||||||
return [
|
return [
|
||||||
'settings.store', 'settings.store_bulk', 'settings.reset_layout'
|
'settings.store', 'settings.store_bulk', 'settings.reset_layout',
|
||||||
|
'settings.save_tree_state', 'settings.get_tree_state'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -145,3 +148,48 @@ def reset_layout():
|
|||||||
)
|
)
|
||||||
|
|
||||||
return make_json_response(result=request.form)
|
return make_json_response(result=request.form)
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route("/save_tree_state/", endpoint="save_tree_state",
|
||||||
|
methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def save_browser_tree_state():
|
||||||
|
"""Save the browser tree state."""
|
||||||
|
data = request.form if request.form else request.data.decode('utf-8')
|
||||||
|
old_data = get_setting('browser_tree_state')
|
||||||
|
|
||||||
|
if old_data and old_data != 'null':
|
||||||
|
if data:
|
||||||
|
data = json.loads(data)
|
||||||
|
|
||||||
|
old_data = json.loads(old_data)
|
||||||
|
|
||||||
|
old_data.update(data)
|
||||||
|
new_data = json.dumps(old_data)
|
||||||
|
else:
|
||||||
|
new_data = data
|
||||||
|
|
||||||
|
try:
|
||||||
|
store_setting('browser_tree_state', new_data)
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.exception(e)
|
||||||
|
return internal_server_error(errormsg=str(e))
|
||||||
|
|
||||||
|
return success_return()
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route("/get_tree_state/", endpoint="get_tree_state",
|
||||||
|
methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def get_browser_tree_state():
|
||||||
|
"""Get the browser tree state."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = get_setting('browser_tree_state')
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.exception(e)
|
||||||
|
return internal_server_error(errormsg=str(e))
|
||||||
|
|
||||||
|
return Response(response=data,
|
||||||
|
status=200,
|
||||||
|
mimetype="application/json")
|
||||||
|
331
web/pgadmin/static/js/tree/pgadmin_tree_save_state.js
Normal file
331
web/pgadmin/static/js/tree/pgadmin_tree_save_state.js
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
import _ from 'underscore';
|
||||||
|
import $ from 'jquery';
|
||||||
|
import url_for from 'sources/url_for';
|
||||||
|
import gettext from 'sources/gettext';
|
||||||
|
import pgAdmin from 'sources/pgadmin';
|
||||||
|
|
||||||
|
const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
|
||||||
|
|
||||||
|
|
||||||
|
const browserTreeState = pgBrowser.browserTreeState = pgBrowser.browserTreeState || {};
|
||||||
|
|
||||||
|
_.extend(pgBrowser.browserTreeState, {
|
||||||
|
|
||||||
|
// Parent node to start saving / reloading the tree state
|
||||||
|
parent: 'server',
|
||||||
|
|
||||||
|
// The original parent of the browser tree
|
||||||
|
orig_parent: 'server_group',
|
||||||
|
|
||||||
|
// Stored tree state
|
||||||
|
// Sample Object
|
||||||
|
// {1:
|
||||||
|
// 'paths': [
|
||||||
|
// server_group/1,/server/1,/coll-database/1,/database/1,
|
||||||
|
// server_group/1,/server/1,/coll-database/1,/database/2,
|
||||||
|
// ],
|
||||||
|
// 'selected': {
|
||||||
|
// 'server/1': 'database/2',
|
||||||
|
// 'database/1': 'table/1',
|
||||||
|
// },
|
||||||
|
// 'conn_status': {
|
||||||
|
// 'database/1': 1,
|
||||||
|
// 'database/2': 0 ,
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// Here key is server ID
|
||||||
|
|
||||||
|
stored_state: {},
|
||||||
|
|
||||||
|
// Previous tree state
|
||||||
|
last_state: {},
|
||||||
|
|
||||||
|
// Current tree state
|
||||||
|
current_state: {},
|
||||||
|
|
||||||
|
init: function() {
|
||||||
|
|
||||||
|
const save_tree_state_period = pgBrowser.get_preference('browser', 'browser_tree_state_save_interval');
|
||||||
|
|
||||||
|
if (!_.isUndefined(save_tree_state_period) && save_tree_state_period.value !== -1) {
|
||||||
|
// Save the tree state every 30 seconds
|
||||||
|
setInterval(this.save_state, (save_tree_state_period.value) * 1000);
|
||||||
|
|
||||||
|
// Fetch the tree last state while loading the browser tree
|
||||||
|
this.fetch_state.apply(this);
|
||||||
|
|
||||||
|
pgBrowser.Events.on('pgadmin:browser:tree:expand-from-previous-tree-state',
|
||||||
|
this.expand_from_previous_state, this);
|
||||||
|
pgBrowser.Events.on('pgadmin:browser:tree:remove-from-tree-state',
|
||||||
|
this.remove_from_cache, this);
|
||||||
|
pgBrowser.Events.on('pgadmin:browser:tree:update-tree-state',
|
||||||
|
this.update_cache, this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
save_state: function() {
|
||||||
|
|
||||||
|
var self = pgBrowser.browserTreeState;
|
||||||
|
if(self.last_state == JSON.stringify(self.current_state))
|
||||||
|
return;
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: url_for('settings.save_tree_state'),
|
||||||
|
type: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify(self.current_state),
|
||||||
|
})
|
||||||
|
.done(function() {
|
||||||
|
self.last_state = JSON.stringify(self.current_state);
|
||||||
|
})
|
||||||
|
.fail(function(jqx) {
|
||||||
|
var msg = jqx.responseText;
|
||||||
|
/* Error from the server */
|
||||||
|
if (jqx.status == 417 || jqx.status == 410 || jqx.status == 500) {
|
||||||
|
try {
|
||||||
|
var data = JSON.parse(jqx.responseText);
|
||||||
|
msg = data.errormsg;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e.stack || e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.warn(
|
||||||
|
gettext('Error saving the tree state."'), msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
fetch_state: function() {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
$.ajax({
|
||||||
|
url: url_for('settings.get_tree_state'),
|
||||||
|
type: 'GET',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: 'application/json',
|
||||||
|
})
|
||||||
|
.done(function(res) {
|
||||||
|
self.stored_state = res;
|
||||||
|
})
|
||||||
|
.fail(function(jqx) {
|
||||||
|
var msg = jqx.responseText;
|
||||||
|
/* Error from the server */
|
||||||
|
if (jqx.status == 417 || jqx.status == 410 || jqx.status == 500) {
|
||||||
|
try {
|
||||||
|
var data = JSON.parse(jqx.responseText);
|
||||||
|
msg = data.errormsg;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e.stack || e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.warn(
|
||||||
|
gettext('Error fetching the tree state."'), msg);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
update_cache: function(item) {
|
||||||
|
|
||||||
|
let data = item && pgBrowser.tree.itemData(item),
|
||||||
|
node = data && pgBrowser.Nodes[data._type],
|
||||||
|
treeHierarchy = node.getTreeNodeHierarchy(item),
|
||||||
|
topParent = undefined,
|
||||||
|
pathIDs = pgBrowser.tree.pathId(item),
|
||||||
|
oldPath = pathIDs.join(),
|
||||||
|
path = [],
|
||||||
|
tmpIndex = -1;
|
||||||
|
|
||||||
|
// If no parent or the server not in tree hierarchy then return
|
||||||
|
if (!pgBrowser.tree.hasParent(item) || !(this.parent in treeHierarchy))
|
||||||
|
return;
|
||||||
|
|
||||||
|
topParent = treeHierarchy[this.parent]['_id'];
|
||||||
|
|
||||||
|
|
||||||
|
if (pgBrowser.tree.isOpen(item)) {
|
||||||
|
// Store paths
|
||||||
|
|
||||||
|
pathIDs.push(data.id);
|
||||||
|
path = pathIDs.join();
|
||||||
|
|
||||||
|
if (!(topParent in this.current_state)) {
|
||||||
|
this.current_state = {};
|
||||||
|
this.current_state[topParent] = {'paths': [], 'selected': {}, 'conn_status': {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// IF the current path is already saved then return
|
||||||
|
let index = _.find(this.current_state[topParent]['paths'], function(tData) {
|
||||||
|
return (tData.search(path) !== -1);
|
||||||
|
});
|
||||||
|
if(!_.isUndefined(index))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Add / Update the current item into the tree path
|
||||||
|
if (!_.isUndefined(this.current_state[topParent]['paths'])) {
|
||||||
|
tmpIndex = this.current_state[topParent]['paths'].indexOf(oldPath);
|
||||||
|
} else {
|
||||||
|
this.current_state[topParent]['paths'] = [];
|
||||||
|
}
|
||||||
|
if (tmpIndex !== -1) {
|
||||||
|
this.current_state[topParent]['paths'][tmpIndex] = path;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.current_state[topParent]['paths'].push(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store current selected item and database connection status
|
||||||
|
this.update_database_conn_status(treeHierarchy);
|
||||||
|
this.update_current_selected_item(treeHierarchy);
|
||||||
|
|
||||||
|
},
|
||||||
|
remove_from_cache: function(item) {
|
||||||
|
let self= this,
|
||||||
|
treeData = self.stored_state || {},
|
||||||
|
data = item && pgBrowser.tree.itemData(item),
|
||||||
|
node = data && pgBrowser.Nodes[data._type],
|
||||||
|
treeHierarchy = node && node.getTreeNodeHierarchy(item);
|
||||||
|
|
||||||
|
if (!pgBrowser.tree.hasParent(item) || !(self.parent in treeHierarchy))
|
||||||
|
return;
|
||||||
|
|
||||||
|
let topParent = treeHierarchy && treeHierarchy[self.parent]['_id'],
|
||||||
|
origParent = treeHierarchy && treeHierarchy[self.orig_parent]['id'];
|
||||||
|
|
||||||
|
this.update_database_conn_status(treeHierarchy);
|
||||||
|
|
||||||
|
if (data._type == self.parent || data._type == 'database') {
|
||||||
|
if (topParent in treeData && 'paths' in treeData[topParent]) {
|
||||||
|
treeData[topParent]['paths'] = self.current_state[topParent]['paths'];
|
||||||
|
self.save_state();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pgBrowser.tree.isClosed(item)) {
|
||||||
|
let tmpTreeData = self.current_state[topParent]['paths'];
|
||||||
|
if (!_.isUndefined(tmpTreeData) && !_.isUndefined(tmpTreeData.length)) {
|
||||||
|
let tcnt = 0,
|
||||||
|
tmpItemDataStr = undefined;
|
||||||
|
_.each(tmpTreeData, function(tData) {
|
||||||
|
if (_.isUndefined(tData))
|
||||||
|
return;
|
||||||
|
|
||||||
|
let tmpItemData = tData.split(',');
|
||||||
|
|
||||||
|
if (tmpItemData.indexOf(data.id) !== -1 ) {
|
||||||
|
let index = tmpItemData.indexOf(data.id);
|
||||||
|
tmpItemData.splice(index);
|
||||||
|
tmpItemDataStr = tmpItemData.join();
|
||||||
|
|
||||||
|
if (tmpItemDataStr == origParent)
|
||||||
|
self.current_state[topParent]['paths'].splice(tData, 1);
|
||||||
|
else
|
||||||
|
self.current_state[topParent]['paths'][tcnt] = tmpItemDataStr;
|
||||||
|
}
|
||||||
|
tcnt ++;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (tmpItemDataStr !== undefined) {
|
||||||
|
let tmpIndex = _.find(tmpTreeData, function(tData) {
|
||||||
|
return (tData.search(tmpItemDataStr) !== -1);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(tmpIndex && tmpIndex !== tmpTreeData.indexOf(tmpItemDataStr))
|
||||||
|
self.current_state[topParent]['paths'].splice(tmpIndex, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expand_from_previous_state: function(item) {
|
||||||
|
let self = this,
|
||||||
|
treeData = self.stored_state || {},
|
||||||
|
data = item && pgBrowser.tree.itemData(item),
|
||||||
|
node = data && pgBrowser.Nodes[data._type],
|
||||||
|
treeHierarchy = node && node.getTreeNodeHierarchy(item);
|
||||||
|
|
||||||
|
if (!pgBrowser.tree.hasParent(item) || !(self.parent in treeHierarchy))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If the server node is open then only we should populate the tree
|
||||||
|
if (data['_type'] == self.parent && (pgBrowser.tree.isOpen(item) === false))
|
||||||
|
return;
|
||||||
|
|
||||||
|
let tmpTreeData = treeData[treeHierarchy[self.parent]['_id']];
|
||||||
|
|
||||||
|
|
||||||
|
// If the server node is open then only we should populate the tree
|
||||||
|
if (data['_type'] == 'database' && tmpTreeData && 'conn_status' in tmpTreeData &&
|
||||||
|
tmpTreeData['conn_status'][data['id']] === 0 )
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
if (!_.isUndefined(tmpTreeData) && ('paths' in tmpTreeData) && !_.isUndefined(tmpTreeData['paths'].length)) {
|
||||||
|
_.each(tmpTreeData['paths'], function(tData) {
|
||||||
|
if (_.isUndefined(tData))
|
||||||
|
return;
|
||||||
|
|
||||||
|
let tmpItemData = tData.split(',');
|
||||||
|
|
||||||
|
// If the item is in the lastTreeState then open it
|
||||||
|
if (tmpItemData.indexOf(data.id) !== -1 ) {
|
||||||
|
let index = tmpItemData.indexOf(data.id);
|
||||||
|
pgBrowser.tree.toggle(item);
|
||||||
|
|
||||||
|
if (index == (tmpItemData.length - 1 )) {
|
||||||
|
let tIndex = treeData[treeHierarchy[self.parent]['_id']]['paths'].indexOf(tData);
|
||||||
|
treeData[treeHierarchy[self.parent]['_id']]['paths'].splice(tIndex, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select the previously selected item
|
||||||
|
this.select_tree_item(item);
|
||||||
|
|
||||||
|
},
|
||||||
|
update_database_conn_status: function(treeHierarchy) {
|
||||||
|
if ('database' in treeHierarchy) {
|
||||||
|
let databaseItem = treeHierarchy['database']['id'],
|
||||||
|
topParent = treeHierarchy && treeHierarchy[this.parent]['_id'];
|
||||||
|
|
||||||
|
if (treeHierarchy['database'].connected) {
|
||||||
|
this.current_state[topParent]['conn_status'][databaseItem] = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.current_state[topParent]['conn_status'][databaseItem] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update_current_selected_item(treeHierarchy) {
|
||||||
|
if ('database' in treeHierarchy) {
|
||||||
|
let databaseItem = treeHierarchy['database']['id'],
|
||||||
|
topParent = treeHierarchy && treeHierarchy[this.parent]['_id'],
|
||||||
|
selectedItem = pgBrowser.tree.itemData(pgBrowser.tree.selected());
|
||||||
|
|
||||||
|
selectedItem = selectedItem ? selectedItem.id : undefined;
|
||||||
|
|
||||||
|
if (!_.isUndefined(selectedItem)) {
|
||||||
|
this.current_state[topParent]['selected'][treeHierarchy[this.parent]['id']] = selectedItem;
|
||||||
|
this.current_state[topParent]['selected'][databaseItem] = selectedItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
select_tree_item(item) {
|
||||||
|
let treeData = this.stored_state || {},
|
||||||
|
data = item && pgBrowser.tree.itemData(item),
|
||||||
|
node = data && pgBrowser.Nodes[data._type],
|
||||||
|
treeHierarchy = node && node.getTreeNodeHierarchy(item),
|
||||||
|
tmpTreeData = treeData[treeHierarchy[this.parent]['_id']];
|
||||||
|
|
||||||
|
|
||||||
|
if ('database' in treeHierarchy) {
|
||||||
|
let databaseItem = treeHierarchy['database']['id'];
|
||||||
|
|
||||||
|
if (tmpTreeData && 'selected' in tmpTreeData && databaseItem in tmpTreeData['selected']) {
|
||||||
|
if (tmpTreeData['selected'][databaseItem] == data.id) {
|
||||||
|
pgBrowser.tree.select(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export {pgBrowser, browserTreeState};
|
242
web/regression/javascript/tree/pgadmin_tree_state_save_spec.js
Normal file
242
web/regression/javascript/tree/pgadmin_tree_state_save_spec.js
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// pgAdmin 4 - PostgreSQL Tools
|
||||||
|
//
|
||||||
|
// Copyright (C) 2013 - 2018, The pgAdmin Development Team
|
||||||
|
// This software is released under the PostgreSQL Licence
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
import {pgBrowser, browserTreeState} from '../../../pgadmin/static/js/tree/pgadmin_tree_save_state';
|
||||||
|
|
||||||
|
|
||||||
|
describe('#browserTreeState', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
pgBrowser.Nodes = {
|
||||||
|
server_group: {
|
||||||
|
_type: 'server_group',
|
||||||
|
hasId: true,
|
||||||
|
id: 'server_group/1',
|
||||||
|
_id: 1,
|
||||||
|
getTreeNodeHierarchy: function() { return {
|
||||||
|
server_group: {
|
||||||
|
_type: 'server_group',
|
||||||
|
hasId: true,
|
||||||
|
id: 'server_group/1',
|
||||||
|
_id: 1,
|
||||||
|
},
|
||||||
|
};},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
_type: 'server',
|
||||||
|
hasId: true,
|
||||||
|
id: 'server/1',
|
||||||
|
_id: 1,
|
||||||
|
getTreeNodeHierarchy: function() { return {
|
||||||
|
server_group: {
|
||||||
|
_type: 'server_group',
|
||||||
|
hasId: true,
|
||||||
|
id: 'server_group/1',
|
||||||
|
_id: 1,
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
_type: 'server',
|
||||||
|
hasId: true,
|
||||||
|
id: 'server/1',
|
||||||
|
_id: 1,
|
||||||
|
},
|
||||||
|
};},
|
||||||
|
},
|
||||||
|
coll_database: {
|
||||||
|
_type: 'coll_database',
|
||||||
|
hasId: false,
|
||||||
|
id: 'coll_database/1',
|
||||||
|
_id: 1,
|
||||||
|
getTreeNodeHierarchy: function() { return {
|
||||||
|
server_group: {
|
||||||
|
_type: 'server_group',
|
||||||
|
hasId: true,
|
||||||
|
id: 'server_group/1',
|
||||||
|
_id: 1,
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
_type: 'server',
|
||||||
|
hasId: true,
|
||||||
|
id: 'server/1',
|
||||||
|
_id: 1,
|
||||||
|
},
|
||||||
|
coll_database: {
|
||||||
|
_type: 'coll_database',
|
||||||
|
hasId: true,
|
||||||
|
id: 'coll_database/1',
|
||||||
|
_id: 1,
|
||||||
|
},
|
||||||
|
};},
|
||||||
|
},
|
||||||
|
database: {
|
||||||
|
_type: 'database',
|
||||||
|
hasId: true,
|
||||||
|
id: 'database/10',
|
||||||
|
_id: 10,
|
||||||
|
getTreeNodeHierarchy: function() { return {
|
||||||
|
server_group: {
|
||||||
|
_type: 'server_group',
|
||||||
|
hasId: true,
|
||||||
|
id: 'server_group/1',
|
||||||
|
_id: 1,
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
_type: 'server',
|
||||||
|
hasId: true,
|
||||||
|
id: 'server/1',
|
||||||
|
_id: 1,
|
||||||
|
},
|
||||||
|
coll_database: {
|
||||||
|
_type: 'coll_database',
|
||||||
|
hasId: true,
|
||||||
|
id: 'coll_database/1',
|
||||||
|
_id: 1,
|
||||||
|
},
|
||||||
|
database: {
|
||||||
|
_type: 'database',
|
||||||
|
hasId: true,
|
||||||
|
id: 'database/10',
|
||||||
|
_id: 10,
|
||||||
|
},
|
||||||
|
};},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
pgBrowser.tree = jasmine.createSpyObj('tree', ['itemData', 'pathId', 'hasParent', 'isOpen', 'isClosed', 'selected']);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('When node is opened tree state is getting updated', () => {
|
||||||
|
|
||||||
|
describe('When tree node server group is opened', () => {
|
||||||
|
let item = {
|
||||||
|
_type: 'server_group',
|
||||||
|
hasId: true,
|
||||||
|
id: 'server_group/1',
|
||||||
|
_id: 1,
|
||||||
|
};
|
||||||
|
beforeEach(() => {
|
||||||
|
pgBrowser.tree.itemData.and.returnValue(item);
|
||||||
|
pgBrowser.tree.pathId.and.returnValue([]);
|
||||||
|
pgBrowser.tree.hasParent.and.returnValue(false);
|
||||||
|
pgBrowser.tree.isOpen.and.returnValue(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('The tree current state will be empty', () => {
|
||||||
|
browserTreeState.update_cache(item);
|
||||||
|
expect(browserTreeState.current_state, {});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('When server node is opened', () => {
|
||||||
|
let item = {
|
||||||
|
_type: 'server',
|
||||||
|
hasId: true,
|
||||||
|
id: 'server/1',
|
||||||
|
_id: 1,
|
||||||
|
};
|
||||||
|
beforeEach(() => {
|
||||||
|
pgBrowser.tree.itemData.and.returnValue(item);
|
||||||
|
pgBrowser.tree.pathId.and.returnValue(['server_group/1']);
|
||||||
|
pgBrowser.tree.hasParent.and.returnValue(true);
|
||||||
|
pgBrowser.tree.isOpen.and.returnValue(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('The tree current state will have server', () => {
|
||||||
|
browserTreeState.update_cache(item);
|
||||||
|
expect(browserTreeState.current_state, {1: {'paths': ['server_group/1,server/1']}});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('When coll_database node is opened', () => {
|
||||||
|
let item = {
|
||||||
|
_type: 'coll_database',
|
||||||
|
hasId: true,
|
||||||
|
id: 'coll_database/1',
|
||||||
|
_id: 1,
|
||||||
|
};
|
||||||
|
beforeEach(() => {
|
||||||
|
pgBrowser.tree.itemData.and.returnValue(item);
|
||||||
|
pgBrowser.tree.pathId.and.returnValue(['server_group/1', 'server/1']);
|
||||||
|
pgBrowser.tree.hasParent.and.returnValue(true);
|
||||||
|
pgBrowser.tree.isOpen.and.returnValue(true);
|
||||||
|
pgBrowser.tree.selected.and.returnValue(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('The tree current state will have coll_database', () => {
|
||||||
|
browserTreeState.update_cache(item);
|
||||||
|
expect(browserTreeState.current_state, {1: {'paths': ['server_group/1,server/1,coll_database/1']}});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('When database node is opened', () => {
|
||||||
|
let item = {
|
||||||
|
_type: 'database',
|
||||||
|
hasId: true,
|
||||||
|
id: 'database/10',
|
||||||
|
_id: 10,
|
||||||
|
};
|
||||||
|
beforeEach(() => {
|
||||||
|
pgBrowser.tree.itemData.and.returnValue(item);
|
||||||
|
pgBrowser.tree.pathId.and.returnValue(['server_group/1', 'server/1', 'coll_database/1']);
|
||||||
|
pgBrowser.tree.hasParent.and.returnValue(true);
|
||||||
|
pgBrowser.tree.isOpen.and.returnValue(true);
|
||||||
|
pgBrowser.tree.selected.and.returnValue(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('The tree current state will have coll_database', () => {
|
||||||
|
browserTreeState.update_cache(item);
|
||||||
|
expect(browserTreeState.current_state, {1: {'paths': ['server_group/1,server/1,coll_database/1','database/10']}});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('When node is closed, node should get removed from the tree state cache', () => {
|
||||||
|
describe('When coll_database node is closed, both database and coll_database should be removed', () => {
|
||||||
|
let item = {
|
||||||
|
_type: 'coll_database',
|
||||||
|
hasId: true,
|
||||||
|
id: 'coll_database/1',
|
||||||
|
_id: 1,
|
||||||
|
};
|
||||||
|
beforeEach(() => {
|
||||||
|
pgBrowser.tree.itemData.and.returnValue(item);
|
||||||
|
pgBrowser.tree.pathId.and.returnValue(['server_group/1', 'server/1']);
|
||||||
|
pgBrowser.tree.hasParent.and.returnValue(true);
|
||||||
|
pgBrowser.tree.isOpen.and.returnValue(true);
|
||||||
|
pgBrowser.tree.isClosed.and.returnValue(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('The tree current state will remove coll_database and database', () => {
|
||||||
|
browserTreeState.remove_from_cache(item);
|
||||||
|
expect(browserTreeState.current_state, {1: {'paths': ['server_group/1,server/1']}});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('When server node is closed, both server and server_group should be removed', () => {
|
||||||
|
let item = {
|
||||||
|
_type: 'server',
|
||||||
|
hasId: true,
|
||||||
|
id: 'server/1',
|
||||||
|
_id: 1,
|
||||||
|
};
|
||||||
|
beforeEach(() => {
|
||||||
|
pgBrowser.tree.itemData.and.returnValue(item);
|
||||||
|
pgBrowser.tree.pathId.and.returnValue(['server_group/1']);
|
||||||
|
pgBrowser.tree.hasParent.and.returnValue(true);
|
||||||
|
pgBrowser.tree.isOpen.and.returnValue(true);
|
||||||
|
pgBrowser.tree.isClosed.and.returnValue(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('The tree current state will remove server_group and server', () => {
|
||||||
|
browserTreeState.remove_from_cache(item);
|
||||||
|
expect(browserTreeState.current_state, {1: []});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user