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 87977fbc7..2f196ef63 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
@@ -7,6 +7,10 @@
//
//////////////////////////////////////////////////////////////
+import { getNodeListByName } from '../../../../../../../static/js/node_ajax';
+import { getNodePrivilegeRoleSchema } from '../../../../../static/js/privilege.ui';
+import ViewSchema from './view.ui';
+
define('pgadmin.node.view', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform',
@@ -90,11 +94,24 @@ define('pgadmin.node.view', [
},
]);
},
-
+ getSchema: function(treeNodeInfo, itemNodeData) {
+ return new ViewSchema(
+ (privileges)=>getNodePrivilegeRoleSchema('', treeNodeInfo, itemNodeData, privileges),
+ treeNodeInfo,
+ {
+ role: ()=>getNodeListByName('role', treeNodeInfo, itemNodeData),
+ schema: ()=>getNodeListByName('schema', treeNodeInfo, itemNodeData, {cacheLevel: 'database'}),
+ },
+ {
+ 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.
- */
+ */
model: pgBrowser.Node.Model.extend({
idAttribute: 'oid',
initialize: function(attrs, args) {
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
new file mode 100644
index 000000000..77c66f86a
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.ui.js
@@ -0,0 +1,186 @@
+/////////////////////////////////////////////////////////////
+//
+// 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 ViewSchema extends BaseUISchema {
+ constructor(getPrivilegeRoleSchema, nodeInfo, fieldOptions={}, initValues) {
+ super({
+ owner: undefined,
+ schema: undefined,
+ ...initValues
+ });
+ this.getPrivilegeRoleSchema = getPrivilegeRoleSchema;
+ this.nodeInfo = nodeInfo;
+ this.warningText = null;
+ this.fieldOptions = {
+ role: [],
+ schema: [],
+ ...fieldOptions,
+ };
+
+ }
+
+ get idAttribute() {
+ return 'oid';
+ }
+
+ notInSchema() {
+ if(this.node_info && 'catalog' in this.node_info) {
+ return true;
+ }
+ return false;
+ }
+
+
+ get baseFields() {
+ let obj = this;
+ return [{
+ id: 'name', label: gettext('Name'), cell: 'text',
+ type: 'text', disabled: obj.notInSchema, noEmpty: true,
+ },{
+ id: 'oid', label: gettext('OID'), cell: 'text',
+ type: 'text', mode: ['properties'],
+ },{
+ id: 'owner', label: gettext('Owner'), cell: 'text',
+ node: 'role', disabled: obj.notInSchema,
+ type: 'select', controlProps: { allowClear: false },
+ options: obj.fieldOptions.role
+ },{
+ id: 'schema', label: gettext('Schema'), cell: 'text',
+ type: 'select', disabled: obj.notInSchema, mode: ['create', 'edit'],
+ controlProps: {
+ allowClear: false,
+ first_empty: false,
+ },
+ options: obj.fieldOptions.schema
+ },{
+ id: 'system_view', label: gettext('System 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', disabled: obj.notInSchema,
+ },{
+ id: 'security_barrier', label: gettext('Security barrier?'),
+ type: 'switch', min_version: '90200', group: gettext('Definition'),
+ disabled: obj.notInSchema,
+ },{
+ id: 'check_option', label: gettext('Check options'),
+ type: 'select', group: gettext('Definition'),
+ min_version: '90400', mode:['properties', 'create', 'edit'],
+ controlProps: {
+ // Set select2 option width to 100%
+ allowClear: false,
+ }, disabled: obj.notInSchema,
+ options:[{
+ label: gettext('No'), value: 'no',
+ },{
+ label: gettext('Local'), value: 'local',
+ },{
+ label: gettext('Cascaded'), value: 'cascaded',
+ }],
+ },{
+ id: 'definition', label: gettext('Code'), cell: 'text',
+ type: 'sql', mode: ['create', 'edit'], group: gettext('Code'),
+ noLabel: true,
+ disabled: obj.notInSchema,
+ controlProps: {
+ className: ['sql-code-control'],
+ },
+ },
+
+ {
+ id: 'datacl', label: gettext('Privileges'), type: 'collection',
+ schema: this.getPrivilegeRoleSchema(['a', 'r', 'w', 'd', 'D', 'x', 't']),
+ 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)) {
+
+ /* view definition validation*/
+ if (isEmptyString(state.definition)) {
+ errmsg = gettext('Please enter view code.');
+ setError('definition', errmsg);
+ return true;
+ } else {
+ errmsg = null;
+ setError('definition', errmsg);
+ }
+
+ if (state.definition) {
+ if (!(obj.nodeInfo.server.server_type == 'pg' &&
+ // No need to check this when creating a view
+ obj.origData.oid !== undefined
+ ) || !(
+ state.definition !== obj.origData.definition
+ )) {
+ obj.warningText = null;
+ return true;
+ }
+
+ let old_def = obj.origData.definition &&
+ obj.origData.definition.replace(
+ /\s/gi, ''
+ ).split('FROM'),
+ new_def = [];
+
+ if (state.definition !== undefined) {
+ new_def = state.definition.replace(
+ /\s/gi, ''
+ ).split('FROM');
+ }
+
+ if ((old_def.length != new_def.length) || (
+ old_def.length > 1 && (
+ old_def[0] != new_def[0]
+ )
+ )) {
+ obj.warningText = gettext(
+ 'Changing the columns in a view requires dropping and re-creating the view. This may fail if other objects are dependent upon this view, or may cause procedural functions to fail if they are not modified to take account of the changes.'
+ ) + '
' + gettext('Do you wish to continue?') +
+ '';
+ } else {
+ obj.warningText = null;
+ }
+ return true;
+ }
+
+ } else {
+ errmsg = null;
+ _.each(['definition'], (item) => {
+ setError(item, errmsg);
+ });
+ }
+ }
+}
diff --git a/web/pgadmin/static/js/SchemaView/FormView.jsx b/web/pgadmin/static/js/SchemaView/FormView.jsx
index c561a80d7..0e86385a1 100644
--- a/web/pgadmin/static/js/SchemaView/FormView.jsx
+++ b/web/pgadmin/static/js/SchemaView/FormView.jsx
@@ -167,6 +167,8 @@ export default function FormView({
}
}, []);
+ let fullTabs = [];
+
/* Prepare the array of components based on the types */
schema.fields.forEach((field)=>{
let {visible, disabled, readonly, canAdd, canEdit, canDelete, modeSupported} =
@@ -232,6 +234,10 @@ export default function FormView({
* lets pass the new changes to dependent and get the new values
* from there as well.
*/
+ if(field.noLabel) {
+ tabsClassname[group] = classes.fullSpace;
+ fullTabs.push(group);
+ }
tabs[group].push(
useMemo(()=>{
@@ -282,6 +288,7 @@ export default function FormView({
useMemo(()=>, [sqlTabActive]),
];
tabsClassname[sqlTabName] = classes.fullSpace;
+ fullTabs.push(sqlTabName);
}
useEffect(()=>{
@@ -314,7 +321,7 @@ export default function FormView({
{Object.keys(tabs).map((tabName, i)=>{
return (
+ className={fullTabs.indexOf(tabName) == -1 ? classes.nestedControl : null}>
{tabs[tabName]}
);
diff --git a/web/pgadmin/static/js/SchemaView/MappedControl.jsx b/web/pgadmin/static/js/SchemaView/MappedControl.jsx
index c11159b50..16b73d13a 100644
--- a/web/pgadmin/static/js/SchemaView/MappedControl.jsx
+++ b/web/pgadmin/static/js/SchemaView/MappedControl.jsx
@@ -18,7 +18,7 @@ import PropTypes from 'prop-types';
import CustomPropTypes from '../custom_prop_types';
/* Control mapping for form view */
-function MappedFormControlBase({type, value, id, onChange, className, visible, inputRef, ...props}) {
+function MappedFormControlBase({type, value, id, onChange, className, visible, inputRef, noLabel, ...props}) {
const name = id;
const onTextChange = useCallback((e) => {
let value = e;
@@ -90,7 +90,7 @@ function MappedFormControlBase({type, value, id, onChange, className, visible, i
case 'file':
return ;
case 'sql':
- return ;
+ return ;
default:
return <>>;
}
@@ -108,6 +108,7 @@ MappedFormControlBase.propTypes = {
]),
visible: PropTypes.bool,
inputRef: CustomPropTypes.ref,
+ noLabel: PropTypes.bool
};
/* Control mapping for grid cell view */
@@ -202,7 +203,7 @@ const ALLOWED_PROPS_FIELD_COMMON = [
];
const ALLOWED_PROPS_FIELD_FORM = [
- 'type', 'onChange', 'state',
+ 'type', 'onChange', 'state', 'noLabel'
];
const ALLOWED_PROPS_FIELD_CELL = [
diff --git a/web/pgadmin/static/js/SchemaView/index.jsx b/web/pgadmin/static/js/SchemaView/index.jsx
index 246c36a29..6c3dcc952 100644
--- a/web/pgadmin/static/js/SchemaView/index.jsx
+++ b/web/pgadmin/static/js/SchemaView/index.jsx
@@ -485,7 +485,25 @@ function SchemaDialogView({
} else {
changeData[schema.idAttribute] = schema.origData[schema.idAttribute];
}
+ if (schema.warningText) {
+ pgAlertify().confirm(
+ gettext('Warning'),
+ schema.warningText,
+ ()=> {
+ save(changeData);
+ },
+ () => {
+ setSaving(false);
+ setLoaderText('');
+ return true;
+ }
+ );
+ } else {
+ save(changeData);
+ }
+ };
+ const save = (changeData) => {
props.onSave(isNew, changeData)
.then(()=>{
if(schema.informText) {
diff --git a/web/pgadmin/static/js/components/FormComponents.jsx b/web/pgadmin/static/js/components/FormComponents.jsx
index d4eef3529..fcca88579 100644
--- a/web/pgadmin/static/js/components/FormComponents.jsx
+++ b/web/pgadmin/static/js/components/FormComponents.jsx
@@ -155,12 +155,16 @@ InputSQL.propTypes = {
};
-export function FormInputSQL({hasError, required, label, className, helpMessage, testcid, value, controlProps, ...props}) {
- return (
-
-
-
- );
+export function FormInputSQL({hasError, required, label, className, helpMessage, testcid, value, controlProps, noLabel, ...props}) {
+ if(noLabel) {
+ return ;
+ } else {
+ return (
+
+
+
+ );
+ }
}
FormInputSQL.propTypes = {
hasError: PropTypes.bool,
diff --git a/web/regression/javascript/schema_ui_files/event_trigger.ui.spec.js b/web/regression/javascript/schema_ui_files/event_trigger.ui.spec.js
index 9de9cafe3..37d318bb4 100644
--- a/web/regression/javascript/schema_ui_files/event_trigger.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/event_trigger.ui.spec.js
@@ -106,6 +106,15 @@ describe('EventTriggerSchema', ()=>{
schemaObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('eventfunname', 'Event trigger function cannot be empty.');
+ state.eventfunname = 'Test';
+ schemaObj.validate(state, setError);
+ expect(setError).toHaveBeenCalledWith('eventfunname', null);
+
+ state.service = 'Test';
+ state.eventfunname = 'Test';
+ schemaObj.validate(state, setError);
+ expect(setError).toHaveBeenCalledWith('eventfunname', 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
new file mode 100644
index 000000000..d801b1c0b
--- /dev/null
+++ b/web/regression/javascript/schema_ui_files/view.ui.spec.js
@@ -0,0 +1,134 @@
+/////////////////////////////////////////////////////////////
+//
+// 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 ViewSchema from '../../../pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.ui.js';
+
+
+class MockSchema extends BaseUISchema {
+ get baseFields() {
+ return [];
+ }
+}
+
+describe('ViewSchema', ()=>{
+ let mount;
+ let schemaObj = new ViewSchema(
+ ()=>new MockSchema(),
+ {server: {server_type: 'pg'}},
+ {
+ role: ()=>[],
+ schema: ()=>[],
+ },
+ {
+ 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 code.');
+
+ 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);
+
+ });
+});
+