Port Materialized View node to react. Fixes #6592.

This commit is contained in:
Nikhil Mohite 2021-07-30 14:53:08 +05:30 committed by Akshay Joshi
parent 6021e07761
commit 10d8135dcc
6 changed files with 342 additions and 82 deletions

View File

@ -7,6 +7,11 @@
// //
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import MViewSchema from './mview.ui';
import { getNodeListByName } from '../../../../../../../static/js/node_ajax';
import { getNodePrivilegeRoleSchema } from '../../../../../static/js/privilege.ui';
import { getNodeVacuumSettingsSchema } from '../../../../../static/js/vacuum.ui';
define('pgadmin.node.mview', [ define('pgadmin.node.mview', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
'sources/pgadmin', 'pgadmin.alertifyjs', 'pgadmin.browser', 'sources/pgadmin', 'pgadmin.alertifyjs', 'pgadmin.browser',
@ -122,7 +127,23 @@ define('pgadmin.node.mview', [
icon: 'fa fa-sync-alt', icon: 'fa fa-sync-alt',
}]); }]);
}, },
getSchema: function(treeNodeInfo, itemNodeData) {
return new MViewSchema(
(privileges)=>getNodePrivilegeRoleSchema('', treeNodeInfo, itemNodeData, privileges),
()=>getNodeVacuumSettingsSchema(this, treeNodeInfo, itemNodeData),
{
role: ()=>getNodeListByName('role', treeNodeInfo, itemNodeData),
schema: ()=>getNodeListByName('schema', treeNodeInfo, itemNodeData, {cacheLevel: 'database'}),
spcname: ()=>getNodeListByName('tablespace', treeNodeInfo, itemNodeData, {}, (m)=> {
return (m.label != 'pg_global');
}),
},
{
owner: pgBrowser.serverInfo[treeNodeInfo.server._id].user.name,
schema: treeNodeInfo.schema.label
}
);
},
/** /**
Define model for the view node and specify the Define model for the view node and specify the
properties of the model in schema. properties of the model in schema.
@ -142,9 +163,6 @@ define('pgadmin.node.mview', [
}, },
defaults: { defaults: {
spcname: undefined, spcname: undefined,
toast_autovacuum_enabled: 'x',
autovacuum_enabled: 'x',
warn_text: undefined,
}, },
schema: [{ schema: [{
id: 'name', label: gettext('Name'), cell: 'string', id: 'name', label: gettext('Name'), cell: 'string',
@ -156,81 +174,9 @@ define('pgadmin.node.mview', [
id: 'owner', label: gettext('Owner'), cell: 'string', id: 'owner', label: gettext('Owner'), cell: 'string',
control: 'node-list-by-name', select2: { allowClear: false }, control: 'node-list-by-name', select2: { allowClear: false },
node: 'role', disabled: 'inSchema', node: 'role', disabled: 'inSchema',
},{
id: 'schema', label: gettext('Schema'), cell: 'string', first_empty: false,
control: 'node-list-by-name', type: 'text', cache_level: 'database',
node: 'schema', mode: ['create', 'edit'], cache_node: 'database',
disabled: 'inSchema', select2: { allowClear: false },
},{
id: 'system_view', label: gettext('System materialized view?'), cell: 'string',
type: 'switch', mode: ['properties'],
}, pgBrowser.SecurityGroupSchema, {
id: 'acl', label: gettext('Privileges'),
mode: ['properties'], type: 'text', group: gettext('Security'),
},{ },{
id: 'comment', label: gettext('Comment'), cell: 'string', id: 'comment', label: gettext('Comment'), cell: 'string',
type: 'multiline', type: 'multiline',
},{
id: 'definition', label: gettext('Definition'), cell: 'string',
type: 'text', mode: ['create', 'edit'], group: gettext('Definition'),
tabPanelCodeClass: 'sql-code-control',
control: Backform.SqlCodeControl.extend({
onChange: function() {
Backform.SqlCodeControl.prototype.onChange.apply(this, arguments);
if(this.model && this.model.changed) {
if(this.model.origSessAttrs && (this.model.changed.definition != this.model.origSessAttrs.definition)) {
this.model.warn_text = gettext(
'Updating the definition will drop and re-create the materialized view. It may result in loss of information about its dependent objects.'
) + '<br><br><b>' + gettext('Do you want to continue?') +
'</b>';
}
else {
this.model.warn_text = undefined;
}
}
else {
this.model.warn_text = undefined;
}
},
}),
},{
id: 'with_data', label: gettext('With data?'),
group: gettext('Storage'), mode: ['edit', 'create'],
type: 'switch',
},{
id: 'spcname', label: gettext('Tablespace'), cell: 'string',
type: 'text', group: gettext('Storage'), first_empty: false,
control: 'node-list-by-name', node: 'tablespace', select2: { allowClear: false },
filter: function(m) {
return (m.label != 'pg_global');
},
},{
id: 'fillfactor', label: gettext('Fill factor'),
group: gettext('Storage'), mode: ['edit', 'create'],
type: 'int', min: 10, max: 100,
},{
id: 'vacuum_settings_str', label: gettext('Storage settings'),
type: 'multiline', group: gettext('Storage'), mode: ['properties'],
},{
type: 'nested', control: 'tab', id: 'materialization',
label: gettext('Parameter'), mode: ['edit', 'create'],
group: gettext('Parameter'),
schema: Backform.VacuumSettingsSchema,
},{
// Add Privilege Control
id: 'datacl', label: gettext('Privileges'), type: 'collection',
model: pgBrowser.Node.PrivilegeRoleModel.extend({
privileges: ['a', 'r', 'w', 'd', 'D', 'x', 't'],
}), uniqueCol : ['grantee'], editable: false,
group: 'security', canAdd: true, canDelete: true,
mode: ['edit', 'create'], control: 'unique-col-collection',
},{
// Add Security Labels Control
id: 'seclabels', label: gettext('Security labels'),
model: pgBrowser.SecLabelModel, editable: false, type: 'collection',
canEdit: false, group: 'security', canDelete: true,
mode: ['edit', 'create'], canAdd: true,
control: 'unique-col-collection', uniqueCol : ['provider'],
}], }],
sessChanged: function() { sessChanged: function() {
/* If only custom autovacuum option is enabled the check if the options table is also changed. */ /* If only custom autovacuum option is enabled the check if the options table is also changed. */

View File

@ -0,0 +1,179 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import gettext from 'sources/gettext';
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import SecLabelSchema from '../../../../../static/js/sec_label.ui';
import { isEmptyString } from 'sources/validators';
export default class MViewSchema extends BaseUISchema {
constructor(getPrivilegeRoleSchema, getVacuumSettingsSchema, fieldOptions={}, initValues) {
super({
spcname: undefined,
toast_autovacuum_enabled: 'x',
autovacuum_enabled: 'x',
warn_text: undefined,
...initValues
});
this.getPrivilegeRoleSchema = getPrivilegeRoleSchema;
this.getVacuumSettingsSchema = getVacuumSettingsSchema;
this.fieldOptions = {
role: [],
schema: [],
spcname: [],
nodeInfo: null,
...fieldOptions,
};
this.nodeInfo = this.fieldOptions.nodeInfo;
}
get idAttribute() {
return 'oid';
}
inSchema() {
if(this.nodeInfo && 'catalog' in this.nodeInfo)
{
return true;
}
return false;
}
get baseFields() {
let obj = this;
return [
{
id: 'name', label: gettext('Name'), cell: 'text',
type: 'text', disabled: obj.inSchema, noEmpty: true,
},{
id: 'oid', label: gettext('OID'), cell: 'text',
type: 'text', mode: ['properties'],
},{
id: 'owner', label: gettext('Owner'),
type: 'select', cell: 'text',
options: obj.fieldOptions.role, controlProps: { allowClear: false },
disabled: obj.inSchema,
},{
id: 'schema', label: gettext('Schema'), cell: 'text',
type: 'select', options: obj.fieldOptions.schema, mode: ['create', 'edit'],
cache_node: 'database', disabled: obj.inSchema,
controlProps: {
allowClear: false,
first_empty: false
},
},{
id: 'system_view', label: gettext('System materialized view?'), cell: 'text',
type: 'switch', mode: ['properties'],
},
{
id: 'acl', label: gettext('Privileges'),
mode: ['properties'], type: 'text', group: gettext('Security'),
},{
id: 'comment', label: gettext('Comment'), cell: 'text',
type: 'multiline',
},{
id: 'definition', label: gettext('Definition'), cell: 'text',
type: 'sql', mode: ['create', 'edit'], group: gettext('Definition'),
isFullTab: true, controlProps: { readOnly: this.nodeInfo && 'catalog' in this.nodeInfo ? true: false },
},{
id: 'with_data', label: gettext('With data?'),
group: gettext('Storage'), mode: ['edit', 'create'],
type: 'switch',
},{
id: 'spcname', label: gettext('Tablespace'), cell: 'text',
type: 'select', group: gettext('Storage'),
options: obj.fieldOptions.spcname,
controlProps: {
allowClear: false,
first_empty: false,
},
},{
id: 'fillfactor', label: gettext('Fill factor'),
group: gettext('Storage'), mode: ['edit', 'create'],
noEmpty: false, type: 'int', controlProps: {min: 10, max: 100}
},{
id: 'vacuum_settings_str', label: gettext('Storage settings'),
type: 'multiline', group: gettext('Storage'), mode: ['properties'],
},
{
type: 'nested-tab', group: gettext('Parameter'), mode: ['create', 'edit'],
schema: this.getVacuumSettingsSchema(),
},
/*{
type: 'nested', control: 'tab', id: 'materialization',
label: gettext('Parameter'), mode: ['edit', 'create'],
group: gettext('Parameter'),
schema: Backform.VacuumSettingsSchema,
},
{
// Add Privilege Control
id: 'datacl', label: gettext('Privileges'), type: 'collection',
model: pgBrowser.Node.PrivilegeRoleModel.extend({
privileges: ['a', 'r', 'w', 'd', 'D', 'x', 't'],
}), uniqueCol : ['grantee'], editable: false,
group: 'security', canAdd: true, canDelete: true,
mode: ['edit', 'create'], control: 'unique-col-collection',
},*/
{
id: 'datacl', label: gettext('Privileges'), type: 'collection',
schema: this.getPrivilegeRoleSchema(['U']),
uniqueCol : ['grantee'],
editable: false,
group: gettext('Security'), mode: ['edit', 'create'],
canAdd: true, canDelete: true,
},
{
// Add Security Labels Control
id: 'seclabels', label: gettext('Security labels'),
schema: new SecLabelSchema(),
editable: false, type: 'collection',
canEdit: false, group: gettext('Security'), canDelete: true,
mode: ['edit', 'create'], canAdd: true,
control: 'unique-col-collection',
uniqueCol : ['provider'],
}
];
}
validate(state, setError) {
let errmsg = null;
let obj = this;
if (isEmptyString(state.service)) {
/* mview definition validation*/
if (isEmptyString(state.definition)) {
errmsg = gettext('Please enter view definition.');
setError('definition', errmsg);
return true;
} else {
errmsg = null;
setError('definition', errmsg);
}
if (state.definition) {
obj.warningText = null;
if (obj.origData.oid !== undefined && state.definition !== obj.origData.definition) {
obj.warningText = gettext(
'Updating the definition will drop and re-create the materialized view. It may result in loss of information about its dependent objects.'
) + '<br><br><b>' + gettext('Do you want to continue?') + '</b>';
}
}
return false;
} else {
errmsg = null;
_.each(['definition'], (item) => {
setError(item, errmsg);
});
}
}
}

View File

@ -104,7 +104,7 @@ define('pgadmin.node.view', [
}, },
{ {
owner: pgBrowser.serverInfo[treeNodeInfo.server._id].user.name, owner: pgBrowser.serverInfo[treeNodeInfo.server._id].user.name,
schema: treeNodeInfo.schema.label schema: treeNodeInfo.schema ? treeNodeInfo.schema.label : ''
} }
); );
}, },

View File

@ -36,7 +36,7 @@ export default class ViewSchema extends BaseUISchema {
} }
notInSchema() { notInSchema() {
if(this.node_info && 'catalog' in this.node_info) { if(this.nodeInfo && 'catalog' in this.nodeInfo) {
return true; return true;
} }
return false; return false;
@ -96,7 +96,7 @@ export default class ViewSchema extends BaseUISchema {
id: 'definition', label: gettext('Code'), cell: 'text', id: 'definition', label: gettext('Code'), cell: 'text',
type: 'sql', mode: ['create', 'edit'], group: gettext('Code'), type: 'sql', mode: ['create', 'edit'], group: gettext('Code'),
isFullTab: true, isFullTab: true,
disabled: obj.notInSchema, controlProps: { readOnly: obj.nodeInfo && 'catalog' in obj.nodeInfo ? true: false },
}, },
{ {
@ -142,7 +142,7 @@ export default class ViewSchema extends BaseUISchema {
state.definition !== obj.origData.definition state.definition !== obj.origData.definition
)) { )) {
obj.warningText = null; obj.warningText = null;
return true; return false;
} }
let old_def = obj.origData.definition && let old_def = obj.origData.definition &&
@ -169,7 +169,7 @@ export default class ViewSchema extends BaseUISchema {
} else { } else {
obj.warningText = null; obj.warningText = null;
} }
return true; return false;
} }
} else { } else {

View File

@ -0,0 +1,135 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import jasmineEnzyme from 'jasmine-enzyme';
import React from 'react';
import '../helper/enzyme.helper';
import { createMount } from '@material-ui/core/test-utils';
import pgAdmin from 'sources/pgadmin';
import {messages} from '../fake_messages';
import SchemaView from '../../../pgadmin/static/js/SchemaView';
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import MViewSchema from '../../../pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/mview.ui';
class MockSchema extends BaseUISchema {
get baseFields() {
return [];
}
}
describe('MaterializedViewSchema', ()=>{
let mount;
let schemaObj = new MViewSchema(
()=>new MockSchema(),
()=>new MockSchema(),
{
role: ()=>[],
schema: ()=>[],
spcname: ()=>[],
},
{
owner: 'postgres',
schema: 'public'
}
);
let getInitData = ()=>Promise.resolve({});
/* Use createMount so that material ui components gets the required context */
/* https://material-ui.com/guides/testing/#api */
beforeAll(()=>{
mount = createMount();
});
afterAll(() => {
mount.cleanUp();
});
beforeEach(()=>{
jasmineEnzyme();
/* messages used by validators */
pgAdmin.Browser = pgAdmin.Browser || {};
pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
});
it('create', ()=>{
mount(<SchemaView
formType='dialog'
schema={schemaObj}
viewHelperProps={{
mode: 'create',
}}
onSave={()=>{}}
onClose={()=>{}}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
/>);
});
it('edit', ()=>{
mount(<SchemaView
formType='dialog'
schema={schemaObj}
getInitData={getInitData}
viewHelperProps={{
mode: 'edit',
}}
onSave={()=>{}}
onClose={()=>{}}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
/>);
});
it('properties', ()=>{
mount(<SchemaView
formType='tab'
schema={schemaObj}
getInitData={getInitData}
viewHelperProps={{
mode: 'properties',
}}
onHelp={()=>{}}
onEdit={()=>{}}
/>);
});
it('validate', ()=>{
let state = {};
let setError = jasmine.createSpy('setError');
state.definition = null;
schemaObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('definition', 'Please enter view definition.');
state.definition = 'SELECT 1;';
schemaObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('definition', null);
state.definition = 'SELECT 1';
schemaObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('definition', null);
state.service = 'Test';
state.definition = 'SELECT 1';
schemaObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('definition', null);
});
});

View File

@ -15,7 +15,7 @@ import pgAdmin from 'sources/pgadmin';
import {messages} from '../fake_messages'; import {messages} from '../fake_messages';
import SchemaView from '../../../pgadmin/static/js/SchemaView'; import SchemaView from '../../../pgadmin/static/js/SchemaView';
import BaseUISchema from 'sources/SchemaView/base_schema.ui'; import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import ViewSchema from '../../../pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.ui.js'; import ViewSchema from '../../../pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.ui';
class MockSchema extends BaseUISchema { class MockSchema extends BaseUISchema {