From 351cb3e6cadbe8ea668a76beab460938a0f6312c Mon Sep 17 00:00:00 2001 From: Nikhil Mohite Date: Mon, 16 Aug 2021 19:48:08 +0530 Subject: [PATCH] Port Triggers node to react. Fixes #6672 --- .../databases/schemas/functions/__init__.py | 3 +- .../functions/static/js/trigger_function.js | 8 +- .../tables/triggers/static/js/trigger.js | 15 + .../tables/triggers/static/js/trigger.ui.js | 492 ++++++++++++++++++ .../schedules/static/js/pga_schedule.ui.js | 1 + web/pgadmin/static/js/SchemaView/FormView.jsx | 1 + .../static/js/components/CodeMirror.jsx | 5 +- .../static/js/components/FormComponents.jsx | 14 +- .../schema_ui_files/trigger.ui.spec.js | 378 ++++++++++++++ 9 files changed, 908 insertions(+), 9 deletions(-) create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/js/trigger.ui.js create mode 100644 web/regression/javascript/schema_ui_files/trigger.ui.spec.js diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/__init__.py index 82c9bed51..60fd80464 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/__init__.py @@ -1480,8 +1480,7 @@ class FunctionView(PGChildNodeView, DataTypeReader, SchemaDiffObjectCompare): parallel_dict = {'u': 'UNSAFE', 's': 'SAFE', 'r': 'RESTRICTED'} # Get Schema Name from its OID. - if self.node_type != 'trigger_function': - self._get_schema_name_from_oid(data) + self._get_schema_name_from_oid(data) if fnid is not None: # Edit Mode diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/trigger_function.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/trigger_function.js index 83ed9c6e3..84e1b03aa 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/trigger_function.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/trigger_function.js @@ -7,7 +7,7 @@ // ////////////////////////////////////////////////////////////// import TriggerFunctionSchema from './trigger_function.ui'; -import { getNodeListByName, getNodeAjaxOptions } from '../../../../../../../static/js/node_ajax'; +import { getNodeListByName, getNodeListById, getNodeAjaxOptions } from '../../../../../../../static/js/node_ajax'; import { getNodeVariableSchema } from '../../../../../static/js/variable.ui'; import { getNodePrivilegeRoleSchema } from '../../../../../static/js/privilege.ui'; @@ -91,7 +91,9 @@ define('pgadmin.node.trigger_function', [ ()=>getNodeVariableSchema(this, treeNodeInfo, itemNodeData, false, false), { role: ()=>getNodeListByName('role', treeNodeInfo, itemNodeData), - schema: ()=>getNodeListByName('schema', treeNodeInfo, itemNodeData, {cacheLevel: 'database'}), + schema: ()=>getNodeListById(pgBrowser.Nodes['schema'], treeNodeInfo, itemNodeData, { + cacheLevel: 'database' + }), language: ()=>getNodeAjaxOptions('get_languages', this, treeNodeInfo, itemNodeData, {noCache: true}, (res) => { return _.reject(res, function(o) { return o.label == 'sql' || o.label == 'edbspl'; @@ -101,7 +103,7 @@ define('pgadmin.node.trigger_function', [ }, { funcowner: pgBrowser.serverInfo[treeNodeInfo.server._id].user.name, - pronamespace: treeNodeInfo.schema ? treeNodeInfo.schema.label : '' + pronamespace: treeNodeInfo.schema ? treeNodeInfo.schema._id : null } ); }, diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/js/trigger.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/js/trigger.js index 22e20fa92..2b9904e0d 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/js/trigger.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/js/trigger.js @@ -6,6 +6,8 @@ // This software is released under the PostgreSQL Licence // ////////////////////////////////////////////////////////////// +import { getNodeListByName, getNodeAjaxOptions } from '../../../../../../../../static/js/node_ajax'; +import TriggerSchema from './trigger.ui'; define('pgadmin.node.trigger', [ 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', @@ -173,6 +175,19 @@ define('pgadmin.node.trigger', [ }, canDrop: SchemaChildTreeNode.isTreeItemOfChildOfSchema, canDropCascade: SchemaChildTreeNode.isTreeItemOfChildOfSchema, + getSchema: function(treeNodeInfo, itemNodeData) { + return new TriggerSchema( + { + triggerFunction: ()=>getNodeAjaxOptions('get_triggerfunctions', this, treeNodeInfo, itemNodeData, {cacheLevel: 'trigger_function'}, (data) => { + return _.reject(data, function(option) { + return option.label == ''; + }); + }), + columns: ()=> getNodeListByName('column', treeNodeInfo, itemNodeData, { cacheLevel: 'column'}), + nodeInfo: treeNodeInfo + }, + ); + }, model: pgAdmin.Browser.Node.Model.extend({ idAttribute: 'oid', defaults: { diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/js/trigger.ui.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/js/trigger.ui.js new file mode 100644 index 000000000..974142f7e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/js/trigger.ui.js @@ -0,0 +1,492 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2021, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import gettext from 'sources/gettext'; +import BaseUISchema from 'sources/SchemaView/base_schema.ui'; +import { isEmptyString } from 'sources/validators'; + +export class EventSchema extends BaseUISchema { + constructor(fieldOptions={}, initValues) { + super({ + evnt_update: false, + evnt_insert: false, + evnt_delete: false, + evnt_truncate: false, + is_row_trigger: false, + is_constraint_trigger: false, + ...initValues, + }); + + this.fieldOptions = { + nodeInfo: null, + ...fieldOptions, + }; + + this.nodeInfo = this.fieldOptions.nodeInfo; + + } + + get idAttribute() { + return 'oid'; + } + + inSchemaWithModelCheck(state) { + // Check if we are under schema node & in 'create' mode + if(this.nodeInfo && 'schema' in this.nodeInfo) { + // We will disable control if it's in 'edit' mode + return !this.isNew(state); + } + return true; + } + + get baseFields() { + let obj = this; + return [{ + id: 'evnt_insert', label: gettext('INSERT'), + type: 'switch', mode: ['create','edit', 'properties'], + group: gettext('Events'), + disabled: (state) => { + var evn_insert = state.evnt_insert; + if (!_.isUndefined(evn_insert) && obj.nodeInfo && obj.nodeInfo.server.server_type == 'ppas' && obj.isNew(state)) + return false; + return obj.inSchemaWithModelCheck(state); + }, + },{ + id: 'evnt_update', label: gettext('UPDATE'), + type: 'switch', mode: ['create','edit', 'properties'], + group: gettext('Events'), + disabled: (state) => { + var evn_update = state.evnt_update; + if (!_.isUndefined(evn_update) && obj.nodeInfo && obj.nodeInfo.server.server_type == 'ppas' && obj.isNew(state)) + return false; + return obj.inSchemaWithModelCheck(state); + }, + },{ + id: 'evnt_delete', label: gettext('DELETE'), + type: 'switch', mode: ['create','edit', 'properties'], + group: gettext('Events'), + disabled: (state) => { + var evn_delete = state.evnt_delete; + if (!_.isUndefined(evn_delete) && obj.nodeInfo && obj.nodeInfo.server.server_type == 'ppas' && obj.isNew(state)) + return false; + return obj.inSchemaWithModelCheck(state); + }, + },{ + id: 'evnt_truncate', label: gettext('TRUNCATE'), + type: 'switch', group: gettext('Events'), deps: ['is_row_trigger', 'is_constraint_trigger'], + disabled: (state) => { + var is_constraint_trigger = state.is_constraint_trigger, + is_row_trigger = state.is_row_trigger, + server_type = obj.nodeInfo ? obj.nodeInfo.server.server_type: null; + if (is_row_trigger == true){ + state.evnt_truncate = false; + return true; + } + + if (server_type === 'ppas' && !_.isUndefined(is_constraint_trigger) && + !_.isUndefined(is_row_trigger) && + is_constraint_trigger === false && obj.isNew(state)) + return false; + + return obj.inSchemaWithModelCheck(state); + }, + }]; + } + + validate(state, setError) { + + if (isEmptyString(state.service)) { + let errmsg = null; + /* events validation*/ + if (state.tfunction && !state.evnt_truncate && !state.evnt_delete && !state.evnt_update && !state.evnt_insert) { + errmsg = gettext('Specify at least one event.'); + //setError('evnt_truncate', errmsg); + //setError('evnt_delete', errmsg); + //setError('evnt_update', errmsg); + setError('evnt_insert', errmsg); + //setError('evnt_update', errmsg); + return true; + } else { + errmsg = null; + //setError('evnt_truncate', errmsg); + //setError('evnt_delete', errmsg); + //setError('evnt_update', errmsg); + setError('evnt_insert', errmsg); + } + } + } + +} + + +export default class TriggerSchema extends BaseUISchema { + constructor(fieldOptions={}, initValues) { + super({ + name: undefined, + is_row_trigger: true, + fires: 'BEFORE', + ...initValues + }); + + this.fieldOptions = { + triggerFunction: [], + //columns: [], + ...fieldOptions, + }; + this.nodeInfo = this.fieldOptions.nodeInfo; + + } + + get idAttribute() { + return 'oid'; + } + + inSchema() { + // Check if under schema node & in 'create' mode + if('catalog' in this.nodeInfo) { + return true; + } + return false; + } + + inSchemaWithModelCheck(state) { + // Check if we are under schema node & in 'create' mode + if('schema' in this.nodeInfo) { + // We will disable control if it's in 'edit' mode + return !this.isNew(state); + } + return true; + } + + disableTransition(state) { + if (!this.isNew()) + return true; + var flag = false, + evnt = null, + name = state.name, + evnt_count = 0; + + // Disable transition tables for view trigger and PG version < 100000 + if(_.indexOf(Object.keys(this.nodeInfo), 'table') == -1 || + this.nodeInfo.server.version < 100000) return true; + + if (name == 'tgoldtable') evnt = 'evnt_delete'; + else if (name == 'tgnewtable') evnt = 'evnt_insert'; + + if(state.evnt_insert) evnt_count++; + if(state.evnt_update) evnt_count++; + if(state.evnt_delete) evnt_count++; + + + // Disable transition tables if + // - It is a constraint trigger + // - Fires other than AFTER + // - More than one events enabled + // - Update event with the column list + + // Disable Old transition table if both UPDATE and DELETE events are disabled + // Disable New transition table if both UPDATE and INSERT events are disabled + if(!state.is_constraint_trigger && state.fires == 'AFTER' && + (state.evnt_update || state[evnt]) && evnt_count == 1) { + flag = (state.evnt_update && (_.size(state.columns) >= 1 && state.columns[0] != '')); + } + + if(flag && state.name) { + state.name = null; + } + + return flag; + } + + 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: 'int', mode: ['properties'], + },{ + id: 'is_enable_trigger', label: gettext('Trigger enabled?'), + mode: ['edit', 'properties'], group: gettext('Definition'), + type: 'select', + disabled: () => { + if('catalog' in obj.nodeInfo || 'view' in obj.nodeInfo) { + return true; + } + return false; + }, + options: [ + {label: gettext('Enable'), value: 'O'}, + {label: gettext('Enable Replica'), value: 'R'}, + {label: gettext('Enable Always'), value: 'A'}, + {label: gettext('Disable'), value: 'D'}, + ], + controlProps: { allowClear: false }, + },{ + id: 'is_row_trigger', label: gettext('Row trigger?'), + type: 'switch', group: gettext('Definition'), + mode: ['create','edit', 'properties'], + deps: ['is_constraint_trigger'], + disabled: (state) => { + // Disabled if table is a partitioned table. + if (!obj.isNew()) + return true; + + if (obj.nodeInfo.table.is_partitioned && obj.nodeInfo.server.version < 110000) + { + state.is_row_trigger = false; + return true; + } + + // If constraint trigger is set to True then row trigger will + // automatically set to True and becomes disable + var is_constraint_trigger = state.is_constraint_trigger; + if(!obj.inSchemaWithModelCheck(state)) { + if(!_.isUndefined(is_constraint_trigger) && + is_constraint_trigger === true) { + // change it's model value + state.is_row_trigger = true; + return true; + } else { + return false; + } + } else { + // Check if it is row trigger then enabled it. + var is_row_trigger = state.is_row_trigger; + if (!_.isUndefined(is_row_trigger) && obj.nodeInfo.server.server_type == 'ppas') { + return false; + } + // Disable it + return true; + } + }, + },{ + id: 'is_constraint_trigger', label: gettext('Constraint trigger?'), + type: 'switch', + mode: ['create','edit', 'properties'], + group: gettext('Definition'), + deps: ['tfunction'], + disabled: (state) => { + // Disabled if table is a partitioned table. + var tfunction = state.tfunction; + if (( _.has(obj.nodeInfo, 'table') && _.has(obj.nodeInfo.table, 'is_partitioned') && + obj.nodeInfo.table.is_partitioned) || ( _.has(obj.nodeInfo, 'view')) || + (obj.nodeInfo.server.server_type === 'ppas' && !_.isUndefined(tfunction) && + tfunction === 'Inline EDB-SPL')) { + state.is_constraint_trigger = false; + return true; + } + return obj.inSchemaWithModelCheck(state); + }, + },{ + id: 'tgdeferrable', label: gettext('Deferrable?'), + type: 'switch', group: gettext('Definition'), + mode: ['create','edit', 'properties'], + deps: ['is_constraint_trigger'], + disabled: (state) => { + // If constraint trigger is set to True then only enable it + var is_constraint_trigger = state.is_constraint_trigger; + if(!obj.inSchemaWithModelCheck(state)) { + if(!_.isUndefined(is_constraint_trigger) && + is_constraint_trigger === true) { + return false; + } else { + // If value is already set then reset it to false + if(state.tgdeferrable) { + state.tgdeferrable = false; + } + return true; + } + } else { + // Disable it + return true; + } + }, + },{ + id: 'tginitdeferred', label: gettext('Deferred?'), + type: 'switch', group: gettext('Definition'), + mode: ['create','edit', 'properties'], + deps: ['tgdeferrable', 'is_constraint_trigger'], + disabled: (state) => { + // If Deferrable is set to True then only enable it + var tgdeferrable = state.tgdeferrable; + if(!obj.inSchemaWithModelCheck(state)) { + if(!_.isUndefined(tgdeferrable) && tgdeferrable) { + return false; + } else { + // If value is already set then reset it to false + if(obj.tginitdeferred) { + state.tginitdeferred = false; + } + // If constraint trigger is set then do not disable + return state.is_constraint_trigger ? false : true; + } + } else { + // Disable it + return true; + } + }, + },{ + id: 'tfunction', label: gettext('Trigger function'), + type: 'select', disabled: obj.inSchemaWithModelCheck, + mode: ['create','edit', 'properties'], group: gettext('Definition'), + control: 'node-ajax-options', url: 'get_triggerfunctions', url_jump_after_node: 'schema', + options: obj.fieldOptions.triggerFunction, + cache_node: 'trigger_function', + },{ + id: 'tgargs', label: gettext('Arguments'), cell: 'text', + group: gettext('Definition'), + type: 'text',mode: ['create','edit', 'properties'], deps: ['tfunction'], + disabled: (state) => { + // We will disable it when EDB PPAS and trigger function is + // set to Inline EDB-SPL + var tfunction = state.tfunction, + server_type = obj.nodeInfo.server.server_type; + if(!obj.inSchemaWithModelCheck(state)) { + if(server_type === 'ppas' && + !_.isUndefined(tfunction) && + tfunction === 'Inline EDB-SPL') { + // Disable and clear its value + state.tgargs = undefined; + return true; + } else { + return false; + } + } else { + // Disable it + return true; + } + }, + },{ + id: 'fires', label: gettext('Fires'), deps: ['is_constraint_trigger'], + mode: ['create','edit', 'properties'], group: gettext('Events'), + options: () => { + var table_options = [ + {label: 'BEFORE', value: 'BEFORE'}, + {label: 'AFTER', value: 'AFTER'}], + view_options = [ + {label: 'BEFORE', value: 'BEFORE'}, + {label: 'AFTER', value: 'AFTER'}, + {label: 'INSTEAD OF', value: 'INSTEAD OF'}]; + // If we are under table then show table specific options + if(_.indexOf(Object.keys(obj.nodeInfo), 'table') != -1) { + return table_options; + } else { + return view_options; + } + }, + type: 'select', controlProps: { allowClear: false }, + disabled: (state) => { + if (!obj.isNew()) + return true; + // If contraint trigger is set to True then only enable it + var is_constraint_trigger = obj.is_constraint_trigger; + if(!obj.inSchemaWithModelCheck(state)) { + if(!_.isUndefined(is_constraint_trigger) && + is_constraint_trigger === true) { + state.fires = 'AFTER'; + return true; + } else { + return false; + } + } else { + // Check if it is row trigger then enabled it. + var fires_ = state.fires; + if (!_.isUndefined(fires_) && obj.nodeInfo.server.server_type == 'ppas') { + return false; + } + // Disable it + return true; + } + }, + },{ + type: 'nested-fieldset', mode: ['create','edit', 'properties'], + label: gettext('Events'), group: gettext('Events'), + schema: new EventSchema({nodeInfo: obj.nodeInfo}), + },{ + id: 'whenclause', label: gettext('When'), + type: 'sql', + readonly: obj.inSchemaWithModelCheck, + mode: ['create', 'edit', 'properties'], visible: true, + group: gettext('Events'), + },{ + id: 'columns', label: gettext('Columns'), + type: 'select', controlProps: { multiple: true }, + deps: ['evnt_update'], group: gettext('Events'), + options: obj.fieldOptions.columns, + disabled: (state) => { + if(obj.nodeInfo && 'catalog' in obj.nodeInfo) { + return true; + } + //Disable in edit mode + if (!obj.isNew()) { + return true; + } + // Enable column only if update event is set true + var isUpdate = state.evnt_update; + if(!_.isUndefined(isUpdate) && isUpdate) { + return false; + } + return true; + }, + },{ + id: 'tgoldtable', label: gettext('Old table'), + type: 'text', group: gettext('Transition'), + cell: 'text', mode: ['create', 'edit', 'properties'], + deps: ['fires', 'is_constraint_trigger', 'evnt_insert', 'evnt_update', 'evnt_delete', 'columns'], + disabled: obj.disableTransition, + },{ + id: 'tgnewtable', label: gettext('New table'), + type: 'text', group: gettext('Transition'), + cell: 'string', mode: ['create', 'edit', 'properties'], + deps: ['fires', 'is_constraint_trigger', 'evnt_insert', 'evnt_update', 'evnt_delete', 'columns'], + disabled: obj.disableTransition, + },{ + id: 'prosrc', label: gettext('Code'), group: gettext('Code'), + type: 'sql', mode: ['create', 'edit'], deps: ['tfunction'], + isFullTab: true, + visible: true, + readonly: (state) => { + // We will enable it only when EDB PPAS and trigger function is + // set to Inline EDB-SPL + var tfunction = state.tfunction, + server_type = obj.nodeInfo.server.server_type; + + return (server_type !== 'ppas' || + _.isUndefined(tfunction) || + tfunction !== 'Inline EDB-SPL'); + }, + },{ + id: 'is_sys_trigger', label: gettext('System trigger?'), cell: 'text', + type: 'switch', disabled: obj.inSchemaWithModelCheck, mode: ['properties'], + },{ + id: 'description', label: gettext('Comment'), cell: 'string', + type: 'multiline', mode: ['properties', 'create', 'edit'], + disabled: obj.inSchema, + }]; + } + + validate(state, setError) { + let errmsg = null; + + if (isEmptyString(state.service)) { + + /* trigger function validation*/ + if (isEmptyString(state.tfunction)) { + errmsg = gettext('Trigger function cannot be empty.'); + setError('tfunction', errmsg); + return true; + } else { + errmsg = null; + setError('tfunction', errmsg); + } + } + } +} diff --git a/web/pgadmin/browser/server_groups/servers/pgagent/schedules/static/js/pga_schedule.ui.js b/web/pgadmin/browser/server_groups/servers/pgagent/schedules/static/js/pga_schedule.ui.js index 16bb02201..7320238d1 100644 --- a/web/pgadmin/browser/server_groups/servers/pgagent/schedules/static/js/pga_schedule.ui.js +++ b/web/pgadmin/browser/server_groups/servers/pgagent/schedules/static/js/pga_schedule.ui.js @@ -405,6 +405,7 @@ export default class PgaJobScheduleSchema extends BaseUISchema { } ]; } + validate(state, setError) { if (isEmptyString(state.jscstart)) { setError('jscstart', gettext('Please enter the start time.')); diff --git a/web/pgadmin/static/js/SchemaView/FormView.jsx b/web/pgadmin/static/js/SchemaView/FormView.jsx index 56c748a4c..310f2ab5b 100644 --- a/web/pgadmin/static/js/SchemaView/FormView.jsx +++ b/web/pgadmin/static/js/SchemaView/FormView.jsx @@ -63,6 +63,7 @@ function SQLTab({active, getSQLValue}) { readOnly: true, }} isAsync={true} + readonly={true} />; } diff --git a/web/pgadmin/static/js/components/CodeMirror.jsx b/web/pgadmin/static/js/components/CodeMirror.jsx index cb03ab18f..4745fda9f 100644 --- a/web/pgadmin/static/js/components/CodeMirror.jsx +++ b/web/pgadmin/static/js/components/CodeMirror.jsx @@ -13,7 +13,7 @@ import {useOnScreen} from 'sources/custom_hooks'; import PropTypes from 'prop-types'; /* React wrapper for CodeMirror */ -export default function CodeMirror({name, value, options, events, ...props}) { +export default function CodeMirror({currObj, name, value, options, events, ...props}) { const taRef = useRef(); const cmObj = useRef(); const cmWrapper = useRef(); @@ -24,6 +24,8 @@ export default function CodeMirror({name, value, options, events, ...props}) { cmObj.current = new OrigCodeMirror.fromTextArea( taRef.current, options); + currObj && currObj(cmObj.current); + if(cmObj.current) { try { cmWrapper.current = cmObj.current.getWrapperElement(); @@ -65,6 +67,7 @@ export default function CodeMirror({name, value, options, events, ...props}) { } CodeMirror.propTypes = { + currObj: PropTypes.func, name: PropTypes.string, value: PropTypes.string, options: PropTypes.object, diff --git a/web/pgadmin/static/js/components/FormComponents.jsx b/web/pgadmin/static/js/components/FormComponents.jsx index 43a80bef5..7505ad044 100644 --- a/web/pgadmin/static/js/components/FormComponents.jsx +++ b/web/pgadmin/static/js/components/FormComponents.jsx @@ -137,11 +137,19 @@ FormInput.propTypes = { testcid: PropTypes.any, }; -export function InputSQL({value, options, onChange, ...props}) { +export function InputSQL({value, options, onChange, readonly, ...props}) { const classes = useStyles(); + const cmObj = useRef(); + + useEffect(()=>{ + if(cmObj.current) { + cmObj.current.setOption('readOnly', readonly); + } + }, [readonly]); return ( cmObj.current=obj} value={value||''} options={{ lineNumbers: true, @@ -161,8 +169,8 @@ export function InputSQL({value, options, onChange, ...props}) { InputSQL.propTypes = { value: PropTypes.string, options: PropTypes.object, - onChange: PropTypes.func - + onChange: PropTypes.func, + readonly: PropTypes.bool }; export function FormInputSQL({hasError, required, label, className, helpMessage, testcid, value, controlProps, noLabel, ...props}) { diff --git a/web/regression/javascript/schema_ui_files/trigger.ui.spec.js b/web/regression/javascript/schema_ui_files/trigger.ui.spec.js new file mode 100644 index 000000000..c437b33ff --- /dev/null +++ b/web/regression/javascript/schema_ui_files/trigger.ui.spec.js @@ -0,0 +1,378 @@ +///////////////////////////////////////////////////////////// +// +// 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 TriggerSchema, { EventSchema } from '../../../pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/js/trigger.ui'; + +describe('TriggerSchema', ()=>{ + let mount; + let schemaObj = new TriggerSchema( + { + triggerFunction: [], + columns: [], + nodeInfo: { + schema: {}, + server: {user: {name:'postgres', id:0}, server_type: 'pg', version: 90400}, + table: {is_partitioned: false} + } + } + ); + 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.tfunction = null; + schemaObj.validate(state, setError); + expect(setError).toHaveBeenCalledWith('tfunction', 'Trigger function cannot be empty.'); + + state.tfunction = 'public'; + schemaObj.validate(state, setError); + expect(setError).toHaveBeenCalledWith('tfunction', null); + }); + + it('catalog create', ()=>{ + let catalogSchemaObj = new TriggerSchema( + { + triggerFunction: [], + columns: [], + nodeInfo: { + catalog: {}, + server: {user: {name:'postgres', id:0}, server_type: 'pg', version: 90400}, + table: {is_partitioned: false} + } + } + ); + + mount({}} + onClose={()=>{}} + onHelp={()=>{}} + onEdit={()=>{}} + onDataChange={()=>{}} + confirmOnCloseReset={false} + hasSQL={false} + disableSqlHelp={false} + />); + }); + + it('catalog properties', ()=>{ + let catalogPropertiesSchemaObj = new TriggerSchema( + { + triggerFunction: [], + columns: [], + nodeInfo: { + catalog: {}, + server: {user: {name:'postgres', id:0}, server_type: 'pg', version: 90400}, + table: {is_partitioned: false} + } + } + ); + + mount({}} + onEdit={()=>{}} + />); + }); + + it('edit disableTransition', ()=>{ + let editSchemaObj = new TriggerSchema( + { + triggerFunction: [], + columns: [], + nodeInfo: { + catalog: {}, + server: {user: {name:'postgres', id:0}, server_type: 'pg', version: 100000}, + table: {is_partitioned: false} + } + } + ); + + let initData = ()=>Promise.resolve({ + tgoldtable: 'tgoldtable', + evnt_insert: true, + evnt_update: true, + evnt_delete: true, + is_constraint_trigger: true, + name: 'tgoldtable', + fires: 'AFTER' + }); + + mount({}} + onClose={()=>{}} + onHelp={()=>{}} + onEdit={()=>{}} + onDataChange={()=>{}} + confirmOnCloseReset={false} + hasSQL={false} + disableSqlHelp={false} + />); + }); + + it('edit disableTransition tgnewtable', ()=>{ + let editSchemaObj = new TriggerSchema( + { + triggerFunction: [], + columns: [], + nodeInfo: { + catalog: {}, + server: {user: {name:'postgres', id:0}, server_type: 'pg', version: 100000}, + table: {is_partitioned: false} + } + } + ); + + let initData = ()=>Promise.resolve({ + tgoldtable: 'tgnewtable', + evnt_insert: true, + evnt_update: true, + evnt_delete: true, + is_constraint_trigger: false, + name: 'tgnewtable', + fires: 'AFTER' + }); + + mount({}} + onClose={()=>{}} + onHelp={()=>{}} + onEdit={()=>{}} + onDataChange={()=>{}} + confirmOnCloseReset={false} + hasSQL={false} + disableSqlHelp={false} + />); + }); + +}); + + +describe('TriggerEventsSchema', ()=>{ + let mount; + let schemaObj = new EventSchema( + { + nodeInfo: { + server: {user: {name:'postgres', id:0}, server_type: 'pg', version: 90400}, + table: {is_partitioned: false} + } + } + ); + 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('properties', ()=>{ + mount({}} + onEdit={()=>{}} + />); + }); + + it('edit', ()=>{ + mount({}} + onClose={()=>{}} + onHelp={()=>{}} + onEdit={()=>{}} + onDataChange={()=>{}} + confirmOnCloseReset={false} + hasSQL={false} + disableSqlHelp={false} + />); + }); + + it('validate', ()=>{ + let state = {}; + let setError = jasmine.createSpy('setError'); + + + state.tfunction = 'public'; + state.evnt_truncate = false; + state.evnt_delete = false; + state.evnt_update = false; + state.evnt_insert = false; + schemaObj.validate(state, setError); + expect(setError).toHaveBeenCalledWith('evnt_insert', 'Specify at least one event.'); + + state.tfunction = 'public'; + state.evnt_insert = true; + schemaObj.validate(state, setError); + expect(setError).toHaveBeenCalledWith('evnt_insert', null); + }); + + //spyOn(schemaObj, 'isNew’).and.returnValue(true); + + /*it('evnt_insert disabled', ()=>{ + let disabled = _.find(schemaObj.fields, (f)=>f.id=='evnt_insert').disabled; + disabled({evnt_insert : true}); + }); + + it('evnt_update disabled', ()=>{ + let disabled = _.find(schemaObj.fields, (f)=>f.id=='evnt_update').disabled; + disabled({evnt_update : true}); + }); + + it('evnt_delete disabled', ()=>{ + let disabled = _.find(schemaObj.fields, (f)=>f.id=='evnt_delete').disabled; + disabled({evnt_delete : true}); + }); + + it('evnt_truncate disabled', ()=>{ + getInitData = ()=>Promise.resolve({is_constraint_trigger: true}); + let disabled = _.find(schemaObj.fields, (f)=>f.id=='evnt_truncate').disabled; + disabled({evnt_truncate : true}); + });*/ + +});