diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/mview.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/mview.js index ad5d7b2ec..ce36dde36 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/mview.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/mview.js @@ -7,6 +7,11 @@ // ////////////////////////////////////////////////////////////// +import MViewSchema from './mview.ui'; +import { getNodeListByName } from '../../../../../../../static/js/node_ajax'; +import { getNodePrivilegeRoleSchema } from '../../../../../static/js/privilege.ui'; +import { getNodeVacuumSettingsSchema } from '../../../../../static/js/vacuum.ui'; + define('pgadmin.node.mview', [ 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'sources/pgadmin', 'pgadmin.alertifyjs', 'pgadmin.browser', @@ -122,7 +127,23 @@ define('pgadmin.node.mview', [ icon: 'fa fa-sync-alt', }]); }, - + getSchema: function(treeNodeInfo, itemNodeData) { + return new MViewSchema( + (privileges)=>getNodePrivilegeRoleSchema('', treeNodeInfo, itemNodeData, privileges), + ()=>getNodeVacuumSettingsSchema(this, treeNodeInfo, itemNodeData), + { + role: ()=>getNodeListByName('role', treeNodeInfo, itemNodeData), + schema: ()=>getNodeListByName('schema', treeNodeInfo, itemNodeData, {cacheLevel: 'database'}), + spcname: ()=>getNodeListByName('tablespace', treeNodeInfo, itemNodeData, {}, (m)=> { + return (m.label != 'pg_global'); + }), + }, + { + owner: pgBrowser.serverInfo[treeNodeInfo.server._id].user.name, + schema: treeNodeInfo.schema.label + } + ); + }, /** Define model for the view node and specify the properties of the model in schema. @@ -142,9 +163,6 @@ define('pgadmin.node.mview', [ }, defaults: { spcname: undefined, - toast_autovacuum_enabled: 'x', - autovacuum_enabled: 'x', - warn_text: undefined, }, schema: [{ id: 'name', label: gettext('Name'), cell: 'string', @@ -156,81 +174,9 @@ define('pgadmin.node.mview', [ id: 'owner', label: gettext('Owner'), cell: 'string', control: 'node-list-by-name', select2: { allowClear: false }, node: 'role', disabled: 'inSchema', - },{ - id: 'schema', label: gettext('Schema'), cell: 'string', first_empty: false, - control: 'node-list-by-name', type: 'text', cache_level: 'database', - node: 'schema', mode: ['create', 'edit'], cache_node: 'database', - disabled: 'inSchema', select2: { allowClear: false }, - },{ - id: 'system_view', label: gettext('System materialized view?'), cell: 'string', - type: 'switch', mode: ['properties'], - }, pgBrowser.SecurityGroupSchema, { - id: 'acl', label: gettext('Privileges'), - mode: ['properties'], type: 'text', group: gettext('Security'), },{ id: 'comment', label: gettext('Comment'), cell: 'string', type: 'multiline', - },{ - id: 'definition', label: gettext('Definition'), cell: 'string', - type: 'text', mode: ['create', 'edit'], group: gettext('Definition'), - tabPanelCodeClass: 'sql-code-control', - control: Backform.SqlCodeControl.extend({ - onChange: function() { - Backform.SqlCodeControl.prototype.onChange.apply(this, arguments); - if(this.model && this.model.changed) { - if(this.model.origSessAttrs && (this.model.changed.definition != this.model.origSessAttrs.definition)) { - this.model.warn_text = gettext( - 'Updating the definition will drop and re-create the materialized view. It may result in loss of information about its dependent objects.' - ) + '

' + gettext('Do you want to continue?') + - ''; - } - else { - this.model.warn_text = undefined; - } - } - else { - this.model.warn_text = undefined; - } - }, - }), - },{ - id: 'with_data', label: gettext('With data?'), - group: gettext('Storage'), mode: ['edit', 'create'], - type: 'switch', - },{ - id: 'spcname', label: gettext('Tablespace'), cell: 'string', - type: 'text', group: gettext('Storage'), first_empty: false, - control: 'node-list-by-name', node: 'tablespace', select2: { allowClear: false }, - filter: function(m) { - return (m.label != 'pg_global'); - }, - },{ - id: 'fillfactor', label: gettext('Fill factor'), - group: gettext('Storage'), mode: ['edit', 'create'], - type: 'int', min: 10, max: 100, - },{ - id: 'vacuum_settings_str', label: gettext('Storage settings'), - type: 'multiline', group: gettext('Storage'), mode: ['properties'], - },{ - type: 'nested', control: 'tab', id: 'materialization', - label: gettext('Parameter'), mode: ['edit', 'create'], - group: gettext('Parameter'), - schema: Backform.VacuumSettingsSchema, - },{ - // Add Privilege Control - id: 'datacl', label: gettext('Privileges'), type: 'collection', - model: pgBrowser.Node.PrivilegeRoleModel.extend({ - privileges: ['a', 'r', 'w', 'd', 'D', 'x', 't'], - }), uniqueCol : ['grantee'], editable: false, - group: 'security', canAdd: true, canDelete: true, - mode: ['edit', 'create'], control: 'unique-col-collection', - },{ - // Add Security Labels Control - id: 'seclabels', label: gettext('Security labels'), - model: pgBrowser.SecLabelModel, editable: false, type: 'collection', - canEdit: false, group: 'security', canDelete: true, - mode: ['edit', 'create'], canAdd: true, - control: 'unique-col-collection', uniqueCol : ['provider'], }], sessChanged: function() { /* If only custom autovacuum option is enabled the check if the options table is also changed. */ diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/mview.ui.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/mview.ui.js new file mode 100644 index 000000000..f7a52d7ac --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/mview.ui.js @@ -0,0 +1,179 @@ +///////////////////////////////////////////////////////////// +// +// 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 { isEmptyString } from 'sources/validators'; + + +export default class MViewSchema extends BaseUISchema { + constructor(getPrivilegeRoleSchema, getVacuumSettingsSchema, fieldOptions={}, initValues) { + super({ + spcname: undefined, + toast_autovacuum_enabled: 'x', + autovacuum_enabled: 'x', + warn_text: undefined, + ...initValues + }); + this.getPrivilegeRoleSchema = getPrivilegeRoleSchema; + this.getVacuumSettingsSchema = getVacuumSettingsSchema; + this.fieldOptions = { + role: [], + schema: [], + spcname: [], + nodeInfo: null, + ...fieldOptions, + }; + + this.nodeInfo = this.fieldOptions.nodeInfo; + } + + get idAttribute() { + return 'oid'; + } + + inSchema() { + if(this.nodeInfo && 'catalog' in this.nodeInfo) + { + return true; + } + return false; + } + + + get baseFields() { + let obj = this; + return [ + { + id: 'name', label: gettext('Name'), cell: 'text', + type: 'text', disabled: obj.inSchema, noEmpty: true, + },{ + id: 'oid', label: gettext('OID'), cell: 'text', + type: 'text', mode: ['properties'], + },{ + id: 'owner', label: gettext('Owner'), + type: 'select', cell: 'text', + options: obj.fieldOptions.role, controlProps: { allowClear: false }, + disabled: obj.inSchema, + },{ + id: 'schema', label: gettext('Schema'), cell: 'text', + type: 'select', options: obj.fieldOptions.schema, mode: ['create', 'edit'], + cache_node: 'database', disabled: obj.inSchema, + controlProps: { + allowClear: false, + first_empty: false + }, + },{ + id: 'system_view', label: gettext('System materialized view?'), cell: 'text', + type: 'switch', mode: ['properties'], + }, + { + id: 'acl', label: gettext('Privileges'), + mode: ['properties'], type: 'text', group: gettext('Security'), + },{ + id: 'comment', label: gettext('Comment'), cell: 'text', + type: 'multiline', + },{ + id: 'definition', label: gettext('Definition'), cell: 'text', + type: 'sql', mode: ['create', 'edit'], group: gettext('Definition'), + isFullTab: true, controlProps: { readOnly: this.nodeInfo && 'catalog' in this.nodeInfo ? true: false }, + },{ + id: 'with_data', label: gettext('With data?'), + group: gettext('Storage'), mode: ['edit', 'create'], + type: 'switch', + },{ + id: 'spcname', label: gettext('Tablespace'), cell: 'text', + type: 'select', group: gettext('Storage'), + options: obj.fieldOptions.spcname, + controlProps: { + allowClear: false, + first_empty: false, + }, + },{ + id: 'fillfactor', label: gettext('Fill factor'), + group: gettext('Storage'), mode: ['edit', 'create'], + noEmpty: false, type: 'int', controlProps: {min: 10, max: 100} + },{ + id: 'vacuum_settings_str', label: gettext('Storage settings'), + type: 'multiline', group: gettext('Storage'), mode: ['properties'], + }, + { + type: 'nested-tab', group: gettext('Parameter'), mode: ['create', 'edit'], + schema: this.getVacuumSettingsSchema(), + }, + /*{ + type: 'nested', control: 'tab', id: 'materialization', + label: gettext('Parameter'), mode: ['edit', 'create'], + group: gettext('Parameter'), + schema: Backform.VacuumSettingsSchema, + }, + { + // Add Privilege Control + id: 'datacl', label: gettext('Privileges'), type: 'collection', + model: pgBrowser.Node.PrivilegeRoleModel.extend({ + privileges: ['a', 'r', 'w', 'd', 'D', 'x', 't'], + }), uniqueCol : ['grantee'], editable: false, + group: 'security', canAdd: true, canDelete: true, + mode: ['edit', 'create'], control: 'unique-col-collection', + },*/ + { + id: 'datacl', label: gettext('Privileges'), type: 'collection', + schema: this.getPrivilegeRoleSchema(['U']), + uniqueCol : ['grantee'], + editable: false, + group: gettext('Security'), mode: ['edit', 'create'], + canAdd: true, canDelete: true, + }, + { + // Add Security Labels Control + id: 'seclabels', label: gettext('Security labels'), + schema: new SecLabelSchema(), + editable: false, type: 'collection', + canEdit: false, group: gettext('Security'), canDelete: true, + mode: ['edit', 'create'], canAdd: true, + control: 'unique-col-collection', + uniqueCol : ['provider'], + } + ]; + } + + validate(state, setError) { + let errmsg = null; + let obj = this; + + if (isEmptyString(state.service)) { + + /* mview definition validation*/ + if (isEmptyString(state.definition)) { + errmsg = gettext('Please enter view definition.'); + setError('definition', errmsg); + return true; + } else { + errmsg = null; + setError('definition', errmsg); + } + + if (state.definition) { + obj.warningText = null; + if (obj.origData.oid !== undefined && state.definition !== obj.origData.definition) { + obj.warningText = gettext( + 'Updating the definition will drop and re-create the materialized view. It may result in loss of information about its dependent objects.' + ) + '

' + gettext('Do you want to continue?') + ''; + } + } + return false; + } else { + errmsg = null; + _.each(['definition'], (item) => { + setError(item, errmsg); + }); + } + } +} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.js index 2f196ef63..4b38ff7fe 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.js @@ -104,7 +104,7 @@ define('pgadmin.node.view', [ }, { owner: pgBrowser.serverInfo[treeNodeInfo.server._id].user.name, - schema: treeNodeInfo.schema.label + schema: treeNodeInfo.schema ? treeNodeInfo.schema.label : '' } ); }, diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.ui.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.ui.js index 136d68d0d..89cdf8c23 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.ui.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.ui.js @@ -36,7 +36,7 @@ export default class ViewSchema extends BaseUISchema { } notInSchema() { - if(this.node_info && 'catalog' in this.node_info) { + if(this.nodeInfo && 'catalog' in this.nodeInfo) { return true; } return false; @@ -96,7 +96,7 @@ export default class ViewSchema extends BaseUISchema { id: 'definition', label: gettext('Code'), cell: 'text', type: 'sql', mode: ['create', 'edit'], group: gettext('Code'), isFullTab: true, - disabled: obj.notInSchema, + controlProps: { readOnly: obj.nodeInfo && 'catalog' in obj.nodeInfo ? true: false }, }, { @@ -142,7 +142,7 @@ export default class ViewSchema extends BaseUISchema { state.definition !== obj.origData.definition )) { obj.warningText = null; - return true; + return false; } let old_def = obj.origData.definition && @@ -169,7 +169,7 @@ export default class ViewSchema extends BaseUISchema { } else { obj.warningText = null; } - return true; + return false; } } else { diff --git a/web/regression/javascript/schema_ui_files/mview.ui.spec.js b/web/regression/javascript/schema_ui_files/mview.ui.spec.js new file mode 100644 index 000000000..66a9b48c0 --- /dev/null +++ b/web/regression/javascript/schema_ui_files/mview.ui.spec.js @@ -0,0 +1,135 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2021, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import jasmineEnzyme from 'jasmine-enzyme'; +import React from 'react'; +import '../helper/enzyme.helper'; +import { createMount } from '@material-ui/core/test-utils'; +import pgAdmin from 'sources/pgadmin'; +import {messages} from '../fake_messages'; +import SchemaView from '../../../pgadmin/static/js/SchemaView'; +import BaseUISchema from 'sources/SchemaView/base_schema.ui'; +import MViewSchema from '../../../pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/mview.ui'; + + +class MockSchema extends BaseUISchema { + get baseFields() { + return []; + } +} + +describe('MaterializedViewSchema', ()=>{ + let mount; + let schemaObj = new MViewSchema( + ()=>new MockSchema(), + ()=>new MockSchema(), + { + role: ()=>[], + schema: ()=>[], + spcname: ()=>[], + }, + { + owner: 'postgres', + schema: 'public' + } + ); + let getInitData = ()=>Promise.resolve({}); + + /* Use createMount so that material ui components gets the required context */ + /* https://material-ui.com/guides/testing/#api */ + beforeAll(()=>{ + mount = createMount(); + }); + + afterAll(() => { + mount.cleanUp(); + }); + + beforeEach(()=>{ + jasmineEnzyme(); + /* messages used by validators */ + pgAdmin.Browser = pgAdmin.Browser || {}; + pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages; + pgAdmin.Browser.utils = pgAdmin.Browser.utils || {}; + }); + + it('create', ()=>{ + mount({}} + 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.definition = null; + schemaObj.validate(state, setError); + expect(setError).toHaveBeenCalledWith('definition', 'Please enter view definition.'); + + state.definition = 'SELECT 1;'; + schemaObj.validate(state, setError); + expect(setError).toHaveBeenCalledWith('definition', null); + + state.definition = 'SELECT 1'; + schemaObj.validate(state, setError); + expect(setError).toHaveBeenCalledWith('definition', null); + + state.service = 'Test'; + state.definition = 'SELECT 1'; + schemaObj.validate(state, setError); + expect(setError).toHaveBeenCalledWith('definition', null); + + }); +}); + diff --git a/web/regression/javascript/schema_ui_files/view.ui.spec.js b/web/regression/javascript/schema_ui_files/view.ui.spec.js index d801b1c0b..244a55390 100644 --- a/web/regression/javascript/schema_ui_files/view.ui.spec.js +++ b/web/regression/javascript/schema_ui_files/view.ui.spec.js @@ -15,7 +15,7 @@ import pgAdmin from 'sources/pgadmin'; import {messages} from '../fake_messages'; import SchemaView from '../../../pgadmin/static/js/SchemaView'; import BaseUISchema from 'sources/SchemaView/base_schema.ui'; -import ViewSchema from '../../../pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.ui.js'; +import ViewSchema from '../../../pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.ui'; class MockSchema extends BaseUISchema {