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.'); + + }); +}); +