public inbox for [email protected]
help / color / mirror / Atom feedFrom: Joao De Almeida Pereira <[email protected]>
To: Ashesh Vashi <[email protected]>
Cc: Dave Page <[email protected]>
Cc: Khushboo Vashi <[email protected]>
Cc: Murtuza Zabuawala <[email protected]>
Cc: pgadmin-hackers <[email protected]>
Subject: Re: [pgadmin4][patch] Initial patch to decouple from ACI Tree
Date: Fri, 27 Apr 2018 22:25:41 +0000
Message-ID: <CAE+jjanjQ295x0DgffT8OARRv8fge5KOPJpMCnLWe+j0jdqGJQ@mail.gmail.com> (raw)
In-Reply-To: <CAG7mmowrM3b8cTY+zcCAcVma8CKWHwXSF3=BUDcSFLpjH=jQBg@mail.gmail.com>
References: <CAE+jja=Gdd032H7tpoZD2C0m2R7SnTZpHX_oPx2K2zGbaaW9yg@mail.gmail.com>
<CAFOhELcrZsRkwV4beOC7UT_HYvi9Rk=HFJ355g8Bj4bqyzMLog@mail.gmail.com>
<CAE+jjaneGOR_R7QvYnZt=khumRsyhnrzFRhpMG5-Q+rR9guLzQ@mail.gmail.com>
<CAKKotZROcoO_rtrrJUTvpfgSh1w4ig1ogQrkDrKSQHuKEL7CdQ@mail.gmail.com>
<CAE+jjakEZtn0ct128Xd+es5xCZ5eZLwoNZw6X0ue7oFB+a83oQ@mail.gmail.com>
<CAFOhELe_t_QGBbcYYm3Zrfwf3z9674PY5sy2QS46wHDTf-8DHA@mail.gmail.com>
<CAE+jja=u+W+FNaWGVs1KPZm1eYA_F=7ktomSbmsxOo-cjuOdBw@mail.gmail.com>
<CAE+jja=TZzMXOq4+t=dA+LfTGkW66urB37Tt9sEJ_UjAiymbbQ@mail.gmail.com>
<CAE+jjamSM_pBLNmYLn6-hWMiQvToceV2iPuME-eH5c+tgaYf7A@mail.gmail.com>
<CA+OCxowN15jg_DV4o+R+OSczyGHHccnJ1sPXE_XukYugaTKyyw@mail.gmail.com>
<CA+OCxozTMcEDKZEu9YCOsSBSed23Ka8hHiO5s6VF76KopbipNQ@mail.gmail.com>
<CAG7mmowrM3b8cTY+zcCAcVma8CKWHwXSF3=BUDcSFLpjH=jQBg@mail.gmail.com>
Hi Hackers,
As you are aware we kept on working on the patch, so we are attaching to
this email a new version of the patch.
This new version contains all the changes in the previous one plus more
extractions of functions and refactoring of code.
The objective of this patch is to create a separation between pgAdmin and
the ACI Tree. We are doing this because we realized that at this point in
time we have the ACI Tree all over the code of pgAdmin. I found a very
interesting article that really talks about this:
https://medium.freecodecamp.org/code-dependencies-are-the-devil-35ed28b556d
In this patch there are some visions and ideas about the location of the
code, the way to organize it and also try to pave the future for a
application that is stable, easy to develop on and that can be release at a
times notice.
We are investing a big chunk of our time in doing this refactoring, but
while doing that we also try to respond to the patches sent to the mailing
list. We would like the feedback from the community because we believe this
is a changing point for the application. The idea is to change the way we
develop this application, instead of only correcting a bug of developing a
feature, with every commit we should correct the bug or develop a feature
but leave the code a little better than we found it (Refactoring,
refactoring, refactoring). This is hard work but that is what the users
from pgAdmin expect from this community of developers.
======
It is a huge patch
86 files changed, 5492 inserts, 1840 deletions
and we would like to get your feedback as soon as possible, because we are
continuing to work on it which means it is going to grow in size.
At this point in time we still have 124 of 176 calls to the function
itemData from ACITree.
What does each patch contain:
0001:
Very simple patch, we found out that the linter was not looking into all
the javascript test files, so this patch will ensure it is
0002:
New Tree abstraction. This patch contains the new Tree that works as an
adaptor for ACI Tree and is going to be used on all the extractions that we
are doing
0003:
Code that extracts, wrap with tests and replace ACI Tree invocations.
We start creating new pattern for the location of Javascript files and
their structure.
Create patterns for creation of dialogs (backup and restore)
Thanks
Joao
On Fri, Apr 27, 2018 at 5:34 AM Ashesh Vashi <[email protected]>
wrote:
> I have quite a few comments for the patch.
> I will send them soon.
>
> On Fri, Apr 27, 2018, 14:45 Dave Page <[email protected]> wrote:
>
>> How is your work on this going Ashesh? Will you be committing today?
>>
>> On Wed, Apr 25, 2018 at 8:52 AM, Dave Page <[email protected]> wrote:
>>
>>> Ashesh; you had agreed to work on this early this week. Please ensure
>>> you do so today.
>>>
>>> Thanks.
>>>
>>> On Tue, Apr 24, 2018 at 8:13 PM, Joao De Almeida Pereira <
>>> [email protected]> wrote:
>>>
>>>> Hi Hackers,
>>>>
>>>> Can someone review and merge this patch?
>>>>
>>>> Thanks
>>>> Joao
>>>>
>>>> On Wed, Apr 18, 2018 at 10:23 AM Joao De Almeida Pereira <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Hackers,
>>>>> Any other comment about this patch?
>>>>>
>>>>> Thanks
>>>>> Joao
>>>>>
>>>>> On Tue, Apr 10, 2018 at 12:00 PM Joao De Almeida Pereira <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hello Khushboo
>>>>>>
>>>>>> On Mon, Apr 9, 2018 at 1:59 AM Khushboo Vashi <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi Joao,
>>>>>>>
>>>>>>> I have reviewed your patch and have some suggestions.
>>>>>>>
>>>>>>> On Sat, Apr 7, 2018 at 12:43 AM, Joao De Almeida Pereira <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Hello Murtuza/Dave,
>>>>>>>> Yes now the extracted functions are spread into different files.
>>>>>>>> The intent would be to make the files as small as possible, and also to
>>>>>>>> group and name them in a way that would be easy to understand what each
>>>>>>>> file is doing without the need of opening it.
>>>>>>>> As a example:
>>>>>>>> static/js/backup will contain all the backup related functionality
>>>>>>>> inside of this folder we can see the file:
>>>>>>>>
>>>>>>> menu_utils.js At this moment in time we decided to group all the
>>>>>>>> functions that are related to the menu, but we can split that also if we
>>>>>>>> believe it is easier to see.
>>>>>>>>
>>>>>>> It's really very good to see the separated code for backup module.
>>>>>>> As we have done for backup, we would like do it for other PG utilities like
>>>>>>> restore, maintenance etc.
>>>>>>> Considering this, we should separate the code in a way that some of
>>>>>>> the common functionalities can be used for other modules like menu (as you
>>>>>>> have mentioned above), dialogue factory etc.
>>>>>>> Also, I think these functionalities should be in their respective
>>>>>>> static folder instead of pgadmin/static.
>>>>>>>
>>>>>>
>>>>>> About the location of the files. The move of the files to
>>>>>> pgadmin/static/js was made on purpose in order to clearly separate
>>>>>> Javascript from python code.
>>>>>> The rational behind it was
>>>>>> - Create a clear separation between the backend and frontend
>>>>>> - Having Javascript code concentrated in a single place, hopefully,
>>>>>> will encourage to developers to look for a functionality, that is already
>>>>>> implemented in another modules, because they are right there. (When we
>>>>>> started this journey we realized that the 'nodes' have a big groups of code
>>>>>> that could be shared, but because the Javascript is spread everywhere it is
>>>>>> much harder to look for it)
>>>>>>
>>>>>>
>>>>>> There are some drawbacks of this separation:
>>>>>> - When creating a new module we will need to put the javascript in a
>>>>>> separate location from the backend code
>>>>>>
>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>> static/js/datagrid folder contains all the datagrid related
>>>>>>>> functionality
>>>>>>>>
>>>>>>> Same as backup module, this should be in it's respective static/js
>>>>>>> folder.
>>>>>>>
>>>>>>>> Inside of the folder we can see the files:
>>>>>>>> get_panel_title.js is responsible for retrieving the name of the
>>>>>>>> panel
>>>>>>>> show_data.js is responsible for showing the datagrid
>>>>>>>> show_query_tool.js is responsible for showing the query tool
>>>>>>>>
>>>>>>>> Does this structure make sense?
>>>>>>>> Can you give an example of a comment that you think is missing and
>>>>>>>> that could help?
>>>>>>>>
>>>>>>>> As a personal note, unless the algorithm is very obscure or very
>>>>>>>> complicated, I believe that if the code needs comments it is a signal that
>>>>>>>> something needs to change in terms of naming, structure of the part in
>>>>>>>> question. This being said, I am open to add some comments that might help
>>>>>>>> people.
>>>>>>>>
>>>>>>> You are right, with the help of naming convention and structure of
>>>>>>> the code, any one can get the idea about the code. But it is very useful to
>>>>>>> understand the code
>>>>>>> very easily with the proper comments especially when there are
>>>>>>> multiple developers working on a single project.
>>>>>>>
>>>>>>> I found some of the places where it would be great to have comments.
>>>>>>>
>>>>>>> - treeMenu: new tree.Tree() in a browser.js
>>>>>>> - tree.js (especially Tree class)
>>>>>>>
>>>>>> About the comment point I need a more clear understanding on what
>>>>>> kind of comments you are looking for. Because when you read the function
>>>>>> names you understand the intent, what they are doing. The parameters also
>>>>>> explain what you need to pass into them.
>>>>>>
>>>>>> If what you are looking for in these comments is the reasoning being
>>>>>> the change itself, then that should be present in the commit message.
>>>>>> Specially because this is going to be a very big patch with a very big
>>>>>> number of changes.
>>>>>>
>>>>>>>
>>>>>>> Thanks
>>>>>>>> Joao
>>>>>>>>
>>>>>>>>
>>>>>>>> Thanks,
>>>>>>> Khushboo
>>>>>>>
>>>>>>>>
>>>>>>>> On Fri, Apr 6, 2018 at 4:48 AM Murtuza Zabuawala <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> Hi Joao,
>>>>>>>>>
>>>>>>>>> Patch looks good and working as expected.
>>>>>>>>>
>>>>>>>>> I also agree with Dave, Can we please add some comments in each
>>>>>>>>> file which can help us to understand the flow, I'm saying because now the
>>>>>>>>> code is segregated in so many separate files it will be hard to keep track
>>>>>>>>> of the flow from one file to another when debugging.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> --
>>>>>>>>> Regards,
>>>>>>>>> Murtuza Zabuawala
>>>>>>>>> EnterpriseDB: http://www.enterprisedb.com
>>>>>>>>> The Enterprise PostgreSQL Company
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> On Thu, Apr 5, 2018 at 7:08 PM, Joao De Almeida Pereira <
>>>>>>>>> [email protected]> wrote:
>>>>>>>>>
>>>>>>>>>> Hi Khushboo,
>>>>>>>>>> Attached you can find both patches rebased
>>>>>>>>>>
>>>>>>>>>> Thanks
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> On Thu, Apr 5, 2018 at 6:31 AM Khushboo Vashi <
>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>
>>>>>>>>>>> Hi Joao,
>>>>>>>>>>>
>>>>>>>>>>> Can you please rebase the second patch?
>>>>>>>>>>>
>>>>>>>>>>> Thanks,
>>>>>>>>>>> Khushboo
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> On Tue, Apr 3, 2018 at 12:15 AM, Joao De Almeida Pereira <
>>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>>
>>>>>>>>>>>> Hi Hackers,
>>>>>>>>>>>>
>>>>>>>>>>>> Attached you can find the patch that will start to decouple
>>>>>>>>>>>> pgAdmin from ACITree library.
>>>>>>>>>>>> This patch is intended to be merged after 3.0, because we do
>>>>>>>>>>>> not want to cause any entropy or delay the release, but we want to start
>>>>>>>>>>>> the discussion and show some code.
>>>>>>>>>>>>
>>>>>>>>>>>> This job that we started is a massive tech debt chore that will
>>>>>>>>>>>> take some time to finalize and we would love the help of the community to
>>>>>>>>>>>> do it.
>>>>>>>>>>>>
>>>>>>>>>>>> *Summary of the patch:*
>>>>>>>>>>>> 0001 patch:
>>>>>>>>>>>> - Creates a new tree that will allow us to create a separation
>>>>>>>>>>>> between the application and ACI Tree
>>>>>>>>>>>> - Creates a Fake Tree (Test double, for reference on the
>>>>>>>>>>>> available test doubles:
>>>>>>>>>>>> https://martinfowler.com/bliki/TestDouble.html) that can be
>>>>>>>>>>>> used to inplace to replace the ACITree and also encapsulate the new tree
>>>>>>>>>>>> behavior on our tests
>>>>>>>>>>>> - Adds tests for all the tree functionalities
>>>>>>>>>>>>
>>>>>>>>>>>> 0002 patch:
>>>>>>>>>>>> - Extracts, refactors, adds tests and remove dependency from
>>>>>>>>>>>> ACI Tree on:
>>>>>>>>>>>> - getTreeNodeHierarchy
>>>>>>>>>>>> - on backup.js: menu_enabled, menu_enabled_server,
>>>>>>>>>>>> start_backup_global_server, backup_objects
>>>>>>>>>>>> - on datagrid.js: show_data_grid, get_panel_title,
>>>>>>>>>>>> show_query_tool
>>>>>>>>>>>> - Start using sprintf-js as Underscore.String is deprecating
>>>>>>>>>>>> sprintf function
>>>>>>>>>>>>
>>>>>>>>>>>> This patch represents only 10 calls to ACITree.itemData out of
>>>>>>>>>>>> 176 that are spread around our code
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> *In Depth look on the process behind the patch:*
>>>>>>>>>>>>
>>>>>>>>>>>> We started writing this patch with the idea that we need to
>>>>>>>>>>>> decouple pgAdmin4 from ACITree, because ACITree is no longer supported, the
>>>>>>>>>>>> documentation is non existent and ACITree is no longer being actively
>>>>>>>>>>>> developed.
>>>>>>>>>>>>
>>>>>>>>>>>> Our process:
>>>>>>>>>>>> 1. We "randomly" selected a function that is part of the
>>>>>>>>>>>> ACITree. From this point we decided to replace that function with our own
>>>>>>>>>>>> version. The function that we choose was "itemData".
>>>>>>>>>>>> The function gives us all the "data" that a specific node of
>>>>>>>>>>>> the tree find.
>>>>>>>>>>>> Given in order to replace the tree we would need to have a
>>>>>>>>>>>> function that would give us the same information. We had 2 options:
>>>>>>>>>>>> a) Create a tree with a function called itemData
>>>>>>>>>>>> Pros:
>>>>>>>>>>>> - At first view this was the simpler solution
>>>>>>>>>>>> - Would keep the status quo
>>>>>>>>>>>> Cons:
>>>>>>>>>>>> - Not a OOP approach
>>>>>>>>>>>> - Not very flexible
>>>>>>>>>>>> b) Create a tree that would return a node given an ID and
>>>>>>>>>>>> then the node would be responsible for giving it's data.
>>>>>>>>>>>> Pros:
>>>>>>>>>>>> - OOP Approach
>>>>>>>>>>>> - More flexible and we do not need to bring the tree around,
>>>>>>>>>>>> just a node
>>>>>>>>>>>> Cons:
>>>>>>>>>>>> - Break the current status quo
>>>>>>>>>>>>
>>>>>>>>>>>> Given these 2 options we decided to go for a more OOP approach
>>>>>>>>>>>> creating a Tree and a TreeNode classes, that in the future will be renamed
>>>>>>>>>>>> to ACITreeWrapper and TreeNode.
>>>>>>>>>>>>
>>>>>>>>>>>> 2. After we decided on the starting point we searched for
>>>>>>>>>>>> occurrences of the function "itemData" and we found out that there were 303
>>>>>>>>>>>> occurrences of "itemData" in the code and roughly 176 calls to the function
>>>>>>>>>>>> itself (some of the hits were variable names).
>>>>>>>>>>>>
>>>>>>>>>>>> 3. We selected the first file on the search and found the
>>>>>>>>>>>> function that was responsible for calling the itemData function.
>>>>>>>>>>>>
>>>>>>>>>>>> 4. Extracted the function to a separate file
>>>>>>>>>>>>
>>>>>>>>>>>> 5. Wrap this function with tests
>>>>>>>>>>>>
>>>>>>>>>>>> 6. Refactor the function to ES6, give more declarative names to
>>>>>>>>>>>> variables and break the functions into smaller chunks
>>>>>>>>>>>>
>>>>>>>>>>>> 7. When all the tests were passing we replaced ACITree with our
>>>>>>>>>>>> Tree
>>>>>>>>>>>>
>>>>>>>>>>>> 8. We ensured that all tests were passing
>>>>>>>>>>>>
>>>>>>>>>>>> 9. Remove function from the original file and use the new
>>>>>>>>>>>> function
>>>>>>>>>>>>
>>>>>>>>>>>> 10. Ensure everything still works
>>>>>>>>>>>>
>>>>>>>>>>>> 11. Find the next function and execute from step 4 until all
>>>>>>>>>>>> the functions are replaced, refactored and tested.
>>>>>>>>>>>>
>>>>>>>>>>>> As you can see by the process this is a pretty huge undertake,
>>>>>>>>>>>> because of the number of calls to the function. This is just the first step
>>>>>>>>>>>> on the direction of completely isolating the ACITree so that we can solve
>>>>>>>>>>>> the problem with a large number of elements on the tree.
>>>>>>>>>>>>
>>>>>>>>>>>> *What is on our radar that we need to address:*
>>>>>>>>>>>> - Finish the complete decoupling of the ACITree
>>>>>>>>>>>> - Performance of the current tree implementation
>>>>>>>>>>>> - Tweak the naming of the Tree class to explicitly tell us
>>>>>>>>>>>> this is to use only with ACITree.
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> Thanks
>>>>>>>>>>>> Joao
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>
>>>
>>>
>>> --
>>> Dave Page
>>> Blog: http://pgsnake.blogspot.com
>>> Twitter: @pgsnake
>>>
>>> EnterpriseDB UK: http://www.enterprisedb.com
>>> The Enterprise PostgreSQL Company
>>>
>>
>>
>>
>> --
>> Dave Page
>> Blog: http://pgsnake.blogspot.com
>> Twitter: @pgsnake
>>
>> EnterpriseDB UK: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>>
>
Attachments:
[application/octet-stream] 0001-Ensure-the-JS-linter-is-running-on-all-the-tests-fil.patch (978B, 3-0001-Ensure-the-JS-linter-is-running-on-all-the-tests-fil.patch)
download | inline diff:
diff --git a/web/pga_eslint.js b/web/pga_eslint.js
index 8c4e3803..11511d67 100644
--- a/web/pga_eslint.js
+++ b/web/pga_eslint.js
@@ -1,6 +1,7 @@
#!/usr/bin/env node
/* eslint no-console:off */
+/* eslint no-undef:off */
'use strict';
const debug = (process.argv.indexOf('--debug') > -1),
@@ -18,9 +19,9 @@ if (debug) {
require('debug').enable('eslint:*,-eslint:code-path');
}
-const fs = require('fs'),
- path = require('path'),
- read = (dir) =>
+const fs = require('fs');
+const path = require('path');
+const read = (dir) =>
fs.readdirSync(dir)
.reduce((files, file) =>
fs.statSync(path.join(dir, file)).isDirectory() ?
@@ -38,6 +39,6 @@ process.exitCode = eslint_cli.execute(
file_argv > -1 ? argv : argv.concat(
read(path.join(__dirname, 'pgadmin'))
).concat(
- 'regression/javascript/**/*.jsx regression/javascript/**/*.js *.js'
+ ['regression/javascript/**/*.jsx','regression/javascript/**/*.js','*.js']
)
);
--
2.16.2
[application/octet-stream] 0002-New-tree-implementation.patch (21.9K, 4-0002-New-tree-implementation.patch)
download | inline diff:
diff --git a/web/pgadmin/browser/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js
index 897d2708..fc68089f 100644
--- a/web/pgadmin/browser/static/js/browser.js
+++ b/web/pgadmin/browser/static/js/browser.js
@@ -1,4 +1,5 @@
define('pgadmin.browser', [
+ 'sources/tree/tree',
'sources/gettext', 'sources/url_for', 'require', 'jquery', 'underscore', 'underscore.string',
'bootstrap', 'sources/pgadmin', 'pgadmin.alertifyjs', 'bundled_codemirror',
'sources/check_node_visibility', 'sources/modify_animation', 'pgadmin.browser.utils', 'wcdocker',
@@ -10,6 +11,7 @@ define('pgadmin.browser', [
'sources/codemirror/addon/fold/pgadmin-sqlfoldcode',
'pgadmin.browser.keyboard',
], function(
+ tree,
gettext, url_for, require, $, _, S, Bootstrap, pgAdmin, Alertify,
codemirror, checkNodeVisibility, modifyAnimation
) {
@@ -86,6 +88,7 @@ define('pgadmin.browser', [
});
b.tree = $('#tree').aciTree('api');
+ b.treeMenu.register($('#tree'));
};
// Extend the browser class attributes
@@ -100,6 +103,7 @@ define('pgadmin.browser', [
editor:null,
// Left hand browser tree
tree:null,
+ treeMenu: new tree.Tree(),
// list of script to be loaded, when a certain type of node is loaded
// It will be used to register extensions, tools, child node scripts,
// etc.
diff --git a/web/pgadmin/static/js/tree/tree.js b/web/pgadmin/static/js/tree/tree.js
new file mode 100644
index 00000000..02685b48
--- /dev/null
+++ b/web/pgadmin/static/js/tree/tree.js
@@ -0,0 +1,187 @@
+//////////////////////////////////////////////////////////////////////////
+//
+// 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, domNode, parent) {
+ this.id = id;
+ this.data = data;
+ this.setParent(parent);
+ this.children = [];
+ this.domNode = domNode;
+ }
+
+ hasParent() {
+ return this.parentNode !== null && this.parentNode !== undefined;
+ }
+
+ parent() {
+ return this.parentNode;
+ }
+
+ setParent(parent) {
+ this.parentNode = parent;
+ this.path = this.id;
+ if (parent !== null && parent !== undefined && parent.path !== undefined) {
+ this.path = parent.path + '.' + this.id;
+ }
+ }
+
+ getData() {
+ if (this.data === undefined) {
+ return undefined;
+ } else if (this.data === null) {
+ return null;
+ }
+ return Object.assign({}, this.data);
+ }
+
+ getHtmlIdentifier() {
+ return this.domNode;
+ }
+
+ reload(tree) {
+ this.unload(tree);
+ tree.aciTreeApi.setInode(this.domNode);
+ tree.aciTreeApi.deselect(this.domNode);
+ setTimeout(() => {
+ tree.selectNode(this.domNode);
+ }, 0);
+ }
+
+ unload(tree) {
+ this.children = [];
+ tree.aciTreeApi.unload(this.domNode);
+ }
+
+ anyParent(condition) {
+ let node = this;
+
+ while (node.hasParent()) {
+ node = node.parent();
+ if (condition(node)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ anyFamilyMember(condition) {
+ if(condition(this)) {
+ return true;
+ }
+
+ return this.anyParent(condition);
+ }
+}
+
+export class Tree {
+ constructor() {
+ this.rootNode = new TreeNode(undefined, {});
+ this.aciTreeApi = undefined;
+ }
+
+ addNewNode(id, data, domNode, parentPath) {
+ const parent = this.findNode(parentPath);
+ return this.createOrUpdateNode(id, data, parent, domNode);
+ }
+
+ findNode(path) {
+ if (path === null || path === undefined || 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();
+ }
+
+ selectNode(aciTreeIdentifier) {
+ this.aciTreeApi.select(aciTreeIdentifier);
+ }
+
+ 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, item, parentId);
+ }
+ }
+ }.bind(this));
+ this.aciTreeApi = $treeJQuery.aciTree('api');
+ }
+
+ createOrUpdateNode(id, data, parent, domNode) {
+ let oldNodePath = [id];
+ if(parent !== null && parent !== undefined) {
+ oldNodePath = [parent.path, id];
+ }
+ const oldNode = this.findNode(oldNodePath);
+ if (oldNode !== null) {
+ oldNode.data = Object.assign({}, data);
+ return oldNode;
+ }
+
+ const node = new TreeNode(id, data, domNode, 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..b285a45f
--- /dev/null
+++ b/web/regression/javascript/tree/tree_fake.js
@@ -0,0 +1,69 @@
+/////////////////////////////////////////////////////////////
+//
+// 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 = {};
+ this.aciTreeApi = jasmine.createSpyObj(
+ ['ACITreeApi'], ['setInode', 'unload', 'deselect', 'select']);
+ }
+
+ addNewNode(id, data, domNode, path) {
+ this.aciTreeToOurTreeTranslator[id] = [id];
+ if (path !== null && path !== undefined) {
+ this.aciTreeToOurTreeTranslator[id] = path.concat(id);
+ }
+ return super.addNewNode(id, data, domNode, path);
+ }
+
+ addChild(parent, child) {
+ child.setParent(parent);
+ this.aciTreeToOurTreeTranslator[child.id] = this.aciTreeToOurTreeTranslator[parent.id].concat(child.id);
+ parent.children.push(child);
+ }
+
+ 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) {
+ if(aciTreeNode === undefined || aciTreeNode[0] === undefined) {
+ return null;
+ }
+ return this.aciTreeToOurTreeTranslator[aciTreeNode[0].id];
+ }
+
+ itemData(aciTreeNode) {
+ let node = this.findNodeByDomElement(aciTreeNode);
+ if (node === undefined || node === null) {
+ return undefined;
+ }
+ return node.getData();
+ }
+
+ selected() {
+ return this.selectedNode;
+ }
+
+ selectNode(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..164ceb5b
--- /dev/null
+++ b/web/regression/javascript/tree/tree_spec.js
@@ -0,0 +1,421 @@
+//////////////////////////////////////////////////////////////////////////
+//
+// 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', () => {
+ context('using [] as the parent', () => {
+ beforeEach(() => {
+ tree.addNewNode('some new node', {data: 'interesting'}, undefined, []);
+ });
+
+ 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();
+ });
+ });
+
+ context('using null as the parent', () => {
+ beforeEach(() => {
+ tree.addNewNode('some new node', {data: 'interesting'}, undefined, null);
+ });
+
+ 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();
+ });
+ });
+
+ context('using undefined as the parent', () => {
+ 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'}, undefined, []);
+ tree.addNewNode('some new node', {data: 'interesting'}, undefined, ['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', () => {
+ beforeEach(() => {
+ tree.addNewNode('parent node', {data: 'parent data'}, undefined, []);
+ tree.addNewNode('some new node', {data: 'interesting'}, undefined, ['parent' +
+ ' node']);
+ });
+
+ it('does not add a new child', () => {
+ tree.addNewNode('some new node', {data: 'interesting 1'}, undefined, ['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'}, undefined, ['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', {}, undefined, []);
+
+ 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', {}, undefined, []);
+ tree.addNewNode('some id', {}, undefined, ['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', {}, []);
+ let treeNode = new TreeNode('123', {}, [], parentNode);
+ expect(treeNode.hasParent()).toBe(true);
+ });
+ });
+ });
+
+ describe('#reload', () => {
+ let tree;
+ let level2;
+ beforeEach(() => {
+ tree = new TreeFake();
+ tree.addNewNode('level1', {data: 'interesting'}, [{id: 'level1'}], []);
+ level2 = tree.addNewNode('level2', {data: 'data'}, [{id: 'level2'}], ['level1']);
+ tree.addNewNode('level3', {data: 'more data'}, [{id: 'level3'}], ['level1', 'level2']);
+
+ tree.aciTreeApi = jasmine.createSpyObj(
+ 'ACITreeApi', ['setInode', 'unload', 'deselect', 'select']);
+ });
+
+ it('reloads the node and its children', () => {
+ level2.reload(tree);
+ expect(tree.findNodeByDomElement([{id: 'level2'}])).toEqual(level2);
+ });
+
+ it('does not reload the children of node', () => {
+ level2.reload(tree);
+ expect(tree.findNodeByDomElement([{id: 'level3'}])).toBeNull();
+ });
+
+ it('select the node', (done) => {
+ level2.reload(tree);
+ setTimeout(() => {
+ expect(tree.selected()).toEqual([{id: 'level2'}]);
+ done();
+ }, 20);
+ });
+
+ describe('ACITree specific', () => {
+ it('sets the current node as a Inode, changing the Icon back to +', () => {
+ level2.reload(tree);
+ expect(tree.aciTreeApi.setInode).toHaveBeenCalledWith([{id: 'level2'}]);
+ });
+
+ it('deselect the node and selects it again to trigger ACI tree' +
+ ' events', (done) => {
+ level2.reload(tree);
+ setTimeout(() => {
+ expect(tree.aciTreeApi.deselect).toHaveBeenCalledWith([{id: 'level2'}]);
+ done();
+ }, 20);
+ });
+ });
+ });
+
+ describe('#unload', () => {
+ let tree;
+ let level2;
+ beforeEach(() => {
+ tree = new TreeFake();
+ tree.addNewNode('level1', {data: 'interesting'}, ['<li>level1</li>'], []);
+ level2 = tree.addNewNode('level2', {data: 'data'}, ['<li>level2</li>'], ['level1']);
+ tree.addNewNode('level3', {data: 'more data'}, ['<li>level3</li>'], ['level1', 'level2']);
+ tree.aciTreeApi = jasmine.createSpyObj('ACITreeApi', ['unload']);
+ });
+
+ it('unloads the children of the current node', () => {
+ level2.unload(tree);
+ expect(tree.findNodeByDomElement([{id: 'level2'}])).toEqual(level2);
+ expect(tree.findNodeByDomElement([{id: 'level3'}])).toBeNull();
+ });
+
+ it('calls unload on the ACI Tree', () => {
+ level2.unload(tree);
+ expect(tree.aciTreeApi.unload).toHaveBeenCalledWith(['<li>level2</li>']);
+ });
+ });
+ });
+
+ 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.selectNode(selectedNode);
+ }
+
+ treeTests(TreeFake, fakeTreeSelectNode);
+
+ describe('#hasParent', () => {
+ context('tree contains multiple levels', () => {
+ let tree;
+ beforeEach(() => {
+ tree = new TreeFake();
+ tree.addNewNode('level1', {data: 'interesting'}, undefined, []);
+ tree.addNewNode('level2', {data: 'interesting'}, undefined, ['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'}, undefined, []);
+ tree.addNewNode('level2', {data: 'interesting'}, undefined, ['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'}, undefined, []);
+ tree.addNewNode('level2', {data: 'expected data'}, undefined, ['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();
+ });
+ });
+ });
+
+ describe('#addChild', () => {
+ let root, child;
+ beforeEach(() => {
+ let tree = new TreeFake();
+ root = tree.addNewNode('root', {}, [{id: 'root'}]);
+ child = new TreeNode('node.1', {}, [{id: 'node.1'}]);
+ tree.addChild(root, child);
+ });
+
+ it('adds a new child to a node', () => {
+ expect(root.children).toEqual([child]);
+ });
+
+ it('changes the parent of the child node', () => {
+ expect(root.children[0].parentNode).toEqual(root);
+ expect(child.parentNode).toEqual(root);
+ });
+
+ it('changes the path of the child', () => {
+ expect(child.path).toEqual('root.node.1');
+ });
+ });
+ });
+});
+
--
2.16.2
[application/octet-stream] 0003-Extract-add-tests-and-refactor-code-to-use-the-new-T.patch (271.9K, 5-0003-Extract-add-tests-and-refactor-code-to-use-the-new-T.patch)
download | inline diff:
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/collations/static/js/collation.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/collations/static/js/collation.js
index 9015d8d2..5a3a4bc0 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/collations/static/js/collation.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/collations/static/js/collation.js
@@ -1,8 +1,9 @@
define('pgadmin.node.collation', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
'underscore.string', 'sources/pgadmin', 'pgadmin.browser',
+ 'sources/menu/can_create',
'pgadmin.browser.collection',
-], function(gettext, url_for, $, _, S, pgAdmin, pgBrowser) {
+], function(gettext, url_for, $, _, S, pgAdmin, pgBrowser, canCreate) {
if (!pgBrowser.Nodes['coll-collation']) {
pgAdmin.Browser.Nodes['coll-collation'] =
@@ -223,32 +224,7 @@ define('pgadmin.node.collation', [
},
}),
canCreate: function(itemData, item, data) {
- //If check is false then , we will allow create menu
- if (data && data.check == false)
- return true;
-
- var t = pgBrowser.tree, i = item, d = itemData;
- // To iterate over tree to check parent node
- while (i) {
- // If it is schema then allow user to create collation
- if (_.indexOf(['schema'], d._type) > -1)
- return true;
-
- if ('coll-collation' == d._type) {
- //Check if we are not child of catalog
- var prev_i = t.hasParent(i) ? t.parent(i) : null,
- prev_d = prev_i ? t.itemData(prev_i) : null;
- if( prev_d._type == 'catalog') {
- return false;
- } else {
- return true;
- }
- }
- i = t.hasParent(i) ? t.parent(i) : null;
- d = i ? t.itemData(i) : null;
- }
- // by default we do not want to allow create menu
- return true;
+ return canCreate.canCreate(pgBrowser, 'coll-collation', item, data);
},
});
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/domains/static/js/domain.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/domains/static/js/domain.js
index 403ca471..1a38b083 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/domains/static/js/domain.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/domains/static/js/domain.js
@@ -2,9 +2,11 @@
define('pgadmin.node.domain', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', 'pgadmin.backgrid',
+ 'sources/menu/can_create',
'pgadmin.browser.collection',
], function(
- gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser, Backform, Backgrid
+ gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser, Backform, Backgrid,
+ canCreate
) {
// Define Domain Collection Node
@@ -297,32 +299,7 @@ define('pgadmin.node.domain', [
},
}),
canCreate: function(itemData, item, data) {
- //If check is false then , we will allow create menu
- if (data && data.check == false)
- return true;
-
- var t = pgBrowser.tree, i = item, d = itemData;
- // To iterate over tree to check parent node
- while (i) {
- // If it is schema then allow user to create domain
- if (_.indexOf(['schema'], d._type) > -1)
- return true;
-
- if ('coll-domain' == d._type) {
- //Check if we are not child of catalog
- var prev_i = t.hasParent(i) ? t.parent(i) : null,
- prev_d = prev_i ? t.itemData(prev_i) : null;
- if( prev_d._type == 'catalog') {
- return false;
- } else {
- return true;
- }
- }
- i = t.hasParent(i) ? t.parent(i) : null;
- d = i ? t.itemData(i) : null;
- }
- // by default we do not want to allow create menu
- return true;
+ return canCreate.canCreate(pgBrowser, 'coll-domain', item, data);
},
isDisabled: function(m){
if (!m.isNew()) {
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/static/js/foreign_table.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/static/js/foreign_table.js
index 160db83f..0e820343 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/static/js/foreign_table.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/static/js/foreign_table.js
@@ -2,9 +2,11 @@
define('pgadmin.node.foreign_table', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', 'pgadmin.backgrid',
+ 'sources/menu/can_create',
'pgadmin.browser.collection',
], function(
- gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser, Backform, Backgrid
+ gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser, Backform, Backgrid,
+ canCreate
) {
if (!pgBrowser.Nodes['coll-foreign_table']) {
@@ -660,32 +662,7 @@ define('pgadmin.node.foreign_table', [
},
}),
canCreate: function(itemData, item, data) {
- //If check is false then , we will allow create menu
- if (data && data.check == false)
- return true;
-
- var t = pgBrowser.tree, i = item, d = itemData;
- // To iterate over tree to check parent node
- while (i) {
- // If it is schema then allow user to create foreign table
- if (_.indexOf(['schema'], d._type) > -1)
- return true;
-
- if ('coll-foreign_table' == d._type) {
- //Check if we are not child of catalog
- var prev_i = t.hasParent(i) ? t.parent(i) : null,
- prev_d = prev_i ? t.itemData(prev_i) : null;
- if( prev_d._type == 'catalog') {
- return false;
- } else {
- return true;
- }
- }
- i = t.hasParent(i) ? t.parent(i) : null;
- d = i ? t.itemData(i) : null;
- }
- // by default we do not want to allow create menu
- return true;
+ return canCreate.canCreate(pgBrowser, 'coll-foreign_table', item, data);
},
});
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/static/js/fts_configuration.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/static/js/fts_configuration.js
index 89806681..f2fe85be 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/static/js/fts_configuration.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/static/js/fts_configuration.js
@@ -1,9 +1,11 @@
define('pgadmin.node.fts_configuration', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', 'pgadmin.backgrid',
+ 'sources/menu/can_create',
'pgadmin.browser.collection',
], function(
- gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser, Backform, Backgrid
+ gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser, Backform, Backgrid,
+ canCreate
) {
// Model for tokens control
@@ -578,32 +580,7 @@ define('pgadmin.node.fts_configuration', [
},
}),
canCreate: function(itemData, item, data) {
- //If check is false then , we will allow create menu
- if (data && data.check == false)
- return true;
-
- var t = pgBrowser.tree, i = item, d = itemData;
- // To iterate over tree to check parent node
- while (i) {
- // If it is schema then allow user to create fts configuration
- if (_.indexOf(['schema'], d._type) > -1)
- return true;
-
- if ('coll-fts_configuration' == d._type) {
- //Check if we are not child of catalog
- var prev_i = t.hasParent(i) ? t.parent(i) : null,
- prev_d = prev_i ? t.itemData(prev_i) : null;
- if( prev_d._type == 'catalog') {
- return false;
- } else {
- return true;
- }
- }
- i = t.hasParent(i) ? t.parent(i) : null;
- d = i ? t.itemData(i) : null;
- }
- // by default we do not want to allow create menu
- return true;
+ return canCreate.canCreate(pgBrowser, 'coll-fts_configuration', item, data);
},
});
}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/static/js/fts_dictionary.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/static/js/fts_dictionary.js
index ed83feb1..f1a330f7 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/static/js/fts_dictionary.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/static/js/fts_dictionary.js
@@ -1,8 +1,10 @@
define('pgadmin.node.fts_dictionary', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
'underscore.string', 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform',
+ 'sources/menu/can_create',
'pgadmin.browser.collection',
-], function(gettext, url_for, $, _, S, pgAdmin, pgBrowser, Backform) {
+], function(gettext, url_for, $, _, S, pgAdmin, pgBrowser, Backform,
+ canCreate) {
// Extend the browser's node model class to create a option/value pair
var OptionLabelModel = pgAdmin.Browser.Node.Model.extend({
@@ -187,32 +189,7 @@ define('pgadmin.node.fts_dictionary', [
},
}),
canCreate: function(itemData, item, data) {
- //If check is false then , we will allow create menu
- if (data && data.check == false)
- return true;
-
- var t = pgBrowser.tree, i = item, d = itemData;
- // To iterate over tree to check parent node
- while (i) {
- // If it is schema then allow user to create fts dictionary
- if (_.indexOf(['schema'], d._type) > -1)
- return true;
-
- if ('coll-fts_dictionary' == d._type) {
- //Check if we are not child of catalog
- var prev_i = t.hasParent(i) ? t.parent(i) : null,
- prev_d = prev_i ? t.itemData(prev_i) : null;
- if( prev_d._type == 'catalog') {
- return false;
- } else {
- return true;
- }
- }
- i = t.hasParent(i) ? t.parent(i) : null;
- d = i ? t.itemData(i) : null;
- }
- // by default we do not want to allow create menu
- return true;
+ return canCreate.canCreate(pgBrowser, 'coll-fts_dictionary', item, data);
},
});
}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/static/js/fts_parser.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/static/js/fts_parser.js
index 92c0786e..daea35de 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/static/js/fts_parser.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_parser/static/js/fts_parser.js
@@ -1,7 +1,9 @@
define('pgadmin.node.fts_parser', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
- 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.browser.collection',
-], function(gettext, url_for, $, _, pgAdmin, pgBrowser) {
+ 'sources/pgadmin', 'pgadmin.browser',
+ 'sources/menu/can_create',
+ 'pgadmin.browser.collection',
+], function(gettext, url_for, $, _, pgAdmin, pgBrowser, canCreate) {
// Extend the collection class for fts parser
if (!pgBrowser.Nodes['coll-fts_parser']) {
@@ -200,32 +202,7 @@ define('pgadmin.node.fts_parser', [
},
}),
canCreate: function(itemData, item, data) {
- //If check is false then , we will allow create menu
- if (data && data.check == false)
- return true;
-
- var t = pgBrowser.tree, i = item, d = itemData;
- // To iterate over tree to check parent node
- while (i) {
- // If it is schema then allow user to create fts parser
- if (_.indexOf(['schema'], d._type) > -1)
- return true;
-
- if ('coll-fts_parser' == d._type) {
- //Check if we are not child of catalog
- var prev_i = t.hasParent(i) ? t.parent(i) : null,
- prev_d = prev_i ? t.itemData(prev_i) : null;
- if( prev_d._type == 'catalog') {
- return false;
- } else {
- return true;
- }
- }
- i = t.hasParent(i) ? t.parent(i) : null;
- d = i ? t.itemData(i) : null;
- }
- // by default we do not want to allow create menu
- return true;
+ return canCreate.canCreate(pgBrowser, 'coll-fts_parser', item, data);
},
});
}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/static/js/fts_template.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/static/js/fts_template.js
index 606a57a6..0c008c83 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/static/js/fts_template.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/static/js/fts_template.js
@@ -1,7 +1,9 @@
define('pgadmin.node.fts_template', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
- 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.browser.collection',
-], function(gettext, url_for, $, _, pgAdmin, pgBrowser) {
+ 'sources/pgadmin', 'pgadmin.browser',
+ 'sources/menu/can_create',
+ 'pgadmin.browser.collection',
+], function(gettext, url_for, $, _, pgAdmin, pgBrowser, canCreate) {
// Extend the collection class for fts template
if (!pgBrowser.Nodes['coll-fts_template']) {
@@ -140,32 +142,7 @@ define('pgadmin.node.fts_template', [
},
}),
canCreate: function(itemData, item, data) {
- //If check is false then , we will allow create menu
- if (data && data.check == false)
- return true;
-
- var t = pgBrowser.tree, i = item, d = itemData;
- // To iterate over tree to check parent node
- while (i) {
- // If it is schema then allow user to create fts fts_template
- if (_.indexOf(['schema'], d._type) > -1)
- return true;
-
- if ('coll-fts_template' == d._type) {
- //Check if we are not child of catalog
- var prev_i = t.hasParent(i) ? t.parent(i) : null,
- prev_d = prev_i ? t.itemData(prev_i) : null;
- if( prev_d._type == 'catalog') {
- return false;
- } else {
- return true;
- }
- }
- i = t.hasParent(i) ? t.parent(i) : null;
- d = i ? t.itemData(i) : null;
- }
- // by default we do not want to allow create menu
- return true;
+ return canCreate.canCreate(pgBrowser, 'coll-fts_template', item, data);
},
});
}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/function.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/function.js
index 6e405165..40636446 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/function.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/function.js
@@ -2,8 +2,10 @@
define('pgadmin.node.function', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform',
+ 'sources/menu/can_create',
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
-], function(gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser, Backform) {
+], function(gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser, Backform,
+ canCreate) {
if (!pgBrowser.Nodes['coll-function']) {
pgBrowser.Nodes['coll-function'] =
@@ -439,32 +441,7 @@ define('pgadmin.node.function', [
},
}),
canCreate: function(itemData, item, data) {
- //If check is false then , we will allow create menu
- if (data && data.check == false)
- return true;
-
- var t = pgBrowser.tree, i = item, d = itemData;
- // To iterate over tree to check parent node
- while (i) {
- // If it is schema then allow user to create Function
- if (_.indexOf(['schema'], d._type) > -1)
- return true;
-
- if ('coll-function' == d._type) {
- //Check if we are not child of catalog
- var prev_i = t.hasParent(i) ? t.parent(i) : null,
- prev_d = prev_i ? t.itemData(prev_i) : null;
- if( prev_d._type == 'catalog') {
- return false;
- } else {
- return true;
- }
- }
- i = t.hasParent(i) ? t.parent(i) : null;
- d = i ? t.itemData(i) : null;
- }
- // by default we do not want to allow create menu
- return true;
+ return canCreate.canCreate(pgBrowser, 'coll-function', item, data);
},
});
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/trigger_function.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/trigger_function.js
index aeb8271b..11b587ed 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/trigger_function.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/trigger_function.js
@@ -2,8 +2,9 @@
define('pgadmin.node.trigger_function', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform',
+ 'sources/menu/can_create',
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
-], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform) {
+], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform, canCreate) {
if (!pgBrowser.Nodes['coll-trigger_function']) {
pgBrowser.Nodes['coll-trigger_function'] =
@@ -358,32 +359,7 @@ define('pgadmin.node.trigger_function', [
},
}),
canCreate: function(itemData, item, data) {
- //If check is false then , we will allow create menu
- if (data && data.check == false)
- return true;
-
- var t = pgBrowser.tree, i = item, d = itemData;
- // To iterate over tree to check parent node
- while (i) {
- // If it is schema then allow user to create Function
- if (_.indexOf(['schema'], d._type) > -1)
- return true;
-
- if ('coll-trigger_function' == d._type) {
- //Check if we are not child of catalog
- var prev_i = t.hasParent(i) ? t.parent(i) : null,
- prev_d = prev_i ? t.itemData(prev_i) : null;
- if( prev_d._type == 'catalog') {
- return false;
- } else {
- return true;
- }
- }
- i = t.hasParent(i) ? t.parent(i) : null;
- d = i ? t.itemData(i) : null;
- }
- // by default we do not want to allow create menu
- return true;
+ return canCreate.canCreate(pgBrowser, 'coll-trigger_function', item, data);
},
});
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/sequences/static/js/sequence.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/sequences/static/js/sequence.js
index 57c95acd..0a59efb0 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/sequences/static/js/sequence.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/sequences/static/js/sequence.js
@@ -1,8 +1,10 @@
define('pgadmin.node.sequence', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
'underscore.string', 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform',
+ 'sources/menu/can_create',
'pgadmin.browser.collection',
-], function(gettext, url_for, $, _, S, pgAdmin, pgBrowser, Backform) {
+], function(gettext, url_for, $, _, S, pgAdmin, pgBrowser, Backform,
+ canCreate) {
// Extend the browser's collection class for sequence collection
if (!pgBrowser.Nodes['coll-sequence']) {
@@ -61,32 +63,7 @@ define('pgadmin.node.sequence', [
canDrop: pgBrowser.Nodes['schema'].canChildDrop,
canDropCascade: pgBrowser.Nodes['schema'].canChildDrop,
canCreate: function(itemData, item, data) {
- //If check is false then , we will allow create menu
- if (data && data.check == false)
- return true;
-
- var t = pgBrowser.tree, i = item, d = itemData;
- // To iterate over tree to check parent node
- while (i) {
- // If it is schema then allow user to create collation
- if (_.indexOf(['schema'], d._type) > -1)
- return true;
-
- if ('coll-sequence' == d._type) {
- //Check if we are not child of catalog
- var prev_i = t.hasParent(i) ? t.parent(i) : null,
- prev_d = prev_i ? t.itemData(prev_i) : null;
- if( prev_d._type == 'catalog') {
- return false;
- } else {
- return true;
- }
- }
- i = t.hasParent(i) ? t.parent(i) : null;
- d = i ? t.itemData(i) : null;
- }
- // by default we want to allow create menu
- return true;
+ return canCreate.canCreate(pgBrowser, 'coll-sequence', item, data);
},
// Define the model for sequence node.
model: pgBrowser.Node.Model.extend({
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..6d003578 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
@@ -1,8 +1,10 @@
define('pgadmin.node.schema', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', 'pgadmin.backgrid',
+ 'sources/schema/can_drop_child',
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
-], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform, Backgrid) {
+], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform, Backgrid,
+canDropChild) {
// VacuumSettings Collection to display all settings parameters as Grid
Backform.VacuumCollectionControl =
@@ -428,51 +430,12 @@ define('pgadmin.node.schema', [
// This function will checks whether we can allow user to
// drop object or not based on location within schema & catalog
canChildDrop: function(itemData, item) {
- var t = pgBrowser.tree, i = item, d = itemData;
- // To iterate over tree to check parent node
- while (i) {
- // If it is schema then allow user to create collation
- if (_.indexOf(['schema'], d._type) > -1)
- return true;
-
- //Check if we are not child of catalog
- var prev_i = t.hasParent(i) ? t.parent(i) : null,
- prev_d = prev_i ? t.itemData(prev_i) : null;
- if(prev_d && prev_d._type == 'catalog') {
- return false;
- }
- i = t.hasParent(i) ? t.parent(i) : null;
- d = i ? t.itemData(i) : null;
- }
- // by default we do not want to allow create menu
- return true;
+ return canDropChild.canDropChild(pgBrowser, itemData, item);
},
});
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..17a0e6ce 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,11 +1,15 @@
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',
+ 'sources/menu/can_create',
'pgadmin.browser.collection', 'pgadmin.browser.table.partition.utils',
],
function(
- gettext, url_for, $, _, S, pgAdmin, pgBrowser, Alertify, Backform, Backgrid
+ pgadminTreeNode,
+ gettext, url_for, $, _, S, pgAdmin, pgBrowser, Alertify, Backform, Backgrid,
+ canCreate
) {
if (!pgBrowser.Nodes['coll-partition']) {
@@ -13,7 +17,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 +83,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',
@@ -1219,32 +1192,7 @@ function(
},
}),
canCreate: function(itemData, item, data) {
- //If check is false then , we will allow create menu
- if (data && data.check == false)
- return true;
-
- var t = pgBrowser.tree, i = item, d = itemData;
- // To iterate over tree to check parent node
- while (i) {
- // If it is schema then allow user to create table
- if (_.indexOf(['schema'], d._type) > -1)
- return true;
-
- if ('coll-table' == d._type) {
- //Check if we are not child of catalog
- var prev_i = t.hasParent(i) ? t.parent(i) : null;
- var prev_d = prev_i ? t.itemData(prev_i) : null;
- if( prev_d._type == 'catalog') {
- return false;
- } else {
- return true;
- }
- }
- i = t.hasParent(i) ? t.parent(i) : null;
- d = i ? t.itemData(i) : null;
- }
- // by default we do not want to allow create menu
- return true;
+ return canCreate.canCreate(pgBrowser, 'coll-table', item, data);
},
// Check to whether table has disable trigger(s)
canCreate_with_trigger_enable: function(itemData, item, data) {
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..c050c469 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
@@ -1,13 +1,17 @@
define('pgadmin.node.table', [
+ 'sources/table',
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
'underscore.string', 'sources/pgadmin', 'pgadmin.browser',
'pgadmin.alertifyjs', 'pgadmin.backform', 'pgadmin.backgrid',
'pgadmin.tables.js/show_advanced_tab',
+ 'sources/menu/can_create',
+
'pgadmin.browser.collection', 'pgadmin.node.column',
'pgadmin.node.constraints', 'pgadmin.browser.table.partition.utils',
], function(
+ tableFunctions,
gettext, url_for, $, _, S, pgAdmin, pgBrowser, Alertify, Backform, Backgrid,
- ShowAdvancedTab
+ ShowAdvancedTab, canCreate
) {
if (!pgBrowser.Nodes['coll-table']) {
@@ -26,7 +30,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',
@@ -118,53 +121,21 @@ define('pgadmin.node.table', [
callbacks: {
/* Enable trigger(s) on table */
enable_triggers_on_table: function(args) {
- var params = {'enable': true };
- this.callbacks.set_triggers.apply(this, [args, params]);
+ tableFunctions.enableTriggers(
+ pgBrowser.treeMenu,
+ Alertify,
+ this.generate_url.bind(this),
+ args
+ );
},
/* Disable trigger(s) on table */
disable_triggers_on_table: function(args) {
- var params = {'enable': false };
- this.callbacks.set_triggers.apply(this, [args, params]);
- },
- set_triggers: function(args, params) {
- // This function will send request to enable or
- // disable triggers on table level
- var input = args || {},
- obj = this,
- t = pgBrowser.tree,
- i = input.item || t.selected(),
- d = i && i.length == 1 ? t.itemData(i) : undefined;
- if (!d)
- return false;
-
- $.ajax({
- url: obj.generate_url(i, 'set_trigger' , d, true),
- type:'PUT',
- data: params,
- dataType: 'json',
- success: function(res) {
- if (res.success == 1) {
- Alertify.success(res.info);
- t.unload(i);
- t.setInode(i);
- t.deselect(i);
- setTimeout(function() {
- t.select(i);
- }, 10);
- }
- },
- error: function(xhr) {
- try {
- var err = $.parseJSON(xhr.responseText);
- if (err.success == 0) {
- Alertify.error(err.errormsg);
- }
- } catch (e) {
- console.warn(e.stack || e);
- }
- t.unload(i);
- },
- });
+ tableFunctions.disableTriggers(
+ pgBrowser.treeMenu,
+ Alertify,
+ this.generate_url.bind(this),
+ args
+ );
},
/* Truncate table */
truncate_table: function(args) {
@@ -1329,32 +1300,7 @@ define('pgadmin.node.table', [
},
}),
canCreate: function(itemData, item, data) {
- //If check is false then , we will allow create menu
- if (data && data.check == false)
- return true;
-
- var t = pgBrowser.tree, i = item, d = itemData;
- // To iterate over tree to check parent node
- while (i) {
- // If it is schema then allow user to create table
- if (_.indexOf(['schema'], d._type) > -1)
- return true;
-
- if ('coll-table' == d._type) {
- //Check if we are not child of catalog
- var prev_i = t.hasParent(i) ? t.parent(i) : null,
- prev_d = prev_i ? t.itemData(prev_i) : null;
- if( prev_d._type == 'catalog') {
- return false;
- } else {
- return true;
- }
- }
- i = t.hasParent(i) ? t.parent(i) : null;
- d = i ? t.itemData(i) : null;
- }
- // by default we do not want to allow create menu
- return true;
+ return canCreate.canCreate(pgBrowser, 'coll-table', item, data);
},
// Check to whether table has disable trigger(s)
canCreate_with_trigger_enable: function(itemData, item, data) {
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/server_groups/servers/databases/schemas/types/static/js/type.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/static/js/type.js
index c1c24861..91ce615f 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/static/js/type.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/static/js/type.js
@@ -1,8 +1,9 @@
define('pgadmin.node.type', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform',
- 'pgadmin.backgrid', 'pgadmin.browser.collection',
-], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform, Backgrid) {
+ 'pgadmin.backgrid', 'sources/menu/can_create', 'pgadmin.browser.collection',
+], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform, Backgrid,
+ canCreate) {
if (!pgBrowser.Nodes['coll-type']) {
pgBrowser.Nodes['coll-type'] =
@@ -912,32 +913,7 @@ define('pgadmin.node.type', [
},
}),
canCreate: function(itemData, item, data) {
- //If check is false then , we will allow create menu
- if (data && data.check == false)
- return true;
-
- var t = pgBrowser.tree, i = item, d = itemData;
- // To iterate over tree to check parent node
- while (i) {
- // If it is schema then allow user to create table
- if (_.indexOf(['schema'], d._type) > -1)
- return true;
-
- if ('coll-type' == d._type) {
- //Check if we are not child of catalog
- var prev_i = t.hasParent(i) ? t.parent(i) : null,
- prev_d = prev_i ? t.itemData(prev_i) : null;
- if( prev_d._type == 'catalog') {
- return false;
- } else {
- return true;
- }
- }
- i = t.hasParent(i) ? t.parent(i) : null;
- d = i ? t.itemData(i) : null;
- }
- // by default we do not want to allow create menu
- return true;
+ return canCreate.canCreate(pgBrowser, 'coll-type', item, data);
},
});
}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/mview.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/mview.js
index 795bbfcf..15a517b8 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/mview.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/mview.js
@@ -1,8 +1,11 @@
define('pgadmin.node.mview', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
'sources/pgadmin', 'pgadmin.alertifyjs', 'pgadmin.browser',
- 'pgadmin.backform', 'pgadmin.browser.server.privilege',
-], function(gettext, url_for, $, _, pgAdmin, Alertify, pgBrowser, Backform) {
+ 'pgadmin.backform',
+ 'sources/menu/can_create',
+ 'pgadmin.browser.server.privilege',
+], function(gettext, url_for, $, _, pgAdmin, Alertify, pgBrowser, Backform,
+ canCreate) {
/**
Create and add a view collection into nodes
@@ -241,37 +244,7 @@ define('pgadmin.node.mview', [
and hide for system view in catalogs.
*/
canCreate: function(itemData, item, data) {
-
- // If check is false then, we will allow create menu
- if (data && data.check === false)
- return true;
-
- var t = pgBrowser.tree, i = item, d = itemData;
-
- // To iterate over tree to check parent node
- while (i) {
-
- // If it is schema then allow user to create view
- if (_.indexOf(['schema'], d._type) > -1)
- return true;
-
- if ('coll-mview' == d._type) {
-
- // Check if we are not child of view
- var prev_i = t.hasParent(i) ? t.parent(i) : null,
- prev_d = prev_i ? t.itemData(prev_i) : null;
- if( prev_d._type == 'catalog') {
- return false;
- } else {
- return true;
- }
- }
- i = t.hasParent(i) ? t.parent(i) : null;
- d = i ? t.itemData(i) : null;
- }
-
- // by default we do not want to allow create menu
- return true;
+ return canCreate.canCreate(pgBrowser, 'coll-mview', item, data);
},
refresh_mview: function(args) {
var input = args || {},
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.js
index 5755a509..e4258188 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.js
@@ -1,9 +1,12 @@
define('pgadmin.node.view', [
'sources/gettext',
'sources/url_for', 'jquery', 'underscore', 'sources/pgadmin',
- 'pgadmin.browser', 'pgadmin.backform', 'pgadmin.browser.server.privilege',
+ 'pgadmin.browser', 'pgadmin.backform',
+ 'sources/menu/can_create',
+ 'pgadmin.browser.server.privilege',
'pgadmin.node.rule',
-], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform) {
+], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform,
+ canCreate) {
/**
Create and add a view collection into nodes
@@ -203,38 +206,7 @@ define('pgadmin.node.view', [
and hide for system view in catalogs.
*/
canCreate: function(itemData, item, data) {
-
- // If check is false then, we will allow create menu
- if (data && data.check == false)
- return true;
-
- var t = pgBrowser.tree, i = item, d = itemData;
-
- // To iterate over tree to check parent node
- while (i) {
-
- // If it is schema then allow user to create view
- if (_.indexOf(['schema'], d._type) > -1)
- return true;
-
- if ('coll-view' == d._type) {
-
- // Check if we are not child of view
- var prev_i = t.hasParent(i) ? t.parent(i) : null,
- prev_d = prev_i ? t.itemData(prev_i) : null;
- if( prev_d._type == 'catalog') {
- return false;
- } else {
- return true;
- }
- }
- i = t.hasParent(i) ? t.parent(i) : null;
- d = i ? t.itemData(i) : null;
- }
-
- // by default we do not want to allow create menu
- return true;
-
+ return canCreate.canCreate(pgBrowser, 'coll-view', item, data);
},
});
}
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/alertify/dialog.js b/web/pgadmin/static/js/alertify/dialog.js
new file mode 100644
index 00000000..958296c3
--- /dev/null
+++ b/web/pgadmin/static/js/alertify/dialog.js
@@ -0,0 +1,107 @@
+/////////////////////////////////////////////////////////////
+//
+// 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 {DialogFactory} from './dialog_factory';
+import Backform from '../backform.pgadmin';
+
+export class Dialog {
+ constructor(errorAlertTitle,
+ dialogContainerSelector,
+ pgBrowser, $, alertify, DialogModel,
+ backform = Backform) {
+ this.errorAlertTitle = errorAlertTitle;
+ this.alertify = alertify;
+ this.pgBrowser = pgBrowser;
+ this.jquery = $;
+ this.dialogModel = DialogModel;
+ this.backform = backform;
+ this.dialogContainerSelector = dialogContainerSelector;
+ }
+
+ 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(this.errorAlertTitle),
+ gettext('Please select server or child node from the browser tree.')
+ );
+ break;
+ }
+ }
+ return serverInformation;
+ }
+
+ 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(this.errorAlertTitle),
+ sprintf(gettext('Failed to load preference %s of module %s'), preference_name, module)
+ );
+ return false;
+ }
+ return true;
+ }
+
+ dialogName() {
+ return undefined;
+ }
+
+ createOrGetDialog(dialogTitle, typeOfDialog) {
+ const dialogName = this.dialogName(typeOfDialog);
+
+ if (!this.alertify[dialogName]) {
+ const self = this;
+ this.alertify.dialog(dialogName, function factory() {
+ return self.dialogFactory(dialogTitle, typeOfDialog);
+ });
+ }
+ return this.alertify[dialogName];
+ }
+
+ dialogFactory(dialogTitle, typeOfDialog) {
+ const factory = new DialogFactory(
+ this.pgBrowser,
+ this.jquery,
+ this.alertify,
+ this.dialogModel,
+ this.backform,
+ this.dialogContainerSelector);
+ return factory.create(dialogTitle, typeOfDialog);
+ }
+}
diff --git a/web/pgadmin/static/js/alertify/dialog_factory.js b/web/pgadmin/static/js/alertify/dialog_factory.js
new file mode 100644
index 00000000..806f8687
--- /dev/null
+++ b/web/pgadmin/static/js/alertify/dialog_factory.js
@@ -0,0 +1,52 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import * as BackupDialog from '../backup/backup_dialog_wrapper';
+import {RestoreDialogWrapper} from '../restore/restore_dialog_wrapper';
+
+export class DialogFactory {
+ constructor(pgBrowser, $,
+ alertify, DialogModel,
+ backform, dialogContainerSelector) {
+ this.pgBrowser = pgBrowser;
+ this.jquery = $;
+ this.alertify = alertify;
+ this.dialogModel = DialogModel;
+ this.backform = backform;
+ this.dialogContainerSelector = dialogContainerSelector;
+ }
+
+ create(dialogTitle, typeOfDialog) {
+ if (typeOfDialog === 'restore') {
+ return this.createRestoreDialog(dialogTitle, typeOfDialog);
+ } else {
+ return this.createBackupDialog(dialogTitle, typeOfDialog);
+ }
+ }
+
+ createRestoreDialog(dialogTitle, typeOfDialog) {
+ return new RestoreDialogWrapper(
+ this.dialogContainerSelector, dialogTitle, typeOfDialog,
+ this.jquery,
+ this.pgBrowser,
+ this.alertify,
+ this.dialogModel,
+ this.backform);
+ }
+
+ createBackupDialog(dialogTitle, typeOfDialog) {
+ return new BackupDialog.BackupDialogWrapper(
+ this.dialogContainerSelector, dialogTitle, typeOfDialog,
+ this.jquery,
+ this.pgBrowser,
+ this.alertify,
+ this.dialogModel,
+ this.backform);
+ }
+}
diff --git a/web/pgadmin/static/js/alertify/dialog_wrapper.js b/web/pgadmin/static/js/alertify/dialog_wrapper.js
new file mode 100644
index 00000000..b5ff8204
--- /dev/null
+++ b/web/pgadmin/static/js/alertify/dialog_wrapper.js
@@ -0,0 +1,57 @@
+import * as commonUtils from '../utils';
+
+export class DialogWrapper {
+ constructor(
+ dialogContainerSelector, dialogTitle, jquery, pgBrowser,
+ alertify, dialogModel, backform) {
+ this.hooks = {
+ onclose: function () {
+ if (this.view) {
+ this.view.remove({
+ data: true,
+ internal: true,
+ silent: true,
+ });
+ }
+ },
+ };
+ this.dialogContainerSelector = dialogContainerSelector;
+ this.dialogTitle = dialogTitle;
+ this.jquery = jquery;
+ this.pgBrowser = pgBrowser;
+ this.alertify = alertify;
+ this.dialogModel = dialogModel;
+ this.backform = backform;
+ }
+
+ build() {
+ this.alertify.pgDialogBuild.apply(this);
+ }
+
+ wasHelpButtonPressed(e) {
+ return e.button.element.name === 'dialog_help'
+ || e.button.element.name === 'object_help';
+ }
+
+ getSelectedNodeData(selectedTreeNode) {
+ if (!this.isNodeSelected(selectedTreeNode)) {
+ return undefined;
+ }
+ const treeNodeData = selectedTreeNode.getData();
+ if (treeNodeData) {
+ return treeNodeData;
+ }
+ return undefined;
+ }
+
+ focusOnDialog(dialog) {
+ dialog.$el.attr('tabindex', -1);
+ this.pgBrowser.keyboardNavigation.getDialogTabNavigator(dialog);
+ const container = dialog.$el.find('.tab-content:first > .tab-pane.active:first');
+ commonUtils.findAndSetFocus(container);
+ }
+
+ isNodeSelected(selectedTreeNode) {
+ return selectedTreeNode;
+ }
+}
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..aa4461f5
--- /dev/null
+++ b/web/pgadmin/static/js/backup/backup_dialog.js
@@ -0,0 +1,65 @@
+/////////////////////////////////////////////////////////////
+//
+// 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 Backform from '../backform.pgadmin';
+import {Dialog} from '../alertify/dialog';
+
+export class BackupDialog extends Dialog {
+ constructor(pgBrowser, $, alertify, BackupModel, backform = Backform) {
+ super('Backup Error',
+ '<div class=\'backup_dialog\'></div>',
+ pgBrowser, $, alertify, BackupModel, backform);
+ }
+
+ draw(action, aciTreeItem, params) {
+ const serverInformation = this.retrieveAncestorOfTypeServer(aciTreeItem);
+
+ if (!serverInformation) {
+ return;
+ }
+
+ if (!this.hasBinariesConfiguration(serverInformation)) {
+ return;
+ }
+
+ const typeOfDialog = BackupDialog.typeOfDialog(params);
+
+ const dialog = this.createOrGetDialog(BackupDialog.dialogTitle(typeOfDialog),
+ 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...'));
+ }
+
+ dialogName(typeOfDialog) {
+ if(typeOfDialog === 'backup_objects') {
+ return typeOfDialog;
+ }
+ return 'BackupDialog_' + typeOfDialog;
+ }
+}
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..7b6250f7
--- /dev/null
+++ b/web/pgadmin/static/js/backup/backup_dialog_wrapper.js
@@ -0,0 +1,258 @@
+/////////////////////////////////////////////////////////////
+//
+// 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 gettext from '../gettext';
+import url_for from '../url_for';
+import _ from 'underscore';
+import {DialogWrapper} from '../alertify/dialog_wrapper';
+
+export class BackupDialogWrapper extends DialogWrapper {
+ constructor(dialogContainerSelector, dialogTitle, typeOfDialog,
+ jquery, pgBrowser, alertify, dialogModel, backform) {
+ super(dialogContainerSelector, dialogTitle, jquery,
+ pgBrowser, alertify, dialogModel, backform);
+ this.typeOfDialog = typeOfDialog;
+ }
+
+ main(title) {
+ this.set('title', title);
+ }
+
+ 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.jquery(this.dialogContainerSelector);
+ const selectedTreeNode = this.getSelectedNode();
+ const selectedTreeNodeData = this.getSelectedNodeData(selectedTreeNode);
+ if (!selectedTreeNodeData) {
+ return;
+ }
+
+ const node = this.pgBrowser.Nodes[selectedTreeNodeData._type];
+ if (this.dialogTitle === null) {
+ const title = `Backup (${node.label}: ${selectedTreeNodeData.label})`;
+ this.main(title);
+ }
+
+ const treeInfo = getTreeNodeHierarchyFromElement(this.pgBrowser, selectedTreeNode);
+ const dialog = this.createDialog(node, treeInfo, this.typeOfDialog, $container);
+ this.addAlertifyClassToBackupNodeChildNodes();
+ dialog.render();
+
+ this.elements.content.appendChild($container.get(0));
+
+ this.focusOnDialog(dialog);
+ this.setListenersForFilenameChanges();
+ }
+
+ callback(event) {
+ const selectedTreeNode = this.getSelectedNode();
+ const selectedTreeNodeData = this.getSelectedNodeData(selectedTreeNode);
+ const node = selectedTreeNodeData && this.pgBrowser.Nodes[selectedTreeNodeData._type];
+
+ if (this.wasHelpButtonPressed(event)) {
+ event.cancel = true;
+ this.pgBrowser.showHelp(
+ event.button.element.name,
+ event.button.element.getAttribute('url'),
+ node,
+ selectedTreeNode,
+ event.button.element.getAttribute('label')
+ );
+ return;
+ }
+
+ if (this.wasBackupButtonPressed(event)) {
+
+ if (!selectedTreeNodeData)
+ return;
+
+ const serverIdentifier = this.retrieveServerIdentifier(node, selectedTreeNode);
+
+ const dialog = this;
+ let urlShortcut = 'backup.create_server_job';
+ if (this.typeOfDialog === 'backup_objects') {
+ urlShortcut = 'backup.create_object_job';
+ }
+ const baseUrl = url_for(urlShortcut, {
+ 'sid': serverIdentifier,
+ });
+
+ const treeInfo = getTreeNodeHierarchyFromElement(
+ this.pgBrowser,
+ selectedTreeNode
+ );
+
+ this.setExtraParameters(selectedTreeNode, treeInfo);
+
+ let service = axios.create({});
+ service.post(
+ baseUrl,
+ this.view.model.toJSON()
+ ).then(function () {
+ dialog.alertify.success(gettext('Backup job created.'), 5);
+ dialog.pgBrowser.Events.trigger('pgadmin-bgprocess:created', dialog);
+ }).catch(function (error) {
+ try {
+ const err = error.response.data;
+ dialog.alertify.alert(
+ gettext('Backup job failed.'),
+ err.errormsg
+ );
+ } catch (e) {
+ console.warn(e.stack || e);
+ }
+ });
+ }
+ }
+
+ addAlertifyClassToBackupNodeChildNodes() {
+ this.jquery(this.elements.body.childNodes[0]).addClass(
+ 'alertify_tools_dialog_properties obj_properties'
+ );
+ }
+
+ getSelectedNode() {
+ const tree = this.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.dialogModel(attributes, {
+ node_info: treeInfo,
+ });
+ const fields = this.backform.generateViewSchema(
+ treeInfo, newModel, 'create', node, treeInfo.server, true
+ );
+
+ return this.view = new this.backform.Dialog({
+ el: $container,
+ model: newModel,
+ schema: fields,
+ });
+ }
+
+ retrieveServerIdentifier(node, selectedTreeNode) {
+ const treeInfo = getTreeNodeHierarchyFromElement(
+ this.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, treeInfo) {
+ if (this.typeOfDialog === 'backup_objects') {
+
+ 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');
+ }
+ }
+ }
+
+ wasBackupButtonPressed(event) {
+ return event.button['data-btn-name'] === 'backup';
+ }
+}
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..d3c1b143
--- /dev/null
+++ b/web/pgadmin/static/js/backup/menu_utils.js
@@ -0,0 +1,23 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import {isProvidedDataValid} from '../menu/menu_enabled';
+
+export const backupSupportedNodes = [
+ 'database', 'schema', 'table', 'partition',
+];
+
+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/grant/wizard/menu_utils.js b/web/pgadmin/static/js/grant/wizard/menu_utils.js
new file mode 100644
index 00000000..b56ce3cd
--- /dev/null
+++ b/web/pgadmin/static/js/grant/wizard/menu_utils.js
@@ -0,0 +1,16 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+export const supportedNodes = [
+ 'schema', 'coll-function', 'coll-sequence',
+ 'coll-table', 'coll-view', 'coll-procedure',
+ 'coll-mview', 'database', 'coll-trigger_function',
+];
+
+
diff --git a/web/pgadmin/static/js/maintenance/menu_utils.js b/web/pgadmin/static/js/maintenance/menu_utils.js
new file mode 100644
index 00000000..8cde1baa
--- /dev/null
+++ b/web/pgadmin/static/js/maintenance/menu_utils.js
@@ -0,0 +1,13 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+export const maintenanceSupportedNodes = [
+ 'database', 'table', 'primary_key',
+ 'unique_constraint', 'index', 'partition',
+];
diff --git a/web/pgadmin/static/js/menu/can_create.js b/web/pgadmin/static/js/menu/can_create.js
new file mode 100644
index 00000000..476be437
--- /dev/null
+++ b/web/pgadmin/static/js/menu/can_create.js
@@ -0,0 +1,37 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+/////////////////////////////////////////////////////////////
+
+export function canCreate(pgBrowser, childOfCatalogType, item, data) {
+ //If check is false then , we will allow create menu
+ if (data && data.check === false) {
+ return true;
+ }
+
+ let node = pgBrowser.treeMenu.findNodeByDomElement(item);
+
+ if (node.anyFamilyMember(parentCatalogOfTableChild.bind(null, childOfCatalogType))) {
+ return false;
+ }
+
+ return true;
+}
+
+function parentCatalogOfTableChild(arg, node) {
+ if (arg === node.getData()._type) {
+ if (node.hasParent()) {
+
+ let parent = node.parent();
+ if ('catalog' === parent.getData()._type) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
diff --git a/web/pgadmin/static/js/menu/menu_enabled.js b/web/pgadmin/static/js/menu/menu_enabled.js
new file mode 100644
index 00000000..05ff0f4a
--- /dev/null
+++ b/web/pgadmin/static/js/menu/menu_enabled.js
@@ -0,0 +1,52 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+function isNodeTypeSupported(backupSupportedNodes, nodeDataType, treeNode) {
+ return _.indexOf(backupSupportedNodes, nodeDataType) !== -1
+ && ancestorWithTypeCatalogDoesNotExists(treeNode);
+}
+
+export function isProvidedDataValid(treeNodeData) {
+ return !_.isUndefined(treeNodeData) && !_.isNull(treeNodeData);
+}
+
+function doesNodeHaveMenu(treeNodeData) {
+ return (treeNodeData._type === 'database' && treeNodeData.allowConn)
+ || treeNodeData._type !== 'database';
+}
+
+function ancestorWithTypeCatalogDoesNotExists(treeNode) {
+ let currentNode = treeNode;
+
+ while(currentNode.hasParent() && treeNode.parent().getData() !== null) {
+ if(currentNode.parent().getData()._type === 'catalog') {
+ return false;
+ }
+
+ currentNode = currentNode.parent();
+ }
+
+ return true;
+}
+
+export function menuEnabled(tree, backupSupportedNodes, treeNodeData, domTreeNode) {
+ let treeNode = tree.findNodeByDomElement(domTreeNode);
+ if (!treeNode) {
+ return false;
+ }
+
+ if (isProvidedDataValid(treeNodeData)) {
+ return isNodeTypeSupported(backupSupportedNodes, treeNodeData._type, treeNode)
+ && doesNodeHaveMenu(treeNodeData);
+ } else {
+ return false;
+ }
+}
+
+
diff --git a/web/pgadmin/static/js/restore/menu_utils.js b/web/pgadmin/static/js/restore/menu_utils.js
new file mode 100644
index 00000000..2d35c951
--- /dev/null
+++ b/web/pgadmin/static/js/restore/menu_utils.js
@@ -0,0 +1,18 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+export const restoreSupportedNodes = [
+ 'database',
+ 'schema',
+ 'table',
+ 'function',
+ 'trigger',
+ 'index',
+ 'partition',
+];
diff --git a/web/pgadmin/static/js/restore/restore_dialog.js b/web/pgadmin/static/js/restore/restore_dialog.js
new file mode 100644
index 00000000..237b8412
--- /dev/null
+++ b/web/pgadmin/static/js/restore/restore_dialog.js
@@ -0,0 +1,53 @@
+/////////////////////////////////////////////////////////////
+//
+// 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 {Dialog} from '../alertify/dialog';
+
+export class RestoreDialog extends Dialog {
+ constructor(pgBrowser, $, alertify, RestoreModel, backform = Backform) {
+ super('Restore Error',
+ '<div class=\'restore_dialog\'></div>',
+ pgBrowser, $, alertify, RestoreModel, backform);
+ }
+
+ draw(action, aciTreeItem) {
+
+ const serverInformation = this.retrieveAncestorOfTypeServer(aciTreeItem);
+
+ if (!serverInformation) {
+ return;
+ }
+
+ if (!this.hasBinariesConfiguration(serverInformation)) {
+ return;
+ }
+
+ let aciTreeItem1 = aciTreeItem || this.pgBrowser.treeMenu.selected();
+ let item = this.pgBrowser.treeMenu.findNodeByDomElement(aciTreeItem1);
+ const data = item.getData();
+ const node = this.pgBrowser.Nodes[data._type];
+
+ if (!node)
+ return;
+
+ let title = sprintf(gettext('Restore (%s: %s)'), node.label, data.label);
+
+ this.createOrGetDialog(title, 'restore');
+
+ this.alertify.pg_restore(title, aciTreeItem1, data, node).resizeTo('65%', '60%');
+ }
+
+ dialogName() {
+ return 'pg_restore';
+ }
+}
+
diff --git a/web/pgadmin/static/js/restore/restore_dialog_wrapper.js b/web/pgadmin/static/js/restore/restore_dialog_wrapper.js
new file mode 100644
index 00000000..13d781fb
--- /dev/null
+++ b/web/pgadmin/static/js/restore/restore_dialog_wrapper.js
@@ -0,0 +1,255 @@
+import {getTreeNodeHierarchyFromElement} from '../tree/pgadmin_tree_node';
+import axios from 'axios/index';
+import _ from 'underscore';
+import gettext from '../gettext';
+import url_for from '../url_for';
+import {DialogWrapper} from '../alertify/dialog_wrapper';
+
+export class RestoreDialogWrapper extends DialogWrapper {
+ constructor(dialogContainerSelector, dialogTitle, typeOfDialog,
+ jquery, pgBrowser, alertify, dialogModel, backform) {
+ super(dialogContainerSelector, dialogTitle, jquery,
+ pgBrowser, alertify, dialogModel, backform);
+ }
+
+ main(title, item, data, node) {
+ this.set('title', title);
+ this.setting('pg_node', node);
+ this.setting('pg_item', item);
+ this.setting('pg_item_data', data);
+ }
+
+ 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('Restore'),
+ },
+ }, {
+ text: '',
+ key: 112,
+ className: 'btn btn-default pull-left fa fa-lg fa-question',
+ attrs: {
+ name: 'dialog_help',
+ type: 'button',
+ label: gettext('Restore'),
+ url: url_for('help.static', {
+ 'filename': 'restore_dialog.html',
+ }),
+ },
+ }, {
+ text: gettext('Restore'),
+ key: 13,
+ className: 'btn btn-primary fa fa-upload pg-alertify-button',
+ restore: true,
+ 'data-btn-name': 'restore',
+ }, {
+ text: gettext('Cancel'),
+ key: 27,
+ className: 'btn btn-danger fa fa-lg fa-times pg-alertify-button',
+ restore: false,
+ '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.disableRestoreButton();
+
+ const $container = this.jquery(this.dialogContainerSelector);
+ const selectedTreeNode = this.getSelectedNode();
+ const selectedTreeNodeData = this.getSelectedNodeData(selectedTreeNode);
+ if (!selectedTreeNodeData) {
+ return;
+ }
+
+ const node = this.pgBrowser.Nodes[selectedTreeNodeData._type];
+
+ const treeInfo = getTreeNodeHierarchyFromElement(this.pgBrowser, selectedTreeNode);
+ const dialog = this.createDialog(node, treeInfo, $container);
+ this.addAlertifyClassToRestoreNodeChildNodes();
+ dialog.render();
+
+ this.elements.content.appendChild($container.get(0));
+
+ this.focusOnDialog(dialog);
+ this.setListenersForFilenameChanges();
+ }
+
+ callback(event) {
+ const selectedTreeNode = this.getSelectedNode();
+ const selectedTreeNodeData = this.getSelectedNodeData(selectedTreeNode);
+ const node = selectedTreeNodeData && this.pgBrowser.Nodes[selectedTreeNodeData._type];
+
+ if (this.wasHelpButtonPressed(event)) {
+ event.cancel = true;
+ this.pgBrowser.showHelp(
+ event.button.element.name,
+ event.button.element.getAttribute('url'),
+ node,
+ selectedTreeNode,
+ event.button.element.getAttribute('label')
+ );
+ return;
+ }
+
+ if (this.wasRestoreButtonPressed(event)) {
+
+ if (!selectedTreeNodeData)
+ return;
+
+ const serverIdentifier = this.retrieveServerIdentifier(node, selectedTreeNode);
+
+ const dialogWrapper = this;
+ let urlShortcut = 'restore.create_job';
+
+ const baseUrl = url_for(urlShortcut, {
+ 'sid': serverIdentifier,
+ });
+
+ const treeInfo = getTreeNodeHierarchyFromElement(
+ this.pgBrowser,
+ selectedTreeNode
+ );
+
+ this.setExtraParameters(selectedTreeNode, treeInfo);
+
+ let service = axios.create({});
+ service.post(
+ baseUrl,
+ this.view.model.toJSON()
+ ).then(function () {
+ dialogWrapper.alertify.success(gettext('Restore job created.'), 5);
+ dialogWrapper.pgBrowser.Events.trigger('pgadmin-bgprocess:created', dialogWrapper);
+ }).catch(function (error) {
+ try {
+ const err = error.response.data;
+ dialogWrapper.alertify.alert(
+ gettext('Restore job failed.'),
+ err.errormsg
+ );
+ } catch (e) {
+ console.warn(e.stack || e);
+ }
+ });
+ }
+ }
+
+ addAlertifyClassToRestoreNodeChildNodes() {
+ this.jquery(this.elements.body.childNodes[0]).addClass(
+ 'alertify_tools_dialog_properties obj_properties'
+ );
+ }
+
+ getSelectedNode() {
+ const tree = this.pgBrowser.treeMenu;
+ const selectedNode = tree.selected();
+ if (selectedNode) {
+ return tree.findNodeByDomElement(selectedNode);
+ } else {
+ return undefined;
+ }
+ }
+
+ disableRestoreButton() {
+ this.__internal.buttons[2].element.disabled = true;
+ }
+
+ enableRestoreButton() {
+ this.__internal.buttons[2].element.disabled = false;
+ }
+
+ createDialog(node, treeInfo, $container) {
+ const newModel = new this.dialogModel({
+ node_data: node,
+ }, {
+ node_info: treeInfo,
+ });
+ const fields = this.backform.generateViewSchema(
+ treeInfo, newModel, 'create', node, treeInfo.server, true
+ );
+
+ return this.view = new this.backform.Dialog({
+ el: $container,
+ model: newModel,
+ schema: fields,
+ });
+ }
+
+ retrieveServerIdentifier(node, selectedTreeNode) {
+ const treeInfo = getTreeNodeHierarchyFromElement(
+ this.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.enableRestoreButton();
+ } else {
+ self.disableRestoreButton();
+ this.errorModel.set('file', gettext('Please provide a filename'));
+ }
+ });
+ }
+
+ setExtraParameters(selectedTreeNode, treeInfo) {
+ this.view.model.set('database', treeInfo.database._label);
+ if (!this.view.model.get('custom')) {
+ const nodeData = selectedTreeNode.getData();
+
+ switch (nodeData._type) {
+ case 'schema':
+ this.view.model.set('schemas', [nodeData._label]);
+ break;
+ case 'table':
+ this.view.model.set('schemas', [treeInfo.schema._label]);
+ this.view.model.set('tables', [nodeData._label]);
+ break;
+ case 'function':
+ this.view.model.set('schemas', [treeInfo.schema._label]);
+ this.view.model.set('functions', [nodeData._label]);
+ break;
+ case 'index':
+ this.view.model.set('schemas', [treeInfo.schema._label]);
+ this.view.model.set('indexes', [nodeData._label]);
+ break;
+ case 'trigger':
+ this.view.model.set('schemas', [treeInfo.schema._label]);
+ this.view.model.set('triggers', [nodeData._label]);
+ break;
+ case 'trigger_func':
+ this.view.model.set('schemas', [treeInfo.schema._label]);
+ this.view.model.set('trigger_funcs', [nodeData._label]);
+ break;
+ }
+ }
+ }
+
+ wasRestoreButtonPressed(event) {
+ return event.button['data-btn-name'] === 'restore';
+ }
+}
diff --git a/web/pgadmin/static/js/schema/can_drop_child.js b/web/pgadmin/static/js/schema/can_drop_child.js
new file mode 100644
index 00000000..84e25b5c
--- /dev/null
+++ b/web/pgadmin/static/js/schema/can_drop_child.js
@@ -0,0 +1,18 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+export function canDropChild(pgBrowser, itemData, item) {
+ let node = pgBrowser.treeMenu.findNodeByDomElement(item);
+
+ if (node.anyParent((parent) => parent.getData()._type === 'catalog')) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/web/pgadmin/static/js/table/enable_disable_triggers.js b/web/pgadmin/static/js/table/enable_disable_triggers.js
new file mode 100644
index 00000000..2d792043
--- /dev/null
+++ b/web/pgadmin/static/js/table/enable_disable_triggers.js
@@ -0,0 +1,52 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import axios from 'axios';
+
+export function disableTriggers(tree, alertify, generateUrl, args) {
+ return setTriggers(tree, alertify, generateUrl, args, {enable: 'false' });
+}
+export function enableTriggers(tree, alertify, generateUrl, args) {
+ return setTriggers(tree, alertify, generateUrl, args, {enable: 'true' });
+}
+
+function setTriggers(tree, alertify, generateUrl, args, params) {
+ const treeNode = retrieveTreeNode(args, tree);
+
+ if (!treeNode || treeNode.getData() === null || treeNode.getData() === undefined)
+ return false;
+
+ axios.put(
+ generateUrl(treeNode.getHtmlIdentifier(), 'set_trigger', treeNode.getData(), true),
+ params
+ )
+ .then((res) => {
+ if (res.data.success === 1) {
+ alertify.success(res.data.info);
+ treeNode.reload(tree);
+ }
+ })
+ .catch((xhr) => {
+ try {
+ const err = xhr.response.data;
+ if (err.success === 0) {
+ alertify.error(err.errormsg);
+ }
+ } catch (e) {
+ console.warn(e.stack || e);
+ }
+ treeNode.unload(tree);
+ });
+}
+
+function retrieveTreeNode(args, tree) {
+ const input = args || {};
+ const domElementIdentifier = input.item || tree.selected();
+ return tree.findNodeByDomElement(domElementIdentifier);
+}
diff --git a/web/pgadmin/static/js/table/index.js b/web/pgadmin/static/js/table/index.js
new file mode 100644
index 00000000..40d0c22d
--- /dev/null
+++ b/web/pgadmin/static/js/table/index.js
@@ -0,0 +1,10 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+export * from './enable_disable_triggers';
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..6459beaf 100644
--- a/web/pgadmin/tools/backup/static/js/backup.js
+++ b/web/pgadmin/tools/backup/static/js/backup.js
@@ -3,9 +3,11 @@ 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',
+ 'sources/menu/menu_enabled',
], function(
gettext, url_for, $, _, S, alertify, Backbone, Backgrid, Backform, pgBrowser,
-commonUtils
+commonUtils, menuUtils, globalBackupDialog, menuEnabled
) {
// if module is already initialized, refer to that.
@@ -394,48 +396,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 +405,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 +414,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 +424,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 +434,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 +443,22 @@ commonUtils
priority: 11,
label: gettext('Backup...'),
icon: 'fa fa-floppy-o',
- enable: menu_enabled,
+ enable: menuEnabled.menuEnabled.bind(null,
+ pgBrowser.treeMenu, menuUtils.backupSupportedNodes),
}];
- 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: menuEnabled.menuEnabled.bind(null,
+ pgBrowser.treeMenu, menuUtils.backupSupportedNodes),
});
}
@@ -521,531 +483,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 b0ed60f6..f8b2cfa2 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.
@@ -384,63 +339,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/pgadmin/tools/grant_wizard/static/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js
index 750887ec..56eb378a 100644
--- a/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js
+++ b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js
@@ -2,12 +2,16 @@
define([
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
'pgadmin.alertifyjs', 'pgadmin.backgrid', 'pgadmin.backform',
- 'pgadmin.browser', 'pgadmin.browser.node', 'backgrid.select.all',
+ 'pgadmin.browser', 'pgadmin.browser.node',
+ 'sources/grant/wizard/menu_utils',
+ 'sources/menu/menu_enabled',
+
+ 'backgrid.select.all',
'backgrid.filter', 'pgadmin.browser.server.privilege',
'pgadmin.browser.wizard',
], function(
gettext, url_for, $, _, Backbone, Alertify, Backgrid, Backform, pgBrowser,
- pgNode
+ pgNode, menuUtils, menuEnabled
) {
// if module is already initialized, refer to that.
@@ -143,41 +147,6 @@ define([
this.initialized = true;
- // Define list of nodes on which grant wizard context menu option appears
- var supported_nodes = [
- 'schema', 'coll-function', 'coll-sequence',
- 'coll-table', 'coll-view', 'coll-procedure',
- 'coll-mview', 'database', 'coll-trigger_function',
- ],
-
- /**
- Enable/disable grantwizard 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
- */
- menu_enabled = function(itemData, item) {
- var t = pgBrowser.tree,
- i = item,
- d = itemData;
- var 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(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;
- };
-
// Define the nodes on which the menus to be appear
var menus = [{
name: 'grant_wizard_schema',
@@ -187,21 +156,23 @@ define([
priority: 14,
label: gettext('Grant Wizard...'),
icon: 'fa fa-unlock-alt',
- enable: menu_enabled,
+ enable: menuEnabled.menuEnabled.bind(null,
+ pgBrowser.treeMenu, menuUtils.supportedNodes),
}];
// Add supported menus into the menus list
- for (var idx = 0; idx < supported_nodes.length; idx++) {
+ for (var idx = 0; idx < menuUtils.supportedNodes.length; idx++) {
menus.push({
- name: 'grant_wizard_schema_context_' + supported_nodes[idx],
- node: supported_nodes[idx],
+ name: 'grant_wizard_schema_context_' + menuUtils.supportedNodes[idx],
+ node: menuUtils.supportedNodes[idx],
module: this,
applies: ['context'],
callback: 'start_grant_wizard',
priority: 14,
label: gettext('Grant Wizard...'),
icon: 'fa fa-unlock-alt',
- enable: menu_enabled,
+ enable: menuEnabled.menuEnabled.bind(null,
+ pgBrowser.treeMenu, menuUtils.supportedNodes),
});
}
pgBrowser.add_menus(menus);
diff --git a/web/pgadmin/tools/import_export/static/js/import_export.js b/web/pgadmin/tools/import_export/static/js/import_export.js
index 3058f122..bbb045cd 100644
--- a/web/pgadmin/tools/import_export/static/js/import_export.js
+++ b/web/pgadmin/tools/import_export/static/js/import_export.js
@@ -1,10 +1,13 @@
define([
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'underscore.string', 'pgadmin.alertifyjs',
'sources/pgadmin', 'pgadmin.browser', 'backbone', 'backgrid', 'backform',
- 'sources/utils', 'pgadmin.backform', 'pgadmin.backgrid', 'pgadmin.browser.node.ui',
+ 'sources/utils',
+ 'sources/menu/menu_enabled',
+
+ 'pgadmin.backform', 'pgadmin.backgrid', 'pgadmin.browser.node.ui',
], function(
gettext, url_for, $, _, S, Alertify, pgAdmin, pgBrowser, Backbone, Backgrid,
-Backform, commonUtils
+Backform, commonUtils, menuEnabled
) {
pgAdmin = pgAdmin || window.pgAdmin || {};
@@ -383,25 +386,6 @@ Backform, commonUtils
this.initialized = true;
- /*
- * Enable/disable import menu in tools based on node selected. Import
- * menu will be enabled only when user select table node.
- */
- var menu_enabled = function(itemData, item) {
- var t = pgBrowser.tree,
- i = item,
- d = itemData;
- var 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))
- return (
- (_.indexOf(['table'], d._type) !== -1 &&
- parent_data._type != 'catalog') ? true : false
- );
- else
- return false;
- };
-
// Initialize the context menu to display the import options when user open the context menu for table
pgBrowser.add_menus([{
name: 'import',
@@ -413,7 +397,7 @@ Backform, commonUtils
priority: 10,
label: gettext('Import/Export...'),
icon: 'fa fa-shopping-cart',
- enable: menu_enabled,
+ enable: menuEnabled.menuEnabled.bind(null, pgBrowser.treeMenu, ['table']),
}]);
},
diff --git a/web/pgadmin/tools/maintenance/static/js/maintenance.js b/web/pgadmin/tools/maintenance/static/js/maintenance.js
index 81e45944..c9976d53 100644
--- a/web/pgadmin/tools/maintenance/static/js/maintenance.js
+++ b/web/pgadmin/tools/maintenance/static/js/maintenance.js
@@ -2,11 +2,14 @@ define([
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
'underscore.string', 'pgadmin.alertifyjs', 'sources/pgadmin', 'pgadmin.browser', 'backbone',
'backgrid', 'backform', 'sources/utils',
+ 'sources/maintenance/menu_utils',
+ 'sources/menu/menu_enabled',
'pgadmin.backform', 'pgadmin.backgrid',
'pgadmin.browser.node.ui',
], function(
gettext, url_for, $, _, S, Alertify, pgAdmin, pgBrowser, Backbone, Backgrid,
- Backform, commonUtils
+ Backform, commonUtils,
+ menuUtils, menuEnabled
) {
pgAdmin = pgAdmin || window.pgAdmin || {};
@@ -168,36 +171,6 @@ define([
this.initialized = true;
- var maintenance_supported_nodes = [
- 'database', 'table', 'primary_key',
- 'unique_constraint', 'index', 'partition',
- ];
-
- /**
- Enable/disable Maintenance menu in tools based on node selected.
- Maintenance menu will be enabled only when user select table and database node.
- */
- var menu_enabled = function(itemData, item) {
- var t = pgBrowser.tree,
- i = item,
- d = itemData;
- var 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(maintenance_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 menus = [{
name: 'maintenance',
module: this,
@@ -206,21 +179,23 @@ define([
priority: 10,
label: gettext('Maintenance...'),
icon: 'fa fa-wrench',
- enable: menu_enabled,
+ enable: menuEnabled.menuEnabled.bind(null,
+ pgBrowser.treeMenu, menuUtils.maintenanceSupportedNodes),
}];
// Add supported menus into the menus list
- for (var idx = 0; idx < maintenance_supported_nodes.length; idx++) {
+ for (var idx = 0; idx < menuUtils.maintenanceSupportedNodes.length; idx++) {
menus.push({
- name: 'maintenance_context_' + maintenance_supported_nodes[idx],
- node: maintenance_supported_nodes[idx],
+ name: 'maintenance_context_' + menuUtils.maintenanceSupportedNodes[idx],
+ node: menuUtils.maintenanceSupportedNodes[idx],
module: this,
applies: ['context'],
callback: 'callback_maintenance',
priority: 10,
label: gettext('Maintenance...'),
icon: 'fa fa-wrench',
- enable: menu_enabled,
+ enable: menuEnabled.menuEnabled.bind(null,
+ pgBrowser.treeMenu, menuUtils.maintenanceSupportedNodes),
});
}
pgBrowser.add_menus(menus);
diff --git a/web/pgadmin/tools/restore/static/js/restore.js b/web/pgadmin/tools/restore/static/js/restore.js
index 5c082a9f..6c9e74ed 100644
--- a/web/pgadmin/tools/restore/static/js/restore.js
+++ b/web/pgadmin/tools/restore/static/js/restore.js
@@ -1,11 +1,12 @@
-// Restore dialog
define('tools.restore', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
'underscore.string', 'pgadmin.alertifyjs', 'pgadmin.browser',
- 'pgadmin.backgrid', 'pgadmin.backform', 'sources/utils',
+ 'pgadmin.backgrid', 'pgadmin.backform', 'sources/utils', 'sources/restore/menu_utils',
+ 'sources/menu/menu_enabled',
+ 'sources/restore/restore_dialog',
], function(
gettext, url_for, $, _, Backbone, S, alertify, pgBrowser, Backgrid, Backform,
-commonUtils
+commonUtils, menuUtils, menuEnabled, restoreDialog
) {
// if module is already initialized, refer to that.
@@ -307,59 +308,6 @@ commonUtils
this.initialized = true;
- // Define list of nodes on which restore context menu option appears
- var restore_supported_nodes = [
- 'database', 'schema',
- 'table', 'function',
- 'trigger', 'index',
- 'partition',
- ];
-
- /**
- Enable/disable restore 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, data) {
- var t = pgBrowser.tree,
- i = item,
- d = itemData;
- var 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(restore_supported_nodes, d._type) !== -1 &&
- is_parent_catalog(itemData, item, data)) {
- 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 is_parent_catalog = function(itemData, item) {
- var t = pgBrowser.tree,
- i = item,
- d = itemData;
-
- // To iterate over tree to check parent node
- while (i) {
- // If it is schema then allow user to restore
- if (_.indexOf(['catalog'], d._type) > -1)
- return false;
- i = t.hasParent(i) ? t.parent(i) : null;
- d = i ? t.itemData(i) : null;
- }
- // by default we do not want to allow create menu
- return true;
- };
-
// Define the nodes on which the menus to be appear
var menus = [{
name: 'restore_object',
@@ -369,20 +317,22 @@ commonUtils
priority: 13,
label: gettext('Restore...'),
icon: 'fa fa-upload',
- enable: menu_enabled,
+ enable: menuEnabled.menuEnabled.bind(null,
+ pgBrowser.treeMenu, menuUtils.restoreSupportedNodes),
}];
- for (var idx = 0; idx < restore_supported_nodes.length; idx++) {
+ for (var idx = 0; idx < menuUtils.restoreSupportedNodes.length; idx++) {
menus.push({
- name: 'restore_' + restore_supported_nodes[idx],
- node: restore_supported_nodes[idx],
+ name: 'restore_' + menuUtils.restoreSupportedNodes[idx],
+ node: menuUtils.restoreSupportedNodes[idx],
module: this,
applies: ['context'],
callback: 'restore_objects',
priority: 13,
label: gettext('Restore...'),
icon: 'fa fa-upload',
- enable: menu_enabled,
+ enable: menuEnabled.menuEnabled.bind(null,
+ pgBrowser.treeMenu, menuUtils.restoreSupportedNodes),
});
}
@@ -391,307 +341,8 @@ commonUtils
},
// Callback to draw Backup Dialog for objects
restore_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('Restore 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 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('Restore Error'),
- S(gettext('Failed to load preference %s of module %s')).sprintf(preference_name, module).value()
- );
- return;
- }
-
- var title = S(gettext('Restore (%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.pg_restore) {
- // Create Dialog title on the fly with node details
- alertify.dialog('pg_restore', function factory() {
- return {
- main: function(title, item, data, node) {
- this.set('title', title);
- this.setting('pg_node', node);
- this.setting('pg_item', item);
- this.setting('pg_item_data', data);
- },
- 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('Restore'),
- },
- }, {
- text: '',
- key: 112,
- className: 'btn btn-default pull-left fa fa-lg fa-question',
- attrs: {
- name: 'dialog_help',
- type: 'button',
- label: gettext('Restore'),
- url: url_for('help.static', {
- 'filename': 'restore_dialog.html',
- }),
- },
- }, {
- text: gettext('Restore'),
- key: 13,
- className: 'btn btn-primary fa fa-upload pg-alertify-button',
- restore: true,
- 'data-btn-name': 'restore',
- }, {
- text: gettext('Cancel'),
- key: 27,
- className: 'btn btn-danger fa fa-lg fa-times pg-alertify-button',
- restore: false,
- '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,
- });
- }
- },
- },
- settings: {
- pg_node: null,
- pg_item: null,
- pg_item_data: null,
- },
- prepare: function() {
-
- var self = this;
- // Disable Backup button until user provides Filename
- this.__internal.buttons[2].element.disabled = true;
- var $container = $('<div class=\'restore_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 RestoreObjectModel({
- node_data: node,
- }, {
- 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));
-
- 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 = this.settings['pg_item'] || t.selected(),
- d = this.settings['pg_item_data'] || (
- i && i.length == 1 ? t.itemData(i) : undefined
- ),
- node = this.settings['pg_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'] === 'restore') {
- if (!d)
- return;
-
- var info = node.getTreeNodeHierarchy.apply(node, [i]),
- m = this.view.model;
- // Set current node info into model
- m.set('database', info.database._label);
- if (!m.get('custom')) {
- switch (d._type) {
- case 'schema':
- m.set('schemas', [d._label]);
- break;
- case 'table':
- m.set('schemas', [info.schema._label]);
- m.set('tables', [d._label]);
- break;
- case 'function':
- m.set('schemas', [info.schema._label]);
- m.set('functions', [d._label]);
- break;
- case 'index':
- m.set('schemas', [info.schema._label]);
- m.set('indexes', [d._label]);
- break;
- case 'trigger':
- m.set('schemas', [info.schema._label]);
- m.set('triggers', [d._label]);
- break;
- case 'trigger_func':
- m.set('schemas', [info.schema._label]);
- m.set('trigger_funcs', [d._label]);
- break;
- }
- } else {
- // TODO::
- // When we will implement the object selection in the
- // import dialog, we will need to select the objects from
- // the tree selection tab.
- }
-
- var self = this,
- baseUrl = url_for('restore.create_job', {
- 'sid': info.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('Restore 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('Restore failed.'),
- err.errormsg
- );
- } catch (e) {
- console.warn(e.stack || e);
- }
- },
- });
- }
- },
- };
- });
- }
-
- alertify.pg_restore(title, item, data, node).resizeTo('65%', '60%');
+ let dialog = new restoreDialog.RestoreDialog(pgBrowser, $, alertify, RestoreObjectModel);
+ dialog.draw(action, treeItem);
},
};
return pgBrowser.Restore;
diff --git a/web/regression/javascript/backform_controls/keyboardshortcut_spec.js b/web/regression/javascript/backform_controls/keyboardshortcut_spec.js
index 02f0715c..404cc365 100644
--- a/web/regression/javascript/backform_controls/keyboardshortcut_spec.js
+++ b/web/regression/javascript/backform_controls/keyboardshortcut_spec.js
@@ -12,16 +12,16 @@ define([
'pgadmin.backform',
], function ($, Backbone, Backform) {
describe('KeyboardshortcutControl', function () {
- let field, innerFields, control, model, event;
+ let field, innerFields, control, model;
beforeEach(() => {
innerFields = [
{'name': 'key', 'type': 'keyCode', 'label': 'Key'},
{'name': 'alt_option', 'type': 'checkbox',
- 'label': 'Alt/Option'},
+ 'label': 'Alt/Option'},
{'name': 'control', 'type': 'checkbox',
- 'label': 'Ctrl'},
+ 'label': 'Ctrl'},
{'name': 'shift', 'type': 'checkbox', 'label': 'Shift'},
];
@@ -91,7 +91,7 @@ define([
'key': {
'key_code': 65,
'char': 'A',
- }
+ },
})
);
@@ -128,7 +128,7 @@ define([
model.set(field.get('name'),
$.extend(true, val, {
- 'control': false
+ 'control': false,
})
);
@@ -165,7 +165,7 @@ define([
model.set(field.get('name'),
$.extend(true, val, {
- 'shift': true
+ 'shift': true,
})
);
@@ -202,7 +202,7 @@ define([
model.set(field.get('name'),
$.extend(true, val, {
- 'alt_option': false
+ 'alt_option': false,
})
);
@@ -387,14 +387,14 @@ define([
// this should change
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeFalsy();
expect(model.get(field.get('name'))).toEqual({
- 'control': true,
- 'shift': false,
- 'alt_option': false,
- 'key': {
- 'key_code': 73,
- 'char': 'I',
- },
- });
+ 'control': true,
+ 'shift': false,
+ 'alt_option': false,
+ 'key': {
+ 'key_code': 73,
+ 'char': 'I',
+ },
+ });
// below three should not change.
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
@@ -431,4 +431,4 @@ define([
});
});
-});
\ No newline at end of file
+});
diff --git a/web/regression/javascript/backform_controls/keycode_spec.js b/web/regression/javascript/backform_controls/keycode_spec.js
index d71ef48c..6e57d972 100644
--- a/web/regression/javascript/backform_controls/keycode_spec.js
+++ b/web/regression/javascript/backform_controls/keycode_spec.js
@@ -18,8 +18,8 @@ define([
'key': {
'key_code': 65,
'char': 'A',
- }
- });
+ },
+ });
field = new Backform.Field({
id: 'key',
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..c2da61c4
--- /dev/null
+++ b/web/regression/javascript/backup/backup_dialog_spec.js
@@ -0,0 +1,190 @@
+/////////////////////////////////////////////////////////////
+//
+// 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 {TreeNode} from '../../../pgadmin/static/js/tree/tree';
+
+const context = describe;
+
+describe('ObjectBackupDialog', () => {
+ let backupDialog;
+ let pgBrowser;
+ let jquerySpy;
+ let alertifySpy;
+ let backupModelSpy;
+
+
+ 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');
+
+ rootNode = pgBrowser.treeMenu.addNewNode('level1', {}, []);
+
+ serverTreeNode = new TreeNode('level1.1', {
+ _type: 'server',
+ _id: 10,
+ });
+ pgBrowser.treeMenu.addChild(rootNode, serverTreeNode);
+
+ databaseTreeNode = new TreeNode(
+ 'level1.1.1', {
+ _type: 'database',
+ _id: 11,
+ label: 'some_database',
+ _label: 'some_database_label',
+ }, [{id: 'level1.1.1'}]);
+ pgBrowser.treeMenu.addChild(serverTreeNode, databaseTreeNode);
+
+ ppasServerTreeNode = new TreeNode('level1.2', {
+ _type: 'server',
+ server_type: 'ppas',
+ });
+ pgBrowser.treeMenu.addChild(rootNode, ppasServerTreeNode);
+
+ const someNodeUnderneathPPASServer = new TreeNode('level3', {});
+ pgBrowser.treeMenu.addChild(ppasServerTreeNode,
+ someNodeUnderneathPPASServer);
+
+ noDataNode = new TreeNode(
+ 'level3.1', undefined);
+ pgBrowser.treeMenu.addChild(someNodeUnderneathPPASServer, noDataNode);
+ });
+
+ 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.selectNode([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, [{id: 'level1.1.1'}], 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;
+ 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%');
+ });
+ });
+ });
+ });
+ });
+
+});
diff --git a/web/regression/javascript/backup/backup_dialog_wrapper_spec.js b/web/regression/javascript/backup/backup_dialog_wrapper_spec.js
new file mode 100644
index 00000000..73e453e5
--- /dev/null
+++ b/web/regression/javascript/backup/backup_dialog_wrapper_spec.js
@@ -0,0 +1,675 @@
+import {TreeFake} from '../tree/tree_fake';
+import {BackupDialogWrapper} from '../../../pgadmin/static/js/backup/backup_dialog_wrapper';
+import axios from 'axios/index';
+import MockAdapter from 'axios-mock-adapter';
+import {FakeModel} from '../fake_model';
+import {TreeNode} from '../../../pgadmin/static/js/tree/tree';
+
+let context = describe;
+
+describe('BackupDialogWrapper', () => {
+ let jquerySpy;
+ let pgBrowser;
+ let alertifySpy;
+ let dialogModelKlassSpy;
+ let backform;
+ let generatedBackupModel;
+ let backupDialogWrapper;
+ let noDataNode;
+ let serverTreeNode;
+ let databaseTreeNode;
+ let viewSchema;
+ let backupJQueryContainerSpy;
+ let backupNodeChildNodeSpy;
+ let backupNode;
+
+ beforeEach(() => {
+ pgBrowser = {
+ treeMenu: new TreeFake(),
+ Nodes: {
+ server: {
+ hasId: true,
+ getTreeNodeHierarchy: jasmine.createSpy('getTreeNodeHierarchy'),
+ },
+ database: {
+ hasId: true,
+ },
+ },
+ keyboardNavigation: jasmine.createSpyObj('keyboardNavigation', ['getDialogTabNavigator']),
+ };
+ noDataNode = pgBrowser.treeMenu.addNewNode('level1.1', undefined, [{id: 'level1'}]);
+ serverTreeNode = pgBrowser.treeMenu.addNewNode('level2.1', {
+ _type: 'server',
+ _id: 10,
+ label: 'some-tree-label',
+ }, [{id: 'level2.1'}]);
+ databaseTreeNode = new TreeNode('database-tree-node', {
+ _type: 'database',
+ _label: 'some-database-label',
+ }, [{id: 'database-tree-node'}]);
+ pgBrowser.treeMenu.addChild(serverTreeNode, databaseTreeNode);
+
+ jquerySpy = jasmine.createSpy('jquerySpy');
+ backupNode = {
+ __internal: {
+ buttons: [{}, {}, {
+ element: {
+ disabled: false,
+ },
+ }],
+ },
+ elements: {
+ body: {
+ childNodes: [
+ {},
+ ],
+ },
+ content: jasmine.createSpyObj('content', ['appendChild', 'attr']),
+ },
+ };
+
+ backupJQueryContainerSpy = jasmine.createSpyObj('backupJQueryContainer', ['get', 'attr']);
+ backupJQueryContainerSpy.get.and.returnValue(backupJQueryContainerSpy);
+
+ generatedBackupModel = {};
+ dialogModelKlassSpy = jasmine.createSpy('dialogModelKlass');
+ dialogModelKlassSpy.and.returnValue(generatedBackupModel);
+
+ viewSchema = {};
+ backform = jasmine.createSpyObj('backform', ['generateViewSchema', 'Dialog']);
+ backform.generateViewSchema.and.returnValue(viewSchema);
+
+ backupNodeChildNodeSpy = jasmine.createSpyObj('something', ['addClass']);
+ jquerySpy.and.callFake((selector) => {
+ if (selector === '<div class=\'backup_dialog\'></div>') {
+ return backupJQueryContainerSpy;
+ } else if (selector === backupNode.elements.body.childNodes[0]) {
+ return backupNodeChildNodeSpy;
+ }
+ });
+
+ });
+
+ describe('#prepare', () => {
+ beforeEach(() => {
+ alertifySpy = jasmine.createSpyObj('alertify', ['alert', 'dialog']);
+ backupDialogWrapper = new BackupDialogWrapper(
+ '<div class=\'backup_dialog\'></div>',
+ 'backupDialogTitle',
+ 'backup',
+ jquerySpy,
+ pgBrowser,
+ alertifySpy,
+ dialogModelKlassSpy,
+ backform
+ );
+ backupDialogWrapper = Object.assign(backupDialogWrapper, backupNode);
+ });
+
+ context('no tree element is selected', () => {
+ it('does not create a backform dialog', () => {
+ backupDialogWrapper.prepare();
+ expect(backform.Dialog).not.toHaveBeenCalled();
+ });
+
+ it('disables the button "submit button" until a filename is selected', () => {
+ backupDialogWrapper.prepare();
+ expect(backupDialogWrapper.__internal.buttons[2].element.disabled).toBe(true);
+ });
+ });
+
+ context('selected tree node has no data', () => {
+ beforeEach(() => {
+ pgBrowser.treeMenu.selectNode(noDataNode.domNode);
+ });
+
+ it('does not create a backform dialog', () => {
+ backupDialogWrapper.prepare();
+ expect(backform.Dialog).not.toHaveBeenCalled();
+ });
+
+ it('disables the button "submit button" until a filename is selected', () => {
+ backupDialogWrapper.prepare();
+ expect(backupDialogWrapper.__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,
+ label: 'some-tree-label',
+ },
+ };
+ pgBrowser.treeMenu.selectNode(serverTreeNode.domNode);
+ pgBrowser.Nodes['server'].getTreeNodeHierarchy.and
+ .returnValue(treeHierarchyInformation);
+ dialogSpy = jasmine.createSpyObj('newView', ['render']);
+ dialogSpy.$el = jasmine.createSpyObj('$el', ['find', 'attr']);
+ dialogSpy.model = jasmine.createSpyObj('model', ['on']);
+ dialogSpy.$el.find.and.returnValue([]);
+
+ backform.Dialog.and.returnValue(dialogSpy);
+ });
+
+ it('creates a backform dialog and displays it', () => {
+ backupDialogWrapper.prepare();
+ expect(backform.Dialog).toHaveBeenCalledWith({
+ el: backupJQueryContainerSpy,
+ model: generatedBackupModel,
+ schema: viewSchema,
+ });
+
+ expect(dialogSpy.render).toHaveBeenCalled();
+ });
+
+
+ it('add alertify classes to restore node childnode', () => {
+ backupDialogWrapper.prepare();
+ expect(backupNodeChildNodeSpy.addClass)
+ .toHaveBeenCalledWith('alertify_tools_dialog_properties obj_properties');
+ });
+
+ it('disables the button submit button until a filename is selected', () => {
+ backupDialogWrapper.prepare();
+ expect(backupNode.__internal.buttons[2].element.disabled).toBe(true);
+ });
+
+ it('generates a new backup model', () => {
+ backupDialogWrapper.prepare();
+ expect(dialogModelKlassSpy).toHaveBeenCalledWith(
+ {type: 'backup'},
+ {node_info: treeHierarchyInformation}
+ );
+ });
+
+ it('add the new dialog to the backup node HTML', () => {
+ backupDialogWrapper.prepare();
+ expect(backupNode.elements.content.appendChild).toHaveBeenCalledWith(backupJQueryContainerSpy);
+ });
+ });
+ });
+
+ describe('onButtonClicked', () => {
+ let networkMock;
+ beforeEach(() => {
+ networkMock = new MockAdapter(axios);
+ backupDialogWrapper = new BackupDialogWrapper(
+ '<div class=\'backup_dialog\'></div>',
+ 'backupDialogTitle',
+ 'backup',
+ jquerySpy,
+ pgBrowser,
+ alertifySpy,
+ dialogModelKlassSpy,
+ backform
+ );
+
+ backupDialogWrapper = Object.assign(backupDialogWrapper, backupNode);
+ });
+
+ afterEach(() => {
+ networkMock.restore();
+ });
+
+ context('dialog help button was pressed', () => {
+ let networkCalled;
+ beforeEach(() => {
+ networkCalled = false;
+ networkMock.onAny(/.*/).reply(() => {
+ networkCalled = true;
+ return [200, {}];
+ });
+ pgBrowser.treeMenu.selectNode(serverTreeNode.domNode);
+ pgBrowser.showHelp = jasmine.createSpy('showHelp');
+
+ const event = {
+ button: {
+ element: {
+ name: 'dialog_help',
+ getAttribute: (attributeName) => {
+ if (attributeName === 'url') {
+ return 'http://someurl';
+ } else if (attributeName === 'label') {
+ return 'some label';
+ }
+ },
+ },
+ },
+ };
+ backupDialogWrapper.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(() => {
+ networkCalled = false;
+ networkMock.onAny(/.*/).reply(() => {
+ networkCalled = true;
+ return [200, {}];
+ });
+ pgBrowser.treeMenu.selectNode(serverTreeNode.domNode);
+ pgBrowser.showHelp = jasmine.createSpy('showHelp');
+
+ const event = {
+ button: {
+ element: {
+ name: 'object_help',
+ getAttribute: (attributeName) => {
+ if (attributeName === 'url') {
+ return 'http://someurl';
+ } else if (attributeName === 'label') {
+ return 'some label';
+ }
+ },
+ },
+ },
+ };
+ backupDialogWrapper.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', () => {
+ it('does not start the backup', () => {
+ let networkCalled = false;
+ networkMock.onAny(/.*/).reply(() => {
+ networkCalled = true;
+ return [200, {}];
+ });
+
+ let event = {
+ button: {
+ 'data-btn-name': 'backup',
+ element: {
+ getAttribute: () => {
+ return 'http://someurl';
+ },
+ },
+ },
+ };
+
+ backupDialogWrapper.callback(event);
+ expect(networkCalled).toBe(false);
+ });
+ });
+
+ context('tree node has no data', () => {
+ it('does not start the backup', () => {
+ pgBrowser.treeMenu.selectNode(noDataNode.domNode);
+
+ let networkCalled = false;
+ networkMock.onAny(/.*/).reply(() => {
+ networkCalled = true;
+ return [200, {}];
+ });
+
+ let event = {
+ button: {
+ 'data-btn-name': 'backup',
+ element: {
+ getAttribute: () => {
+ return 'http://someurl';
+ },
+ },
+ },
+ };
+
+ backupDialogWrapper.callback(event);
+ expect(networkCalled).toBe(false);
+ });
+ });
+
+ context('tree node has data', () => {
+ context('when dialog type is global', () => {
+ let event;
+ beforeEach(() => {
+ pgBrowser.treeMenu.selectNode(serverTreeNode.domNode);
+
+ backupDialogWrapper.view = {
+ model: new FakeModel(),
+ };
+
+ event = {
+ button: {
+ 'data-btn-name': 'backup',
+ element: {
+ getAttribute: () => {
+ return 'http://someurl';
+ },
+ },
+ },
+ };
+ });
+
+ context('when the backup job is created successfully', () => {
+ let dataSentToServer;
+ beforeEach(() => {
+ pgBrowser.Events = jasmine.createSpyObj('Events', ['trigger']);
+ alertifySpy.success = jasmine.createSpy('success');
+
+ networkMock.onPost('/backup/job/10').reply((request) => {
+ dataSentToServer = request.data;
+ return [200, {}];
+ });
+ });
+
+ it('creates a success alert box', (done) => {
+ backupDialogWrapper.callback(event);
+ setTimeout(() => {
+ expect(alertifySpy.success).toHaveBeenCalledWith(
+ 'Backup job created.',
+ 5
+ );
+ done();
+ }, 0);
+ });
+
+ it('trigger an event to background process', (done) => {
+ backupDialogWrapper.callback(event);
+
+ setTimeout(() => {
+ expect(pgBrowser.Events.trigger).toHaveBeenCalledWith(
+ 'pgadmin-bgprocess:created',
+ backupDialogWrapper
+ );
+ done();
+ }, 0);
+ });
+
+ it('send the correct paramenters to the backend', (done) => {
+ backupDialogWrapper.callback(event);
+ setTimeout(() => {
+ expect(JSON.parse(dataSentToServer)).toEqual(
+ {}
+ );
+ done();
+ }, 0);
+ });
+ });
+
+ context('when creating backup job fails', () => {
+ it('creates an alert box', (done) => {
+ alertifySpy.alert = jasmine.createSpy('alert');
+ networkMock.onPost('/backup/job/10').reply(() => {
+ return [400, {
+ errormsg: 'some-error-message',
+ }];
+ });
+
+ backupDialogWrapper.callback(event);
+ setTimeout(() => {
+ expect(alertifySpy.alert).toHaveBeenCalledWith(
+ 'Backup job failed.',
+ 'some-error-message'
+ );
+ done();
+ }, 0);
+
+ });
+ });
+ });
+
+ context('when dialog type is object', () => {
+ let event;
+ beforeEach(() => {
+ backupDialogWrapper = new BackupDialogWrapper(
+ '<div class=\'backup_dialog\'></div>',
+ 'backupDialogTitle',
+ 'backup_objects',
+ jquerySpy,
+ pgBrowser,
+ alertifySpy,
+ dialogModelKlassSpy,
+ backform
+ );
+
+ pgBrowser.treeMenu.selectNode(databaseTreeNode.domNode);
+
+ backupDialogWrapper.view = {
+ model: new FakeModel(),
+ };
+
+ event = {
+ button: {
+ 'data-btn-name': 'backup',
+ element: {
+ getAttribute: () => {
+ return 'http://someurl';
+ },
+ },
+ },
+ };
+ });
+
+ context('when the backup job is created successfully', () => {
+ let dataSentToServer;
+ beforeEach(() => {
+ pgBrowser.Events = jasmine.createSpyObj('Events', ['trigger']);
+ alertifySpy.success = jasmine.createSpy('success');
+
+ networkMock.onPost('/backup/job/10/object').reply((request) => {
+ dataSentToServer = request.data;
+ return [200, {}];
+ });
+ });
+
+ it('creates a success alert box', (done) => {
+ backupDialogWrapper.callback(event);
+ setTimeout(() => {
+ expect(alertifySpy.success).toHaveBeenCalledWith(
+ 'Backup job created.',
+ 5
+ );
+ done();
+ }, 0);
+ });
+
+ it('trigger an event to background process', (done) => {
+ backupDialogWrapper.callback(event);
+
+ setTimeout(() => {
+ expect(pgBrowser.Events.trigger).toHaveBeenCalledWith(
+ 'pgadmin-bgprocess:created',
+ backupDialogWrapper
+ );
+ done();
+ }, 0);
+ });
+
+ it('send the correct parameters to the backend', (done) => {
+ backupDialogWrapper.callback(event);
+ setTimeout(() => {
+ expect(JSON.parse(dataSentToServer)).toEqual(
+ {database: 'some-database-label'}
+ );
+ done();
+ }, 0);
+ });
+ });
+
+ context('when creating backup job fails', () => {
+ it('creates an alert box', (done) => {
+ alertifySpy.alert = jasmine.createSpy('alert');
+ networkMock.onPost('/backup/job/10/object').reply(() => {
+ return [400, {
+ errormsg: 'some-error-message',
+ }];
+ });
+
+ backupDialogWrapper.callback(event);
+ setTimeout(() => {
+ expect(alertifySpy.alert).toHaveBeenCalledWith(
+ 'Backup job failed.',
+ 'some-error-message'
+ );
+ done();
+ }, 0);
+ });
+ });
+ });
+ });
+ });
+ });
+
+ describe('#setExtraParameters', () => {
+ let selectedTreeNode;
+ let treeInfo;
+ let model;
+
+ context('when dialog type is global', () => {
+ beforeEach(() => {
+ backupDialogWrapper = new BackupDialogWrapper(
+ '<div class=\'backup_dialog\'></div>',
+ 'backupDialogTitle',
+ 'backup',
+ jquerySpy,
+ pgBrowser,
+ alertifySpy,
+ dialogModelKlassSpy,
+ backform
+ );
+
+ treeInfo = {};
+ model = new FakeModel();
+ backupDialogWrapper.view = {
+ model: model,
+ };
+ });
+
+
+ it('sets nothing on the view model', () => {
+ backupDialogWrapper.setExtraParameters(selectedTreeNode, treeInfo);
+ expect(model.toJSON()).toEqual({});
+ });
+ });
+
+ context('when dialog type is object', () => {
+ beforeEach(() => {
+ backupDialogWrapper = new BackupDialogWrapper(
+ '<div class=\'backup_dialog\'></div>',
+ 'backupDialogTitle',
+ 'backup_objects',
+ jquerySpy,
+ pgBrowser,
+ alertifySpy,
+ dialogModelKlassSpy,
+ backform
+ );
+
+ treeInfo = {
+ database: {
+ _label: 'some-database-label',
+ },
+ schema: {
+ _label: 'some-treeinfo-label',
+ },
+ };
+
+ model = new FakeModel();
+ selectedTreeNode = new TreeNode('some-selected-node',
+ {_type: 'some-type', _label: 'some-selected-label'},
+ []);
+ backupDialogWrapper.view = {
+ model: model,
+ };
+ });
+
+ it('sets the database label on the model', () => {
+ backupDialogWrapper.setExtraParameters(selectedTreeNode, treeInfo);
+ expect(model.toJSON()).toEqual({
+ 'database': 'some-database-label',
+ });
+ });
+
+ context('when the selected is a schema type', () => {
+ beforeEach(() => {
+ selectedTreeNode = new TreeNode('some-selected-node',
+ {_type: 'schema', _label: 'some-schema-label'},
+ []);
+ });
+
+ it('sets the schema label on the model', () => {
+ backupDialogWrapper.setExtraParameters(selectedTreeNode, treeInfo);
+ expect(model.toJSON()).toEqual({
+ 'database': 'some-database-label',
+ 'schemas': ['some-schema-label'],
+ });
+ });
+ });
+
+ context('when the selected is a table type', () => {
+ beforeEach(() => {
+ selectedTreeNode = new TreeNode('some-selected-node',
+ {_type: 'table', _label: 'some-table-label'},
+ []);
+ });
+
+ it('sets the schema label on the model', () => {
+ backupDialogWrapper.setExtraParameters(selectedTreeNode, treeInfo);
+ expect(model.toJSON()).toEqual({
+ 'database': 'some-database-label',
+ 'tables': [['some-treeinfo-label', 'some-table-label']],
+ });
+ });
+ });
+
+ context('when the model has no ratio value', () => {
+ beforeEach(() => {
+ model.set('ratio', '');
+ });
+
+ it('sets clears the ratio value', () => {
+ backupDialogWrapper.setExtraParameters(selectedTreeNode, treeInfo);
+ expect(model.get('ratio')).toBeUndefined();
+ });
+ });
+
+ context('when the model has a valid ratio value', () => {
+ beforeEach(() => {
+ model.set('ratio', '0.25');
+ });
+
+ it('sets clears the ratio value', () => {
+ backupDialogWrapper.setExtraParameters(selectedTreeNode, treeInfo);
+ expect(model.get('ratio')).toEqual('0.25');
+ });
+ });
+ });
+ });
+});
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..67cca9db
--- /dev/null
+++ b/web/regression/javascript/backup/global_server_backup_dialog_spec.js
@@ -0,0 +1,168 @@
+/////////////////////////////////////////////////////////////
+//
+// 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';
+
+const context = describe;
+
+describe('GlobalServerBackupDialog', () => {
+ let backupDialog;
+ let pgBrowser;
+ let jquerySpy;
+ let alertifySpy;
+ let backupModelSpy;
+
+
+ let rootNode;
+ let serverTreeNode;
+ let ppasServerTreeNode;
+
+ 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');
+
+ rootNode = pgBrowser.treeMenu.addNewNode('level1', {}, undefined, []);
+ serverTreeNode = pgBrowser.treeMenu.addNewNode('level1.1', {
+ _type: 'server',
+ _id: 10,
+ }, undefined, ['level1']);
+ ppasServerTreeNode = pgBrowser.treeMenu.addNewNode('level1.2', {
+ _type: 'server',
+ server_type: 'ppas',
+ }, undefined, ['level1']);
+ pgBrowser.treeMenu.addNewNode('level3', {}, undefined, ['level1', 'level1.2']);
+ pgBrowser.treeMenu.addNewNode('level3.1', undefined, 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.selectNode([{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 server backup', () => {
+ it('displays the dialog', () => {
+ backupDialog.draw(null, [serverTreeNode], {server: true});
+ expect(alertifySpy['BackupDialog_server']).toHaveBeenCalledWith(true);
+ expect(serverResizeToSpy.resizeTo).toHaveBeenCalledWith('60%', '50%');
+ });
+ });
+ });
+ });
+ });
+ });
+});
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..ecf8abcd
--- /dev/null
+++ b/web/regression/javascript/backup/menu_utils_spec.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 {menuEnabledServer} from '../../../pgadmin/static/js/backup/menu_utils';
+
+const context = describe;
+
+describe('backup.menuUtils', () => {
+ describe('#menuEnabledServer', () => {
+ 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/browser/modify_animation_spec.js b/web/regression/javascript/browser/modify_animation_spec.js
index b8060eed..51c882d1 100644
--- a/web/regression/javascript/browser/modify_animation_spec.js
+++ b/web/regression/javascript/browser/modify_animation_spec.js
@@ -7,8 +7,8 @@
//
//////////////////////////////////////////////////////////////
-import $ from 'jquery'
-import modifyAnimation from 'sources/modify_animation'
+import $ from 'jquery';
+import modifyAnimation from 'sources/modify_animation';
describe('modifyAnimation', function () {
@@ -16,12 +16,12 @@ describe('modifyAnimation', function () {
let dummyElement;
beforeEach(() => {
- pgBrowser = jasmine.createSpyObj('pgBrowser', ['get_preference', 'tree'])
+ pgBrowser = jasmine.createSpyObj('pgBrowser', ['get_preference', 'tree']);
pgBrowser.tree = jasmine.createSpyObj('tree', ['options']);
pgBrowser.tree.options.and.returnValue({
- show: {},
- hide: {},
- view: {},
+ show: {},
+ hide: {},
+ view: {},
});
dummyElement = document.createElement('link');
spyOn($.fn, 'find').and.returnValue($(dummyElement));
diff --git a/web/regression/javascript/browser/server_groups/servers/model_validation_spec.js b/web/regression/javascript/browser/server_groups/servers/model_validation_spec.js
index 85e51eb9..99050742 100644
--- a/web/regression/javascript/browser/server_groups/servers/model_validation_spec.js
+++ b/web/regression/javascript/browser/server_groups/servers/model_validation_spec.js
@@ -62,7 +62,7 @@ describe('Server#ModelValidation', () => {
expect(model.errorModel.set).toHaveBeenCalledWith({
host: 'Either Host name, Address or Service must be specified.',
hostaddr: 'Either Host name, Address or Service must be specified.',
- db: 'Maintenance database must be specified.'
+ db: 'Maintenance database must be specified.',
});
});
});
@@ -81,7 +81,7 @@ describe('Server#ModelValidation', () => {
hostaddr: 'Either Host name, Address or Service must be specified.',
db: 'Maintenance database must be specified.',
username: 'Username must be specified.',
- port: 'Port must be specified.'
+ port: 'Port must be specified.',
});
});
});
@@ -96,7 +96,7 @@ describe('Server#ModelValidation', () => {
hostaddr: 'Host address must be valid IPv4 or IPv6 address.',
db: 'Maintenance database must be specified.',
username: 'Username must be specified.',
- port: 'Port must be specified.'
+ port: 'Port must be specified.',
});
});
});
@@ -109,7 +109,7 @@ describe('Server#ModelValidation', () => {
expect(model.errorModel.set).toHaveBeenCalledWith({
name: 'Name must be specified.',
username: 'Username must be specified.',
- port: 'Port must be specified.'
+ port: 'Port must be specified.',
});
});
});
diff --git a/web/regression/javascript/common_keyboard_shortcuts_spec.js b/web/regression/javascript/common_keyboard_shortcuts_spec.js
index b89af5b0..e27929bf 100644
--- a/web/regression/javascript/common_keyboard_shortcuts_spec.js
+++ b/web/regression/javascript/common_keyboard_shortcuts_spec.js
@@ -10,18 +10,14 @@
import keyboardShortcuts from 'sources/keyboard_shortcuts';
describe('the keyboard shortcuts', () => {
- const F1_KEY = 112,
- EDIT_KEY = 71, // Key: G -> Grid values
- LEFT_ARROW_KEY = 37,
- RIGHT_ARROW_KEY = 39,
- MOVE_NEXT = 'right';
+ const F1_KEY = 112;
let debuggerElementSpy, event, debuggerUserShortcutSpy;
debuggerUserShortcutSpy = jasmine.createSpyObj(
'userDefinedShortcuts', [
{ 'edit_grid_keys': null },
{ 'next_panel_keys': null },
- { 'previous_panel_keys': null }
+ { 'previous_panel_keys': null },
]
);
beforeEach(() => {
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..2594ec96
--- /dev/null
+++ b/web/regression/javascript/datagrid/get_panel_title_spec.js
@@ -0,0 +1,82 @@
+/////////////////////////////////////////////////////////////
+//
+// 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';
+import {TreeNode} from '../../../pgadmin/static/js/tree/tree';
+
+const context = describe;
+
+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', () => {
+ const root = tree.addNewNode('level1', {_type: 'server_groups'});
+ tree.addChild(root, new TreeNode('level1.1', {_type: 'other'}));
+ tree.selectNode([{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.selectNode([{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', () => {
+ const root = tree.addNewNode('level1', {
+ _type: 'server',
+ db: 'other db label',
+ user: {name: 'some user name'},
+ label: 'server label',
+ });
+ const level1 = new TreeNode('level1.1', {
+ _type: 'database',
+ label: 'db label',
+ });
+ tree.addChild(root, level1);
+ tree.addChild(level1,
+ new TreeNode('level1.1.1', {_type: 'table'}));
+ tree.selectNode([{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..3e4feec9
--- /dev/null
+++ b/web/regression/javascript/datagrid/show_data_spec.js
@@ -0,0 +1,171 @@
+/////////////////////////////////////////////////////////////
+//
+// 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';
+import {TreeNode} from '../../../pgadmin/static/js/tree/tree';
+
+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,
+ },
+ },
+ };
+ const parent = pgBrowser.treeMenu.addNewNode('parent', {_type: 'parent'}, []);
+ const serverGroup1 = new TreeNode('server_group1', {
+ _type: 'server_group',
+ _id: 1,
+ });
+ pgBrowser.treeMenu.addChild(parent, serverGroup1);
+
+ const server1 = new TreeNode('server1', {
+ _type: 'server',
+ label: 'server1',
+ server_type: 'pg',
+ _id: 2,
+ }, ['parent', 'server_group1']);
+ pgBrowser.treeMenu.addChild(serverGroup1, server1);
+
+ const database1 = new TreeNode('database1', {
+ _type: 'database',
+ label: 'database1',
+ _id: 3,
+ }, ['parent', 'server_group1', 'server1']);
+ pgBrowser.treeMenu.addChild(server1, database1);
+
+ const schema1 = new TreeNode('schema1', {
+ _type: 'schema',
+ label: 'schema1',
+ _id: 4,
+ });
+ pgBrowser.treeMenu.addChild(database1, schema1);
+
+ const view1 = new TreeNode('view1', {
+ _type: 'view',
+ label: 'view1',
+ _id: 5,
+ }, ['parent', 'server_group1', 'server1', 'database1']);
+ pgBrowser.treeMenu.addChild(database1, view1);
+
+ const catalog1 = new TreeNode('catalog1', {
+ _type: 'catalog',
+ label: 'catalog1',
+ _id: 6,
+ }, ['parent', 'server_group1', 'server1', 'database1']);
+ pgBrowser.treeMenu.addChild(database1, catalog1);
+ });
+
+ 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..e58112c0
--- /dev/null
+++ b/web/regression/javascript/datagrid/show_query_tool_spec.js
@@ -0,0 +1,125 @@
+/////////////////////////////////////////////////////////////
+//
+// 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';
+import {TreeNode} from '../../../pgadmin/static/js/tree/tree';
+
+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,
+ },
+ },
+ };
+ const parent = pgBrowser.treeMenu.addNewNode('parent', {_type: 'parent'});
+ const serverGroup1 = new TreeNode('server_group1', {
+ _type: 'server_group',
+ _id: 1,
+ }, ['parent']);
+ pgBrowser.treeMenu.addChild(parent, serverGroup1);
+
+ const server1 = new TreeNode('server1', {
+ _type: 'server',
+ label: 'server1',
+ server_type: 'pg',
+ _id: 2,
+ });
+ pgBrowser.treeMenu.addChild(serverGroup1, server1);
+
+ const database1 = new TreeNode('database1', {
+ _type: 'database',
+ label: 'database1',
+ _id: 3,
+ });
+ pgBrowser.treeMenu.addChild(server1, database1);
+ });
+
+ 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/debugger_utils_spec.js b/web/regression/javascript/debugger_utils_spec.js
index 3e6be976..65f95dde 100644
--- a/web/regression/javascript/debugger_utils_spec.js
+++ b/web/regression/javascript/debugger_utils_spec.js
@@ -16,12 +16,12 @@ describe('debuggerUtils', function () {
let tab_key = {
which: 9,
keyCode: 9,
- }
+ };
let enter_key = {
which: 13,
keyCode: 13,
- }
+ };
describe('debuggerUtils', function () {
it('returns undefined if no command is passed', function () {
@@ -31,7 +31,7 @@ describe('debuggerUtils', function () {
describe('debuggerUtils', function () {
it('should call focus on editor', function () {
- setFocusToDebuggerEditor(editor, enter_key)
+ setFocusToDebuggerEditor(editor, enter_key);
expect(editor.focus).toHaveBeenCalled();
});
});
diff --git a/web/regression/javascript/dialog_tab_navigator_spec.js b/web/regression/javascript/dialog_tab_navigator_spec.js
index d4082c87..8321e4f2 100644
--- a/web/regression/javascript/dialog_tab_navigator_spec.js
+++ b/web/regression/javascript/dialog_tab_navigator_spec.js
@@ -10,11 +10,11 @@ import dialogTabNavigator from 'sources/dialog_tab_navigator';
import $ from 'jquery';
import 'bootstrap';
- describe('dialogTabNavigator', function () {
- let dialog, tabNavigator, backward_shortcut, forward_shortcut;
+describe('dialogTabNavigator', function () {
+ let dialog, tabNavigator, backward_shortcut, forward_shortcut;
- beforeEach(() => {
- let dialogHtml =$('<div tabindex="1" class="backform-tab" role="tabpanel">'+
+ beforeEach(() => {
+ let dialogHtml =$('<div tabindex="1" class="backform-tab" role="tabpanel">'+
' <ul class="nav nav-tabs" role="tablist">'+
' <li role="presentation" class="active">'+
' <a data-toggle="tab" tabindex="-1" data-tab-index="1" href="#1" aria-controls="1"> General</a>'+
@@ -52,64 +52,64 @@ import 'bootstrap';
' </ul>'+
'</div>');
- dialog = {};
+ dialog = {};
- dialog.el = dialogHtml[0];
- dialog.$el = dialogHtml;
+ dialog.el = dialogHtml[0];
+ dialog.$el = dialogHtml;
- backward_shortcut = {
- 'alt': false,
- 'shift': true,
- 'control': true,
- 'key': {'key_code': 91, 'char': '['}
- };
+ backward_shortcut = {
+ 'alt': false,
+ 'shift': true,
+ 'control': true,
+ 'key': {'key_code': 91, 'char': '['},
+ };
- forward_shortcut = {
- 'alt': false,
- 'shift': true,
- 'control': true,
- 'key': {'key_code': 93, 'char': ']'}
- };
+ forward_shortcut = {
+ 'alt': false,
+ 'shift': true,
+ 'control': true,
+ 'key': {'key_code': 93, 'char': ']'},
+ };
- tabNavigator = new dialogTabNavigator.dialogTabNavigator(
+ tabNavigator = new dialogTabNavigator.dialogTabNavigator(
dialog, backward_shortcut, forward_shortcut);
- });
+ });
- describe('navigate', function () {
+ describe('navigate', function () {
- beforeEach(() => {
- spyOn(tabNavigator, 'navigateBackward').and.callThrough();
-
- spyOn(tabNavigator, 'navigateForward').and.callThrough();
- });
+ beforeEach(() => {
+ spyOn(tabNavigator, 'navigateBackward').and.callThrough();
- it('navigate backward', function () {
- tabNavigator.onKeyboardEvent({}, 'shift+ctrl+[');
+ spyOn(tabNavigator, 'navigateForward').and.callThrough();
+ });
- expect(tabNavigator.navigateBackward).toHaveBeenCalled();
+ it('navigate backward', function () {
+ tabNavigator.onKeyboardEvent({}, 'shift+ctrl+[');
- expect(tabNavigator.navigateForward).not.toHaveBeenCalled();
+ expect(tabNavigator.navigateBackward).toHaveBeenCalled();
- });
+ expect(tabNavigator.navigateForward).not.toHaveBeenCalled();
- it('navigate forward', function () {
- tabNavigator.onKeyboardEvent({}, 'shift+ctrl+]');
+ });
- expect(tabNavigator.navigateForward).toHaveBeenCalled();
+ it('navigate forward', function () {
+ tabNavigator.onKeyboardEvent({}, 'shift+ctrl+]');
- expect(tabNavigator.navigateBackward).not.toHaveBeenCalled();
+ expect(tabNavigator.navigateForward).toHaveBeenCalled();
- });
+ expect(tabNavigator.navigateBackward).not.toHaveBeenCalled();
- it('should not navigate', function () {
- tabNavigator.onKeyboardEvent({}, 'shift+ctrl+a');
+ });
- expect(tabNavigator.navigateForward).not.toHaveBeenCalled();
+ it('should not navigate', function () {
+ tabNavigator.onKeyboardEvent({}, 'shift+ctrl+a');
- expect(tabNavigator.navigateBackward).not.toHaveBeenCalled();
+ expect(tabNavigator.navigateForward).not.toHaveBeenCalled();
- });
+ expect(tabNavigator.navigateBackward).not.toHaveBeenCalled();
});
- });
\ No newline at end of file
+ });
+
+});
\ No newline at end of file
diff --git a/web/regression/javascript/fake_endpoints.js b/web/regression/javascript/fake_endpoints.js
index 63ab05dc..c060ba78 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>',
+ 'restore.create_job': '/restore/job/<int:sid>',
};
});
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/menu/can_create_spec.js b/web/regression/javascript/menu/can_create_spec.js
new file mode 100644
index 00000000..5ee030f2
--- /dev/null
+++ b/web/regression/javascript/menu/can_create_spec.js
@@ -0,0 +1,98 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+/////////////////////////////////////////////////////////////
+
+import {canCreate} from '../../../pgadmin/static/js/menu/can_create';
+import {TreeFake} from '../tree/tree_fake';
+
+const context = describe;
+
+describe('#canCreate', () => {
+ let ourBrowser;
+ let data;
+ let tree;
+
+ beforeEach(() => {
+ tree = new TreeFake();
+ ourBrowser = {
+ treeMenu: tree,
+ };
+
+ tree.addNewNode('level1', {}, undefined, []);
+ });
+
+ context('data is not null and check is false ', () => {
+ beforeEach(() => {
+ data = {action: 'create', check: false};
+ });
+ it('returns true', () => {
+ expect(canCreate({}, {}, {}, data)).toBe(true);
+ });
+ });
+
+ context('data is not null and check is true', () => {
+ beforeEach(() => {
+ data = {action: 'create', check: true};
+ });
+
+ context('is node with type schema', () => {
+ beforeEach(() => {
+ tree.addNewNode('level2', {_type: 'schema'}, [{id: 'level2'}], ['level1']);
+ });
+
+ it('returns true', () => {
+ expect(canCreate(ourBrowser, 'coll-table', [{id: 'level2'}], data)).toBe(true);
+ });
+ });
+
+ context('has ancestor with type schema', () => {
+ beforeEach(() => {
+ tree.addNewNode('level2', {_type: 'schema'}, undefined, ['level1']);
+ tree.addNewNode('level3', {_type: 'database'}, [{id: 'level3'}], ['level1', 'level2']);
+ });
+
+ it('returns true', () => {
+ expect(canCreate(ourBrowser, 'coll-table', [{id: 'level3'}], data)).toBe(true);
+ });
+ });
+
+ context('when type is not "coll-table"', () => {
+ beforeEach(() => {
+ tree.addNewNode('level2', {_type: 'database'}, undefined, ['level1']);
+ });
+
+ it('returns true', () => {
+ expect(canCreate(ourBrowser, 'coll-table', [{id: 'level2'}], data)).toBe(true);
+ });
+ });
+
+ context('when type is "coll-table"', () => {
+ context('when parent type is "catalog"', () => {
+ beforeEach(() => {
+ tree.addNewNode('level2', {_type: 'catalog'}, undefined, ['level1']);
+ tree.addNewNode('level3', {_type: 'coll-table'}, [{id: 'level3'}], ['level1', 'level2']);
+ });
+
+ it('returns false', () => {
+ expect(canCreate(ourBrowser, 'coll-table', [{id: 'level3'}], data)).toBe(false);
+ });
+ });
+
+ context('when parent type is not "catalog"', () => {
+ beforeEach(() => {
+ tree.addNewNode('level2', {_type: 'database'}, undefined, ['level1']);
+ tree.addNewNode('level3', {_type: 'coll-table'}, [{id: 'level3'}], ['level1', 'level2']);
+ });
+
+ it('returns false', () => {
+ expect(canCreate(ourBrowser, 'coll-table', [{id: 'level3'}], data)).toBe(true);
+ });
+ });
+ });
+ });
+});
diff --git a/web/regression/javascript/menu/menu_enabled_spec.js b/web/regression/javascript/menu/menu_enabled_spec.js
new file mode 100644
index 00000000..06967d77
--- /dev/null
+++ b/web/regression/javascript/menu/menu_enabled_spec.js
@@ -0,0 +1,131 @@
+/////////////////////////////////////////////////////////////
+//
+// 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 {menuEnabled} from '../../../pgadmin/static/js/menu/menu_enabled';
+
+const context = describe;
+describe('#menuEnabled', () => {
+ let ourBrowser;
+ beforeEach(() => {
+ const tree = new TreeFake();
+ ourBrowser = {
+ treeMenu: tree,
+ };
+ tree.addNewNode('level1', {}, undefined, []);
+ tree.addNewNode('level1.1', {_type: 'catalog'}, undefined, ['level1']);
+ tree.addNewNode('level1.1.1', {_type: 'database'}, undefined, ['level1', 'level1.1']);
+ tree.addNewNode('level1.2', {_type: 'bamm'}, undefined, ['level1']);
+ tree.addNewNode('level1.2.1', {
+ _type: 'database',
+ allowConn: true,
+ }, undefined, ['level1', 'level1.2']);
+ tree.addNewNode('level1.2.2', {
+ _type: 'database',
+ allowConn: false,
+ }, undefined, ['level1', 'level1.2']);
+ tree.addNewNode('level1.2.3', {
+ _type: 'table',
+ }, undefined, ['level1', 'level1.2']);
+
+ tree.addNewNode('level2', {}, undefined, []);
+ tree.addNewNode('level2.1', null, undefined, ['level2']);
+ tree.addNewNode('level2.1.1', {}, undefined, ['level2', 'level2.1']);
+ });
+
+ context('When the current node is a root node', () => {
+ it('return false', () => {
+ expect(menuEnabled(ourBrowser.treeMenu, [], {}, [{id: 'level1'}])).toBe(false);
+ });
+ });
+
+ context('when current node does not exist', () => {
+ it('return false', () => {
+ expect(menuEnabled(ourBrowser.treeMenu, [], {}, [{id: 'bamm'}])).toBe(false);
+ });
+ });
+
+ context('provided node data is undefined', () => {
+ it('returns false', () => {
+ expect(menuEnabled(ourBrowser.treeMenu, [], undefined, [{id: 'level1'}])).toBe(false);
+ });
+ });
+
+ context('provided node data is null', () => {
+ it('returns false', () => {
+ expect(menuEnabled(ourBrowser.treeMenu, [], null, [{id: 'level1'}])).toBe(false);
+ });
+ });
+
+ context('When the current node is not a root node', () => {
+ context('parent data does not exist', () => {
+ it('returns false', () => {
+ expect(menuEnabled(ourBrowser.treeMenu, [], {}, [{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(ourBrowser.treeMenu,
+ ['schema'],
+ {_type: 'schema'},
+ [{id: 'level1.1.1'}]
+ )).toBe(false);
+ });
+ });
+ context('an ancestor with type catalog exists', () => {
+ it('returns false', () => {
+ expect(menuEnabled(ourBrowser.treeMenu,
+ ['table'],
+ {_type: 'table'},
+ [{id: 'level1.1.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(ourBrowser.treeMenu,
+ ['database'],
+ {
+ _type: 'database',
+ allowConn: true,
+ }, [{id: 'level1.2.1'}])).toBe(true);
+ });
+ });
+ context('current node do not allow connection', () => {
+ it('returns false', () => {
+ expect(menuEnabled(ourBrowser.treeMenu,
+ ['database'], {
+ _type: 'database',
+ allowConn: false,
+ }, [{id: 'level1.2.2'}])).toBe(false);
+ });
+ });
+ });
+ context('current node is not of the type database', () => {
+ it('returns true', () => {
+ expect(menuEnabled(ourBrowser.treeMenu,
+ ['schema'], {
+ _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(ourBrowser.treeMenu, [], {_type: 'catalog'}, [{id: 'level1.1'}])).toBe(false);
+ });
+ });
+ });
+ });
+});
diff --git a/web/regression/javascript/misc/statistics/statistics_spec.js b/web/regression/javascript/misc/statistics/statistics_spec.js
index 40c0065e..e63ffad9 100644
--- a/web/regression/javascript/misc/statistics/statistics_spec.js
+++ b/web/regression/javascript/misc/statistics/statistics_spec.js
@@ -7,7 +7,7 @@
//
//////////////////////////////////////////////////////////////////////////
-import {nodeHasStatistics} from "../../../../pgadmin/static/js/misc/statistics/statistics";
+import {nodeHasStatistics} from '../../../../pgadmin/static/js/misc/statistics/statistics';
describe('#nodeHasStatistics', () => {
describe('when node hasStatistics is not a function', () => {
diff --git a/web/regression/javascript/restore/restore_dialog_spec.js b/web/regression/javascript/restore/restore_dialog_spec.js
new file mode 100644
index 00000000..48ef5961
--- /dev/null
+++ b/web/regression/javascript/restore/restore_dialog_spec.js
@@ -0,0 +1,181 @@
+/////////////////////////////////////////////////////////////
+//
+// 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 {
+ RestoreDialog,
+} from '../../../pgadmin/static/js/restore/restore_dialog';
+import {TreeNode} from '../../../pgadmin/static/js/tree/tree';
+
+const context = describe;
+
+describe('RestoreDialog', () => {
+ let restoreDialog;
+ let pgBrowser;
+ let jquerySpy;
+ let alertifySpy;
+ let restoreModelSpy;
+
+
+ let rootNode;
+ let serverTreeNode;
+ let databaseTreeNode;
+ let ppasServerTreeNode;
+ let noDataNode;
+
+ beforeEach(() => {
+ pgBrowser = {
+ treeMenu: new TreeFake(),
+ Nodes: {
+ server: jasmine.createSpyObj('Node[server]', ['getTreeNodeHierarchy']),
+ database: jasmine.createSpyObj('Node[database]', ['getTreeNodeHierarchy']),
+ },
+ };
+ pgBrowser.Nodes.server.hasId = true;
+ pgBrowser.Nodes.database.hasId = true;
+ jquerySpy = jasmine.createSpy('jquerySpy');
+ restoreModelSpy = jasmine.createSpy('restoreModelSpy');
+
+ rootNode = pgBrowser.treeMenu.addNewNode('level1', {}, [{id: 'level1'}], []);
+ serverTreeNode = new TreeNode('level1.1', {
+ _type: 'server',
+ _id: 10,
+ label: 'some-tree-label',
+ }, [{id: 'level1.1'}]);
+ pgBrowser.treeMenu.addChild(rootNode, serverTreeNode);
+
+ databaseTreeNode = new TreeNode('level1.1.1', {
+ _type: 'database',
+ _id: 10,
+ _label: 'some-database-label',
+ }, [{id: 'level1.1.1'}], ['level1', 'level1.1']);
+ pgBrowser.treeMenu.addChild(serverTreeNode, databaseTreeNode);
+
+ ppasServerTreeNode = new TreeNode('level1.2', {
+ _type: 'server',
+ server_type: 'ppas',
+ }, [{id: 'level1.2'}], ['level1']);
+ pgBrowser.treeMenu.addChild(rootNode, ppasServerTreeNode);
+
+ const level3 = new TreeNode('level3', {}, [{id: 'level3'}]);
+ pgBrowser.treeMenu.addChild(ppasServerTreeNode, level3);
+
+ noDataNode = pgBrowser.treeMenu.addNewNode('level3.1', undefined, [{id: 'level1'}]);
+ pgBrowser.treeMenu.addChild(level3, noDataNode);
+
+ });
+
+ describe('#draw', () => {
+ beforeEach(() => {
+ alertifySpy = jasmine.createSpyObj('alertify', ['alert', 'dialog']);
+ alertifySpy['pg_restore'] = jasmine.createSpy('pg_restore');
+ restoreDialog = new RestoreDialog(
+ pgBrowser,
+ jquerySpy,
+ alertifySpy,
+ restoreModelSpy
+ );
+
+ pgBrowser.get_preference = jasmine.createSpy('get_preferences');
+ });
+
+ context('there are no ancestors of the type server', () => {
+ it('does not create a dialog', () => {
+ pgBrowser.treeMenu.selectNode([{id: 'level1'}]);
+ restoreDialog.draw(null, null, null);
+ expect(alertifySpy['pg_restore']).not.toHaveBeenCalled();
+ });
+
+ it('display an alert with a Restore Error', () => {
+ restoreDialog.draw(null, [{id: 'level1'}], null);
+ expect(alertifySpy.alert).toHaveBeenCalledWith(
+ 'Restore 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 "Restore Error"', () => {
+ restoreDialog.draw(null, [serverTreeNode], null);
+ expect(alertifySpy.alert).toHaveBeenCalledWith(
+ 'Restore Error',
+ 'Failed to load preference pg_bin_dir of module paths'
+ );
+ });
+ });
+
+ context('server is not a ppas server', () => {
+ it('display an alert with "Restore Error"', () => {
+ restoreDialog.draw(null, [ppasServerTreeNode], null);
+ expect(alertifySpy.alert).toHaveBeenCalledWith(
+ 'Restore 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"', () => {
+ restoreDialog.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"', () => {
+ restoreDialog.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 spy;
+ beforeEach(() => {
+ spy = jasmine.createSpyObj('globals', ['resizeTo']);
+ alertifySpy['pg_restore'].and
+ .returnValue(spy);
+ pgBrowser.get_preference.and.returnValue({value: '/some/path'});
+ pgBrowser.Nodes.server.label = 'some-server-label';
+ });
+
+ it('displays the dialog', () => {
+ restoreDialog.draw(null, [{id: 'level1.1'}], {server: true});
+ expect(alertifySpy['pg_restore']).toHaveBeenCalledWith(
+ 'Restore (some-server-label: some-tree-label)',
+ [{id: 'level1.1'}],
+ serverTreeNode.getData(),
+ pgBrowser.Nodes.server
+ );
+ expect(spy.resizeTo).toHaveBeenCalledWith('65%', '60%');
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/web/regression/javascript/restore/restore_dialog_wrapper_spec.js b/web/regression/javascript/restore/restore_dialog_wrapper_spec.js
new file mode 100644
index 00000000..84de8e62
--- /dev/null
+++ b/web/regression/javascript/restore/restore_dialog_wrapper_spec.js
@@ -0,0 +1,593 @@
+/////////////////////////////////////////////////////////////
+//
+// 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 {RestoreDialogWrapper} from '../../../pgadmin/static/js/restore/restore_dialog_wrapper';
+import MockAdapter from 'axios-mock-adapter';
+import axios from 'axios/index';
+import {FakeModel} from '../fake_model';
+import {TreeNode} from '../../../pgadmin/static/js/tree/tree';
+
+let context = describe;
+
+describe('RestoreDialogWrapper', () => {
+ let jquerySpy;
+ let pgBrowser;
+ let alertifySpy;
+ let dialogModelKlassSpy;
+ let backform;
+ let generatedRestoreModel;
+ let restoreDialogWrapper;
+ let noDataNode;
+ let serverTreeNode;
+ let viewSchema;
+ let restoreJQueryContainerSpy;
+ let restoreNodeChildNodeSpy;
+ let restoreNode;
+
+ beforeEach(() => {
+ pgBrowser = {
+ treeMenu: new TreeFake(),
+ Nodes: {
+ server: {
+ hasId: true,
+ getTreeNodeHierarchy: jasmine.createSpy('getTreeNodeHierarchy'),
+ },
+ },
+ keyboardNavigation: jasmine.createSpyObj('keyboardNavigation', ['getDialogTabNavigator']),
+ };
+
+ noDataNode = pgBrowser.treeMenu.addNewNode('level1.1', undefined, [{id: 'level1'}]);
+ serverTreeNode = pgBrowser.treeMenu.addNewNode('level2.1', {
+ _type: 'server',
+ _id: 10,
+ label: 'some-tree-label',
+ }, [{id: 'level2.1'}]);
+ jquerySpy = jasmine.createSpy('jquerySpy');
+ dialogModelKlassSpy = jasmine.createSpy('dialogModelKlass');
+ generatedRestoreModel = {};
+ viewSchema = {};
+ backform = jasmine.createSpyObj('backform', ['generateViewSchema', 'Dialog']);
+ backform.generateViewSchema.and.returnValue(viewSchema);
+ dialogModelKlassSpy.and.returnValue(generatedRestoreModel);
+ restoreJQueryContainerSpy = jasmine.createSpyObj('restoreJQueryContainer', ['get', 'attr']);
+ restoreJQueryContainerSpy.get.and.returnValue(restoreJQueryContainerSpy);
+
+ restoreNode = {
+ __internal: {
+ buttons: [
+ {}, {},
+ {
+ element: {
+ disabled: false,
+ },
+ },
+ ],
+ },
+ elements: {
+ body: {
+ childNodes: [
+ {},
+ ],
+ },
+ content: jasmine.createSpyObj('content', ['appendChild', 'attr']),
+ },
+ };
+
+
+ restoreNodeChildNodeSpy = jasmine.createSpyObj('something', ['addClass']);
+
+ jquerySpy.and.callFake((selector) => {
+ if (selector === '<div class=\'restore_dialog\'></div>') {
+ return restoreJQueryContainerSpy;
+ } else if (selector === restoreNode.elements.body.childNodes[0]) {
+ return restoreNodeChildNodeSpy;
+ }
+ });
+ });
+
+ describe('#prepare', () => {
+
+ beforeEach(() => {
+ alertifySpy = jasmine.createSpyObj('alertify', ['alert', 'dialog']);
+ restoreDialogWrapper = new RestoreDialogWrapper(
+ '<div class=\'restore_dialog\'></div>',
+ 'restoreDialogTitle',
+ 'restore',
+ jquerySpy,
+ pgBrowser,
+ alertifySpy,
+ dialogModelKlassSpy,
+ backform
+ );
+ restoreDialogWrapper = Object.assign(restoreDialogWrapper, restoreNode);
+ });
+ context('no tree element is selected', () => {
+ it('does not create a backform dialog', () => {
+ restoreDialogWrapper.prepare();
+ expect(backform.Dialog).not.toHaveBeenCalled();
+ });
+
+ it('disables the button "submit button" until a filename is selected', () => {
+ restoreDialogWrapper.prepare();
+ expect(restoreDialogWrapper.__internal.buttons[2].element.disabled).toBe(true);
+ });
+ });
+
+ context('selected tree node has no data', () => {
+ beforeEach(() => {
+ pgBrowser.treeMenu.selectNode(noDataNode.domNode);
+ });
+
+ it('does not create a backform dialog', () => {
+ restoreDialogWrapper.prepare();
+ expect(backform.Dialog).not.toHaveBeenCalled();
+ });
+
+ it('disables the button "submit button" until a filename is selected', () => {
+ restoreDialogWrapper.prepare();
+ expect(restoreDialogWrapper.__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,
+ label: 'some-tree-label',
+ },
+ };
+ pgBrowser.treeMenu.selectNode(serverTreeNode.domNode);
+ pgBrowser.Nodes['server'].getTreeNodeHierarchy.and
+ .returnValue(treeHierarchyInformation);
+ dialogSpy = jasmine.createSpyObj('newView', ['render']);
+ dialogSpy.$el = jasmine.createSpyObj('$el', ['find', 'attr']);
+ dialogSpy.model = jasmine.createSpyObj('model', ['on']);
+ dialogSpy.$el.find.and.returnValue([]);
+
+ backform.Dialog.and.returnValue(dialogSpy);
+ });
+
+ it('creates a backform dialog and displays it', () => {
+ restoreDialogWrapper.prepare();
+ expect(backform.Dialog).toHaveBeenCalledWith({
+ el: restoreJQueryContainerSpy,
+ model: generatedRestoreModel,
+ schema: viewSchema,
+ });
+
+ expect(dialogSpy.render).toHaveBeenCalled();
+ });
+
+ it('add alertify classes to restore node childnode', () => {
+ restoreDialogWrapper.prepare();
+ expect(restoreNodeChildNodeSpy.addClass)
+ .toHaveBeenCalledWith('alertify_tools_dialog_properties obj_properties');
+ });
+
+ it('disables the button submit button until a filename is selected', () => {
+ restoreDialogWrapper.prepare();
+ expect(restoreNode.__internal.buttons[2].element.disabled).toBe(true);
+ });
+
+ it('generates a new restore model', () => {
+ restoreDialogWrapper.prepare();
+ expect(dialogModelKlassSpy).toHaveBeenCalledWith(
+ {node_data: pgBrowser.Nodes['server']},
+ {node_info: treeHierarchyInformation}
+ );
+ });
+
+ it('add the new dialog to the restore node HTML', () => {
+ restoreDialogWrapper.prepare();
+ expect(restoreNode.elements.content.appendChild).toHaveBeenCalledWith(restoreJQueryContainerSpy);
+ });
+ });
+ });
+
+ describe('onButtonClicked', () => {
+ let networkMock;
+
+ beforeEach(() => {
+ pgBrowser.showHelp = jasmine.createSpy('showHelp');
+ networkMock = new MockAdapter(axios);
+ alertifySpy = jasmine.createSpyObj('alertify', ['success', 'alert']);
+ restoreDialogWrapper = new RestoreDialogWrapper(
+ '<div class=\'restore_dialog\'></div>',
+ 'restoreDialogTitle',
+ 'restore',
+ jquerySpy,
+ pgBrowser,
+ alertifySpy,
+ dialogModelKlassSpy,
+ backform
+ );
+ restoreDialogWrapper = Object.assign(restoreDialogWrapper, restoreNode);
+
+ });
+
+ afterEach(function () {
+ networkMock.restore();
+ });
+
+ context('dialog help button was pressed', () => {
+ let networkCalled;
+ beforeEach(() => {
+ networkCalled = false;
+ pgBrowser.treeMenu.selectNode(serverTreeNode.domNode);
+ 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';
+ }
+ },
+ },
+ },
+ };
+ restoreDialogWrapper.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 restore', () => {
+ expect(networkCalled).toBe(false);
+ });
+ });
+
+ context('object help button was pressed', () => {
+ let networkCalled;
+ beforeEach(() => {
+ networkCalled = false;
+ pgBrowser.treeMenu.selectNode(serverTreeNode.domNode);
+ 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';
+ }
+ },
+ },
+ },
+ };
+ restoreDialogWrapper.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 restore', () => {
+ expect(networkCalled).toBe(false);
+ });
+ });
+
+ context('restore button was pressed', () => {
+ let networkCalled;
+ let event;
+
+ context('no tree node is selected', () => {
+ beforeEach(() => {
+ networkCalled = false;
+ networkMock.onAny(/.+/).reply(() => {
+ networkCalled = true;
+ return [200, {}];
+ });
+ event = {
+ button: {
+ 'data-btn-name': 'restore',
+ element: {
+ getAttribute: () => {
+ return 'http://someurl';
+ },
+ },
+ },
+ };
+ });
+
+ it('does not start the restore', () => {
+ restoreDialogWrapper.callback(event);
+ expect(networkCalled).toBe(false);
+ });
+ });
+
+ context('tree node selected has no data', () => {
+ beforeEach(() => {
+ networkCalled = false;
+ networkMock.onAny(/.+/).reply(() => {
+ networkCalled = true;
+ return [200, {}];
+ });
+ event = {
+ button: {
+ 'data-btn-name': 'restore',
+ element: {
+ getAttribute: () => {
+ return 'http://someurl';
+ },
+ },
+ },
+ };
+ pgBrowser.treeMenu.selectNode(noDataNode.domNode);
+ });
+
+ it('does not start the restore', () => {
+ restoreDialogWrapper.callback(event);
+ expect(networkCalled).toBe(false);
+ });
+ });
+
+ context('tree node select has data', () => {
+
+ let databaseTreeNode;
+
+ beforeEach(() => {
+ databaseTreeNode = pgBrowser.treeMenu.addNewNode('level3.1', {
+ _type: 'database',
+ _id: 10,
+ _label: 'some-database-label',
+ }, [{id: 'level3.1'}]);
+ pgBrowser.treeMenu.addChild(serverTreeNode, databaseTreeNode);
+ pgBrowser.Nodes.database = {
+ hasId: true,
+ _label: 'some-database-label',
+ };
+ let fakeModel = new FakeModel();
+ fakeModel.set('some-key', 'some-value');
+ restoreDialogWrapper.view = {
+ model: fakeModel,
+ };
+ pgBrowser.treeMenu.selectNode(databaseTreeNode.domNode);
+ pgBrowser.Events = jasmine.createSpyObj('pgBrowserEventsSpy', ['trigger']);
+ event = {
+ button: {
+ 'data-btn-name': 'restore',
+ element: {
+ getAttribute: () => {
+ return 'http://someurl';
+ },
+ },
+ },
+ };
+ });
+ context('restore job created successfully', () => {
+ let dataSentToServer;
+ beforeEach(() => {
+ networkMock.onPost('/restore/job/10').reply((request) => {
+ dataSentToServer = request.data;
+ return [200, {}];
+ });
+ });
+
+ it('create an success alert box', (done) => {
+ restoreDialogWrapper.callback(event);
+ setTimeout(() => {
+ expect(alertifySpy.success).toHaveBeenCalledWith(
+ 'Restore job created.',
+ 5
+ );
+ done();
+ }, 0);
+ });
+
+ it('trigger background process', (done) => {
+ restoreDialogWrapper.callback(event);
+ setTimeout(() => {
+ expect(pgBrowser.Events.trigger).toHaveBeenCalledWith(
+ 'pgadmin-bgprocess:created',
+ restoreDialogWrapper
+ );
+ done();
+ }, 0);
+ });
+
+ it('send correct data to server', (done) => {
+ restoreDialogWrapper.callback(event);
+ setTimeout(() => {
+ expect(JSON.parse(dataSentToServer)).toEqual({
+ 'some-key': 'some-value',
+ 'database': 'some-database-label',
+ });
+ done();
+ }, 0);
+ });
+ });
+
+ context('error creating restore job', () => {
+ beforeEach(() => {
+ networkMock.onPost('/restore/job/10').reply(() => {
+ return [400, {}];
+ });
+ });
+
+ it('creates an alert box', (done) => {
+ restoreDialogWrapper.callback(event);
+ setTimeout(() => {
+ expect(alertifySpy.alert).toHaveBeenCalledWith(
+ 'Restore job failed.',
+ undefined
+ );
+ done();
+ }, 0);
+ });
+ });
+ });
+ });
+ });
+
+ describe('setExtraParameters', () => {
+ let selectedNode;
+ let treeInfo;
+ let model;
+
+ beforeEach(() => {
+ restoreDialogWrapper = new RestoreDialogWrapper(
+ '<div class=\'restore_dialog\'></div>',
+ 'restoreDialogTitle',
+ 'restore',
+ jquerySpy,
+ pgBrowser,
+ alertifySpy,
+ dialogModelKlassSpy,
+ backform
+ );
+
+ model = new FakeModel();
+ restoreDialogWrapper.view = {
+ model: model,
+ };
+ });
+
+ context('when it is a custom model', () => {
+ beforeEach(() => {
+ model.set('custom', true);
+ treeInfo = {
+ 'database': {
+ '_label': 'some-database-label',
+ },
+ };
+ });
+
+ it('only sets the database', () => {
+ restoreDialogWrapper.setExtraParameters(selectedNode, treeInfo);
+ expect(model.toJSON()).toEqual({
+ 'custom': true,
+ 'database': 'some-database-label',
+ });
+ });
+ });
+
+ context('when it is not a custom model', () => {
+ beforeEach(() => {
+ model.set('custom', false);
+ treeInfo = {
+ 'database': {
+ '_label': 'some-database-label',
+ },
+ 'schema': {
+ '_label': 'some-schema-label',
+ },
+ };
+ });
+
+ context('when selected node is a schema', () => {
+ it('sets schemas on the model', () => {
+ selectedNode = new TreeNode('schema', {_type: 'schema', _label: 'some-schema-label'}, '');
+ restoreDialogWrapper.setExtraParameters(selectedNode, treeInfo);
+ expect(model.toJSON()).toEqual({
+ custom: false,
+ database: 'some-database-label',
+ schemas: ['some-schema-label'],
+ });
+ });
+ });
+
+ context('when selected node is a table', () => {
+ it('sets schemas and table on the model', () => {
+ selectedNode = new TreeNode('table', {_type: 'table', _label: 'some-table-label'}, '');
+ restoreDialogWrapper.setExtraParameters(selectedNode, treeInfo);
+ expect(model.toJSON()).toEqual({
+ custom: false,
+ database: 'some-database-label',
+ schemas: ['some-schema-label'],
+ tables: ['some-table-label'],
+ });
+ });
+ });
+
+ context('when selected node is a function', () => {
+ it('sets schemas and function on the model', () => {
+ selectedNode = new TreeNode('function', {_type: 'function', _label: 'some-function-label'}, '');
+ restoreDialogWrapper.setExtraParameters(selectedNode, treeInfo);
+ expect(model.toJSON()).toEqual({
+ custom: false,
+ database: 'some-database-label',
+ schemas: ['some-schema-label'],
+ functions: ['some-function-label'],
+ });
+ });
+ });
+
+ context('when selected node is an index', () => {
+ it('sets schemas and index on the model', () => {
+ selectedNode = new TreeNode('index', {_type: 'index', _label: 'some-index-label'}, '');
+ restoreDialogWrapper.setExtraParameters(selectedNode, treeInfo);
+ expect(model.toJSON()).toEqual({
+ custom: false,
+ database: 'some-database-label',
+ schemas: ['some-schema-label'],
+ indexes: ['some-index-label'],
+ });
+ });
+ });
+
+ context('when selected node is a trigger', () => {
+ it('sets schemas and trigger on the model', () => {
+ selectedNode = new TreeNode('trigger', {_type: 'trigger', _label: 'some-trigger-label'}, '');
+ restoreDialogWrapper.setExtraParameters(selectedNode, treeInfo);
+ expect(model.toJSON()).toEqual({
+ custom: false,
+ database: 'some-database-label',
+ schemas: ['some-schema-label'],
+ triggers: ['some-trigger-label'],
+ });
+ });
+ });
+
+ context('when selected node is a trigger_func', () => {
+ it('sets schemas and trigger_func on the model', () => {
+ selectedNode = new TreeNode('trigger_func', {_type: 'trigger_func', _label: 'some-trigger_func-label'}, '');
+ restoreDialogWrapper.setExtraParameters(selectedNode, treeInfo);
+ expect(model.toJSON()).toEqual({
+ custom: false,
+ database: 'some-database-label',
+ schemas: ['some-schema-label'],
+ trigger_funcs: ['some-trigger_func-label'],
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/web/regression/javascript/schema/can_drop_child_spec.js b/web/regression/javascript/schema/can_drop_child_spec.js
new file mode 100644
index 00000000..388dffbc
--- /dev/null
+++ b/web/regression/javascript/schema/can_drop_child_spec.js
@@ -0,0 +1,82 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import {canDropChild} from '../../../pgadmin/static/js/schema/can_drop_child';
+import {TreeFake} from '../tree/tree_fake';
+
+let context = describe;
+
+describe('can_drop_child', () => {
+
+ let browser;
+ let itemData;
+ let item;
+
+ beforeEach(() => {
+ browser = {
+ treeMenu: new TreeFake(),
+ };
+ item = [];
+ browser.treeMenu.addNewNode('node1', {_type: 'schema'}, [{id: 'node1'}], []);
+ browser.treeMenu.addNewNode('node1.1', {_type: 'database'}, [{id: 'node1.1'}], ['node1']);
+ browser.treeMenu.addNewNode('node2', {_type: 'catalog'}, [{id: 'node2'}], []);
+ browser.treeMenu.addNewNode('node2.1', {_type: 'table'}, [{id: 'node2.1'}], ['node2']);
+ browser.treeMenu.addNewNode('node3', {_type: 'function'}, [{id: 'node3'}], []);
+ browser.treeMenu.addNewNode('node3.1', {_type: 'procedure'}, [{id: 'node3.1'}], ['node3']);
+ });
+
+ context('when current node is of the type schema', () => {
+ beforeEach(() => {
+ itemData = {
+ _type: 'schema',
+ };
+ item = [{id: 'node1'}];
+ });
+
+ it('returns true', () => {
+ let bool = canDropChild(browser, itemData, item);
+ expect(bool).toBe(true);
+ });
+ });
+
+ context('when a parent of the current node is a schema', () => {
+ it('returns true', () => {
+ itemData = {
+ _type: 'database',
+ };
+ item = [{id: 'node1.1'}];
+ let bool = canDropChild(browser, itemData, item);
+ expect(bool).toBe(true);
+ });
+ });
+
+ context('when a parent of the current node is a catalog', () => {
+ it('returns false', () => {
+ itemData= {
+ _type: 'table',
+ };
+ item = [{id: 'node2.1'}];
+
+ let bool = canDropChild(browser, itemData, item);
+ expect(bool).toBe(false);
+ });
+ });
+
+ context('when a parent of the current node is not catalog nor schema', () => {
+ it('returns true', () => {
+ itemData = {
+ _type: 'procedure',
+ };
+ item = [{id: 'node3.1'}];
+
+ let bool = canDropChild(browser, itemData, item);
+ expect(bool).toBe(true);
+ });
+ });
+});
diff --git a/web/regression/javascript/sqleditor/calculate_query_run_time_spec.js b/web/regression/javascript/sqleditor/calculate_query_run_time_spec.js
index c1ed5c57..31c14294 100644
--- a/web/regression/javascript/sqleditor/calculate_query_run_time_spec.js
+++ b/web/regression/javascript/sqleditor/calculate_query_run_time_spec.js
@@ -29,7 +29,7 @@ describe('#calculateQueryRunTime', () => {
minutes:30,
seconds:21,
milliseconds:70}).toDate();
- expect(calculateQueryRunTime(startDate, endDate))
+ expect(calculateQueryRunTime(startDate, endDate))
.toEqual('947 msec');
});
});
@@ -52,7 +52,7 @@ describe('#calculateQueryRunTime', () => {
minutes:31,
seconds:15,
milliseconds:70}).toDate();
- expect(calculateQueryRunTime(startDate, endDate))
+ expect(calculateQueryRunTime(startDate, endDate))
.toEqual('54 secs 947 msec');
});
});
@@ -75,7 +75,7 @@ describe('#calculateQueryRunTime', () => {
minutes:40,
seconds:15,
milliseconds:70}).toDate();
- expect(calculateQueryRunTime(startDate, endDate))
+ expect(calculateQueryRunTime(startDate, endDate))
.toEqual('9 min 54 secs');
});
});
diff --git a/web/regression/javascript/sqleditor/filter_dialog_specs.js b/web/regression/javascript/sqleditor/filter_dialog_specs.js
index e13fa097..cea75e6b 100644
--- a/web/regression/javascript/sqleditor/filter_dialog_specs.js
+++ b/web/regression/javascript/sqleditor/filter_dialog_specs.js
@@ -7,18 +7,15 @@
//
//////////////////////////////////////////////////////////////////////////
import filterDialog from 'sources/sqleditor/filter_dialog';
-import filterDialogModel from 'sources/sqleditor/filter_dialog_model';
describe('filterDialog', () => {
- let sqlEditorController;
- sqlEditorController = jasmine.createSpy('sqlEditorController')
describe('filterDialog', () => {
describe('when using filter dialog', () => {
beforeEach(() => {
spyOn(filterDialog, 'dialog');
});
- it("it should be defined as function", function() {
+ it('it should be defined as function', function() {
expect(filterDialog.dialog).toBeDefined();
});
diff --git a/web/regression/javascript/sqleditor/keyboard_shortcuts_spec.js b/web/regression/javascript/sqleditor/keyboard_shortcuts_spec.js
index 18a4cc1b..06586d34 100644
--- a/web/regression/javascript/sqleditor/keyboard_shortcuts_spec.js
+++ b/web/regression/javascript/sqleditor/keyboard_shortcuts_spec.js
@@ -35,48 +35,48 @@ describe('the keyboard shortcuts', () => {
shift: false,
control: false,
key: {
- key_code: F5_KEY
- }
+ key_code: F5_KEY,
+ },
},
explain: {
alt: false,
shift: false,
control: false,
key: {
- key_code: F7_KEY
- }
+ key_code: F7_KEY,
+ },
},
explain_analyze: {
alt: false,
shift: true,
control: false,
key: {
- key_code: F7_KEY
- }
+ key_code: F7_KEY,
+ },
},
download_csv: {
alt: false,
shift: false,
control: false,
key: {
- key_code: F8_KEY
- }
+ key_code: F8_KEY,
+ },
},
move_next: {
alt: false,
shift: false,
control: false,
key: {
- key_code: null
- }
+ key_code: null,
+ },
},
move_previous: {
alt: false,
shift: false,
control: false,
key: {
- key_code: null
- }
+ key_code: null,
+ },
},
};
diff --git a/web/regression/javascript/sqleditor/query_tool_http_error_handler_spec.js b/web/regression/javascript/sqleditor/query_tool_http_error_handler_spec.js
index 21281523..bc5f50e2 100644
--- a/web/regression/javascript/sqleditor/query_tool_http_error_handler_spec.js
+++ b/web/regression/javascript/sqleditor/query_tool_http_error_handler_spec.js
@@ -9,7 +9,7 @@
import {
httpResponseRequiresNewTransaction,
- handleQueryToolAjaxError
+ handleQueryToolAjaxError,
} from '../../../pgadmin/static/js/sqleditor/query_tool_http_error_handler';
describe('#httpResponseRequiresNewTransaction', () => {
@@ -71,29 +71,29 @@ describe('#httpResponseRequiresNewTransaction', () => {
describe('#handleQueryToolAjaxError', () => {
let sqlEditorHandler,
exceptionSpy, stateToSave,
- stateParameters, checkTransaction, UserManagementMock,
+ stateParameters, checkTransaction,
pgBrowserMock;
- beforeEach(() => {
- stateToSave = 'testState';
- stateParameters = [];
- checkTransaction = false;
- sqlEditorHandler = jasmine.createSpyObj(
+ beforeEach(() => {
+ stateToSave = 'testState';
+ stateParameters = [];
+ checkTransaction = false;
+ sqlEditorHandler = jasmine.createSpyObj(
'handler', ['initTransaction', 'saveState', 'handle_connection_lost']
);
- exceptionSpy = {
- readyState: 0,
- status: 404,
- data: {
- info: 'CONNECTION_LOST',
- },
- };
- pgBrowserMock = {
- 'Browser': {
- 'UserManagement': jasmine.createSpyObj('UserManagement', ['isPgaLoginRequired', 'pgaLogin'])
- }
- };
- });
+ exceptionSpy = {
+ readyState: 0,
+ status: 404,
+ data: {
+ info: 'CONNECTION_LOST',
+ },
+ };
+ pgBrowserMock = {
+ 'Browser': {
+ 'UserManagement': jasmine.createSpyObj('UserManagement', ['isPgaLoginRequired', 'pgaLogin']),
+ },
+ };
+ });
describe('when ready state is 0', () => {
it('should return connection', () => {
@@ -149,7 +149,7 @@ describe('#handleQueryToolAjaxError', () => {
exceptionSpy.readyState = 1;
exceptionSpy.status = 503;
exceptionSpy.responseJSON = {
- 'info': 'CONNECTION_LOST'
+ 'info': 'CONNECTION_LOST',
};
pgBrowserMock.Browser.UserManagement.isPgaLoginRequired.and.returnValue(false);
checkTransaction = false;
diff --git a/web/regression/javascript/table/enable_disable_triggers_spec.js b/web/regression/javascript/table/enable_disable_triggers_spec.js
new file mode 100644
index 00000000..767c78a4
--- /dev/null
+++ b/web/regression/javascript/table/enable_disable_triggers_spec.js
@@ -0,0 +1,271 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import MockAdapter from 'axios-mock-adapter';
+import axios from 'axios/index';
+import {
+ enableTriggers,
+ disableTriggers,
+} from '../../../pgadmin/static/js/table/enable_disable_triggers';
+import {TreeFake} from '../tree/tree_fake';
+import {TreeNode} from '../../../pgadmin/static/js/tree/tree';
+
+describe('#enableTriggers', () => {
+ let networkMock;
+ let tree;
+ let alertify;
+ let generateUrlSpy;
+ beforeEach(() => {
+ networkMock = new MockAdapter(axios);
+ tree = new TreeFake();
+ const server1 = tree.addNewNode('server1', {_id: 1}, ['<li>server1</li>']);
+ const database1 = tree.addNewNode('database1', {_type: 'database'}, ['<li>database1</li>']);
+ tree.addChild(server1, database1);
+
+ const schema1 = tree.addNewNode('schema1', {_type: 'schema'}, ['<li>schema1</li>']);
+ tree.addChild(database1, schema1);
+
+ const table1 = tree.addNewNode('table1', {_type: 'table'}, ['<li>table1</li>']);
+ tree.addChild(schema1, table1);
+
+ const column1 = tree.addNewNode('column1', {_type: 'column'}, ['<li>column1</li>']);
+ tree.addChild(table1, column1);
+
+ const tableNoData = tree.addNewNode('table-no-data', undefined, ['<li>table-no-data</li>']);
+ tree.addChild(schema1, tableNoData);
+
+ alertify = jasmine.createSpyObj('alertify', ['success', 'error']);
+ generateUrlSpy = jasmine.createSpy('generateUrl');
+ generateUrlSpy.and.returnValue('/some/place');
+ });
+
+ describe('no node is selected', () => {
+ it('does not send the request to the backend', (done) => {
+ networkMock.onAny('.*').reply(200, () => {
+ });
+
+ setTimeout(() => {
+ expect(enableTriggers(tree, alertify, generateUrlSpy, {})).toBe(false);
+ done();
+ }, 0);
+ });
+ });
+
+ describe('a node is selected', () => {
+ describe('node as no data', () => {
+ it('does not send the request to the backend', () => {
+ tree.selectNode([{id: 'table-no-data'}]);
+
+ networkMock.onAny('.*').reply(200, () => {
+ });
+
+ setTimeout(() => {
+ expect(enableTriggers(tree, alertify, generateUrlSpy, {})).toBe(false);
+ }, 0);
+ });
+ });
+
+ describe('node as data', () => {
+ describe('backend responds with success', () => {
+ let networkMockCalledWith;
+ beforeEach(() => {
+ networkMockCalledWith = false;
+ networkMock.onPut(/.*/).reply((configuration) => {
+ networkMockCalledWith = configuration;
+ return [200, {
+ success: 1,
+ info: 'some information',
+ }];
+ });
+ });
+
+ it('displays an alert box with success', (done) => {
+ tree.selectNode([{id: 'table1'}]);
+ enableTriggers(tree, alertify, generateUrlSpy, {});
+ setTimeout(() => {
+ expect(alertify.success).toHaveBeenCalledWith('some information');
+ done();
+ }, 0);
+ });
+
+ it('reloads the node', (done) => {
+ enableTriggers(tree, alertify, generateUrlSpy, {item: [{id: 'table1'}]});
+ setTimeout(() => {
+ expect(tree.selected()).toEqual(['<li>table1</li>']);
+ done();
+ }, 20);
+ });
+
+ it('call backend with the correct parameters', (done) => {
+ enableTriggers(tree, alertify, generateUrlSpy, {item: [{id: 'table1'}]});
+ setTimeout(() => {
+ expect(networkMockCalledWith.data).toEqual(JSON.stringify({enable: 'true'}));
+ done();
+ }, 0);
+ });
+ });
+
+ describe('backend responds with error', () => {
+ beforeEach(() => {
+ networkMock.onPut(/.*/).reply(() => {
+ return [500, {
+ success: 0,
+ errormsg: 'some error message',
+ }];
+ });
+ });
+
+ it('displays an error alert', (done) => {
+ tree.selectNode([{id: 'table1'}]);
+ enableTriggers(tree, alertify, generateUrlSpy, {});
+ setTimeout(() => {
+ expect(alertify.error).toHaveBeenCalledWith('some error message');
+ done();
+ }, 0);
+ });
+
+ it('unload the node', (done) => {
+ enableTriggers(tree, alertify, generateUrlSpy, {item: [{id: 'table1'}]});
+
+ setTimeout(() => {
+ expect(tree.findNodeByDomElement([{id: 'table1'}]).children.length).toBe(0);
+ done();
+ }, 20);
+ });
+ });
+ });
+ });
+});
+
+describe('#disableTriggers', () => {
+ let networkMock;
+ let tree;
+ let alertify;
+ let generateUrlSpy;
+ beforeEach(() => {
+ networkMock = new MockAdapter(axios);
+ tree = new TreeFake();
+ const server1 = tree.addNewNode('server1', {_id: 1}, ['<li>server1</li>']);
+ const database1 = new TreeNode('database1', {_type: 'database'}, ['<li>database1</li>']);
+ tree.addChild(server1, database1);
+
+ const schema1 = new TreeNode('schema1', {_type: 'schema'}, ['<li>schema1</li>']);
+ tree.addChild(database1, schema1);
+
+ const table1 = new TreeNode('table1', {_type: 'table'}, ['<li>table1</li>']);
+ tree.addChild(schema1, table1);
+
+ const column1 = new TreeNode('column1', {_type: 'column'}, ['<li>column1</li>']);
+ tree.addChild(table1, column1);
+
+ const tableNoData = new TreeNode('table-no-data', undefined, ['<li>table-no-data</li>']);
+ tree.addChild(schema1, tableNoData);
+
+ alertify = jasmine.createSpyObj('alertify', ['success', 'error']);
+ generateUrlSpy = jasmine.createSpy('generateUrl');
+ generateUrlSpy.and.returnValue('/some/place');
+ });
+
+ describe('no node is selected', () => {
+ it('does not send the request to the backend', (done) => {
+ networkMock.onAny('.*').reply(200, () => {
+ });
+
+ setTimeout(() => {
+ expect(disableTriggers(tree, alertify, generateUrlSpy, {})).toBe(false);
+ done();
+ }, 0);
+ });
+ });
+
+ describe('a node is selected', () => {
+ describe('node as no data', () => {
+ it('does not send the request to the backend', () => {
+ tree.selectNode([{id: 'table-no-data'}]);
+
+ networkMock.onAny('.*').reply(200, () => {
+ });
+
+ setTimeout(() => {
+ expect(disableTriggers(tree, alertify, generateUrlSpy, {})).toBe(false);
+ }, 0);
+ });
+ });
+
+ describe('node as data', () => {
+ describe('backend responds with success', () => {
+ let networkMockCalledWith;
+ beforeEach(() => {
+ networkMockCalledWith = false;
+ networkMock.onPut(/.*/).reply((configuration) => {
+ networkMockCalledWith = configuration;
+ return [200, {
+ success: 1,
+ info: 'some information',
+ }];
+ });
+ });
+
+ it('displays an alert box with success', (done) => {
+ tree.selectNode([{id: 'table1'}]);
+ disableTriggers(tree, alertify, generateUrlSpy, {});
+ setTimeout(() => {
+ expect(alertify.success).toHaveBeenCalledWith('some information');
+ done();
+ }, 0);
+ });
+
+ it('reloads the node', (done) => {
+ disableTriggers(tree, alertify, generateUrlSpy, {item: [{id: 'table1'}]});
+ setTimeout(() => {
+ expect(tree.selected()).toEqual(['<li>table1</li>']);
+ done();
+ }, 20);
+ });
+
+ it('call backend with the correct parameters', (done) => {
+ disableTriggers(tree, alertify, generateUrlSpy, {item: [{id: 'table1'}]});
+ setTimeout(() => {
+ expect(networkMockCalledWith.data).toEqual(JSON.stringify({enable: 'false'}));
+ done();
+ }, 0);
+ });
+ });
+
+ describe('backend responds with error', () => {
+ beforeEach(() => {
+ networkMock.onPut(/.*/).reply(() => {
+ return [500, {
+ success: 0,
+ errormsg: 'some error message',
+ }];
+ });
+ });
+
+ it('displays an error alert', (done) => {
+ tree.selectNode([{id: 'table1'}]);
+ disableTriggers(tree, alertify, generateUrlSpy, {});
+ setTimeout(() => {
+ expect(alertify.error).toHaveBeenCalledWith('some error message');
+ done();
+ }, 0);
+ });
+
+ it('unload the node', (done) => {
+ disableTriggers(tree, alertify, generateUrlSpy, {item: [{id: 'table1'}]});
+
+ setTimeout(() => {
+ expect(tree.findNodeByDomElement([{id: 'table1'}]).children.length).toBe(0);
+ done();
+ }, 20);
+ });
+ });
+ });
+ });
+});
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..479e515c
--- /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 {TreeNode} from '../../../pgadmin/static/js/tree/tree';
+import {TreeFake} from './tree_fake';
+
+const context = describe;
+
+describe('tree#node#getTreeNodeHierarchy', () => {
+ let browser;
+ let newTree;
+ beforeEach(() => {
+ newTree = new TreeFake();
+ 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;
+ });
+
+ 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',
+ });
+ });
+
+ it('returns a object with the element type passed data and priority == 0', () => {
+ const result = getTreeNodeHierarchyFromIdentifier.bind(browser)([{id: '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',
+ }, []);
+ });
+
+ 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',
+ }, []);
+ });
+
+ 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(() => {
+ const root = newTree.addNewNode('root', {
+ 'some key': 'some value',
+ '_type': 'special one',
+ });
+ const firstChild = new TreeNode('first child', {
+ 'some key': 'some other value',
+ '_type': 'child special',
+ }, ['root']);
+ newTree.addChild(root, firstChild);
+ });
+
+ it('returns a empty object', () => {
+ const result = getTreeNodeHierarchyFromIdentifier.bind(browser)([{id: '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', () => {
+ const rootNode = newTree.addNewNode('root', {
+ 'some key': 'some value',
+ '_type': 'special one',
+ }, []);
+
+ const level1 = new TreeNode('level 1', {
+ 'some key': 'some value',
+ '_type': 'other type',
+ });
+ newTree.addChild(rootNode, level1);
+
+ newTree.addChild(level1, new TreeNode('level 2', {
+ 'some key': 'expected value',
+ '_type': 'special one',
+ 'some other key': 'some other value',
+ }));
+
+ const result = getTreeNodeHierarchyFromIdentifier.bind(browser)([{id: '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', () => {
+ const root = newTree.addNewNode('root', {
+ 'some key': 'some value',
+ '_type': 'special one',
+ }, []);
+ const level1 = new TreeNode('level 1', {
+ 'some key': 'some value',
+ '_type': 'table',
+ });
+ newTree.addChild(root, level1);
+ newTree.addChild(level1, new TreeNode('level 2', {
+ 'some key': 'expected value',
+ '_type': 'partition',
+ 'some other key': 'some other value',
+ }));
+
+ const result = getTreeNodeHierarchyFromIdentifier.bind(browser)([{id:'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(() => {
+ const root = newTree.addNewNode('root', {
+ 'some key': 'some value',
+ '_type': 'special one',
+ }, []);
+ treeNode = new TreeNode('first child', {
+ 'some key': 'some other value',
+ '_type': 'child special',
+ });
+ newTree.addChild(root, treeNode);
+ });
+
+ 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', () => {
+ const root = newTree.addNewNode('root', {
+ 'some key': 'some value',
+ '_type': 'special one',
+ }, []);
+ const level1 = new TreeNode('level 1', {
+ 'some key': 'some value',
+ '_type': 'other type',
+ });
+ newTree.addChild(root, level1);
+ treeNode = new TreeNode('level 2', {
+ 'some key': 'expected value',
+ '_type': 'special one',
+ 'some other key': 'some other value',
+ });
+ newTree.addChild(level1, treeNode);
+
+ 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', () => {
+ const root = newTree.addNewNode('root', {
+ 'some key': 'some value',
+ '_type': 'special one',
+ });
+ const level1 = newTree.addNewNode('level 1', {
+ 'some key': 'some value',
+ '_type': 'table',
+ });
+ newTree.addChild(root, level1);
+ treeNode = new TreeNode('level 2', {
+ 'some key': 'expected value',
+ '_type': 'partition',
+ 'some other key': 'some other value',
+ });
+ newTree.addChild(level1, treeNode);
+
+ 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,
+ },
+ });
+ });
+ });
+ });
+ });
+});
--
2.16.2
view thread (69+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected], [email protected]
Subject: Re: [pgadmin4][patch] Initial patch to decouple from ACI Tree
In-Reply-To: <CAE+jjanjQ295x0DgffT8OARRv8fge5KOPJpMCnLWe+j0jdqGJQ@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