diff --git a/docs/en_US/release_notes_4_27.rst b/docs/en_US/release_notes_4_27.rst index 6c43ec72d..2b300b6fb 100644 --- a/docs/en_US/release_notes_4_27.rst +++ b/docs/en_US/release_notes_4_27.rst @@ -10,6 +10,7 @@ New features ************ | `Issue #1402 `_ - Added Macro support. +| `Issue #2519 `_ - Added support to view trigger function under the respective trigger node. | `Issue #3794 `_ - Allow user to change the database connection from an open query tool tab. | `Issue #5200 `_ - Added support to ignore the owner while comparing objects in the Schema Diff tool. | `Issue #5857 `_ - Added documentation for Macro support. 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 4c1ce416b..a50568f2e 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 @@ -1151,6 +1151,14 @@ class FunctionView(PGChildNodeView, DataTypeReader, SchemaDiffObjectCompare): else: object_type = 'function' + # We are showing trigger functions under the trigger node. + # It may possible that trigger is in one schema and trigger + # function is in another schema, so to show the SQL we need to + # change the schema id i.e scid. + if self.node_type == 'trigger_function' and \ + scid != resp_data['pronamespace']: + scid = resp_data['pronamespace'] + # Get Schema Name from its OID. self._get_schema_name_from_oid(resp_data) 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 a3d24adf4..ea0613a31 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 @@ -38,9 +38,26 @@ define('pgadmin.node.trigger_function', [ dialogHelp: url_for('help.static', {'filename': 'trigger_function_dialog.html'}), label: gettext('Trigger function'), collection_type: 'coll-trigger_function', + canEdit: function(itemData, item) { + let node = pgBrowser.treeMenu.findNodeByDomElement(item); + + if (!node || node.parentNode.getData()._type === 'trigger') + return false; + + return true; + }, hasSQL: true, + showMenu: function(itemData, item) { + let node = pgBrowser.treeMenu.findNodeByDomElement(item); + + if (!node || node.parentNode.getData()._type === 'trigger') + return false; + + return true; + }, hasDepends: true, hasStatistics: true, + url_jump_after_node: 'schema', Init: function() { /* Avoid mulitple registration of menus */ if (this.initialized) @@ -68,7 +85,6 @@ define('pgadmin.node.trigger_function', [ enable: 'canCreate', }, ]); - }, model: pgBrowser.Node.Model.extend({ idAttribute: 'oid', diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/9.1_plus/get_function_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/9.1_plus/get_function_oid.sql new file mode 100644 index 000000000..cf9e79339 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/9.1_plus/get_function_oid.sql @@ -0,0 +1,9 @@ +SELECT p.oid AS tfuncoid, p.proname AS tfunction, + p.pronamespace AS tfuncschoid, n.nspname AS tfuncschema, l.lanname +FROM pg_trigger t + LEFT OUTER JOIN pg_proc p ON p.oid=t.tgfoid + LEFT OUTER JOIN pg_namespace n ON n.oid = p.pronamespace + LEFT OUTER JOIN pg_language l ON l.oid=p.prolang +WHERE NOT tgisinternal + AND tgrelid = {{tid}}::OID + AND t.oid = {{trid}}::OID diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/9.1_plus/get_function_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/9.1_plus/get_function_oid.sql new file mode 100644 index 000000000..cf9e79339 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/9.1_plus/get_function_oid.sql @@ -0,0 +1,9 @@ +SELECT p.oid AS tfuncoid, p.proname AS tfunction, + p.pronamespace AS tfuncschoid, n.nspname AS tfuncschema, l.lanname +FROM pg_trigger t + LEFT OUTER JOIN pg_proc p ON p.oid=t.tgfoid + LEFT OUTER JOIN pg_namespace n ON n.oid = p.pronamespace + LEFT OUTER JOIN pg_language l ON l.oid=p.prolang +WHERE NOT tgisinternal + AND tgrelid = {{tid}}::OID + AND t.oid = {{trid}}::OID diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/__init__.py index ac6685cb0..f19365521 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/__init__.py @@ -121,7 +121,7 @@ class TriggerModule(CollectionNodeModule): """ Load the module node as a leaf node """ - return False + return True @property def module_use_template_javascript(self): @@ -280,6 +280,11 @@ class TriggerView(PGChildNodeView, SchemaDiffObjectCompare): ) self.template_path = 'triggers/sql/{0}/#{1}#'.format( self.manager.server_type, self.manager.version) + + self.trigger_function_template_path = \ + 'trigger_functions/{0}/sql/#{1}#'.format( + self.manager.server_type, self.manager.version) + # Store server type self.server_type = self.manager.server_type # We need parent's name eg table name and schema name @@ -293,6 +298,69 @@ class TriggerView(PGChildNodeView, SchemaDiffObjectCompare): return wrap + @check_precondition + def get_children_nodes(self, manager, **kwargs): + """ + Function is used to get the child nodes. + :param manager: + :param kwargs: + :return: + """ + nodes = [] + scid = kwargs.get('scid') + tid = kwargs.get('tid') + trid = kwargs.get('trid') + + try: + SQL = render_template( + "/".join([self.template_path, 'get_function_oid.sql']), + tid=tid, trid=trid + ) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + if len(rset['rows']) == 0: + return gone( + gettext("Could not find the specified trigger function")) + + # For language EDB SPL we should not display any node. + if rset['rows'][0]['lanname'] != 'edbspl': + trigger_function_schema_oid = rset['rows'][0]['tfuncschoid'] + + sql = render_template("/".join( + [self.trigger_function_template_path, self._NODE_SQL]), + scid=trigger_function_schema_oid, + fnid=rset['rows'][0]['tfuncoid'] + ) + status, res = self.conn.execute_2darray(sql) + if not status: + return internal_server_error(errormsg=rset) + + if len(res['rows']) == 0: + return gone(gettext( + "Could not find the specified trigger function")) + + row = res['rows'][0] + func_name = row['name'] + # If trigger function is from another schema then we should + # display the name as schema qulified name. + if scid != trigger_function_schema_oid: + func_name = \ + rset['rows'][0]['tfuncschema'] + '.' + row['name'] + + trigger_func = current_app.blueprints['NODE-trigger_function'] + nodes.append(trigger_func.generate_browser_node( + row['oid'], trigger_function_schema_oid, + gettext(func_name), + icon="icon-trigger_function", funcowner=row['funcowner'], + language=row['lanname'], inode=False) + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + return nodes + @check_precondition def get_trigger_functions(self, gid, sid, did, scid, tid, trid=None): """ diff --git a/web/pgadmin/browser/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js index e9463f2ea..b735dcbe3 100644 --- a/web/pgadmin/browser/static/js/browser.js +++ b/web/pgadmin/browser/static/js/browser.js @@ -404,7 +404,7 @@ define('pgadmin.browser', [ // Create the object menu dynamically if (item && obj.menus['object'] && obj.menus['object'][d._type]) { pgAdmin.Browser.MenuCreator( - $obj_mnu, obj.menus['object'][d._type], obj.menu_categories, d, item + obj.Nodes, $obj_mnu, obj.menus['object'][d._type], obj.menu_categories, d, item ); } else { // Create a dummy 'no object seleted' menu @@ -503,7 +503,7 @@ define('pgadmin.browser', [ context_menu = {}; pgAdmin.Browser.MenuCreator( - $div, menus, obj.menu_categories, d, item, context_menu + obj.Nodes, $div, menus, obj.menu_categories, d, item, context_menu ); return { @@ -877,7 +877,7 @@ define('pgadmin.browser', [ $dropdown.empty(); if (pgAdmin.Browser.MenuCreator( - $dropdown, obj.menus[o.menu], obj.menu_categories + obj.Nodes, $dropdown, obj.menus[o.menu], obj.menu_categories )) { $mnu.removeClass('d-none'); } diff --git a/web/pgadmin/browser/static/js/menu.js b/web/pgadmin/browser/static/js/menu.js index e716b1e86..71b9b035b 100644 --- a/web/pgadmin/browser/static/js/menu.js +++ b/web/pgadmin/browser/static/js/menu.js @@ -8,8 +8,8 @@ ////////////////////////////////////////////////////////////// define([ - 'underscore', 'sources/pgadmin', 'jquery', 'sources/utils', -], function(_, pgAdmin, $, pgadminUtils) { + 'underscore', 'sources/pgadmin', 'jquery', 'sources/utils', 'sources/gettext', +], function(_, pgAdmin, $, pgadminUtils, gettext) { 'use strict'; pgAdmin.Browser = pgAdmin.Browser || {}; @@ -274,20 +274,38 @@ define([ * menu-items. * * Arguments: - * 1. jQuery Element on which you may want to created the menus - * 2. list of menu-items - * 3. categories - metadata information about the categories, based on which + * 1. nodes_obj - Nodes object contains each node object. + * 2. jQuery Element on which you may want to created the menus + * 3. list of menu-items + * 4. categories - metadata information about the categories, based on which * the submenu (menu-group) will be created (if any). - * 4. d - Data object for the selected browser tree item. - * 5. item - The selected browser tree item - * 6. menu_items - A empty object on which the context menu for the given + * 5. d - Data object for the selected browser tree item. + * 6. item - The selected browser tree item + * 7. menu_items - A empty object on which the context menu for the given * list of menu-items. * * Returns if any menu generated for the given input. */ pgAdmin.Browser.MenuCreator = function( - $mnu, menus, categories, d, item, menu_items + nodes_obj, $mnu, menus, categories, d, item, menu_items ) { + let showMenu = true; + + /* We check showMenu function is defined by the respective node, if it is + * defined then call the function which will return true or false. + */ + if (d && nodes_obj[d._type] && !_.isUndefined(nodes_obj[d._type].showMenu)) + showMenu = nodes_obj[d._type].showMenu(d, item); + + if (!showMenu) { + menu_items = menu_items || {}; + menu_items[_.uniqueId('ctx_')+ '1_1_ms'] = { + disabled : true, + name: gettext('No menu available for this object.'), + }; + return; + } + var groups = { 'common': [], }, diff --git a/web/pgadmin/browser/static/js/node.js b/web/pgadmin/browser/static/js/node.js index a9e8ce9c9..827c7caf4 100644 --- a/web/pgadmin/browser/static/js/node.js +++ b/web/pgadmin/browser/static/js/node.js @@ -132,6 +132,10 @@ define('pgadmin.browser.node', [ 'action': 'edit', }, icon: 'fa fa-edit', + enable: _.isFunction(self.canEdit) ? + function() { + return !!(self.canEdit.apply(self, arguments)); + } : (!!self.canEdit), }]); } @@ -1233,7 +1237,7 @@ define('pgadmin.browser.node', [ tooltip: gettext('Edit'), extraClasses: ['btn', 'btn-primary', 'pull-right', 'm-1'], icon: 'fa fa-sm fa-pencil-alt', - disabled: !that.canEdit, + disabled: _.isFunction(that.canEdit) ? !that.canEdit.apply(that, [d, i]) : !that.canEdit, register: function(btn) { btn.on('click',() => { onEdit();