diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/static/js/type.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/static/js/type.js index 2b5b069e6..314c9c187 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/static/js/type.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/static/js/type.js @@ -7,6 +7,11 @@ // ////////////////////////////////////////////////////////////// +// import TypeSchema from './type.ui'; +import { getNodeListByName } from '../../../../../../../static/js/node_ajax'; +import { getNodePrivilegeRoleSchema } from '../../../../../static/js/privilege.ui'; +import TypeSchema, { getCompositeSchema, getRangeSchema, getExternalSchema, getDataTypeSchema } from './type.ui'; + define('pgadmin.node.type', [ 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', @@ -29,228 +34,6 @@ define('pgadmin.node.type', [ }); } - // Integer Cell for Columns Length and Precision - var IntegerDepCell = Backgrid.IntegerCell.extend({ - initialize: function() { - Backgrid.NumberCell.prototype.initialize.apply(this, arguments); - Backgrid.Extension.DependentCell.prototype.initialize.apply(this, arguments); - }, - dependentChanged: function () { - this.model.set(this.column.get('name'), null); - this.render(); - return this; - }, - remove: Backgrid.Extension.DependentCell.prototype.remove, - }); - - // Node-Ajax-Cell with Deps - var NodeAjaxOptionsDepsCell = Backgrid.Extension.NodeAjaxOptionsCell.extend({ - initialize: function() { - Backgrid.Extension.NodeAjaxOptionsCell.prototype.initialize.apply(this, arguments); - Backgrid.Extension.DependentCell.prototype.initialize.apply(this, arguments); - }, - dependentChanged: function () { - var model = this.model, - column = this.column, - editable = this.column.get('editable'), - input = this.$el.find('select').first(); - - var is_editable = _.isFunction(editable) ? !!editable.apply(column, [model]) : !!editable; - if (is_editable) { - this.$el.addClass('editable'); - input.prop('disabled', false); - } else { - this.$el.removeClass('editable'); - input.prop('disabled', true); - } - - this.delegateEvents(); - return this; - }, - remove: Backgrid.Extension.DependentCell.prototype.remove, - }); - - // Composite type model declaration - var CompositeModel = Backform.CompositeModel = pgBrowser.Node.Model.extend({ - idAttribute: 'attnum', - defaults: { - attnum: undefined, - member_name: undefined, - type: undefined, - tlength: undefined, - is_tlength: false, - precision: undefined, - is_precision: false, - collation: undefined, - min_val: undefined, - max_val: undefined, - }, - type_options: undefined, - subtypes: undefined, - schema: [{ - id: 'member_name', label: gettext('Member Name'), - type: 'text', disabled: false, editable: true, - },{ - id: 'type', label: gettext('Type'), control: 'node-ajax-options', - type: 'text', url: 'get_types', disabled: false, node: 'type', - cache_node: 'domain', editable: true, - cell: 'node-ajax-options', select2: {allowClear: false}, - transform: function(d, control){ - control.model.type_options = d; - return d; - }, - },{ - // Note: There are ambiguities in the PG catalogs and docs between - // precision and scale. In the UI, we try to follow the docs as - // closely as possible, therefore we use Length/Precision and Scale - id: 'tlength', label: gettext('Length/Precision'), deps: ['type'], type: 'text', - disabled: false, cell: IntegerDepCell, - editable: function(m) { - // We will store type from selected from combobox - var of_type = m.get('type'); - if(m.type_options) { - // iterating over all the types - _.each(m.type_options, function(o) { - // if type from selected from combobox matches in options - if ( of_type == o.value ) { - // if length is allowed for selected type - if(o.length) - { - // set the values in model - m.set('is_tlength', true, {silent: true}); - m.set('min_val', o.min_val, {silent: true}); - m.set('max_val', o.max_val, {silent: true}); - } else { - // set the values in model - m.set('is_tlength', false, {silent: true}); - } - } - }); - } - return m.get('is_tlength'); - }, - },{ - // Note: There are ambiguities in the PG catalogs and docs between - // precision and scale. In the UI, we try to follow the docs as - // closely as possible, therefore we use Length/Precision and Scale - id: 'precision', label: gettext('Scale'), deps: ['type'], - type: 'text', disabled: false, cell: IntegerDepCell, - editable: function(m) { - // We will store type from selected from combobox - var of_type = m.get('type'); - if(m.type_options) { - // iterating over all the types - _.each(m.type_options, function(o) { - // if type from selected from combobox matches in options - if ( of_type == o.value ) { - // if precession is allowed for selected type - if(o.precision) - { - // set the values in model - m.set('is_precision', true, {silent: true}); - m.set('min_val', o.min_val, {silent: true}); - m.set('max_val', o.max_val, {silent: true}); - } else { - // set the values in model - m.set('is_precision', false, {silent: true}); - } - } - }); - } - return m.get('is_precision'); - }, - },{ - id: 'collation', label: gettext('Collation'), - cell: NodeAjaxOptionsDepsCell, deps: ['type'], - select2: {allowClear: false}, - control: 'node-ajax-options', editable: function(m) { - var of_type = m.get('type'), - flag = false; - if(m.type_options) { - _.each(m.type_options, function(o) { - if ( of_type == o.value ) { - if(o.is_collatable) - { - flag = true; - } - } - }); - } - - if (flag) { - setTimeout(function(){ - m.set('collspcname', '', {silent: true}); - }, 10); - } - return flag; - }, - type: 'text', disabled: false, url: 'get_collations', node: 'type', - }], - validate: function() { - var errmsg = null; - - // Clearing previous errors first. - this.errorModel.clear(); - // Validation for member name - if ( _.isUndefined(this.get('member_name')) || - _.isNull(this.get('member_name')) || - String(this.get('member_name')).replace(/^\s+|\s+$/g, '') == '') { - errmsg = gettext('Please specify the value for member name.'); - this.errorModel.set('member_name', errmsg); - return errmsg; - } - else if ( _.isUndefined(this.get('type')) || - _.isNull(this.get('type')) || - String(this.get('type')).replace(/^\s+|\s+$/g, '') == '') { - errmsg = gettext('Please specify the type.'); - this.errorModel.set('type', errmsg); - return errmsg; - } - // Validation for Length/Precision field (see comments above if confused about the naming!) - else if (this.get('is_tlength') - && !_.isUndefined(this.get('tlength'))) { - if (this.get('tlength') < this.get('min_val')) - errmsg = gettext('Length/Precision should not be less than %s.', this.get('min_val')); - if (this.get('tlength') > this.get('max_val') ) - errmsg = gettext('Length/Precision should not be greater than %s.', this.get('max_val')); - // If we have any error set then throw it to user - if(errmsg) { - this.errorModel.set('tlength', errmsg); - return errmsg; - } - } - // Validation for scale field (see comments above if confused about the naming!) - else if (this.get('is_precision') - && !_.isUndefined(this.get('precision'))) { - if (this.get('precision') < this.get('min_val')) - errmsg = gettext('Scale should not be less than %s.', this.get('min_val')); - if (this.get('precision') > this.get('max_val')) - errmsg = gettext('Scale should not be greater than %s.', this.get('max_val')); - // If we have any error set then throw it to user - if(errmsg) { - this.errorModel.set('precision', errmsg); - return errmsg; - } - } - return null; - }, - }); - - var EnumModel = Backform.EnumModel = pgBrowser.Node.Model.extend({ - defaults: { - label: undefined, - }, - schema: [{ - id: 'label', label: gettext('Label'),type: 'text', disabled: false, - cellHeaderClasses: 'width_percent_99', editable: function(m) { - return _.isUndefined(m.get('label')); - }, - }], - validate: function() { - return null; - }, - }); - if (!pgBrowser.Nodes['type']) { pgBrowser.Nodes['type'] = schemaChild.SchemaChildNode.extend({ type: 'type', @@ -292,6 +75,9 @@ define('pgadmin.node.type', [ }, ext_funcs: undefined, + /* Few fields are kept since the properties tab for collection is not + yet migrated to new react schema. Once the properties for collection + is removed, remove this model */ model: pgBrowser.Node.Model.extend({ defaults: { name: undefined, @@ -325,762 +111,38 @@ define('pgadmin.node.type', [ control: 'node-list-by-name', type: 'text', mode: ['properties', 'create', 'edit'], node: 'role', disabled: 'inSchema', select2: {allowClear: false}, - },{ - id: 'schema', label: gettext('Schema'), cell: 'string', - type: 'text', mode: ['create', 'edit'], node: 'schema', - disabled: 'schemaCheck', filter: function(d) { - // If schema name start with pg_* then we need to exclude them - if(d && d.label.match(/^pg_/)) - { - return false; - } - return true; - }, cache_node: 'database', cache_level: 'database', - control: 'node-list-by-name', select2: {allowClear: false}, - },{ - id: 'typtype', label: gettext('Type'), - mode: ['create','edit'], disabled: 'inSchema', readonly: 'inEditMode', - group: gettext('Definition'), - select2: { allowClear: false }, - options: function() { - var typetype = [ - {label: gettext('Composite'), value: 'c'}, - {label: gettext('Enumeration'), value: 'e'}, - {label: gettext('External'), value: 'b'}, - {label: gettext('Range'), value: 'r'}, - {label: gettext('Shell'), value: 'p'}, - ]; - if (this.node_info.server.server_type === 'ppas' && - this.node_info.server.version >= 90500){ - typetype.push( - {label: gettext('Nested Table'), value: 'N'}, - {label: gettext('Varying Array'), value: 'V'} - ); - } - return typetype; - }, - // If create mode then by default open composite type - control: Backform.Select2Control.extend({ - render: function(){ - // Initialize parent's render method - Backform.Select2Control.prototype.render.apply(this, arguments); - if(this.model.isNew()) { - this.model.set({'typtype': 'c'}); - } - return this; - }, - }), - },{ - id: 'composite', label: gettext('Composite Type'), - model: CompositeModel, editable: true, type: 'collection', - group: gettext('Definition'), mode: ['edit', 'create'], - control: 'unique-col-collection', uniqueCol : ['member_name'], - canAdd: true, canEdit: false, canDelete: true, disabled: 'inSchema', - deps: ['typtype'], - visible: function(m) { - return m.get('typtype') === 'c'; - }, - },{ - id: 'enum', label: gettext('Enumeration type'), - model: EnumModel, editable: true, type: 'collection', - group: gettext('Definition'), mode: ['edit', 'create'], - canAdd: true, canEdit: false, canDelete: function(m) { - // We will disable it if it's in 'edit' mode - return m.isNew(); - }, - disabled: 'inSchema', deps: ['typtype'], - control: 'unique-col-collection', uniqueCol : ['label'], - visible: function(m) { - return m.get('typtype') === 'e'; - }, - },{ - type: 'nested', - group: gettext('Definition'), - label: '', - control: 'plain-fieldset', - deps: ['typtype'], - mode: ['edit', 'create', 'properties'], - visible: function (m) { - return m.get('typtype') === 'N' || m.get('typtype') === 'V'; - }, - schema: [{ - id: 'type', - type: 'text', - label: gettext('Data Type'), - group: gettext('Definition'), - control: 'node-ajax-options', - mode: ['edit', 'create','properties'], - readonly: 'inEditMode', - url: 'get_types', - disabled: false, - node: 'type', - cache_node: 'domain', - editable: true, - deps: ['typtype'], - cell: 'node-ajax-options', - select2: { allowClear: false }, - transform: function (d, control) { - var data_types = []; - _.each(d, function (o) { - if (!(o.value.includes('[]'))) { - data_types.push(o); - } - }); - control.model.type_options = data_types; - return data_types; - }, - },{ - id: 'maxsize', - group: gettext('Definition'), - label: gettext('Size'), - type: 'int', - deps: ['typtype'], - cell: IntegerDepCell, - mode: ['create', 'edit','properties'], - readonly: 'inEditMode', - visible: function (m) { - return m.get('typtype') === 'V'; - } - },{ - // Note: There are ambiguities in the PG catalogs and docs between - // precision and scale. In the UI, we try to follow the docs as - // closely as possible, therefore we use Length/Precision and Scale - id: 'tlength', - group: gettext('Data Type'), - label: gettext('Length/Precision'), - mode: ['edit', 'create','properties'], - deps: ['type'], - type: 'text', - readonly: 'inEditMode', - cell: IntegerDepCell, - visible: function (m) { - return m.get('typtype') === 'N'; - }, - disabled: function (m) { - var of_type = m.get('type'), - flag = true; - _.each(m.type_options, function (o) { - if (of_type == o.value) { - if (o.length) { - m.set('min_val_attlen', o.min_val, { silent: true }); - m.set('max_val_attlen', o.max_val, { silent: true }); - flag = false; - } - } - }); - flag && setTimeout(function () { - if (m.get('attlen')) { - m.set('attlen', null); - } - }, 10); - return flag; - }, - editable: function (m) { - // We will store type from selected from combobox - var of_type = m.get('type'); - if (m.type_options) { - // iterating over all the types - _.each(m.type_options, function (o) { - // if type from selected from combobox matches in options - if (of_type == o.value) { - // if length is allowed for selected type - if (o.length) { - // set the values in model - m.set('is_tlength', true, { silent: true }); - m.set('min_val', o.min_val, { silent: true }); - m.set('max_val', o.max_val, { silent: true }); - } else { - // set the values in model - m.set('is_tlength', false, { silent: true }); - } - } - }); - } - return m.get('is_tlength'); - } - },{ - // Note: There are ambiguities in the PG catalogs and docs between - // precision and scale. In the UI, we try to follow the docs as - // closely as possible, therefore we use Length/Precision and Scale - id: 'precision', - group: gettext('Data Type'), - label: gettext('Scale'), - mode: ['edit', 'create','properties'], - deps: ['type'], - type: 'text', - readonly: 'inEditMode', - cell: IntegerDepCell, - visible: function (m) { - return m.get('typtype') === 'N'; - }, - disabled: function(m) { - var of_type = m.get('type'), - flag = true; - _.each(m.type_options, function(o) { - if ( of_type == o.value ) { - if(o.precision) { - m.set('min_val_attprecision', 0, {silent: true}); - m.set('max_val_attprecision', o.max_val, {silent: true}); - flag = false; - } - } - }); - - flag && setTimeout(function() { - if(m.get('attprecision')) { - m.set('attprecision', null); - } - },10); - return flag; - }, - editable: function(m) { - // We will store type from selected from combobox - var of_type = m.get('type'); - if(m.type_options) { - // iterating over all the types - _.each(m.type_options, function(o) { - // if type from selected from combobox matches in options - if ( of_type == o.value ) { - // if precession is allowed for selected type - if(o.precision) - { - // set the values in model - m.set('is_precision', true, {silent: true}); - m.set('min_val', o.min_val, {silent: true}); - m.set('max_val', o.max_val, {silent: true}); - } else { - // set the values in model - m.set('is_precision', false, {silent: true}); - } - } - }); - } - return m.get('is_precision'); - }, - }] - },{ - // We will disable range type control in edit mode - type: 'nested', control: 'plain-fieldset', group: gettext('Definition'), - mode: ['edit', 'create'], - visible: function(m) { - return m.get('typtype') === 'r'; - }, deps: ['typtype'], label: '', - schema:[{ - id: 'typname', label: gettext('Subtype'), cell: 'string', - control: 'node-ajax-options', - select2: { allowClear: true, placeholder: '', width: '100%' }, - url: 'get_stypes', type: 'text', mode: ['properties', 'create', 'edit'], - group: gettext('Range Type'), disabled: 'inSchema', - readonly: 'inEditMode', - transform: function(d, self){ - self.model.subtypes = d; - return d; - }, - },{ - id: 'opcname', label: gettext('Subtype operator class'), cell: 'string', - mode: ['properties', 'create', 'edit'], group: gettext('Range Type'), - disabled: 'inSchema', readonly: 'inEditMode', deps: ['typname'], - control: 'select2', options: function(control) { - var l_typname = control.model.get('typname'), - result = []; - - if(!_.isUndefined(l_typname) && l_typname != '') - { - var node = control.field.get('schema_node'), - _url = node.generate_url.apply( - node, [ - null, 'get_subopclass', control.field.get('node_data'), false, - control.field.get('node_info'), - ]); - $.ajax({ - async: false, - url: _url, - cache: false, - data: {'typname' : l_typname}, - }) - .done(function(res) { - result = res.data; - }) - .fail(function() { - control.model.trigger('pgadmin:view:fetch:error', control.model, control.field); - }); - // - } - return result; - }, - },{ - id: 'collname', label: gettext('Collation'), cell: 'string', - type: 'text', mode: ['properties', 'create', 'edit'], - group: gettext('Range Type'), - deps: ['typname'], control: 'node-ajax-options', url: 'get_collations', - select2: { allowClear: true, placeholder: '', width: '100%' }, - disabled: function(m) { - if(this.node_info && 'catalog' in this.node_info) - { - return true; - } - - // Disbale in edit mode - if (!m.isNew()) { - return true; - } - - // To check if collation is allowed? - var of_subtype = m.get('typname'), - is_collate = undefined; - if(!_.isUndefined(of_subtype)) { - // iterating over all the types - _.each(m.subtypes, function(s) { - // if subtype from selected from combobox matches - if ( of_subtype === s.label ) { - // if collation is allowed for selected subtype - // then enable it else disable it - is_collate = s.is_collate; - } - }); - } - // If is_collate is true then do not disable - if(!is_collate) { - m.set({'collname': '', silent: true}); - this.options = []; - } - - return is_collate ? false : true; - }, - },{ - id: 'rngcanonical', label: gettext('Canonical function'), cell: 'string', - type: 'text', mode: ['properties', 'create', 'edit'], - group: gettext('Range Type'), - disabled: 'inSchema', readonly: 'inEditMode', deps: ['name', 'typname'], - control: 'select2', options: function(control) { - var name = control.model.get('name'), - result = []; - - if(!_.isUndefined(name) && name != '') - { - var node = control.field.get('schema_node'), - _url = node.generate_url.apply( - node, [ - null, 'get_canonical', control.field.get('node_data'), false, - control.field.get('node_info'), - ]); - $.ajax({ - async: false, - url: _url, - cache: false, - data: {'name' : name}, - }) - .done(function(res) { - result = res.data; - }) - .fail(function() { - control.model.trigger('pgadmin:view:fetch:error', control.model, control.field); - }); - } - return result; - }, - },{ - id: 'rngsubdiff', label: gettext('Subtype diff function'), cell: 'string', - type: 'text', mode: ['properties', 'create', 'edit'], - group: gettext('Range Type'), - disabled: 'inSchema', readonly: 'inEditMode', deps: ['typname', 'opcname'], - control: 'select2', options: function(control) { - var l_typname = control.model.get('typname'), - l_opcname = control.model.get('opcname'), - result = []; - this.options = []; - control.model.set({'rngsubdiff': [], silent: true}); - - if(!_.isUndefined(l_typname) && l_typname != '' && - !_.isUndefined(l_opcname) && l_opcname != '') { - var node = control.field.get('schema_node'), - _url = node.generate_url.apply( - node, [ - null, 'get_stypediff', - control.field.get('node_data'), false, - control.field.get('node_info'), - ]); - $.ajax({ - async: false, - url: _url, - cache: false, - data: {'typname' : l_typname, 'opcname': l_opcname}, - }) - .done(function(res) { - result = res.data; - }) - .fail(function() { - control.model.trigger('pgadmin:view:fetch:error', control.model, control.field); - }); - } - return result; - }, - }], - },{ - type: 'nested', control: 'tab', group: gettext('Definition'), - label: gettext('External Type'), deps: ['typtype'], - mode: ['create', 'edit'], tabPanelExtraClasses:'inline-tab-panel-padded', - visible: function(m) { - return m.get('typtype') === 'b'; - }, - schema:[{ - id: 'spacer_ctrl', group: gettext('Required'), mode: ['edit', 'create'], type: 'spacer', - },{ - id: 'typinput', label: gettext('Input function'), - cell: 'string',type: 'text', - mode: ['properties', 'create', 'edit'], group: gettext('Required'), - disabled: 'inSchema', readonly: 'inEditMode', - control: 'node-ajax-options', url: 'get_external_functions', - transform: 'external_func_combo', - select2: { allowClear: true, placeholder: '', width: '100%' }, - },{ - id: 'typoutput', label: gettext('Output function'), - cell: 'string', - type: 'text', mode: ['properties', 'create', 'edit'], - group: gettext('Required'), - disabled: 'inSchema', readonly: 'inEditMode', - control: 'node-ajax-options', url: 'get_external_functions', - transform: 'external_func_combo', - select2: { allowClear: true, placeholder: '', width: '100%' }, - },{ - id: 'spacer_ctrl_optional_1', group: gettext('Optional-1'), mode: ['edit', 'create'], type: 'spacer', - },{ - id: 'typreceive', label: gettext('Receive function'), - cell: 'string', type: 'text', group: gettext('Optional-1'), - mode: ['properties', 'create', 'edit'], - disabled: 'inSchema', readonly: 'inEditMode', - control: 'node-ajax-options', url: 'get_external_functions', - transform: 'external_func_combo', - select2: { allowClear: true, placeholder: '', width: '100%' }, - },{ - id: 'typsend', label: gettext('Send function'), - cell: 'string', group: gettext('Optional-1'), - type: 'text', mode: ['properties', 'create', 'edit'], - disabled: 'inSchema', readonly: 'inEditMode', - control: 'node-ajax-options', url: 'get_external_functions', - transform: 'external_func_combo', - select2: { allowClear: true, placeholder: '', width: '100%' }, - },{ - id: 'typmodin', label: gettext('Typmod in function'), - cell: 'string', type: 'text', - mode: ['properties', 'create', 'edit'], group: gettext('Optional-1'), - disabled: 'inSchema', readonly: 'inEditMode', - control: 'node-ajax-options', url: 'get_external_functions', - select2: { allowClear: true, placeholder: '', width: '100%' }, - transform: function(d) { - var result = [{label :'', value : ''}]; - _.each(d, function(item) { - // if type from selected from combobox matches in options - if ( item.cbtype === 'typmodin' || item.cbtype === 'all') { - result.push(item); - } - }); - return result; - }, - },{ - id: 'typmodout', label: gettext('Typmod out function'), - cell: 'string', group: gettext('Optional-1'), - type: 'text', mode: ['properties', 'create', 'edit'], - disabled: 'inSchema', readonly: 'inEditMode', - control: 'node-ajax-options', url: 'get_external_functions', - select2: { allowClear: true, placeholder: '', width: '100%' }, - transform: function(d) { - var result = [{label :'', value : ''}]; - _.each(d, function(item) { - // if type from selected from combobox matches in options - if ( item.cbtype === 'typmodout' || item.cbtype === 'all') { - result.push(item); - } - }); - return result; - }, - },{ - id: 'typlen', label: gettext('Internal length'), - cell: 'integer', group: gettext('Optional-1'), - type: 'int', mode: ['properties', 'create', 'edit'], - disabled: 'inSchema', readonly: 'inEditMode', - },{ - id: 'variable', label: gettext('Variable?'), cell: 'switch', - group: gettext('Optional-1'), type: 'switch', - mode: ['create','edit'], - disabled: 'inSchema', readonly: 'inEditMode', - },{ - id: 'typdefault', label: gettext('Default?'), - cell: 'string', group: gettext('Optional-1'), - type: 'text', mode: ['properties', 'create','edit'], - disabled: 'inSchema', readonly: 'inEditMode', - },{ - id: 'typanalyze', label: gettext('Analyze function'), - cell: 'string', group: gettext('Optional-1'), - type: 'text', mode: ['properties', 'create','edit'], - disabled: 'inSchema', readonly: 'inEditMode', - control: 'node-ajax-options', url: 'get_external_functions', - transform: 'external_func_combo', - select2: { allowClear: true, placeholder: '', width: '100%' }, - },{ - id: 'typcategory', label: gettext('Category type'), - cell: 'string', group: gettext('Optional-1'), - type: 'text', mode: ['properties', 'create','edit'], - disabled: 'inSchema', readonly: 'inEditMode', control: 'select2', - select2: { allowClear: true, placeholder: '', width: '100%' }, - options: [ - {label :'', value : ''}, - {label :'Array types', value : 'A'}, - {label :'Boolean types', value : 'B'}, - {label :'Composite types', value : 'C'}, - {label :'Date/time types', value : 'D'}, - {label :'Enum types', value : 'E'}, - {label :'Geometric types', value : 'G'}, - {label :'Network address types', value : 'I'}, - {label :'Numeric types', value : 'N'}, - {label :'Pseudo-types', value : 'P'}, - {label :'String types', value : 'S'}, - {label :'Timespan types', value : 'T'}, - {label :'User-defined types', value : 'U'}, - {label :'Bit-string types', value : 'V'}, - {label :'unknown type', value : 'X'}, - ], - },{ - id: 'typispreferred', label: gettext('Preferred?'), cell: 'switch', - type: 'switch', mode: ['properties', 'create','edit'], - disabled: 'inSchema', readonly: 'inEditMode', - group: gettext('Optional-1'), - },{ - id: 'spacer_ctrl_optional_2', group: gettext('Optional-2'), mode: ['edit', 'create'], type: 'spacer', - },{ - id: 'element', label: gettext('Element type'), cell: 'string', - control: 'node-ajax-options', group: gettext('Optional-2'), - type: 'text', mode: ['properties', 'create', 'edit'], - disabled: 'inSchema', readonly: 'inEditMode', url: 'get_types', - },{ - id: 'typdelim', label: gettext('Delimiter'), cell: 'string', - type: 'text', mode: ['properties', 'create', 'edit'], - group: gettext('Optional-2'), disabled: 'inSchema', - readonly: 'inEditMode', - },{ - id: 'typalign', label: gettext('Alignment type'), - cell: 'string', group: gettext('Optional-2'), - type: 'text', mode: ['properties', 'create', 'edit'], - disabled: 'inSchema', readonly: 'inEditMode', control: 'select2', - select2: { allowClear: true, placeholder: '', width: '100%' }, - options: [ - {label :'', value : ''}, - {label: 'char', value: 'c'}, - {label: 'int2', value: 's'}, - {label: 'int4', value: 'i'}, - {label: 'double', value: 'd'}, - ], - },{ - id: 'typstorage', label: gettext('Storage type'), - type: 'text', mode: ['properties', 'create', 'edit'], - group: gettext('Optional-2'), cell: 'string', - disabled: 'inSchema', readonly: 'inEditMode', control: 'select2', - select2: { allowClear: true, placeholder: '', width: '100%' }, - options: [ - {label :'', value : ''}, - {label: 'PLAIN', value: 'p'}, - {label: 'EXTERNAL', value: 'e'}, - {label: 'MAIN', value: 'm'}, - {label: 'EXTENDED', value: 'x'}, - ], - },{ - id: 'typbyval', label: gettext('Passed by value?'), - cell: 'switch', - type: 'switch', mode: ['properties', 'create', 'edit'], - disabled: 'inSchema', readonly: 'inEditMode', group: gettext('Optional-2'), - },{ - id: 'is_collatable', label: gettext('Collatable?'), - cell: 'switch', min_version: 90100, group: gettext('Optional-2'), - type: 'switch', mode: ['properties', 'create', 'edit'], - disabled: 'inSchema', readonly: 'inEditMode', - // End of extension tab - }], - },{ - id: 'alias', label: gettext('Alias'), cell: 'string', - type: 'text', mode: ['properties'], - disabled: 'inSchema', - }, pgBrowser.SecurityGroupSchema,{ - id: 'type_acl', label: gettext('Privileges'), cell: 'string', - type: 'text', mode: ['properties'], group: 'security', - disabled: 'inSchema', - },{ - id: 'member_list', label: gettext('Members'), cell: 'string', - type: 'text', mode: ['properties'], group: gettext('Definition'), - disabled: 'inSchema', visible: function(m) { - if(m.get('typtype') === 'c') { - return true; - } - return false; - }, - },{ - id: 'enum_list', label: gettext('Labels'), cell: 'string', - type: 'text', mode: ['properties'], group: gettext('Definition'), - disabled: 'inSchema', visible: function(m) { - if(m.get('typtype') === 'e') { - return true; - } - return false; - }, - },{ - id: 'is_sys_type', label: gettext('System type?'), cell: 'switch', - type: 'switch', mode: ['properties'], - disabled: 'inSchema', - },{ + }, { id: 'description', label: gettext('Comment'), cell: 'string', type: 'multiline', mode: ['properties', 'create', 'edit'], disabled: 'inSchema', - },{ - id: 'typacl', label: gettext('Privileges'), type: 'collection', - group: 'security', control: 'unique-col-collection', - model: pgBrowser.Node.PrivilegeRoleModel.extend({ - privileges: ['U'], - }), - mode: ['edit', 'create'], canDelete: true, - uniqueCol : ['grantee'], deps: ['typtype'], - canAdd: function(m) { - // Do not allow to add when shell type is selected - // Clear acl & security label collections as well - if (m.get('typtype') === 'p') { - var acl = m.get('typacl'); - if(acl.length > 0) - acl.reset(); - } - return (m.get('typtype') !== 'p'); - }, - },{ - id: 'seclabels', label: gettext('Security labels'), - model: pgBrowser.SecLabelModel, editable: false, type: 'collection', - group: 'security', mode: ['edit', 'create'], - min_version: 90100, canEdit: false, canDelete: true, - control: 'unique-col-collection', deps: ['typtype'], - canAdd: function(m) { - // Do not allow to add when shell type is selected - // Clear acl & security label collections as well - if (m.get('typtype') === 'p') { - var secLabs = m.get('seclabels'); - if(secLabs.length > 0) - secLabs.reset(); - } - return (m.get('typtype') !== 'p'); - }, - }], - validate: function() { - // Validation code for required fields - var msg; - - this.errorModel.clear(); - - if ( - _.isUndefined(this.get('name')) || - _.isNull(this.get('name')) || - String(this.get('name')).replace(/^\s+|\s+$/g, '') == '' - ) { - msg = gettext('Name cannot be empty.'); - this.errorModel.set('name', msg); - return msg; - } - - if ( - _.isUndefined(this.get('schema')) || - _.isNull(this.get('schema')) || - String(this.get('schema')).replace(/^\s+|\s+$/g, '') == '' - ) { - msg = gettext('Schema cannot be empty.'); - this.errorModel.set('schema', msg); - return msg; - } - - if ( - _.isUndefined(this.get('typtype')) || - _.isNull(this.get('typtype')) || - String(this.get('typtype')).replace(/^\s+|\s+$/g, '') == '' - ) { - msg = gettext('Type cannot be empty.'); - this.errorModel.set('typtype', msg); - return msg; - } - - // For Range - if(this.get('typtype') == 'r') { - if ( - _.isUndefined(this.get('typname')) || - _.isNull(this.get('typname')) || - String(this.get('typname')).replace(/^\s+|\s+$/g, '') == '' - ) { - msg = gettext('Subtype name cannot be empty.'); - this.errorModel.set('typname', msg); - return msg; - } - } - - // For External - if(this.get('typtype') == 'b') { - if ( - _.isUndefined(this.get('typinput')) || - _.isNull(this.get('typinput')) || - String(this.get('typinput')).replace(/^\s+|\s+$/g, '') == '' - ) { - msg = gettext('Input function cannot be empty.'); - this.errorModel.set('typinput', msg); - return msg; - } - if ( - _.isUndefined(this.get('typoutput')) || - _.isNull(this.get('typoutput')) || - String(this.get('typoutput')).replace(/^\s+|\s+$/g, '') == '' - ) { - msg = gettext('Output function cannot be empty.'); - this.errorModel.set('typoutput', msg); - return msg; - } - } - - // For Nested Table and Varying Array - if(this.get('typtype') == 'N' || this.get('typtype') == 'V') { - if (_.isUndefined(this.get('type')) || _.isNull(this.get('type')) || - String(this.get('type')).replace(/^\s+|\s+$/g, '') == '') { - msg = gettext('Data type cannot be empty.'); - this.errorModel.set('type', msg); - return msg; - } - } - - return null; - }, - // We will disable everything if we are under catalog node - inSchema: function() { - if(this.node_info && 'catalog' in this.node_info) - { - return true; - } - return false; - }, - inEditMode: function(m) { - return !m.isNew(); - }, - schemaCheck: function(m) { - if(this.node_info && 'schema' in this.node_info) - { - if (m.isNew()) { - return false; - } else { - return m.get('typtype') === 'p'; - } - } - return true; - }, - // Function will help us to fill combobox - external_func_combo: function(d) { - var result = []; - _.each(d, function(item) { - // if type from selected from combobox matches in options - if ( item.cbtype == 'all' ) { - result.push(item); - } - }); - return result; - }, + }] }), + getSchema: (treeNodeInfo, itemNodeData) => { + let nodeObj = pgAdmin.Browser.Nodes['type']; + return new TypeSchema( + (privileges)=>getNodePrivilegeRoleSchema(nodeObj, treeNodeInfo, itemNodeData, privileges), + ()=>getCompositeSchema(nodeObj, treeNodeInfo, itemNodeData), + ()=>getRangeSchema(nodeObj, treeNodeInfo, itemNodeData), + ()=>getExternalSchema(nodeObj, treeNodeInfo, itemNodeData), + ()=>getDataTypeSchema(nodeObj, treeNodeInfo, itemNodeData), + { + roles:() => getNodeListByName('role', treeNodeInfo, itemNodeData, { + cacheLevel: 'database' + }), + schemas:() => getNodeListByName('schema', treeNodeInfo, itemNodeData, { + cacheLevel: 'database' + }), + server_info: pgBrowser.serverInfo[treeNodeInfo.server._id], + node_info: treeNodeInfo + }, + { + typeowner: pgBrowser.serverInfo[treeNodeInfo.server._id].user.name, + schema: 'catalog' in treeNodeInfo ? treeNodeInfo.catalog.label : treeNodeInfo.schema.label, + // If create mode then by default open composite type + typtype: 'c' + } + ); + } }); } return pgBrowser.Nodes['type']; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/static/js/type.ui.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/static/js/type.ui.js new file mode 100644 index 000000000..50c6d0a5f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/static/js/type.ui.js @@ -0,0 +1,1640 @@ +///////////////////////////////////////////////////////////// +// +// 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 SecLabelSchema from '../../../../../static/js/sec_label.ui'; + +import { getNodeAjaxOptions } from '../../../../../../../static/js/node_ajax'; +import _ from 'underscore'; +import getApiInstance from 'sources/api_instance'; +import { isEmptyString, integerValidator } from 'sources/validators'; + + +function getCompositeSchema(nodeObj, treeNodeInfo, itemNodeData) { + return new CompositeSchema( + { + types: () => getNodeAjaxOptions('get_types', nodeObj, treeNodeInfo, itemNodeData, { + cacheLevel: 'domain' + }), + collations: () => getNodeAjaxOptions('get_collations', nodeObj, treeNodeInfo, itemNodeData) + } + ); +} + +function getRangeSchema(nodeObj, treeNodeInfo, itemNodeData) { + return new RangeSchema( + { + typnameList: () => getNodeAjaxOptions('get_stypes', nodeObj, treeNodeInfo, itemNodeData), + getSubOpClass: (typname) => { + return new Promise((resolve, reject)=>{ + const api = getApiInstance(); + + var _url = nodeObj.generate_url.apply( + nodeObj, [ + null, 'get_subopclass', itemNodeData, false, + treeNodeInfo, + ]); + var data; + + if(!_.isUndefined(typname) && typname != ''){ + api.get(_url, { + params: { + 'typname' : typname + } + }).then(res=>{ + data = res.data.data; + resolve(data); + }).catch((err)=>{ + reject(err); + }); + } + }); + }, + collationsList: () => getNodeAjaxOptions('get_collations', nodeObj, treeNodeInfo, itemNodeData), + getCanonicalFunctions: (name) => { + return new Promise((resolve, reject)=>{ + const api = getApiInstance(); + + var _url = nodeObj.generate_url.apply( + nodeObj, [ + null, 'get_canonical', itemNodeData, false, + treeNodeInfo, + ]); + var data; + + if(!_.isUndefined(name) && name != ''){ + api.get(_url, { + params: { + 'name' : name + } + }).then(res=>{ + data = res.data.data; + resolve(data); + }).catch((err)=>{ + reject(err); + }); + } + }); + }, + getSubDiffFunctions: (typname, opcname) => { + return new Promise((resolve, reject)=>{ + const api = getApiInstance(); + + var _url = nodeObj.generate_url.apply( + nodeObj, [ + null, 'get_stypediff', itemNodeData, false, + treeNodeInfo, + ]); + var data; + + if(!_.isUndefined(typname) && typname != '' && + !_.isUndefined(opcname) && opcname != ''){ + api.get(_url, { + params: { + 'typname' : typname, + 'opcname': opcname + } + }).then(res=>{ + data = res.data.data; + resolve(data); + }).catch((err)=>{ + reject(err); + }); + } + }); + }, + }, { + node_info: treeNodeInfo + } + ); +} + +function getExternalSchema(nodeObj, treeNodeInfo, itemNodeData) { + return new ExternalSchema( + { + externalFunctionsList: () => getNodeAjaxOptions('get_external_functions', nodeObj, treeNodeInfo, itemNodeData), + types: () => getNodeAjaxOptions('get_types', nodeObj, treeNodeInfo, itemNodeData, { + cacheLevel: 'domain' + }) + }, { + node_info: treeNodeInfo + } + ); +} + +function getDataTypeSchema(nodeObj, treeNodeInfo, itemNodeData) { + return new DataTypeSchema( + { + types: () => getNodeAjaxOptions('get_types', nodeObj, treeNodeInfo, itemNodeData, { + cacheLevel: 'domain' + }) + } + ); +} + +class EnumerationSchema extends BaseUISchema { + + constructor() { + super({ + oid: undefined, + label: undefined, + }); + } + + get idAttribute() { + return 'oid'; + } + + get baseFields() { + var obj = this; + return [ + { + id: 'label', label: gettext('Label'), + type: 'text', cell: 'text', + cellHeaderClasses: 'width_percent_99', + readonly: function (state) { + return !obj.isNew(state); + }, + } + ]; + } +} + +class RangeSchema extends BaseUISchema { + constructor(fieldOptions = {}, node_info, initValues) { + super({ + typname: null, + oid: undefined, + is_sys_type: false, + ...initValues + }); + this.fieldOptions = { + typnameList: [], + collationsList: [], + ...fieldOptions + }; + this.node_info = { + ...node_info.node_info + }; + } + + get idAttribute() { + return 'oid'; + } + + get baseFields() { + var obj = this; + return [{ + // We will disable range type control in edit mode + id: 'typname', label: gettext('Subtype'), + type: (state) => { + return { + type: 'select', + options: obj.fieldOptions.typnameList, + optionsLoaded: (options) => { obj.fieldOptions.typnameList = options; }, + controlProps: { + allowClear: true, + filter: (options) => { + let res = []; + if (state && obj.isNew(state)) { + if(options && options.length > 0) { + state.subtypes = options; + } + options.forEach((option) => { + if(option && option.label == '') { + return; + } + res.push({ label: option.label, value: option.value }); + }); + } else { + res = options; + } + return res; + } + } + }; + }, + controlProps: { allowClear: true, placeholder: '', width: '100%' }, + mode: ['properties', 'create', 'edit'], + group: gettext('Range Type'), + disabled: () => inSchema(obj.node_info), + readonly: function (state) { + return !obj.isNew(state); + }, + }, { + id: 'opcname', label: gettext('Subtype operator class'), cell: 'string', + mode: ['properties', 'create', 'edit'], group: gettext('Range Type'), + disabled: () => inSchema(obj.node_info), + readonly: function(state) { + return !obj.isNew(state); + }, + deps: ['typname'], + type: (state)=>{ + return { + type: 'select', + options: () => obj.fieldOptions.getSubOpClass(state.typname), + optionsReloadBasis: state.typname, + controlProps: { + allowClear: true, + filter: (options) => { + let res = []; + if (state) { + options.forEach((option) => { + if(option && option.label == '') { + return; + } + res.push({ label: option.label, value: option.value }); + }); + } else { + res = options; + } + return res; + } + } + }; + }, + }, { + id: 'collname', label: gettext('Collation'), cell: 'string', + type: (state) => { + return { + type: 'select', + options: obj.fieldOptions.collationsList, + optionsLoaded: (options) => { obj.fieldOptions.collationsList = options; }, + controlProps: { + allowClear: true, + placeholder: '', + width: '100%', + filter: (options) => { + let res = []; + if (state && obj.isNew(state)) { + options.forEach((option) => { + if(option && option.label == '') { + return; + } + res.push({ label: option.label, value: option.value }); + }); + } else { + res = options; + } + return res; + } + } + }; + }, + mode: ['properties', 'create', 'edit'], + group: gettext('Range Type'), + deps: ['typname'], + controlProps: { allowClear: true, placeholder: '', width: '100%' }, + disabled: (state) => { + let disableCollNameControl = inSchema(obj.node_info); + if (disableCollNameControl) + return disableCollNameControl; + + // To check if collation is allowed? + var of_subtype = state.typname; + if(!_.isUndefined(of_subtype)) { + // iterating over all the types + _.each(state.subtypes, function(s) { + // if subtype from selected from combobox matches + if ( of_subtype === s.label ) { + // if collation is allowed for selected subtype + // then enable it else disable it + disableCollNameControl = s.is_collate; + } + }); + } + // If is_collate is true then do not disable + if(!disableCollNameControl) { + state.collname = ''; + this.options = []; + } + + return disableCollNameControl ? false : true; + }, + readonly: function(state) { + return !obj.isNew(state); + } + }, { + id: 'rngcanonical', label: gettext('Canonical function'), cell: 'string', + type: (state)=>{ + return { + type: 'select', + options: () => obj.fieldOptions.getCanonicalFunctions(state.name), + optionsReloadBasis: state.typname, + controlProps: { + allowClear: true, + filter: (options) => { + let res = []; + if (state) { + options.forEach((option) => { + if(option && option.label == '') { + return; + } + res.push({ label: option.label, value: option.value }); + }); + } else { + res = options; + } + return res; + } + } + }; + }, + mode: ['properties', 'create', 'edit'], + group: gettext('Range Type'), + disabled: () => inSchema(obj.node_info), + readonly: function(state) { + return !obj.isNew(state); + }, + deps: ['name', 'typname'], + }, { + id: 'rngsubdiff', label: gettext('Subtype diff function'), cell: 'string', + mode: ['properties', 'create', 'edit'], + group: gettext('Range Type'), + disabled: () => inSchema(obj.node_info), + readonly: function(state) { + return !obj.isNew(state); + }, + deps: ['typname', 'opcname'], + type: (state)=>{ + let fetchOptionsBasis = state.typname + state.opcname; + return { + type: 'select', + options: () => obj.fieldOptions.getSubDiffFunctions(state.typname, state.opcname), + optionsReloadBasis: fetchOptionsBasis, + controlProps: { + allowClear: true, + filter: (options) => { + let res = []; + if (state) { + options.forEach((option) => { + if(option && option.label == '') { + return; + } + res.push({ label: option.label, value: option.value }); + }); + } else { + res = options; + } + return res; + } + } + }; + }, + } + ]; + } + + validate(state, setError) { + + let errmsg = null; + + if(state.typtype === 'r') { + if (isEmptyString(state.typname)) { + errmsg = gettext('Subtype cannot be empty'); + setError('typname', errmsg); + return true; + } + } + } +} + +class ExternalSchema extends BaseUISchema { + constructor(fieldOptions = {}, node_info, initValues) { + super({ + name: null, + typinput: undefined, + oid: undefined, + is_sys_type: false, + typtype: undefined, + ...initValues + }); + this.fieldOptions = { + types: [], + externalFunctionsList: [], + ...fieldOptions + }; + this.fieldOptions.typeCategory = [ + {label :'Array types', value : 'A'}, + {label :'Boolean types', value : 'B'}, + {label :'Composite types', value : 'C'}, + {label :'Date/time types', value : 'D'}, + {label :'Enum types', value : 'E'}, + {label :'Geometric types', value : 'G'}, + {label :'Network address types', value : 'I'}, + {label :'Numeric types', value : 'N'}, + {label :'Pseudo-types', value : 'P'}, + {label :'String types', value : 'S'}, + {label :'Timespan types', value : 'T'}, + {label :'User-defined types', value : 'U'}, + {label :'Bit-string types', value : 'V'}, + {label :'unknown type', value : 'X'}, + ]; + this.fieldOptions.typeAlignOptions = [ + {label: 'char', value: 'c'}, + {label: 'int2', value: 's'}, + {label: 'int4', value: 'i'}, + {label: 'double', value: 'd'}, + ]; + this.fieldOptions.typStorageOptions = [ + {label: 'PLAIN', value: 'p'}, + {label: 'EXTERNAL', value: 'e'}, + {label: 'MAIN', value: 'm'}, + {label: 'EXTENDED', value: 'x'}, + ]; + this.node_info = { + ...node_info.node_info + }; + } + + get idAttribute() { + return 'oid'; + } + + // Function will help us to fill combobox + external_func_combo(control) { + var result = []; + _.each(control, function(item) { + + if(item && item.label == '') { + return; + } + // if type from selected from combobox matches in options + if ( item.cbtype == 'all' ) { + result.push(item); + } + }); + return result; + } + + get baseFields() { + var obj = this; + return [{ + id: 'spacer_ctrl', group: gettext('Required'), mode: ['edit', 'create'], type: 'spacer', + },{ + id: 'typinput', label: gettext('Input function'), + mode: ['properties', 'create', 'edit'], group: gettext('Required'), + disabled: () => inSchema(obj.node_info), + readonly: function (state) { + return !obj.isNew(state); + }, + type: (state) => { + return { + type: 'select', + options: obj.fieldOptions.externalFunctionsList, + optionsLoaded: (options) => { obj.fieldOptions.externalFunctionsList = options; }, + controlProps: { + allowClear: true, + placeholder: '', + width: '100%', + filter: (options) => { + let res = []; + if (state && obj.isNew(state)) { + res = obj.external_func_combo(options); + } else { + res = options; + } + return res; + } + } + }; + }, + }, { + id: 'typoutput', label: gettext('Output function'), + mode: ['properties', 'create', 'edit'], + group: gettext('Required'), + type: (state) => { + return { + type: 'select', + options: obj.fieldOptions.externalFunctionsList, + optionsLoaded: (options) => { obj.fieldOptions.externalFunctionsList = options; }, + controlProps: { + allowClear: true, + placeholder: '', + width: '100%', + filter: (options) => { + let res = []; + if (state && obj.isNew(state)) { + res = obj.external_func_combo(options); + } else { + res = options; + } + return res; + } + } + }; + }, + readonly: function (state) { + return !obj.isNew(state); + }, + disabled: () => inSchema(obj.node_info), + controlProps: { allowClear: true, placeholder: '', width: '100%' }, + },{ + id: 'spacer_ctrl_optional_1', group: gettext('Optional-1'), mode: ['edit', 'create'], type: 'spacer', + },{ + id: 'typreceive', label: gettext('Receive function'), + type: (state) => { + return { + type: 'select', + options: obj.fieldOptions.externalFunctionsList, + optionsLoaded: (options) => { obj.fieldOptions.externalFunctionsList = options; }, + controlProps: { + allowClear: true, + placeholder: '', + width: '100%', + filter: (options) => { + let res = []; + if (state && obj.isNew(state)) { + res = obj.external_func_combo(options); + } else { + res = options; + } + return res; + } + } + }; + }, + group: gettext('Optional-1'), + mode: ['properties', 'create', 'edit'], + disabled: () => inSchema(obj.node_info), + readonly: function (state) { + return !obj.isNew(state); + }, + controlProps: { allowClear: true, placeholder: '', width: '100%' }, + },{ + id: 'typsend', label: gettext('Send function'), + group: gettext('Optional-1'), + type: (state) => { + return { + type: 'select', + options: obj.fieldOptions.externalFunctionsList, + optionsLoaded: (options) => { obj.fieldOptions.externalFunctionsList = options; }, + controlProps: { + allowClear: true, + placeholder: '', + width: '100%', + filter: (options) => { + let res = []; + if (state && obj.isNew(state)) { + res = obj.external_func_combo(options); + } else { + res = options; + } + return res; + } + } + }; + }, + mode: ['properties', 'create', 'edit'], + disabled: () => inSchema(obj.node_info), + readonly: function (state) { + return !obj.isNew(state); + }, + controlProps: { allowClear: true, placeholder: '', width: '100%' }, + },{ + id: 'typmodin', label: gettext('Typmod in function'), + type: (state) => { + return { + type: 'select', + options: obj.fieldOptions.externalFunctionsList, + optionsLoaded: (options) => { obj.fieldOptions.externalFunctionsList = options; }, + controlProps: { + allowClear: true, + placeholder: '', + width: '100%', + filter: (options) => { + let res = []; + if (state && obj.isNew(state)) { + _.each(options, function(item) { + if(item && item.label == '') { + return; + } + // if type from selected from combobox matches in options + if ( item.cbtype === 'typmodin' || item.cbtype === 'all') { + res.push(item); + } + }); + } else { + res = options; + } + return res; + } + } + }; + }, + mode: ['properties', 'create', 'edit'], group: gettext('Optional-1'), + disabled: () => inSchema(obj.node_info), + readonly: function (state) { + return !obj.isNew(state); + }, + controlProps: { allowClear: true, placeholder: '', width: '100%' }, + },{ + id: 'typmodout', label: gettext('Typmod out function'), + group: gettext('Optional-1'), + type: (state) => { + return { + type: 'select', + options: obj.fieldOptions.externalFunctionsList, + optionsLoaded: (options) => { obj.fieldOptions.externalFunctionsList = options; }, + controlProps: { + allowClear: true, + placeholder: '', + width: '100%', + filter: (options) => { + let res = []; + if (state && obj.isNew(state)) { + _.each(options, function(item) { + if(item && item.label == '') { + return; + } + // if type from selected from combobox matches in options + if ( item.cbtype === 'typmodout' || item.cbtype === 'all') { + res.push(item); + } + }); + } else { + res = options; + } + return res; + } + } + }; + }, + mode: ['properties', 'create', 'edit'], + disabled: () => inSchema(obj.node_info), + readonly: function (state) { + return !obj.isNew(state); + }, + },{ + id: 'typlen', label: gettext('Internal length'), + cell: 'integer', group: gettext('Optional-1'), + type: 'int', mode: ['properties', 'create', 'edit'], + disabled: () => inSchema(obj.node_info), + readonly: function (state) { + return !obj.isNew(state); + }, + },{ + id: 'variable', label: gettext('Variable?'), cell: 'switch', + group: gettext('Optional-1'), type: 'switch', + mode: ['create','edit'], + disabled: () => inSchema(obj.node_info), + readonly: function (state) { + return !obj.isNew(state); + }, + },{ + id: 'typdefault', label: gettext('Default?'), + cell: 'string', group: gettext('Optional-1'), + type: 'text', mode: ['properties', 'create','edit'], + disabled: () => inSchema(obj.node_info), + readonly: function (state) { + return !obj.isNew(state); + }, + },{ + id: 'typanalyze', label: gettext('Analyze function'), + group: gettext('Optional-1'), + type: (state) => { + return { + type: 'select', + options: obj.fieldOptions.externalFunctionsList, + optionsLoaded: (options) => { obj.fieldOptions.externalFunctionsList = options; }, + controlProps: { + allowClear: true, + placeholder: '', + width: '100%', + filter: (options) => { + let res = []; + if (state && obj.isNew(state)) { + res = obj.external_func_combo(options); + } else { + res = options; + } + return res; + } + } + }; + }, + mode: ['properties', 'create','edit'], + disabled: () => inSchema(obj.node_info), + readonly: function (state) { + return !obj.isNew(state); + }, + controlProps: { allowClear: true, placeholder: '', width: '100%' }, + },{ + id: 'typcategory', label: gettext('Category type'), + cell: 'string', + group: gettext('Optional-1'), + type: () => { + return { + type: 'select', + options: obj.fieldOptions.typeCategory, + optionsLoaded: (options) => { obj.fieldOptions.typeCategory = options; }, + controlProps: { + allowClear: true, + placeholder: '', + width: '100%', + } + }; + }, + mode: ['properties', 'create','edit'], + disabled: () => inSchema(obj.node_info), + readonly: function (state) { + return !obj.isNew(state); + }, + controlProps: { allowClear: true, placeholder: '', width: '100%' }, + },{ + id: 'typispreferred', label: gettext('Preferred?'), + type: 'switch', mode: ['properties', 'create','edit'], + disabled: () => inSchema(obj.node_info), + readonly: function (state) { + return !obj.isNew(state); + }, + group: gettext('Optional-1'), + },{ + id: 'spacer_ctrl_optional_2', group: gettext('Optional-2'), mode: ['edit', 'create'], type: 'spacer', + },{ + id: 'element', label: gettext('Element type'), + group: gettext('Optional-2'), + type: (state) => { + return { + type: 'select', + options: obj.fieldOptions.types, + controlProps: { + allowClear: true, + placeholder: '', + width: '100%', + filter: (options) => { + let res = []; + if (state && obj.isNew(state)) { + _.each(options, function(item) { + if(item && item.label == '') { + return; + } + // if type from selected from combobox matches in options + res.push(item); + }); + } else { + res = options; + } + return res; + } + } + }; + }, + mode: ['properties', 'create', 'edit'], + disabled: () => inSchema(obj.node_info), + readonly: function (state) { + return !obj.isNew(state); + }, + },{ + id: 'typdelim', label: gettext('Delimiter'), + cell: 'string', + type: 'text', + mode: ['properties', 'create', 'edit'], + group: gettext('Optional-2'), + disabled: () => inSchema(obj.node_info), + readonly: function (state) { + return !obj.isNew(state); + }, + },{ + id: 'typalign', label: gettext('Alignment type'), + cell: 'string', group: gettext('Optional-2'), + type: 'select', + options: obj.fieldOptions.typeAlignOptions, + mode: ['properties', 'create', 'edit'], + disabled: () => inSchema(obj.node_info), + readonly: function (state) { + return !obj.isNew(state); + }, + controlProps: { allowClear: true, placeholder: '', width: '100%' }, + },{ + id: 'typstorage', label: gettext('Storage type'), + type: 'select', mode: ['properties', 'create', 'edit'], + group: gettext('Optional-2'), cell: 'string', + disabled: () => inSchema(obj.node_info), + readonly: function (state) { + return !obj.isNew(state); + }, + controlProps: { allowClear: true, placeholder: '', width: '100%' }, + options: obj.fieldOptions.typStorageOptions, + },{ + id: 'typbyval', label: gettext('Passed by value?'), + cell: 'switch', + type: 'switch', mode: ['properties', 'create', 'edit'], + disabled: () => inSchema(obj.node_info), + readonly: function (state) { + return !obj.isNew(state); + }, + group: gettext('Optional-2'), + },{ + id: 'is_collatable', label: gettext('Collatable?'), + cell: 'switch', min_version: 90100, group: gettext('Optional-2'), + type: 'switch', mode: ['properties', 'create', 'edit'], + disabled: () => inSchema(obj.node_info), + readonly: function (state) { + return !obj.isNew(state); + }, + // End of extension tab + }]; + } + + validate(state, setError) { + + let errmsg = null; + + if(state.typtype === 'b') { + + if (isEmptyString(state.typinput)) { + errmsg = gettext('Input function cannot be empty'); + setError('typinput', errmsg); + return true; + } + + if (isEmptyString(state.typoutput)) { + errmsg = gettext('Output function cannot be empty'); + setError('typoutput', errmsg); + return true; + } + } + return false; + } +} + +class CompositeSchema extends BaseUISchema { + constructor(fieldOptions = {}, initValues) { + super({ + oid: undefined, + is_sys_type: false, + attnum: undefined, + member_name: undefined, + type: undefined, + tlength: null, + is_tlength: false, + precision: undefined, + is_precision: false, + collation: undefined, + min_val: undefined, + max_val: undefined, + ...initValues + }); + this.fieldOptions = { + types: [], + collations: [], + ...fieldOptions + }; + this.type_options = {}; + } + + setTypeOptions(options) { + options.forEach((option)=>{ + this.type_options[option.value] = { + ...option, + }; + }); + } + + get idAttribute() { + return 'oid'; + } + + get baseFields() { + var obj = this; + return [{ + id: 'member_name', label: gettext('Member Name'), + type: 'text', cell: 'text' + }, + { + id: 'type', label: gettext('Type'), + type: 'text', + cell: ()=>({ + cell: 'select', options: obj.fieldOptions.types, + optionsLoaded: (options)=>{obj.setTypeOptions(options);}, + controlProps: { + allowClear: false, + } + }), + }, { + // Note: There are ambiguities in the PG catalogs and docs between + // precision and scale. In the UI, we try to follow the docs as + // closely as possible, therefore we use Length/Precision and Scale + id: 'tlength', label: gettext('Length/Precision'), deps: ['type'], type: 'text', + disabled: false, + cell: 'int', + depChange: (state, changeSource)=>{ + if(_.isArray(changeSource) && changeSource[2] == 'type') { + state.tlength = null; + return {...state + , value: null + }; + } + }, + editable: (state) => { + // We will store type from selected from combobox + var of_type = state.type; + if(obj.type_options) { + // iterating over all the types + _.each(obj.type_options, function(o) { + // if type from selected from combobox matches in options + if ( of_type == o.value ) { + // if length is allowed for selected type + if(o.length) + { + // set the values in state + state.is_tlength = true; + state.min_val = o.min_val; + state.max_val = o.max_val; + } else { + // set the values in state + state.is_tlength = false; + } + return; + } + }); + } + return state.is_tlength; + }, + }, { + // Note: There are ambiguities in the PG catalogs and docs between + // precision and scale. In the UI, we try to follow the docs as + // closely as possible, therefore we use Length/Precision and Scale + id: 'precision', label: gettext('Scale'), deps: ['type'], + type: 'text', disabled: false, cell: 'int', + depChange: (state, changeSource)=>{ + if(_.isArray(changeSource) && changeSource[2] == 'type') { + return {...state + , value: null + }; + } + }, + editable: (state) => { + // We will store type from selected from combobox + var of_type = state.type; + if(obj.type_options) { + // iterating over all the types + _.each(obj.type_options, function(o) { + // if type from selected from combobox matches in options + if ( of_type == o.value ) { + // if precession is allowed for selected type + if(o.precision) + { + // set the values in state + state.is_precision = true; + state.min_val = o.min_val; + state.max_val = o.max_val; + } else { + // set the values in state + state.is_precision = false; + } + } + }); + } + return state.is_precision; + }, + }, { + id: 'collation', label: gettext('Collation'), type: 'text', + depChange: (state, changeSource)=>{ + if(_.isArray(changeSource) && changeSource[2] == 'type') { + return {...state + , value: null + }; + } + }, + cell: ()=>({ + cell: 'select', options: obj.fieldOptions.collations, + controlProps: { + allowClear: false, + } + }), + deps: ['type'], + editable: (state) => { + // We will store type from selected from combobox + var of_type = state.type; + var flag = false; + if(obj.type_options) { + // iterating over all the types + _.each(obj.type_options, function(o) { + // if type from selected from combobox matches in options + if ( of_type == o.value ) { + if(o.is_collatable) { + flag = true; + return; + } + } + }); + } + return flag; + }, + }]; + } + + validate(state, setError) { + + let errmsg = null; + + if (isEmptyString(state.member_name)) { + errmsg = gettext('Please specify the value for member name.'); + setError('member_name', errmsg); + return true; + } else if(isEmptyString(state.type)) { + errmsg = gettext('Please specify the type.'); + setError('type', errmsg); + return true; + } + if(state.is_tlength) { + + if (state.tlength < state.min_val) { + errmsg = gettext('Length/Precision should not be less than %s.', state.min_val); + } + else if (state.tlength > state.max_val) { + errmsg = gettext('Length/Precision should not be greater than %s.', state.max_val); + } + if(_.isUndefined(errmsg) || errmsg == null) + errmsg = integerValidator('Length/Precision', state.tlength); + // If we have any error set then throw it to user + if(!_.isUndefined(errmsg) && errmsg != null) { + setError('tlength', errmsg); + return true; + } + } + if(state.is_precision) { + errmsg = integerValidator('Scale', state.precision); + if(!_.isUndefined(errmsg) && errmsg != null) { + setError('precision', errmsg); + return true; + } + } + if(_.isUndefined(errmsg) || errmsg == null) { + errmsg = null; + setError('member_name', errmsg); + setError('type', errmsg); + setError('tlength', errmsg); + setError('precision', errmsg); + } + return false; + } +} + +class DataTypeSchema extends BaseUISchema { + + constructor(fieldOptions = {}, initValues) { + super({ + oid: undefined, + is_sys_type: false, + attnum: undefined, + member_name: undefined, + type: undefined, + tlength: null, + is_tlength: false, + precision: undefined, + is_precision: false, + min_val: undefined, + max_val: undefined, + attlen: undefined, + min_val_attlen: undefined, + max_val_attlen: undefined, + attprecision: null, + min_val_attprecision: undefined, + max_val_attprecision: undefined, + ...initValues + }); + this.types = fieldOptions.types; + this.type_options = []; + } + + get idAttribute() { + return 'oid'; + } + + get baseFields() { + var dataTypeObj = this; + return [{ + id: 'type', + label: gettext('Data Type'), + group: gettext('Definition'), + mode: ['edit', 'create','properties'], + disabled: false, + readonly: function (state) { + return !dataTypeObj.isNew(state); + }, + node: 'type', + cache_node: 'domain', + editable: true, + deps: ['typtype'], + type: (state) => { + return { + type: 'select', + options: dataTypeObj.types, + optionsLoaded: (options) => { dataTypeObj.types = options; }, + controlProps: { + allowClear: false, + filter: (options) => { + var data_types = []; + options.forEach((option) => { + if (!(option.value.includes('[]'))) { + data_types.push(option); + } + }); + state.type_options = data_types; + return data_types; + } + } + }; + } + },{ + id: 'maxsize', + group: gettext('Definition'), + label: gettext('Size'), + type: 'int', + deps: ['typtype'], + cell: 'int', + mode: ['create', 'edit','properties'], + readonly: function (state) { + return !dataTypeObj.isNew(state); + }, + visible: function (state) { + return state.typtype === 'V'; + } + },{ + // Note: There are ambiguities in the PG catalogs and docs between + // precision and scale. In the UI, we try to follow the docs as + // closely as possible, therefore we use Length/Precision and Scale + id: 'tlength', + group: gettext('Data Type'), + label: gettext('Length/Precision'), + mode: ['edit', 'create','properties'], + deps: ['type'], + type: 'text', + cell: 'int', + readonly: function (state) { + return !dataTypeObj.isNew(state); + }, + visible: function (state) { + return state.typtype === 'N'; + }, + disabled: function(state) { + + var of_type = state.type, + flag = true; + if (state.type_options) { + _.each(state.type_options, function (o) { + if (of_type == o.value) { + if (o.length) { + state.min_val_attlen = o.min_val; + state.max_val_attlen = o.max_val; + flag = false; + } + } + }); + } + flag && setTimeout(function () { + if (state.attlen) { + state.attlen = null; + } + }, 10); + return flag; + }, + editable: function(state) { + // We will store type from selected from combobox + var of_type = state.type; + if (state.type_options) { + // iterating over all the types + _.each(state.type_options, function (o) { + // if type from selected from combobox matches in options + if (of_type == o.value) { + // if length is allowed for selected type + if (o.length) { + // set the values in state + state.is_tlength = true; + state.min_val = o.min_val; + state.max_val = o.max_val; + } else { + // set the values in staet + state.is_tlength = false; + } + } + }); + } + return state.is_tlength; + } + },{ + // Note: There are ambiguities in the PG catalogs and docs between + // precision and scale. In the UI, we try to follow the docs as + // closely as possible, therefore we use Length/Precision and Scale + id: 'precision', + group: gettext('Data Type'), + label: gettext('Scale'), + mode: ['edit', 'create','properties'], + deps: ['type'], + type: 'text', + readonly: function (state) { + return !dataTypeObj.isNew(state); + }, + cell: 'int', + visible: function (state) { + return state.typtype === 'N'; + }, + disabled: function(state) { + var of_type = state.type, + flag = true; + _.each(state.type_options, function(o) { + if ( of_type == o.value ) { + if(o.precision) { + state.min_val_attprecision = 0; + state.max_val_attprecision = o.max_val; + flag = false; + } + } + }); + + flag && setTimeout(function() { + if(state.attprecision) { + state.attprecision = null; + } + },10); + return flag; + }, + editable: function(state) { + // We will store type from selected from combobox + var of_type = state.type; + if(state.type_options) { + // iterating over all the types + _.each(state.type_options, function(o) { + // if type from selected from combobox matches in options + if ( of_type == o.value ) { + // if precession is allowed for selected type + if(o.precision) + { + // set the values in model + state.is_precision = true; + state.min_val = o.min_val; + state.max_val = o.max_val; + } else { + // set the values in model + state.is_precision = false; + } + } + }); + } + return state.is_precision; + }, + }]; + } +} + +function inSchema(node_info) { + if(node_info && 'catalog' in node_info) + { + return true; + } + return false; +} + +export default class TypeSchema extends BaseUISchema { + constructor(getPrivilegeRoleSchema, getCompositeSchema, getRangeSchema, getExternalSchema, getDataTypeSchema, fieldOptions = {}, initValues) { + super({ + name: null, + oid: undefined, + is_sys_type: false, + typtype: undefined, + typeowner: undefined, + schema: undefined, + ...initValues + }); + this.fieldOptions = { + roles: [], + schemas: [], + server_info: [], + node_info: [], + ...fieldOptions + }; + this.getPrivilegeRoleSchema = getPrivilegeRoleSchema; + this.getCompositeSchema = getCompositeSchema; + this.getRangeSchema = getRangeSchema; + this.getExternalSchema = getExternalSchema; + this.getDataTypeSchema = getDataTypeSchema; + } + + schemaCheck(state) { + if(this.fieldOptions.node_info && 'schema' in this.fieldOptions.node_info) + { + if(!state) + return true; + if (this.isNew(state)) { + return false; + } else { + return state && state.typtype === 'p'; + } + } + return true; + } + + get idAttribute() { + return 'oid'; + } + + get baseFields() { + var obj = this; + return [{ + id: 'name', label: gettext('Name'), cell: 'string', + type: 'text', mode: ['properties', 'create', 'edit'], + noEmpty: true, + disabled: (state) => obj.schemaCheck(state), + },{ + id: 'oid', label: gettext('OID'), cell: 'string', + type: 'text' , mode: ['properties'], + },{ + id: 'typeowner', label: gettext('Owner'), cell: 'string', + mode: ['properties', 'create', 'edit'], noEmpty: true, + type: 'select', options: this.fieldOptions.roles, + controlProps: { allowClear: false }, + disabled: () => inSchema(obj.node_info), + },{ + id: 'schema', label: gettext('Schema'), cell: 'string', + mode: ['create', 'edit'], noEmpty: true, + disabled: (state) => obj.schemaCheck(state), + type: (state) => { + return { + type: 'select', + options: obj.fieldOptions.schemas, + optionsLoaded: (options) => { obj.fieldOptions.schemas = options; }, + controlProps: { + allowClear: true, + filter: (options) => { + let res = []; + if (state && obj.isNew(state)) { + options.forEach((option) => { + // If schema name start with pg_* then we need to exclude them + if(option && option.label.match(/^pg_/)) { + return; + } + res.push({ label: option.label, value: option.value }); + }); + } else { + res = options; + } + return res; + } + } + }; + } + }, + { + id: 'typtype', label: gettext('Type'), + mode: ['create','edit'], group: gettext('Definition'), + type: 'select', + disabled: () => inSchema(obj.node_info), + readonly: function (state) { + return !obj.isNew(state); + }, + controlProps: { allowClear: false }, + options: function() { + var typetype = [ + {label: gettext('Composite'), value: 'c'}, + {label: gettext('Enumeration'), value: 'e'}, + {label: gettext('External'), value: 'b'}, + {label: gettext('Range'), value: 'r'}, + {label: gettext('Shell'), value: 'p'}, + ]; + if (obj.fieldOptions.server_info.server_type === 'ppas' && + obj.fieldOptions.server_info.version >= 90500){ + typetype.push( + {label: gettext('Nested Table'), value: 'N'}, + {label: gettext('Varying Array'), value: 'V'} + ); + } + return typetype; + }, + }, + { + id: 'composite', label: gettext('Composite Type'), + editable: true, type: 'collection', + group: gettext('Definition'), mode: ['edit', 'create'], + uniqueCol : ['member_name'], + canAdd: true, canEdit: false, canDelete: true, + disabled: () => inSchema(obj.node_info), + schema: obj.getCompositeSchema(), + deps: ['typtype'], + visible: (state) => { + return state.typtype === 'c'; + }, + }, + { + id: 'enum', label: gettext('Enumeration type'), + schema: new EnumerationSchema(), + editable: true, + type: 'collection', + group: gettext('Definition'), mode: ['edit', 'create'], + canAdd: true, canEdit: false, + canDelete: function(state) { + // We will disable it if it's in 'edit' mode + return !obj.isNew(state); + }, + disabled: () => inSchema(obj.node_info), + deps: ['typtype'], + uniqueCol : ['label'], + visible: function(state) { + return state.typtype === 'e'; + }, + }, { + type: 'nested-fieldset', + group: gettext('Definition'), + label: '', + deps: ['typtype'], + mode: ['edit', 'create', 'properties'], + visible: function(state) { + return state.typtype === 'N' || state.typtype === 'V'; + }, + schema: obj.getDataTypeSchema() + }, { + // We will disable range type control in edit mode + type: 'nested-fieldset', + group: gettext('Definition'), + label: '', + mode: ['edit', 'create'], + visible: (state) => { + return state.typtype && state.typtype === 'r'; + }, + deps: ['typtype'], + schema: obj.getRangeSchema(), + }, { + type: 'nested-tab', + group: gettext('Definition'), + label: gettext('External Type'), deps: ['typtype'], + mode: ['create', 'edit'], tabPanelExtraClasses:'inline-tab-panel-padded', + visible: function(state) { + return state.typtype === 'b'; + }, + schema: obj.getExternalSchema(), + }, + { + id: 'alias', label: gettext('Alias'), cell: 'string', + type: 'text', mode: ['properties'], + disabled: () => inSchema(obj.node_info), + }, + { + id: 'member_list', label: gettext('Members'), cell: 'string', + type: 'text', mode: ['properties'], group: gettext('Definition'), + disabled: () => inSchema(obj.node_info), + visible: function(state) { + if(state.typtype === 'c') { + return true; + } + return false; + }, + },{ + id: 'enum_list', label: gettext('Labels'), cell: 'string', + type: 'text', mode: ['properties'], group: gettext('Definition'), + disabled: () => inSchema(obj.node_info), + visible: function(state) { + if(state.typtype === 'e') { + return true; + } + return false; + }, + }, + { + id: 'typname', label: gettext('SubType'), cell: 'string', + type: 'text', mode: ['properties'], group: gettext('Definition'), + disabled: () => inSchema(obj.node_info), + visible: function(state) { + if(state.typtype === 'r') { + return true; + } + return false; + }, + }, + { + id: 'opcname', label: gettext('Subtype operator class'), cell: 'string', + type: 'text', mode: ['properties'], group: gettext('Definition'), + disabled: () => inSchema(obj.node_info), + visible: function(state) { + if(state.typtype === 'r') { + return true; + } + return false; + }, + }, + { + id: 'collname', label: gettext('Collation'), cell: 'string', + type: 'text', mode: ['properties'], group: gettext('Definition'), + disabled: () => inSchema(obj.node_info), + visible: function(state) { + if(state.typtype === 'r') { + return true; + } + return false; + }, + }, + { + id: 'rngcanonical', label: gettext('Canonical function'), cell: 'string', + type: 'text', mode: ['properties'], group: gettext('Definition'), + disabled: () => inSchema(obj.node_info), + visible: function(state) { + if(state.typtype === 'r') { + return true; + } + return false; + }, + }, + { + id: 'rngsubdiff', label: gettext('Subtype diff function'), cell: 'string', + type: 'text', mode: ['properties'], group: gettext('Definition'), + disabled: () => inSchema(obj.node_info), + visible: function(state) { + if(state.typtype === 'r') { + return true; + } + return false; + }, + }, + { + id: 'typinput', label: gettext('Input function'), cell: 'string', + type: 'text', mode: ['properties'], group: gettext('Definition'), + disabled: () => inSchema(obj.node_info), + visible: function(state) { + if(state.typtype === 'b') { + return true; + } + return false; + }, + }, + { + id: 'typoutput', label: gettext('Output function'), cell: 'string', + type: 'text', mode: ['properties'], group: gettext('Definition'), + disabled: () => inSchema(obj.node_info), + visible: function(state) { + if(state.typtype === 'b') { + return true; + } + return false; + }, + }, + { + id: 'type_acl', label: gettext('Privileges'), cell: 'string', + type: 'text', mode: ['properties'], group: gettext('Security'), + disabled: () => inSchema(obj.node_info), + }, + { + id: 'is_sys_type', label: gettext('System type?'), cell: 'switch', + type: 'switch', mode: ['properties'], + disabled: () => inSchema(obj.node_info), + }, + { + id: 'description', label: gettext('Comment'), cell: 'string', + type: 'multiline', mode: ['properties', 'create', 'edit'], + disabled: () => inSchema(obj.node_info), + }, + { + id: 'typacl', label: gettext('Privileges'), type: 'collection', + group: gettext('Security'), + schema: this.getPrivilegeRoleSchema(['U']), + mode: ['edit', 'create'], canDelete: true, + uniqueCol : ['grantee'], deps: ['typtype'], + canAdd: function(state) { + // Do not allow to add when shell type is selected + // Clear acl & security label collections as well + if (state.typtype === 'p') { + var acl = state.typacl; + if(acl && acl.length > 0) + acl.reset(); + } + return (state.typtype !== 'p'); + }, + }, + { + id: 'seclabels', label: gettext('Security labels'), + schema: new SecLabelSchema(), + editable: false, type: 'collection', + group: gettext('Security'), mode: ['edit', 'create'], + min_version: 90100, canEdit: false, canDelete: true, + uniqueCol : ['provider'], deps: ['typtype'], + canAdd: function(state) { + // Do not allow to add when shell type is selected + // Clear acl & security label collections as well + if (state.typtype === 'p') { + var secLabs = state.seclabels; + if(secLabs && secLabs.length > 0) + secLabs.reset(); + } + return (state.typtype !== 'p'); + }, + }]; + } +} + +export { + CompositeSchema, + EnumerationSchema, + ExternalSchema, + RangeSchema, + getCompositeSchema, + getRangeSchema, + getExternalSchema, + getDataTypeSchema +}; \ No newline at end of file diff --git a/web/pgadmin/static/js/SchemaView/FieldSetView.jsx b/web/pgadmin/static/js/SchemaView/FieldSetView.jsx index 611f6e5f8..5953c397d 100644 --- a/web/pgadmin/static/js/SchemaView/FieldSetView.jsx +++ b/web/pgadmin/static/js/SchemaView/FieldSetView.jsx @@ -20,7 +20,7 @@ import { getFieldMetaData } from './FormView'; import FieldSet from '../components/FieldSet'; export default function FieldSetView({ - value, formErr, schema={}, viewHelperProps, accessPath, dataDispatch, controlClassName, isDataGridForm=false, label}) { + value, formErr, schema={}, viewHelperProps, accessPath, dataDispatch, controlClassName, isDataGridForm=false, label, visible}) { const depListener = useContext(DepListenerContext); useEffect(()=>{ @@ -91,6 +91,10 @@ export default function FieldSetView({ } }); + if(!visible) { + return <>; + } + return (
{viewFields} @@ -108,4 +112,7 @@ FieldSetView.propTypes = { dataDispatch: PropTypes.func, controlClassName: CustomPropTypes.className, label: PropTypes.string, + visible: PropTypes.oneOfType([ + PropTypes.bool, PropTypes.func, + ]), }; diff --git a/web/pgadmin/static/js/SchemaView/FormView.jsx b/web/pgadmin/static/js/SchemaView/FormView.jsx index 2b6e174b6..b3d51ef08 100644 --- a/web/pgadmin/static/js/SchemaView/FormView.jsx +++ b/web/pgadmin/static/js/SchemaView/FormView.jsx @@ -122,7 +122,7 @@ export function getFieldMetaData(field, schema, value, viewHelperProps) { /* The first component of schema view form */ export default function FormView({ value, formErr, schema={}, viewHelperProps, isNested=false, accessPath, dataDispatch, hasSQLTab, - getSQLValue, onTabChange, firstEleRef, className, isDataGridForm=false}) { + getSQLValue, onTabChange, firstEleRef, className, isDataGridForm=false, visible}) { let defaultTab = 'General'; let tabs = {}; let tabsClassname = {}; @@ -190,7 +190,8 @@ export default function FormView({ } tabs[group].push( + schema={field.schema} accessPath={accessPath} dataDispatch={dataDispatch} isNested={true} isDataGridForm={isDataGridForm} + {...field} visible={visible}/> ); } else if(field.type === 'nested-fieldset') { /* Pass on the top schema */ @@ -203,7 +204,7 @@ export default function FormView({ + {...field} visible={visible}/> ); } else if(field.type === 'collection') { /* If its a collection, let data grid view handle it */ @@ -305,6 +306,11 @@ export default function FormView({ onTabChange && onTabChange(tabValue, Object.keys(tabs)[tabValue], sqlTabActive); }, [tabValue]); + /* check whether form is kept hidden by visible prop */ + if(!_.isUndefined(visible) && !visible) { + return <>; + } + return ( <> @@ -343,6 +349,9 @@ FormView.propTypes = { viewHelperProps: PropTypes.object, isNested: PropTypes.bool, isDataGridForm: PropTypes.bool, + visible: PropTypes.oneOfType([ + PropTypes.bool, PropTypes.func, + ]), accessPath: PropTypes.array.isRequired, dataDispatch: PropTypes.func, hasSQLTab: PropTypes.bool, diff --git a/web/regression/javascript/fake_messages.js b/web/regression/javascript/fake_messages.js index f8888a838..5fbcdae8b 100644 --- a/web/regression/javascript/fake_messages.js +++ b/web/regression/javascript/fake_messages.js @@ -15,6 +15,7 @@ define('pgadmin.browser.messages',['sources/pgadmin'], function(pgAdmin) { pgBrowser.messages = { 'CANNOT_BE_EMPTY': '\'%s\' cannot be empty.', + 'MUST_BE_INT': '\'%s\' must be an integer.' }; return pgBrowser; }); diff --git a/web/regression/javascript/schema_ui_files/type.ui.spec.js b/web/regression/javascript/schema_ui_files/type.ui.spec.js new file mode 100644 index 000000000..88c42ca81 --- /dev/null +++ b/web/regression/javascript/schema_ui_files/type.ui.spec.js @@ -0,0 +1,457 @@ +///////////////////////////////////////////////////////////// +// +// 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 pgAdmin from 'sources/pgadmin'; +import {messages} from '../fake_messages'; +import { createMount } from '@material-ui/core/test-utils'; +import SchemaView from '../../../pgadmin/static/js/SchemaView'; +import * as nodeAjax from '../../../pgadmin/browser/static/js/node_ajax'; +import gettext from 'sources/gettext'; +import { integerValidator } from 'sources/validators'; +import { getNodePrivilegeRoleSchema } from '../../../pgadmin/browser/server_groups/servers/static/js/privilege.ui'; + +import TypeSchema, { EnumerationSchema, getCompositeSchema, getExternalSchema, getRangeSchema, getDataTypeSchema } from '../../../pgadmin/browser/server_groups/servers/databases/schemas/types/static/js/type.ui'; + +describe('TypeSchema', ()=>{ + + let mount; + 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 || {}; + pgAdmin.Browser.utils.support_ssh_tunnel = true; + }); + + describe('composite schema describe', () => { + + let compositeCollObj = getCompositeSchema({}, {server: {user: {name: 'postgres'}}}, {}); + let types = [{ label: '', value: ''}, { label: 'lb1', value: 'numeric[]', length: true, min_val: 10, max_val: 100, precision: true, is_collatable: true}]; + let collations = [{ label: '', value: ''}, { label: 'lb1', value: 'numeric[]'}]; + + it('composite collection', ()=>{ + + spyOn(nodeAjax, 'getNodeAjaxOptions').and.returnValue([]); + spyOn(compositeCollObj.fieldOptions, 'types').and.returnValue(types); + spyOn(compositeCollObj.fieldOptions, 'collations').and.returnValue(collations); + + spyOn(compositeCollObj, 'type_options').and.returnValue(compositeCollObj.fieldOptions.types()); + + mount({}} + onClose={()=>{}} + onHelp={()=>{}} + onEdit={()=>{}} + onDataChange={()=>{}} + confirmOnCloseReset={false} + hasSQL={false} + disableSqlHelp={false} + />); + + mount({}} + onClose={()=>{}} + getInitData={getInitData} + onHelp={()=>{}} + onEdit={()=>{}} + onDataChange={()=>{}} + confirmOnCloseReset={false} + hasSQL={false} + disableSqlHelp={false} + />); + }); + + it('composite validate', () => { + let state = { typtype: 'b' }; //validating for ExternalSchema which is distinguish as r + let setError = jasmine.createSpy('setError'); + + compositeCollObj.validate(state, setError); + expect(setError).toHaveBeenCalledWith('member_name', 'Please specify the value for member name.'); + + state.member_name = 'demo_member'; + compositeCollObj.validate(state, setError); + expect(setError).toHaveBeenCalledWith('type', 'Please specify the type.'); + + state.type = 'char'; + state.min_val = 10; + state.max_val = 100; + state.is_tlength = true; + state.tlength = 9; + compositeCollObj.validate(state, setError); + expect(setError).toHaveBeenCalledWith('tlength', gettext('Length/Precision should not be less than %s.', state.min_val)); + + state.tlength = 200; + compositeCollObj.validate(state, setError); + expect(setError).toHaveBeenCalledWith('tlength', gettext('Length/Precision should not be greater than %s.', state.max_val)); + + state.tlength = 'ert'; + compositeCollObj.validate(state, setError); + expect(setError).toHaveBeenCalledWith('tlength', integerValidator('Length/Precision', state.tlength)); + + state.tlength = 90; + state.is_precision = true; + state.precision = 'ert'; + compositeCollObj.validate(state, setError); + expect(setError).toHaveBeenCalledWith('precision', integerValidator('Scale', state.precision)); + + state.precision = 9; + compositeCollObj.validate(state, setError); + expect(setError).toHaveBeenCalled(); + }); + + it('tlength editable', ()=>{ + compositeCollObj.type_options = types; + let editable = _.find(compositeCollObj.fields, (f)=>f.id=='tlength').editable; + let status = editable({type: 'numeric[]'}); + expect(status).toBe(true); + }); + + it('precision editable', ()=>{ + compositeCollObj.type_options = types; + let editable = _.find(compositeCollObj.fields, (f)=>f.id=='precision').editable; + let status = editable({type: 'numeric[]'}); + expect(status).toBe(true); + }); + + it('collation editable', ()=>{ + compositeCollObj.type_options = types; + let editable = _.find(compositeCollObj.fields, (f)=>f.id=='collation').editable; + let status = editable({type: 'numeric[]'}); + expect(status).toBe(true); + }); + + it('setTypeOptions', ()=>{ + compositeCollObj.setTypeOptions(types); + }); + }); + + describe('enumeration schema describe', () => { + + it('enumeration collection', ()=>{ + + let enumerationCollObj = new EnumerationSchema( + ()=>[], + ()=>[] + ); + + mount({}} + onClose={()=>{}} + onHelp={()=>{}} + onEdit={()=>{}} + onDataChange={()=>{}} + confirmOnCloseReset={false} + hasSQL={false} + disableSqlHelp={false} + />); + + mount({}} + getInitData={getInitData} + onClose={()=>{}} + onHelp={()=>{}} + onEdit={()=>{}} + onDataChange={()=>{}} + confirmOnCloseReset={false} + hasSQL={false} + disableSqlHelp={false} + />); + }); + }); + + describe('external schema describe', () => { + + let externalCollObj = getExternalSchema({}, {server: {user: {name: 'postgres'}}}, {}); + + it('external collection', ()=>{ + + spyOn(nodeAjax, 'getNodeAjaxOptions').and.returnValue([]); + spyOn(externalCollObj.fieldOptions, 'externalFunctionsList').and.returnValue([{ label: '', value: ''}, { label: 'lb1', cbtype: 'typmodin', value: 'val1'}, { label: 'lb2', cbtype: 'all', value: 'val2'}]); + spyOn(externalCollObj.fieldOptions, 'types').and.returnValue([{ label: '', value: ''}]); + + mount({}} + onClose={()=>{}} + onHelp={()=>{}} + onEdit={()=>{}} + onDataChange={()=>{}} + confirmOnCloseReset={false} + hasSQL={false} + disableSqlHelp={false} + />); + + mount({}} + onClose={()=>{}} + getInitData={getInitData} + onHelp={()=>{}} + onEdit={()=>{}} + onDataChange={()=>{}} + confirmOnCloseReset={false} + hasSQL={false} + disableSqlHelp={false} + />); + }); + + it('external validate', () => { + let state = { typtype: 'b' }; //validating for ExternalSchema which is distinguish as r + let setError = jasmine.createSpy('setError'); + + externalCollObj.validate(state, setError); + expect(setError).toHaveBeenCalledWith('typinput', 'Input function cannot be empty'); + + state.typinput = 'demo_input'; + externalCollObj.validate(state, setError); + expect(setError).toHaveBeenCalledWith('typoutput', 'Output function cannot be empty'); + }); + }); + + describe('range schema describe', () => { + + let rangeCollObj = getRangeSchema({}, {server: {user: {name: 'postgres'}}}, {}); + + it('range collection', ()=>{ + + spyOn(nodeAjax, 'getNodeAjaxOptions').and.returnValue([]); + spyOn(rangeCollObj.fieldOptions, 'getSubOpClass').and.returnValue([{ label: '', value: ''}, { label: 'lb1', value: 'val1'}]); + spyOn(rangeCollObj.fieldOptions, 'getCanonicalFunctions').and.returnValue([{ label: '', value: ''}, { label: 'lb1', value: 'val1'}]); + spyOn(rangeCollObj.fieldOptions, 'getSubDiffFunctions').and.returnValue([{ label: '', value: ''}, { label: 'lb1', value: 'val1'}]); + spyOn(rangeCollObj.fieldOptions, 'typnameList').and.returnValue([{ label: '', value: ''}, { label: 'lb1', value: 'val1'}]); + spyOn(rangeCollObj.fieldOptions, 'collationsList').and.returnValue([{ label: '', value: ''}, { label: 'lb1', value: 'val1'}]); + + mount({}} + onClose={()=>{}} + onHelp={()=>{}} + onEdit={()=>{}} + onDataChange={()=>{}} + confirmOnCloseReset={false} + hasSQL={false} + disableSqlHelp={false} + />); + + mount({}} + onClose={()=>{}} + getInitData={getInitData} + onHelp={()=>{}} + onEdit={()=>{}} + onDataChange={()=>{}} + confirmOnCloseReset={false} + hasSQL={false} + disableSqlHelp={false} + />); + }); + + it('range validate', () => { + let state = { typtype: 'r' }; //validating for RangeSchema which is distinguish as r + let setError = jasmine.createSpy('setError'); + + rangeCollObj.validate(state, setError); + expect(setError).toHaveBeenCalledWith('typname', 'Subtype cannot be empty'); + }); + }); + + describe('data type schema describe', () => { + + let dataTypeObj = getDataTypeSchema({}, {server: {user: {name: 'postgres'}}}, {}); + let types = [{ label: '', value: ''}, { label: 'lb1', value: 'numeric', length: true, min_val: 10, max_val: 100, precision: true}]; + + it('data type collection', ()=>{ + + spyOn(nodeAjax, 'getNodeAjaxOptions').and.returnValue([]); + + mount({}} + onClose={()=>{}} + onHelp={()=>{}} + onEdit={()=>{}} + onDataChange={()=>{}} + confirmOnCloseReset={false} + hasSQL={false} + disableSqlHelp={false} + />); + + mount({}} + getInitData={getInitData} + onClose={()=>{}} + onHelp={()=>{}} + onEdit={()=>{}} + onDataChange={()=>{}} + confirmOnCloseReset={false} + hasSQL={false} + disableSqlHelp={false} + />); + }); + + it('tlength editable', ()=>{ + dataTypeObj.type_options = types; + let editable = _.find(dataTypeObj.fields, (f)=>f.id=='tlength').editable; + let status = editable({type: 'numeric', type_options: types}); + expect(status).toBe(true); + }); + + it('tlength disabled', ()=>{ + dataTypeObj.type_options = types; + let disabled = _.find(dataTypeObj.fields, (f)=>f.id=='tlength').disabled; + let status = disabled({type: 'numeric', type_options: types}); + expect(status).toBe(false); + }); + + it('precision editable', ()=>{ + dataTypeObj.type_options = types; + let editable = _.find(dataTypeObj.fields, (f)=>f.id=='precision').editable; + let status = editable({type: 'numeric', type_options: types}); + expect(status).toBe(true); + }); + + it('precision disabled', ()=>{ + dataTypeObj.type_options = types; + let disabled = _.find(dataTypeObj.fields, (f)=>f.id=='precision').disabled; + let status = disabled({type: 'numeric', type_options: types}); + expect(status).toBe(false); + }); + }); + + let typeSchemaObj = new TypeSchema( + (privileges)=>getNodePrivilegeRoleSchema({}, {server: {user: {name: 'postgres'}}}, {}, privileges), + ()=>getCompositeSchema({}, {server: {user: {name: 'postgres'}}}, {}), + ()=>getRangeSchema({}, {server: {user: {name: 'postgres'}}}, {}), + ()=>getExternalSchema({}, {server: {user: {name: 'postgres'}}}, {}), + ()=>getDataTypeSchema({}, {server: {user: {name: 'postgres'}}}, {}), + { + roles: ()=>[], + schemas: ()=>[{ label: 'pg_demo', value: 'pg_demo'}], + server_info: [], + node_info: {'schema': []} + }, + { + typowner: 'postgres', + schema: 'public', + typtype: 'c' + } + ); + + it('create', ()=>{ + mount({}} + onClose={()=>{}} + onHelp={()=>{}} + onEdit={()=>{}} + onDataChange={()=>{}} + confirmOnCloseReset={false} + hasSQL={false} + disableSqlHelp={false} + />); + }); + + it('edit', ()=>{ + mount({}} + getInitData={getInitData} + onClose={()=>{}} + onHelp={()=>{}} + onEdit={()=>{}} + onDataChange={()=>{}} + confirmOnCloseReset={false} + hasSQL={false} + disableSqlHelp={false} + />); + }); + + it('properties', ()=>{ + mount({}} + onEdit={()=>{}} + />); + }); +}); \ No newline at end of file