Port Synonyms node to react. Fixes #6599

pull/58/head
Akshay Joshi 2021-07-15 12:49:20 +05:30
parent 47855eae0b
commit 5c1ce23780
6 changed files with 307 additions and 120 deletions

View File

@ -7,6 +7,9 @@
//
//////////////////////////////////////////////////////////////
import { getNodeAjaxOptions, getNodeListByName } from '../../../../../../../static/js/node_ajax';
import SynonymSchema from './synonym.ui';
define('pgadmin.node.synonym', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.alertifyjs',
@ -68,6 +71,35 @@ define('pgadmin.node.synonym', [
]);
},
getSchema: function(treeNodeInfo, itemNodeData) {
return new SynonymSchema(
{
role: ()=>getNodeListByName('role', treeNodeInfo, itemNodeData),
schema: ()=>getNodeListByName('schema', treeNodeInfo, itemNodeData, {
cacheLevel: 'database',
cacheNode: 'database'
}),
synobjschema: ()=>getNodeListByName('schema', treeNodeInfo, itemNodeData, {}, (m)=>{
// Exclude PPAS catalogs
var exclude_catalogs = ['pg_catalog', 'sys', 'dbo', 'pgagent', 'information_schema', 'dbms_job_procedure'];
return m && _.indexOf(exclude_catalogs, m.label) == -1;
}),
getTargetObjectOptions: (targettype, synobjschema) =>
{
return getNodeAjaxOptions('get_target_objects', this, treeNodeInfo,
itemNodeData, {urlParams: {'trgTyp' : targettype, 'trgSchema' : synobjschema}, useCache: false});
},
},
treeNodeInfo,
{
owner: pgBrowser.serverInfo[treeNodeInfo.server._id].user.name,
schema: itemNodeData.label,
synobjschema: itemNodeData.label,
}
);
},
model: pgAdmin.Browser.Node.Model.extend({
isNew: function() {
return !this.fetchFromServer;
@ -104,123 +136,8 @@ define('pgadmin.node.synonym', [
type: 'text', mode: ['properties', 'create', 'edit'],
readonly: true , control: 'node-list-by-name',
node: 'role', visible: false,
},{
id: 'schema', label: gettext('Schema'), cell: 'string',
type: 'text', mode: ['properties', 'create', 'edit'],
readonly: function(m) { return !m.isNew(); }, node: 'schema',
control: 'node-list-by-name', cache_node: 'database',
cache_level: 'database',
},{
id: 'targettype', label: gettext('Target type'), cell: 'string',
disabled: 'inSchema', group: gettext('Definition'),
select2: { width: '50%', allowClear: false },
options: function() {
return [
{label: gettext('Function'), value: 'f'},
{label: gettext('Package'), value: 'P'},
{label: gettext('Procedure'), value: 'p'},
{label: gettext('Public Synonym'), value: 's'},
{label: gettext('Sequence'), value: 'S'},
{label: gettext('Table'), value: 'r'},
{label: gettext('View'), value: 'v'},
];
},
control: 'select2',
},{
id: 'synobjschema', label: gettext('Target schema'), cell: 'string',
type: 'text', mode: ['properties', 'create', 'edit'],
group: gettext('Definition'), deps: ['targettype'],
select2: { allowClear: false }, control: 'node-list-by-name',
node: 'schema', filter: function(d) {
// Exclude PPAS catalogs
var exclude_catalogs = ['pg_catalog', 'sys', 'dbo',
'pgagent', 'information_schema',
'dbms_job_procedure'];
return d && _.indexOf(exclude_catalogs, d.label) == -1;
},
disabled: function(m) {
// If tagetType is synonym then disable it
if(!m.inSchema.apply(this, [m])) {
var is_synonym = (m.get('targettype') == 's');
if(is_synonym) {
m.set('synobjschema', 'public', {silent: true});
return true;
} else {
return false;
}
}
return true;
},
},{
id: 'synobjname', label: gettext('Target object'), cell: 'string',
type: 'text', group: gettext('Definition'),
deps: ['targettype', 'synobjschema'],
control: 'node-ajax-options',
options: function(control) {
var trgTyp = control.model.get('targettype');
var trgSchema = control.model.get('synobjschema');
var res = [];
var node = control.field.get('schema_node'),
_url = node.generate_url.apply(
node, [
null, 'get_target_objects', control.field.get('node_data'), false,
control.field.get('node_info') ]);
$.ajax({
type: 'GET',
timeout: 30000,
url: _url,
cache: false,
async: false,
data: {'trgTyp' : trgTyp, 'trgSchema' : trgSchema},
})
// On success return function list from server
.done(function(result) {
res = result.data;
return res;
})
// On failure show error appropriate error message to user
.fail(function(xhr, status, error) {
alertify.pgRespErrorNotify(xhr, error);
});
return res;
},
disabled: function(m) {
if (this.node_info && 'catalog' in this.node_info) {
return true;
}
// Check the changed attributes if targettype or synobjschema
// is changed then reset the target object
if ('changed' in m && !('name' in m.changed) &&
('targettype' in m.changed || 'synobjschema' in m.changed)) {
m.set('synobjname', undefined);
}
}],
return false;
},
},{
id: 'is_sys_obj', label: gettext('System synonym?'),
cell:'boolean', type: 'switch', mode: ['properties'],
},
],
validate: function() {
var msg = undefined;
this.errorModel.clear();
if (_.isUndefined(this.get('name'))
|| String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') {
msg = gettext('Name cannot be empty.');
this.errorModel.set('name', msg);
} else if (_.isUndefined(this.get('synobjschema'))
|| String(this.get('synobjschema')).replace(/^\s+|\s+$/g, '') == '') {
msg = gettext('Target schema cannot be empty.');
this.errorModel.set('synobjschema', msg);
} else if (_.isUndefined(this.get('synobjname'))
|| String(this.get('synobjname')).replace(/^\s+|\s+$/g, '') == '') {
msg = gettext('Target object cannot be empty.');
this.errorModel.set('synobjname', msg);
}
return null;
},
// We will disable everything if we are under catalog node
inSchema: function() {
if(this.node_info && 'catalog' in this.node_info)

View File

@ -0,0 +1,146 @@
/////////////////////////////////////////////////////////////
//
// 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 { emptyValidator } from 'sources/validators';
export default class SynonymSchema extends BaseUISchema {
constructor(fieldOptions={}, nodeInfo, initValues) {
super({
targettype: 'r',
...initValues,
});
this.fieldOptions = fieldOptions;
this.nodeInfo = nodeInfo;
}
get idAttribute() {
return 'oid';
}
get baseFields() {
let obj = this;
return [
{
id: 'name', label: gettext('Name'), cell: 'text',
type: 'text', mode: ['properties', 'create', 'edit'],
readonly: function(state) { return !obj.isNew(state); },
}, {
id: 'oid', label: gettext('OID'), cell: 'text',
type: 'text', mode: ['properties'],
}, {
id: 'owner', label: gettext('Owner'),
options: this.fieldOptions.role,
controlProps: { allowClear: false, editable: false},
type: 'select', mode: ['properties', 'create', 'edit'],
readonly: true , visible: false,
}, {
id: 'schema', label: gettext('Schema'),
type: 'select', mode: ['properties', 'create', 'edit'],
options: this.fieldOptions.schema,
controlProps: { allowClear: false, editable: false },
readonly: function(state) { return !obj.isNew(state); },
}, {
id: 'targettype', label: gettext('Target type'),
readonly: this.inSchema, group: gettext('Definition'),
controlProps: { allowClear: false },
type: 'select',
options: [
{label: gettext('Function'), value: 'f'},
{label: gettext('Package'), value: 'P'},
{label: gettext('Procedure'), value: 'p'},
{label: gettext('Public Synonym'), value: 's'},
{label: gettext('Sequence'), value: 'S'},
{label: gettext('Table'), value: 'r'},
{label: gettext('View'), value: 'v'}
]
}, {
id: 'synobjschema', label: gettext('Target schema'),
type: 'select', mode: ['properties', 'create', 'edit'],
group: gettext('Definition'), deps: ['targettype'],
controlProps: { allowClear: false},
options: this.fieldOptions.synobjschema,
readonly: function(state) {
// If tagetType is synonym then disable it
if(!obj.inSchema()) {
if(state.targettype == 's') {
return true;
} else {
return false;
}
}
return true;
},
depChange: function(state) {
if(!obj.inSchema() && state.targettype == 's') {
return { synobjschema: 'public'};
}
}
}, {
id: 'synobjname', label: gettext('Target object'),
group: gettext('Definition'),
deps: ['targettype', 'synobjschema'],
type: (state)=>{
let fetchOptionsBasis = state.targettype + state.synobjschema;
return {
type: 'select',
options: ()=>obj.fieldOptions.getTargetObjectOptions(state.targettype, state.synobjschema),
optionsReloadBasis: fetchOptionsBasis,
};
},
readonly: function() {
if(!obj.inSchema()) {
return false;
}
return true;
}
}, {
id: 'is_sys_obj', label: gettext('System synonym?'),
cell:'boolean', type: 'switch', mode: ['properties'],
}
];
}
validate(state, setError) {
let errmsg = null;
errmsg = emptyValidator('Name', state.name);
if (errmsg) {
setError('name', errmsg);
return true;
} else {
setError('name', errmsg);
}
errmsg = emptyValidator('Target schema', state.synobjschema);
if (errmsg) {
setError('synobjschema', errmsg);
return true;
} else {
setError('synobjschema', errmsg);
}
errmsg = emptyValidator('Target object', state.synobjname);
if (errmsg) {
setError('synobjname', errmsg);
return true;
} else {
setError('synobjname', errmsg);
}
}
inSchema() {
if(this.nodeInfo && 'catalog' in this.nodeInfo)
{
return true;
}
return false;
}
}

View File

@ -64,6 +64,7 @@ export function getNodeAjaxOptions(url, nodeObj, treeNodeInfo, itemNodeData, par
let otherParams = {
urlWithId: false,
jumpAfterNode: null,
useCache: true,
...params
};
return new Promise((resolve, reject)=>{
@ -91,7 +92,7 @@ export function getNodeAjaxOptions(url, nodeObj, treeNodeInfo, itemNodeData, par
})
.then((res)=>{
data = res.data.data;
cacheNode.cache(nodeObj.type + '#' + url, treeNodeInfo, cacheLevel, data);
otherParams.useCache && cacheNode.cache(nodeObj.type + '#' + url, treeNodeInfo, cacheLevel, data);
resolve(transform(data));
})
.catch((err)=>{

View File

@ -116,9 +116,7 @@ export default function FormView({
visible = _.isUndefined(visible) ? true : visible;
let _visible = true;
if(visible) {
_visible = evalFunc(schema, visible, value);
}
_visible = evalFunc(schema, visible, value);
_visible = _visible && verInLimit;
disabled = evalFunc(schema, disabled, value);

View File

@ -597,6 +597,7 @@ function SchemaPropertiesView({
<MappedFormControl
key={field.id}
viewHelperProps={viewHelperProps}
state={origData}
name={field.id}
value={origData[field.id]}
readonly={readonly}

View File

@ -0,0 +1,124 @@
/////////////////////////////////////////////////////////////
//
// 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 SynonymSchema from '../../../pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.ui';
describe('SynonymSchema', ()=>{
let mount;
let schemaObj = new SynonymSchema(
{
role: ()=>[],
schema: ()=>[],
synobjschema: ()=>[],
getTargetObjectOptions: ()=>[],
},
[],
{
owner: 'postgres',
schema: 'public',
synobjschema: '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.name = null;
schemaObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('name', '\'Name\' cannot be empty.');
state.name = 'my_syn';
state.synobjschema = null;
schemaObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('synobjschema', '\'Target schema\' cannot be empty.');
state.synobjschema = 'public';
state.synobjname = null;
schemaObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('synobjname', '\'Target object\' cannot be empty.');
});
});