Port Maintenance dialog to React. Fixes #7019

This commit is contained in:
Nikhil Mohite 2022-01-07 12:07:49 +05:30 committed by Akshay Joshi
parent 65a6c18263
commit ad862f4084
9 changed files with 283 additions and 352 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View File

@ -15,6 +15,7 @@ Housekeeping
************
| `Issue #7018 <https://redmine.postgresql.org/issues/7018>`_ - Port Restore dialog to React.
| `Issue #7019 <https://redmine.postgresql.org/issues/7019>`_ - Port Maintenance dialog to React.
Bug fixes
*********

View File

@ -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 <PublishIcon />;
} else if(props.customSaveBtnIconType == 'done') {
return <DoneIcon />;
}
return <SaveIcon />;
};
@ -720,7 +723,7 @@ function SchemaDialogView({
<DefaultButton data-test="Reset" onClick={onResetClick} startIcon={<SettingsBackupRestoreIcon />} disabled={!dirty || saving} className={classes.buttonMargin}>
{gettext('Reset')}
</DefaultButton>
<PrimaryButton data-test="Save" onClick={onSaveClick} startIcon={ButtonIcon} disabled={!dirty || saving || Boolean(formErr.name) || !formReady}>
<PrimaryButton data-test="Save" onClick={onSaveClick} startIcon={ButtonIcon} disabled={ !(viewHelperProps.mode === 'edit' ? dirty : true) || saving || Boolean(formErr.name) || !formReady}>
{props.customSaveBtnName ? gettext(props.customSaveBtnName) : gettext('Save')}
</PrimaryButton>
</Box>

View File

@ -127,7 +127,7 @@ define([
Alertify.grantWizardDialog().elements.modal.style.overflow='visible';
Alertify.grantWizardDialog().elements.dimmer.style.display='none';
}
}, 500);
}, 10);
},
prepare: function () {

View File

@ -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 () {

View File

@ -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'))

View File

@ -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 = $('<div class=\'maintenance_dlg\'></div>');
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')

View File

@ -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;
}
},
},
];
}
}

View File

@ -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(<SchemaView
formType='dialog'
schema={backupSchemaObj}
viewHelperProps={{
mode: 'create',
}}
onSave={()=>{}}
onClose={()=>{}}
onHelp={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
disableDialogHelp={false}
/>);
});
});