Hi,

Please find the attached updated patch.

Feature Details:
- The current tree state as well as the previous will be stored in the
sqlite database.
- The time interval to store the tree state is configurable via preferences
and the default is 30 secs.
   -1 can be used to stop the tree saving functionality,
- Jasmine test cases are included.

Thanks,
Khushboo











On Thu, Aug 30, 2018 at 3:34 PM Dave Page <dp...@pgadmin.org> wrote:

> Hi
>
> On Tue, Aug 28, 2018 at 1:40 PM, Khushboo Vashi <
> khushboo.va...@enterprisedb.com> wrote:
>
>>
>>
>> On Mon, Aug 27, 2018 at 1:19 PM, Akshay Joshi <
>> akshay.jo...@enterprisedb.com> wrote:
>>
>>> Hi Khushboo
>>>
>>> Patch looks good to me, following are my review comments:
>>>
>>>    - Logic doesn't work when we disconnect and re-connect to the
>>>    database server. We have duplicated RM #1335.
>>>
>>> I have saved the current state of the tree. so If I have disconnected
>> the server, so that closed state will be considered for that server.
>> If another server(s) is still connected and opened at some node, and then
>> you reload the browser, on connecting/expanding the server it will populate
>> the server nodes.
>>
>> @Dave,
>>
>> Do I need to change this behaviour?
>>
>
> Per our Skype call, let's store and restore both the current tree state
> and the previous state of disconnected servers (as pgAdmin 3 did). So:
>
> - On browser open, the treeview is restored to the previous state, with
> databases reconnected as required.
>
> - On re-connect of a database, the sub-treeview for that database is
> restored to the state it was in prior to disconnection.
>
> Thanks!
>
>>
>>>    -
>>>    - Instead of -1 we should use 0 to stop the tree saving
>>>    functionality. Send request at 0 second doesn't make any sense.
>>>    - What if pgAdmin4 opens in two different browsers or two different
>>>    tabs with different tree hierarchy in that case it will override the 
>>> state
>>>    of each other. (This could be an enhancement request).
>>>
>>>
>>> On Mon, Aug 27, 2018 at 11:09 AM, Aditya Toshniwal <
>>> aditya.toshni...@enterprisedb.com> wrote:
>>>
>>>> Hi Khushboo,
>>>>
>>>> Cheers, the patch looks good to me. I have few suggestions:
>>>> 1) Expand the tree -> Go to server properties and rename the server ->
>>>> the tree closes. It should expand to last state. Same applies to other
>>>> nodes which can be renamed.
>>>> 2) It would be nice if we can also save the last selected object. The
>>>> tree expands but the last selected object is not selected.
>>>> 3) In /web/pgadmin/static/js/tree/pgadmin_tree_save_state.js
>>>>  import pgAdmin from '../../../static/js/pgadmin'; -- please use alias
>>>> here rather than relative path.
>>>> 4) I would suggest to add 1 example/format JSON of tree state in
>>>> comments in the file so that it will be easier to relate the code with.
>>>>
>>>>
>>>> On Fri, Aug 24, 2018 at 10:08 AM Khushboo Vashi <
>>>> khushboo.va...@enterprisedb.com> wrote:
>>>>
>>>>> Hi,
>>>>>
>>>>> Please find the attached patch for RM #1253 - Store and reload current
>>>>> location in treeview
>>>>>
>>>>> Feature Details:
>>>>> - The current tree state will be stored in the sqlite database.
>>>>> - The time interval to store the tree state is configurable via
>>>>> preferences and the default is 30 secs.
>>>>>    -1 can be used to stop the tree saving functionality,
>>>>> - On window unload the current tree state will be saved.
>>>>> - On Reload, while opening the server, the stored path will be
>>>>> populated.
>>>>> - On closing the node the saved will be updated accordingly.
>>>>> - Jasmine test cases are included.
>>>>>
>>>>>
>>>>> Thanks,
>>>>> Khushboo
>>>>>
>>>>
>>>>
>>>> --
>>>> Thanks and Regards,
>>>> Aditya Toshniwal
>>>> Software Engineer | EnterpriseDB Software Solutions | Pune
>>>> "Don't Complain about Heat, Plant a tree"
>>>>
>>>
>>>
>>>
>>> --
>>> *Akshay Joshi*
>>>
>>> *Sr. Software Architect *
>>>
>>>
>>>
>>> *Phone: +91 20-3058-9517Mobile: +91 976-788-8246*
>>>
>>
>>
>
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>
diff --git a/web/pgadmin/browser/register_browser_preferences.py b/web/pgadmin/browser/register_browser_preferences.py
index 2eed500..df30346 100644
--- a/web/pgadmin/browser/register_browser_preferences.py
+++ b/web/pgadmin/browser/register_browser_preferences.py
@@ -28,6 +28,16 @@ def register_browser_preferences(self):
         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(
         'properties', 'table_row_count_threshold',
         gettext("Count rows if estimated less than"), 'integer', 2000,
diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py
index 1c26e0c..6646066 100644
--- a/web/pgadmin/browser/server_groups/servers/__init__.py
+++ b/web/pgadmin/browser/server_groups/servers/__init__.py
@@ -585,7 +585,8 @@ class ServerNode(PGChildNodeView):
                 server_icon_and_background(connected, manager, server),
                 True,
                 self.node_type,
-                connected=False,
+                connected=connected,
+                user=manager.user_info if connected else None,
                 server_type='pg'  # default server type
             )
         )
diff --git a/web/pgadmin/browser/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js
index ee02504..f1fd3b2 100644
--- a/web/pgadmin/browser/static/js/browser.js
+++ b/web/pgadmin/browser/static/js/browser.js
@@ -9,7 +9,7 @@ define('pgadmin.browser', [
   'pgadmin.browser.error', 'pgadmin.browser.frame',
   'pgadmin.browser.node', 'pgadmin.browser.collection',
   'sources/codemirror/addon/fold/pgadmin-sqlfoldcode',
-  'pgadmin.browser.keyboard',
+  'pgadmin.browser.keyboard', 'sources/tree/pgadmin_tree_save_state',
 ], function(
   tree,
   gettext, url_for, require, $, _, S, Bootstrap, pgAdmin, Alertify,
@@ -513,9 +513,11 @@ define('pgadmin.browser', [
         .done(function() {})
         .fail(function() {});
       }, 300000);
+
       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:refresh', obj.onRefreshTreeNode, obj);
+
     },
 
     add_menu_category: function(
@@ -1942,19 +1944,10 @@ define('pgadmin.browser', [
     pgAdmin.Browser.editor_shortcut_keys.Tab = 'insertSoftTab';
   }
 
-  window.onbeforeunload = function() {
-    var e = window.event,
-      msg = S(gettext('Are you sure you wish to close the %s browser?')).sprintf(pgBrowser.utils.app_name).value();
-
-    // For IE and Firefox prior to version 4
-    if (e) {
-      e.returnValue = msg;
-    }
-
-    // For Safari
-    return msg;
-  };
-
+  $(window).on('beforeunload', function() {
+    if (pgBrowser.get_preference('browser', 'browser_tree_state_save_interval').value !== -1)
+      pgAdmin.Browser.browserTreeState.save_state();
+  });
 
   return pgAdmin.Browser;
 });
diff --git a/web/pgadmin/browser/static/js/node.js b/web/pgadmin/browser/static/js/node.js
index 3394696..f27db63 100644
--- a/web/pgadmin/browser/static/js/node.js
+++ b/web/pgadmin/browser/static/js/node.js
@@ -1,11 +1,11 @@
 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',
   'pgadmin.browser.menu', 'backbone', 'pgadmin.alertifyjs', 'pgadmin.browser.datamodel',
   'backform', 'sources/browser/generate_url', 'sources/utils', 'pgadmin.browser.utils',
   'pgadmin.backform',
 ], function(
-  pgadminTreeNode,
+  pgadminTreeNode, url_for,
   gettext, $, _, S, pgAdmin, Menu, Backbone, Alertify, pgBrowser, Backform, generateUrl, commonUtils
 ) {
 
@@ -849,7 +849,6 @@ define('pgadmin.browser.node', [
           }
         }
       },
-
       added: function(item, data, browser) {
         var b = browser || pgBrowser,
           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);
       },
       // Callback called - when a node is selected in browser tree.
@@ -958,6 +959,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
diff --git a/web/pgadmin/browser/static/js/preferences.js b/web/pgadmin/browser/static/js/preferences.js
index 5ca9e3c..1767d72 100644
--- a/web/pgadmin/browser/static/js/preferences.js
+++ b/web/pgadmin/browser/static/js/preferences.js
@@ -94,6 +94,9 @@ _.extend(pgBrowser, {
           modifyAnimation.modifyAlertifyAnimation(self);
         }
 
+        // Initialize Tree saving/reloading
+        pgBrowser.browserTreeState.init();
+
         /* Once the cache is loaded after changing the preferences,
          * notify the modules of the change
          */
diff --git a/web/pgadmin/preferences/static/js/preferences.js b/web/pgadmin/preferences/static/js/preferences.js
index eeaf4bb..840fc0a 100644
--- a/web/pgadmin/preferences/static/js/preferences.js
+++ b/web/pgadmin/preferences/static/js/preferences.js
@@ -2,6 +2,7 @@ define('pgadmin.preferences', [
   'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
   'pgadmin.alertifyjs', 'sources/pgadmin', 'pgadmin.backform',
   'pgadmin.browser', 'sources/modify_animation',
+  'sources/tree/pgadmin_tree_save_state',
 ], function(
   gettext, url_for, $, _, Backbone, Alertify, pgAdmin, Backform, pgBrowser,
   modifyAnimation
diff --git a/web/pgadmin/settings/__init__.py b/web/pgadmin/settings/__init__.py
index 269bfdf..7adf5b4 100644
--- a/web/pgadmin/settings/__init__.py
+++ b/web/pgadmin/settings/__init__.py
@@ -10,13 +10,15 @@
 """Utility functions for storing and retrieving user configuration settings."""
 
 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_login import current_user
 from flask_security import login_required
 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.model import db, Setting
@@ -52,7 +54,8 @@ class SettingsModule(PgAdminModule):
             list: a list of url endpoints exposed to the client.
         """
         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)
+
+
+@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")
diff --git a/web/pgadmin/static/js/tree/pgadmin_tree_save_state.js b/web/pgadmin/static/js/tree/pgadmin_tree_save_state.js
new file mode 100644
index 0000000..f8efda4
--- /dev/null
+++ b/web/pgadmin/static/js/tree/pgadmin_tree_save_state.js
@@ -0,0 +1,247 @@
+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:  [
+  //    server_group/1,/server/1,/coll-database/1,/database/1,
+  //    server_group/1,/server/1,/coll-database/1,/database/2
+  //  ]}
+  // 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 so 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)) {
+      pathIDs.push(data.id);
+      path = pathIDs.join();
+
+      // IF the current path is already saved then return
+      let index = _.find(this.current_state[topParent], 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])) {
+        tmpIndex = this.current_state[topParent].indexOf(oldPath);
+      } else {
+        this.current_state[topParent] = [];
+      }
+      if (tmpIndex !== -1) {
+        this.current_state[topParent][tmpIndex] = path;
+      }
+      else {
+        this.current_state[topParent].push(path);
+      }
+    }
+  },
+  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'];
+
+    if (data._type == self.parent) {
+      treeData[topParent] = self.current_state[topParent];
+      self.save_state();
+      return;
+    }
+
+    if (pgBrowser.tree.isClosed(item)) {
+      let tmpTreeData =  self.current_state[topParent];
+      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].splice(tData, 1);
+            else
+              self.current_state[topParent][tcnt] = tmpItemDataStr;
+          }
+          tcnt ++;
+        });
+
+        if (tmpItemDataStr !== undefined) {
+          let tmpIndex = _.find(tmpTreeData, function(tData) {
+            return (tData.search(tmpItemDataStr) !== -1);
+          });
+
+          if(tmpIndex && tmpIndex !== tmpTreeData.indexOf(tmpItemDataStr))
+            this.current_state[topParent].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 (!_.isUndefined(tmpTreeData) && !_.isUndefined(tmpTreeData.length)) {
+      _.each(tmpTreeData, 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']].indexOf(tData);
+            treeData[treeHierarchy[self.parent]['_id']].splice(tIndex, 1);
+          }
+        }
+      });
+    }
+  },
+
+});
+
+export {pgBrowser, browserTreeState};
diff --git a/web/regression/javascript/tree/pgadmin_tree_state_save_spec.js b/web/regression/javascript/tree/pgadmin_tree_state_save_spec.js
new file mode 100644
index 0000000..769b663
--- /dev/null
+++ b/web/regression/javascript/tree/pgadmin_tree_state_save_spec.js
@@ -0,0 +1,240 @@
+/////////////////////////////////////////////////////////////
+//
+// 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']);
+  });
+
+  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: ['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);
+      });
+
+      it('The tree current state will have coll_database', () => {
+        browserTreeState.update_cache(item);
+        expect(browserTreeState.current_state, {1: ['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);
+      });
+
+      it('The tree current state will have coll_database', () => {
+        browserTreeState.update_cache(item);
+        expect(browserTreeState.current_state, {1: ['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: ['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: []});
+      });
+    });
+  });
+
+});

Reply via email to