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