From 7dd6372eeb3fb2c979fa767afc95d5ee6e2e41ee Mon Sep 17 00:00:00 2001 From: Joao De Almeida Pereira Date: Tue, 5 Jun 2018 16:06:19 +0530 Subject: [PATCH] Extract the tests and refactor some of the methods. Extract some of the ACI Tree functionalities, and decouple it from the main source. Also - create some abstractions from the repeated code around the enable/disable the schema children object create/edit/delete functionalities, and also created the dialog wrappers for backup and restore dialogs. Reviewed by: Khushboo and Ashesh Refactored by: Ashesh --- .../schemas/collations/static/js/collation.js | 34 +- .../schemas/domains/static/js/domain.js | 38 +- .../foreign_tables/static/js/foreign_table.js | 37 +- .../static/js/fts_configuration.js | 38 +- .../static/js/fts_dictionary.js | 39 +- .../fts_parser/static/js/fts_parser.js | 38 +- .../fts_templates/static/js/fts_template.js | 38 +- .../schemas/functions/static/js/function.js | 41 +- .../functions/static/js/trigger_function.js | 40 +- .../schemas/sequences/static/js/sequence.js | 39 +- .../databases/schemas/static/js/child.js | 22 + .../databases/schemas/static/js/schema.js | 46 +- .../static/js/schema_child_tree_node.js | 41 ++ .../schemas/tables/column/static/js/column.js | 36 +- .../static/js/check_constraint.js | 1 - .../static/js/exclusion_constraint.js | 1 - .../foreign_key/static/js/foreign_key.js | 1 - .../index_constraint/static/js/primary_key.js | 1 - .../static/js/unique_constraint.js | 1 - .../constraints/static/js/constraints.js | 2 - .../schemas/tables/indexes/static/js/index.js | 12 +- .../tables/partitions/static/js/partition.js | 68 +- .../schemas/tables/rules/static/js/rule.js | 2 - .../static/js/enable_disable_triggers.js | 52 ++ .../schemas/tables/static/js/table.js | 112 +-- .../tables/triggers/static/js/trigger.js | 59 +- .../databases/schemas/types/static/js/type.js | 41 +- .../schemas/views/static/js/mview.js | 52 +- .../databases/schemas/views/static/js/view.js | 59 +- web/pgadmin/browser/static/js/collection.js | 8 +- web/pgadmin/browser/static/js/node.js | 32 +- web/pgadmin/static/js/alertify/dialog.js | 150 ++++ .../static/js/alertify/dialog_factory.js | 52 ++ .../static/js/alertify/dialog_wrapper.js | 57 ++ .../js/nodes/supported_database_node.js | 37 + .../static/js/tree/pgadmin_tree_node.js | 72 ++ web/pgadmin/static/js/tree/tree.js | 18 +- web/pgadmin/tools/backup/static/js/backup.js | 616 +--------------- .../tools/backup/static/js/backup_dialog.js | 72 ++ .../backup/static/js/backup_dialog_wrapper.js | 258 +++++++ .../tools/backup/static/js/menu_utils.js | 23 + .../tools/datagrid/static/js/datagrid.js | 117 +-- .../datagrid/static/js/get_panel_title.js | 33 + .../tools/datagrid/static/js/show_data.js | 92 +++ .../datagrid/static/js/show_query_tool.js | 63 ++ .../grant_wizard/static/js/grant_wizard.js | 56 +- .../grant_wizard/static/js/menu_utils.js | 16 + .../import_export/static/js/import_export.js | 29 +- .../maintenance/static/js/maintenance.js | 49 +- .../tools/maintenance/static/js/menu_utils.js | 13 + .../tools/restore/static/js/menu_utils.js | 18 + .../tools/restore/static/js/restore.js | 389 +--------- .../tools/restore/static/js/restore.js.rej | 322 +++++++++ .../tools/restore/static/js/restore_dialog.js | 57 ++ .../static/js/restore_dialog_wrapper.js | 255 +++++++ .../javascript/backup/backup_dialog_spec.js | 205 ++++++ .../backup/backup_dialog_wrapper_spec.js | 675 ++++++++++++++++++ .../global_server_backup_dialog_spec.js | 168 +++++ .../javascript/backup/menu_utils_spec.js | 55 ++ .../common_keyboard_shortcuts_spec.js | 4 - .../datagrid/get_panel_title_spec.js | 82 +++ .../javascript/datagrid/show_data_spec.js | 171 +++++ .../datagrid/show_query_tool_spec.js | 125 ++++ .../javascript/fake_browser/browser.js | 12 + web/regression/javascript/fake_endpoints.js | 6 + web/regression/javascript/fake_model.js | 21 + .../nodes/schema/child_menu_spec.js | 253 +++++++ .../javascript/restore/restore_dialog_spec.js | 203 ++++++ .../restore/restore_dialog_wrapper_spec.js | 593 +++++++++++++++ .../sqleditor/filter_dialog_specs.js | 2 - .../table/enable_disable_triggers_spec.js | 271 +++++++ .../javascript/tree/pgadmin_tree_node_spec.js | 353 +++++++++ web/regression/javascript/tree/tree_fake.js | 28 +- web/webpack.shim.js | 1 + web/webpack.test.config.js | 2 + 75 files changed, 5186 insertions(+), 1939 deletions(-) create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/static/js/child.js create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/static/js/schema_child_tree_node.js create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/enable_disable_triggers.js create mode 100644 web/pgadmin/static/js/alertify/dialog.js create mode 100644 web/pgadmin/static/js/alertify/dialog_factory.js create mode 100644 web/pgadmin/static/js/alertify/dialog_wrapper.js create mode 100644 web/pgadmin/static/js/nodes/supported_database_node.js create mode 100644 web/pgadmin/static/js/tree/pgadmin_tree_node.js create mode 100644 web/pgadmin/tools/backup/static/js/backup_dialog.js create mode 100644 web/pgadmin/tools/backup/static/js/backup_dialog_wrapper.js create mode 100644 web/pgadmin/tools/backup/static/js/menu_utils.js create mode 100644 web/pgadmin/tools/datagrid/static/js/get_panel_title.js create mode 100644 web/pgadmin/tools/datagrid/static/js/show_data.js create mode 100644 web/pgadmin/tools/datagrid/static/js/show_query_tool.js create mode 100644 web/pgadmin/tools/grant_wizard/static/js/menu_utils.js create mode 100644 web/pgadmin/tools/maintenance/static/js/menu_utils.js create mode 100644 web/pgadmin/tools/restore/static/js/menu_utils.js create mode 100644 web/pgadmin/tools/restore/static/js/restore.js.rej create mode 100644 web/pgadmin/tools/restore/static/js/restore_dialog.js create mode 100644 web/pgadmin/tools/restore/static/js/restore_dialog_wrapper.js create mode 100644 web/regression/javascript/backup/backup_dialog_spec.js create mode 100644 web/regression/javascript/backup/backup_dialog_wrapper_spec.js create mode 100644 web/regression/javascript/backup/global_server_backup_dialog_spec.js create mode 100644 web/regression/javascript/backup/menu_utils_spec.js create mode 100644 web/regression/javascript/datagrid/get_panel_title_spec.js create mode 100644 web/regression/javascript/datagrid/show_data_spec.js create mode 100644 web/regression/javascript/datagrid/show_query_tool_spec.js create mode 100644 web/regression/javascript/fake_browser/browser.js create mode 100644 web/regression/javascript/fake_model.js create mode 100644 web/regression/javascript/nodes/schema/child_menu_spec.js create mode 100644 web/regression/javascript/restore/restore_dialog_spec.js create mode 100644 web/regression/javascript/restore/restore_dialog_wrapper_spec.js create mode 100644 web/regression/javascript/table/enable_disable_triggers_spec.js create mode 100644 web/regression/javascript/tree/pgadmin_tree_node_spec.js 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 9015d8d2e..7fd28a7c4 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,8 @@ define('pgadmin.node.collation', [ 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'underscore.string', 'sources/pgadmin', 'pgadmin.browser', - 'pgadmin.browser.collection', -], function(gettext, url_for, $, _, S, pgAdmin, pgBrowser) { + 'pgadmin.node.schema.dir/child', 'pgadmin.browser.collection', +], function(gettext, url_for, $, _, S, pgAdmin, pgBrowser, schemaChild) { if (!pgBrowser.Nodes['coll-collation']) { pgAdmin.Browser.Nodes['coll-collation'] = @@ -15,7 +15,7 @@ define('pgadmin.node.collation', [ } if (!pgBrowser.Nodes['collation']) { - pgAdmin.Browser.Nodes['collation'] = pgBrowser.Node.extend({ + pgAdmin.Browser.Nodes['collation'] = schemaChild.SchemaChildNode.extend({ type: 'collation', sqlAlterHelp: 'sql-altercollation.html', sqlCreateHelp: 'sql-createcollation.html', @@ -222,34 +222,6 @@ define('pgadmin.node.collation', [ return true; }, }), - 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; - }, }); } 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 403ca4717..a91daa5f7 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,10 @@ define('pgadmin.node.domain', [ 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone', 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', 'pgadmin.backgrid', - 'pgadmin.browser.collection', + 'pgadmin.node.schema.dir/child', 'pgadmin.browser.collection', ], function( - gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser, Backform, Backgrid + gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser, Backform, Backgrid, + schemaChild ) { // Define Domain Collection Node @@ -79,7 +80,7 @@ define('pgadmin.node.domain', [ // Domain Node if (!pgBrowser.Nodes['domain']) { - pgBrowser.Nodes['domain'] = pgBrowser.Node.extend({ + pgBrowser.Nodes['domain'] = schemaChild.SchemaChildNode.extend({ type: 'domain', sqlAlterHelp: 'sql-alterdomain.html', sqlCreateHelp: 'sql-createdomain.html', @@ -88,7 +89,6 @@ define('pgadmin.node.domain', [ collection_type: 'coll-domain', hasSQL: true, hasDepends: true, - parent_type: ['schema', 'catalog'], Init: function() { // Avoid mulitple registration of menus if (this.initialized) @@ -118,8 +118,6 @@ define('pgadmin.node.domain', [ ]); }, - canDrop: pgBrowser.Nodes['schema'].canChildDrop, - canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, // Domain Node Model model: pgBrowser.Node.Model.extend({ initialize: function(attrs, args) { @@ -296,34 +294,6 @@ define('pgadmin.node.domain', [ return errmsg; }, }), - 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; - }, isDisabled: function(m){ if (!m.isNew()) { var server = this.node_info.server; 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 160db83f0..24f5e1a72 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,10 @@ define('pgadmin.node.foreign_table', [ 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone', 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', 'pgadmin.backgrid', - 'pgadmin.browser.collection', + 'pgadmin.node.schema.dir/child', 'pgadmin.browser.collection', ], function( - gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser, Backform, Backgrid + gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser, Backform, Backgrid, + schemaChild ) { if (!pgBrowser.Nodes['coll-foreign_table']) { @@ -469,7 +470,7 @@ define('pgadmin.node.foreign_table', [ if (!pgBrowser.Nodes['foreign_table']) { - pgBrowser.Nodes['foreign_table'] = pgBrowser.Node.extend({ + pgBrowser.Nodes['foreign_table'] = schemaChild.SchemaChildNode.extend({ type: 'foreign_table', sqlAlterHelp: 'sql-alterforeigntable.html', sqlCreateHelp: 'sql-createforeigntable.html', @@ -509,8 +510,6 @@ define('pgadmin.node.foreign_table', [ ]); }, - canDrop: pgBrowser.Nodes['schema'].canChildDrop, - canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, model: pgBrowser.Node.Model.extend({ initialize: function(attrs, args) { var isNew = (_.size(attrs) === 0); @@ -659,34 +658,6 @@ define('pgadmin.node.foreign_table', [ return errmsg; }, }), - 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; - }, }); } 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 898066813..cba627890 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,10 @@ define('pgadmin.node.fts_configuration', [ 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone', 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', 'pgadmin.backgrid', - 'pgadmin.browser.collection', + 'pgadmin.node.schema.dir/child', 'pgadmin.browser.collection', ], function( - gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser, Backform, Backgrid + gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser, Backform, Backgrid, + schemaChild ) { // Model for tokens control @@ -410,14 +411,11 @@ define('pgadmin.node.fts_configuration', [ // Extend the node class for FTS Configuration if (!pgBrowser.Nodes['fts_configuration']) { - pgAdmin.Browser.Nodes['fts_configuration'] = pgAdmin.Browser.Node.extend({ - parent_type: ['schema', 'catalog'], + pgAdmin.Browser.Nodes['fts_configuration'] = schemaChild.SchemaChildNode.extend({ type: 'fts_configuration', sqlAlterHelp: 'sql-altertsconfig.html', sqlCreateHelp: 'sql-createtsconfig.html', dialogHelp: url_for('help.static', {'filename': 'fts_configuration_dialog.html'}), - canDrop: true, - canDropCascade: true, label: gettext('FTS Configuration'), hasSQL: true, hasDepends: true, @@ -577,34 +575,6 @@ define('pgadmin.node.fts_configuration', [ return null; }, }), - 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; - }, }); } 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 ed83feb1e..cf733922c 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', - 'pgadmin.browser.collection', -], function(gettext, url_for, $, _, S, pgAdmin, pgBrowser, Backform) { + 'pgadmin.node.schema.dir/child', 'pgadmin.browser.collection', +], function( + gettext, url_for, $, _, S, pgAdmin, pgBrowser, Backform, schemaChild +) { // Extend the browser's node model class to create a option/value pair var OptionLabelModel = pgAdmin.Browser.Node.Model.extend({ @@ -55,14 +57,11 @@ define('pgadmin.node.fts_dictionary', [ // Extend the node class for FTS Dictionary if (!pgBrowser.Nodes['fts_dictionary']) { - pgAdmin.Browser.Nodes['fts_dictionary'] = pgAdmin.Browser.Node.extend({ - parent_type: ['schema', 'catalog'], + pgAdmin.Browser.Nodes['fts_dictionary'] = schemaChild.SchemaChildNode.extend({ type: 'fts_dictionary', sqlAlterHelp: 'sql-altertsdictionary.html', sqlCreateHelp: 'sql-createtsdictionary.html', dialogHelp: url_for('help.static', {'filename': 'fts_dictionary_dialog.html'}), - canDrop: true, - canDropCascade: true, label: gettext('FTS Dictionary'), hasSQL: true, hasDepends: true, @@ -186,34 +185,6 @@ define('pgadmin.node.fts_dictionary', [ return null; }, }), - 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; - }, }); } 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 92c0786eb..bebc9dc59 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,8 @@ 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', 'pgadmin.node.schema.dir/child', + 'pgadmin.browser.collection', +], function(gettext, url_for, $, _, pgAdmin, pgBrowser, schemaChild) { // Extend the collection class for fts parser if (!pgBrowser.Nodes['coll-fts_parser']) { @@ -16,14 +17,11 @@ define('pgadmin.node.fts_parser', [ // Extend the node class for fts parser if (!pgBrowser.Nodes['fts_parser']) { - pgAdmin.Browser.Nodes['fts_parser'] = pgAdmin.Browser.Node.extend({ - parent_type: ['schema', 'catalog'], + pgAdmin.Browser.Nodes['fts_parser'] = schemaChild.SchemaChildNode.extend({ type: 'fts_parser', sqlAlterHelp: 'sql-altertsparser.html', sqlCreateHelp: 'sql-createtsparser.html', dialogHelp: url_for('help.static', {'filename': 'fts_parser_dialog.html'}), - canDrop: true, - canDropCascade: true, label: gettext('FTS Parser'), hasSQL: true, hasDepends: true, @@ -199,34 +197,6 @@ define('pgadmin.node.fts_parser', [ return null; }, }), - 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; - }, }); } 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 606a57a61..cd0207ab0 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,8 @@ 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', 'pgadmin.node.schema.dir/child', + 'pgadmin.browser.collection', +], function(gettext, url_for, $, _, pgAdmin, pgBrowser, schemaChild) { // Extend the collection class for fts template if (!pgBrowser.Nodes['coll-fts_template']) { @@ -16,14 +17,11 @@ define('pgadmin.node.fts_template', [ // Extend the node class for fts template if (!pgBrowser.Nodes['fts_template']) { - pgAdmin.Browser.Nodes['fts_template'] = pgAdmin.Browser.Node.extend({ - parent_type: ['schema', 'catalog'], + pgAdmin.Browser.Nodes['fts_template'] = schemaChild.SchemaChildNode.extend({ type: 'fts_template', sqlAlterHelp: 'sql-altertstemplate.html', sqlCreateHelp: 'sql-createtstemplate.html', dialogHelp: url_for('help.static', {'filename': 'fts_template_dialog.html'}), - canDrop: true, - canDropCascade: true, label: gettext('FTS Template'), hasSQL: true, hasDepends: true, @@ -139,34 +137,6 @@ define('pgadmin.node.fts_template', [ return null; }, }), - 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; - }, }); } 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 6e4051650..c4cd91aa7 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,11 @@ define('pgadmin.node.function', [ 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone', 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', - 'pgadmin.browser.collection', 'pgadmin.browser.server.privilege', -], function(gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser, Backform) { + 'pgadmin.node.schema.dir/child', 'pgadmin.browser.collection', + 'pgadmin.browser.server.privilege', +], function( + gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser, Backform, schemaChild +) { if (!pgBrowser.Nodes['coll-function']) { pgBrowser.Nodes['coll-function'] = @@ -83,7 +86,8 @@ define('pgadmin.node.function', [ }); if (!pgBrowser.Nodes['function']) { - pgBrowser.Nodes['function'] = pgBrowser.Node.extend({ + + pgBrowser.Nodes['function'] = schemaChild.SchemaChildNode.extend({ type: 'function', sqlAlterHelp: 'sql-alterfunction.html', sqlCreateHelp: 'sql-createfunction.html', @@ -96,7 +100,6 @@ define('pgadmin.node.function', [ return treeInformation.server.server_type !== 'gpdb'; }, hasScriptTypes: ['create', 'select'], - parent_type: ['schema', 'catalog'], Init: function() { /* Avoid mulitple registration of menus */ if (this.initialized) @@ -126,8 +129,6 @@ define('pgadmin.node.function', [ ]); }, - canDrop: pgBrowser.Nodes['schema'].canChildDrop, - canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, model: pgBrowser.Node.Model.extend({ initialize: function(attrs, args) { var isNew = (_.size(attrs) === 0); @@ -438,34 +439,6 @@ define('pgadmin.node.function', [ return true; }, }), - 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; - }, }); } 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 aeb8271bb..fcdf28fb5 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,11 @@ define('pgadmin.node.trigger_function', [ 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', - 'pgadmin.browser.collection', 'pgadmin.browser.server.privilege', -], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform) { + 'pgadmin.node.schema.dir/child', 'pgadmin.browser.collection', + 'pgadmin.browser.server.privilege', +], function( + gettext, url_for, $, _, pgAdmin, pgBrowser, Backform, schemaChild +) { if (!pgBrowser.Nodes['coll-trigger_function']) { pgBrowser.Nodes['coll-trigger_function'] = @@ -17,7 +20,7 @@ define('pgadmin.node.trigger_function', [ } if (!pgBrowser.Nodes['trigger_function']) { - pgBrowser.Nodes['trigger_function'] = pgBrowser.Node.extend({ + pgBrowser.Nodes['trigger_function'] = schemaChild.SchemaChildNode.extend({ type: 'trigger_function', sqlAlterHelp: 'plpgsql-trigger.html', sqlCreateHelp: 'plpgsql-trigger.html', @@ -27,7 +30,6 @@ define('pgadmin.node.trigger_function', [ hasSQL: true, hasDepends: true, hasStatistics: true, - parent_type: ['schema', 'catalog'], Init: function() { /* Avoid mulitple registration of menus */ if (this.initialized) @@ -57,8 +59,6 @@ define('pgadmin.node.trigger_function', [ ]); }, - canDrop: pgBrowser.Nodes['schema'].canChildDrop, - canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, model: pgBrowser.Node.Model.extend({ initialize: function(attrs, args) { var isNew = (_.size(attrs) === 0); @@ -357,34 +357,6 @@ define('pgadmin.node.trigger_function', [ return !(this.node_info && 'catalog' in this.node_info); }, }), - 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; - }, }); } 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 57c95acd3..300f0b106 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', - 'pgadmin.browser.collection', -], function(gettext, url_for, $, _, S, pgAdmin, pgBrowser, Backform) { + 'pgadmin.node.schema.dir/child', 'pgadmin.browser.collection', +], function( + gettext, url_for, $, _, S, pgAdmin, pgBrowser, Backform, schemaChild +) { // Extend the browser's collection class for sequence collection if (!pgBrowser.Nodes['coll-sequence']) { @@ -18,7 +20,7 @@ define('pgadmin.node.sequence', [ // Extend the browser's node class for sequence node if (!pgBrowser.Nodes['sequence']) { - pgBrowser.Nodes['sequence'] = pgBrowser.Node.extend({ + pgBrowser.Nodes['sequence'] = schemaChild.SchemaChildNode.extend({ type: 'sequence', sqlAlterHelp: 'sql-altersequence.html', sqlCreateHelp: 'sql-createsequence.html', @@ -28,7 +30,6 @@ define('pgadmin.node.sequence', [ hasSQL: true, hasDepends: true, hasStatistics: true, - parent_type: ['schema', 'catalog'], Init: function() { /* Avoid mulitple registration of menus */ if (this.initialized) @@ -58,36 +59,6 @@ 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; - }, // Define the model for sequence node. model: pgBrowser.Node.Model.extend({ defaults: { diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/static/js/child.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/static/js/child.js new file mode 100644 index 000000000..f8e5951cf --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/static/js/child.js @@ -0,0 +1,22 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2018, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +///////////////////////////////////////////////////////////// + +import * as Node from 'pgbrowser/node'; +import { + isTreeItemOfChildOfSchema, childCreateMenuEnabled, +} from './schema_child_tree_node'; + +let SchemaChildNode = Node.extend({ + parent_type: ['schema', 'catalog'], + canDrop: isTreeItemOfChildOfSchema, + canDropCascade: isTreeItemOfChildOfSchema, + canCreate: childCreateMenuEnabled, +}, false); + +export {SchemaChildNode}; 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 a7fd4c7cd..3b9b0f350 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 @@ -425,54 +425,10 @@ define('pgadmin.node.schema', [ return null; }, }), - // 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; - }, }); 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/static/js/schema_child_tree_node.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/static/js/schema_child_tree_node.js new file mode 100644 index 000000000..1f67d1afa --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/static/js/schema_child_tree_node.js @@ -0,0 +1,41 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2018, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +///////////////////////////////////////////////////////////// + +import * as pgBrowser from 'pgbrowser/browser'; + +export function childCreateMenuEnabled(itemData, 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) + return node.anyFamilyMember( + (node) => (node.getData()._type === 'schema') + ); + + return false; +} + +export function isTreeItemOfChildOfSchema(itemData, item) { + let node = pgBrowser.treeMenu.findNodeByDomElement(item); + + if (node) + return isTreeNodeOfSchemaChild(node); + + return false; +} + +export function isTreeNodeOfSchemaChild(node) { + return node.anyParent( + (parentNode) => (parentNode.getData()._type === 'schema') + ); +} 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 3eac530d0..582167a4f 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', @@ -97,27 +96,24 @@ define('pgadmin.node.column', [ sqlAlterHelp: 'sql-altertable.html', sqlCreateHelp: 'sql-altertable.html', dialogHelp: url_for('help.static', {'filename': 'column_dialog.html'}), - canDrop: function(itemData, item, data){ - if (pgBrowser.Nodes['schema'].canChildDrop.apply(this, [itemData, item, data])) { - var t = pgBrowser.tree, i = item, d = itemData, parents = []; - // To iterate over tree to check parent node - while (i) { - parents.push(d._type); - i = t.hasParent(i) ? t.parent(i) : null; - d = i ? t.itemData(i) : null; - } + canDrop: function(itemData, item){ + let node = pgBrowser.treeMenu.findNodeByDomElement(item); - // Check if menu is allowed ? - if(_.indexOf(parents, 'catalog') > -1 || - _.indexOf(parents, 'view') > -1 || - _.indexOf(parents, 'mview') > -1) { - return false; - } else if(_.indexOf(parents, 'table') > -1) { - return true; - } - } else { + if (!node) return false; - } + + // Only a column of a table can be droped, and only when it is not of + // catalog. + return node.anyParent( + (parentNode) => ( + parentNode.getData()._type === 'table' && + !parentNode.anyParent( + (grandParentNode) => ( + grandParentNode.getData()._type === 'catalog' + ) + ) + ) + ); }, hasDepends: true, hasStatistics: true, 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 857cf4c43..ab28a86be 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 0bbf66a1f..adccf2e95 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 3c4b89f30..9899df92e 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 d3a6cff4d..0ad0f054f 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 769185d6c..18d3ca338 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 cb242cd2a..9c0e24fe0 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 ec2b4da1d..e58bb4630 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 @@ -1,10 +1,12 @@ define('pgadmin.node.index', [ 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone', 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.alertifyjs', - 'pgadmin.backform', 'pgadmin.backgrid', 'pgadmin.browser.collection', + 'pgadmin.backform', 'pgadmin.backgrid', + 'pgadmin.node.schema.dir/schema_child_tree_node', + 'pgadmin.browser.collection', ], function( gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser, Alertify, Backform, - Backgrid + Backgrid, SchemaChildTreeNode ) { if (!pgBrowser.Nodes['coll-index']) { @@ -13,7 +15,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 +216,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', @@ -266,8 +266,8 @@ define('pgadmin.node.index', [ }, ]); }, - canDrop: pgBrowser.Nodes['schema'].canChildDrop, - canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, + canDrop: SchemaChildTreeNode.isTreeItemOfChildOfSchema, + canDropCascade: SchemaChildTreeNode.isTreeItemOfChildOfSchema, model: pgAdmin.Browser.Node.Model.extend({ idAttribute: 'oid', 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 d807304ea..746497217 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 @@ -2,10 +2,12 @@ define([ 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'underscore.string', 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.alertifyjs', 'pgadmin.backform', 'pgadmin.backgrid', + 'pgadmin.node.schema.dir/schema_child_tree_node', 'pgadmin.browser.collection', 'pgadmin.browser.table.partition.utils', ], function( - gettext, url_for, $, _, S, pgAdmin, pgBrowser, Alertify, Backform, Backgrid + gettext, url_for, $, _, S, pgAdmin, pgBrowser, Alertify, Backform, Backgrid, + SchemaChildTreeNode ) { if (!pgBrowser.Nodes['coll-partition']) { @@ -13,7 +15,6 @@ function( pgAdmin.Browser.Collection.extend({ node: 'partition', label: gettext('Partitions'), - getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy, type: 'coll-partition', columns: [ 'name', 'schema', 'partition_value', 'is_partitioned', 'description', @@ -80,36 +81,6 @@ function( }, ]); }, - getTreeNodeHierarchy: function(i) { - var idx = 0, - res = {}, - t = pgBrowser.tree; - - do { - var d = t.itemData(i); - if ( - d._type in pgBrowser.Nodes && pgBrowser.Nodes[d._type].hasId - ) { - if (d._type == 'partition' && 'partition' in res) { - if (!('table' in res)) { - res['table'] = _.extend({}, d, {'priority': idx}); - idx -= 1; - } - } else if (d._type == 'table') { - if (!('table' in res)) { - res['table'] = _.extend({}, d, {'priority': idx}); - idx -= 1; - } - } else { - res[d._type] = _.extend({}, d, {'priority': idx}); - idx -= 1; - } - } - i = t.hasParent(i) ? t.parent(i) : null; - } while (i); - - return res; - }, generate_url: function(item, type, d, with_id, info) { if (_.indexOf([ 'stats', 'statistics', 'dependency', 'dependent', 'reset', @@ -133,8 +104,8 @@ function( encodeURIComponent(info['partition']._id) ).value(); }, - canDrop: pgBrowser.Nodes['schema'].canChildDrop, - canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, + canDrop: SchemaChildTreeNode.isTreeItemOfChildOfSchema, + canDropCascade: SchemaChildTreeNode.isTreeItemOfChildOfSchema, callbacks: { /* Enable trigger(s) on table */ enable_triggers_on_table: function(args) { @@ -1189,34 +1160,7 @@ function( return data; }, }), - 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; - }, + canCreate: SchemaChildTreeNode.isTreeItemOfChildOfSchema, // Check to whether table has disable trigger(s) canCreate_with_trigger_enable: function(itemData, item, data) { if(this.canCreate.apply(this, [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 3af617547..354909f36 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/enable_disable_triggers.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/enable_disable_triggers.js new file mode 100644 index 000000000..2d7920433 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/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/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 d440bf046..1f9bcf978 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,16 @@ define('pgadmin.node.table', [ + 'pgadmin.tables.js/enable_disable_triggers', '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', - 'pgadmin.browser.collection', 'pgadmin.node.column', - 'pgadmin.node.constraints', 'pgadmin.browser.table.partition.utils', + 'pgadmin.node.schema.dir/child', '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, SchemaChild ) { if (!pgBrowser.Nodes['coll-table']) { @@ -25,8 +28,7 @@ define('pgadmin.node.table', [ } if (!pgBrowser.Nodes['table']) { - pgBrowser.Nodes['table'] = pgBrowser.Node.extend({ - getTreeNodeHierarchy: pgBrowser.tableChildTreeNodeHierarchy, + pgBrowser.Nodes['table'] = SchemaChild.SchemaChildNode.extend({ type: 'table', label: gettext('Table'), collection_type: 'coll-table', @@ -39,7 +41,6 @@ define('pgadmin.node.table', [ sqlAlterHelp: 'sql-altertable.html', sqlCreateHelp: 'sql-createtable.html', dialogHelp: url_for('help.static', {'filename': 'table_dialog.html'}), - parent_type: ['schema', 'catalog'], hasScriptTypes: ['create', 'select', 'insert', 'update', 'delete'], height: '95%', width: '85%', @@ -113,51 +114,24 @@ define('pgadmin.node.table', [ this.handle_cache, this ); }, - canDrop: pgBrowser.Nodes['schema'].canChildDrop, - canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, 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, status, error) { - Alertify.pgRespErrorNotify(xhr, error); - t.unload(i); - }, - }); + tableFunctions.disableTriggers( + pgBrowser.treeMenu, + Alertify, + this.generate_url.bind(this), + args + ); }, /* Truncate table */ truncate_table: function(args) { @@ -1299,55 +1273,15 @@ define('pgadmin.node.table', [ return data; }, }), - 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; - }, // Check to whether table has disable trigger(s) canCreate_with_trigger_enable: function(itemData, item, data) { - if(this.canCreate.apply(this, [itemData, item, data])) { - // We are here means we can create menu, now let's check condition - if(itemData.tigger_count > 0) { - return true; - } else { - return false; - } - } + return itemData.tigger_count > 0 && + this.canCreate.apply(this, [itemData, item, data]); }, // Check to whether table has enable trigger(s) canCreate_with_trigger_disable: function(itemData, item, data) { - if(this.canCreate.apply(this, [itemData, item, data])) { - // We are here means we can create menu, now let's check condition - if(itemData.tigger_count > 0 && itemData.has_enable_triggers > 0) { - return true; - } else { - return false; - } - } + return itemData.tigger_count > 0 && itemData.has_enable_triggers > 0 && + this.canCreate.apply(this, [itemData, item, data]); }, onTableUpdated: function(_node, _oldNodeData, _newNodeData) { var key, childIDs; 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 a2c271880..a6e79ce2e 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 @@ -1,8 +1,13 @@ define('pgadmin.node.trigger', [ 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', - 'underscore.string', 'sources/pgadmin', 'pgadmin.browser', 'backform', 'pgadmin.alertifyjs', + 'underscore.string', 'sources/pgadmin', 'pgadmin.browser', + 'pgadmin.backform', 'pgadmin.alertifyjs', + 'pgadmin.node.schema.dir/schema_child_tree_node', 'pgadmin.browser.collection', -], function(gettext, url_for, $, _, S, pgAdmin, pgBrowser, Backform, alertify) { +], function( + gettext, url_for, $, _, S, pgAdmin, pgBrowser, Backform, alertify, + SchemaChildTreeNode +) { Backform.CustomSwitchControl = Backform.SwitchControl.extend({ template: _.template([ @@ -29,14 +34,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', @@ -175,8 +178,8 @@ define('pgadmin.node.trigger', [ }); }, }, - canDrop: pgBrowser.Nodes['schema'].canChildDrop, - canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, + canDrop: SchemaChildTreeNode.isTreeItemOfChildOfSchema, + canDropCascade: SchemaChildTreeNode.isTreeItemOfChildOfSchema, model: pgAdmin.Browser.Node.Model.extend({ defaults: { name: undefined, @@ -618,50 +621,16 @@ define('pgadmin.node.trigger', [ return flag; }, }), - // Below function will enable right click menu for creating column - 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, parents = []; - // To iterate over tree to check parent node - while (i) { - // If it is schema then allow user to c reate table - if (_.indexOf(['schema'], d._type) > -1) - return true; - parents.push(d._type); - i = t.hasParent(i) ? t.parent(i) : null; - d = i ? t.itemData(i) : null; - } - // If node is under catalog then do not allow 'create' menu - if (_.indexOf(parents, 'catalog') > -1) { - return false; - } else { - return true; - } - }, + canCreate: SchemaChildTreeNode.isTreeItemOfChildOfSchema, // Check to whether trigger is disable ? canCreate_with_trigger_enable: function(itemData, item, data) { - if(this.canCreate.apply(this, [itemData, item, data])) { - // We are here means we can create menu, now let's check condition - if(itemData.icon === 'icon-trigger-bad') { - return true; - } else { - return false; - } - } + return itemData.icon === 'icon-trigger-bad' && + this.canCreate.apply(this, [itemData, item, data]); }, // Check to whether trigger is enable ? canCreate_with_trigger_disable: function(itemData, item, data) { - if(this.canCreate.apply(this, [itemData, item, data])) { - // We are here means we can create menu, now let's check condition - if(itemData.icon === 'icon-trigger') { - return true; - } else { - return false; - } - } + return itemData.icon === 'icon-trigger' && + this.canCreate.apply(this, [itemData, item, data]); }, }); } 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 c1c24861b..5860a7527 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,12 @@ 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', 'pgadmin.node.schema.dir/child', + 'pgadmin.browser.collection', +], function( + gettext, url_for, $, _, pgAdmin, pgBrowser, Backform, Backgrid, + schemaChild +) { if (!pgBrowser.Nodes['coll-type']) { pgBrowser.Nodes['coll-type'] = @@ -245,7 +249,7 @@ define('pgadmin.node.type', [ }); if (!pgBrowser.Nodes['type']) { - pgBrowser.Nodes['type'] = pgBrowser.Node.extend({ + pgBrowser.Nodes['type'] = schemaChild.SchemaChildNode.extend({ type: 'type', sqlAlterHelp: 'sql-altertype.html', sqlCreateHelp: 'sql-createtype.html', @@ -254,7 +258,6 @@ define('pgadmin.node.type', [ collection_type: 'coll-type', hasSQL: true, hasDepends: true, - parent_type: ['schema', 'catalog'], Init: function() { /* Avoid multiple registration of menus */ if (this.initialized) @@ -284,8 +287,6 @@ define('pgadmin.node.type', [ ]); }, - canDrop: pgBrowser.Nodes['schema'].canChildDrop, - canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, ext_funcs: undefined, model: pgBrowser.Node.Model.extend({ defaults: { @@ -911,34 +912,6 @@ define('pgadmin.node.type', [ return result; }, }), - 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 pgBrowser.Nodes['type']; 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 073ef5cb1..dcfdd54bf 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,12 @@ 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', 'pgadmin.node.schema.dir/child', + 'pgadmin.browser.server.privilege', +], function( + gettext, url_for, $, _, pgAdmin, Alertify, pgBrowser, Backform, + schemaChild +) { /** Create and add a view collection into nodes @@ -33,19 +37,16 @@ define('pgadmin.node.mview', [ view option in the context menu */ if (!pgBrowser.Nodes['mview']) { - pgBrowser.Nodes['mview'] = pgBrowser.Node.extend({ - parent_type: ['schema', 'catalog'], + pgBrowser.Nodes['mview'] = schemaChild.SchemaChildNode.extend({ type: 'mview', sqlAlterHelp: 'sql-altermaterializedview.html', sqlCreateHelp: 'sql-creatematerializedview.html', dialogHelp: url_for('help.static', {'filename': 'materialized_view_dialog.html'}), label: gettext('Materialized View'), - hasSQL: true, + hasSQL: true, hasDepends: true, hasScriptTypes: ['create', 'select'], collection_type: 'coll-mview', - canDrop: pgBrowser.Nodes['schema'].canChildDrop, - canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, Init: function() { // Avoid mulitple registration of menus @@ -236,43 +237,6 @@ define('pgadmin.node.mview', [ }), - /** - Show or hide create view menu option on parent node - 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; - }, refresh_mview: function(args) { var input = args || {}, obj = this, 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 5755a5098..cd61ef210 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', + 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', + 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', + 'pgadmin.node.schema.dir/child', 'pgadmin.browser.server.privilege', 'pgadmin.node.rule', -], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform) { +], function( + gettext, url_for, $, _, pgAdmin, pgBrowser, Backform, schemaChild +) { + /** Create and add a view collection into nodes @@ -28,14 +31,9 @@ define('pgadmin.node.view', [ under which this node to display @param {variable} type - Type of Node @param {variable} hasSQL - To show SQL tab - @param {variable} canDrop - Adds drop view option - in the context menu - @param {variable} canDropCascade - Adds drop Cascade - view option in the context menu */ if (!pgBrowser.Nodes['view']) { - pgBrowser.Nodes['view'] = pgBrowser.Node.extend({ - parent_type: ['schema', 'catalog'], + pgBrowser.Nodes['view'] = schemaChild.SchemaChildNode.extend({ type: 'view', sqlAlterHelp: 'sql-alterview.html', sqlCreateHelp: 'sql-createview.html', @@ -45,8 +43,6 @@ define('pgadmin.node.view', [ hasDepends: true, hasScriptTypes: ['create', 'select', 'insert'], collection_type: 'coll-view', - canDrop: pgBrowser.Nodes['schema'].canChildDrop, - canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, Init: function() { // Avoid mulitple registration of menus @@ -197,45 +193,6 @@ define('pgadmin.node.view', [ return false; }, }), - - /** - Show or hide create view menu option on parent node - 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; - - }, }); } diff --git a/web/pgadmin/browser/static/js/collection.js b/web/pgadmin/browser/static/js/collection.js index 05f0edd5f..67e442144 100644 --- a/web/pgadmin/browser/static/js/collection.js +++ b/web/pgadmin/browser/static/js/collection.js @@ -115,17 +115,17 @@ define([ // Fetch Data collection.fetch({ reset: true, - error: function(xhr, error, message) { + error: function(model, error, xhr) { pgBrowser.Events.trigger( 'pgadmin:collection:retrieval:error', 'properties', xhr, error, - message, item, that + error.message, item, that ); if (!Alertify.pgHandleItemError( - xhr, error, message, {item: item, info: info} + xhr, error, error.message, {item: item, info: info} )) { Alertify.pgNotifier(error, xhr, S( gettext('Error retrieving properties - %s.') - ).sprintf(message || that.label).value(), function() { + ).sprintf(error.message || that.label).value(), function() { console.warn(arguments); }); } diff --git a/web/pgadmin/browser/static/js/node.js b/web/pgadmin/browser/static/js/node.js index 460ab03d7..35eca24bb 100644 --- a/web/pgadmin/browser/static/js/node.js +++ b/web/pgadmin/browser/static/js/node.js @@ -1,9 +1,13 @@ 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 = { @@ -28,7 +32,7 @@ define('pgadmin.browser.node', [ // // It is unlikely - we will instantiate an object for this class. // (Inspired by Backbone.extend function) - pgBrowser.Node.extend = function(props) { + pgBrowser.Node.extend = function(props, initialize) { var parent = this; var child; @@ -44,6 +48,10 @@ define('pgadmin.browser.node', [ // Make sure - a child have all the callbacks of the parent. child.callbacks = _.extend({}, parent.callbacks, props.callbacks); + // Let's not bind the callbacks, or initialize the child. + if (initialize === false) + return child; + var bindToChild = function(cb) { if (typeof(child.callbacks[cb]) == 'function') { child.callbacks[cb] = child.callbacks[cb].bind(child); @@ -1566,7 +1574,6 @@ define('pgadmin.browser.node', [ * depends, statistics */ generate_url: function(item, type, d, with_id, info) { - var opURL = { 'create': 'obj', 'drop': 'obj', @@ -1608,24 +1615,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 000000000..4a0a1b89b --- /dev/null +++ b/web/pgadmin/static/js/alertify/dialog.js @@ -0,0 +1,150 @@ +///////////////////////////////////////////////////////////// +// +// 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'; +import {getTreeNodeHierarchyFromIdentifier} from '../tree/pgadmin_tree_node'; + +/** + * This class can be extended to create new dialog boxes. + * Examples of this can be found in: + * `web/pgadmin/static/js/backup/backup_dialog.js` + * + * Do not forget to add the new Dialog type to the `DialogFactory` + */ +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); + + if (treeNode) { + let nodeData; + let databaseNode = treeNode.ancestorNode( + (node) => { + nodeData = node.getData(); + return (nodeData._type === 'database'); + } + ); + let isServerNode = (node) => { + nodeData = node.getData(); + return nodeData._type === 'server'; + }; + + if (databaseNode !== null) { + if (nodeData._label.indexOf('=') >= 0) { + this.alertify.alert( + gettext(this.errorAlertTitle), + gettext( + 'Databases with = symbols in the name cannot be backed up or restored using this utility.' + ) + ); + } else { + if (databaseNode.hasParent(isServerNode)) + serverInformation = nodeData; + } + } else { + if (treeNode.anyFamilyMember(isServerNode)) + serverInformation = nodeData; + } + } + + if (serverInformation === null) { + this.alertify.alert( + gettext(this.errorAlertTitle), + gettext('Please select server or child node from the browser tree.') + ); + } + + 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); + } + + canExecuteOnCurrentDatabase(aciTreeItem) { + const treeInfo = getTreeNodeHierarchyFromIdentifier.apply(this.pgBrowser, [aciTreeItem]); + + if (treeInfo.database && treeInfo.database._label.indexOf('=') >= 0) { + this.alertify.alert( + gettext(this.errorAlertTitle), + gettext('Databases with = symbols in the name cannot be backed up or restored using this utility.') + ); + return false; + } + + return true; + } +} 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 000000000..500140b81 --- /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 '../../../tools/backup/static/js/backup_dialog_wrapper'; +import {RestoreDialogWrapper} from '../../../tools/restore/static/js/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 000000000..b5ff82042 --- /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/nodes/supported_database_node.js b/web/pgadmin/static/js/nodes/supported_database_node.js new file mode 100644 index 000000000..fde1cf985 --- /dev/null +++ b/web/pgadmin/static/js/nodes/supported_database_node.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 +// +////////////////////////////////////////////////////////////// + +import {isValidTreeNodeData} from 'sources/tree/tree'; + +function checkAllowConnIfDatabaseNode(treeNodeData) { + return (treeNodeData._type === 'database' && treeNodeData.allowConn) + || treeNodeData._type !== 'database'; +} + +function ancestorWithTypeCatalog(treeNode) { + return treeNode.anyFamilyMember((node) => { + return node.getData()._type === 'catalog'; + }); +} + +export function enabled(tree, supportedNodes, treeNodeData, domTreeNode) { + if (!isValidTreeNodeData(treeNodeData)) + return false; + + let treeNode = tree.findNodeByDomElement(domTreeNode); + if (!treeNode) { + return false; + } + + return checkAllowConnIfDatabaseNode(treeNodeData) && + _.indexOf(supportedNodes, treeNodeData._type) !== -1 && + !ancestorWithTypeCatalog(treeNode); +} + + 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 000000000..00f10d242 --- /dev/null +++ b/web/pgadmin/static/js/tree/pgadmin_tree_node.js @@ -0,0 +1,72 @@ +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2018, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////////////////// + +/** + * This method received pgBrowser and new TreeNode object + * + * This method retrieves all the data that exists in the tree node and in + * `pgBrowser.Nodes` for all the parent node of the provided node. + * + * The 2 condition to get the information from pgBrowser.Nodes are: + * 1 - the variable _type of the tree node + * 2 - the presence of hasId in the pgBrowser.Nodes for the specific node + * + * Number 2 is used to ignore coll-* nodes as they do not add any useful + * information + */ +export function getTreeNodeHierarchyFromElement(pgBrowser, treeNode) { + return getTreeNodeHierarchy.call(pgBrowser, treeNode); +} + +/** + * This method received an ACI Tree JQuery node + * + * NOTE: this function need to be called on pgBrowser instance. + * getTreeNodeHierarchyFromIdentifier.apply(pgBrowser, [aciTreeNodeIdentifier]) + * + * This method retrieves all the data that exists in the tree node and in + * `pgBrowser.Nodes` for all the parent node of the provided node. + * + * The 2 condition to get the information from pgBrowser.Nodes are: + * 1 - the variable _type of the tree node + * 2 - the presence of hasId in the pgBrowser.Nodes for the specific node + * + * Number 2 is used to ignore coll-* nodes as they do not add any useful + * information + */ +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/static/js/tree/tree.js b/web/pgadmin/static/js/tree/tree.js index 01edb6c3c..b5655a70b 100644 --- a/web/pgadmin/static/js/tree/tree.js +++ b/web/pgadmin/static/js/tree/tree.js @@ -59,17 +59,20 @@ export class TreeNode { tree.aciTreeApi.unload(this.domNode); } - anyParent(condition) { + /* + * Find the ancestor with matches this condition + */ + ancestorNode(condition) { let node = this; while (node.hasParent()) { node = node.parent(); if (condition(node)) { - return true; + return node; } } - return false; + return null; } /** @@ -81,7 +84,10 @@ export class TreeNode { return true; } - return this.anyParent(condition); + return this.ancestorNode(condition) !== null; + } + anyParent(condition) { + return this.ancestorNode(condition) !== null; } } @@ -210,3 +216,7 @@ function findInTree(rootNode, path) { } })(rootNode); } + +export function isValidTreeNodeData(treeNodeData) { + return !_.isUndefined(treeNodeData) && !_.isNull(treeNodeData); +} diff --git a/web/pgadmin/tools/backup/static/js/backup.js b/web/pgadmin/tools/backup/static/js/backup.js index 94ab8b762..d6fd48c52 100644 --- a/web/pgadmin/tools/backup/static/js/backup.js +++ b/web/pgadmin/tools/backup/static/js/backup.js @@ -3,9 +3,12 @@ define([ 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'underscore.string', 'pgadmin.alertifyjs', 'backbone', 'pgadmin.backgrid', 'pgadmin.backform', 'pgadmin.browser', 'sources/utils', + 'tools/backup/static/js/menu_utils', + 'tools/backup/static/js/backup_dialog', + 'sources/nodes/supported_database_node', ], function( gettext, url_for, $, _, S, alertify, Backbone, Backgrid, Backform, pgBrowser, -commonUtils + commonUtils, menuUtils, globalBackupDialog, supportedNodes ) { // if module is already initialized, refer to that. @@ -394,48 +397,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 +406,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 +415,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 +425,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 +435,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 +444,24 @@ commonUtils priority: 11, label: gettext('Backup...'), icon: 'fa fa-floppy-o', - enable: menu_enabled, + enable: supportedNodes.enabled.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: supportedNodes.enabled.bind( + null, pgBrowser.treeMenu, menuUtils.backupSupportedNodes + ), }); } @@ -521,542 +486,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 = $('
'); - // 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 = JSON.parse(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; - - var treeInfo = node.getTreeNodeHierarchy.apply(node, [item]); - - if (treeInfo.database._label.indexOf('=') >= 0) { - alertify.alert( - gettext('Backup error'), - gettext('Backup job creation failed. '+ - 'Databases with = symbols in the name cannot be backed up using this utility.') - ); - 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 = $('
'); - 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 = JSON.parse(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/backup/static/js/backup_dialog.js b/web/pgadmin/tools/backup/static/js/backup_dialog.js new file mode 100644 index 000000000..2f530d80b --- /dev/null +++ b/web/pgadmin/tools/backup/static/js/backup_dialog.js @@ -0,0 +1,72 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2018, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import gettext from '../../../../static/js/gettext'; +import Backform from '../../../../static/js/backform.pgadmin'; +import {Dialog} from '../../../../static/js/alertify/dialog'; + +export class BackupDialog extends Dialog { + constructor(pgBrowser, $, alertify, BackupModel, backform = Backform) { + super('Backup Error', + '
', + 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); + + if (!this.canExecuteOnCurrentDatabase(aciTreeItem)) { + return; + } + + 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/tools/backup/static/js/backup_dialog_wrapper.js b/web/pgadmin/tools/backup/static/js/backup_dialog_wrapper.js new file mode 100644 index 000000000..2cebe3d17 --- /dev/null +++ b/web/pgadmin/tools/backup/static/js/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 '../../../../static/js/tree/pgadmin_tree_node'; +import axios from 'axios/index'; +import gettext from '../../../../static/js/gettext'; +import url_for from '../../../../static/js/url_for'; +import _ from 'underscore'; +import {DialogWrapper} from '../../../../static/js/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/tools/backup/static/js/menu_utils.js b/web/pgadmin/tools/backup/static/js/menu_utils.js new file mode 100644 index 000000000..47a9f0d35 --- /dev/null +++ b/web/pgadmin/tools/backup/static/js/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 {isValidTreeNodeData} from '../../../../static/js/tree/tree'; + +export const backupSupportedNodes = [ + 'database', 'schema', 'table', 'partition', +]; + +function isNodeAServerAndConnected(treeNodeData) { + return (('server' === treeNodeData._type) && treeNodeData.connected); +} + +export function menuEnabledServer(treeNodeData) { + return isValidTreeNodeData(treeNodeData) + && isNodeAServerAndConnected(treeNodeData); +} diff --git a/web/pgadmin/tools/datagrid/static/js/datagrid.js b/web/pgadmin/tools/datagrid/static/js/datagrid.js index 42122ed8e..c730b180e 100644 --- a/web/pgadmin/tools/datagrid/static/js/datagrid.js +++ b/web/pgadmin/tools/datagrid/static/js/datagrid.js @@ -1,10 +1,14 @@ 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', + 'tools/datagrid/static/js/show_data', + 'tools/datagrid/static/js/get_panel_title', + 'tools/datagrid/static/js/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 +165,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 +340,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/datagrid/static/js/get_panel_title.js b/web/pgadmin/tools/datagrid/static/js/get_panel_title.js new file mode 100644 index 000000000..64b3a3d27 --- /dev/null +++ b/web/pgadmin/tools/datagrid/static/js/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 '../../../../static/js/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/tools/datagrid/static/js/show_data.js b/web/pgadmin/tools/datagrid/static/js/show_data.js new file mode 100644 index 000000000..373c97cd8 --- /dev/null +++ b/web/pgadmin/tools/datagrid/static/js/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 '../../../../static/js/gettext'; +import url_for from '../../../../static/js/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/tools/datagrid/static/js/show_query_tool.js b/web/pgadmin/tools/datagrid/static/js/show_query_tool.js new file mode 100644 index 000000000..0eb12b8be --- /dev/null +++ b/web/pgadmin/tools/datagrid/static/js/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 '../../../../static/js/gettext'; +import url_for from '../../../../static/js/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/tools/grant_wizard/static/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js index 5ce0e2fbe..2ace07e56 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,15 @@ 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', + 'tools/grant_wizard/static/js/menu_utils', + 'sources/nodes/supported_database_node', + 'backgrid.select.all', 'backgrid.filter', 'pgadmin.browser.server.privilege', 'pgadmin.browser.wizard', ], function( gettext, url_for, $, _, Backbone, Alertify, Backgrid, Backform, pgBrowser, - pgNode + pgNode, menuUtils, supportedNodes ) { // if module is already initialized, refer to that. @@ -143,41 +146,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 +155,25 @@ define([ priority: 14, label: gettext('Grant Wizard...'), icon: 'fa fa-unlock-alt', - enable: menu_enabled, + enable: supportedNodes.enabled.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: supportedNodes.enabled.bind( + null, pgBrowser.treeMenu, menuUtils.supportedNodes + ), }); } pgBrowser.add_menus(menus); diff --git a/web/pgadmin/tools/grant_wizard/static/js/menu_utils.js b/web/pgadmin/tools/grant_wizard/static/js/menu_utils.js new file mode 100644 index 000000000..b56ce3cd8 --- /dev/null +++ b/web/pgadmin/tools/grant_wizard/static/js/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/tools/import_export/static/js/import_export.js b/web/pgadmin/tools/import_export/static/js/import_export.js index e64d3dee7..5c73537e4 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,12 @@ 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/nodes/supported_database_node', + 'pgadmin.backform', 'pgadmin.backgrid', 'pgadmin.browser.node.ui', ], function( gettext, url_for, $, _, S, Alertify, pgAdmin, pgBrowser, Backbone, Backgrid, -Backform, commonUtils +Backform, commonUtils, supportedNodes ) { pgAdmin = pgAdmin || window.pgAdmin || {}; @@ -383,25 +385,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 +396,9 @@ Backform, commonUtils priority: 10, label: gettext('Import/Export...'), icon: 'fa fa-shopping-cart', - enable: menu_enabled, + enable: supportedNodes.enabled.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 f2102602a..df05c3d57 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', + 'tools/maintenance/static/js/menu_utils', + 'sources/nodes/supported_database_node', 'pgadmin.backform', 'pgadmin.backgrid', 'pgadmin.browser.node.ui', ], function( gettext, url_for, $, _, S, Alertify, pgAdmin, pgBrowser, Backbone, Backgrid, - Backform, commonUtils + Backform, commonUtils, + menuUtils, supportedNodes ) { 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,25 @@ define([ priority: 10, label: gettext('Maintenance...'), icon: 'fa fa-wrench', - enable: menu_enabled, + enable: supportedNodes.enabled.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: supportedNodes.enabled.bind( + null, pgBrowser.treeMenu, menuUtils.maintenanceSupportedNodes + ), }); } pgBrowser.add_menus(menus); diff --git a/web/pgadmin/tools/maintenance/static/js/menu_utils.js b/web/pgadmin/tools/maintenance/static/js/menu_utils.js new file mode 100644 index 000000000..8cde1baa6 --- /dev/null +++ b/web/pgadmin/tools/maintenance/static/js/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/tools/restore/static/js/menu_utils.js b/web/pgadmin/tools/restore/static/js/menu_utils.js new file mode 100644 index 000000000..2d35c951f --- /dev/null +++ b/web/pgadmin/tools/restore/static/js/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/tools/restore/static/js/restore.js b/web/pgadmin/tools/restore/static/js/restore.js index 7daa76452..aeff6d3d4 100644 --- a/web/pgadmin/tools/restore/static/js/restore.js +++ b/web/pgadmin/tools/restore/static/js/restore.js @@ -1,11 +1,13 @@ -// 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', + 'tools/restore/static/js/menu_utils', + 'sources/nodes/supported_database_node', + 'tools/restore/static/js/restore_dialog', ], function( gettext, url_for, $, _, Backbone, S, alertify, pgBrowser, Backgrid, Backform, -commonUtils +commonUtils, menuUtils, supportedNodes, restoreDialog ) { // if module is already initialized, refer to that. @@ -307,59 +309,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 +318,24 @@ commonUtils priority: 13, label: gettext('Restore...'), icon: 'fa fa-upload', - enable: menu_enabled, + enable: supportedNodes.enabled.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: supportedNodes.enabled.bind( + null, pgBrowser.treeMenu, menuUtils.restoreSupportedNodes + ), }); } @@ -391,318 +344,10 @@ 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; - - var treeInfo = node.getTreeNodeHierarchy.apply(node, [item]); - - if (treeInfo.database._label.indexOf('=') >= 0) { - alertify.alert( - gettext('Restore error'), - gettext('Restore job creation failed. '+ - 'Databases with = symbols in the name cannot be restored using this utility.') - ); - 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 = $('
'); - 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 = JSON.parse(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/pgadmin/tools/restore/static/js/restore.js.rej b/web/pgadmin/tools/restore/static/js/restore.js.rej new file mode 100644 index 000000000..43577f427 --- /dev/null +++ b/web/pgadmin/tools/restore/static/js/restore.js.rej @@ -0,0 +1,322 @@ +diff a/web/pgadmin/tools/restore/static/js/restore.js b/web/pgadmin/tools/restore/static/js/restore.js (rejected hunks) +@@ -391,318 +344,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; +- +- var treeInfo = node.getTreeNodeHierarchy.apply(node, [item]); +- +- if (treeInfo.database._label.indexOf('=') >= 0) { +- alertify.alert( +- gettext('Restore error'), +- gettext('Restore job creation failed. '+ +- 'Databases with = symbols in the name cannot be restored using this utility.') +- ); +- 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 = $('
'); +- 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/pgadmin/tools/restore/static/js/restore_dialog.js b/web/pgadmin/tools/restore/static/js/restore_dialog.js new file mode 100644 index 000000000..4884d9012 --- /dev/null +++ b/web/pgadmin/tools/restore/static/js/restore_dialog.js @@ -0,0 +1,57 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2018, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import gettext from '../../../../static/js/gettext'; +import {sprintf} from 'sprintf-js'; +import Backform from '../../../../static/js/backform.pgadmin'; +import {Dialog} from '../../../../static/js/alertify/dialog'; + +export class RestoreDialog extends Dialog { + constructor(pgBrowser, $, alertify, RestoreModel, backform = Backform) { + super('Restore Error', + '
', + pgBrowser, $, alertify, RestoreModel, backform); + } + + draw(action, aciTreeItem) { + + const serverInformation = this.retrieveAncestorOfTypeServer(aciTreeItem); + + if (!serverInformation) { + return; + } + + if (!this.hasBinariesConfiguration(serverInformation)) { + return; + } + + if (!this.canExecuteOnCurrentDatabase(aciTreeItem)) { + 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/tools/restore/static/js/restore_dialog_wrapper.js b/web/pgadmin/tools/restore/static/js/restore_dialog_wrapper.js new file mode 100644 index 000000000..845da7a3d --- /dev/null +++ b/web/pgadmin/tools/restore/static/js/restore_dialog_wrapper.js @@ -0,0 +1,255 @@ +import {getTreeNodeHierarchyFromElement} from '../../../../static/js/tree/pgadmin_tree_node'; +import axios from 'axios/index'; +import _ from 'underscore'; +import gettext from '../../../../static/js/gettext'; +import url_for from '../../../../static/js/url_for'; +import {DialogWrapper} from '../../../../static/js/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/regression/javascript/backup/backup_dialog_spec.js b/web/regression/javascript/backup/backup_dialog_spec.js new file mode 100644 index 000000000..2655059d1 --- /dev/null +++ b/web/regression/javascript/backup/backup_dialog_spec.js @@ -0,0 +1,205 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2018, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// +import {BackupDialog} from '../../../pgadmin/tools/backup/static/js/backup_dialog'; +import {TreeFake} from '../tree/tree_fake'; + +const context = describe; + +describe('BackupDialog', () => { + let backupDialog; + let pgBrowser; + let jquerySpy; + let alertifySpy; + let backupModelSpy; + + 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'); + + const hierarchy = { + children: [ + { + id: 'root', + children: [ + { + id: 'serverTreeNode', + data: { + _id: 10, + _type: 'server', + }, + children: [ + { + id: 'some_database', + data: { + _type: 'database', + _id: 11, + label: 'some_database', + _label: 'some_database_label', + }, + }, { + id: 'database_with_equal_in_name', + data: { + _type: 'database', + label: 'some_database', + _label: '=some_database_label', + }, + }, + ], + }, + { + id: 'ppasServer', + data: { + _type: 'server', + server_type: 'ppas', + children: [ + {id: 'someNodeUnderneathPPASServer'}, + ], + }, + }, + ], + }, + ], + }; + + pgBrowser.treeMenu = TreeFake.build(hierarchy); + }); + + 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([{id: 'root'}]); + backupDialog.draw(null, null, null); + expect(alertifySpy['backup_objects']).not.toHaveBeenCalled(); + }); + + it('display an alert with a Backup Error', () => { + backupDialog.draw(null, [{id: 'root'}], 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: 'some_database'}], 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, [{id: 'ppasServer'}], 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, [{id: '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, [{id: 'ppasServer'}], 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, [{id: 'serverTreeNode'}], null); + expect(alertifySpy['backup_objects']).toHaveBeenCalledWith(true); + expect(backupDialogResizeToSpy.resizeTo).toHaveBeenCalledWith('60%', '50%'); + }); + + context('database label contain "="', () => { + it('should create alert dialog with backup error', () => { + backupDialog.draw(null, [{id: 'database_with_equal_in_name'}], null); + expect(alertifySpy.alert).toHaveBeenCalledWith('Backup Error', + 'Databases with = symbols in the name cannot be backed up or restored using this utility.'); + }); + }); + }); + }); + }); + }); +}); 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 000000000..58705318d --- /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/tools/backup/static/js/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 === '
') { + return backupJQueryContainerSpy; + } else if (selector === backupNode.elements.body.childNodes[0]) { + return backupNodeChildNodeSpy; + } + }); + + }); + + describe('#prepare', () => { + beforeEach(() => { + alertifySpy = jasmine.createSpyObj('alertify', ['alert', 'dialog']); + backupDialogWrapper = new BackupDialogWrapper( + '
', + '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( + '
', + '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( + '
', + '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( + '
', + '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( + '
', + '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 000000000..86df672e0 --- /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/tools/backup/static/js/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 000000000..9435d699d --- /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/tools/backup/static/js/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/common_keyboard_shortcuts_spec.js b/web/regression/javascript/common_keyboard_shortcuts_spec.js index 9ea31efd3..e27929bf3 100644 --- a/web/regression/javascript/common_keyboard_shortcuts_spec.js +++ b/web/regression/javascript/common_keyboard_shortcuts_spec.js @@ -11,10 +11,6 @@ import keyboardShortcuts from 'sources/keyboard_shortcuts'; describe('the keyboard shortcuts', () => { const F1_KEY = 112; - // const EDIT_KEY = 71; // Key: G -> Grid values - // const LEFT_ARROW_KEY = 37; - // const RIGHT_ARROW_KEY = 39; - // const MOVE_NEXT = 'right'; let debuggerElementSpy, event, debuggerUserShortcutSpy; debuggerUserShortcutSpy = jasmine.createSpyObj( 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 000000000..8a344a846 --- /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/tools/datagrid/static/js/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 000000000..80d25eb3f --- /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/tools/datagrid/static/js/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 000000000..66bd37ceb --- /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/tools/datagrid/static/js/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/fake_browser/browser.js b/web/regression/javascript/fake_browser/browser.js new file mode 100644 index 000000000..195e5c51e --- /dev/null +++ b/web/regression/javascript/fake_browser/browser.js @@ -0,0 +1,12 @@ +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2018, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////////////////// + +let treeMenu = null; + +export {treeMenu}; diff --git a/web/regression/javascript/fake_endpoints.js b/web/regression/javascript/fake_endpoints.js index 54b86a94c..c060ba783 100644 --- a/web/regression/javascript/fake_endpoints.js +++ b/web/regression/javascript/fake_endpoints.js @@ -12,5 +12,11 @@ define(function () { 'static': '/base/pgadmin/static/', 'sqleditor.poll': '/sqleditor/query_tool/poll/', 'sqleditor.query_tool_start': '/sqleditor/query_tool/start/', + 'backup.create_server_job': '/backup/job/', + 'backup.create_object_job': '/backup/job//object', + 'datagrid.initialize_datagrid': '/initialize/datagrid//////', + 'datagrid.initialize_query_tool': '/initialize/query_tool//', + 'datagrid.initialize_query_tool_with_did': '/initialize/query_tool///', + 'restore.create_job': '/restore/job/', }; }); diff --git a/web/regression/javascript/fake_model.js b/web/regression/javascript/fake_model.js new file mode 100644 index 000000000..acfaa5327 --- /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/nodes/schema/child_menu_spec.js b/web/regression/javascript/nodes/schema/child_menu_spec.js new file mode 100644 index 000000000..3d3dc55ea --- /dev/null +++ b/web/regression/javascript/nodes/schema/child_menu_spec.js @@ -0,0 +1,253 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2018, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +///////////////////////////////////////////////////////////// + +import { + isTreeItemOfChildOfSchema, childCreateMenuEnabled, +} from 'pgadmin.schema.dir/schema_child_tree_node'; + +import * as pgBrowser from 'pgbrowser/browser'; +import {TreeFake} from '../../tree/tree_fake'; + +describe('#childCreateMenuEnabled', () => { + let data; + let tree; + + describe(' - when data is not null', () => { + beforeEach(() => { + data = {}; + }); + describe(' and check is false', () => { + beforeEach(() => { + data = {check: false}; + }); + it(', then it returns true', () => { + expect(childCreateMenuEnabled({}, {}, data)).toBe(true); + }); + }); + + describe(' and check', () => { + describe(' is true', () => { + beforeEach(() => { + data = {check: true}; + }); + + describe(', on schema node', () => { + beforeEach(() => { + let hierarchy = { + id: 'root', + children: [{ + id: 'level2', + data: {_type: 'schema'}, + }], + }; + + tree = TreeFake.build(hierarchy); + pgBrowser.treeMenu = tree; + }); + it(' it is true', () => { + expect(childCreateMenuEnabled( + {}, [{id: 'level2'}], data + )).toBe(true); + + }); + }); + + describe(', on child collection node under schema node ', () => { + beforeEach(() => { + let hierarchy = { + id: 'root', + children: [{ + id: 'level2', + data: {_type: 'schema'}, + children: [{ + id: 'coll-table', + data: {_type: 'coll-table'}, + }], + }], + }; + + tree = TreeFake.build(hierarchy); + pgBrowser.treeMenu = tree; + }); + + it(' it is true', () => { + expect(childCreateMenuEnabled( + {}, [{id: 'coll-table'}], data + )).toBe(true); + }); + }); + + describe(', on one of the child node under schema node ', () => { + beforeEach(() => { + let hierarchy = { + id: 'root', + children: [{ + id: 'level2', + data: {_type: 'schema'}, + children: [{ + id: 'coll-table', + data: {_type: 'coll-table'}, + children: [{ + id: 'table/1', + data: {_type: 'table'}, + }], + }], + }], + }; + + tree = TreeFake.build(hierarchy); + pgBrowser.treeMenu = tree; + }); + + it(' it is true', () => { + expect(childCreateMenuEnabled( + {}, [{id: 'table/1'}], data + )).toBe(true); + }); + }); + + describe(', on catalog node', () => { + beforeEach(() => { + let hierarchy = { + id: 'root', + children: [{ + id: 'level2', + data: {_type: 'catalog'}, + }], + }; + + tree = TreeFake.build(hierarchy); + pgBrowser.treeMenu = tree; + }); + it(' it is false', () => { + expect( + childCreateMenuEnabled({}, [{id: 'level2'}], data) + ).toBe(false); + }); + }); + + describe(', on child collection node under catalog node ', () => { + beforeEach(() => { + let hierarchy = { + id: 'root', + children: [{ + id: 'level2', + data: {_type: 'catalog'}, + children: [{ + id: 'coll-table', + data: {_type: 'coll-table'}, + }], + }], + }; + + tree = TreeFake.build(hierarchy); + pgBrowser.treeMenu = tree; + }); + + it(' it is false', () => { + expect(childCreateMenuEnabled( + {}, [{id: 'coll-table'}], data + )).toBe(false); + }); + }); + + describe(', on one of the child node under catalog node ', () => { + beforeEach(() => { + let hierarchy = { + id: 'root', + children: [{ + id: 'level2', + data: {_type: 'catalog'}, + children: [{ + id: 'coll-table', + data: {_type: 'coll-table'}, + children: [{ + id: 'table/1', + data: {_type: 'table'}, + }], + }], + }], + }; + + tree = TreeFake.build(hierarchy); + pgBrowser.treeMenu = tree; + }); + + it(' it is false', () => { + expect(childCreateMenuEnabled( + {}, [{id: 'table/1'}], data + )).toBe(false); + }); + }); + }); + }); + }); +}); + +describe('#childDropMenuEnabled', () => { + let tree; + + describe(' - the child node under schema node ', () => { + beforeEach(() => { + let hierarchy = { + id: 'root', + children: [{ + id: 'level2', + data: {_type: 'schema'}, + children: [{ + id: 'coll-table', + data: {_type: 'coll-table'}, + children: [{ + id: 'table/1', + data: {_type: 'table'}, + }], + }], + }], + }; + + tree = TreeFake.build(hierarchy); + pgBrowser.treeMenu = tree; + }); + + it(' it is true', () => { + expect(isTreeItemOfChildOfSchema( + {}, [{id: 'table/1'}] + )).toBe(true); + }); + }); + + describe('- the child node under the catalog node ', () => { + beforeEach(() => { + let hierarchy = { + id: 'root', + children: [{ + id: 'level2', + data: {_type: 'catalog'}, + children: [{ + id: 'coll-table', + data: {_type: 'coll-table'}, + children: [{ + id: 'table/1', + data: {_type: 'table'}, + }], + }], + }], + }; + + tree = TreeFake.build(hierarchy); + pgBrowser.treeMenu = tree; + }); + + it(' it is false', () => { + expect(isTreeItemOfChildOfSchema( + {}, [{id: 'table/1'}] + )).toBe(false); + }); + }); +}); 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 000000000..156f56bb3 --- /dev/null +++ b/web/regression/javascript/restore/restore_dialog_spec.js @@ -0,0 +1,203 @@ +///////////////////////////////////////////////////////////// +// +// 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/tools/restore/static/js/restore_dialog'; + +const context = describe; + +describe('RestoreDialog', () => { + let restoreDialog; + let pgBrowser; + let jquerySpy; + let alertifySpy; + let restoreModelSpy; + + 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'); + + const hierarchy = { + children: [ + { + id: 'root', + children: [ + { + id: 'serverTreeNode', + data: { + _id: 10, + _type: 'server', + label: 'some-tree-label', + }, + children: [ + { + id: 'some_database', + data: { + _type: 'database', + _id: 11, + label: 'some_database', + _label: 'some_database_label', + }, + }, { + id: 'database_with_equal_in_name', + data: { + _type: 'database', + label: 'some_database', + _label: '=some_database_label', + }, + }, + ], + }, + { + id: 'ppasServer', + data: { + _type: 'server', + server_type: 'ppas', + children: [ + {id: 'someNodeUnderneathPPASServer'}, + ], + }, + }, + ], + }, + ], + }; + + pgBrowser.treeMenu = TreeFake.build(hierarchy); + }); + + 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: 'root'}]); + restoreDialog.draw(null, null, null); + expect(alertifySpy['pg_restore']).not.toHaveBeenCalled(); + }); + + it('display an alert with a Restore Error', () => { + restoreDialog.draw(null, [{id: 'root'}], 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, [{id: '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, [{id: 'ppasServer'}], 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, [{id: '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, [{id: 'ppasServer'}], 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: 'serverTreeNode'}], {server: true}); + expect(alertifySpy['pg_restore']).toHaveBeenCalledWith( + 'Restore (some-server-label: some-tree-label)', + [{id: 'serverTreeNode'}], + { + _id: 10, + _type: 'server', + label: 'some-tree-label', + }, + pgBrowser.Nodes.server + ); + expect(spy.resizeTo).toHaveBeenCalledWith('65%', '60%'); + }); + + context('database label contain "="', () => { + it('should create alert dialog with restore error', () => { + restoreDialog.draw(null, [{id: 'database_with_equal_in_name'}], null); + expect(alertifySpy.alert).toHaveBeenCalledWith('Restore Error', + 'Databases with = symbols in the name cannot be backed up or restored using this utility.'); + }); + }); + }); + }); + }); + }); +}); 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 000000000..c2a31d55a --- /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/tools/restore/static/js/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 === '
') { + return restoreJQueryContainerSpy; + } else if (selector === restoreNode.elements.body.childNodes[0]) { + return restoreNodeChildNodeSpy; + } + }); + }); + + describe('#prepare', () => { + + beforeEach(() => { + alertifySpy = jasmine.createSpyObj('alertify', ['alert', 'dialog']); + restoreDialogWrapper = new RestoreDialogWrapper( + '
', + '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( + '
', + '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( + '
', + '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/sqleditor/filter_dialog_specs.js b/web/regression/javascript/sqleditor/filter_dialog_specs.js index ed77dff59..cea75e6b9 100644 --- a/web/regression/javascript/sqleditor/filter_dialog_specs.js +++ b/web/regression/javascript/sqleditor/filter_dialog_specs.js @@ -7,10 +7,8 @@ // ////////////////////////////////////////////////////////////////////////// import filterDialog from 'sources/sqleditor/filter_dialog'; -// import filterDialogModel from 'sources/sqleditor/filter_dialog_model'; describe('filterDialog', () => { - jasmine.createSpy('sqlEditorController'); describe('filterDialog', () => { describe('when using filter dialog', () => { beforeEach(() => { 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 000000000..7bdd284eb --- /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/browser/server_groups/servers/databases/schemas/tables/static/js/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}, ['
  • server1
  • ']); + const database1 = tree.addNewNode('database1', {_type: 'database'}, ['
  • database1
  • ']); + tree.addChild(server1, database1); + + const schema1 = tree.addNewNode('schema1', {_type: 'schema'}, ['
  • schema1
  • ']); + tree.addChild(database1, schema1); + + const table1 = tree.addNewNode('table1', {_type: 'table'}, ['
  • table1
  • ']); + tree.addChild(schema1, table1); + + const column1 = tree.addNewNode('column1', {_type: 'column'}, ['
  • column1
  • ']); + tree.addChild(table1, column1); + + const tableNoData = tree.addNewNode('table-no-data', undefined, ['
  • table-no-data
  • ']); + 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(['
  • table1
  • ']); + 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}, ['
  • server1
  • ']); + const database1 = new TreeNode('database1', {_type: 'database'}, ['
  • database1
  • ']); + tree.addChild(server1, database1); + + const schema1 = new TreeNode('schema1', {_type: 'schema'}, ['
  • schema1
  • ']); + tree.addChild(database1, schema1); + + const table1 = new TreeNode('table1', {_type: 'table'}, ['
  • table1
  • ']); + tree.addChild(schema1, table1); + + const column1 = new TreeNode('column1', {_type: 'column'}, ['
  • column1
  • ']); + tree.addChild(table1, column1); + + const tableNoData = new TreeNode('table-no-data', undefined, ['
  • table-no-data
  • ']); + 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(['
  • table1
  • ']); + 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 000000000..479e515c7 --- /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, + }, + }); + }); + }); + }); + }); +}); diff --git a/web/regression/javascript/tree/tree_fake.js b/web/regression/javascript/tree/tree_fake.js index b285a45f3..e03d71fbb 100644 --- a/web/regression/javascript/tree/tree_fake.js +++ b/web/regression/javascript/tree/tree_fake.js @@ -10,6 +10,32 @@ import {Tree} from '../../../pgadmin/static/js/tree/tree'; export class TreeFake extends Tree { + static build(structure) { + let tree = new TreeFake(); + let rootNode = tree.rootNode; + + if (structure.children !== undefined) { + structure.children.forEach((child) => { + TreeFake.recursivelyAddNodes(tree, child, rootNode); + }); + } + + return tree; + } + + static recursivelyAddNodes(tree, newNode, parent) { + let id = newNode.id; + let data = newNode.data ? newNode.data : {}; + let domNode = newNode.domNode ? newNode.domNode : [{id: id}]; + tree.addNewNode(id, data, domNode, tree.translateTreeNodeIdFromACITree([parent])); + + if (newNode.children !== undefined) { + newNode.children.forEach((child) => { + TreeFake.recursivelyAddNodes(tree, child, newNode); + }); + } + } + constructor() { super(); this.aciTreeToOurTreeTranslator = {}; @@ -45,7 +71,7 @@ export class TreeFake extends Tree { } translateTreeNodeIdFromACITree(aciTreeNode) { - if(aciTreeNode === undefined || aciTreeNode[0] === undefined) { + if (aciTreeNode === undefined || aciTreeNode[0] === undefined) { return null; } return this.aciTreeToOurTreeTranslator[aciTreeNode[0].id]; diff --git a/web/webpack.shim.js b/web/webpack.shim.js index 64f24336c..c12e7f3b9 100644 --- a/web/webpack.shim.js +++ b/web/webpack.shim.js @@ -130,6 +130,7 @@ var webpackShimConfig = { 'sources/utils': path.join(__dirname, './pgadmin/static/js/utils'), 'babel-polyfill': path.join(__dirname, './node_modules/babel-polyfill/dist/polyfill'), 'tools': path.join(__dirname, './pgadmin/tools/'), + 'pgbrowser': path.join(__dirname, './pgadmin/browser/static/js/'), // Vendor JS 'jquery': path.join(__dirname, './node_modules/jquery/dist/jquery'), diff --git a/web/webpack.test.config.js b/web/webpack.test.config.js index a1fc68243..ef893e0ba 100644 --- a/web/webpack.test.config.js +++ b/web/webpack.test.config.js @@ -81,6 +81,8 @@ module.exports = { 'pgadmin.alertifyjs': sourcesDir + '/js/alertify.pgadmin.defaults', 'pgadmin.backgrid': sourcesDir + '/js/backgrid.pgadmin', 'pgadmin.backform': sourcesDir + '/js/backform.pgadmin', + 'pgbrowser': path.resolve(__dirname, 'regression/javascript/fake_browser'), + 'pgadmin.schema.dir': path.resolve(__dirname, 'pgadmin/browser/server_groups/servers/databases/schemas/static/js'), }, }, };