diff --git a/docs/en_US/images/maintenance.png b/docs/en_US/images/maintenance.png index 8ad9fc286..d0d84d355 100644 Binary files a/docs/en_US/images/maintenance.png and b/docs/en_US/images/maintenance.png differ diff --git a/docs/en_US/release_notes_6_4.rst b/docs/en_US/release_notes_6_4.rst index 9ab7cdee8..10a243174 100644 --- a/docs/en_US/release_notes_6_4.rst +++ b/docs/en_US/release_notes_6_4.rst @@ -15,6 +15,7 @@ Housekeeping ************ | `Issue #7018 `_ - Port Restore dialog to React. +| `Issue #7019 `_ - Port Maintenance dialog to React. Bug fixes ********* diff --git a/web/pgadmin/static/js/SchemaView/index.jsx b/web/pgadmin/static/js/SchemaView/index.jsx index 64feeb27a..fe9f6e29e 100644 --- a/web/pgadmin/static/js/SchemaView/index.jsx +++ b/web/pgadmin/static/js/SchemaView/index.jsx @@ -13,6 +13,7 @@ import {Accordion, AccordionSummary, AccordionDetails} from '@material-ui/core'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import SaveIcon from '@material-ui/icons/Save'; import PublishIcon from '@material-ui/icons/Publish'; +import DoneIcon from '@material-ui/icons/Done'; import SettingsBackupRestoreIcon from '@material-ui/icons/SettingsBackupRestore'; import CloseIcon from '@material-ui/icons/Close'; import InfoIcon from '@material-ui/icons/InfoRounded'; @@ -687,6 +688,8 @@ function SchemaDialogView({ const getButtonIcon = () => { if(props.customSaveBtnIconType == 'upload') { return ; + } else if(props.customSaveBtnIconType == 'done') { + return ; } return ; }; @@ -720,7 +723,7 @@ function SchemaDialogView({ } disabled={!dirty || saving} className={classes.buttonMargin}> {gettext('Reset')} - + {props.customSaveBtnName ? gettext(props.customSaveBtnName) : gettext('Save')} diff --git a/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js index 8e5fbe2ea..cb5e9f994 100644 --- a/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js +++ b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js @@ -127,7 +127,7 @@ define([ Alertify.grantWizardDialog().elements.modal.style.overflow='visible'; Alertify.grantWizardDialog().elements.dimmer.style.display='none'; } - }, 500); + }, 10); }, prepare: function () { diff --git a/web/pgadmin/tools/import_export_servers/static/js/import_export_servers.js b/web/pgadmin/tools/import_export_servers/static/js/import_export_servers.js index d0948bd15..3b12000f6 100644 --- a/web/pgadmin/tools/import_export_servers/static/js/import_export_servers.js +++ b/web/pgadmin/tools/import_export_servers/static/js/import_export_servers.js @@ -93,7 +93,7 @@ export default class ImportExportServersModule { Alertify.importExportWizardDialog().elements.modal.style.overflow='visible'; Alertify.importExportWizardDialog().elements.dimmer.style.display='none'; } - }, 500); + }, 10); }, prepare: function () { diff --git a/web/pgadmin/tools/maintenance/__init__.py b/web/pgadmin/tools/maintenance/__init__.py index ae67eb924..c641e49bb 100644 --- a/web/pgadmin/tools/maintenance/__init__.py +++ b/web/pgadmin/tools/maintenance/__init__.py @@ -139,9 +139,9 @@ class Message(IProcessDesc): res = _('VACUUM ({0})') opts = [] - if self.data['vacuum_full']: + if 'vacuum_full' in self.data and self.data['vacuum_full']: opts.append(_('FULL')) - if self.data['vacuum_freeze']: + if 'vacuum_freeze' in self.data and self.data['vacuum_freeze']: opts.append(_('FREEZE')) if self.data['verbose']: opts.append(_('VERBOSE')) diff --git a/web/pgadmin/tools/maintenance/static/js/maintenance.js b/web/pgadmin/tools/maintenance/static/js/maintenance.js index b6bce2774..1a6305e63 100644 --- a/web/pgadmin/tools/maintenance/static/js/maintenance.js +++ b/web/pgadmin/tools/maintenance/static/js/maintenance.js @@ -8,149 +8,27 @@ ////////////////////////////////////////////////////////////// import Notify from '../../../../static/js/helpers/Notifier'; +import {getUtilityView} from '../../../../browser/static/js/utility_view'; +import getApiInstance from 'sources/api_instance'; +import MaintenanceSchema, {getVacuumSchema} from './maintenance.ui'; define([ - 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', - 'pgadmin.alertifyjs', 'sources/pgadmin', 'pgadmin.browser', 'backbone', - 'backgrid', 'backform', 'sources/utils', + 'sources/gettext', 'sources/url_for', 'sources/pgadmin', 'pgadmin.browser', 'tools/maintenance/static/js/menu_utils', - 'sources/nodes/supported_database_node', - 'pgadmin.backform', 'pgadmin.backgrid', - 'pgadmin.browser.node.ui', + 'sources/nodes/supported_database_node' ], function( - gettext, url_for, $, _, Alertify, pgAdmin, pgBrowser, Backbone, Backgrid, - Backform, commonUtils, - menuUtils, supportedNodes + gettext, url_for, pgAdmin, pgBrowser, menuUtils, supportedNodes ) { pgAdmin = pgAdmin || window.pgAdmin || {}; var pgTools = pgAdmin.Tools = pgAdmin.Tools || {}; + const api = getApiInstance(); // Return back, this has been called more than once if (pgAdmin.Tools.maintenance) return pgAdmin.Tools.maintenance; - // Main model for Maintenance functionality - var MaintenanceModel = Backbone.Model.extend({ - defaults: { - op: 'VACUUM', - vacuum_full: false, - vacuum_freeze: false, - vacuum_analyze: false, - verbose: true, - }, - initialize: function() { - var node_info = arguments[1]['node_info']; - // If node is Unique or Primary key then set op to reindex - if ('primary_key' in node_info || 'unique_constraint' in node_info || - 'index' in node_info) { - this.set('op', 'REINDEX'); - this.set('verbose', false); - } - }, - schema: [{ - id: 'op', - label: gettext('Maintenance operation'), - cell: 'string', - type: 'radioModern', - controlsClassName: 'pgadmin-controls col-12 col-sm-8', - controlLabelClassName: 'control-label col-sm-4 col-12', - group: gettext('Options'), - value: 'VACUUM', - options: [{ - 'label': 'VACUUM', - 'value': 'VACUUM', - }, - { - 'label': 'ANALYZE', - 'value': 'ANALYZE', - }, - { - 'label': 'REINDEX', - 'value': 'REINDEX', - }, - { - 'label': 'CLUSTER', - 'value': 'CLUSTER', - }, - ], - }, - { - type: 'nested', - control: 'fieldset', - label: gettext('Vacuum'), - group: gettext('Options'), - contentClass: 'row', - schema: [{ - id: 'vacuum_full', - group: gettext('Vacuum'), - disabled: 'isDisabled', - type: 'switch', - extraToggleClasses: 'pg-el-sm-4', - controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12', - controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12', - label: gettext('FULL'), - deps: ['op'], - }, { - id: 'vacuum_freeze', - deps: ['op'], - disabled: 'isDisabled', - type: 'switch', - extraToggleClasses: 'pg-el-sm-4', - controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12', - controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12', - label: gettext('FREEZE'), - group: gettext('Vacuum'), - }, { - id: 'vacuum_analyze', - deps: ['op'], - disabled: 'isDisabled', - type: 'switch', - extraToggleClasses: 'pg-el-sm-4', - controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12', - controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12', - label: gettext('ANALYZE'), - group: gettext('Vacuum'), - }], - }, - { - id: 'verbose', - group: gettext('Options'), - deps: ['op'], - type: 'switch', - label: gettext('Verbose Messages'), - disabled: 'isDisabled', - }, - ], - - // Enable/Disable the items based on the user maintenance operation - // selection. - isDisabled: function(m) { - var node_info = this.node_info; - - switch (this.name) { - case 'vacuum_full': - case 'vacuum_freeze': - case 'vacuum_analyze': - return (m.get('op') != 'VACUUM'); - case 'verbose': - if ('primary_key' in node_info || 'unique_constraint' in node_info || - 'index' in node_info) { - if (m.get('op') == 'REINDEX') { - setTimeout(function() { - m.set('verbose', false); - }, 10); - return true; - } - } - return m.get('op') == 'REINDEX'; - default: - return false; - } - }, - }); - pgTools.maintenance = { init: function() { @@ -197,7 +75,33 @@ define([ } pgBrowser.add_menus(menus); }, + getUISchema: function(treeItem) { + let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(treeItem); + return new MaintenanceSchema( + ()=>getVacuumSchema(), + { + nodeInfo: treeNodeInfo + } + ); + }, + saveCallBack: function(data, dialog) { + if(data.errormsg) { + Notify.alert( + gettext('Utility not found'), + gettext(data.errormsg) + ); + } else { + Notify.success(data.data.info); + pgBrowser.Events.trigger('pgadmin-bgprocess:created', dialog); + } + }, + setExtraParameters(treeInfo) { + var extraData = {}; + extraData['database'] = treeInfo.database._label; + extraData['save_btn_icon'] = 'done'; + return extraData; + }, /* Open the dialog for the maintenance functionality */ @@ -224,12 +128,7 @@ define([ return; } - if (!commonUtils.hasBinariesConfiguration(pgBrowser, server_data, Alertify)) { - return; - } - - var self = this, - t = pgBrowser.tree; + var t = pgBrowser.tree; i = item || t.selected(); @@ -249,232 +148,50 @@ define([ return; } - if (!Alertify.MaintenanceDialog) { - Alertify.dialog('MaintenanceDialog', function factory() { - - return { - main: function(title) { - this.set('title', title); - }, - setup: function() { - return { - buttons: [{ - text: '', - className: 'btn btn-primary-icon pull-left fa fa-info pg-alertify-icon-button', - attrs: { - name: 'object_help', - type: 'button', - url: 'maintenance.html', - label: gettext('Maintenance'), - 'aria-label': gettext('Object Help'), - }, - }, { - text: '', - key: 112, - className: 'btn btn-primary-icon pull-left fa fa-question pg-alertify-icon-button', - attrs: { - name: 'dialog_help', - type: 'button', - label: gettext('Maintenance'), - 'aria-label': gettext('Help'), - url: url_for( - 'help.static', { - 'filename': 'maintenance_dialog.html', - } - ), - }, - }, { - text: gettext('Cancel'), - key: 27, - className: 'btn btn-secondary fa fa-lg fa-times pg-alertify-button', - 'data-btn-name': 'cancel', - }, { - text: gettext('OK'), - key: 13, - className: 'btn btn-primary fa fa-lg fa-check pg-alertify-button', - 'data-btn-name': 'ok', - }], - options: { - modal: 0, - pinnable: false, - //disable both padding and overflow control. - padding: !1, - overflow: !1, - }, - }; - }, - // Callback functions when click on the buttons of the Alertify dialogs - callback: function(e) { - var sel_item = pgBrowser.tree.selected(), - itemData = sel_item ? pgBrowser.tree.itemData(sel_item) : undefined, - sel_node = itemData && pgBrowser.Nodes[itemData._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'), - sel_node, sel_item); - return; - } - - if (e.button['data-btn-name'] === 'ok') { - - var schema = undefined, - table = undefined, - primary_key = undefined, - unique_constraint = undefined, - index = undefined; - - if (!itemData) - return; - - var node_hierarchy = pgBrowser.tree.getTreeNodeHierarchy(sel_item); - - if (node_hierarchy.schema != undefined) { - schema = node_hierarchy.schema._label; - } - - if (node_hierarchy.partition != undefined) { - table = node_hierarchy.partition._label; - } else if (node_hierarchy.table != undefined) { - table = node_hierarchy.table._label; - } else if (node_hierarchy.mview != undefined) { - table = node_hierarchy.mview._label; - } - - if (node_hierarchy.primary_key != undefined) { - primary_key = node_hierarchy.primary_key._label; - } else if (node_hierarchy.unique_constraint != undefined) { - unique_constraint = node_hierarchy.unique_constraint._label; - } else if (node_hierarchy.index != undefined) { - index = node_hierarchy.index._label; - } - - this.view.model.set({ - 'database': node_hierarchy.database._label, - 'schema': schema, - 'table': table, - 'primary_key': primary_key, - 'unique_constraint': unique_constraint, - 'index': index, - }); - - $.ajax({ - url: url_for( - 'maintenance.create_job', { - 'sid': node_hierarchy.server._id, - 'did': node_hierarchy.database._id, - }), - method: 'POST', - data: { - 'data': JSON.stringify(this.view.model.toJSON()), - }, - }) - .done(function(res) { - if (res.data && res.data.status) { - //Do nothing as we are creating the job and exiting from the main dialog - Notify.success(res.data.info); - pgBrowser.Events.trigger('pgadmin-bgprocess:created', self); - } else { - Notify.alert( - gettext('Maintenance job creation failed.'), - res.errormsg - ); - } - }) - .fail(function() { - Notify.alert( - gettext('Maintenance job creation failed.') - ); - }); - } - }, - build: function() { - Alertify.pgDialogBuild.apply(this); - }, - hooks: { - onclose: function() { - if (this.view) { - this.view.remove({ - data: true, - internal: true, - silent: true, - }); - } - }, - onshow: function() { - var container = $(this.elements.body).find('.tab-content:first > .tab-pane.active:first'); - commonUtils.findAndSetFocus(container); - }, - }, - prepare: function() { - // Main maintenance tool dialog container - var $container = $('
'); - var tree = pgBrowser.tree, - sel_item = tree.selected(), - itemInfo = sel_item ? tree.itemData(sel_item) : undefined, - nodeData = itemInfo && pgBrowser.Nodes[itemInfo._type]; - - if (!itemInfo) - return; - - var treeData = tree.getTreeNodeHierarchy(sel_item); - - var newModel = new MaintenanceModel({}, { - node_info: treeData, - }), - fields = Backform.generateViewSchema( - treeData, newModel, 'create', nodeData, treeData.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(); - - // If node is Index, Unique or Primary key then disable vacuum & analyze button - if (itemInfo._type == 'primary_key' || itemInfo._type == 'unique_constraint' || - itemInfo._type == 'index') { - var vacuum_analyze_btns = $container.find( - '.btn-group label.btn:lt(2)' - ).addClass('disabled'); - // Find reindex button element & add active class to it - var reindex_btn = vacuum_analyze_btns[1].nextElementSibling; - $(reindex_btn).trigger('click'); - } - - view.$el.attr('tabindex', -1); - this.elements.content.appendChild($container.get(0)); - }, - }; - }); - } - const baseUrl = url_for('maintenance.utility_exists', { 'sid': server_data._id, }); + let that = this; // Check psql utility exists or not. - $.ajax({ + api({ url: baseUrl, type:'GET', }) - .done(function(res) { - if (!res.success) { + .then(function(res) { + if (!res.data.success) { Notify.alert( gettext('Utility not found'), - res.errormsg + res.data.errormsg ); return; + } else{ + + pgBrowser.Node.registerUtilityPanel(); + var panel = pgBrowser.Node.addUtilityPanel(pgBrowser.stdW.md), + j = panel.$container.find('.obj_properties').first(); + + var schema = that.getUISchema(item); + panel.title(gettext('Maintenance')); + panel.focus(); + + let urlShortcut = 'maintenance.create_job', + baseUrl = url_for(urlShortcut, { + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id + }); + let extraData = that.setExtraParameters(treeInfo); + + var sqlHelpUrl = 'maintenance.html', + helpUrl = url_for('help.static', { + 'filename': 'maintenance_dialog.html', + }); + + getUtilityView( + schema, treeInfo, 'select', 'dialog', j[0], panel, that.saveCallBack, extraData, 'OK', baseUrl, sqlHelpUrl, helpUrl); } - // Open the Alertify dialog - Alertify.MaintenanceDialog(gettext('Maintenance...')).set('resizable', true) - .resizeTo(pgAdmin.Browser.stdW.md,pgAdmin.Browser.stdH.md); }) - .fail(function() { + .catch(function() { Notify.alert( gettext('Utility not found'), gettext('Failed to fetch Utility information') diff --git a/web/pgadmin/tools/maintenance/static/js/maintenance.ui.js b/web/pgadmin/tools/maintenance/static/js/maintenance.ui.js new file mode 100644 index 000000000..3748ffbd6 --- /dev/null +++ b/web/pgadmin/tools/maintenance/static/js/maintenance.ui.js @@ -0,0 +1,158 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import BaseUISchema from 'sources/SchemaView/base_schema.ui'; +import gettext from 'sources/gettext'; + +export class VacuumSchema extends BaseUISchema { + constructor(fieldOptions={}) { + super(); + + this.fieldOptions = { + ...fieldOptions, + }; + } + + get idAttribute() { + return 'op'; + } + + + get baseFields() { + return [{ + id: 'vacuum_full', + group: gettext('Vacuum'), + disabled: function(state) { + if(state?.op) { + return (state.op != 'VACUUM'); + } else { + return false; + } + }, + type: 'switch', + label: gettext('FULL'), + deps: ['op'], + }, { + id: 'vacuum_freeze', + deps: ['op'], + disabled: function(state) { + if(state?.op) { + return (state.op != 'VACUUM'); + } else { + return false; + } + }, + type: 'switch', + label: gettext('FREEZE'), + group: gettext('Vacuum'), + }, { + id: 'vacuum_analyze', + deps: ['op'], + type: 'switch', + disabled: function(state) { + if(state?.op) { + return (state.op != 'VACUUM'); + } else { + return false; + } + }, + label: gettext('ANALYZE'), + group: gettext('Vacuum'), + }]; + } +} + +export function getVacuumSchema(fieldOptions) { + return new VacuumSchema(fieldOptions); +} + + +//Maintenance Schema +export default class MaintenanceSchema extends BaseUISchema { + + constructor(getVacuumSchema, fieldOptions = {}) { + super({ + op: 'VACUUM', + verbose: true, + vacuum_full: false, + vacuum_freeze: false, + vacuum_analyze: false, + }); + + this.fieldOptions = { + nodeInfo: null, + ...fieldOptions, + }; + + this.getVacuumSchema = getVacuumSchema; + this.nodeInfo = fieldOptions.nodeInfo; + } + + get idAttribute() { + return 'id'; + } + + get baseFields() { + var obj = this; + return [ + { + id: 'op', + label: gettext('Maintenance operation'), + type: 'toggle', + group: gettext('Options'), + options: [ + { + 'label': gettext('VACUUM'), + value: 'VACUUM', + }, + { + 'label': gettext('ANALYZE'), + value: 'ANALYZE', + }, + { + 'label': gettext('REINDEX'), + value: 'REINDEX', + }, + { + 'label': gettext('CLUSTER'), + value: 'CLUSTER', + }, + ], + }, + { + type: 'nested-fieldset', + label: gettext('Type of objects'), + schema: obj.getVacuumSchema(), + group: gettext('Options'), + }, + { + id: 'verbose', + group: gettext('Options'), + deps: ['op'], + type: 'switch', + label: gettext('Verbose Messages'), + disabled: function(state) { + var nodeInfo = this.nodeInfo; + if(state?.verbose) { + if ('primary_key' in nodeInfo || 'unique_constraint' in nodeInfo || + 'index' in nodeInfo) { + if (state.op == 'REINDEX') { + state.verbose = false; + return true; + } + } + return state.op == 'REINDEX'; + } else { + return false; + } + }, + }, + ]; + } +} diff --git a/web/regression/javascript/schema_ui_files/maintenance.ui.spec.js b/web/regression/javascript/schema_ui_files/maintenance.ui.spec.js new file mode 100644 index 000000000..0ef15cd9e --- /dev/null +++ b/web/regression/javascript/schema_ui_files/maintenance.ui.spec.js @@ -0,0 +1,52 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import React from 'react'; +import '../helper/enzyme.helper'; +import { createMount } from '@material-ui/core/test-utils'; +import SchemaView from '../../../pgadmin/static/js/SchemaView'; +import MaintenanceSchema, {getVacuumSchema} from '../../../pgadmin/tools/maintenance/static/js/maintenance.ui'; + + +describe('MaintenanceSchema', ()=>{ + let mount; + beforeAll(()=>{ + mount = createMount(); + }); + + afterAll(() => { + mount.cleanUp(); + }); + let backupSchemaObj = new MaintenanceSchema( + ()=> getVacuumSchema(), + { + nodeInfo: {schema: {label: 'public'}, server: {version: 90400}} + } + ); + + it('start maintenance', ()=>{ + mount({}} + onClose={()=>{}} + onHelp={()=>{}} + onDataChange={()=>{}} + confirmOnCloseReset={false} + hasSQL={false} + disableSqlHelp={false} + disableDialogHelp={false} + />); + }); + +}); +