From c94fc73c11390ad917f4a17685bcb8ff5b679239 Mon Sep 17 00:00:00 2001 From: Pradip Parkale Date: Wed, 14 Jul 2021 14:49:30 +0530 Subject: [PATCH] Port Cast node to react. Fixes #6566. --- .../servers/databases/casts/static/js/cast.js | 261 +++--------------- .../databases/casts/static/js/cast.ui.js | 193 +++++++++++++ .../templates/casts/sql/default/create.sql | 6 +- .../schema_ui_files/cast.ui.spec.js | 112 ++++++++ 4 files changed, 347 insertions(+), 225 deletions(-) create mode 100644 web/pgadmin/browser/server_groups/servers/databases/casts/static/js/cast.ui.js create mode 100644 web/regression/javascript/schema_ui_files/cast.ui.spec.js diff --git a/web/pgadmin/browser/server_groups/servers/databases/casts/static/js/cast.js b/web/pgadmin/browser/server_groups/servers/databases/casts/static/js/cast.js index ecaf50159..6854db123 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/casts/static/js/cast.js +++ b/web/pgadmin/browser/server_groups/servers/databases/casts/static/js/cast.js @@ -6,12 +6,16 @@ // This software is released under the PostgreSQL Licence // ////////////////////////////////////////////////////////////// +import { getNodeAjaxOptions } from '../../../../../../static/js/node_ajax'; +import CastSchema from './cast.ui'; +import getApiInstance from '../../../../../../../static/js/api_instance'; + define('pgadmin.node.cast', [ 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.alertifyjs', 'pgadmin.backform', 'pgadmin.browser.collection', -], function(gettext, url_for, $, _, pgAdmin, pgBrowser, alertify, Backform) { +], function(gettext, url_for, $, _, pgAdmin, pgBrowser) { // Extend the collection class for cast if (!pgBrowser.Nodes['coll-cast']) { pgAdmin.Browser.Nodes['coll-cast'] = @@ -68,16 +72,6 @@ define('pgadmin.node.cast', [ // Define the backform model for cast node model: pgAdmin.Browser.Node.Model.extend({ idAttribute: 'oid', - defaults: { - name: undefined, // Name of the cast - encoding: 'UTF8', - srctyp: undefined, // Source type - trgtyp: undefined, // Target type - proname: undefined, // Function - castcontext: undefined, // Context (IMPLICIT/EXPLICIT/ASSIGNMENT) - syscast: undefined, // Is this cast is system object? Yes/No - description: undefined, // Comment on the cast - }, // Define the schema for cast schema: [{ @@ -86,224 +80,47 @@ define('pgadmin.node.cast', [ },{ id: 'oid', label: gettext('OID'), cell: 'string', editable: false, type: 'text', mode: ['properties'], - },{ - id: 'srctyp', label: gettext('Source type'), url: 'get_type', - type: 'text', group: gettext('Definition'), readonly: function(m) { - return !m.isNew(); - }, mode: ['create'], - - transform: function(rows) { - _.each(rows, function(r) { - r['image'] = 'icon-cast'; - }); - return rows; - }, - - /* - * Control is extended to create cast name from source type and destination type - * once their values are changed - */ - control: Backform.NodeAjaxOptionsControl.extend({ - - onChange: function() { - Backform.NodeAjaxOptionsControl.prototype.onChange.apply( - this, arguments - ); - - /* - * On source type change, check if both source type and - * target type are set, if yes then fetch values from both - * controls and generate cast name - */ - var srctype = this.model.get('srctyp'); - var trgtype = this.model.get('trgtyp'); - if(srctype != undefined && srctype != '' && - trgtype != undefined && trgtype != '') - this.model.set('name', srctype+'->'+trgtype); - else - this.model.unset('name'); - }, - }), }, - - /* - * Text control for viewing source type in properties and - * edit mode only - */ { - id: 'srctyp', label: gettext('Source type'), type: 'text', - group: gettext('Definition'), readonly: true, mode:['properties','edit'], - },{ - id: 'trgtyp', label: gettext('Target type'), url: 'get_type', - type: 'text', group: gettext('Definition'), readonly: function(m) { - return !m.isNew(); - }, mode: ['create'], - transform: function(rows) { - _.each(rows, function(r) { - r['image'] = 'icon-cast'; - }); - return rows; - }, - - /* - * Control is extended to create cast name from source type and destination type - * once their values are changed - */ - control: Backform.NodeAjaxOptionsControl.extend({ - - onChange: function() { - Backform.NodeAjaxOptionsControl.prototype.onChange.apply( - this, arguments - ); - - /* - * on target type change, check if both source type and - * target type are set, if yes then fetch values from both - * controls and generate cast name - */ - var srcType = this.model.get('srctyp'); - var trgtype = this.model.get('trgtyp'); - if(srcType != undefined && srcType != '' && - trgtype != undefined && trgtype != '') - this.model.set('name', srcType+'->'+trgtype); - else - this.model.unset('name'); - }, - }), - }, - /* - * Text control for viewing target type in properties and - * edit mode only - */ - { - id: 'trgtyp', label: gettext('Target type'), type: 'text', - group: gettext('Definition'), readonly: true, mode:['properties','edit'], - }, - - /* - * Proname field is dependent on source type and target type. - * On source and target type changed event, - * associated functions will be fetch using ajax call - */ - { - id: 'proname', label: gettext('Function'), deps:['srctyp', 'trgtyp'], - type: 'text', readonly: function(m) { return !m.isNew(); }, - group: gettext('Definition'), mode: ['create'], - control: 'node-ajax-options', - options: function(control) { - var srcTyp = control.model.get('srctyp'); - var trgtyp = control.model.get('trgtyp'); - var res = []; - - if(srcTyp != undefined && srcTyp != '' && - trgtyp != undefined && trgtyp != '') - { - var node = control.field.get('schema_node'), - _url = node.generate_url.apply( - node, [ - null, 'get_functions', control.field.get('node_data'), false, - control.field.get('node_info'), - ]); - $.ajax({ - type: 'POST', - timeout: 30000, - url: _url, - cache: false, - async: false, - data: {'srctyp' : srcTyp, 'trgtyp' : trgtyp}, - }) - // 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; - }, - }, - /* - * Text type control for viewing function name in properties and - * edit mode only - */ - { - id: 'proname', label: gettext('Function'), type: 'text', - group: gettext('Definition'), readonly: true, mode:['properties','edit'], - },{ - id: 'castcontext', label: gettext('Context'), - options:{'onText':'IMPLICIT','offText':'EXPLICIT', width: '90'}, - editable: false, type: 'string', group: gettext('Definition'), - mode:['create'], - control: Backform.SwitchControl.extend({ - getValueFromDOM: function() { - return this.$input.prop('checked') ? 'IMPLICIT' : 'EXPLICIT'; - }, - }), - }, - /* - * Text control for viewing context in properties and - * edit mode - */ - { - id: 'castcontext', label: gettext('Context'), readonly: true, - options:[{ - label: 'IMPLICIT', value: 'IMPLICIT', - },{ - label: 'EXPLICIT', value: 'EXPLICIT', - },{ - label: 'ASSIGNMENT', value: 'ASSIGNMENT', - }], editable: false, type: 'select2', group: gettext('Definition'), - mode:['properties', 'edit'], - },{ - id: 'syscast', label: gettext('System cast?'), - cell: 'switch', type: 'switch', mode: ['properties'], - },{ id: 'description', label: gettext('Comment'), type: 'multiline', cellHeaderClasses: 'width_percent_50', }, ], - - /* - * Triggers control specific error messages for source type and - * target type if any one of them is not selected while creating - * new cast - */ - validate: function() { - - var srctype = this.get('srctyp'), - trgtype = this.get('trgtyp'), - msg; - - // validate source type control - if ( - _.isUndefined(srctype) || _.isNull(srctype) || - String(srctype).replace(/^\s+|\s+$/g, '') == '' - ) { - msg = gettext('Source type must be selected.'); - this.errorModel.set('srctyp', msg); - return msg; - } else { - this.errorModel.unset('srctyp'); - } - - // validate target type control - if ( - _.isUndefined(trgtype) || _.isNull(trgtype) || - String(trgtype).replace(/^\s+|\s+$/g, '') == '' - ) { - msg = gettext('Target type must be selected.'); - this.errorModel.set('trgtyp', msg); - return msg; - } else { - this.errorModel.unset('trgtyp'); - } - this.trigger('on-status-clear'); - return null; - }, }), + + getSchema: function(treeNodeInfo, itemNodeData){ + let schema = new CastSchema({ + getTypeOptions: ()=>getNodeAjaxOptions('get_type', this, treeNodeInfo, itemNodeData), + getFuncOptions: (srcTyp, trgtyp) => + { + return new Promise((resolve, reject)=>{ + const api = getApiInstance(); + + var _url = pgBrowser.Nodes['cast'].generate_url.apply( + pgBrowser.Nodes['cast'], [ + null, 'get_functions', itemNodeData, false, + treeNodeInfo, + ]); + var data = {'srctyp' : srcTyp, 'trgtyp' : trgtyp}; + + if(srcTyp != undefined && srcTyp != '' && + trgtyp != undefined && trgtyp != ''){ + + api.post(_url, data) + .then(res=>{ + data = res.data.data; + resolve(data); + }) + .catch((err)=>{ + reject(err); + }); + } + }); + }, + }, + ); + return schema; + }, }); } diff --git a/web/pgadmin/browser/server_groups/servers/databases/casts/static/js/cast.ui.js b/web/pgadmin/browser/server_groups/servers/databases/casts/static/js/cast.ui.js new file mode 100644 index 000000000..47aa6e4fb --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/casts/static/js/cast.ui.js @@ -0,0 +1,193 @@ +///////////////////////////////////////////////////////////// +// +// 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'; + + + +export default class CastSchema extends BaseUISchema { + constructor(fieldOptions={}, initValues) { + super({ + name: undefined, // Name of the cast + encoding: 'UTF8', + srctyp: undefined, // Source type + trgtyp: undefined, // Target type + proname: undefined, // Function + castcontext: true, // Context (IMPLICIT/EXPLICIT/ASSIGNMENT) + syscast: undefined, // Is this cast is system object? Yes/No + description: undefined, // Comment on the cast + ...initValues, + }); + this.fieldOptions=fieldOptions; + + } + get idAttribute() { + return 'oid'; + } + + get baseFields() { + let obj = this; + return [{ + id: 'name', label: gettext('Name'), cell: 'string', + editable: false, type: 'text', readonly: true, cellHeaderClasses: 'width_percent_50', + },{ + id: 'oid', label: gettext('OID'), cell: 'string', + editable: false, type: 'text', mode: ['properties'], + },{ + id: 'srctyp', label: gettext('Source type'), + type: 'select', group: gettext('Definition'), readonly: function(state) { + return !obj.isNew(state); + }, mode: ['create'], + options:this.fieldOptions.getTypeOptions, + /* + * Control is extended to create cast name from source type and destination type + * once their values are changed + */ + depChange: (state)=>{ + /* + * On source type change, check if both source type and + * target type are set, if yes then fetch values from both + * controls and generate cast name + */ + var srctype = state.srctyp; + var trgtype = state.trgtyp; + if(srctype != undefined && srctype != '' && + trgtype != undefined && trgtype != '') + return state.name = srctype+'->'+trgtype; + else + return state.name = ''; + }, + }, + + /* + * Text control for viewing source type in properties and + * edit mode only + */ + { + id: 'srctyp', label: gettext('Source type'), type: 'text', + group: gettext('Definition'), readonly: true, mode:['properties','edit'], + },{ + id: 'trgtyp', label: gettext('Target type'), + type: 'select', group: gettext('Definition'), readonly: function(state) { + return !obj.isNew(state); + }, mode: ['create'], + options:this.fieldOptions.getTypeOptions, + + depChange: (state)=>{ + /* + * On source type change, check if both source type and + * target type are set, if yes then fetch values from both + * controls and generate cast name + */ + var srctype = state.srctyp; + var trgtype = state.trgtyp; + if(srctype != undefined && srctype != '' && + trgtype != undefined && trgtype != '') + return state.name = srctype+'->'+trgtype; + else + return state.name = ''; + }, + }, + /* + * Text control for viewing target type in properties and + * edit mode only + */ + { + id: 'trgtyp', label: gettext('Target type'), type: 'text', + group: gettext('Definition'), readonly: true, mode:['properties','edit'], + }, + + /* + * Proname field is dependent on source type and target type. + * On source and target type changed event, + * associated functions will be fetch using ajax call + */ + { + id: 'proname', label: gettext('Function'), deps:['srctyp', 'trgtyp'], + readonly: function(state) { return !obj.isNew(state); }, + group: gettext('Definition'), mode: ['create'], + first_empty: true, + type: (state)=>{ + + let fetchOptionsBasis = state.srctyp + state.trgtyp; + return { + type: 'select', + options: ()=>obj.fieldOptions.getFuncOptions(state.srctyp, state.trgtyp), + optionsReloadBasis: fetchOptionsBasis, + }; + }, + }, + /* + * Text type control for viewing function name in properties and + * edit mode only + */ + { + id: 'proname', label: gettext('Function'), type: 'text', + group: gettext('Definition'), readonly: true, mode:['properties','edit'], + }, + { + id: 'castcontext', label: gettext('Context'),type: 'toggle', + options: [ + {'label': gettext('IMPLICIT'), value: true}, + {'label': gettext('EXPLICIT'), value: false}, + ], + group: gettext('Definition'), + mode:['create'], + }, + /* + * Text control for viewing context in properties and + * edit mode + */ + { + id: 'castcontext', label: gettext('Context'), readonly: true, + options:[{ + label: 'IMPLICIT', value: 'IMPLICIT', + },{ + label: 'EXPLICIT', value: 'EXPLICIT', + },{ + label: 'ASSIGNMENT', value: 'ASSIGNMENT', + }], type: 'select', group: gettext('Definition'), + mode:['properties', 'edit'], + controlProps: { + editable: false, + }, + },{ + id: 'syscast', label: gettext('System cast?'), + cell: 'switch', type: 'switch', mode: ['properties'], + },{ + id: 'description', label: gettext('Comment'), + type: 'multiline', cellHeaderClasses: 'width_percent_50', + }, + ]; + } + + validate(state, setError) { + let errmsg = null; + if (isEmptyString(state.srctyp)) { + errmsg = gettext('Source type must be selected.'); + setError('srctyp', errmsg); + return true; + } else { + errmsg = null; + setError('srctyp', errmsg); + } + + if (isEmptyString(state.trgtyp)) { + errmsg = gettext('Target type must be selected.'); + setError('trgtyp', errmsg); + return true; + } else { + errmsg = null; + setError('trgtyp', errmsg); + } + return false; + } +} + diff --git a/web/pgadmin/browser/server_groups/servers/databases/casts/templates/casts/sql/default/create.sql b/web/pgadmin/browser/server_groups/servers/databases/casts/templates/casts/sql/default/create.sql index edd2444be..25f12945e 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/casts/templates/casts/sql/default/create.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/casts/templates/casts/sql/default/create.sql @@ -5,11 +5,11 @@ CREATE CAST ({{ conn|qtTypeIdent(data.srctyp) }} AS {{ conn|qtTypeIdent(data.trg {% if data.proname and data.proname != 'binary compatible'%} WITH FUNCTION {{data.proname}}{% else %} WITHOUT FUNCTION{% endif %} -{% if data.castcontext and data.castcontext != 'EXPLICIT' %} +{% if data.castcontext and data.castcontext != 'false' %} - AS {{data.castcontext}}{% endif %}; + AS {{'IMPLICIT'}}{% endif %}; {# Description for CAST #} {% if data.description %} COMMENT ON CAST ({{ conn|qtTypeIdent(data.srctyp) }} AS {{ conn|qtTypeIdent(data.trgtyp) }}) IS {{ data.description|qtLiteral }}; -{% endif %}{% endif %} \ No newline at end of file +{% endif %}{% endif %} diff --git a/web/regression/javascript/schema_ui_files/cast.ui.spec.js b/web/regression/javascript/schema_ui_files/cast.ui.spec.js new file mode 100644 index 000000000..e954c4400 --- /dev/null +++ b/web/regression/javascript/schema_ui_files/cast.ui.spec.js @@ -0,0 +1,112 @@ +///////////////////////////////////////////////////////////// +// +// 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 CastSchema from '../../../pgadmin/browser/server_groups/servers/databases/casts/static/js/cast.ui'; + + +describe('CastSchema', ()=>{ + let mount; + let schemaObj = new CastSchema( + { + getTypeOptions: ()=>[], + getFuncOptions: ()=>[], + }, + ); + 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.srctyp = null; + schemaObj.validate(state, setError); + expect(setError).toHaveBeenCalledWith('srctyp', 'Source type must be selected.'); + + state.srctyp = 'bigint'; + state.trgtyp = null; + schemaObj.validate(state, setError); + expect(setError).toHaveBeenCalledWith('trgtyp', 'Target type must be selected.'); + }); +}); +