Port Trigger Functions node to react. Fixes #6665

This commit is contained in:
Nikhil Mohite 2021-08-10 11:37:51 +05:30 committed by Akshay Joshi
parent 9bfef1f6e5
commit 169d8fa480
4 changed files with 443 additions and 27 deletions

View File

@ -1480,7 +1480,8 @@ class FunctionView(PGChildNodeView, DataTypeReader, SchemaDiffObjectCompare):
parallel_dict = {'u': 'UNSAFE', 's': 'SAFE', 'r': 'RESTRICTED'}
# Get Schema Name from its OID.
self._get_schema_name_from_oid(data)
if self.node_type != 'trigger_function':
self._get_schema_name_from_oid(data)
if fnid is not None:
# Edit Mode

View File

@ -6,6 +6,10 @@
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import TriggerFunctionSchema from './trigger_function.ui';
import { getNodeListByName, getNodeAjaxOptions } from '../../../../../../../static/js/node_ajax';
import { getNodeVariableSchema } from '../../../../../static/js/variable.ui';
import { getNodePrivilegeRoleSchema } from '../../../../../static/js/privilege.ui';
/* Create and Register Function Collection and Node. */
define('pgadmin.node.trigger_function', [
@ -81,6 +85,26 @@ define('pgadmin.node.trigger_function', [
},
]);
},
getSchema: function(treeNodeInfo, itemNodeData) {
return new TriggerFunctionSchema(
(privileges)=>getNodePrivilegeRoleSchema('', treeNodeInfo, itemNodeData, privileges),
()=>getNodeVariableSchema(this, treeNodeInfo, itemNodeData, false, false),
{
role: ()=>getNodeListByName('role', treeNodeInfo, itemNodeData),
schema: ()=>getNodeListByName('schema', treeNodeInfo, itemNodeData, {cacheLevel: 'database'}),
language: ()=>getNodeAjaxOptions('get_languages', this, treeNodeInfo, itemNodeData, {noCache: true}, (res) => {
return _.reject(res, function(o) {
return o.label == 'sql' || o.label == 'edbspl';
});
}),
nodeInfo: treeNodeInfo
},
{
funcowner: pgBrowser.serverInfo[treeNodeInfo.server._id].user.name,
pronamespace: treeNodeInfo.schema ? treeNodeInfo.schema.label : ''
}
);
},
model: pgBrowser.Node.Model.extend({
idAttribute: 'oid',
initialize: function(attrs, args) {
@ -99,34 +123,8 @@ define('pgadmin.node.trigger_function', [
defaults: {
name: undefined,
oid: undefined,
xmin: undefined,
funcowner: undefined,
pronamespace: undefined,
description: undefined,
pronargs: undefined, /* Argument Count */
proargs: undefined, /* Arguments */
proargtypenames: undefined, /* Argument Signature */
prorettypename: 'trigger', /* Return Type */
lanname: 'plpgsql', /* Language Name in which function is being written */
provolatile: undefined, /* Volatility */
proretset: undefined, /* Return Set */
proisstrict: undefined,
prosecdef: undefined, /* Security of definer */
proiswindow: undefined, /* Window Function ? */
procost: undefined, /* Estimated execution Cost */
prorows: undefined, /* Estimated number of rows */
proleakproof: undefined,
args: [],
prosrc: undefined,
prosrc_c: undefined,
probin: '$libdir/',
options: [],
variables: [],
proacl: undefined,
seclabels: [],
acl: [],
sysfunc: undefined,
sysproc: undefined,
},
schema: [{
id: 'name', label: gettext('Name'), cell: 'string',

View File

@ -0,0 +1,291 @@
/////////////////////////////////////////////////////////////
//
// 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 SecLabelSchema from '../../../../../static/js/sec_label.ui';
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import { isEmptyString } from 'sources/validators';
export default class TriggerFunctionSchema extends BaseUISchema {
constructor(getPrivilegeRoleSchema, getVariableSchema, fieldOptions={}, initValues) {
super({
name: null,
oid: null,
xmin: null,
funcowner: null,
pronamespace: null,
description: null,
pronargs: null, /* Argument Count */
proargs: null, /* Arguments */
proargtypenames: null, /* Argument Signature */
prorettypename: 'trigger', /* Return Type */
lanname: 'plpgsql', /* Language Name in which function is being written */
provolatile: null, /* Volatility */
proretset: null, /* Return Set */
proisstrict: null,
prosecdef: null, /* Security of definer */
proiswindow: null, /* Window Function ? */
procost: null, /* Estimated execution Cost */
prorows: null, /* Estimated number of rows */
proleakproof: null,
args: [],
prosrc: null,
prosrc_c: null,
probin: '$libdir/',
options: [],
variables: [],
proacl: null,
seclabels: [],
acl: [],
sysfunc: null,
sysproc: null,
...initValues
});
this.getPrivilegeRoleSchema = getPrivilegeRoleSchema;
this.getVariableSchema = getVariableSchema;
this.fieldOptions = {
role: [],
schema: [],
language: [],
nodeInfo: null,
...fieldOptions,
};
}
get idAttribute() {
return 'oid';
}
isReadonly(state) {
switch(state.name){
case 'proargs':
case 'proargtypenames':
case 'prorettypename':
case 'proretset':
case 'proiswindow':
return !this.isNew();
default:
return false;
}
}
isVisible(state) {
if (state.name == 'sysproc') { return false; }
return true;
}
isDisabled() {
if('catalog' in this.fieldOptions.nodeInfo) {
return true;
}
return false;
}
get baseFields() {
let obj = this;
return [
{
id: 'name', label: gettext('Name'), cell: 'text',
type: 'text', mode: ['properties', 'create', 'edit'],
disabled: obj.isDisabled, readonly: obj.isReadonly,
noEmpty: true
},{
id: 'oid', label: gettext('OID'), cell: 'text',
type: 'text' , mode: ['properties'],
},{
id: 'funcowner', label: gettext('Owner'), cell: 'text',
type:'select', disabled: obj.isDisabled, readonly: obj.isReadonly,
options: obj.fieldOptions.role,
controlProps: { allowClear: false }
},{
id: 'pronamespace', label: gettext('Schema'), cell: 'string',
type: 'select', cache_level: 'database',
disabled: obj.isDisabled, readonly: obj.isReadonly,
mode: ['create', 'edit'],
options: obj.fieldOptions.schema,
controlProps: { allowClear: false }
},{
id: 'sysfunc', label: gettext('System trigger function?'),
cell:'boolean', type: 'switch',
mode: ['properties'], visible: obj.isVisible
},{
id: 'sysproc', label: gettext('System procedure?'),
cell:'boolean', type: 'switch',
mode: ['properties'], visible: obj.isVisible
},{
id: 'description', label: gettext('Comment'), cell: 'string',
type: 'multiline', disabled: obj.isDisabled, readonly: obj.isReadonly,
},{
id: 'pronargs', label: gettext('Argument count'), cell: 'text',
type: 'text', group: gettext('Definition'), mode: ['properties'],
},{
id: 'proargs', label: gettext('Arguments'), cell: 'string',
type: 'text', group: gettext('Definition'), mode: ['properties', 'edit'],
ddisabled: obj.isDisabled, readonly: obj.isReadonly,
},{
id: 'proargtypenames', label: gettext('Signature arguments'), cell:
'text', type: 'text', group: gettext('Definition'), mode: ['properties'],
disabled: obj.isDisabled, readonly: obj.isReadonly,
},{
id: 'prorettypename', label: gettext('Return type'), cell: 'text',
type: 'select', group: gettext('Definition'),
disabled: obj.isDisabled, readonly: obj.isReadonly,
controlProps: {
width: '100%',
allowClear: false,
},
mode: ['create'], visible: obj.isVisible,
options: [
{label: gettext('trigger'), value: 'trigger'},
{label: gettext('event_trigger'), value: 'event_trigger'},
],
},{
id: 'prorettypename', label: gettext('Return type'), cell: 'text',
type: 'text', group: gettext('Definition'),
mode: ['properties', 'edit'], disabled: obj.isDisabled, readonly: obj.isReadonly,
visible: obj.isVisible
}, {
id: 'lanname', label: gettext('Language'), cell: 'text',
type: 'select', group: gettext('Definition'),
disabled: obj.isDisabled, readonly: obj.isReadonly,
options: obj.fieldOptions.language,
controlProps: {
allowClear: false,
filter: (options) => {
return (options||[]).filter(option => {
return option.label != '';
});
}
},
},{
id: 'prosrc', label: gettext('Code'), cell: 'text',
type: 'sql', isFullTab: true,
mode: ['properties', 'create', 'edit'],
group: gettext('Code'), deps: ['lanname'],
visible: (state) => {
if (state.lanname == 'c') {
return false;
}
return true;
},
disabled: obj.isDisabled, readonly: obj.isReadonly,
},{
id: 'probin', label: gettext('Object file'), cell: 'string',
type: 'text', group: gettext('Definition'), deps: ['lanname'],
visible: (state) => {
if (state.lanname == 'c') { return true; }
return false;
},
disabled: obj.isDisabled, readonly: obj.isReadonly,
},{
id: 'prosrc_c', label: gettext('Link symbol'), cell: 'string',
type: 'text', group: gettext('Definition'), deps: ['lanname'],
visible: (state) => {
if (state.lanname == 'c') { return true; }
return false;
},
disabled: obj.isDisabled, readonly: obj.isReadonly,
},{
id: 'provolatile', label: gettext('Volatility'), cell: 'text',
type: 'select', group: gettext('Options'),
options:[
{'label': 'VOLATILE', 'value': 'v'},
{'label': 'STABLE', 'value': 's'},
{'label': 'IMMUTABLE', 'value': 'i'},
], disabled: obj.isDisabled, readonly: obj.isReadonly,
controlProps: { allowClear: false },
},{
id: 'proretset', label: gettext('Returns a set?'), type: 'switch',
group: gettext('Options'), disabled: obj.isDisabled, readonly: obj.isReadonly,
visible: obj.isVisible
},{
id: 'proisstrict', label: gettext('Strict?'), type: 'switch',
disabled: obj.isDisabled, readonly: obj.isReadonly, group: gettext('Options'),
},{
id: 'prosecdef', label: gettext('Security of definer?'),
group: gettext('Options'), cell:'boolean', type: 'switch',
disabled: obj.isDisabled, readonly: obj.isReadonly,
},{
id: 'proiswindow', label: gettext('Window?'),
group: gettext('Options'), cell:'boolean', type: 'switch',
disabled: obj.isDisabled, readonly: obj.isReadonly, visible: obj.isVisible
},{
id: 'procost', label: gettext('Estimated cost'), type: 'text',
group: gettext('Options'), disabled: obj.isDisabled, readonly: obj.isReadonly,
},{
id: 'prorows', label: gettext('Estimated rows'), type: 'text',
group: gettext('Options'),
disabled: (state) => {
let isDisabled = true;
if(state.proretset == true) {
isDisabled = false;
}
return isDisabled;
},
readonly: obj.isReadonly,
deps: ['proretset'], visible: obj.isVisible
},{
id: 'proleakproof', label: gettext('Leak proof?'),
group: gettext('Options'), cell:'boolean', type: 'switch', min_version: 90200,
disabled: obj.isDisabled, readonly: obj.isReadonly,
}, {
id: 'proacl', label: gettext('Privileges'), mode: ['properties'],
group: gettext('Security'), type: 'text',
},
{
id: 'variables', label: '', type: 'collection',
group: gettext('Parameters'), control: 'variable-collection',
mode: ['edit', 'create'], canEdit: false,
canDelete: true, disabled: obj.isDisabled, readonly: obj.isReadonly,
schema: this.getVariableSchema(),
editable: false,
},
{
id: 'acl', label: gettext('Privileges'), type: 'collection',
schema: this.getPrivilegeRoleSchema(['X']),
uniqueCol : ['grantee'],
editable: false,
group: gettext('Security'), mode: ['edit', 'create'],
canAdd: true, canDelete: true,
},
{
id: 'seclabels', label: gettext('Security labels'), type: 'collection',
schema: new SecLabelSchema(),
editable: false, group: gettext('Security'),
mode: ['edit', 'create'],
canAdd: true, canEdit: false, canDelete: true,
uniqueCol : ['provider'],
min_version: 90200,
disabled: obj.isDisabled, readonly: obj.isReadonly,
}
];
}
validate(state, setError) {
let errmsg = null;
if (isEmptyString(state.service)) {
/* code validation*/
if (isEmptyString(state.prosrc)) {
errmsg = gettext('Code cannot be empty.');
setError('prosrc', errmsg);
return true;
} else {
errmsg = null;
setError('prosrc', errmsg);
}
}
}
}

View File

@ -0,0 +1,126 @@
/////////////////////////////////////////////////////////////
//
// 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 TriggerFunctionSchema from '../../../pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/trigger_function.ui';
class MockSchema extends BaseUISchema {
get baseFields() {
return [];
}
}
describe('TriggerFunctionSchema', ()=>{
let mount;
let schemaObj = new TriggerFunctionSchema(
()=>new MockSchema(),
()=>new MockSchema(),
{
role: [],
schema: [],
language: [],
nodeInfo: {}
},
{
funcowner: 'postgres',
pronamespace: '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.prosrc = null;
schemaObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('prosrc', 'Code cannot be empty.');
state.prosrc = 'SELECT 1';
schemaObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('prosrc', null);
});
});