public inbox for [email protected]  
help / color / mirror / Atom feed
From: Joao De Almeida Pereira <[email protected]>
To: Khushboo Vashi <[email protected]>
Cc: pgadmin-hackers <[email protected]>
Subject: Re: [pgadmin4][patch] Initial patch to decouple from ACI Tree
Date: Thu, 05 Apr 2018 13:38:31 +0000
Message-ID: <CAE+jjaneGOR_R7QvYnZt=khumRsyhnrzFRhpMG5-Q+rR9guLzQ@mail.gmail.com> (raw)
In-Reply-To: <CAFOhELcrZsRkwV4beOC7UT_HYvi9Rk=HFJ355g8Bj4bqyzMLog@mail.gmail.com>
References: <CAE+jja=Gdd032H7tpoZD2C0m2R7SnTZpHX_oPx2K2zGbaaW9yg@mail.gmail.com>
	<CAFOhELcrZsRkwV4beOC7UT_HYvi9Rk=HFJ355g8Bj4bqyzMLog@mail.gmail.com>

Hi Khushboo,
Attached you can find both patches rebased

Thanks


On Thu, Apr 5, 2018 at 6:31 AM Khushboo Vashi <
[email protected]> wrote:

> Hi Joao,
>
> Can you please rebase the second patch?
>
> Thanks,
> Khushboo
>
>
> On Tue, Apr 3, 2018 at 12:15 AM, Joao De Almeida Pereira <
> [email protected]> wrote:
>
>> Hi Hackers,
>>
>> Attached you can find the patch that will start to decouple pgAdmin from
>> ACITree library.
>> This patch is intended to be merged after 3.0, because we do not want to
>> cause any entropy or delay the release, but we want to start the discussion
>> and show some code.
>>
>> This job that we started is a massive tech debt chore that will take some
>> time to finalize and we would love the help of the community to do it.
>>
>> *Summary of the patch:*
>> 0001 patch:
>>  - Creates a new tree that will allow us to create a separation between
>> the application and ACI Tree
>>  - Creates a Fake Tree (Test double, for reference on the available test
>> doubles: https://martinfowler.com/bliki/TestDouble.html) that can be
>> used to inplace to replace the ACITree and also encapsulate the new tree
>> behavior on our tests
>>  - Adds tests for all the tree functionalities
>>
>> 0002 patch:
>>  - Extracts, refactors, adds tests and remove dependency from ACI Tree on:
>> - getTreeNodeHierarchy
>> - on backup.js: menu_enabled, menu_enabled_server,
>> start_backup_global_server, backup_objects
>> - on datagrid.js: show_data_grid, get_panel_title, show_query_tool
>> - Start using sprintf-js as Underscore.String is deprecating sprintf
>> function
>>
>> This patch represents only 10 calls to ACITree.itemData out of 176 that
>> are spread around our code
>>
>>
>> *In Depth look on the process behind the patch:*
>>
>> We started writing this patch with the idea that we need to decouple
>> pgAdmin4 from ACITree, because ACITree is no longer supported, the
>> documentation is non existent and ACITree is no longer being actively
>> developed.
>>
>> Our process:
>> 1. We "randomly" selected a function that is part of the ACITree. From
>> this point we decided to replace that function with our own version. The
>> function that we choose was "itemData".
>> The function gives us all the "data" that a specific node of the tree
>> find.
>> Given in order to replace the tree we would need to have a function that
>> would give us the same information. We had 2 options:
>>   a) Create a tree with a function called itemData
>> Pros:
>>  - At first view this was the simpler solution
>>  - Would keep the status quo
>> Cons:
>>  - Not a OOP approach
>>  - Not very flexible
>>   b) Create a tree that would return a node given an ID and then the node
>> would be responsible for giving it's data.
>> Pros:
>>  - OOP Approach
>>  - More flexible and we do not need to bring the tree around, just a node
>> Cons:
>>  - Break the current status quo
>>
>> Given these 2 options we decided to go for a more OOP approach creating a
>> Tree and a TreeNode classes, that in the future will be renamed to
>> ACITreeWrapper and TreeNode.
>>
>> 2. After we decided on the starting point we searched for occurrences of
>> the function "itemData" and we found out that there were 303 occurrences of
>> "itemData" in the code and roughly 176 calls to the function itself (some
>> of the hits were variable names).
>>
>> 3. We selected the first file on the search and found the function that
>> was responsible for calling the itemData function.
>>
>> 4. Extracted the function to a separate file
>>
>> 5. Wrap this function with tests
>>
>> 6. Refactor the function to ES6, give more declarative names to variables
>> and break the functions into smaller chunks
>>
>> 7. When all the tests were passing we replaced ACITree with our Tree
>>
>> 8. We ensured that all tests were passing
>>
>> 9. Remove function from the original file and use the new function
>>
>> 10. Ensure everything still works
>>
>> 11. Find the next function and execute from step 4 until all the
>> functions are replaced, refactored and tested.
>>
>> As you can see by the process this is a pretty huge undertake, because of
>> the number of calls to the function. This is just the first step on the
>> direction of completely isolating the ACITree so that we can solve the
>> problem with a large number of elements on the tree.
>>
>> *What is on our radar that we need to address:*
>>  - Finish the complete decoupling of the ACITree
>>  - Performance of the current tree implementation
>>  - Tweak the naming of the Tree class to explicitly tell us this is to
>> use only with ACITree.
>>
>>
>> Thanks
>> Joao
>>
>>
>


Attachments:

  [text/x-patch] 0001-Tree-of-information-on-the-current-status-of-the-app_v1.patch (14.1K, 3-0001-Tree-of-information-on-the-current-status-of-the-app_v1.patch)
  download | inline diff:
diff --git a/web/pgadmin/static/js/tree/tree.js b/web/pgadmin/static/js/tree/tree.js
new file mode 100644
index 00000000..ed67aa85
--- /dev/null
+++ b/web/pgadmin/static/js/tree/tree.js
@@ -0,0 +1,134 @@
+//////////////////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////////////////
+export class TreeNode {
+  constructor(id, data, parent) {
+    this.id = id;
+    this.data = data;
+    this.parentNode = parent;
+    this.path = this.id;
+    if (parent !== null && parent !== undefined && parent.path !== undefined) {
+      this.path = parent.path + '.' + id;
+    }
+    this.children = [];
+  }
+
+  hasParent() {
+    return this.parentNode !== null && this.parentNode !== undefined;
+  }
+
+  parent() {
+    return this.parentNode;
+  }
+
+  getData() {
+    if (this.data === undefined) {
+      return undefined;
+    } else if (this.data === null) {
+      return null;
+    }
+    return Object.assign({}, this.data);
+  }
+}
+
+export class Tree {
+  constructor() {
+    this.rootNode = new TreeNode(undefined, {});
+    this.aciTreeApi = undefined;
+  }
+
+  addNewNode(id, data, path) {
+    const parent = this.findNode(path);
+    return this.createOrUpdateNode(id, data, parent);
+  }
+
+  findNode(path) {
+    if (path.length === 0) {
+      return this.rootNode;
+    }
+    return findInTree(this.rootNode, path.join('.'));
+  }
+
+  findNodeByDomElement(domElement) {
+    const path = this.translateTreeNodeIdFromACITree(domElement);
+    if(!path || !path[0]) {
+      return undefined;
+    }
+
+    return this.findNode(path);
+  }
+
+  selected() {
+    return this.aciTreeApi.selected();
+  }
+
+  register($treeJQuery) {
+    $treeJQuery.on('acitree', function (event, api, item, eventName) {
+      if (api.isItem(item)) {
+        if (eventName === 'added') {
+          const id = api.getId(item);
+          const data = api.itemData(item);
+          const parentId = this.translateTreeNodeIdFromACITree(api.parent(item));
+          this.addNewNode(id, data, parentId);
+        }
+      }
+    }.bind(this));
+    this.aciTreeApi = $treeJQuery.aciTree('api');
+  }
+
+  createOrUpdateNode(id, data, parent) {
+    const oldNode = this.findNode([parent.path, id]);
+    if (oldNode !== null) {
+      oldNode.data = Object.assign({}, data);
+      return oldNode;
+    }
+
+    const node = new TreeNode(id, data, parent);
+    if (parent === this.rootNode) {
+      node.parentNode = null;
+    }
+    parent.children.push(node);
+    return node;
+  }
+
+  translateTreeNodeIdFromACITree(aciTreeNode) {
+    let currentTreeNode = aciTreeNode;
+    let path = [];
+    while (currentTreeNode !== null && currentTreeNode !== undefined && currentTreeNode.length > 0) {
+      path.unshift(this.aciTreeApi.getId(currentTreeNode));
+      if (this.aciTreeApi.hasParent(currentTreeNode)) {
+        currentTreeNode = this.aciTreeApi.parent(currentTreeNode);
+      } else {
+        break;
+      }
+    }
+    return path;
+  }
+}
+
+export let tree = new Tree();
+
+function findInTree(rootNode, path) {
+  if (path === null) {
+    return rootNode;
+  }
+  return (function findInNode(currentNode) {
+    for (let i = 0, length = currentNode.children.length; i < length; i++) {
+      const calculatedNode = findInNode(currentNode.children[i]);
+      if (calculatedNode !== null) {
+        return calculatedNode;
+      }
+    }
+
+    if (currentNode.path === path) {
+      return currentNode;
+    } else {
+      return null;
+    }
+  })(rootNode);
+}
diff --git a/web/regression/javascript/tree/tree_fake.js b/web/regression/javascript/tree/tree_fake.js
new file mode 100644
index 00000000..a0983c67
--- /dev/null
+++ b/web/regression/javascript/tree/tree_fake.js
@@ -0,0 +1,55 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import {Tree} from "../../../pgadmin/static/js/tree/tree";
+
+export class TreeFake extends Tree {
+  constructor() {
+    super();
+    this.aciTreeToOurTreeTranslator = {};
+  }
+
+  addNewNode(id, data, path) {
+    this.aciTreeToOurTreeTranslator[id] = path.concat(id);
+    return super.addNewNode(id, data, path);
+  }
+
+  hasParent(aciTreeNode) {
+    return this.translateTreeNodeIdFromACITree(aciTreeNode).length > 1;
+  }
+
+  parent(aciTreeNode) {
+    if (this.hasParent(aciTreeNode)) {
+      let path = this.translateTreeNodeIdFromACITree(aciTreeNode);
+      return [{id: this.findNode(path).parent().id}];
+    }
+
+    return null;
+  }
+
+  translateTreeNodeIdFromACITree(aciTreeNode) {
+    return this.aciTreeToOurTreeTranslator[aciTreeNode[0].id];
+  }
+
+  itemData(aciTreeNode) {
+    let path = this.translateTreeNodeIdFromACITree(aciTreeNode);
+    if(path === undefined || path === null) {
+      return undefined;
+    }
+    return this.findNode(path).getData();
+  }
+
+  selected() {
+    return this.selectedNode;
+  }
+
+  setSelectedNode(selectedNode) {
+    this.selectedNode = selectedNode;
+  }
+}
diff --git a/web/regression/javascript/tree/tree_spec.js b/web/regression/javascript/tree/tree_spec.js
new file mode 100644
index 00000000..e8930d84
--- /dev/null
+++ b/web/regression/javascript/tree/tree_spec.js
@@ -0,0 +1,284 @@
+//////////////////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////////////////
+
+import {Tree, TreeNode} from "../../../pgadmin/static/js/tree/tree";
+import {TreeFake} from "./tree_fake";
+
+const context = describe;
+
+const treeTests = (treeClass, setDefaultCallBack) => {
+  let tree;
+  beforeEach(() => {
+    tree = new treeClass();
+  });
+
+  describe('#addNewNode', () => {
+    describe('when add a new root element', () => {
+      beforeEach(() => {
+        tree.addNewNode('some new node', {data: 'interesting'}, []);
+      });
+
+      it('can be retrieved', () => {
+        const node = tree.findNode(['some new node']);
+        expect(node.data).toEqual({data: 'interesting'});
+      });
+
+      it('return false for #hasParent()', () => {
+        const node = tree.findNode(['some new node']);
+        expect(node.hasParent()).toBe(false);
+      });
+
+      it('return null for #parent()', () => {
+        const node = tree.findNode(['some new node']);
+        expect(node.parent()).toBeNull();
+      });
+    });
+
+    describe('when add a new element as a child', () => {
+      let parentNode;
+      beforeEach(() => {
+        parentNode = tree.addNewNode('parent node', {data: 'parent data'}, []);
+        tree.addNewNode('some new node', {data: 'interesting'}, ['parent' +
+        ' node']);
+      });
+
+      it('can be retrieved', () => {
+        const node = tree.findNode(['parent node', 'some new node']);
+        expect(node.data).toEqual({data: 'interesting'});
+      });
+
+      it('return true for #hasParent()', () => {
+        const node = tree.findNode(['parent node', 'some new node']);
+        expect(node.hasParent()).toBe(true);
+      });
+
+      it('return "parent node" object for #parent()', () => {
+        const node = tree.findNode(['parent node', 'some new node']);
+        expect(node.parent()).toEqual(parentNode);
+      });
+    });
+
+    describe('when add an element that already exists under a parent', () => {
+      let parentNode;
+      beforeEach(() => {
+        parentNode = tree.addNewNode('parent node', {data: 'parent data'}, []);
+        tree.addNewNode('some new node', {data: 'interesting'}, ['parent' +
+        ' node']);
+      });
+
+      it('does not add a new child', () => {
+        tree.addNewNode('some new node', {data: 'interesting 1'}, ['parent' +
+        ' node']);
+        const parentNode = tree.findNode(['parent node']);
+        expect(parentNode.children.length).toBe(1);
+      });
+
+      it('updates the existing node data', () => {
+        tree.addNewNode('some new node', {data: 'interesting 1'}, ['parent' +
+        ' node']);
+        const node = tree.findNode(['parent node', 'some new node']);
+        expect(node.data).toEqual({data: 'interesting 1'});
+      });
+    });
+  });
+
+  describe('#translateTreeNodeIdFromACITree', () => {
+    let aciTreeApi;
+    beforeEach(() => {
+      aciTreeApi = jasmine.createSpyObj('ACITreeApi', [
+        'hasParent',
+        'parent',
+        'getId',
+      ]);
+
+      aciTreeApi.getId.and.callFake((node) => {
+        return node[0].id;
+      });
+      tree.aciTreeApi = aciTreeApi;
+    });
+
+    describe('When tree as a single level', () => {
+      beforeEach(() => {
+        aciTreeApi.hasParent.and.returnValue(false);
+      });
+
+      it('returns an array with the ID of the first level', () => {
+        let node = [{
+          id: 'some id',
+        }];
+        tree.addNewNode('some id', {}, []);
+
+        expect(tree.translateTreeNodeIdFromACITree(node)).toEqual(['some id']);
+      });
+    });
+
+    describe('When tree as a 2 levels', () => {
+      describe('When we try to retrieve the node in the second level', () => {
+        it('returns an array with the ID of the first level and second level', () => {
+          aciTreeApi.hasParent.and.returnValues(true, false);
+          aciTreeApi.parent.and.returnValue([{
+            id: 'parent id'
+          }]);
+          let node = [{
+            id: 'some id',
+          }];
+
+          tree.addNewNode('parent id', {}, []);
+          tree.addNewNode('some id', {}, ['parent id']);
+
+          expect(tree.translateTreeNodeIdFromACITree(node))
+            .toEqual(['parent id', 'some id']);
+        });
+      });
+    });
+  });
+
+  describe('#selected', () => {
+    context('a node in the tree is selected', () => {
+      it('returns that node object', () => {
+        let selectedNode = new TreeNode('bamm', {}, []);
+        setDefaultCallBack(tree, selectedNode)
+        expect(tree.selected()).toEqual(selectedNode);
+      });
+    });
+  });
+
+  describe('#findNodeByTreeElement', () => {
+    context('retrieve data from node not found', () => {
+      it('return undefined', () => {
+        let aciTreeApi = jasmine.createSpyObj('ACITreeApi', [
+          'hasParent',
+          'parent',
+          'getId',
+        ]);
+
+        aciTreeApi.getId.and.callFake((node) => {
+          return node[0].id;
+        });
+        tree.aciTreeApi = aciTreeApi;
+        expect(tree.findNodeByDomElement(['<li>something</li>'])).toBeUndefined();
+      });
+    });
+  });
+};
+
+describe('tree tests', () => {
+  describe('TreeNode', () => {
+    describe('#hasParent', () => {
+      context('parent is null', () => {
+        it('returns false', () => {
+          let treeNode = new TreeNode('123', {}, null);
+          expect(treeNode.hasParent()).toBe(false);
+        });
+      });
+      context('parent is undefined', () => {
+        it('returns false', () => {
+          let treeNode = new TreeNode('123', {}, undefined);
+          expect(treeNode.hasParent()).toBe(false);
+        });
+      });
+      context('parent exists', () => {
+        it('returns true', () => {
+          let parentNode = new TreeNode('456', {}, undefined);
+          let treeNode = new TreeNode('123', {}, parentNode);
+          expect(treeNode.hasParent()).toBe(true);
+        });
+      });
+    });
+  });
+
+  describe('Tree', () => {
+    function realTreeSelectNode(tree, selectedNode) {
+      let aciTreeApi = jasmine.createSpyObj('ACITreeApi', [
+        'selected',
+      ]);
+      tree.aciTreeApi = aciTreeApi;
+      aciTreeApi.selected.and.returnValue(selectedNode);
+    }
+
+    treeTests(Tree, realTreeSelectNode);
+  });
+
+  describe('TreeFake', () => {
+    function fakeTreeSelectNode(tree, selectedNode) {
+      tree.setSelectedNode(selectedNode);
+    }
+
+    treeTests(TreeFake, fakeTreeSelectNode);
+
+    describe('#hasParent', () => {
+      context('tree contains multiple levels', () => {
+        let tree;
+        beforeEach(() => {
+          tree = new TreeFake();
+          tree.addNewNode('level1', {data: 'interesting'}, []);
+          tree.addNewNode('level2', {data: 'interesting'}, ['level1']);
+        });
+
+        context('node is at the first level', () => {
+          it('returns false', () => {
+            expect(tree.hasParent([{id: 'level1'}])).toBe(false);
+          });
+        });
+
+        context('node is at the second level', () => {
+          it('returns true', () => {
+            expect(tree.hasParent([{id: 'level2'}])).toBe(true);
+          });
+        });
+      });
+    });
+
+    describe('#parent', () => {
+      let tree;
+      beforeEach(() => {
+        tree = new TreeFake();
+        tree.addNewNode('level1', {data: 'interesting'}, []);
+        tree.addNewNode('level2', {data: 'interesting'}, ['level1']);
+      });
+
+      context('node is the root', () => {
+        it('returns null', () => {
+          expect(tree.parent([{id: 'level1'}])).toBeNull();
+        });
+      });
+
+      context('node is not root', () => {
+        it('returns root element', () => {
+          expect(tree.parent([{id: 'level2'}])).toEqual([{id: 'level1'}]);
+        });
+      });
+    });
+
+    describe('#itemData', () => {
+      let tree;
+      beforeEach(() => {
+        tree = new TreeFake();
+        tree.addNewNode('level1', {data: 'interesting'}, []);
+        tree.addNewNode('level2', {data: 'expected data'}, ['level1']);
+      });
+
+      context('retrieve data from the node', () => {
+        it('return the node data', () => {
+          expect(tree.itemData([{id: 'level2'}])).toEqual({
+            data: 'expected' +
+            ' data'
+          })
+        });
+      });
+
+      context('retrieve data from node not found', () => {
+        it('return undefined', () => {
+          expect(tree.itemData([{id: 'bamm'}])).toBeUndefined();
+        });
+      });
+    });
+  });
+});
+


  [text/x-patch] 0002-Extractions-of-multiples-locations-where-we-used-the_v2.patch (142.1K, 4-0002-Extractions-of-multiples-locations-where-we-used-the_v2.patch)
  download | inline diff:
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/static/js/schema.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/static/js/schema.js
index a7fd4c7c..10dc7e3b 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/static/js/schema.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/static/js/schema.js
@@ -450,29 +450,7 @@ define('pgadmin.node.schema', [
     });
 
     pgBrowser.tableChildTreeNodeHierarchy = function(i) {
-      var idx = 0,
-        res = {},
-        t = pgBrowser.tree;
-
-      do {
-        var d = t.itemData(i);
-        if (
-          d._type in pgBrowser.Nodes && pgBrowser.Nodes[d._type].hasId
-        ) {
-          if (d._type === 'partition' || d._type === 'table') {
-            if (!('table' in res)) {
-              res['table'] = _.extend({}, d, {'priority': idx});
-              idx -= 1;
-            }
-          } else {
-            res[d._type] = _.extend({}, d, {'priority': idx});
-            idx -= 1;
-          }
-        }
-        i = t.hasParent(i) ? t.parent(i) : null;
-      } while (i);
-
-      return res;
+      return this.getTreeNodeHierarchy(i);
     };
   }
 
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/static/js/column.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/static/js/column.js
index 72b6eb0c..1fdacf52 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/static/js/column.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/static/js/column.js
@@ -88,7 +88,6 @@ define('pgadmin.node.column', [
 
   if (!pgBrowser.Nodes['column']) {
     pgBrowser.Nodes['column'] = pgBrowser.Node.extend({
-      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
       parent_type: ['table', 'view', 'mview'],
       collection_type: ['coll-table', 'coll-view', 'coll-mview'],
       type: 'column',
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/static/js/check_constraint.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/static/js/check_constraint.js
index bf7ef556..b1cb2518 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/static/js/check_constraint.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/static/js/check_constraint.js
@@ -8,7 +8,6 @@ define('pgadmin.node.check_constraint', [
   // Check Constraint Node
   if (!pgBrowser.Nodes['check_constraint']) {
     pgAdmin.Browser.Nodes['check_constraint'] = pgBrowser.Node.extend({
-      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
       type: 'check_constraint',
       label: gettext('Check'),
       collection_type: 'coll-constraints',
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/static/js/exclusion_constraint.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/static/js/exclusion_constraint.js
index 0bbf66a1..adccf2e9 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/static/js/exclusion_constraint.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/static/js/exclusion_constraint.js
@@ -605,7 +605,6 @@ define('pgadmin.node.exclusion_constraint', [
   // Extend the browser's node class for exclusion constraint node
   if (!pgBrowser.Nodes['exclusion_constraint']) {
     pgAdmin.Browser.Nodes['exclusion_constraint'] = pgBrowser.Node.extend({
-      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
       type: 'exclusion_constraint',
       label: gettext('Exclusion constraint'),
       collection_type: 'coll-constraints',
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.js
index 4997d175..788cecfc 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.js
@@ -603,7 +603,6 @@ define('pgadmin.node.foreign_key', [
   // Extend the browser's node class for foreign key node
   if (!pgBrowser.Nodes['foreign_key']) {
     pgAdmin.Browser.Nodes['foreign_key'] = pgBrowser.Node.extend({
-      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
       type: 'foreign_key',
       label: gettext('Foreign key'),
       collection_type: 'coll-constraints',
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/primary_key.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/primary_key.js
index d3a6cff4..0ad0f054 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/primary_key.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/primary_key.js
@@ -20,7 +20,6 @@ define('pgadmin.node.primary_key', [
       parent_type: ['table','partition'],
       canDrop: true,
       canDropCascade: true,
-      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
       Init: function() {
         /* Avoid multiple registration of menus */
         if (this.initialized)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/unique_constraint.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/unique_constraint.js
index 769185d6..18d3ca33 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/unique_constraint.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/unique_constraint.js
@@ -20,7 +20,6 @@ define('pgadmin.node.unique_constraint', [
       parent_type: ['table','partition'],
       canDrop: true,
       canDropCascade: true,
-      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
       Init: function() {
         /* Avoid multiple registration of menus */
         if (this.initialized)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/static/js/constraints.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/static/js/constraints.js
index cb242cd2..9c0e24fe 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/static/js/constraints.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/static/js/constraints.js
@@ -12,14 +12,12 @@ define('pgadmin.node.constraints', [
         node: 'constraints',
         label: gettext('Constraints'),
         type: 'coll-constraints',
-        getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
         columns: ['name', 'comment'],
       });
   }
 
   if (!pgBrowser.Nodes['constraints']) {
     pgAdmin.Browser.Nodes['constraints'] = pgBrowser.Node.extend({
-      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
       type: 'constraints',
       label: gettext('Constraints'),
       collection_type: 'coll-constraints',
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/js/index.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/js/index.js
index ec2b4da1..2b687c1a 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/js/index.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/js/index.js
@@ -13,7 +13,6 @@ define('pgadmin.node.index', [
         node: 'index',
         label: gettext('Indexes'),
         type: 'coll-index',
-        getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
         sqlAlterHelp: 'sql-alterindex.html',
         sqlCreateHelp: 'sql-createindex.html',
         dialogHelp: url_for('help.static', {'filename': 'index_dialog.html'}),
@@ -215,7 +214,6 @@ define('pgadmin.node.index', [
 
   if (!pgBrowser.Nodes['index']) {
     pgAdmin.Browser.Nodes['index'] = pgBrowser.Node.extend({
-      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
       parent_type: ['table', 'view', 'mview', 'partition'],
       collection_type: ['coll-table', 'coll-view'],
       sqlAlterHelp: 'sql-alterindex.html',
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/static/js/partition.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/static/js/partition.js
index fd53f743..ef8eb534 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/static/js/partition.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/static/js/partition.js
@@ -1,10 +1,12 @@
 define([
+  'sources/tree/pgadmin_tree_node',
   'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
   'underscore.string', 'sources/pgadmin', 'pgadmin.browser',
   'pgadmin.alertifyjs', 'pgadmin.backform', 'pgadmin.backgrid',
   'pgadmin.browser.collection', 'pgadmin.browser.table.partition.utils',
 ],
 function(
+  pgadminTreeNode,
   gettext, url_for, $, _, S, pgAdmin, pgBrowser, Alertify, Backform, Backgrid
 ) {
 
@@ -13,7 +15,6 @@ function(
       pgAdmin.Browser.Collection.extend({
         node: 'partition',
         label: gettext('Partitions'),
-        getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
         type: 'coll-partition',
         columns: [
           'name', 'schema', 'partition_value', 'is_partitioned', 'description',
@@ -80,36 +81,6 @@ function(
         },
         ]);
       },
-      getTreeNodeHierarchy: function(i) {
-        var idx = 0,
-          res = {},
-          t = pgBrowser.tree;
-
-        do {
-          var d = t.itemData(i);
-          if (
-            d._type in pgBrowser.Nodes && pgBrowser.Nodes[d._type].hasId
-          ) {
-            if (d._type == 'partition' && 'partition' in res) {
-              if (!('table' in res)) {
-                res['table'] = _.extend({}, d, {'priority': idx});
-                idx -= 1;
-              }
-            } else if (d._type == 'table') {
-              if (!('table' in res)) {
-                res['table'] = _.extend({}, d, {'priority': idx});
-                idx -= 1;
-              }
-            } else {
-              res[d._type] = _.extend({}, d, {'priority': idx});
-              idx -= 1;
-            }
-          }
-          i = t.hasParent(i) ? t.parent(i) : null;
-        } while (i);
-
-        return res;
-      },
       generate_url: function(item, type, d, with_id, info) {
         if (_.indexOf([
           'stats', 'statistics', 'dependency', 'dependent', 'reset',
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/static/js/rule.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/static/js/rule.js
index 3af61754..354909f3 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/static/js/rule.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/static/js/rule.js
@@ -16,7 +16,6 @@ define('pgadmin.node.rule', [
         node: 'rule',
         label: gettext('Rules'),
         type: 'coll-rule',
-        getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
         columns: ['name', 'owner', 'comment'],
       });
   }
@@ -35,7 +34,6 @@ define('pgadmin.node.rule', [
    */
   if (!pgBrowser.Nodes['rule']) {
     pgAdmin.Browser.Nodes['rule'] = pgBrowser.Node.extend({
-      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
       parent_type: ['table','view', 'partition'],
       type: 'rule',
       sqlAlterHelp: 'sql-alterrule.html',
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js
index 647d1bfe..99d60243 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js
@@ -26,7 +26,6 @@ define('pgadmin.node.table', [
 
   if (!pgBrowser.Nodes['table']) {
     pgBrowser.Nodes['table'] = pgBrowser.Node.extend({
-      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
       type: 'table',
       label: gettext('Table'),
       collection_type: 'coll-table',
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/js/trigger.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/js/trigger.js
index fbe7659e..9a1ce2e0 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/js/trigger.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/js/trigger.js
@@ -29,14 +29,12 @@ define('pgadmin.node.trigger', [
         node: 'trigger',
         label: gettext('Triggers'),
         type: 'coll-trigger',
-        getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
         columns: ['name', 'description'],
       });
   }
 
   if (!pgBrowser.Nodes['trigger']) {
     pgAdmin.Browser.Nodes['trigger'] = pgBrowser.Node.extend({
-      getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy,
       parent_type: ['table', 'view', 'partition'],
       collection_type: ['coll-table', 'coll-view'],
       type: 'trigger',
diff --git a/web/pgadmin/browser/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js
index 0f640a90..c94616dd 100644
--- a/web/pgadmin/browser/static/js/browser.js
+++ b/web/pgadmin/browser/static/js/browser.js
@@ -1,4 +1,5 @@
 define('pgadmin.browser', [
+  'sources/tree/tree',
   'sources/gettext', 'sources/url_for', 'require', 'jquery', 'underscore', 'underscore.string',
   'bootstrap', 'sources/pgadmin', 'pgadmin.alertifyjs', 'bundled_codemirror',
   'sources/check_node_visibility', 'sources/modify_animation', 'pgadmin.browser.utils', 'wcdocker',
@@ -10,6 +11,7 @@ define('pgadmin.browser', [
   'sources/codemirror/addon/fold/pgadmin-sqlfoldcode',
   'pgadmin.browser.keyboard',
 ], function(
+  tree,
   gettext, url_for, require, $, _, S, Bootstrap, pgAdmin, Alertify,
   codemirror, checkNodeVisibility, modifyAnimation
 ) {
@@ -86,6 +88,7 @@ define('pgadmin.browser', [
       });
 
       b.tree = $('#tree').aciTree('api');
+      b.treeMenu.register($('#tree'));
     };
 
   // Extend the browser class attributes
@@ -100,6 +103,7 @@ define('pgadmin.browser', [
     editor:null,
     // Left hand browser tree
     tree:null,
+    treeMenu: new tree.Tree(),
     // list of script to be loaded, when a certain type of node is loaded
     // It will be used to register extensions, tools, child node scripts,
     // etc.
diff --git a/web/pgadmin/browser/static/js/node.js b/web/pgadmin/browser/static/js/node.js
index ad582483..3baf554a 100644
--- a/web/pgadmin/browser/static/js/node.js
+++ b/web/pgadmin/browser/static/js/node.js
@@ -1,9 +1,12 @@
 define('pgadmin.browser.node', [
+  'sources/tree/pgadmin_tree_node',
   '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(gettext, $, _, S, pgAdmin, Menu, Backbone, Alertify, pgBrowser, Backform, generateUrl, commonUtils) {
+], function(
+  pgadminTreeNode,
+  gettext, $, _, S, pgAdmin, Menu, Backbone, Alertify, pgBrowser, Backform, generateUrl, commonUtils) {
 
   var wcDocker = window.wcDocker,
     keyCode = {
@@ -1566,7 +1569,6 @@ define('pgadmin.browser.node', [
      * depends, statistics
      */
     generate_url: function(item, type, d, with_id, info) {
-
       var opURL = {
           'create': 'obj',
           'drop': 'obj',
@@ -1608,24 +1610,7 @@ define('pgadmin.browser.node', [
     Collection: pgBrowser.DataCollection,
     // Base class for Node Data Model
     Model: pgBrowser.DataModel,
-    getTreeNodeHierarchy: function(i) {
-      var idx = 0,
-        res = {},
-        t = pgBrowser.tree,
-        d;
-      do {
-        d = t.itemData(i);
-        if (d._type in pgBrowser.Nodes && pgBrowser.Nodes[d._type].hasId) {
-          res[d._type] = _.extend({}, d, {
-            'priority': idx,
-          });
-          idx -= 1;
-        }
-        i = t.hasParent(i) ? t.parent(i) : null;
-      } while (i);
-
-      return res;
-    },
+    getTreeNodeHierarchy: pgadminTreeNode.getTreeNodeHierarchyFromIdentifier.bind(pgBrowser),
     cache: function(url, node_info, level, data) {
       var cached = this.cached = this.cached || {},
         hash = url,
diff --git a/web/pgadmin/static/js/backup/backup_dialog.js b/web/pgadmin/static/js/backup/backup_dialog.js
new file mode 100644
index 00000000..779170ee
--- /dev/null
+++ b/web/pgadmin/static/js/backup/backup_dialog.js
@@ -0,0 +1,142 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import gettext from '../gettext';
+import {sprintf} from 'sprintf-js';
+import Backform from '../backform.pgadmin';
+import {DialogFactory} from './backup_dialog_factory';
+
+export class BackupDialog {
+  constructor(pgBrowser, $, alertify, BackupModel, backform = Backform) {
+    this.pgBrowser = pgBrowser;
+    this.jquery = $;
+    this.alertify = alertify;
+    this.backupModel = BackupModel;
+    this.backform = backform;
+  }
+
+  draw(action, item, params) {
+    const serverInformation = this.retrieveAncestorOfTypeServer(item);
+
+    if (!serverInformation) {
+      return;
+    }
+
+    if (!this.hasBinariesConfiguration(serverInformation)) {
+      return;
+    }
+
+    const typeOfDialog = BackupDialog.typeOfDialog(params);
+
+    const dialog = this.createOrGetDialog(typeOfDialog);
+    dialog(true).resizeTo('60%', '50%');
+  }
+
+  static typeOfDialog(params) {
+    if(params === null) {
+      return 'backup_objects';
+    }
+    let typeOfDialog = 'server';
+    if (!_.isUndefined(params['globals']) && params['globals']) {
+      typeOfDialog = 'globals';
+    }
+    return typeOfDialog;
+  }
+
+  static dialogTitle(typeOfDialog) {
+    if(typeOfDialog === 'backup_objects') {
+      return null;
+    }
+    return ((typeOfDialog === 'globals') ?
+      gettext('Backup Globals...') :
+      gettext('Backup Server...'));
+  }
+
+  createOrGetDialog(typeOfDialog) {
+    const dialogName = this.dialogName(typeOfDialog);
+    const dialogTitle = BackupDialog.dialogTitle(typeOfDialog);
+
+    if (!this.alertify[dialogName]) {
+      const self = this;
+      this.alertify.dialog(dialogName, function factory() {
+        return self.dialogFactory(dialogTitle, typeOfDialog);
+      });
+    }
+    return this.alertify[dialogName];
+  }
+
+  dialogName(typeOfDialog) {
+    if(typeOfDialog === 'backup_objects') {
+      return typeOfDialog;
+    }
+    return 'BackupDialog_' + typeOfDialog;
+  }
+
+  hasBinariesConfiguration(serverInformation) {
+    const module = 'paths';
+    let preference_name = 'pg_bin_dir';
+    let msg = gettext('Please configure the PostgreSQL Binary Path in the Preferences dialog.');
+
+    if ((serverInformation.type && serverInformation.type === 'ppas') ||
+      serverInformation.server_type === 'ppas') {
+      preference_name = 'ppas_bin_dir';
+      msg = gettext('Please configure the EDB Advanced Server Binary Path in the Preferences dialog.');
+    }
+    const preference = this.pgBrowser.get_preference(module, preference_name);
+
+    if (preference) {
+      if (!preference.value) {
+        this.alertify.alert(gettext('Configuration required'), msg);
+        return false;
+      }
+    } else {
+      this.alertify.alert(
+        gettext('Backup Error'),
+        sprintf(gettext('Failed to load preference %s of module %s'), preference_name, module)
+      );
+      return false;
+    }
+    return true;
+  }
+
+  retrieveAncestorOfTypeServer(item) {
+    let serverInformation = null;
+    let aciTreeItem = item || this.pgBrowser.treeMenu.selected();
+    let treeNode = this.pgBrowser.treeMenu.findNodeByDomElement(aciTreeItem);
+
+    while (treeNode) {
+      const node_data = treeNode.getData();
+      if (node_data._type === 'server') {
+        serverInformation = node_data;
+        break;
+      }
+
+      if (treeNode.hasParent()) {
+        treeNode = treeNode.parent();
+      } else {
+        this.alertify.alert(
+          gettext('Backup Error'),
+          gettext('Please select server or child node from the browser tree.')
+        );
+        break;
+      }
+    }
+    return serverInformation;
+  }
+
+  dialogFactory(dialogTitle, typeOfDialog) {
+    const factory = new DialogFactory(
+      this.pgBrowser,
+      this.jquery,
+      this.alertify,
+      this.backupModel,
+      this.backform);
+    return factory.create(dialogTitle, typeOfDialog);
+  }
+}
diff --git a/web/pgadmin/static/js/backup/backup_dialog_factory.js b/web/pgadmin/static/js/backup/backup_dialog_factory.js
new file mode 100644
index 00000000..2fd491d6
--- /dev/null
+++ b/web/pgadmin/static/js/backup/backup_dialog_factory.js
@@ -0,0 +1,56 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import * as commonUtils from '../utils';
+import {DialogWrapper} from './backup_dialog_wrapper';
+
+export class DialogFactory {
+  constructor(pgBrowser, $, alertify, BackupModel, backform) {
+    this.pgBrowser = pgBrowser;
+    this.jquery = $;
+    this.alertify = alertify;
+    this.backupModel = BackupModel;
+    this.backform = backform;
+  }
+
+  create(dialogTitle, typeOfDialog) {
+    const dialogContainerSelector = '<div class=\'backup_dialog\'></div>';
+    return new DialogWrapper(dialogContainerSelector, this, dialogTitle, typeOfDialog);
+  }
+
+  static wasBackupButtonPressed(event) {
+    return event.button['data-btn-name'] === 'backup';
+  }
+
+  static wasHelpButtonPressed(e) {
+    return e.button.element.name === 'dialog_help'
+      || e.button.element.name === 'object_help';
+  }
+
+  static getSelectedNode(selectedTreeNode) {
+    if (!DialogFactory.isNodeSelected(selectedTreeNode)) {
+      return undefined;
+    }
+    const treeNodeData = selectedTreeNode.getData();
+    if (treeNodeData) {
+      return treeNodeData;
+    }
+    return undefined;
+  }
+
+  static focusOnDialog(dialog) {
+    const container = dialog.$el.find('.tab-content:first > .tab-pane.active:first');
+    commonUtils.findAndSetFocus(container);
+  }
+
+  static isNodeSelected(selectedTreeNode) {
+    return selectedTreeNode;
+  }
+}
+
diff --git a/web/pgadmin/static/js/backup/backup_dialog_wrapper.js b/web/pgadmin/static/js/backup/backup_dialog_wrapper.js
new file mode 100644
index 00000000..e5c803c0
--- /dev/null
+++ b/web/pgadmin/static/js/backup/backup_dialog_wrapper.js
@@ -0,0 +1,270 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import {getTreeNodeHierarchyFromElement} from '../tree/pgadmin_tree_node';
+import axios from 'axios/index';
+import {DialogFactory} from './backup_dialog_factory';
+import gettext from '../gettext';
+import url_for from '../url_for';
+import _ from 'underscore';
+
+export class DialogWrapper {
+  constructor(dialogContainerSelector, factory, dialogTitle, typeOfDialog) {
+    this.hooks = {
+      // Triggered when the dialog is closed
+      onclose: function () {
+        if (this.view) {
+          // clear our backform model/view
+          this.view.remove({
+            data: true,
+            internal: true,
+            silent: true,
+          });
+        }
+      },
+    };
+    this.dialogContainerSelector = dialogContainerSelector;
+    this.factory = factory;
+    this.dialogTitle = dialogTitle;
+    this.typeOfDialog = typeOfDialog;
+  }
+
+  main(title) {
+    this.set('title', title);
+  }
+
+  build() {
+    this.factory.alertify.pgDialogBuild.apply(this);
+  }
+
+  setup() {
+    return {
+      buttons: [{
+        text: '',
+        className: 'btn btn-default pull-left fa fa-lg fa-info',
+        attrs: {
+          name: 'object_help',
+          type: 'button',
+          url: 'backup.html',
+          label: gettext('Backup'),
+        },
+      }, {
+        text: '',
+        key: 112,
+        className: 'btn btn-default pull-left fa fa-lg fa-question',
+        attrs: {
+          name: 'dialog_help',
+          type: 'button',
+          label: gettext('Backup'),
+          url: url_for('help.static', {
+            'filename': 'backup_dialog.html',
+          }),
+        },
+      }, {
+        text: gettext('Backup'),
+        key: 13,
+        className: 'btn btn-primary fa fa-lg fa-save pg-alertify-button',
+        'data-btn-name': 'backup',
+      }, {
+        text: gettext('Cancel'),
+        key: 27,
+        className: 'btn btn-danger fa fa-lg fa-times pg-alertify-button',
+        'data-btn-name': 'cancel',
+      }],
+      // Set options for dialog
+      options: {
+        title: this.dialogTitle,
+        //disable both padding and overflow control.
+        padding: !1,
+        overflow: !1,
+        model: 0,
+        resizable: true,
+        maximizable: true,
+        pinnable: false,
+        closableByDimmer: false,
+        modal: false,
+      },
+    };
+  }
+
+  prepare() {
+    this.disableBackupButton();
+
+    const $container = this.factory.jquery(this.dialogContainerSelector);
+    const selectedTreeNode = this.getSelectedNode();
+    const selectedTreeNodeData = DialogFactory.getSelectedNode(selectedTreeNode);
+    if (!selectedTreeNodeData) {
+      return;
+    }
+
+    const node = this.factory.pgBrowser.Nodes[selectedTreeNodeData._type];
+    if (this.dialogTitle === null) {
+      const title = `Backup (${node.label}: ${selectedTreeNodeData.label})`;
+      this.main(title);
+    }
+
+    const treeInfo = getTreeNodeHierarchyFromElement(this.factory.pgBrowser, selectedTreeNode);
+    const dialog = this.createDialog(node, treeInfo, this.typeOfDialog, $container);
+    this.addAlertifyClassToBackupNodeChildNodes();
+    dialog.render();
+
+    this.elements.content.appendChild($container.get(0));
+
+    DialogFactory.focusOnDialog(dialog);
+    this.setListenersForFilenameChanges();
+  }
+
+  callback(event) {
+    const selectedTreeNode = this.getSelectedNode();
+    const selectedTreeNodeData = DialogFactory.getSelectedNode(selectedTreeNode);
+    const node = selectedTreeNodeData && this.factory.pgBrowser.Nodes[selectedTreeNodeData._type];
+
+    if (DialogFactory.wasHelpButtonPressed(event)) {
+      event.cancel = true;
+      this.factory.pgBrowser.showHelp(
+        event.button.element.name,
+        event.button.element.getAttribute('url'),
+        node,
+        selectedTreeNode,
+        event.button.element.getAttribute('label')
+      );
+      return;
+    }
+
+    if (DialogFactory.wasBackupButtonPressed(event)) {
+
+      if (!selectedTreeNodeData)
+        return;
+
+      const serverIdentifier = this.retrieveServerIdentifier(node, selectedTreeNode);
+
+      const factory = this.factory;
+      let urlShortcut = 'backup.create_server_job';
+      if (this.typeOfDialog === 'backup_objects') {
+        urlShortcut = 'backup.create_object_job';
+      }
+      const baseUrl = url_for(urlShortcut, {
+        'sid': serverIdentifier,
+      });
+
+      this.setExtraParameters(selectedTreeNode);
+
+      let service = axios.create({});
+      service.post(
+        baseUrl,
+        this.view.model.toJSON()
+      ).then(function () {
+        factory.alertify.success(gettext('Backup job created.'), 5);
+        factory.pgBrowser.Events.trigger('pgadmin-bgprocess:created', factory);
+      }).catch(function (error) {
+        try {
+          const err = error.response.data;
+          factory.alertify.alert(
+            gettext('Backup job failed.'),
+            err.errormsg
+          );
+        } catch (e) {
+          console.warn(e.stack || e);
+        }
+      });
+    }
+  }
+
+  addAlertifyClassToBackupNodeChildNodes() {
+    this.factory.jquery(this.elements.body.childNodes[0]).addClass(
+      'alertify_tools_dialog_properties obj_properties'
+    );
+  }
+
+  getSelectedNode() {
+    const tree = this.factory.pgBrowser.treeMenu;
+    const selectedNode = tree.selected();
+    if (selectedNode) {
+      return tree.findNodeByDomElement(selectedNode);
+    } else {
+      return undefined;
+    }
+  }
+
+  disableBackupButton() {
+    this.__internal.buttons[2].element.disabled = true;
+  }
+
+  enableBackupButton() {
+    this.__internal.buttons[2].element.disabled = false;
+  }
+
+  createDialog(node, treeInfo, typeOfDialog, $container) {
+    let attributes = {};
+    if (typeOfDialog !== 'backup_objects') {
+      attributes['type'] = typeOfDialog;
+    }
+    // Instance of backbone model
+    const newModel = new this.factory.backupModel(attributes, {
+      node_info: treeInfo,
+    });
+    const fields = this.factory.backform.generateViewSchema(
+      treeInfo, newModel, 'create', node, treeInfo.server, true
+    );
+
+    return this.view = new this.factory.backform.Dialog({
+      el: $container,
+      model: newModel,
+      schema: fields,
+    });
+  }
+
+  retrieveServerIdentifier(node, selectedTreeNode) {
+    const treeInfo = getTreeNodeHierarchyFromElement(
+      this.factory.pgBrowser,
+      selectedTreeNode
+    );
+    return treeInfo.server._id;
+  }
+
+  setListenersForFilenameChanges() {
+    const self = this;
+
+    this.view.model.on('change', function () {
+      if (!_.isUndefined(this.get('file')) && this.get('file') !== '') {
+        this.errorModel.clear();
+        self.enableBackupButton();
+      } else {
+        self.disableBackupButton();
+        this.errorModel.set('file', gettext('Please provide a filename'));
+      }
+    });
+  }
+
+  setExtraParameters(selectedTreeNode) {
+    if (this.typeOfDialog === 'backup_objects') {
+      const treeInfo = getTreeNodeHierarchyFromElement(
+        this.factory.pgBrowser,
+        selectedTreeNode
+      );
+
+      this.view.model.set('database', treeInfo.database._label);
+
+      const nodeData = selectedTreeNode.getData();
+      if (nodeData._type === 'schema') {
+        this.view.model.set('schemas', [nodeData._label]);
+      }
+
+      if (nodeData._type === 'table') {
+        this.view.model.set('tables', [
+          [treeInfo.schema._label, nodeData._label],
+        ]);
+      }
+
+      if (_.isEmpty(this.view.model.get('ratio'))) {
+        this.view.model.unset('ratio');
+      }
+    }
+  }
+}
diff --git a/web/pgadmin/static/js/backup/menu_utils.js b/web/pgadmin/static/js/backup/menu_utils.js
new file mode 100644
index 00000000..f67a436a
--- /dev/null
+++ b/web/pgadmin/static/js/backup/menu_utils.js
@@ -0,0 +1,60 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+export const backupSupportedNodes = [
+  'database', 'schema', 'table', 'partition',
+];
+
+function isNodeTypeSupported(nodeDataType, parentNodeType) {
+  return _.indexOf(backupSupportedNodes, nodeDataType) !== -1
+    && parentNodeType !== 'catalog';
+}
+
+function isProvidedDataValid(treeNodeData) {
+  return !_.isUndefined(treeNodeData) && !_.isNull(treeNodeData);
+}
+
+function doesNodeHaveMenu(treeNodeData) {
+  return (treeNodeData._type === 'database' && treeNodeData.allowConn)
+    || treeNodeData._type !== 'database';
+}
+
+function retrieveParentNodeType(treeNode) {
+  if (!treeNode.hasParent()) {
+    return null;
+  }
+  if(treeNode.parent().getData() === null) {
+    return null;
+  }
+  return treeNode.parent().getData()._type;
+}
+
+export function menuEnabled(treeNodeData, domTreeNode) {
+  let treeNode = this.treeMenu.findNodeByDomElement(domTreeNode);
+  if(!treeNode) {
+    return false;
+  }
+  let parentNodeType = retrieveParentNodeType(treeNode);
+
+  if (isProvidedDataValid(treeNodeData) && !_.isNull(parentNodeType)) {
+    return isNodeTypeSupported(treeNodeData._type, parentNodeType)
+      && doesNodeHaveMenu(treeNodeData);
+  } else {
+    return false;
+  }
+}
+
+function isNodeAServerAndConnected(treeNodeData) {
+  return (('server' === treeNodeData._type) && treeNodeData.connected);
+}
+
+export function menuEnabledServer(treeNodeData) {
+  return isProvidedDataValid(treeNodeData)
+    && isNodeAServerAndConnected(treeNodeData);
+}
diff --git a/web/pgadmin/static/js/datagrid/get_panel_title.js b/web/pgadmin/static/js/datagrid/get_panel_title.js
new file mode 100644
index 00000000..f5a5664d
--- /dev/null
+++ b/web/pgadmin/static/js/datagrid/get_panel_title.js
@@ -0,0 +1,33 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import {getTreeNodeHierarchyFromIdentifier} from '../tree/pgadmin_tree_node';
+
+function getDatabaseLabel(parentData) {
+  return parentData.database ? parentData.database.label
+    : parentData.server.db;
+}
+
+function isServerInformationAvailable(parentData) {
+  return parentData.server === undefined;
+}
+
+export function getPanelTitle(pgBrowser) {
+  const selected_item = pgBrowser.treeMenu.selected();
+
+  const parentData = getTreeNodeHierarchyFromIdentifier
+    .call(pgBrowser, selected_item);
+  if (isServerInformationAvailable(parentData)) {
+    return;
+  }
+
+  const db_label = getDatabaseLabel(parentData);
+
+  return `${db_label} on ${parentData.server.user.name}@${parentData.server.label}`;
+}
diff --git a/web/pgadmin/static/js/datagrid/show_data.js b/web/pgadmin/static/js/datagrid/show_data.js
new file mode 100644
index 00000000..a414fd77
--- /dev/null
+++ b/web/pgadmin/static/js/datagrid/show_data.js
@@ -0,0 +1,92 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+import gettext from '../gettext';
+import url_for from '../url_for';
+import {getTreeNodeHierarchyFromIdentifier} from '../../../static/js/tree/pgadmin_tree_node';
+
+export function showDataGrid(
+  datagrid,
+  pgBrowser,
+  alertify,
+  connectionData,
+  aciTreeIdentifier
+) {
+  const node = pgBrowser.treeMenu.findNodeByDomElement(aciTreeIdentifier);
+  if (node === undefined || !node.getData()) {
+    alertify.alert(
+      gettext('Data Grid Error'),
+      gettext('No object selected.')
+    );
+    return;
+  }
+
+  const parentData = getTreeNodeHierarchyFromIdentifier.call(
+    pgBrowser,
+    aciTreeIdentifier
+  );
+
+  if (hasServerOrDatabaseConfiguration(parentData)
+    || !hasSchemaOrCatalogOrViewInformation(parentData)) {
+    return;
+  }
+
+  let namespaceName = retrieveNameSpaceName(parentData);
+  const baseUrl = generateUrl(connectionData, node.getData(), parentData);
+  const grid_title = generateDatagridTitle(parentData, namespaceName, node.getData());
+
+  datagrid.create_transaction(
+    baseUrl,
+    null,
+    'false',
+    parentData.server.server_type,
+    '',
+    grid_title,
+    ''
+  );
+}
+
+
+function retrieveNameSpaceName(parentData) {
+  if (parentData.schema !== undefined) {
+    return parentData.schema.label;
+  }
+  else if (parentData.view !== undefined) {
+    return parentData.view.label;
+  }
+  else if (parentData.catalog !== undefined) {
+    return parentData.catalog.label;
+  }
+  return '';
+}
+
+function generateUrl(connectionData, nodeData, parentData) {
+  const url_params = {
+    'cmd_type': connectionData.mnuid,
+    'obj_type': nodeData._type,
+    'sgid': parentData.server_group._id,
+    'sid': parentData.server._id,
+    'did': parentData.database._id,
+    'obj_id': nodeData._id,
+  };
+
+  return url_for('datagrid.initialize_datagrid', url_params);
+}
+
+function hasServerOrDatabaseConfiguration(parentData) {
+  return parentData.server === undefined || parentData.database === undefined;
+}
+
+function hasSchemaOrCatalogOrViewInformation(parentData) {
+  return parentData.schema !== undefined || parentData.view !== undefined ||
+    parentData.catalog !== undefined;
+}
+
+function generateDatagridTitle(parentData, namespaceName, nodeData) {
+  return `${parentData.server.label} - ${parentData.database.label} - ${namespaceName}.${nodeData.label}`;
+}
diff --git a/web/pgadmin/static/js/datagrid/show_query_tool.js b/web/pgadmin/static/js/datagrid/show_query_tool.js
new file mode 100644
index 00000000..0436e0fd
--- /dev/null
+++ b/web/pgadmin/static/js/datagrid/show_query_tool.js
@@ -0,0 +1,63 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import gettext from '../gettext';
+import url_for from '../url_for';
+import {getTreeNodeHierarchyFromIdentifier} from '../../../static/js/tree/pgadmin_tree_node';
+
+function hasDatabaseInformation(parentData) {
+  return parentData.database;
+}
+
+function generateUrl(parentData) {
+  let url_endpoint = 'datagrid.initialize_query_tool';
+  let url_params = {
+    'sgid': parentData.server_group._id,
+    'sid': parentData.server._id,
+  };
+
+  if (hasDatabaseInformation(parentData)) {
+    url_params['did'] = parentData.database._id;
+    url_endpoint = 'datagrid.initialize_query_tool_with_did';
+  }
+
+  return url_for(url_endpoint, url_params);
+}
+
+function hasServerInformations(parentData) {
+  return parentData.server === undefined;
+}
+
+export function showQueryTool(datagrid, pgBrowser, alertify, url,
+                              aciTreeIdentifier, panelTitle) {
+  const sURL = url || '';
+  const queryToolTitle = panelTitle || '';
+
+  const currentNode = pgBrowser.treeMenu.findNodeByDomElement(aciTreeIdentifier);
+  if (currentNode === undefined) {
+    alertify.alert(
+      gettext('Query Tool Error'),
+      gettext('No object selected.')
+    );
+    return;
+  }
+
+  const parentData = getTreeNodeHierarchyFromIdentifier.call(
+    pgBrowser, aciTreeIdentifier);
+
+  if (hasServerInformations(parentData)) {
+    return;
+  }
+
+  const baseUrl = generateUrl(parentData);
+
+  datagrid.create_transaction(
+    baseUrl, null, 'true',
+    parentData.server.server_type, sURL, queryToolTitle, '', false);
+}
diff --git a/web/pgadmin/static/js/tree/pgadmin_tree_node.js b/web/pgadmin/static/js/tree/pgadmin_tree_node.js
new file mode 100644
index 00000000..bccbe587
--- /dev/null
+++ b/web/pgadmin/static/js/tree/pgadmin_tree_node.js
@@ -0,0 +1,43 @@
+//////////////////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////////////////
+
+export function getTreeNodeHierarchyFromElement(pgBrowser, treeNode) {
+  return getTreeNodeHierarchy.call(pgBrowser, treeNode);
+}
+
+export function getTreeNodeHierarchyFromIdentifier(aciTreeNodeIdentifier) {
+  let identifier = this.treeMenu.translateTreeNodeIdFromACITree(aciTreeNodeIdentifier);
+  let currentNode = this.treeMenu.findNode(identifier);
+  return getTreeNodeHierarchy.call(this, currentNode);
+}
+
+export function getTreeNodeHierarchy(currentNode) {
+  let idx = 0;
+  let result = {};
+
+  do {
+    const currentNodeData = currentNode.getData();
+    if (currentNodeData._type in this.Nodes && this.Nodes[currentNodeData._type].hasId) {
+      const nodeType = mapType(currentNodeData._type);
+      if (result[nodeType] === undefined) {
+        result[nodeType] = _.extend({}, currentNodeData, {
+          'priority': idx,
+        });
+        idx -= 1;
+      }
+    }
+    currentNode = currentNode.hasParent() ? currentNode.parent() : null;
+  } while (currentNode);
+
+  return result;
+}
+
+function mapType(type) {
+  return type === 'partition' ? 'table' : type;
+}
diff --git a/web/pgadmin/tools/backup/static/js/backup.js b/web/pgadmin/tools/backup/static/js/backup.js
index ddbfaee4..ffbae127 100644
--- a/web/pgadmin/tools/backup/static/js/backup.js
+++ b/web/pgadmin/tools/backup/static/js/backup.js
@@ -3,9 +3,10 @@ define([
   'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
   'underscore.string', 'pgadmin.alertifyjs', 'backbone', 'pgadmin.backgrid',
   'pgadmin.backform', 'pgadmin.browser', 'sources/utils',
+  'sources/backup/menu_utils', 'sources/backup/backup_dialog',
 ], function(
   gettext, url_for, $, _, S, alertify, Backbone, Backgrid, Backform, pgBrowser,
-commonUtils
+commonUtils, menuUtils, globalBackupDialog
 ) {
 
   // if module is already initialized, refer to that.
@@ -394,48 +395,6 @@ commonUtils
 
       this.initialized = true;
 
-      // Define list of nodes on which backup context menu option appears
-      var backup_supported_nodes = [
-        'database', 'schema', 'table', 'partition',
-      ];
-
-      /**
-        Enable/disable backup menu in tools based
-        on node selected
-        if selected node is present in supported_nodes,
-        menu will be enabled otherwise disabled.
-        Also, hide it for system view in catalogs
-      */
-      var menu_enabled = function(itemData, item) {
-        var t = pgBrowser.tree,
-          i = item,
-          d = itemData,
-          parent_item = t.hasParent(i) ? t.parent(i) : null,
-          parent_data = parent_item ? t.itemData(parent_item) : null;
-
-        if (!_.isUndefined(d) && !_.isNull(d) && !_.isNull(parent_data)) {
-          if (_.indexOf(backup_supported_nodes, d._type) !== -1 &&
-            parent_data._type != 'catalog') {
-            if (d._type == 'database' && d.allowConn)
-              return true;
-            else if (d._type != 'database')
-              return true;
-            else
-              return false;
-          } else
-            return false;
-        } else
-          return false;
-      };
-
-      var menu_enabled_server = function(itemData) {
-        // If server node selected && connected
-        if (!_.isUndefined(itemData) && !_.isNull(itemData))
-          return (('server' === itemData._type) && itemData.connected);
-        else
-          return false;
-      };
-
       // Define the nodes on which the menus to be appear
       var menus = [{
         name: 'backup_global',
@@ -445,7 +404,7 @@ commonUtils
         priority: 12,
         label: gettext('Backup Globals...'),
         icon: 'fa fa-floppy-o',
-        enable: menu_enabled_server,
+        enable: menuUtils.menuEnabledServer,
       }, {
         name: 'backup_server',
         module: this,
@@ -454,7 +413,7 @@ commonUtils
         priority: 12,
         label: gettext('Backup Server...'),
         icon: 'fa fa-floppy-o',
-        enable: menu_enabled_server,
+        enable: menuUtils.menuEnabledServer,
       }, {
         name: 'backup_global_ctx',
         module: this,
@@ -464,7 +423,7 @@ commonUtils
         priority: 12,
         label: gettext('Backup Globals...'),
         icon: 'fa fa-floppy-o',
-        enable: menu_enabled_server,
+        enable: menuUtils.menuEnabledServer,
       }, {
         name: 'backup_server_ctx',
         module: this,
@@ -474,7 +433,7 @@ commonUtils
         priority: 12,
         label: gettext('Backup Server...'),
         icon: 'fa fa-floppy-o',
-        enable: menu_enabled_server,
+        enable: menuUtils.menuEnabledServer,
       }, {
         name: 'backup_object',
         module: this,
@@ -483,20 +442,20 @@ commonUtils
         priority: 11,
         label: gettext('Backup...'),
         icon: 'fa fa-floppy-o',
-        enable: menu_enabled,
+        enable: menuUtils.menuEnabled.bind(pgBrowser),
       }];
 
-      for (var idx = 0; idx < backup_supported_nodes.length; idx++) {
+      for (var idx = 0; idx < menuUtils.backupSupportedNodes.length; idx++) {
         menus.push({
-          name: 'backup_' + backup_supported_nodes[idx],
-          node: backup_supported_nodes[idx],
+          name: 'backup_' + menuUtils.backupSupportedNodes[idx],
+          node: menuUtils.backupSupportedNodes[idx],
           module: this,
           applies: ['context'],
           callback: 'backup_objects',
           priority: 11,
           label: gettext('Backup...'),
           icon: 'fa fa-floppy-o',
-          enable: menu_enabled,
+          enable: menuUtils.menuEnabled.bind(pgBrowser),
         });
       }
 
@@ -521,531 +480,25 @@ commonUtils
     },
 
     // Callback to draw Backup Dialog for globals/server
-    start_backup_global_server: function(action, item, params) {
-      var i = item || pgBrowser.tree.selected(),
-        server_data = null;
-
-      while (i) {
-        var node_data = pgBrowser.tree.itemData(i);
-        if (node_data._type == 'server') {
-          server_data = node_data;
-          break;
-        }
-
-        if (pgBrowser.tree.hasParent(i)) {
-          i = $(pgBrowser.tree.parent(i));
-        } else {
-          alertify.alert(
-            gettext('Backup Error'),
-            gettext('Please select server or child node from the browser tree.')
-          );
-          break;
-        }
-      }
-
-      if (!server_data) {
-        return;
-      }
-
-      var module = 'paths',
-        preference_name = 'pg_bin_dir',
-        msg = gettext('Please configure the PostgreSQL Binary Path in the Preferences dialog.');
-
-      if ((server_data.type && server_data.type == 'ppas') ||
-        server_data.server_type == 'ppas') {
-        preference_name = 'ppas_bin_dir';
-        msg = gettext('Please configure the EDB Advanced Server Binary Path in the Preferences dialog.');
-      }
-
-      var preference = pgBrowser.get_preference(module, preference_name);
-
-      if (preference) {
-        if (!preference.value) {
-          alertify.alert(gettext('Configuration required'), msg);
-          return;
-        }
-      } else {
-        alertify.alert(
-          gettext('Backup Error'),
-          S(gettext('Failed to load preference %s of module %s')).sprintf(preference_name, module).value()
-        );
-        return;
-      }
-
-      var of_type = undefined;
-
-      // Set Notes according to type of backup
-      if (!_.isUndefined(params['globals']) && params['globals']) {
-        of_type = 'globals';
-      } else {
-        of_type = 'server';
-      }
-
-      var DialogName = 'BackupDialog_' + of_type,
-        DialogTitle = ((of_type == 'globals') ?
-          gettext('Backup Globals...') :
-          gettext('Backup Server...'));
-
-      if (!alertify[DialogName]) {
-        alertify.dialog(DialogName, function factory() {
-          return {
-            main: function(title) {
-              this.set('title', title);
-            },
-            build: function() {
-              alertify.pgDialogBuild.apply(this);
-            },
-            setup: function() {
-              return {
-                buttons: [{
-                  text: '',
-                  className: 'btn btn-default pull-left fa fa-lg fa-info',
-                  attrs: {
-                    name: 'object_help',
-                    type: 'button',
-                    url: 'backup.html',
-                    label: gettext('Backup'),
-                  },
-                }, {
-                  text: '',
-                  key: 112,
-                  className: 'btn btn-default pull-left fa fa-lg fa-question',
-                  attrs: {
-                    name: 'dialog_help',
-                    type: 'button',
-                    label: gettext('Backup'),
-                    url: url_for('help.static', {
-                      'filename': 'backup_dialog.html',
-                    }),
-                  },
-                }, {
-                  text: gettext('Backup'),
-                  key: 13,
-                  className: 'btn btn-primary fa fa-lg fa-save pg-alertify-button',
-                  'data-btn-name': 'backup',
-                }, {
-                  text: gettext('Cancel'),
-                  key: 27,
-                  className: 'btn btn-danger fa fa-lg fa-times pg-alertify-button',
-                  'data-btn-name': 'cancel',
-                }],
-                // Set options for dialog
-                options: {
-                  title: DialogTitle,
-                  //disable both padding and overflow control.
-                  padding: !1,
-                  overflow: !1,
-                  model: 0,
-                  resizable: true,
-                  maximizable: true,
-                  pinnable: false,
-                  closableByDimmer: false,
-                  modal: false,
-                },
-              };
-            },
-            hooks: {
-              // Triggered when the dialog is closed
-              onclose: function() {
-                if (this.view) {
-                  // clear our backform model/view
-                  this.view.remove({
-                    data: true,
-                    internal: true,
-                    silent: true,
-                  });
-                }
-              },
-            },
-            prepare: function() {
-              var self = this;
-              // Disable Backup button until user provides Filename
-              this.__internal.buttons[2].element.disabled = true;
-
-              var $container = $('<div class=\'backup_dialog\'></div>');
-              // Find current/selected node
-              var t = pgBrowser.tree,
-                i = t.selected(),
-                d = i && i.length == 1 ? t.itemData(i) : undefined,
-                node = d && pgBrowser.Nodes[d._type];
-
-              if (!d)
-                return;
-              // Create treeInfo
-              var treeInfo = node.getTreeNodeHierarchy.apply(node, [i]);
-              // Instance of backbone model
-              var newModel = new BackupModel({
-                  type: of_type,
-                }, {
-                  node_info: treeInfo,
-                }),
-                fields = Backform.generateViewSchema(
-                  treeInfo, newModel, 'create', node, treeInfo.server, true
-                );
-
-              var view = this.view = new Backform.Dialog({
-                el: $container,
-                model: newModel,
-                schema: fields,
-              });
-              // Add our class to alertify
-              $(this.elements.body.childNodes[0]).addClass(
-                'alertify_tools_dialog_properties obj_properties'
-              );
-              // Render dialog
-              view.render();
-
-              this.elements.content.appendChild($container.get(0));
-
-              var container = view.$el.find('.tab-content:first > .tab-pane.active:first');
-              commonUtils.findAndSetFocus(container);
-
-              // Listen to model & if filename is provided then enable Backup button
-              this.view.model.on('change', function() {
-                if (!_.isUndefined(this.get('file')) && this.get('file') !== '') {
-                  this.errorModel.clear();
-                  self.__internal.buttons[2].element.disabled = false;
-                } else {
-                  self.__internal.buttons[2].element.disabled = true;
-                  this.errorModel.set('file', gettext('Please provide a filename'));
-                }
-              });
-            },
-            // Callback functions when click on the buttons of the Alertify dialogs
-            callback: function(e) {
-              // Fetch current server id
-              var t = pgBrowser.tree,
-                i = t.selected(),
-                d = i && i.length == 1 ? t.itemData(i) : undefined,
-                node = d && pgBrowser.Nodes[d._type];
-
-              if (e.button.element.name == 'dialog_help' || e.button.element.name == 'object_help') {
-                e.cancel = true;
-                pgBrowser.showHelp(e.button.element.name, e.button.element.getAttribute('url'),
-                  node, i, e.button.element.getAttribute('label'));
-                return;
-              }
-
-              if (e.button['data-btn-name'] === 'backup') {
-
-                if (!d)
-                  return;
-
-                var treeInfo = node.getTreeNodeHierarchy.apply(node, [i]);
-
-                var self = this,
-                  baseUrl = url_for('backup.create_server_job', {
-                    'sid': treeInfo.server._id,
-                  }),
-                  args = this.view.model.toJSON();
-
-                $.ajax({
-                  url: baseUrl,
-                  method: 'POST',
-                  data: {
-                    'data': JSON.stringify(args),
-                  },
-                  success: function(res) {
-                    if (res.success) {
-                      alertify.success(gettext('Backup job created.'), 5);
-                      pgBrowser.Events.trigger('pgadmin-bgprocess:created', self);
-                    } else {
-                      console.warn(res);
-                    }
-                  },
-                  error: function(xhr) {
-                    try {
-                      var err = $.parseJSON(xhr.responseText);
-                      alertify.alert(
-                        gettext('Backup job failed.'),
-                        err.errormsg
-                      );
-                    } catch (e) {
-                      console.warn(e.stack || e);
-                    }
-                  },
-                });
-              }
-            },
-          };
-        });
-      }
-      alertify[DialogName](true).resizeTo('60%', '50%');
+    start_backup_global_server: function(action, treeItem, params) {
+      let dialog = new globalBackupDialog.BackupDialog(
+        pgBrowser,
+        $,
+        alertify,
+        BackupModel
+      );
+      dialog.draw(action, treeItem, params);
     },
 
     // Callback to draw Backup Dialog for objects
     backup_objects: function(action, treeItem) {
-
-      var i = treeItem || pgBrowser.tree.selected(),
-        server_data = null;
-
-      while (i) {
-        var node_data = pgBrowser.tree.itemData(i);
-        if (node_data._type == 'server') {
-          server_data = node_data;
-          break;
-        }
-
-        if (pgBrowser.tree.hasParent(i)) {
-          i = $(pgBrowser.tree.parent(i));
-        } else {
-          alertify.alert(
-            gettext('Backup Error'),
-            gettext('Please select server or child node from tree.')
-          );
-          break;
-        }
-      }
-
-      if (!server_data) {
-        return;
-      }
-
-      var module = 'paths',
-        preference_name = 'pg_bin_dir',
-        msg = gettext('Please set binary path for PostgreSQL Server from preferences.');
-
-      if ((server_data.type && server_data.type == 'ppas') ||
-        server_data.server_type == 'ppas') {
-        preference_name = 'ppas_bin_dir';
-        msg = gettext('Please set binary path for EDB Postgres Advanced Server from preferences.');
-      }
-
-      var preference = pgBrowser.get_preference(module, preference_name);
-
-      if (preference) {
-        if (!preference.value) {
-          alertify.alert(gettext('Configuration required'), msg);
-          return;
-        }
-      } else {
-        alertify.alert(
-          gettext('Backup Error'),
-          S(gettext('Failed to load preference %s of module %s')).sprintf(preference_name, module).value()
-        );
-        return;
-      }
-
-      var title = S(gettext('Backup (%s: %s)')),
-        tree = pgBrowser.tree,
-        item = treeItem || tree.selected(),
-        data = item && item.length == 1 && tree.itemData(item),
-        node = data && data._type && pgBrowser.Nodes[data._type];
-
-      if (!node)
-        return;
-
-      title = title.sprintf(node.label, data.label).value();
-
-      if (!alertify.backup_objects) {
-        // Create Dialog title on the fly with node details
-        alertify.dialog('backup_objects', function factory() {
-          return {
-            main: function(title) {
-              this.set('title', title);
-            },
-            build: function() {
-              alertify.pgDialogBuild.apply(this);
-            },
-            setup: function() {
-              return {
-                buttons: [{
-                  text: '',
-                  className: 'btn btn-default pull-left fa fa-lg fa-info',
-                  attrs: {
-                    name: 'object_help',
-                    type: 'button',
-                    url: 'backup.html',
-                    label: gettext('Backup'),
-                  },
-                }, {
-                  text: '',
-                  key: 112,
-                  className: 'btn btn-default pull-left fa fa-lg fa-question',
-                  attrs: {
-                    name: 'dialog_help',
-                    type: 'button',
-                    label: gettext('Backup'),
-                    url: url_for('help.static', {
-                      'filename': 'backup_dialog.html',
-                    }),
-                  },
-                }, {
-                  text: gettext('Backup'),
-                  key: 13,
-                  className: 'btn btn-primary fa fa-lg fa-save pg-alertify-button',
-                  'data-btn-name': 'backup',
-                }, {
-                  text: gettext('Cancel'),
-                  key: 27,
-                  className: 'btn btn-danger fa fa-lg fa-times pg-alertify-button',
-                  'data-btn-name': 'cancel',
-                }],
-                // Set options for dialog
-                options: {
-                  title: title,
-                  //disable both padding and overflow control.
-                  padding: !1,
-                  overflow: !1,
-                  model: 0,
-                  resizable: true,
-                  maximizable: true,
-                  pinnable: false,
-                  closableByDimmer: false,
-                  modal: false,
-                },
-              };
-            },
-            hooks: {
-              // triggered when the dialog is closed
-              onclose: function() {
-                if (this.view) {
-                  this.view.remove({
-                    data: true,
-                    internal: true,
-                    silent: true,
-                  });
-                }
-              },
-            },
-            prepare: function() {
-              var self = this;
-              // Disable Backup button until user provides Filename
-              this.__internal.buttons[2].element.disabled = true;
-              var $container = $('<div class=\'backup_dialog\'></div>');
-              var t = pgBrowser.tree,
-                i = t.selected(),
-                d = i && i.length == 1 ? t.itemData(i) : undefined,
-                node = d && pgBrowser.Nodes[d._type];
-
-              if (!d)
-                return;
-
-              var treeInfo = node.getTreeNodeHierarchy.apply(node, [i]);
-
-              var newModel = new BackupObjectModel({}, {
-                  node_info: treeInfo,
-                }),
-                fields = Backform.generateViewSchema(
-                  treeInfo, newModel, 'create', node, treeInfo.server, true
-                );
-
-              var view = this.view = new Backform.Dialog({
-                el: $container,
-                model: newModel,
-                schema: fields,
-              });
-
-              $(this.elements.body.childNodes[0]).addClass(
-                'alertify_tools_dialog_properties obj_properties'
-              );
-
-              view.render();
-
-              this.elements.content.appendChild($container.get(0));
-
-              if(view) {
-                view.$el.attr('tabindex', -1);
-                // var dialogTabNavigator = pgBrowser.keyboardNavigation.getDialogTabNavigator(view);
-                pgBrowser.keyboardNavigation.getDialogTabNavigator(view);
-                var container = view.$el.find('.tab-content:first > .tab-pane.active:first');
-                commonUtils.findAndSetFocus(container);
-              }
-              // Listen to model & if filename is provided then enable Backup button
-              this.view.model.on('change', function() {
-                if (!_.isUndefined(this.get('file')) && this.get('file') !== '') {
-                  this.errorModel.clear();
-                  self.__internal.buttons[2].element.disabled = false;
-                } else {
-                  self.__internal.buttons[2].element.disabled = true;
-                  this.errorModel.set('file', gettext('Please provide filename'));
-                }
-              });
-
-            },
-            // Callback functions when click on the buttons of the Alertify dialogs
-            callback: function(e) {
-              // Fetch current server id
-              var t = pgBrowser.tree,
-                i = t.selected(),
-                d = i && i.length == 1 ? t.itemData(i) : undefined,
-                node = d && pgBrowser.Nodes[d._type];
-
-              if (e.button.element.name == 'dialog_help' || e.button.element.name == 'object_help') {
-                e.cancel = true;
-                pgBrowser.showHelp(e.button.element.name, e.button.element.getAttribute('url'),
-                  node, i, e.button.element.getAttribute('label'));
-                return;
-              }
-
-              if (e.button['data-btn-name'] === 'backup') {
-                if (!d)
-                  return;
-
-                var treeInfo = node.getTreeNodeHierarchy.apply(node, [i]);
-
-                // Set current database into model
-                this.view.model.set('database', treeInfo.database._label);
-
-                // We will remove once object tree is implemented
-                // If selected node is Schema then add it in model
-                if (d._type == 'schema') {
-                  var schemas = [];
-                  schemas.push(d._label);
-                  this.view.model.set('schemas', schemas);
-                }
-                // If selected node is Table then add it in model along with
-                // its schema
-                if (d._type == 'table') {
-                  this.view.model.set(
-                    'tables', [
-                      [treeInfo.schema._label, d._label],
-                    ]
-                  );
-                }
-
-                // Remove ratio attribute from model if it has empty string.
-                // The valid value can be between 0 to 9.
-                if (_.isEmpty(this.view.model.get('ratio'))) {
-                  this.view.model.unset('ratio');
-                }
-
-                var self = this,
-                  baseUrl = url_for('backup.create_object_job', {
-                    'sid': treeInfo.server._id,
-                  }),
-                  args = this.view.model.toJSON();
-
-                $.ajax({
-                  url: baseUrl,
-                  method: 'POST',
-                  data: {
-                    'data': JSON.stringify(args),
-                  },
-                  success: function(res) {
-                    if (res.success) {
-                      alertify.success(gettext('Backup job created.'), 5);
-                      pgBrowser.Events.trigger('pgadmin-bgprocess:created', self);
-                    }
-                  },
-                  error: function(xhr) {
-                    try {
-                      var err = $.parseJSON(xhr.responseText);
-                      alertify.alert(
-                        gettext('Backup job failed.'),
-                        err.errormsg
-                      );
-                    } catch (e) {
-                      console.warn(e.stack || e);
-                    }
-                  },
-                });
-              }
-            },
-          };
-        });
-      }
-      alertify.backup_objects(title).resizeTo('65%', '60%');
+      let dialog = new globalBackupDialog.BackupDialog(
+        pgBrowser,
+        $,
+        alertify,
+        BackupObjectModel
+      );
+      dialog.draw(action, treeItem, null);
     },
   };
   return pgBrowser.Backup;
diff --git a/web/pgadmin/tools/datagrid/static/js/datagrid.js b/web/pgadmin/tools/datagrid/static/js/datagrid.js
index 473f20ad..d8d1c902 100644
--- a/web/pgadmin/tools/datagrid/static/js/datagrid.js
+++ b/web/pgadmin/tools/datagrid/static/js/datagrid.js
@@ -1,10 +1,13 @@
 define('pgadmin.datagrid', [
   'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
   'pgadmin.alertifyjs', 'sources/pgadmin', 'bundled_codemirror',
-  'sources/sqleditor_utils', 'backbone', 'wcdocker',
+  'sources/sqleditor_utils', 'backbone', 'sources/datagrid/show_data',
+  'sources/datagrid/get_panel_title',
+  'sources/datagrid/show_query_tool',
+  'wcdocker',
 ], function(
   gettext, url_for, $, _, alertify, pgAdmin, codemirror, sqlEditorUtils,
-  Backbone
+  Backbone, showData, panelTitle, showQueryTool
 ) {
   // Some scripts do export their object in the window only.
   // Generally the one, which do no have AMD support.
@@ -161,55 +164,7 @@ define('pgadmin.datagrid', [
 
       // This is a callback function to show data when user click on menu item.
       show_data_grid: function(data, i) {
-        var self = this,
-          d = pgAdmin.Browser.tree.itemData(i);
-        if (d === undefined) {
-          alertify.alert(
-            gettext('Data Grid Error'),
-            gettext('No object selected.')
-          );
-          return;
-        }
-
-        // Get the parent data from the tree node hierarchy.
-        var node = pgBrowser.Nodes[d._type],
-          parentData = node.getTreeNodeHierarchy(i);
-
-        // If server, database or schema is undefined then return from the function.
-        if (parentData.server === undefined || parentData.database === undefined) {
-          return;
-        }
-        // If schema, view, catalog object all are undefined then return from the function.
-        if (parentData.schema === undefined && parentData.view === undefined &&
-             parentData.catalog === undefined) {
-          return;
-        }
-
-        var nsp_name = '';
-
-        if (parentData.schema != undefined) {
-          nsp_name = parentData.schema.label;
-        }
-        else if (parentData.view != undefined) {
-          nsp_name = parentData.view.label;
-        }
-        else if (parentData.catalog != undefined) {
-          nsp_name = parentData.catalog.label;
-        }
-        var url_params = {
-          'cmd_type': data.mnuid,
-          'obj_type': d._type,
-          'sgid': parentData.server_group._id,
-          'sid': parentData.server._id,
-          'did': parentData.database._id,
-          'obj_id': d._id,
-        };
-
-        var baseUrl = url_for('datagrid.initialize_datagrid', url_params);
-        var grid_title = parentData.server.label + ' - ' + parentData.database.label + ' - '
-                        + nsp_name + '.' + d.label;
-
-        self.create_transaction(baseUrl, null, 'false', parentData.server.server_type, '', grid_title, '');
+        showData.showDataGrid(this, pgBrowser, alertify, data, i);
       },
 
       // This is a callback function to show filtered data when user click on menu item.
@@ -382,63 +337,12 @@ define('pgadmin.datagrid', [
       },
 
       get_panel_title: function() {
-        // Get the parent data from the tree node hierarchy.
-        var tree = pgAdmin.Browser.tree,
-          selected_item = tree.selected(),
-          item_data = tree.itemData(selected_item);
-
-        var node = pgBrowser.Nodes[item_data._type],
-          parentData = node.getTreeNodeHierarchy(selected_item);
-
-        // If server, database is undefined then return from the function.
-        if (parentData.server === undefined) {
-          return;
-        }
-        // If Database is not available then use default db
-        var db_label = parentData.database ? parentData.database.label
-                                           : parentData.server.db;
-
-        var grid_title = db_label + ' on ' + parentData.server.user.name + '@' +
-                parentData.server.label;
-        return grid_title;
+        return panelTitle.getPanelTitle(pgBrowser);
       },
       // This is a callback function to show query tool when user click on menu item.
-      show_query_tool: function(url, i, panel_title) {
-        var sURL = url || '',
-          d = pgAdmin.Browser.tree.itemData(i);
-
-        panel_title = panel_title || '';
-        if (d === undefined) {
-          alertify.alert(
-            gettext('Query Tool Error'),
-            gettext('No object selected.')
-          );
-          return;
-        }
-
-        // Get the parent data from the tree node hierarchy.
-        var node = pgBrowser.Nodes[d._type],
-          parentData = node.getTreeNodeHierarchy(i);
-
-        // If server, database is undefined then return from the function.
-        if (parentData.server === undefined) {
-          return;
-        }
-
-        var url_params = {
-            'sgid': parentData.server_group._id,
-            'sid': parentData.server._id,
-          },
-          url_endpoint = 'datagrid.initialize_query_tool';
-        // If database not present then use Maintenance database
-        // We will handle this at server side
-        if (parentData.database) {
-          url_params['did'] = parentData.database._id;
-          url_endpoint = 'datagrid.initialize_query_tool_with_did';
-        }
-        var baseUrl = url_for(url_endpoint, url_params);
-
-        this.create_transaction(baseUrl, null, 'true', parentData.server.server_type, sURL, panel_title, '', false);
+      show_query_tool: function(url, aciTreeIdentifier, panelTitle) {
+        showQueryTool.showQueryTool(this, pgBrowser, alertify, url,
+          aciTreeIdentifier, panelTitle);
       },
       create_transaction: function(baseUrl, target, is_query_tool, server_type, sURL, panel_title, sql_filter, recreate) {
         var self = this;
diff --git a/web/regression/javascript/backup/backup_dialog_spec.js b/web/regression/javascript/backup/backup_dialog_spec.js
new file mode 100644
index 00000000..e272f094
--- /dev/null
+++ b/web/regression/javascript/backup/backup_dialog_spec.js
@@ -0,0 +1,651 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+import {BackupDialog} from '../../../pgadmin/static/js/backup/backup_dialog';
+import {TreeFake} from '../tree/tree_fake';
+import MockAdapter from 'axios-mock-adapter';
+import axios from 'axios/index';
+import {FakeModel} from '../fake_model';
+import {DialogWrapper} from "../../../pgadmin/static/js/backup/backup_dialog_wrapper";
+
+const context = describe;
+
+describe('ObjectBackupDialog', () => {
+  let backupDialog;
+  let backupNode;
+  let pgBrowser;
+  let jquerySpy;
+  let alertifySpy;
+  let backupModelSpy;
+  let backform;
+
+
+  let rootNode;
+  let serverTreeNode;
+  let databaseTreeNode;
+  let ppasServerTreeNode;
+  let noDataNode;
+
+  beforeEach(() => {
+    pgBrowser = {
+      treeMenu: new TreeFake(),
+      Nodes: {
+        server: {
+          hasId: true,
+          label: 'server',
+          getTreeNodeHierarchy: jasmine.createSpy('server.getTreeNodeHierarchy'),
+        },
+        database: {
+          hasId: true,
+          label: 'database',
+          getTreeNodeHierarchy: jasmine.createSpy('db.getTreeNodeHierarchy'),
+        },
+        schema: {
+          hasId: true,
+          label: 'schema',
+          getTreeNodeHierarchy: jasmine.createSpy('db.getTreeNodeHierarchy'),
+        },
+      }
+    };
+    pgBrowser.Nodes.server.hasId = true;
+    pgBrowser.Nodes.database.hasId = true;
+    jquerySpy = jasmine.createSpy('jquerySpy');
+    backupModelSpy = jasmine.createSpy('backupModelSpy');
+    backupNode = {};
+    backform = jasmine.createSpyObj('backform', ['generateViewSchema', 'Dialog']);
+
+    rootNode = pgBrowser.treeMenu.addNewNode('level1', {}, []);
+    serverTreeNode = pgBrowser.treeMenu.addNewNode('level1.1', {
+      _type: 'server',
+      _id: 10,
+    }, ['level1']);
+    databaseTreeNode = pgBrowser.treeMenu.addNewNode(
+      'level1.1.1', {
+        _type: 'database',
+        _id: 11,
+        label: 'some_database',
+        _label: 'some_database_label',
+      }, ['level1', 'level1.1']);
+    ppasServerTreeNode = pgBrowser.treeMenu.addNewNode('level1.2', {
+      _type: 'server',
+      server_type: 'ppas',
+    }, ['level1']);
+    pgBrowser.treeMenu.addNewNode('level3', {}, ['level1', 'level1.2']);
+    noDataNode = pgBrowser.treeMenu.addNewNode(
+      'level3.1', undefined, ['level1', 'level1.2', 'level3']);
+  });
+
+  describe('#draw', () => {
+    beforeEach(() => {
+      alertifySpy = jasmine.createSpyObj('alertify', ['alert', 'dialog']);
+      alertifySpy['backup_objects'] = jasmine.createSpy('backup_objects');
+      backupDialog = new BackupDialog(
+        pgBrowser,
+        jquerySpy,
+        alertifySpy,
+        backupModelSpy
+      );
+
+      pgBrowser.get_preference = jasmine.createSpy('get_preferences');
+    });
+
+    context('there are no ancestors of the type server', () => {
+      it('does not create a dialog', () => {
+        pgBrowser.treeMenu.setSelectedNode([rootNode]);
+        backupDialog.draw(null, null, null);
+        expect(alertifySpy['backup_objects']).not.toHaveBeenCalled();
+      });
+
+      it('display an alert with a Backup Error', () => {
+        backupDialog.draw(null, [rootNode], null);
+        expect(alertifySpy.alert).toHaveBeenCalledWith(
+          'Backup Error',
+          'Please select server or child node from the browser tree.'
+        );
+      });
+    });
+
+    context('there is an ancestor of the type server', () => {
+      context('no preference can be found', () => {
+        beforeEach(() => {
+          pgBrowser.get_preference.and.returnValue(undefined);
+        });
+
+        context('server is a ppas server', () => {
+          it('display an alert with "Backup Error"', () => {
+            backupDialog.draw(null, [serverTreeNode], null);
+            expect(alertifySpy.alert).toHaveBeenCalledWith(
+              'Backup Error',
+              'Failed to load preference pg_bin_dir of module paths'
+            );
+          });
+        });
+
+        context('server is not a ppas server', () => {
+          it('display an alert with "Backup Error"', () => {
+            backupDialog.draw(null, [ppasServerTreeNode], null);
+            expect(alertifySpy.alert).toHaveBeenCalledWith(
+              'Backup Error',
+              'Failed to load preference ppas_bin_dir of module paths'
+            );
+          });
+        });
+      });
+
+      context('preference can be found', () => {
+        context('binary folder is not configured', () => {
+          beforeEach(() => {
+            pgBrowser.get_preference.and.returnValue({});
+          });
+
+          context('server is a ppas server', () => {
+            it('display an alert with "Configuration required"', () => {
+              backupDialog.draw(null, [serverTreeNode], null);
+              expect(alertifySpy.alert).toHaveBeenCalledWith(
+                'Configuration required',
+                'Please configure the PostgreSQL Binary Path in the Preferences dialog.'
+              );
+            });
+          });
+
+          context('server is not a ppas server', () => {
+            it('display an alert with "Configuration required"', () => {
+              backupDialog.draw(null, [ppasServerTreeNode], null);
+              expect(alertifySpy.alert).toHaveBeenCalledWith(
+                'Configuration required',
+                'Please configure the EDB Advanced Server Binary Path in the Preferences dialog.'
+              );
+            });
+          });
+        });
+
+        context('binary folder is configured', () => {
+          let backupDialogResizeToSpy;
+          let serverResizeToSpy;
+          beforeEach(() => {
+            backupDialogResizeToSpy = jasmine.createSpyObj('backupDialogResizeToSpy', ['resizeTo']);
+            alertifySpy['backup_objects'].and
+              .returnValue(backupDialogResizeToSpy);
+            pgBrowser.get_preference.and.returnValue({value: '/some/path'});
+          });
+
+          it('displays the dialog', () => {
+            backupDialog.draw(null, [serverTreeNode], null);
+            expect(alertifySpy['backup_objects']).toHaveBeenCalledWith(true);
+            expect(backupDialogResizeToSpy.resizeTo).toHaveBeenCalledWith('60%', '50%');
+          });
+        });
+      });
+    });
+  });
+
+  describe('#dialogFactory', () => {
+    describe('#prepare', () => {
+      let backupJQueryContainer;
+      let viewSchema;
+      let generatedBackupModel;
+      let backupNodeChildNode;
+      beforeEach(() => {
+        backupJQueryContainer = jasmine.createSpyObj('backupJQueryContainer', ['get']);
+        backupJQueryContainer.get.and.returnValue(backupJQueryContainer);
+        viewSchema = {};
+        generatedBackupModel = {};
+        backupNode.__internal = {
+          buttons: [
+            {}, {},
+            {
+              element: {
+                disabled: false,
+              }
+            }
+          ]
+        };
+        backupNode.elements = {
+          body: {
+            childNodes: [
+              {}
+            ]
+          },
+          content: jasmine.createSpyObj('content', ['appendChild']),
+        };
+
+        backform.generateViewSchema.and.returnValue(viewSchema);
+        backupModelSpy.and.returnValue(generatedBackupModel);
+
+        backupDialog = new BackupDialog(
+          pgBrowser,
+          jquerySpy,
+          alertifySpy,
+          backupModelSpy,
+          backform
+        );
+
+        backupNodeChildNode = jasmine.createSpyObj('something', ['addClass']);
+
+        jquerySpy.and.callFake((selector) => {
+          if (selector === '<div class=\'backup_dialog\'></div>') {
+            return backupJQueryContainer;
+          } else if (selector === backupNode.elements.body.childNodes[0]) {
+            return backupNodeChildNode;
+          }
+        })
+      });
+
+      context('selected tree node has no data', () => {
+        beforeEach(() => {
+          pgBrowser.treeMenu.setSelectedNode([noDataNode]);
+          const dialog = backupDialog.dialogFactory(null, 'backup_objects');
+          Object.assign(dialog, backupNode).prepare();
+        });
+
+        it('does not create a backform dialog', () => {
+          expect(backform.Dialog).not.toHaveBeenCalledWith();
+        });
+
+        it('disables the button submit button until a filename is selected', () => {
+          expect(backupNode.__internal.buttons[2].element.disabled).toBe(true);
+        });
+      });
+
+      context('no tree element is selected', () => {
+        beforeEach(() => {
+          const dialog = backupDialog.dialogFactory(null, 'backup_objects');
+          Object.assign(dialog, backupNode).prepare();
+        });
+
+        it('does not create a backform dialog', () => {
+          expect(backform.Dialog).not.toHaveBeenCalledWith();
+        });
+
+        it('disables the button submit button until a filename is selected', () => {
+          expect(backupNode.__internal.buttons[2].element.disabled).toBe(true);
+        });
+      });
+
+      context('tree element is selected', () => {
+        let treeHierarchyInformation;
+        let dialogSpy;
+        let dialog;
+        beforeEach(() => {
+          treeHierarchyInformation = {
+            database: {
+              _type: 'database',
+              _id: 11,
+              label: 'some_database',
+              _label: 'some_database_label',
+              priority: 0,
+            },
+            server: {
+              _type: 'server',
+              _id: 10,
+              priority: -1,
+            }
+          };
+          pgBrowser.treeMenu.setSelectedNode([{id: 'level1.1.1'}]);
+          pgBrowser.Nodes['database'].getTreeNodeHierarchy.and
+            .returnValue(treeHierarchyInformation);
+          dialogSpy = jasmine.createSpyObj('newView', ['render']);
+          dialogSpy.$el = jasmine.createSpyObj('$el', ['find']);
+          dialogSpy.model = jasmine.createSpyObj('model', ['on'])
+          dialogSpy.$el.find.and.returnValue([]);
+
+          backform.Dialog.and.returnValue(dialogSpy);
+
+          dialog = backupDialog.dialogFactory(null, 'backup_objects');
+          dialog.set = jasmine.createSpy('dialog.set');
+          Object.assign(dialog, backupNode).prepare();
+        });
+
+        it('creates a backform dialog and displays it', () => {
+          expect(backform.Dialog).toHaveBeenCalledWith({
+            el: backupJQueryContainer,
+            model: generatedBackupModel,
+            schema: viewSchema,
+          });
+
+          expect(dialogSpy.render).toHaveBeenCalled();
+        });
+
+        it('add alertify classes to backup node childnode', () => {
+          expect(backupNodeChildNode.addClass)
+            .toHaveBeenCalledWith('alertify_tools_dialog_properties obj_properties');
+        });
+
+        it('disables the button submit button until a filename is selected', () => {
+          expect(backupNode.__internal.buttons[2].element.disabled).toBe(true);
+        });
+
+        it('generates a new backup model', () => {
+          expect(backupModelSpy).toHaveBeenCalledWith(
+            {},
+            {node_info: treeHierarchyInformation}
+          );
+        });
+
+        it('add the new dialog to the backup node HTML', () => {
+          expect(backupNode.elements.content.appendChild).toHaveBeenCalledWith(backupJQueryContainer);
+        });
+
+        it('sets the title to "Backup (database: some_database)"', () => {
+          expect(dialog.set).toHaveBeenCalledWith('title', 'Backup (database: some_database)');
+        });
+      });
+    });
+
+    describe('#onButtonClicked', () => {
+      let backupJQueryContainer;
+      let backupNodeChildNode;
+      let dialog;
+      let networkMock;
+
+      beforeEach(() => {
+        networkMock = new MockAdapter(axios);
+        backupJQueryContainer = jasmine.createSpyObj('backupJQueryContainer', ['get']);
+        backupJQueryContainer.get.and.returnValue(backupJQueryContainer);
+        pgBrowser.showHelp = jasmine.createSpy('showHelp');
+        alertifySpy = jasmine.createSpyObj('alertify', ['success', 'alert']);
+
+        backupDialog = new BackupDialog(
+          pgBrowser,
+          jquerySpy,
+          alertifySpy,
+          backupModelSpy,
+          backform
+        );
+
+        backupNodeChildNode = jasmine.createSpyObj('something', ['addClass']);
+
+        dialog = Object.assign(
+          backupDialog.dialogFactory(null, 'backup_objects'),
+          backupNode
+        );
+      });
+
+      afterEach(() => {
+        networkMock.restore();
+      });
+
+      context('dialog help button was pressed', () => {
+        let networkCalled;
+        beforeEach(() => {
+          pgBrowser.treeMenu.setSelectedNode([{id: 'level1.1.1'}]);
+          networkCalled = false;
+          networkMock.onAny(/.+/).reply(() => {
+            networkCalled = true;
+            return [200, {}];
+          });
+
+          const event = {
+            button: {
+              element: {
+                name: 'dialog_help',
+                getAttribute: (attributeName) => {
+                  if (attributeName === 'url') {
+                    return 'http://someurl';
+                  } else if (attributeName === 'label') {
+                    return 'some label';
+                  }
+                }
+              }
+            }
+          };
+          dialog.callback(event);
+        });
+
+        it('displays help for dialog', () => {
+          expect(pgBrowser.showHelp).toHaveBeenCalledWith(
+            'dialog_help',
+            'http://someurl',
+            pgBrowser.Nodes['database'],
+            databaseTreeNode,
+            'some label'
+          )
+        });
+
+        it('does not start the backup', () => {
+          expect(networkCalled).toBe(false);
+        });
+      });
+
+      context('object help button was pressed', () => {
+        let networkCalled;
+        beforeEach(() => {
+          pgBrowser.treeMenu.setSelectedNode([{id: 'level1.1.1'}]);
+          networkCalled = false;
+          networkMock.onAny(/.+/).reply(() => {
+            networkCalled = true;
+            return [200, {}];
+          });
+
+          const event = {
+            button: {
+              element: {
+                name: 'object_help',
+                getAttribute: (attributeName) => {
+                  if (attributeName === 'url') {
+                    return 'http://someurl';
+                  } else if (attributeName === 'label') {
+                    return 'some label';
+                  }
+                }
+              }
+            }
+          };
+          dialog.callback(event);
+        });
+
+        it('displays help for dialog', () => {
+          expect(pgBrowser.showHelp).toHaveBeenCalledWith(
+            'object_help',
+            'http://someurl',
+            pgBrowser.Nodes['database'],
+            databaseTreeNode,
+            'some label'
+          )
+        });
+
+        it('does not start the backup', () => {
+          expect(networkCalled).toBe(false);
+        });
+      });
+
+      context('backup button was pressed', () => {
+        context('no tree node is selected', () => {
+          let networkCalled;
+          beforeEach(() => {
+            networkCalled = false;
+            networkMock.onPost('/backup/job/10/object').reply(() => {
+              networkCalled = true;
+              return [200, {}];
+            });
+
+            const event = {
+              button: {
+                'data-btn-name': 'backup',
+                element: {
+                  getAttribute: (attributeName) => {
+                    if (attributeName === 'url') {
+                      return 'http://someurl';
+                    } else if (attributeName === 'label') {
+                      return 'some label';
+                    }
+                  }
+                }
+              }
+            };
+            dialog.callback(event);
+          });
+
+          it('does not start the backup', () => {
+            expect(networkCalled).toBe(false);
+          });
+        });
+
+        context('selected node has no data', () => {
+          let networkCalled;
+          beforeEach(() => {
+            pgBrowser.treeMenu.setSelectedNode([noDataNode]);
+            networkCalled = false;
+            networkMock.onPost('/backup/job/10/object').reply(() => {
+              networkCalled = true;
+              return [200, {}];
+            });
+
+            const event = {
+              button: {
+                'data-btn-name': 'backup',
+                element: {
+                  getAttribute: (attributeName) => {
+                    if (attributeName === 'url') {
+                      return 'http://someurl';
+                    } else if (attributeName === 'label') {
+                      return 'some label';
+                    }
+                  }
+                }
+              }
+            };
+            dialog.callback(event);
+          });
+
+          it('does not start the backup', () => {
+            expect(networkCalled).toBe(false);
+          });
+        });
+
+        context('selected node has data', () => {
+          beforeEach(() => {
+            pgBrowser.treeMenu.setSelectedNode([{id: 'level1.1.1'}]);
+            pgBrowser.Events = jasmine.createSpyObj('Events', ['trigger']);
+
+            backupNode.view = {
+              model: new FakeModel(),
+            };
+            backupNode.view.model.set('dqoute', false);
+            backupNode.view.model.set('file', 'test');
+            backupNode.view.model.set('verbose', true);
+
+            const event = {
+              button: {
+                'data-btn-name': 'backup',
+                element: {
+                  getAttribute: (attributeName) => {
+                    if (attributeName === 'url') {
+                      return 'http://someurl';
+                    } else if (attributeName === 'label') {
+                      return 'some label';
+                    }
+                  }
+                }
+              }
+            };
+            Object.assign(dialog, backupNode).callback(event);
+          });
+          context('creation of backup job successfull', () => {
+            let dataSentToServer;
+            beforeEach(() => {
+              networkMock.onPost('/backup/job/10/object').reply((request) => {
+                dataSentToServer = request.data;
+                return [200, {}];
+              });
+            });
+
+            it('creates an success alert box', (done) => {
+              setTimeout(() => {
+                expect(alertifySpy.success).toHaveBeenCalledWith('Backup job' +
+                  ' created.', 5);
+                done();
+              }, 0);
+            });
+
+            it('trigger background process', (done) => {
+              setTimeout(() => {
+                expect(pgBrowser.Events.trigger).toHaveBeenCalledWith(
+                  'pgadmin-bgprocess:created',
+                  jasmine.anything()
+                );
+                done();
+              }, 0);
+            });
+
+            it('send correct data to server', () => {
+              expect(dataSentToServer).toEqual(JSON.stringify({
+                dqoute: false,
+                file: 'test',
+                verbose: true,
+                database: 'some_database_label'
+              }));
+            });
+          });
+
+          context('error creating backup job', () => {
+            beforeEach(() => {
+              const response = {
+                errormsg: 'some error message'
+              };
+
+              networkMock.onPost('/backup/job/10/object').reply(() => {
+                return [500, response];
+              });
+            });
+
+            it('creates an alert box', (done) => {
+              setTimeout(() => {
+                expect(alertifySpy.alert).toHaveBeenCalledWith('Backup job' +
+                  ' failed.', 'some error message');
+                done();
+              }, 0);
+            });
+          });
+        });
+      });
+    });
+  });
+
+  describe('BackupDialogWrapper', () => {
+    describe('#setExtraParameters', () => {
+      let model;
+      let dialog;
+      let schemaNode;
+      let tableNode;
+      beforeEach(() => {
+        model = new FakeModel();
+        dialog = new DialogWrapper({}, {
+          pgBrowser: pgBrowser,
+        }, '', 'backup_objects');
+        dialog.view = {
+          model: model,
+        };
+        schemaNode = pgBrowser.treeMenu.addNewNode(
+          'schema', {
+            _label: 'schema_label',
+            _type: 'schema',
+          }, ['level1', 'level1.1', 'level1.1.1']);
+        tableNode = pgBrowser.treeMenu.addNewNode(
+          'table', {
+            _label: 'table_label',
+            _type: 'table',
+          }, ['level1', 'level1.1', 'level1.1.1', 'schema']);
+      });
+
+      context('type is schema', () => {
+        it('add schema array to the model', () => {
+          dialog.setExtraParameters(schemaNode);
+          expect(model.get('schemas')).toEqual(['schema_label']);
+        });
+      });
+
+      context('type is table', () => {
+        it('add schema and table to the model', () => {
+          dialog.setExtraParameters(tableNode);
+          expect(model.get('tables')).toEqual([['schema_label', 'table_label']]);
+        });
+      });
+    });
+  });
+});
diff --git a/web/regression/javascript/backup/global_server_backup_dialog_spec.js b/web/regression/javascript/backup/global_server_backup_dialog_spec.js
new file mode 100644
index 00000000..0b229038
--- /dev/null
+++ b/web/regression/javascript/backup/global_server_backup_dialog_spec.js
@@ -0,0 +1,585 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+import {BackupDialog} from '../../../pgadmin/static/js/backup/backup_dialog';
+import {TreeFake} from '../tree/tree_fake';
+import MockAdapter from 'axios-mock-adapter';
+import axios from 'axios/index';
+import {FakeModel} from '../fake_model';
+
+const context = describe;
+
+describe('GlobalServerBackupDialog', () => {
+  let backupDialog;
+  let backupNode;
+  let pgBrowser;
+  let jquerySpy;
+  let alertifySpy;
+  let backupModelSpy;
+  let backform;
+
+
+  let rootNode;
+  let serverTreeNode;
+  let ppasServerTreeNode;
+  let noDataNode;
+
+  beforeEach(() => {
+    pgBrowser = {
+      treeMenu: new TreeFake(),
+      Nodes: {
+        server: jasmine.createSpyObj('Node[server]', ['getTreeNodeHierarchy']),
+      }
+    };
+    pgBrowser.Nodes.server.hasId = true;
+    jquerySpy = jasmine.createSpy('jquerySpy');
+    backupModelSpy = jasmine.createSpy('backupModelSpy');
+    backupNode = {};
+    backform = jasmine.createSpyObj('backform', ['generateViewSchema', 'Dialog']);
+
+    rootNode = pgBrowser.treeMenu.addNewNode('level1', {}, []);
+    serverTreeNode = pgBrowser.treeMenu.addNewNode('level1.1', {
+      _type: 'server',
+      _id: 10,
+    }, ['level1']);
+    ppasServerTreeNode = pgBrowser.treeMenu.addNewNode('level1.2', {
+      _type: 'server',
+      server_type: 'ppas',
+    }, ['level1']);
+    pgBrowser.treeMenu.addNewNode('level3', {}, ['level1', 'level1.2']);
+    noDataNode = pgBrowser.treeMenu.addNewNode(
+      'level3.1', undefined, ['level1', 'level1.2', 'level3']);
+  });
+
+  describe('#draw', () => {
+    beforeEach(() => {
+      alertifySpy = jasmine.createSpyObj('alertify', ['alert', 'dialog']);
+      alertifySpy['BackupDialog_globals'] = jasmine.createSpy('BackupDialog_globals');
+      alertifySpy['BackupDialog_server'] = jasmine.createSpy('BackupDialog_server');
+      backupDialog = new BackupDialog(
+        pgBrowser,
+        jquerySpy,
+        alertifySpy,
+        backupModelSpy
+      );
+
+      pgBrowser.get_preference = jasmine.createSpy('get_preferences');
+    });
+
+    context('there are no ancestors of the type server', () => {
+      it('does not create a dialog', () => {
+        pgBrowser.treeMenu.setSelectedNode([{id: 'level1'}]);
+        backupDialog.draw(null, null, null);
+        expect(alertifySpy['BackupDialog_globals']).not.toHaveBeenCalled();
+        expect(alertifySpy['BackupDialog_server']).not.toHaveBeenCalled();
+      });
+
+      it('display an alert with a Backup Error', () => {
+        backupDialog.draw(null, [rootNode], null);
+        expect(alertifySpy.alert).toHaveBeenCalledWith(
+          'Backup Error',
+          'Please select server or child node from the browser tree.'
+        );
+      });
+    });
+
+    context('there is an ancestor of the type server', () => {
+      context('no preference can be found', () => {
+        beforeEach(() => {
+          pgBrowser.get_preference.and.returnValue(undefined);
+        });
+
+        context('server is a ppas server', () => {
+          it('display an alert with "Backup Error"', () => {
+            backupDialog.draw(null, [serverTreeNode], null);
+            expect(alertifySpy.alert).toHaveBeenCalledWith(
+              'Backup Error',
+              'Failed to load preference pg_bin_dir of module paths'
+            );
+          });
+        });
+
+        context('server is not a ppas server', () => {
+          it('display an alert with "Backup Error"', () => {
+            backupDialog.draw(null, [ppasServerTreeNode], null);
+            expect(alertifySpy.alert).toHaveBeenCalledWith(
+              'Backup Error',
+              'Failed to load preference ppas_bin_dir of module paths'
+            );
+          });
+        });
+      });
+
+      context('preference can be found', () => {
+        context('binary folder is not configured', () => {
+          beforeEach(() => {
+            pgBrowser.get_preference.and.returnValue({});
+          });
+
+          context('server is a ppas server', () => {
+            it('display an alert with "Configuration required"', () => {
+              backupDialog.draw(null, [serverTreeNode], null);
+              expect(alertifySpy.alert).toHaveBeenCalledWith(
+                'Configuration required',
+                'Please configure the PostgreSQL Binary Path in the Preferences dialog.'
+              );
+            });
+          });
+
+          context('server is not a ppas server', () => {
+            it('display an alert with "Configuration required"', () => {
+              backupDialog.draw(null, [ppasServerTreeNode], null);
+              expect(alertifySpy.alert).toHaveBeenCalledWith(
+                'Configuration required',
+                'Please configure the EDB Advanced Server Binary Path in the Preferences dialog.'
+              );
+            });
+          });
+        });
+
+        context('binary folder is configured', () => {
+          let globalResizeToSpy;
+          let serverResizeToSpy;
+          beforeEach(() => {
+            globalResizeToSpy = jasmine.createSpyObj('globals', ['resizeTo']);
+            alertifySpy['BackupDialog_globals'].and
+              .returnValue(globalResizeToSpy);
+            serverResizeToSpy = jasmine.createSpyObj('server', ['resizeTo']);
+            alertifySpy['BackupDialog_server'].and
+              .returnValue(serverResizeToSpy);
+            pgBrowser.get_preference.and.returnValue({value: '/some/path'});
+          });
+
+          context('dialog for global backup', () => {
+            it('displays the dialog', () => {
+              backupDialog.draw(null, [serverTreeNode], {globals: true});
+              expect(alertifySpy['BackupDialog_globals']).toHaveBeenCalledWith(true);
+              expect(globalResizeToSpy.resizeTo).toHaveBeenCalledWith('60%', '50%');
+            });
+          });
+
+          context('dialog for global backup', () => {
+            it('displays the dialog', () => {
+              backupDialog.draw(null, [serverTreeNode], {server: true});
+              expect(alertifySpy['BackupDialog_server']).toHaveBeenCalledWith(true);
+              expect(serverResizeToSpy.resizeTo).toHaveBeenCalledWith('60%', '50%');
+            });
+          });
+        });
+      });
+    });
+  });
+
+  describe('#dialogFactory', () => {
+    describe('#prepare', () => {
+      let backupJQueryContainer;
+      let viewSchema;
+      let generatedBackupModel;
+      let backupNodeChildNode;
+      beforeEach(() => {
+        backupJQueryContainer = jasmine.createSpyObj('backupJQueryContainer', ['get']);
+        backupJQueryContainer.get.and.returnValue(backupJQueryContainer);
+        viewSchema = {};
+        generatedBackupModel = {};
+        backupNode.__internal = {
+          buttons: [
+            {}, {},
+            {
+              element: {
+                disabled: false,
+              }
+            }
+          ]
+        };
+        backupNode.elements = {
+          body: {
+            childNodes: [
+              {}
+            ]
+          },
+          content: jasmine.createSpyObj('content', ['appendChild']),
+        };
+
+        backform.generateViewSchema.and.returnValue(viewSchema);
+        backupModelSpy.and.returnValue(generatedBackupModel);
+
+        backupDialog = new BackupDialog(
+          pgBrowser,
+          jquerySpy,
+          alertifySpy,
+          backupModelSpy,
+          backform
+        );
+
+        backupNodeChildNode = jasmine.createSpyObj('something', ['addClass']);
+
+        jquerySpy.and.callFake((selector) => {
+          if (selector === '<div class=\'backup_dialog\'></div>') {
+            return backupJQueryContainer;
+          } else if (selector === backupNode.elements.body.childNodes[0]) {
+            return backupNodeChildNode;
+          }
+        })
+      });
+
+      context('selected tree node has no data', () => {
+        beforeEach(() => {
+          pgBrowser.treeMenu.setSelectedNode([noDataNode]);
+          const dialog = backupDialog.dialogFactory('Some title', 'globals');
+          Object.assign(dialog, backupNode).prepare();
+        });
+
+        it('does not create a backform dialog', () => {
+          expect(backform.Dialog).not.toHaveBeenCalledWith();
+        });
+
+        it('disables the button submit button until a filename is selected', () => {
+          expect(backupNode.__internal.buttons[2].element.disabled).toBe(true);
+        });
+      });
+
+      context('no tree element is selected', () => {
+        beforeEach(() => {
+          const dialog = backupDialog.dialogFactory('Some title', 'globals');
+          Object.assign(dialog, backupNode).prepare();
+        });
+
+        it('does not create a backform dialog', () => {
+          expect(backform.Dialog).not.toHaveBeenCalledWith();
+        });
+
+        it('disables the button submit button until a filename is selected', () => {
+          expect(backupNode.__internal.buttons[2].element.disabled).toBe(true);
+        });
+      });
+
+      context('tree element is selected', () => {
+        let treeHierarchyInformation;
+        let dialogSpy;
+        beforeEach(() => {
+          treeHierarchyInformation = {
+            server: {
+              _type: 'server',
+              _id: 10,
+              priority: 0,
+            }
+          };
+          pgBrowser.treeMenu.setSelectedNode([serverTreeNode]);
+          pgBrowser.Nodes['server'].getTreeNodeHierarchy.and
+            .returnValue(treeHierarchyInformation);
+          dialogSpy = jasmine.createSpyObj('newView', ['render']);
+          dialogSpy.$el = jasmine.createSpyObj('$el', ['find']);
+          dialogSpy.model = jasmine.createSpyObj('model', ['on'])
+          dialogSpy.$el.find.and.returnValue([]);
+
+          backform.Dialog.and.returnValue(dialogSpy);
+
+          const dialog = backupDialog.dialogFactory('Some title', 'globals');
+          Object.assign(dialog, backupNode).prepare();
+        });
+
+        it('creates a backform dialog and displays it', () => {
+          expect(backform.Dialog).toHaveBeenCalledWith({
+            el: backupJQueryContainer,
+            model: generatedBackupModel,
+            schema: viewSchema,
+          });
+
+          expect(dialogSpy.render).toHaveBeenCalled();
+        });
+
+        it('add alertify classes to backup node childnode', () => {
+          expect(backupNodeChildNode.addClass)
+            .toHaveBeenCalledWith('alertify_tools_dialog_properties obj_properties');
+        });
+
+        it('disables the button submit button until a filename is selected', () => {
+          expect(backupNode.__internal.buttons[2].element.disabled).toBe(true);
+        });
+
+        it('generates a new backup model', () => {
+          expect(backupModelSpy).toHaveBeenCalledWith(
+            {type: 'globals'},
+            {node_info: treeHierarchyInformation}
+          );
+        });
+
+        it('add the new dialog to the backup node HTML', () => {
+          expect(backupNode.elements.content.appendChild).toHaveBeenCalledWith(backupJQueryContainer);
+        });
+      });
+    });
+
+    describe('#onButtonClicked', () => {
+      let backupJQueryContainer;
+      let backupNodeChildNode;
+      let dialog;
+      let networkMock;
+
+      beforeEach(() => {
+        networkMock = new MockAdapter(axios);
+        backupJQueryContainer = jasmine.createSpyObj('backupJQueryContainer', ['get']);
+        backupJQueryContainer.get.and.returnValue(backupJQueryContainer);
+        pgBrowser.showHelp = jasmine.createSpy('showHelp');
+        alertifySpy = jasmine.createSpyObj('alertify', ['success', 'alert']);
+
+        backupDialog = new BackupDialog(
+          pgBrowser,
+          jquerySpy,
+          alertifySpy,
+          backupModelSpy,
+          backform
+        );
+
+        backupNodeChildNode = jasmine.createSpyObj('something', ['addClass']);
+
+        dialog = Object.assign(backupDialog.dialogFactory('Some title', 'server'), backupNode);
+      });
+
+      afterEach(() => {
+        networkMock.restore();
+      });
+
+      context('dialog help button was pressed', () => {
+        let networkCalled;
+        beforeEach(() => {
+          pgBrowser.treeMenu.setSelectedNode([serverTreeNode]);
+          networkCalled = false;
+          networkMock.onAny(/.+/).reply(() => {
+            networkCalled = true;
+            return [200, {}];
+          });
+
+          const event = {
+            button: {
+              element: {
+                name: 'dialog_help',
+                getAttribute: (attributeName) => {
+                  if (attributeName === 'url') {
+                    return 'http://someurl';
+                  } else if (attributeName === 'label') {
+                    return 'some label';
+                  }
+                }
+              }
+            }
+          };
+          dialog.callback(event);
+        });
+
+        it('displays help for dialog', () => {
+          expect(pgBrowser.showHelp).toHaveBeenCalledWith(
+            'dialog_help',
+            'http://someurl',
+            pgBrowser.Nodes['server'],
+            serverTreeNode,
+            'some label'
+          )
+        });
+
+        it('does not start the backup', () => {
+          expect(networkCalled).toBe(false);
+        });
+      });
+
+      context('object help button was pressed', () => {
+        let networkCalled;
+        beforeEach(() => {
+          pgBrowser.treeMenu.setSelectedNode([serverTreeNode]);
+          networkCalled = false;
+          networkMock.onAny(/.+/).reply(() => {
+            networkCalled = true;
+            return [200, {}];
+          });
+
+          const event = {
+            button: {
+              element: {
+                name: 'object_help',
+                getAttribute: (attributeName) => {
+                  if (attributeName === 'url') {
+                    return 'http://someurl';
+                  } else if (attributeName === 'label') {
+                    return 'some label';
+                  }
+                }
+              }
+            }
+          };
+          dialog.callback(event);
+        });
+
+        it('displays help for dialog', () => {
+          expect(pgBrowser.showHelp).toHaveBeenCalledWith(
+            'object_help',
+            'http://someurl',
+            pgBrowser.Nodes['server'],
+            serverTreeNode,
+            'some label'
+          )
+        });
+
+        it('does not start the backup', () => {
+          expect(networkCalled).toBe(false);
+        });
+      });
+
+      context('backup button was pressed', () => {
+        context('no tree node is selected', () => {
+          let networkCalled;
+          beforeEach(() => {
+            networkCalled = false;
+            networkMock.onAny(/.+/).reply(() => {
+              networkCalled = true;
+              return [200, {}];
+            });
+
+            const event = {
+              button: {
+                'data-btn-name': 'backup',
+                element: {
+                  getAttribute: (attributeName) => {
+                    if (attributeName === 'url') {
+                      return 'http://someurl';
+                    } else if (attributeName === 'label') {
+                      return 'some label';
+                    }
+                  }
+                }
+              }
+            };
+            dialog.callback(event);
+          });
+
+          it('does not start the backup', () => {
+            expect(networkCalled).toBe(false);
+          });
+        });
+
+        context('selected node has no data', () => {
+          let networkCalled;
+          beforeEach(() => {
+            pgBrowser.treeMenu.setSelectedNode([noDataNode]);
+            networkCalled = false;
+            networkMock.onAny(/.+/).reply(() => {
+              networkCalled = true;
+              return [200, {}];
+            });
+
+            const event = {
+              button: {
+                'data-btn-name': 'backup',
+                element: {
+                  getAttribute: (attributeName) => {
+                    if (attributeName === 'url') {
+                      return 'http://someurl';
+                    } else if (attributeName === 'label') {
+                      return 'some label';
+                    }
+                  }
+                }
+              }
+            };
+            dialog.callback(event);
+          });
+
+          it('does not start the backup', () => {
+            expect(networkCalled).toBe(false);
+          });
+        });
+
+        context('selected node has data', () => {
+          beforeEach(() => {
+            pgBrowser.treeMenu.setSelectedNode([serverTreeNode]);
+            pgBrowser.Events = jasmine.createSpyObj('Events', ['trigger']);
+
+            backupNode.view = {
+              model: new FakeModel(),
+            };
+            backupNode.view.model.set('dqoute', false);
+            backupNode.view.model.set('file', 'test');
+            backupNode.view.model.set('type', 'globals');
+            backupNode.view.model.set('verbose', true);
+
+            const event = {
+              button: {
+                'data-btn-name': 'backup',
+                element: {
+                  getAttribute: (attributeName) => {
+                    if (attributeName === 'url') {
+                      return 'http://someurl';
+                    } else if (attributeName === 'label') {
+                      return 'some label';
+                    }
+                  }
+                }
+              }
+            };
+            Object.assign(dialog, backupNode).callback(event);
+          });
+          context('creation of backup job successfull', () => {
+            let dataSentToServer;
+            beforeEach(() => {
+              networkMock.onPost('/backup/job/10').reply((request) => {
+                dataSentToServer = request.data;
+                return [200, {}];
+              });
+            });
+
+            it('creates an success alert box', (done) => {
+              setTimeout(() => {
+                expect(alertifySpy.success).toHaveBeenCalledWith('Backup job' +
+                  ' created.', 5);
+                done();
+              }, 0);
+            });
+
+            it('trigger background process', (done) => {
+              setTimeout(() => {
+                expect(pgBrowser.Events.trigger).toHaveBeenCalledWith(
+                  'pgadmin-bgprocess:created',
+                  jasmine.anything()
+                );
+                done();
+              }, 0);
+            });
+
+            it('send correct data to server', () => {
+              expect(dataSentToServer).toEqual(JSON.stringify({
+                dqoute: false,
+                file: 'test',
+                type: 'globals',
+                verbose: true
+              }));
+            });
+          });
+
+          context('error creating backup job', () => {
+            beforeEach(() => {
+              const response = {
+                errormsg: 'some error message'
+              };
+
+              networkMock.onPost('/backup/job/10').reply(() => {
+                return [500, response];
+              });
+            });
+
+            it('creates an alert box', (done) => {
+              setTimeout(() => {
+                expect(alertifySpy.alert).toHaveBeenCalledWith('Backup job' +
+                  ' failed.', 'some error message');
+                done();
+              }, 0);
+            });
+          });
+        });
+      });
+    });
+  });
+});
diff --git a/web/regression/javascript/backup/menu_utils_spec.js b/web/regression/javascript/backup/menu_utils_spec.js
new file mode 100644
index 00000000..8f776976
--- /dev/null
+++ b/web/regression/javascript/backup/menu_utils_spec.js
@@ -0,0 +1,167 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+
+import {TreeFake} from "../tree/tree_fake";
+import {
+  menuEnabledServer,
+  menuEnabled
+} from "../../../pgadmin/static/js/backup/menu_utils";
+
+const context = describe;
+
+describe('backup.menuUtils', () => {
+  describe('#menuEnabled', () => {
+    let ourBrowser;
+    beforeEach(() => {
+      const tree = new TreeFake();
+      ourBrowser = {
+        treeMenu: tree,
+      };
+      tree.addNewNode('level1', {}, []);
+      tree.addNewNode('level1.1', {_type: 'catalog'}, ['level1']);
+      tree.addNewNode('level1.1.1', {_type: 'database'}, ['level1', 'level1.1']);
+      tree.addNewNode('level1.2', {_type: 'bamm'}, ['level1']);
+      tree.addNewNode('level1.2.1', {
+        _type: 'database',
+        allowConn: true
+      }, ['level1', 'level1.2']);
+      tree.addNewNode('level1.2.2', {
+        _type: 'database',
+        allowConn: false
+      }, ['level1', 'level1.2']);
+      tree.addNewNode('level1.2.3', {
+        _type: 'table'
+      }, ['level1', 'level1.2']);
+
+      tree.addNewNode('level2', {}, []);
+      tree.addNewNode('level2.1', null, ['level2']);
+      tree.addNewNode('level2.1.1', {}, ['level2', 'level2.1']);
+    });
+
+    context('When the current node is a root node', () => {
+      it('return false', () => {
+        expect(menuEnabled.apply(ourBrowser, [{}, [{id: 'level1'}]])).toBe(false);
+      });
+    });
+
+    context('when current node does not exist', () => {
+      it('return false', () => {
+        expect(menuEnabled.apply(ourBrowser, [{}, [{id: 'bamm'}]])).toBe(false);
+      });
+    });
+
+    context('When the current node is not a root node', () => {
+      context('parent data does not exist', () => {
+        it('returns false', () => {
+          expect(menuEnabled.apply(ourBrowser, [{}, [{id: 'level2.1.1'}]])).toBe(false);
+        });
+      });
+
+      context('parent as data', () => {
+        context('the current node type is in the supported node types', () => {
+          context('the parent is of the type catalog', () => {
+            it('returns false', () => {
+              expect(menuEnabled.apply(ourBrowser, [
+                {_type: 'schema'},
+                [{id: 'level1.1.1'}]
+              ])).toBe(false);
+            });
+          });
+          context('the parent is not of the type catalog', () => {
+            context('current node is of the type database', () => {
+              context('current node allows connection', () => {
+                it('returns true', () => {
+                  expect(menuEnabled.apply(ourBrowser, [{
+                    _type: 'database',
+                    allowConn: true
+                  }, [{id: 'level1.2.1'}]])).toBe(true);
+                });
+              });
+              context('current node do not allow connection', () => {
+                it('returns false', () => {
+                  expect(menuEnabled.apply(ourBrowser, [{
+                    _type: 'database',
+                    allowConn: false
+                  }, [{id: 'level1.2.2'}]])).toBe(false);
+                });
+              });
+            });
+            context('current node is not of the type database', () => {
+              it('returns true', () => {
+                expect(menuEnabled.apply(ourBrowser, [{
+                  _type: 'schema'
+                }, [{id: 'level1.2.3'}]])).toBe(true);
+              });
+            });
+          });
+        });
+        context('the current node type is not in the supported node types', () => {
+          it('returns false', () => {
+            expect(menuEnabled.apply(ourBrowser, [{_type: 'catalog'}, [{id: 'level1.1'}]])).toBe(false);
+          });
+        });
+      });
+    });
+
+    context('provided node data is undefined', () => {
+      it('returns false', () => {
+        expect(menuEnabled.apply(ourBrowser, [undefined, [{id: 'level1'}]])).toBe(false);
+      });
+    });
+
+    context('provided node data is null', () => {
+      it('returns false', () => {
+        expect(menuEnabled.apply(ourBrowser, [null, [{id: 'level1'}]])).toBe(false);
+      });
+    });
+  });
+
+  describe('#menuEnabledServer', () => {
+    let ourBrowser;
+    beforeEach(() => {
+      const tree = new TreeFake();
+      ourBrowser = {
+        treeMenu: tree,
+      };
+    });
+
+    context('provided node data is undefined', () => {
+      it('returns false', () => {
+        expect(menuEnabledServer(undefined)).toBe(false);
+      });
+    });
+
+    context('provided node data is null', () => {
+      it('returns false', () => {
+        expect(menuEnabledServer(null)).toBe(false);
+      });
+    });
+
+    context('current node type is not of the type server', () => {
+      it('returns false', () => {
+        expect(menuEnabledServer({_type: 'schema'})).toBe(false);
+      });
+    });
+
+    context('current node type is of the type server', () => {
+      context('is connected', () => {
+        it('returns true', () => {
+          expect(menuEnabledServer({_type: 'server', connected: true})).toBe(true);
+        });
+      });
+      context('is not connected', () => {
+        it('returns false', () => {
+          expect(menuEnabledServer({_type: 'server', connected: false})).toBe(false);
+        });
+      });
+    });
+  });
+});
+
diff --git a/web/regression/javascript/datagrid/get_panel_title_spec.js b/web/regression/javascript/datagrid/get_panel_title_spec.js
new file mode 100644
index 00000000..3baedaa1
--- /dev/null
+++ b/web/regression/javascript/datagrid/get_panel_title_spec.js
@@ -0,0 +1,79 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import {getPanelTitle} from "../../../pgadmin/static/js/datagrid/get_panel_title";
+import {TreeFake} from "../tree/tree_fake";
+
+const context = describe;
+const xcontext = xdescribe;
+
+describe('#getPanelTitle', () => {
+  let pgBrowser;
+  let tree;
+  beforeEach(() => {
+    tree = new TreeFake();
+    pgBrowser = {
+      treeMenu: tree,
+      Nodes: {
+        server: {
+          hasId: true,
+          _type: 'server',
+        },
+        database: {
+          hasId: true,
+          _type: 'database',
+        },
+      }
+    };
+  });
+
+  context('selected node does not belong to a server', () => {
+    it('returns undefined', () => {
+      tree.addNewNode('level1', {_type: 'server_groups'}, []);
+      tree.addNewNode('level1.1', {_type: 'other'}, ['level1']);
+      tree.setSelectedNode([{id: 'level1'}]);
+      expect(getPanelTitle(pgBrowser)).toBeUndefined();
+    });
+  });
+
+  context('selected node belong to a server', () => {
+    context('selected node does not belong to a database', () => {
+      it('returns the server label and the username', () => {
+        tree.addNewNode('level1', {
+          _type: 'server',
+          db: 'other db label',
+          user: {name: 'some user name'},
+          label: 'server label'
+        }, []);
+        tree.setSelectedNode([{id: 'level1'}]);
+        expect(getPanelTitle(pgBrowser))
+          .toBe('other db label on some user name@server label');
+      });
+    });
+
+    context('selected node belongs to a database', () => {
+      it('returns the database label and the username', () => {
+        tree.addNewNode('level1', {
+          _type: 'server',
+          db: 'other db label',
+          user: {name: 'some user name'},
+          label: 'server label'
+        }, []);
+        tree.addNewNode('level1.1', {
+          _type: 'database',
+          label: 'db label'
+        }, ['level1']);
+        tree.addNewNode('level1.1.1', {_type: 'table'}, ['level1', 'level1.1']);
+        tree.setSelectedNode([{id: 'level1.1.1'}]);
+        expect(getPanelTitle(pgBrowser))
+          .toBe('db label on some user name@server label');
+      });
+    });
+  });
+});
diff --git a/web/regression/javascript/datagrid/show_data_spec.js b/web/regression/javascript/datagrid/show_data_spec.js
new file mode 100644
index 00000000..e0ef8783
--- /dev/null
+++ b/web/regression/javascript/datagrid/show_data_spec.js
@@ -0,0 +1,159 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import {showDataGrid} from "../../../pgadmin/static/js/datagrid/show_data";
+import {TreeFake} from "../tree/tree_fake";
+
+const context = describe;
+
+describe('#show_data', () => {
+  let datagrid;
+  let pgBrowser;
+  let alertify;
+  beforeEach(() => {
+    alertify = jasmine.createSpyObj('alertify', ['alert']);
+    datagrid = {
+      create_transaction: jasmine.createSpy('create_transaction'),
+    };
+    pgBrowser = {
+      treeMenu: new TreeFake(),
+      Nodes: {
+        server_group: {
+          _type: 'server_group',
+          hasId: true,
+        },
+        server: {
+          _type: 'server',
+          hasId: true,
+        },
+        database: {
+          _type: 'database',
+          hasId: true,
+        },
+        schema: {
+          _type: 'schema',
+          hasId: true,
+        },
+        view: {
+          _type: 'view',
+          hasId: true,
+        },
+        catalog: {
+          _type: 'catalog',
+          hasId: true,
+        },
+      }
+    };
+    pgBrowser.treeMenu.addNewNode('parent', {_type: 'parent'}, []);
+    pgBrowser.treeMenu.addNewNode('server_group1', {
+      _type: 'server_group',
+      _id: 1,
+    }, ['parent']);
+    pgBrowser.treeMenu.addNewNode('server1', {
+      _type: 'server',
+      label: 'server1',
+      server_type: 'pg',
+      _id: 2,
+    }, ['parent', 'server_group1']);
+    pgBrowser.treeMenu.addNewNode('database1', {
+      _type: 'database',
+      label: 'database1',
+      _id: 3,
+    }, ['parent', 'server_group1', 'server1']);
+    pgBrowser.treeMenu.addNewNode('schema1', {
+      _type: 'schema',
+      label: 'schema1',
+      _id: 4,
+    }, ['parent', 'server_group1', 'server1', 'database1']);
+    pgBrowser.treeMenu.addNewNode('view1', {
+      _type: 'view',
+      label: 'view1',
+      _id: 5,
+    }, ['parent', 'server_group1', 'server1', 'database1']);
+    pgBrowser.treeMenu.addNewNode('catalog1', {
+      _type: 'catalog',
+      label: 'catalog1',
+      _id: 6,
+    }, ['parent', 'server_group1', 'server1', 'database1']);
+  });
+
+  context('cannot find the tree node', () => {
+    it('does not create a transaction', () => {
+      showDataGrid(datagrid, pgBrowser, alertify, {}, [{id: '10'}]);
+      expect(datagrid.create_transaction).not.toHaveBeenCalled();
+    });
+
+    it('display alert', () => {
+      showDataGrid(datagrid, pgBrowser, alertify, {}, [{id: '10'}]);
+      expect(alertify.alert).toHaveBeenCalledWith(
+        'Data Grid Error',
+        'No object selected.'
+      );
+    });
+  });
+
+  context('current node is not underneath a server', () => {
+    it('does not create a transaction', () => {
+      showDataGrid(datagrid, pgBrowser, alertify, {}, [{id: 'parent'}]);
+      expect(datagrid.create_transaction).not.toHaveBeenCalled();
+    });
+  });
+
+  context('current node is not underneath a schema or view or catalog', () => {
+    it('does not create a transaction', () => {
+      showDataGrid(datagrid, pgBrowser, alertify, {}, [{id: 'database1'}]);
+      expect(datagrid.create_transaction).not.toHaveBeenCalled();
+    });
+  });
+
+  context('current node is underneath a schema', () => {
+    it('does not create a transaction', () => {
+      showDataGrid(datagrid, pgBrowser, alertify, {mnuid: 11}, [{id: 'schema1'}]);
+      expect(datagrid.create_transaction).toHaveBeenCalledWith(
+        '/initialize/datagrid/11/schema/1/2/3/4',
+        null,
+        'false',
+        'pg',
+        '',
+        'server1 - database1 - schema1.schema1',
+        ''
+      );
+    });
+  });
+
+  context('current node is underneath a view', () => {
+    it('does not create a transaction', () => {
+      showDataGrid(datagrid, pgBrowser, alertify, {mnuid: 11}, [{id: 'view1'}]);
+      expect(datagrid.create_transaction).toHaveBeenCalledWith(
+        '/initialize/datagrid/11/view/1/2/3/5',
+        null,
+        'false',
+        'pg',
+        '',
+        'server1 - database1 - view1.view1',
+        ''
+      );
+    });
+  });
+
+  context('current node is underneath a catalog', () => {
+    it('does not create a transaction', () => {
+      showDataGrid(datagrid, pgBrowser, alertify, {mnuid: 11}, [{id: 'catalog1'}]);
+      expect(datagrid.create_transaction).toHaveBeenCalledWith(
+        '/initialize/datagrid/11/catalog/1/2/3/6',
+        null,
+        'false',
+        'pg',
+        '',
+        'server1 - database1 - catalog1.catalog1',
+        ''
+      );
+    });
+  });
+});
diff --git a/web/regression/javascript/datagrid/show_query_tool_spec.js b/web/regression/javascript/datagrid/show_query_tool_spec.js
new file mode 100644
index 00000000..b36dc4a7
--- /dev/null
+++ b/web/regression/javascript/datagrid/show_query_tool_spec.js
@@ -0,0 +1,119 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import {TreeFake} from '../tree/tree_fake';
+import {showQueryTool} from '../../../pgadmin/static/js/datagrid/show_query_tool';
+
+const context = describe;
+
+describe('#showQueryTool', () => {
+  let queryTool;
+  let pgBrowser;
+  let alertify;
+  beforeEach(() => {
+    alertify = jasmine.createSpyObj('alertify', ['alert']);
+    queryTool = {
+      create_transaction: jasmine.createSpy('create_transaction'),
+    };
+    pgBrowser = {
+      treeMenu: new TreeFake(),
+      Nodes: {
+        server_group: {
+          _type: 'server_group',
+          hasId: true,
+        },
+        server: {
+          _type: 'server',
+          hasId: true,
+        },
+        database: {
+          _type: 'database',
+          hasId: true,
+        },
+      }
+    };
+    pgBrowser.treeMenu.addNewNode('parent', {_type: 'parent'}, []);
+    pgBrowser.treeMenu.addNewNode('server_group1', {
+      _type: 'server_group',
+      _id: 1,
+    }, ['parent']);
+    pgBrowser.treeMenu.addNewNode('server1', {
+      _type: 'server',
+      label: 'server1',
+      server_type: 'pg',
+      _id: 2,
+    }, ['parent', 'server_group1']);
+    pgBrowser.treeMenu.addNewNode('database1', {
+      _type: 'database',
+      label: 'database1',
+      _id: 3,
+    }, ['parent', 'server_group1', 'server1']);
+  });
+
+  context('cannot find the tree node', () => {
+    beforeEach(() => {
+      showQueryTool(queryTool, pgBrowser, alertify, '', [{id: '10'}], 'title');
+    });
+    it('does not create a transaction', () => {
+      expect(queryTool.create_transaction).not.toHaveBeenCalled();
+    });
+
+    it('display alert', () => {
+      expect(alertify.alert).toHaveBeenCalledWith(
+        'Query Tool Error',
+        'No object selected.'
+      );
+    });
+  });
+
+  context('current node is not underneath a server', () => {
+    it('does not create a transaction', () => {
+      showQueryTool(queryTool, pgBrowser, alertify, '', [{id: 'parent'}], 'title');
+      expect(queryTool.create_transaction).not.toHaveBeenCalled();
+    });
+
+    it('no alert is displayed', () => {
+      expect(alertify.alert).not.toHaveBeenCalled();
+    });
+  });
+
+  context('current node is underneath a server', () => {
+    context('current node is not underneath a database', () => {
+      it('creates a transaction', () => {
+        showQueryTool(queryTool, pgBrowser, alertify, 'http://someurl', [{id: 'server1'}], 'title');
+        expect(queryTool.create_transaction).toHaveBeenCalledWith(
+          '/initialize/query_tool/1/2',
+          null,
+          'true',
+          'pg',
+          'http://someurl',
+          'title',
+          '',
+          false
+        );
+      });
+    });
+
+    context('current node is underneath a database', () => {
+      it('creates a transaction', () => {
+        showQueryTool(queryTool, pgBrowser, alertify, 'http://someurl', [{id: 'database1'}], 'title');
+        expect(queryTool.create_transaction).toHaveBeenCalledWith(
+          '/initialize/query_tool/1/2/3',
+          null,
+          'true',
+          'pg',
+          'http://someurl',
+          'title',
+          '',
+          false
+        );
+      });
+    });
+  });
+});
diff --git a/web/regression/javascript/fake_endpoints.js b/web/regression/javascript/fake_endpoints.js
index 63ab05dc..e4ae7020 100644
--- a/web/regression/javascript/fake_endpoints.js
+++ b/web/regression/javascript/fake_endpoints.js
@@ -11,6 +11,12 @@ define(function () {
   return {
     'static': '/base/pgadmin/static/<path:filename>',
     'sqleditor.poll': '/sqleditor/query_tool/poll/<path:trans_id>',
-    'sqleditor.query_tool_start': '/sqleditor/query_tool/start/<path:trans_id>'
+    'sqleditor.query_tool_start': '/sqleditor/query_tool/start/<path:trans_id>',
+    'backup.create_server_job':  '/backup/job/<int:sid>',
+    'backup.create_object_job':  '/backup/job/<int:sid>/object',
+    'datagrid.initialize_datagrid': '/initialize/datagrid/<int:cmd_type>/<obj_type>/<int:sgid>/<int:sid>/<int:did>/<int:obj_id>',
+    'datagrid.initialize_query_tool': '/initialize/query_tool/<int:sgid>/<int:sid>',
+    'datagrid.initialize_query_tool_with_did': '/initialize/query_tool/<int:sgid>/<int:sid>/<int:did>',
+
   };
 });
diff --git a/web/regression/javascript/fake_model.js b/web/regression/javascript/fake_model.js
new file mode 100644
index 00000000..acfaa532
--- /dev/null
+++ b/web/regression/javascript/fake_model.js
@@ -0,0 +1,21 @@
+export class FakeModel {
+  constructor() {
+    this.values = {};
+  }
+
+  set(key, value) {
+    this.values[key] = value;
+  }
+
+  get(key) {
+    return this.values[key];
+  }
+
+  unset(key) {
+    delete this.values[key];
+  }
+
+  toJSON() {
+    return Object.assign({}, this.values);
+  }
+}
diff --git a/web/regression/javascript/tree/pgadmin_tree_node_spec.js b/web/regression/javascript/tree/pgadmin_tree_node_spec.js
new file mode 100644
index 00000000..1cab65f0
--- /dev/null
+++ b/web/regression/javascript/tree/pgadmin_tree_node_spec.js
@@ -0,0 +1,353 @@
+//////////////////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////////////////
+
+import {
+  getTreeNodeHierarchyFromElement,
+  getTreeNodeHierarchyFromIdentifier
+} from "../../../pgadmin/static/js/tree/pgadmin_tree_node";
+import {Tree} from "../../../pgadmin/static/js/tree/tree";
+
+const context = describe;
+
+describe('tree#node#getTreeNodeHierarchy', () => {
+  let browser;
+  let newTree;
+  let translateTreeNodeIdFromACITreeSpy;
+  beforeEach(() => {
+    newTree = new Tree();
+    browser = {
+      Nodes: {
+        'special one': {hasId: true},
+        'child special': {hasId: true},
+        'other type': {hasId: true},
+        'table': {hasId: true},
+        'partition': {hasId: true},
+        'no id': {hasId: false},
+      }
+    };
+    browser.treeMenu = newTree;
+    translateTreeNodeIdFromACITreeSpy = spyOn(newTree, 'translateTreeNodeIdFromACITree');
+  });
+
+  context('getTreeNodeHierarchy is called with aciTreeNode object', () => {
+    describe('When the current node is root element', () => {
+      beforeEach(() => {
+        newTree.addNewNode('root', {
+          'some key': 'some value',
+          '_type': 'special one'
+        }, []);
+        translateTreeNodeIdFromACITreeSpy.and.returnValue(['root']);
+      });
+
+      it('returns a object with the element type passed data and priority == 0', () => {
+        const result = getTreeNodeHierarchyFromIdentifier.bind(browser)('root');
+        expect(result).toEqual({
+          'special one': {
+            'some key': 'some value',
+            '_type': 'special one',
+            'priority': 0,
+          }
+        });
+      });
+    });
+
+    describe('When the current node is not of a known type', () => {
+      beforeEach(() => {
+        newTree.addNewNode('root', {
+          'some key': 'some value',
+          '_type': 'do not exist'
+        }, []);
+        translateTreeNodeIdFromACITreeSpy.and.returnValue(['root']);
+      });
+
+      it('returns a empty object', () => {
+        const result = getTreeNodeHierarchyFromIdentifier.bind(browser)('root');
+        expect(result).toEqual({});
+      });
+    });
+
+    describe('When the current node type has no id', () => {
+      beforeEach(() => {
+        newTree.addNewNode('root', {
+          'some key': 'some value',
+          '_type': 'no id'
+        }, []);
+        translateTreeNodeIdFromACITreeSpy.and.returnValue(['root']);
+      });
+
+      it('returns a empty object', () => {
+        const result = getTreeNodeHierarchyFromIdentifier.bind(browser)('root');
+        expect(result).toEqual({});
+      });
+    });
+
+    describe('When the current node is at the second level', () => {
+      beforeEach(() => {
+        newTree.addNewNode('root', {
+          'some key': 'some value',
+          '_type': 'special one'
+        }, []);
+        newTree.addNewNode('first child', {
+          'some key': 'some other value',
+          '_type': 'child special'
+        }, ['root']);
+        translateTreeNodeIdFromACITreeSpy.and.returnValue(['root', 'first' +
+        ' child']);
+      });
+
+      it('returns a empty object', () => {
+        const result = getTreeNodeHierarchyFromIdentifier.bind(browser)('first child');
+        expect(result).toEqual({
+          'child special': {
+            'some key': 'some other value',
+            '_type': 'child special',
+            'priority': 0,
+          },
+          'special one': {
+            'some key': 'some value',
+            '_type': 'special one',
+            'priority': -1,
+          },
+        });
+      });
+    });
+
+    context('When tree as "special type"', () => {
+      context('When "special type" have "other type"', () => {
+        context('When "other type" have "special type"', () => {
+          describe('When retrieving lead node', () => {
+            it('does not override previous node type data', () => {
+              newTree.addNewNode('root', {
+                'some key': 'some value',
+                '_type': 'special one'
+              }, []);
+              newTree.addNewNode('level 1', {
+                'some key': 'some value',
+                '_type': 'other type'
+              }, ['root']);
+              newTree.addNewNode('level 2', {
+                'some key': 'expected value',
+                '_type': 'special one',
+                'some other key': 'some other value',
+              }, ['root', 'level 1']);
+
+              translateTreeNodeIdFromACITreeSpy.and.returnValue(['root', 'level 1', 'level 2']);
+
+              const result = getTreeNodeHierarchyFromIdentifier.bind(browser)('level 2');
+              expect(result).toEqual({
+                'special one': {
+                  'some key': 'expected value',
+                  '_type': 'special one',
+                  'some other key': 'some other value',
+                  'priority': 0,
+                },
+                'other type': {
+                  'some key': 'some value',
+                  '_type': 'other type',
+                  'priority': -1,
+                },
+              });
+            });
+          });
+        });
+      });
+    });
+
+    context('When tree has table', () => {
+      context('when table has partition', () => {
+        it('returns table with partition parameters', () => {
+          newTree.addNewNode('root', {
+            'some key': 'some value',
+            '_type': 'special one'
+          }, []);
+          newTree.addNewNode('level 1', {
+            'some key': 'some value',
+            '_type': 'table'
+          }, ['root']);
+          newTree.addNewNode('level 2', {
+            'some key': 'expected value',
+            '_type': 'partition',
+            'some other key': 'some other value',
+          }, ['root', 'level 1']);
+
+          translateTreeNodeIdFromACITreeSpy.and.returnValue(['root', 'level 1', 'level 2']);
+
+          const result = getTreeNodeHierarchyFromIdentifier.bind(browser)('level 2');
+          expect(result).toEqual({
+            'special one': {
+              'some key': 'some value',
+              '_type': 'special one',
+              'priority': -1,
+            },
+            'table': {
+              'some key': 'expected value',
+              'some other key': 'some other value',
+              '_type': 'partition',
+              'priority': 0,
+            },
+          });
+        });
+      });
+    });
+  });
+
+  context('getTreeNodeHierarchy is called with TreeNode object', () => {
+    let treeNode;
+    describe('When the current node is root element', () => {
+      beforeEach(() => {
+        treeNode = newTree.addNewNode('root', {
+          'some key': 'some value',
+          '_type': 'special one'
+        }, []);
+      });
+
+      it('returns a object with the element type passed data and priority == 0', () => {
+        const result = getTreeNodeHierarchyFromElement(browser, treeNode);
+        expect(result).toEqual({
+          'special one': {
+            'some key': 'some value',
+            '_type': 'special one',
+            'priority': 0,
+          }
+        });
+      });
+    });
+
+    describe('When the current node is not of a known type', () => {
+      beforeEach(() => {
+        treeNode = newTree.addNewNode('root', {
+          'some key': 'some value',
+          '_type': 'do not exist'
+        }, []);
+      });
+
+      it('returns a empty object', () => {
+        const result = getTreeNodeHierarchyFromElement(browser, treeNode);
+        expect(result).toEqual({});
+      });
+    });
+
+    describe('When the current node type has no id', () => {
+      beforeEach(() => {
+        treeNode = newTree.addNewNode('root', {
+          'some key': 'some value',
+          '_type': 'no id'
+        }, []);
+      });
+
+      it('returns a empty object', () => {
+        const result = getTreeNodeHierarchyFromElement(browser, treeNode);
+        expect(result).toEqual({});
+      });
+    });
+
+    describe('When the current node is at the second level', () => {
+      beforeEach(() => {
+        newTree.addNewNode('root', {
+          'some key': 'some value',
+          '_type': 'special one'
+        }, []);
+        treeNode = newTree.addNewNode('first child', {
+          'some key': 'some other value',
+          '_type': 'child special'
+        }, ['root']);
+      });
+
+      it('returns a empty object', () => {
+        const result = getTreeNodeHierarchyFromElement(browser, treeNode);
+        expect(result).toEqual({
+          'child special': {
+            'some key': 'some other value',
+            '_type': 'child special',
+            'priority': 0,
+          },
+          'special one': {
+            'some key': 'some value',
+            '_type': 'special one',
+            'priority': -1,
+          },
+        });
+      });
+    });
+
+    context('When tree as "special type"', () => {
+      context('When "special type" have "other type"', () => {
+        context('When "other type" have "special type"', () => {
+          describe('When retrieving lead node', () => {
+            it('does not override previous node type data', () => {
+              newTree.addNewNode('root', {
+                'some key': 'some value',
+                '_type': 'special one'
+              }, []);
+              newTree.addNewNode('level 1', {
+                'some key': 'some value',
+                '_type': 'other type'
+              }, ['root']);
+              treeNode = newTree.addNewNode('level 2', {
+                'some key': 'expected value',
+                '_type': 'special one',
+                'some other key': 'some other value',
+              }, ['root', 'level 1']);
+
+              const result = getTreeNodeHierarchyFromElement(browser, treeNode);
+              expect(result).toEqual({
+                'special one': {
+                  'some key': 'expected value',
+                  '_type': 'special one',
+                  'some other key': 'some other value',
+                  'priority': 0,
+                },
+                'other type': {
+                  'some key': 'some value',
+                  '_type': 'other type',
+                  'priority': -1,
+                },
+              });
+            });
+          });
+        });
+      });
+    });
+
+    context('When tree has table', () => {
+      context('when table has partition', () => {
+        it('returns table with partition parameters', () => {
+          newTree.addNewNode('root', {
+            'some key': 'some value',
+            '_type': 'special one'
+          }, []);
+          newTree.addNewNode('level 1', {
+            'some key': 'some value',
+            '_type': 'table'
+          }, ['root']);
+          treeNode = newTree.addNewNode('level 2', {
+            'some key': 'expected value',
+            '_type': 'partition',
+            'some other key': 'some other value',
+          }, ['root', 'level 1']);
+
+          const result = getTreeNodeHierarchyFromElement(browser, treeNode);
+          expect(result).toEqual({
+            'special one': {
+              'some key': 'some value',
+              '_type': 'special one',
+              'priority': -1,
+            },
+            'table': {
+              'some key': 'expected value',
+              'some other key': 'some other value',
+              '_type': 'partition',
+              'priority': 0,
+            },
+          });
+        });
+      });
+    });
+  });
+});


view thread (69+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected]
  Subject: Re: [pgadmin4][patch] Initial patch to decouple from ACI Tree
  In-Reply-To: <CAE+jjaneGOR_R7QvYnZt=khumRsyhnrzFRhpMG5-Q+rR9guLzQ@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox