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