public inbox for [email protected]
help / color / mirror / Atom feedFrom: Joao De Almeida Pereira <[email protected]>
To: pgadmin-hackers <[email protected]>
Subject: [pgadmin4][patch] Initial patch to decouple from ACI Tree
Date: Mon, 02 Apr 2018 18:45:32 +0000
Message-ID: <CAE+jja=Gdd032H7tpoZD2C0m2R7SnTZpHX_oPx2K2zGbaaW9yg@mail.gmail.com> (raw)
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:
[application/octet-stream] 0001-Tree-of-information-on-the-current-status-of-the-app.patch (14.1K, 3-0001-Tree-of-information-on-the-current-status-of-the-app.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();
+ });
+ });
+ });
+ });
+});
+
--
2.15.1
[application/octet-stream] 0002-Extractions-of-multiples-locations-where-we-used-the.patch (142.9K, 4-0002-Extractions-of-multiples-locations-where-we-used-the.patch)
download | inline diff:
diff --git a/web/package.json b/web/package.json
index 3ce86a11..f8d87607 100644
--- a/web/package.json
+++ b/web/package.json
@@ -84,6 +84,7 @@
"slickgrid": "git+https://github.com/6pac/SlickGrid.git#2.3.7",
"snapsvg": "^0.5.1",
"spectrum-colorpicker": "^1.8.0",
+ "sprintf-js": "^1.1.1",
"underscore": "^1.8.3",
"underscore.string": "^3.3.4",
"watchify": "~3.9.0",
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 46b60332..d38049db 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', '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
) {
@@ -84,6 +86,7 @@ define('pgadmin.browser', [
});
b.tree = $('#tree').aciTree('api');
+ b.treeMenu.register($('#tree'));
};
// Extend the browser class attributes
@@ -98,6 +101,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,
+ },
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/web/yarn.lock b/web/yarn.lock
index 88edeb1f..da5e5f64 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -7422,7 +7422,7 @@ [email protected]:
version "1.1.0"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.0.tgz#cffcaf702daf65ea39bb4e0fa2b299cec1a1be46"
-sprintf-js@^1.0.3:
+sprintf-js@^1.0.3, sprintf-js@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.1.tgz#36be78320afe5801f6cea3ee78b6e5aab940ea0c"
--
2.15.1
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]
Subject: Re: [pgadmin4][patch] Initial patch to decouple from ACI Tree
In-Reply-To: <CAE+jja=Gdd032H7tpoZD2C0m2R7SnTZpHX_oPx2K2zGbaaW9yg@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