diff --git a/web/pgadmin/browser/server_groups/servers/databases/event_triggers/static/js/event_trigger.js b/web/pgadmin/browser/server_groups/servers/databases/event_triggers/static/js/event_trigger.js
index f3dadbf89..6ff4ed920 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/event_triggers/static/js/event_trigger.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/event_triggers/static/js/event_trigger.js
@@ -7,6 +7,9 @@
//
//////////////////////////////////////////////////////////////
+import EventTriggerSchema from './event_trigger.ui';
+import { getNodeListByName, getNodeAjaxOptions } from '../../../../../../static/js/node_ajax';
+
define('pgadmin.node.event_trigger', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
'sources/pgadmin', 'pgadmin.browser',
@@ -64,6 +67,19 @@ define('pgadmin.node.event_trigger', [
},
]);
},
+ getSchema: function(treeNodeInfo, itemNodeData) {
+ return new EventTriggerSchema(
+ {
+ role: ()=>getNodeListByName('role', treeNodeInfo, itemNodeData),
+ function_names: ()=>getNodeAjaxOptions('fopts', this, treeNodeInfo, itemNodeData, {
+ cacheLevel: 'trigger_function',
+ }),
+ },
+ {
+ eventowner: pgBrowser.serverInfo[treeNodeInfo.server._id].user.name,
+ }
+ );
+ },
// Define the model for event trigger node
model: pgAdmin.Browser.Node.Model.extend({
idAttribute: 'oid',
diff --git a/web/pgadmin/browser/server_groups/servers/databases/event_triggers/static/js/event_trigger.ui.js b/web/pgadmin/browser/server_groups/servers/databases/event_triggers/static/js/event_trigger.ui.js
new file mode 100644
index 000000000..9b7710713
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/event_triggers/static/js/event_trigger.ui.js
@@ -0,0 +1,133 @@
+/////////////////////////////////////////////////////////////
+//
+// 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 EventTriggerSchema extends BaseUISchema {
+ constructor(fieldOptions={}, initValues) {
+ super({
+ oid: undefined,
+ name: undefined,
+ eventowner: undefined,
+ is_sys_obj: undefined,
+ comment: undefined,
+ enabled: 'O',
+ eventfuncoid: undefined,
+ eventfunname: undefined,
+ eventname: 'DDL_COMMAND_START',
+ when: undefined,
+ xmin: undefined,
+ source: undefined,
+ language: undefined,
+ ...initValues
+ });
+
+ this.fieldOptions = {
+ role: [],
+ function_names: [],
+ ...fieldOptions,
+ };
+
+ }
+
+ get idAttribute() {
+ return 'oid';
+ }
+
+
+ get baseFields() {
+ //let obj = this;
+ return [
+ {
+ id: 'name', label: gettext('Name'), cell: 'text',
+ type: 'text', noEmpty: true
+ },
+ {
+ id: 'oid', label: gettext('OID'), cell: 'text',
+ type: 'text', mode: ['properties'],
+ },{
+ id: 'eventowner', label: gettext('Owner'),
+ type: 'select', mode: ['properties', 'edit','create'], node: 'role',
+ options: this.fieldOptions.role
+ },
+ {
+ id: 'is_sys_obj', label: gettext('System event trigger?'),
+ cell:'switch', type: 'switch',
+ mode: ['properties'],
+ },
+ {
+ id: 'comment', label: gettext('Comment'), type: 'multiline',
+ },{
+ id: 'enabled', label: gettext('Trigger enabled?'),
+ group: gettext('Definition'), mode: ['properties', 'edit','create'],
+ options: [
+ {label: gettext('Enable'), value: 'O'},
+ {label: gettext('Disable'), value: 'D'},
+ {label: gettext('Replica'), value: 'R'},
+ {label: gettext('Always'), value: 'A'},
+ ],
+ type: 'select', controlProps: { allowClear: false, width: '100%' },
+ },{
+ id: 'eventfunname', label: gettext('Trigger function'),
+ type: 'select', group: gettext('Definition'),
+ options: this.fieldOptions.function_names
+ },{
+ id: 'eventname', label: gettext('Event'),
+ group: gettext('Definition'), cell: 'text',
+ options: [
+ {label: gettext('DDL COMMAND START'), value: 'DDL_COMMAND_START'},
+ {label: gettext('DDL COMMAND END'), value: 'DDL_COMMAND_END'},
+ {label: gettext('SQL DROP'), value: 'SQL_DROP'},
+ ],
+ type: 'select', controlProps: { allowClear: false, width: '100%' },
+ },
+ {
+ id: 'when', label: gettext('When TAG in'), cell: 'string',
+ type: 'sql', group: gettext('Definition'),
+ controlProps: {className:['custom_height_css_class']},
+ },
+ {
+ id: 'seclabels', label: gettext('Security labels'), type: 'collection',
+ schema: new SecLabelSchema(),
+ editable: false, group: gettext('Security'),
+ mode: ['edit', 'create'],
+ canAdd: true, canEdit: false, canDelete: true,
+ uniqueCol : ['provider'],
+ min_version: 90200,
+ }
+ ];
+ }
+
+ validate(state, setError) {
+ let errmsg = null;
+
+ if (isEmptyString(state.service)) {
+
+ /* Event function name validation*/
+ if (isEmptyString(state.eventfunname)) {
+ errmsg = gettext('Event trigger function cannot be empty.');
+ setError('eventfunname', errmsg);
+ return true;
+ } else {
+ errmsg = null;
+ setError('eventfunname', errmsg);
+ }
+
+ } else {
+ errmsg = null;
+ _.each(['eventfunname'], (item) => {
+ setError(item, errmsg);
+ });
+ }
+ }
+}
diff --git a/web/pgadmin/static/js/SchemaView/FormView.jsx b/web/pgadmin/static/js/SchemaView/FormView.jsx
index 1b78cf2bb..58b752de4 100644
--- a/web/pgadmin/static/js/SchemaView/FormView.jsx
+++ b/web/pgadmin/static/js/SchemaView/FormView.jsx
@@ -56,6 +56,7 @@ function SQLTab({active, getSQLValue}) {
options={{
readOnly: true,
}}
+ isAsync={true}
/>;
}
diff --git a/web/pgadmin/static/js/SchemaView/MappedControl.jsx b/web/pgadmin/static/js/SchemaView/MappedControl.jsx
index 6195d610f..182519054 100644
--- a/web/pgadmin/static/js/SchemaView/MappedControl.jsx
+++ b/web/pgadmin/static/js/SchemaView/MappedControl.jsx
@@ -10,7 +10,7 @@
import React, { useCallback } from 'react';
import _ from 'lodash';
-import { FormInputText, FormInputSelect, FormInputSwitch, FormInputCheckbox, InputSQL, FormInputColor, FormInputFileSelect, FormInputToggle, InputSwitch } from '../components/FormComponents';
+import { FormInputText, FormInputSelect, FormInputSwitch, FormInputCheckbox, FormInputColor, FormInputFileSelect, FormInputToggle, InputSwitch, FormInputSQL } from '../components/FormComponents';
import { InputSelect, InputText } from '../components/FormComponents';
import Privilege from '../components/Privilege';
import { evalFunc } from 'sources/utils';
@@ -28,6 +28,10 @@ function MappedFormControlBase({type, value, id, onChange, className, visible, i
onChange && onChange(value);
});
+ const onSqlChange = useCallback((e, cm) => {
+ onChange && onChange(cm.getValue());
+ });
+
const onIntChange = useCallback((e) => {
let value = e;
if(e && e.target) {
@@ -73,7 +77,7 @@ function MappedFormControlBase({type, value, id, onChange, className, visible, i
case 'file':
return ;
case 'sql':
- return ;
+ return ;
default:
return <>>;
}
diff --git a/web/pgadmin/static/js/components/CodeMirror.jsx b/web/pgadmin/static/js/components/CodeMirror.jsx
index 6fac7e2cd..cb03ab18f 100644
--- a/web/pgadmin/static/js/components/CodeMirror.jsx
+++ b/web/pgadmin/static/js/components/CodeMirror.jsx
@@ -9,26 +9,57 @@
import React, { useEffect, useRef } from 'react';
import {default as OrigCodeMirror} from 'bundled_codemirror';
+import {useOnScreen} from 'sources/custom_hooks';
import PropTypes from 'prop-types';
/* React wrapper for CodeMirror */
-export default function CodeMirror({name, value, options}) {
+export default function CodeMirror({name, value, options, events, ...props}) {
const taRef = useRef();
const cmObj = useRef();
+ const cmWrapper = useRef();
+ const isVisibleTrack = useRef();
useEffect(()=>{
/* Create the object only once on mount */
cmObj.current = new OrigCodeMirror.fromTextArea(
taRef.current, options);
+
+ if(cmObj.current) {
+ try {
+ cmWrapper.current = cmObj.current.getWrapperElement();
+ } catch(e) {
+ cmWrapper.current = null;
+ }
+ }
+
+ Object.keys(events||{}).forEach((eventName)=>{
+ cmObj.current.on(eventName, events[eventName]);
+ });
}, []);
useEffect(()=>{
+ /* Refresh when value changes async */
+ if(props.isAsync) {
+ if(cmObj.current) {
+ cmObj.current.setValue(value);
+ cmObj.current.refresh();
+ }
+ }
+ }, [value]);
+
+ const onScreenVisible = useOnScreen(cmWrapper);
+ if(!isVisibleTrack.current && onScreenVisible) {
+ isVisibleTrack.current = true;
+
/* Refresh when value changes */
if(cmObj.current) {
cmObj.current.setValue(value);
cmObj.current.refresh();
}
- }, [value]);
+ cmObj.current.refresh();
+ } else if(!onScreenVisible) {
+ isVisibleTrack.current = false;
+ }
return ;
}
@@ -36,5 +67,8 @@ export default function CodeMirror({name, value, options}) {
CodeMirror.propTypes = {
name: PropTypes.string,
value: PropTypes.string,
- options: PropTypes.object
+ options: PropTypes.object,
+ change: PropTypes.func,
+ events: PropTypes.object,
+ isAsync: PropTypes.bool
};
diff --git a/web/pgadmin/static/js/components/FormComponents.jsx b/web/pgadmin/static/js/components/FormComponents.jsx
index 2307dde78..2c63c98be 100644
--- a/web/pgadmin/static/js/components/FormComponents.jsx
+++ b/web/pgadmin/static/js/components/FormComponents.jsx
@@ -127,8 +127,9 @@ FormInput.propTypes = {
testcid: PropTypes.any,
};
-export function InputSQL({value, options}) {
+export function InputSQL({value, options, onChange, ...props}) {
const classes = useStyles();
+
return (
{
+ onChange && onChange(cm.getValue(), cm);
+ },
+ }}
+ {...props}
/>
);
}
InputSQL.propTypes = {
value: PropTypes.string,
options: PropTypes.object,
+ onChange: PropTypes.func
+
};
-export function FormInputSQL({hasError, required, label, className, helpMessage, testcid, value, controlProps}) {
+export function FormInputSQL({hasError, required, label, className, helpMessage, testcid, value, controlProps, ...props}) {
return (
-
-
+
+
);
}
@@ -162,6 +171,7 @@ FormInputSQL.propTypes = {
testcid: PropTypes.string,
value: PropTypes.string,
controlProps: PropTypes.object,
+ change: PropTypes.func,
};
diff --git a/web/pgadmin/static/js/custom_hooks.js b/web/pgadmin/static/js/custom_hooks.js
index b99dcbab4..488f87bd7 100644
--- a/web/pgadmin/static/js/custom_hooks.js
+++ b/web/pgadmin/static/js/custom_hooks.js
@@ -1,4 +1,4 @@
-import {useRef, useEffect} from 'react';
+import {useRef, useEffect, useState} from 'react';
/* React hook for setInterval */
export function useInterval(callback, delay) {
@@ -39,3 +39,21 @@ export function useDelayDebounce(callback, args, delay) {
}, [args]);
}
+export function useOnScreen(ref) {
+ const [isIntersecting, setIntersecting] = useState(false);
+ const observer = new IntersectionObserver(
+ ([entry]) => {
+ setIntersecting(entry.isIntersecting);
+ }
+ );
+ useEffect(() => {
+ if (ref.current) {
+ observer.observe(ref.current);
+ }
+ // Remove the observer as soon as the component is unmounted
+ return () => { observer.disconnect(); };
+ }, []);
+
+ return isIntersecting;
+}
+
diff --git a/web/regression/javascript/components/CodeMirror.spec.js b/web/regression/javascript/components/CodeMirror.spec.js
index 1adfe8145..5b347ea6c 100644
--- a/web/regression/javascript/components/CodeMirror.spec.js
+++ b/web/regression/javascript/components/CodeMirror.spec.js
@@ -26,6 +26,7 @@ describe('CodeMirror', ()=>{
cmInstance = mount(
);
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
new file mode 100644
index 000000000..ac1faaa7a
--- /dev/null
+++ b/web/regression/javascript/schema_ui_files/event_trigger.ui.spec.js
@@ -0,0 +1,111 @@
+/////////////////////////////////////////////////////////////
+//
+// 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 EventTriggerSchema from '../../../pgadmin/browser/server_groups/servers/databases/event_triggers/static/js/event_trigger.ui';
+
+
+describe('EventTriggerSchema', ()=>{
+ let mount;
+ let schemaObj = new EventTriggerSchema(
+ {
+ role: ()=>[],
+ function_names: ()=>[],
+ },
+ {
+ eventowner: 'postgres'
+ }
+ );
+ 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.eventfunname = null;
+ schemaObj.validate(state, setError);
+ expect(setError).toHaveBeenCalledWith('eventfunname', 'Event trigger function cannot be empty.');
+
+ });
+});
+