mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2024-11-22 00:37:36 -06:00
Port Trigger Functions node to react. Fixes #6665
This commit is contained in:
parent
9bfef1f6e5
commit
169d8fa480
@ -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
|
||||
|
@ -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',
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user