diff --git a/web/pgadmin/browser/server_groups/servers/databases/languages/static/js/language.js b/web/pgadmin/browser/server_groups/servers/databases/languages/static/js/language.js
index fcd514f19..c4919b924 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/languages/static/js/language.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/languages/static/js/language.js
@@ -7,6 +7,10 @@
//
//////////////////////////////////////////////////////////////
+import { getNodeAjaxOptions, getNodeListByName } from '../../../../../../static/js/node_ajax';
+import LanguageSchema from './language.ui';
+import { getNodePrivilegeRoleSchema } from '../../../../static/js/privilege.ui';
+
define('pgadmin.node.language', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform',
@@ -65,24 +69,10 @@ define('pgadmin.node.language', [
icon: 'wcTabIcon icon-language', data: {action: 'create'},
}]);
},
+
// Define the model for language node
model: pgBrowser.Node.Model.extend({
idAttribute: 'oid',
- defaults: {
- name: undefined,
- lanowner: undefined,
- comment: undefined,
- lanacl: [],
- seclabels:[],
- trusted: true,
- lanproc: undefined,
- laninl: undefined,
- lanval: undefined,
- is_template: false,
- template_list: [],
- },
-
- // Default values!
initialize: function(attrs, args) {
var isNew = (_.size(attrs) === 0);
if (isNew) {
@@ -96,48 +86,7 @@ define('pgadmin.node.language', [
// Define the schema for the language node
schema: [{
id: 'name', label: gettext('Name'), type: 'text',
- control: 'node-ajax-options', mode: ['properties', 'create', 'edit'],
- url:'get_templates', select2: { allowClear: false, tags: true, multiple: false },
- transform: function(data, cell) {
- var res = [],
- control = cell || this,
- label = control.model.get('name');
-
- if (!control.model.isNew()) {
- res.push({label: label, value: label});
- }
- else {
- var tmp_list = [];
- if (data && _.isArray(data)) {
- _.each(data, function(d) {
- res.push({label: d.tmplname, value: d.tmplname});
- tmp_list.push(d.tmplname);
- });
- }
- this.model.set({'template_list': tmp_list});
- }
-
- return res;
- },
- visible: function() {
- if(!_.isUndefined(this.node_info) && !_.isUndefined(this.node_info.server)
- && !_.isUndefined(this.node_info.server.version) &&
- this.node_info.server.version < 130000) {
- return true;
- }
- return false;
- },
- }, {
- id: 'name', label: gettext('Name'), type: 'text',
- mode: ['properties', 'create', 'edit'],
- visible: function() {
- if(!_.isUndefined(this.node_info) && !_.isUndefined(this.node_info.server)
- && !_.isUndefined(this.node_info.server.version) &&
- this.node_info.server.version >= 130000) {
- return true;
- }
- return false;
- },
+ mode: ['properties'],
},{
id: 'oid', label: gettext('OID'), cell: 'string', mode: ['properties'],
type: 'text',
@@ -145,145 +94,29 @@ define('pgadmin.node.language', [
id: 'lanowner', label: gettext('Owner'), type: 'text',
control: Backform.NodeListByNameControl, node: 'role',
mode: ['edit', 'properties', 'create'], select2: { allowClear: false },
- },{
- id: 'acl', label: gettext('Privileges'), type: 'text',
- group: gettext('Security'), mode: ['properties'],
- },{
- id: 'is_sys_obj', label: gettext('System language?'),
- cell:'boolean', type: 'switch', mode: ['properties'],
- },{
+ },
+ {
id: 'description', label: gettext('Comment'), cell: 'string',
type: 'multiline',
- },{
- id: 'trusted', label: gettext('Trusted?'), type: 'switch',
- group: gettext('Definition'), mode: ['edit', 'properties', 'create'], deps: ['name'],
- disabled: function(m) {
- if (m.isNew()) {
- if (m.get('template_list').indexOf(m.get('name')) == -1) {
- m.set({'is_template': false});
- return false;
- }
- else {
- m.set({'is_template': true});
- return true;
- }
- }
- return false;
- },
- readonly: function(m) {return !m.isNew();},
- },{
- id: 'lanproc', label: gettext('Handler function'), type: 'text', control: 'node-ajax-options',
- group: gettext('Definition'), mode: ['edit', 'properties', 'create'], url:'get_functions',
- deps: ['name'], first_empty: false,
- /* This function is used to populate the handler function
- * for the selected language node. It will check if the property
- * type is 'handler' then push the data into the result array.
- */
- transform: function(data) {
- var res = [];
- if (data && _.isArray(data)) {
- _.each(data, function(d) {
- if (d.prop_type == 'handler') {
- res.push({label: d.label, value: d.label});
- }
- });
- }
- return res;
- }, disabled: 'isDisabled',
- readonly: function(m) {return !m.isNew();},
- },{
- id: 'laninl', label: gettext('Inline function'), type: 'text', control: 'node-ajax-options',
- group: gettext('Definition'), mode: ['edit', 'properties', 'create'], url:'get_functions',
- deps: ['name'], first_empty: false,
- /* This function is used to populate the inline function
- * for the selected language node. It will check if the property
- * type is 'inline' then push the data into the result array.
- */
- transform: function(data) {
- var res = [];
- if (data && _.isArray(data)) {
- _.each(data, function(d) {
- if (d.prop_type == 'inline') {
- res.push({label: d.label, value: d.label});
- }
- });
- }
- return res;
- }, disabled: 'isDisabled',
- readonly: function(m) {return !m.isNew();},
- },{
- id: 'lanval', label: gettext('Validator function'), type: 'text', control: 'node-ajax-options',
- group: gettext('Definition'), mode: ['edit', 'properties', 'create'], url:'get_functions',
- deps: ['name'],
- /* This function is used to populate the validator function
- * for the selected language node. It will check if the property
- * type is 'validator' then push the data into the result array.
- */
- transform: function(data) {
- var res = [];
- if (data && _.isArray(data)) {
- _.each(data, function(d) {
- if (d.prop_type == 'validator') {
- res.push({label: d.label, value: d.label});
- }
- });
- }
- return res;
- }, disabled: 'isDisabled',
- readonly: function(m) {return !m.isNew();},
- }, {
- id: 'lanacl', label: gettext('Privileges'), type: 'collection',
- group: gettext('Security'), control: 'unique-col-collection', mode: ['edit', 'create'],
- model: pgBrowser.Node.PrivilegeRoleModel.extend({
- privileges: ['U'],
- }), canAdd: true, canDelete: true, uniqueCol : ['grantee'],
- },{
- id: 'seclabels', label: gettext('Security labels'), mode: ['edit', 'create'],
- model: pgBrowser.SecLabelModel, editable: false,
- type: 'collection', group: gettext('Security'), min_version: 90200,
- canAdd: true, canEdit: false, canDelete: true,
- control: 'unique-col-collection',
},
],
- /* validate function is used to validate the input given by
- * the user. In case of error, message will be displayed on
- * the GUI for the respective control.
- */
- validate: function() {
- var name = this.get('name'),
- msg;
-
- if (_.isUndefined(name) || _.isNull(name) ||
- String(name).replace(/^\s+|\s+$/g, '') == '') {
- msg = gettext('Name cannot be empty.');
- this.errorModel.set('name', msg);
- return msg;
- } else {
- this.errorModel.unset('name');
- }
-
- // If predefined template is selected then no need to validate it.
- if (!this.get('is_template')) {
- var handler_func = this.get('lanproc');
- if (_.isUndefined(handler_func) || _.isNull(handler_func) ||
- String(handler_func).replace(/^\s+|\s+$/g, '') == '') {
- msg = gettext('Handler function cannot be empty.');
- this.errorModel.set('lanproc', msg);
- return msg;
- } else {
- this.errorModel.unset('lanproc');
- }
- }
-
- return null;
- },
- isDisabled: function(m){
- if (m.isNew()) {
- return m.get('template_list').indexOf(m.get('name')) != -1;
- }
- return false;
- },
}),
+
+ getSchema: function(treeNodeInfo, itemNodeData){
+ let schema = new LanguageSchema(
+ (privileges)=>getNodePrivilegeRoleSchema(this, treeNodeInfo, itemNodeData, privileges),
+ {
+ lan_functions: ()=>getNodeAjaxOptions('get_functions', this, treeNodeInfo, itemNodeData),
+ templates_data: ()=>getNodeAjaxOptions('get_templates', this, treeNodeInfo, itemNodeData),
+ role:()=>getNodeListByName('role', treeNodeInfo, itemNodeData),
+
+ },
+ {
+ node_info: treeNodeInfo.server,
+ },
+ );
+ return schema;
+ },
});
}
return pgBrowser.Nodes['coll-language'];
diff --git a/web/pgadmin/browser/server_groups/servers/databases/languages/static/js/language.ui.js b/web/pgadmin/browser/server_groups/servers/databases/languages/static/js/language.ui.js
new file mode 100644
index 000000000..21bdd607b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/languages/static/js/language.ui.js
@@ -0,0 +1,234 @@
+/////////////////////////////////////////////////////////////
+//
+// 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 { isEmptyString } from 'sources/validators';
+import SecLabelSchema from '../../../../static/js/sec_label.ui';
+
+export default class LanguageSchema extends BaseUISchema {
+ constructor(getPrivilegeRoleSchema, fieldOptions={}, node_info, initValues) {
+ super({
+ name: undefined,
+ lanowner: (node_info) ? node_info['node_info'].user.name: undefined,
+ comment: undefined,
+ lanacl: [],
+ seclabels:[],
+ trusted: true,
+ lanproc: undefined,
+ laninl: undefined,
+ lanval: undefined,
+ ...initValues,
+ });
+
+ this.fieldOptions=fieldOptions;
+ this.getPrivilegeRoleSchema = getPrivilegeRoleSchema;
+ this.node_info = node_info;
+ this.templateList = [];
+ this.isTemplate = false;
+ }
+ get idAttribute() {
+ return 'oid';
+ }
+ // This function check whether the server is less than 13 or not.
+ isLessThan13(){
+ if(!_.isUndefined(this.node_info)
+ && !_.isUndefined(this.node_info['node_info'])
+ && !_.isUndefined(this.node_info['node_info'].version)
+ && this.node_info['node_info'].version < 130000)
+ { return true; }
+ else{ return false; }
+
+ }
+ isDisabled(state){
+ if (this.templateList.some(code => code.tmplname === state.name)){
+ this.isTemplate = false;
+ return true;
+ }else{
+ this.isTemplate =true;
+ return false;
+ }
+ }
+ get baseFields() {
+ let obj = this;
+ return [{
+ id: 'name', label: gettext('Name'), noEmpty: true,
+ mode: ['properties', 'create', 'edit'],
+ optionsLoaded: (options) => { obj.templateList = options; },
+ type: (state) => {
+ if (obj.isLessThan13()){
+ return {
+ type: 'select',
+ options: this.fieldOptions.templates_data,
+ controlProps: {
+ allowClear: false,
+ multiple: false,
+ creatable: true,
+ filter: (options) => {
+ let res = [];
+ if (state) {
+ options.forEach((option) => {
+ res.push({label: option.tmplname, value: option.tmplname});
+ });
+ }
+ return res;
+ }
+ }
+ };
+ }else{
+ return {type: 'text'};
+ }
+ },
+ },
+ {
+ id: 'oid', label: gettext('OID'), cell: 'string', mode: ['properties'],
+ type: 'text',
+ },{
+ id: 'lanowner', label: gettext('Owner'), type: 'select',
+ options: this.fieldOptions.role,
+ mode: ['edit', 'properties', 'create'],
+ },{
+ id: 'acl', label: gettext('Privileges'), type: 'text',
+ group: gettext('Security'), mode: ['properties'],
+ },{
+ id: 'is_sys_obj', label: gettext('System language?'),
+ cell:'boolean', type: 'switch', mode: ['properties'],
+ },{
+ id: 'description', label: gettext('Comment'), cell: 'string',
+ type: 'multiline',
+ },{
+ id: 'trusted', label: gettext('Trusted?'), type: 'switch',
+ group: gettext('Definition'), mode: ['edit', 'properties', 'create'],
+ disabled:obj.isDisabled, deps:['name'],
+ readonly: (state) => {return !obj.isNew(state);},
+ },
+ {
+ id: 'lanproc', label: gettext('Handler function'),
+ group: gettext('Definition'), mode: ['properties'], type: 'text',
+ },
+ {
+ id: 'lanproc', label: gettext('Handler function'), deps:['name'],
+ group: gettext('Definition'), mode: ['edit', 'create'],
+ /* This function is used to populate the handler function
+ * for the selected language node. It will check if the property
+ * type is 'handler' then push the data into the result array.
+ */
+ type: (state) => {
+ return {
+ type: 'select',
+ options: this.fieldOptions.lan_functions,
+ controlProps: {
+ allowClear: false,
+ filter: (options) => {
+ let res = [];
+ if (state) {
+ options.forEach((option) => {
+ if(option.prop_type == 'handler')
+ res.push({label: option.label, value: option.label});
+ });
+ }
+ return res;
+ }
+ }
+ };
+ },
+ disabled: obj.isDisabled,
+ readonly: (state) => {return !obj.isNew(state);},
+ },{
+ id: 'laninl', label: gettext('Inline function'),
+ group: gettext('Definition'), mode: ['edit', 'create'],
+ deps:['name'], first_empty: false,
+ type: (state) => {
+ return {
+ type: 'select',
+ options: this.fieldOptions.lan_functions,
+ controlProps: {
+ allowClear: false,
+ filter: (options) => {
+ let res = [];
+ if (state) {
+ options.forEach((option) => {
+ if(option.prop_type == 'inline')
+ res.push({label: option.label, value: option.label});
+ });
+ }
+ return res;
+ }
+ }
+ };
+ },
+ disabled: obj.isDisabled,
+ readonly: (state) => {return !obj.isNew(state);},
+ },
+ {
+ id: 'laninl', label: gettext('Handler function'),
+ group: gettext('Definition'), mode: ['properties'], type: 'text',
+ },{
+ id: 'lanval', label: gettext('Validator function'), deps:['name'],
+ group: gettext('Definition'), mode: ['edit', 'create'],
+ type: (state) => {
+ return {
+ type: 'select',
+ options: this.fieldOptions.lan_functions,
+ optionsLoaded: (options) => { this.fieldOptions.lan_functions = options; },
+
+ controlProps: {
+ allowClear: false,
+ filter: (options) => {
+ let res = [];
+ if (state) {
+ options.forEach((option) => {
+ if(option.prop_type == 'validator')
+ res.push({label: option.label, value: option.label});
+ });
+ }
+ return res;
+ }
+ }
+ };
+ },
+ /* This function is used to populate the validator function
+ * for the selected language node. It will check if the property
+ * type is 'validator' then push the data into the result array.
+ */
+ disabled: obj.isDisabled,
+ readonly: (state) => {return !obj.isNew(state);},
+ },
+ {
+ id: 'lanval', label: gettext('Handler function'),
+ group: gettext('Definition'), mode: ['properties'], type: 'text',
+ },
+ {
+ id: 'lanacl', label: gettext('Privileges'), type: 'collection',
+ group: gettext('Security'), mode: ['edit', 'create'],
+ schema: this.getPrivilegeRoleSchema(['U']),
+ canAdd: true, canDelete: true, uniqueCol : ['grantee'],
+ },
+ {
+ id: 'seclabels', label: gettext('Security labels'), mode: ['edit', 'create'],
+ schema: new SecLabelSchema(), editable: false,
+ type: 'collection', group: gettext('Security'), min_version: 90200,
+ canAdd: true, canEdit: false, canDelete: true,
+ },
+ ];
+ }
+
+ validate(state, setError) {
+ let errmsg = null;
+ if (this.isTemplate && isEmptyString(state.lanproc)) {
+ errmsg = gettext('Handler function cannot be empty.');
+ setError('lanproc', errmsg);
+ return true;
+ } else {
+ errmsg = null;
+ setError('lanproc', errmsg);
+ }
+ return false;
+ }
+}
+
diff --git a/web/pgadmin/static/js/components/FormComponents.jsx b/web/pgadmin/static/js/components/FormComponents.jsx
index f1a97ff8b..82bebdc95 100644
--- a/web/pgadmin/static/js/components/FormComponents.jsx
+++ b/web/pgadmin/static/js/components/FormComponents.jsx
@@ -388,7 +388,7 @@ export function InputToggle({cid, value, onChange, options, disabled, readonly,
const isSelected = option.value === value;
const isDisabled = disabled || (readonly && isSelected);
return (
-
{option.label}
@@ -556,7 +556,8 @@ function getRealValue(options, value, creatable, formatter) {
realValue = realValue.map((val)=>(_.find(options, (option)=>option.value==val)));
}
} else {
- realValue = _.find(options, (option)=>option.value==value) || null;
+ realValue = _.find(options, (option)=>option.value==value) ||
+ (creatable && !_.isUndefined(value) ? {label:value, value: value} : null);
}
return realValue;
}
diff --git a/web/regression/javascript/schema_ui_files/language.ui.spec.js b/web/regression/javascript/schema_ui_files/language.ui.spec.js
new file mode 100644
index 000000000..5ba43a9d9
--- /dev/null
+++ b/web/regression/javascript/schema_ui_files/language.ui.spec.js
@@ -0,0 +1,123 @@
+/////////////////////////////////////////////////////////////
+//
+// 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 BaseUISchema from 'sources/SchemaView/base_schema.ui';
+import SchemaView from '../../../pgadmin/static/js/SchemaView';
+import LanguageSchema from '../../../pgadmin/browser/server_groups/servers/databases/languages/static/js/language.ui';
+
+class MockSchema extends BaseUISchema {
+ get baseFields() {
+ return [];
+ }
+}
+
+describe('LanguageSchema', ()=>{
+ let mount;
+ let schemaObj = new LanguageSchema(
+ ()=>new MockSchema(),
+ {
+ lan_functions: ()=>[],
+ templates_data: ()=>[],
+ },
+ {
+ node_info: {connected: true,
+ user: {id: 10, name: 'postgres', is_superuser: true, can_create_role: true, can_create_db: true},
+ user_id: 1,
+ user_name: 'postgres',
+ version: 120005,
+ },
+ },
+ );
+ 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({}}
+ onClose={()=>{}}
+ onHelp={()=>{}}
+ onEdit={()=>{}}
+ onDataChange={()=>{}}
+ confirmOnCloseReset={false}
+ hasSQL={false}
+ disableSqlHelp={false}
+ />);
+ });
+
+ it('edit', ()=>{
+ mount({}}
+ onClose={()=>{}}
+ onHelp={()=>{}}
+ onEdit={()=>{}}
+ onDataChange={()=>{}}
+ confirmOnCloseReset={false}
+ hasSQL={false}
+ disableSqlHelp={false}
+ />);
+ });
+
+ it('properties', ()=>{
+ mount({}}
+ onEdit={()=>{}}
+ />);
+ });
+
+ it('validate', ()=>{
+ let state = {};
+ let setError = jasmine.createSpy('setError');
+
+ state.lanproc = null;
+ schemaObj.validate(state, setError);
+ expect(setError).toHaveBeenCalledWith('lanproc', 'Handler function cannot be empty.');
+
+ });
+});
+