diff --git a/docs/en_US/debugger.rst b/docs/en_US/debugger.rst index 6223e55e0..6d3639533 100644 --- a/docs/en_US/debugger.rst +++ b/docs/en_US/debugger.rst @@ -117,6 +117,8 @@ The toolbar options are: +-------------------------+-----------------------------------------------------------------------------------------------------------+ | *Stop* | Click the *Stop* icon to halt the execution of a program. | +-------------------------+-----------------------------------------------------------------------------------------------------------+ +| *Help* | Click the *Help* icon to open debugger documentation. | ++-------------------------+-----------------------------------------------------------------------------------------------------------+ The top panel of the debugger window displays the program body; click in the grey margin next to a line number to add a breakpoint. The highlighted line in diff --git a/docs/en_US/images/debug_error_message.png b/docs/en_US/images/debug_error_message.png index b321d4918..41d1004ba 100644 Binary files a/docs/en_US/images/debug_error_message.png and b/docs/en_US/images/debug_error_message.png differ diff --git a/docs/en_US/images/debug_ic_step_in.png b/docs/en_US/images/debug_ic_step_in.png index 549da1a2e..5c9989683 100644 Binary files a/docs/en_US/images/debug_ic_step_in.png and b/docs/en_US/images/debug_ic_step_in.png differ diff --git a/docs/en_US/images/debug_main.png b/docs/en_US/images/debug_main.png index 3341cc72c..42e7715dc 100644 Binary files a/docs/en_US/images/debug_main.png and b/docs/en_US/images/debug_main.png differ diff --git a/docs/en_US/images/debug_params.png b/docs/en_US/images/debug_params.png index dcc6c630f..c4fa46f0c 100644 Binary files a/docs/en_US/images/debug_params.png and b/docs/en_US/images/debug_params.png differ diff --git a/docs/en_US/images/debug_set_breakpoint.png b/docs/en_US/images/debug_set_breakpoint.png index e74d2aaa2..6fd99e81f 100644 Binary files a/docs/en_US/images/debug_set_breakpoint.png and b/docs/en_US/images/debug_set_breakpoint.png differ diff --git a/docs/en_US/images/debug_stack.png b/docs/en_US/images/debug_stack.png index 69b0740a9..e445c82c4 100644 Binary files a/docs/en_US/images/debug_stack.png and b/docs/en_US/images/debug_stack.png differ diff --git a/docs/en_US/images/debug_step_in.png b/docs/en_US/images/debug_step_in.png index 1d485ab15..5c9989683 100644 Binary files a/docs/en_US/images/debug_step_in.png and b/docs/en_US/images/debug_step_in.png differ diff --git a/docs/en_US/images/debug_toolbar.png b/docs/en_US/images/debug_toolbar.png index c3066c1f0..1bc1f5811 100644 Binary files a/docs/en_US/images/debug_toolbar.png and b/docs/en_US/images/debug_toolbar.png differ diff --git a/docs/en_US/images/debug_variables.png b/docs/en_US/images/debug_variables.png index 7ac50dede..fd97970f6 100644 Binary files a/docs/en_US/images/debug_variables.png and b/docs/en_US/images/debug_variables.png differ diff --git a/docs/en_US/release_notes_6_11.rst b/docs/en_US/release_notes_6_11.rst index 6cc9e125b..73b8256eb 100644 --- a/docs/en_US/release_notes_6_11.rst +++ b/docs/en_US/release_notes_6_11.rst @@ -9,6 +9,8 @@ This release contains a number of bug fixes and new features since the release o New features ************ + | `Issue #2647 `_ - Added mouse over indication for breakpoint area in the Debugger. + | `Issue #2648 `_ - Added search text option to the Debugger panel. | `Issue #7178 `_ - Added capability to deploy PostgreSQL servers on Microsoft Azure. | `Issue #7332 `_ - Added support for passing password using Docker Secret to Docker images. | `Issue #7351 `_ - Added the option 'Show template databases?' to display template databases regardless of the setting of 'Show system objects?'. @@ -16,6 +18,7 @@ New features Housekeeping ************ + | `Issue #6132 `_ - Port Debugger to React. | `Issue #7315 `_ - Updates documentation for the Traefik v2 container deployment. | `Issue #7411 `_ - Update pgcli to latest release 3.4.1. | `Issue #7469 `_ - Upgrade Chartjs to the latest 3.8.0. diff --git a/web/pgadmin/feature_tests/xss_checks_pgadmin_debugger_test.py b/web/pgadmin/feature_tests/xss_checks_pgadmin_debugger_test.py index 3f54a23c3..818bdf3b4 100644 --- a/web/pgadmin/feature_tests/xss_checks_pgadmin_debugger_test.py +++ b/web/pgadmin/feature_tests/xss_checks_pgadmin_debugger_test.py @@ -114,15 +114,15 @@ class CheckDebuggerForXssFeatureTest(BaseFeatureTest): ) wait.until(EC.presence_of_element_located( - (By.XPATH, "//td[contains(@class,'test_function') and " - "contains(.,'Hello, pgAdmin4')]")) + (By.XPATH, "//div[@id='id-results']//td " + "[contains(.,'Hello, pgAdmin4')]")) ) # Only this tab is vulnerable rest are BackGrid & Code Mirror # control which are already tested in Query tool test case - self.page.click_tab("Messages") + self.page.click_tab('id-debugger-messages', rc_dock=True) source_code = self.page.find_by_xpath( - "//*[@id='messages']" + "//div[@id='id-debugger-messages'] //div[@id='debugger-msg']" ).get_attribute('innerHTML') self._check_escaped_characters( source_code, @@ -140,5 +140,6 @@ class CheckDebuggerForXssFeatureTest(BaseFeatureTest): def _check_escaped_characters(self, source_code, string_to_find, source): # For XSS we need to search against element's html code - assert source_code.find(string_to_find) != - \ - 1, "{0} might be vulnerable to XSS ".format(source) + assert source_code.find( + string_to_find) != -1, "{0} might be vulnerable to XSS ".format( + source) diff --git a/web/pgadmin/static/js/components/CodeMirror.jsx b/web/pgadmin/static/js/components/CodeMirror.jsx index 292541a07..7e3adff53 100644 --- a/web/pgadmin/static/js/components/CodeMirror.jsx +++ b/web/pgadmin/static/js/components/CodeMirror.jsx @@ -330,7 +330,7 @@ function handlePaste(editor, e) { } /* React wrapper for CodeMirror */ -export default function CodeMirror({currEditor, name, value, options, events, readonly, disabled, className, autocomplete=false}) { +export default function CodeMirror({currEditor, name, value, options, events, readonly, disabled, className, autocomplete=false, gutters=['CodeMirror-linenumbers', 'CodeMirror-foldgutter']}) { const taRef = useRef(); const editor = useRef(); const cmWrapper = useRef(); @@ -355,7 +355,7 @@ export default function CodeMirror({currEditor, name, value, options, events, re widget: '\u2026', }, foldGutter: true, - gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], + gutters: gutters, extraKeys: { // Autocomplete sql command ...(autocomplete ? { @@ -416,22 +416,6 @@ export default function CodeMirror({currEditor, name, value, options, events, re } Object.keys(events||{}).forEach((eventName)=>{ - if(eventName === 'change') { - let timeoutId; - const change = (...args)=>{ - /* In case of indent, change is triggered for each line */ - /* This can be avoided and taking only the latest */ - if(timeoutId) { - clearTimeout(timeoutId); - } - timeoutId = setTimeout(()=>{ - events[eventName](...args); - timeoutId = null; - }, 0); - }; - editor.current.on(eventName, change); - return; - } editor.current.on(eventName, events[eventName]); }); editor.current.on('drop', handleDrop); @@ -531,4 +515,5 @@ CodeMirror.propTypes = { disabled: PropTypes.bool, className: CustomPropTypes.className, autocomplete: PropTypes.bool, + gutters: PropTypes.array }; diff --git a/web/pgadmin/static/scss/_codemirror.overrides.scss b/web/pgadmin/static/scss/_codemirror.overrides.scss index f49fe7f52..ca119965d 100644 --- a/web/pgadmin/static/scss/_codemirror.overrides.scss +++ b/web/pgadmin/static/scss/_codemirror.overrides.scss @@ -114,6 +114,11 @@ width: .9em; } +.breakpoints { + width: .9em; + cursor: pointer; +} + .CodeMirror-foldgutter-open, .CodeMirror-foldgutter-folded { cursor: pointer; diff --git a/web/pgadmin/tools/debugger/__init__.py b/web/pgadmin/tools/debugger/__init__.py index e13516856..05ae5625c 100644 --- a/web/pgadmin/tools/debugger/__init__.py +++ b/web/pgadmin/tools/debugger/__init__.py @@ -65,9 +65,9 @@ class DebuggerModule(PgAdminModule): def get_own_javascripts(self): scripts = list() for name, script in [ + ['pgadmin.tools.debugger', 'js/index'], ['pgadmin.tools.debugger.controller', 'js/debugger'], ['pgadmin.tools.debugger.ui', 'js/debugger_ui'], - ['pgadmin.tools.debugger.direct', 'js/direct'] ]: scripts.append({ 'name': name, @@ -266,17 +266,6 @@ def index(): ) -@blueprint.route("/js/debugger.js") -@login_required -def script(): - """render the main debugger javascript file""" - return Response( - response=render_template("debugger/js/debugger.js", _=gettext), - status=200, - mimetype=MIMETYPE_APP_JS - ) - - @blueprint.route("/js/debugger_ui.js") @login_required def script_debugger_js(): @@ -288,7 +277,7 @@ def script_debugger_js(): ) -@blueprint.route("/js/direct.js") +@blueprint.route("/js/debugger.js") @login_required def script_debugger_direct_js(): """ @@ -296,7 +285,7 @@ def script_debugger_direct_js(): from server for debugging """ return Response( - response=render_template("debugger/js/direct.js", _=gettext), + response=render_template("debugger/js/debugger.js", _=gettext), status=200, mimetype=MIMETYPE_APP_JS ) @@ -885,7 +874,7 @@ def initialize_target(debug_type, trans_id, sid, did, # be be required if request.method == 'POST': de_inst.function_data['args_value'] = \ - json.loads(request.values['data'], encoding='utf-8') + json.loads(request.data, encoding='utf-8') # Update the debugger data session variable # Here frame_id is required when user debug the multilevel function. @@ -1562,10 +1551,17 @@ def clear_all_breakpoint(trans_id): else: template_path = DEBUGGER_SQL_V3_PATH + status = True + result = '' if conn.connected(): # get the data sent through post from client - if request.form['breakpoint_list']: - line_numbers = request.form['breakpoint_list'].split(",") + if 'breakpoint_list' in json.loads(request.data): + line_numbers = [] + if json.loads(request.data)['breakpoint_list'] is not None and \ + json.loads(request.data)['breakpoint_list'] != '': + line_numbers = json.loads(request.data)[ + 'breakpoint_list'].split(",") + for line_no in line_numbers: sql = render_template( "/".join([template_path, "clear_breakpoint.sql"]), @@ -1629,7 +1625,7 @@ def deposit_parameter_value(trans_id): if conn.connected(): # get the data sent through post from client - data = json.loads(request.values['data'], encoding='utf-8') + data = json.loads(request.data, encoding='utf-8') if data: sql = render_template( @@ -1842,8 +1838,8 @@ def set_arguments_sqlite(sid, did, scid, func_id): - Function Id """ - if request.values['data']: - data = json.loads(request.values['data'], encoding='utf-8') + if request.data: + data = json.loads(request.data, encoding='utf-8') try: for i in range(0, len(data)): diff --git a/web/pgadmin/tools/debugger/static/js/DebuggerConstants.js b/web/pgadmin/tools/debugger/static/js/DebuggerConstants.js new file mode 100644 index 000000000..64e7c2e37 --- /dev/null +++ b/web/pgadmin/tools/debugger/static/js/DebuggerConstants.js @@ -0,0 +1,57 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +export const DEBUGGER_EVENTS = { + TRIGGER_CLEAR_ALL_BREAKPOINTS: 'TRIGGER_CLEAR_ALL_BREAKPOINTS', + TRIGGER_TOGGLE_BREAKPOINTS: 'TRIGGER_TOGGLE_BREAKPOINTS', + TRIGGER_STOP_DEBUGGING: 'TRIGGER_STOP_DEBUGGING', + TRIGGER_CONTINUE_DEBUGGING: 'TRIGGER_CONTINUE_DEBUGGING', + TRIGGER_STEPOVER_DEBUGGING: 'TRIGGER_STEPOVER_DEBUGGING', + TRIGGER_STEINTO_DEBUGGING: 'TRIGGER_STEINTO_DEBUGGING', + + SET_STACK: 'SET_STACK', + SET_MESSAGES: 'SET_MESSAGES', + SET_RESULTS: 'SET_RESULTS', + SET_LOCAL_VARIABLES: 'SET_LOCAL_VARIABLES', + SET_PARAMETERS: 'SET_PARAMETERS', + SET_FRAME: 'SET_FRAME', + + SET_LOCAL_VARIABLE_VALUE_CHANGE: 'SET_LOCAL_VARIABLE_VALUE_CHANGE', + SET_PARAMETERS_VALUE_CHANGE: 'SET_PARAMETERS_VALUE_CHANGE', + + DISABLE_MENU: 'DISABLE_MENU', + ENABLE_MENU: 'ENABLE_MENU', + ENABLE_SPECIFIC_MENU: 'ENABLE_SPECIFIC_MENU', + + FOCUS_PANEL: 'FOCUS_PANEL', + GET_TOOL_BAR_BUTTON_STATUS: 'GET_TOOL_BAR_BUTTON_STATUS' +}; + +export const PANELS = { + DEBUGGER: 'id-debugger', + PARAMETERS: 'id-parameters', + LOCAL_VARIABLES: 'id-local-variables', + MESSAGES: 'id-debugger-messages', + RESULTS: 'id-results', + STACK: 'id-stack', +}; + +export const MENUS = { + STEPINTO: 'step-into', + STEPOVER: 'step-over', + STOP: 'stop', + CONTINUE: 'continue', + CLEAR_ALL_BREAKPOINT: 'clear-al-breakpoint', + TOGGLE_BREAKPOINT: 'toggle-breakpoint' +}; + +export const DEBUGGER_ARGS = { + NO_DEFAULT: '', + NO_DEFAULT_VALUE: '', +}; diff --git a/web/pgadmin/tools/debugger/static/js/DebuggerModule.js b/web/pgadmin/tools/debugger/static/js/DebuggerModule.js new file mode 100644 index 000000000..cc5016a6e --- /dev/null +++ b/web/pgadmin/tools/debugger/static/js/DebuggerModule.js @@ -0,0 +1,727 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import $ from 'jquery'; + +import React from 'react'; +import ReactDOM from 'react-dom'; + +import gettext from 'sources/gettext'; +import { sprintf, registerDetachEvent } from 'sources/utils'; +import url_for from 'sources/url_for'; +import pgWindow from 'sources/window'; +import alertify from 'pgadmin.alertifyjs'; +import Kerberos from 'pgadmin.authenticate.kerberos'; + +import { refresh_db_node } from 'tools/sqleditor/static/js/sqleditor_title'; +import { _set_dynamic_tab } from '../../../sqleditor/static/js/show_query_tool'; +import getApiInstance from '../../../../static/js/api_instance'; +import Notify from '../../../../static/js/helpers/Notifier'; +import { getFunctionId, getProcedureId, getAppropriateLabel, setDebuggerTitle } from './debugger_utils'; +import FunctionArguments from './debugger_ui'; +import ModalProvider from '../../../../static/js/helpers/ModalProvider'; +import DebuggerComponent from './components/DebuggerComponent'; + +export default class Debugger { + static instance; + + static getInstance(...args) { + if (!Debugger.instance) { + Debugger.instance = new Debugger(...args); + } + return Debugger.instance; + } + + constructor(pgAdmin, pgBrowser) { + this.pgAdmin = pgAdmin; + this.pgBrowser = pgBrowser; + this.funcArgs = new FunctionArguments(); + this.wcDocker = window.wcDocker; + this.api = getApiInstance(); + } + + init() { + if (this.initialized) + return; + this.initialized = true; + // Initialize the context menu to display the debugging options when user open the context menu for functions, procedures, triggers and trigger functions. + this.pgBrowser.add_menus([ + { + name: 'direct_debugger', + node: 'function', + module: this, + applies: ['object', 'context'], + callback: 'getFunctionInformation', + category: gettext('Debugging'), + priority: 10, + label: gettext('Debug'), + data: { + object: 'function', + }, + icon: 'fa fa-arrow-circle-right', + enable: 'canDebug', + }, { + name: 'global_debugger', + node: 'function', + module: this, + applies: ['object', 'context'], + callback: 'checkFuncDebuggable', + category: gettext('Debugging'), + priority: 10, + label: gettext('Set Breakpoint'), + data: { + object: 'function', + debug_type: 'indirect', + }, + icon: 'fa fa-arrow-circle-right', + enable: 'canDebug', + }, { + name: 'procedure_direct_debugger', + node: 'procedure', + module: this, + applies: ['object', 'context'], + callback: 'getFunctionInformation', + category: gettext('Debugging'), + priority: 10, + label: gettext('Debug'), + data: { + object: 'procedure', + }, + icon: 'fa fa-arrow-circle-right', + enable: 'can_debug', + }, { + name: 'procedure_indirect_debugger', + node: 'procedure', + module: this, + applies: ['object', 'context'], + callback: 'checkFuncDebuggable', + category: gettext('Debugging'), + priority: 10, + label: gettext('Set Breakpoint'), + data: { + object: 'procedure', + debug_type: 'indirect', + }, + icon: 'fa fa-arrow-circle-right', + enable: 'can_debug', + }, { + name: 'trigger_function_indirect_debugger', + node: 'trigger_function', + module: this, + applies: ['object', 'context'], + callback: 'checkFuncDebuggable', + priority: 10, + label: gettext('Set Breakpoint'), + category: gettext('Debugging'), + icon: 'fa fa-arrow-circle-right', + data: { + object: 'trigger_function', + debug_type: 'indirect', + }, + enable: 'can_debug', + }, { + name: 'trigger_indirect_debugger', + node: 'trigger', + module: this, + applies: ['object', 'context'], + callback: 'checkFuncDebuggable', + priority: 10, + label: gettext('Set Breakpoint'), + category: gettext('Debugging'), + icon: 'fa fa-arrow-circle-right', + data: { + object: 'trigger', + debug_type: 'indirect', + }, + enable: 'can_debug', + }, { + name: 'package_function_direct_debugger', + node: 'edbfunc', + module: this, + applies: ['object', 'context'], + callback: 'getFunctionInformation', + category: gettext('Debugging'), + priority: 10, + label: gettext('Debug'), + data: { + object: 'edbfunc', + }, + icon: 'fa fa-arrow-circle-right', + enable: 'can_debug', + }, { + name: 'package_function_global_debugger', + node: 'edbfunc', + module: this, + applies: ['object', 'context'], + callback: 'checkFuncDebuggable', + category: gettext('Debugging'), + priority: 10, + label: gettext('Set Breakpoint'), + data: { + object: 'edbfunc', + debug_type: 'indirect', + }, + icon: 'fa fa-arrow-circle-right', + enable: 'can_debug', + }, { + name: 'package_procedure_direct_debugger', + node: 'edbproc', + module: this, + applies: ['object', 'context'], + callback: 'getFunctionInformation', + category: gettext('Debugging'), + priority: 10, + label: gettext('Debug'), + data: { + object: 'edbproc', + }, + icon: 'fa fa-arrow-circle-right', + enable: 'can_debug', + }, { + name: 'package_procedure_global_debugger', + node: 'edbproc', + module: this, + applies: ['object', 'context'], + callback: 'checkFuncDebuggable', + category: gettext('Debugging'), + priority: 10, + label: gettext('Set Breakpoint'), + data: { + object: 'edbproc', + debug_type: 'indirect', + }, + icon: 'fa fa-arrow-circle-right', + enable: 'can_debug', + } + ]); + + /* Create and load the new frame required for debugger panel */ + this.frame = new this.pgBrowser.Frame({ + name: 'frm_debugger', + title: gettext('Debugger'), + showTitle: true, + isCloseable: true, + isRenamable: true, + isPrivate: true, + icon: 'fa fa-bug', + url: 'about:blank', + }); + + this.frame.load(this.pgBrowser.docker); + } + + // It will check weather the function is actually debuggable or not with pre-required condition. + canDebug(itemData, item, data) { + var t = this.pgBrowser.tree, + i = item, + d = itemData; + // To iterate over tree to check parent node + while (i) { + if ('catalog' == d._type) { + //Check if we are not child of catalog + return false; + } + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + + // Find the function is really available in database + var tree = this.pgBrowser.tree, + info = tree.selected(), + d_ = info ? tree.itemData(info) : undefined; + + if (!d_) + return false; + + var treeInfo = tree.getTreeNodeHierarchy(info); + + // For indirect debugging user must be super user + if (data && data.debug_type && data.debug_type == 'indirect' && + !treeInfo.server.user.is_superuser) + return false; + + // Fetch object owner + var obj_owner = treeInfo.function && treeInfo.function.funcowner || + treeInfo.procedure && treeInfo.procedure.funcowner || + treeInfo.edbfunc && treeInfo.edbfunc.funcowner || + treeInfo.edbproc && treeInfo.edbproc.funcowner; + + // Must be a super user or object owner to create breakpoints of any kind + if (!(treeInfo.server.user.is_superuser || obj_owner == treeInfo.server.user.name)) + return false; + + // For trigger node, language will be undefined - we should allow indirect debugging for trigger node + if ((d_.language == undefined && d_._type == 'trigger') || + (d_.language == undefined && d_._type == 'edbfunc') || + (d_.language == undefined && d_._type == 'edbproc')) { + return true; + } + + let returnValue = true; + if (d_.language != 'plpgsql' && d_.language != 'edbspl') { + returnValue = false; + } + + return returnValue; + } + /* + For the direct debugging, we need to fetch the function information to display in the dialog so "generate_url" + will dynamically generate the URL from the server_id, database_id, schema_id and function id. + */ + generate_url(_url, treeInfo, node) { + var url = '{BASEURL}{URL}/{OBJTYPE}{REF}', + ref = ''; + + _.each( + _.sortBy( + _.values( + _.pick(treeInfo, + function (v, k) { + return (k != 'server_group'); + }) + ), + function (o) { + return o.priority; + } + ), + function (o) { + ref = sprintf('%s/%s', ref, encodeURI(o._id)); + }); + + var args = { + 'URL': _url, + 'BASEURL': url_for('debugger.index'), + 'REF': ref, + 'OBJTYPE': encodeURI(node.type), + }; + + return url.replace(/{(\w+)}/g, function (match, arg) { + return args[arg]; + }); + } + + getUrl(_d, newTreeInfo, trans_id) { + let baseUrl = undefined; + if (_d._type == 'function' || _d._type == 'edbfunc') { + baseUrl = url_for( + 'debugger.initialize_target_for_function', { + 'debug_type': 'direct', + 'trans_id': trans_id, + 'sid': newTreeInfo.server._id, + 'did': newTreeInfo.database._id, + 'scid': newTreeInfo.schema._id, + 'func_id': getFunctionId(newTreeInfo), + } + ); + } else if (_d._type == 'procedure' || _d._type == 'edbproc') { + baseUrl = url_for( + 'debugger.initialize_target_for_function', { + 'debug_type': 'direct', + 'trans_id': trans_id, + 'sid': newTreeInfo.server._id, + 'did': newTreeInfo.database._id, + 'scid': newTreeInfo.schema._id, + 'func_id': getProcedureId(newTreeInfo), + } + ); + } + return baseUrl; + } + + checkDbNameChange(data, dbNode, newTreeInfo, db_label) { + if (data && data.data_obj && data.data_obj.db_name != newTreeInfo.database.label) { + db_label = data.data_obj.db_name; + var message = `Current database has been moved or renamed to ${db_label}. Click on the OK button to refresh the database name.`; + refresh_db_node(message, dbNode); + } + return db_label; + } + /* + Get the function information for the direct debugging to display the functions arguments and other informations + in the user input dialog + */ + getFunctionInformation(args, item) { + var self = this, + t = this.pgBrowser.tree, + i = item || t.selected(), + d = i ? t.itemData(i) : undefined, + node = d && this.pgBrowser.Nodes[d._type], + tree_data = this.pgBrowser.tree.translateTreeNodeIdFromReactTree(i), + db_data = this.pgBrowser.tree.findNode(tree_data[3]), + dbNode = db_data.domNode; + + if (!d) + return; + + var is_edb_proc = d._type == 'edbproc'; + + var treeInfo = t.getTreeNodeHierarchy(i), + _url = this.generate_url('init', treeInfo, node); + + this.api({ + url: _url, + method: 'GET', + }).then((res) => { + let debug_info = res.data.data.debug_info, + trans_id = res.data.data.trans_id; + // Open dialog to take the input arguments from user if function having input arguments + if (debug_info[0]['require_input']) { + self.funcArgs.show(debug_info[0], 0, is_edb_proc, trans_id); + } else { + /* Initialize the target and create asynchronous connection and unique transaction ID + If there is no arguments to the functions then we should not ask for for function arguments and + Directly open the panel */ + var _t = this.pgBrowser.tree, + _i = _t.selected(), + _d = _i ? _t.itemData(_i) : undefined; + + var newTreeInfo = _t.getTreeNodeHierarchy(_i); + + var baseUrl = self.getUrl(_d, newTreeInfo, trans_id); + + self.api({ + url: baseUrl, + method: 'GET', + }) + .then(function (result) { + + var data = result.data.data; + + var url = url_for('debugger.direct', { + 'trans_id': trans_id, + }); + + var browser_preferences = self.pgBrowser.get_preferences_for_module('browser'); + var open_new_tab = browser_preferences.new_browser_tab_open; + if (open_new_tab && open_new_tab.includes('debugger')) { + window.open(url, '_blank'); + // Send the signal to runtime, so that proper zoom level will be set. + setTimeout(function () { + self.pgBrowser.send_signal_to_runtime('Runtime new window opened'); + }, 500); + } else { + self.pgBrowser.Events.once( + 'pgadmin-browser:frame:urlloaded:frm_debugger', + function (frame) { + frame.openURL(url); + }); + + // Create the debugger panel as per the data received from user input dialog. + var dashboardPanel = self.pgBrowser.docker.findPanels( + 'properties' + ), + panel = self.pgBrowser.docker.addPanel( + 'frm_debugger', self.wcDocker.DOCK.STACKED, dashboardPanel[0] + ), + db_label = newTreeInfo.database.label; + panel.trans_id = trans_id; + + _set_dynamic_tab(self.pgBrowser, browser_preferences['dynamic_tabs']); + registerDetachEvent(panel); + + db_label = self.checkDbNameChange(data, dbNode, newTreeInfo, db_label); + + var label = getAppropriateLabel(newTreeInfo); + setDebuggerTitle(panel, browser_preferences, label, newTreeInfo.schema.label, db_label, null, self.pgBrowser); + + panel.focus(); + + // Register Panel Closed event + panel.on(self.wcDocker.EVENT.CLOSED, function () { + var closeUrl = url_for('debugger.close', { + 'trans_id': trans_id, + }); + self.api({ + url: closeUrl, + method: 'DELETE', + }); + }); + + panel.on(self.wcDocker.EVENT.RENAME, function (panel_data) { + self.panel_rename_event(panel_data, panel, treeInfo); + }); + } + }) + .catch(function (e) { + Notify.alert( + gettext('Debugger Target Initialization Error'), + e.responseJSON.errormsg + ); + }); + } + }) + .catch((err) => { + Notify.alert(gettext('Debugger Error'), err.response.data.errormsg); + }); + } + + checkFuncDebuggable(args, item) { + var self = this, + t = this.pgBrowser.tree, + i = item || t.selected(), + d = i ? t.itemData(i) : undefined, + node = d && this.pgBrowser.Nodes[d._type]; + + if (!d) + return; + + var treeInfo = t.getTreeNodeHierarchy(i), + _url = this.generate_url('init', treeInfo, node); + + self.api({ + url: _url, + cache: false, + }) + .then(function (res) { + self.startGlobalDebugger(args, item, res.data.data.trans_id); + }) + .catch(function (xhr) { + self.onFail(xhr); + }); + } + + getGlobalUrl(d, treeInfo, trans_id) { + var baseUrl = null; + if (d._type == 'function' || d._type == 'edbfunc') { + baseUrl = url_for( + 'debugger.initialize_target_for_function', { + 'debug_type': 'indirect', + 'trans_id': trans_id, + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id, + 'scid': treeInfo.schema._id, + 'func_id': getFunctionId(treeInfo), + } + ); + } else if (d._type == 'procedure' || d._type == 'edbproc') { + baseUrl = url_for( + 'debugger.initialize_target_for_function', { + 'debug_type': 'indirect', + 'trans_id': trans_id, + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id, + 'scid': treeInfo.schema._id, + 'func_id': getProcedureId(treeInfo), + } + ); + } else if (d._type == 'trigger_function') { + baseUrl = url_for( + 'debugger.initialize_target_for_function', { + 'debug_type': 'indirect', + 'trans_id': trans_id, + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id, + 'scid': treeInfo.schema._id, + 'func_id': treeInfo.trigger_function._id, + } + ); + } else if (d._type == 'trigger' && 'table' in treeInfo) { + baseUrl = url_for( + 'debugger.initialize_target_for_trigger', { + 'debug_type': 'indirect', + 'trans_id': trans_id, + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id, + 'scid': treeInfo.schema._id, + 'func_id': treeInfo.table._id, + 'tri_id': treeInfo.trigger._id, + } + ); + } else if (d._type == 'trigger' && 'view' in treeInfo) { + baseUrl = url_for( + 'debugger.initialize_target_for_trigger', { + 'debug_type': 'indirect', + 'trans_id': trans_id, + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id, + 'scid': treeInfo.schema._id, + 'func_id': treeInfo.view._id, + 'tri_id': treeInfo.trigger._id, + } + ); + } + + return baseUrl; + } + + updatedDbLabel(res, db_label, treeInfo, dbNode) { + if (res.data.data.data_obj.db_name != treeInfo.database.label) { + db_label = res.data.data.data_obj.db_name; + var message = gettext(`Current database has been moved or renamed to ${db_label}. Click on the OK button to refresh the database name.`); + refresh_db_node(message, dbNode); + } + } + + //Callback function when user start the indirect debugging ( Listen to another session to invoke the target ) + startGlobalDebugger(args, item, trans_id) { + // Initialize the target and create asynchronous connection and unique transaction ID + var self = this; + var t = this.pgBrowser.tree, + i = item || t.selected(), + d = i ? t.itemData(i) : undefined, + tree_data = this.pgBrowser.tree.translateTreeNodeIdFromReactTree(i), + db_data = this.pgBrowser.tree.findNode(tree_data[3]), + dbNode = db_data.domNode; + + if (!d) + return; + + var treeInfo = t.getTreeNodeHierarchy(i); + var baseUrl = self.getGlobalUrl(d, treeInfo, trans_id); + + self.api({ + url: baseUrl, + method: 'GET', + }) + .then(function (res) { + var url = url_for('debugger.direct', { + 'trans_id': res.data.data.debuggerTransId, + }); + var browser_preferences = self.pgBrowser.get_preferences_for_module('browser'); + var open_new_tab = browser_preferences.new_browser_tab_open; + if (open_new_tab && open_new_tab.includes('debugger')) { + window.open(url, '_blank'); + // Send the signal to runtime, so that proper zoom level will be set. + setTimeout(function () { + self.pgBrowser.send_signal_to_runtime('Runtime new window opened'); + }, 500); + } else { + self.pgBrowser.Events.once( + 'pgadmin-browser:frame:urlloaded:frm_debugger', + function (frame) { + frame.openURL(url); + }); + + // Create the debugger panel as per the data received from user input dialog. + var dashboardPanel = self.pgBrowser.docker.findPanels( + 'properties' + ), + panel = self.pgBrowser.docker.addPanel( + 'frm_debugger', self.wcDocker.DOCK.STACKED, dashboardPanel[0] + ), + db_label = treeInfo.database.label; + panel.trans_id = trans_id; + + self.updatedDbLabel(res, db_label, treeInfo, dbNode); + + var label = getAppropriateLabel(treeInfo); + setDebuggerTitle(panel, browser_preferences, label, db_label, db_label, null, self.pgBrowser); + + panel.focus(); + + // Panel Closed event + panel.on(self.wcDocker.EVENT.CLOSED, function () { + var closeUrl = url_for('debugger.close', { + 'trans_id': res.data.data.debuggerTransId, + }); + $.ajax({ + url: closeUrl, + method: 'DELETE', + }); + }); + + // Panel Rename event + panel.on(self.wcDocker.EVENT.RENAME, function (panel_data) { + self.panel_rename_event(panel_data, panel, treeInfo); + }); + } + }) + .catch(self.raiseError); + } + + raiseError(xhr) { + try { + var err = xhr.response.data; + if (err.errormsg.search('Ticket expired') !== -1) { + let fetchTicket = Kerberos.fetch_ticket(); + fetchTicket.then( + function () { + self.startGlobalDebugger(); + }, + function (error) { + Notify.alert(gettext('Debugger Error'), error); + } + ); + } else { + if (err.success == 0) { + Notify.alert(gettext('Debugger Error'), err.errormsg); + } + } + } catch (e) { + console.warn(e.stack || e); + } + } + + /* We should get the transaction id from the server during initialization here */ + load(container, trans_id, debug_type, function_name_with_arguments, layout) { + this.trans_id = trans_id; + this.debug_type = debug_type; + this.first_time_indirect_debug = false; + this.direct_execution_completed = false; + this.polling_timeout_idle = false; + this.debug_restarted = false; + this.is_user_aborted_debugging = false; + this.is_polling_required = true; // Flag to stop unwanted ajax calls + this.function_name_with_arguments = function_name_with_arguments; + this.layout = layout; + this.preferences = this.pgBrowser.get_preferences_for_module('debugger'); + + let panel = null; + let selectedNodeInfo = pgWindow.pgAdmin.Browser.tree.getTreeNodeHierarchy( + pgWindow.pgAdmin.Browser.tree.selected() + ); + + // Find debugger panel. + pgWindow.pgAdmin.Browser.docker.findPanels('frm_debugger').forEach(p => { + if (parseInt(p.trans_id) == trans_id) { + panel = p; + } + }); + + ReactDOM.render( + + + , + container + ); + } + + onFail(xhr) { + try { + var err = xhr.response.data; + if (err.success == 0) { + Notify.alert(gettext('Debugger Error'), err.errormsg); + } + } catch (e) { + console.warn(e.stack || e); + } + } + + panel_rename_event(panel_data, panel, treeInfo) { + alertify.prompt('', panel_data.$titleText[0].textContent, + // We will execute this function when user clicks on the OK button + function (evt, value) { + if (value) { + // Remove the leading and trailing white spaces. + value = value.trim(); + let preferences = this.pgBrowser.get_preferences_for_module('browser'); + var name = getAppropriateLabel(treeInfo); + setDebuggerTitle(panel, preferences, name, treeInfo.schema.label, treeInfo.database.label, value, this.pgBrowser); + } + }, + // We will execute this function when user clicks on the Cancel + // button. Do nothing just close it. + function (evt) { evt.cancel = false; } + ).set({ 'title': gettext('Rename Panel') }); + } +} \ No newline at end of file diff --git a/web/pgadmin/tools/debugger/static/js/components/DebuggerArgs.ui.js b/web/pgadmin/tools/debugger/static/js/components/DebuggerArgs.ui.js new file mode 100644 index 000000000..ef889f643 --- /dev/null +++ b/web/pgadmin/tools/debugger/static/js/components/DebuggerArgs.ui.js @@ -0,0 +1,124 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, 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'; + +class ArgementsCollectionSchema extends BaseUISchema { + constructor() { + super({ + name: undefined, + type: undefined, + is_null: false, + expr: false, + value: undefined, + use_default: false, + default_value: undefined, + }); + + this.isValid = true; + } + + get baseFields() { + return [ + { + id: 'name', + label: gettext('Name'), + editable: false, + type: 'text', + cell: '', + }, + { + id: 'type', + label: gettext('Type'), + editable: false, + type: 'text', + cell: '', + width: 30 + }, + { + id: 'is_null', + label: gettext('Null?'), + type: 'checkbox', + cell: 'checkbox', + width: 38, + align_center: true, + }, + { + id: 'expr', + label: gettext('Expression?'), + type: 'checkbox', + cell: 'checkbox', + width: 60, + align_center: true, + }, + { + id: 'value', + label: gettext('Value'), + type: 'text', + cell: (state) => { + let dtype = 'text'; + if(state.type == 'date') { + dtype = 'datetimepicker'; + } else if(state.type == 'numeric') { + dtype = 'numeric'; + } + + return { + cell: dtype, + controlProps: { + ...(dtype=='datetimepicker' && { + placeholder: gettext('YYYY-MM-DD'), + autoOk: true, pickerType: 'Date', ampm: false, + }) + } + }; + }, + editable: true, + align_center: true, + }, + { + id: 'use_default', + label: gettext('Use Default?'), + type: 'checkbox', + cell: 'checkbox', + width: 62, + disabled: (state) => {return state.disable_use_default;} + }, + { + id: 'default_value', + label: gettext('Default'), + type: 'text', + editable: false, + cell: '', + }, + ]; + } + +} + +export class DebuggerArgumentSchema extends BaseUISchema { + constructor() { + super(); + } + + get baseFields() { + return [{ + id: 'aregsCollection', label: gettext(''), + mode: ['edit'], + type: 'collection', + canAdd: false, + canDelete: false, + canEdit: false, + editable: false, + disabled: false, + schema: new ArgementsCollectionSchema(), + }]; + } +} diff --git a/web/pgadmin/tools/debugger/static/js/components/DebuggerArgumentComponent.jsx b/web/pgadmin/tools/debugger/static/js/components/DebuggerArgumentComponent.jsx new file mode 100644 index 000000000..f553aa840 --- /dev/null +++ b/web/pgadmin/tools/debugger/static/js/components/DebuggerArgumentComponent.jsx @@ -0,0 +1,917 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import _ from 'lodash'; +import PropTypes from 'prop-types'; + +import React, { useEffect, useRef } from 'react'; + +import DeleteSweepIcon from '@material-ui/icons/DeleteSweep'; +import { Box } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import BugReportRoundedIcon from '@material-ui/icons/BugReportRounded'; +import CloseSharpIcon from '@material-ui/icons/CloseSharp'; + +import url_for from 'sources/url_for'; +import gettext from 'sources/gettext'; +import * as commonUtils from 'sources/utils'; +import pgAdmin from 'sources/pgadmin'; +import Alertify from 'pgadmin.alertifyjs'; + +import SchemaView from '../../../../../static/js/SchemaView'; +import getApiInstance from '../../../../../static/js/api_instance'; +import { DefaultButton, PrimaryButton } from '../../../../../static/js/components/Buttons'; +import { getAppropriateLabel, setDebuggerTitle } from '../debugger_utils'; +import Notify from '../../../../../static/js/helpers/Notifier'; +import { DebuggerArgumentSchema } from './DebuggerArgs.ui'; +import { DEBUGGER_ARGS } from '../DebuggerConstants'; + + + +const useStyles = makeStyles((theme) => + ({ + root: { + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + height: '100%', + backgroundColor: theme.palette.background.default, + overflow: 'hidden', + }, + body: { + display: 'flex', + flexDirection: 'column', + height: '100%', + }, + actionBtn: { + alignItems: 'flex-start', + }, + buttonMargin: { + marginLeft: '0.5em' + }, + debugBtn: { + fontSize: '1.12rem !important', + }, + footer: { + borderTop: '1px solid #dde0e6 !important', + padding: '0.5rem', + display: 'flex', + width: '100%', + background: theme.otherVars.headerBg, + } + }), +); + + +export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug, isEdbProc, transId, ...props }) { + const classes = useStyles(); + const debuggerArgsSchema = useRef(new DebuggerArgumentSchema()); + const api = getApiInstance(); + const debuggerArgsData = useRef([]); + const [loadArgs, setLoadArgs] = React.useState(0); + const [isDisableDebug, setIsDisableDebug] = React.useState(true); + const debuggerFinalArgs = useRef([]); + const InputArgIds = useRef([]); + const wcDocker = window.wcDocker; + + function getURL() { + var _Url = null; + + if (restartDebug == 0) { + var t = pgAdmin.Browser.tree, + i = t.selected(), + d = i ? t.itemData(i) : undefined; + + if (!d) + return; + + var treeInfo = t.getTreeNodeHierarchy(i); + + if (d._type == 'function') { + // Get the existing function parameters available from sqlite database + _Url = url_for('debugger.get_arguments', { + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id, + 'scid': treeInfo.schema._id, + 'func_id': treeInfo.function._id, + }); + } else if (d._type == 'procedure') { + // Get the existing function parameters available from sqlite database + _Url = url_for('debugger.get_arguments', { + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id, + 'scid': treeInfo.schema._id, + 'func_id': treeInfo.procedure._id, + }); + } else if (d._type == 'edbfunc') { + // Get the existing function parameters available from sqlite database + _Url = url_for('debugger.get_arguments', { + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id, + 'scid': treeInfo.schema._id, + 'func_id': treeInfo.edbfunc._id, + }); + } else if (d._type == 'edbproc') { + // Get the existing function parameters available from sqlite database + _Url = url_for('debugger.get_arguments', { + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id, + 'scid': treeInfo.schema._id, + 'func_id': treeInfo.edbproc._id, + }); + } + } else { + // Get the existing function parameters available from sqlite database + _Url = url_for('debugger.get_arguments', { + 'sid': debuggerInfo.server_id, + 'did': debuggerInfo.database_id, + 'scid': debuggerInfo.schema_id, + 'func_id': debuggerInfo.function_id, + }); + } + return _Url; + } + + function setArgs(res) { + let funcArgsData = []; + if (res.data.data.args_count != 0) { + setIsDisableDebug(false); + for(const i of res.data.data.result) { + // Below will format the data to be stored in sqlite database + funcArgsData.push({ + 'arg_id': i['arg_id'], + 'is_null': i['is_null'], + 'is_expression': i['is_expression'], + 'use_default': i['use_default'], + 'value': i['value'], + }); + } + } + return funcArgsData; + } + + function checkModesAndTypes() { + // Check modes and type in the arguments. + if (debuggerInfo['proargmodes'] != null) { + var argmodes = debuggerInfo['proargmodes'].split(','); + argmodes.forEach((NULL, indx) => { + if (argmodes[indx] == 'i' || argmodes[indx] == 'b' || + (isEdbProc && argmodes[indx] == 'o')) { + InputArgIds.current.push(indx); + } + }); + } else { + var argtypes = debuggerInfo['proargtypenames'].split(','); + argtypes.forEach((NULL, indx) => { + InputArgIds.current.push(indx); + }); + } + + // Get argument types + let argType = debuggerInfo['proargtypenames'].split(','); + let argMode, defaultArgs, argCnt; + + if (debuggerInfo['proargmodes'] != null) { + argMode = debuggerInfo['proargmodes'].split(','); + } + + if (debuggerInfo['pronargdefaults']) { + let defaultArgsCount = debuggerInfo['pronargdefaults']; + defaultArgs = debuggerInfo['proargdefaults'].split(','); + argCnt = defaultArgsCount; + } + + return [argType, argMode, argCnt, defaultArgs]; + } + + function setDefaultValues(defValList, argCnt, argName, argMode, defaultArgs) { + for (let j = (argName.length - 1); j >= 0; j--) { + if (debuggerInfo['proargmodes'] != null) { + if (argMode && (argMode[j] == 'i' || argMode[j] == 'b' || + (isEdbProc && argMode[j] == 'o'))) { + defValList[j] = DEBUGGER_ARGS.NO_DEFAULT; + if (argCnt) { + argCnt = argCnt - 1; + defValList[j] = defaultArgs[argCnt]; + } + } + } else if (argCnt) { + argCnt = argCnt - 1; + defValList[j] = defaultArgs[argCnt]; + } else { + defValList[j] = DEBUGGER_ARGS.NO_DEFAULT; + } + } + } + + function addArg(argtype, defvalList, argname, useDefValue) { + let myObj = {}; + if (defvalList != DEBUGGER_ARGS.NO_DEFAULT) { + useDefValue = true; + } + myObj = { + 'name': argname, + 'type': argtype, + 'use_default': useDefValue, + 'default_value': defvalList, + 'disable_use_default': defvalList == DEBUGGER_ARGS.NO_DEFAULT + }; + + return myObj; + } + + function getArgsList(argType, argMode, defValList, argName, useDefValue) { + var myObj = []; + if (argType.length != 0) { + for (let i = 0; i < argType.length; i++) { + if (debuggerInfo['proargmodes'] != null) { + if (argMode && (argMode[i] == 'i' || argMode[i] == 'b' || + (isEdbProc && argMode[i] == 'o'))) { + useDefValue = false; + myObj.push(addArg(argType[i], defValList[i], argName[i], useDefValue)); + } + } else { + useDefValue = false; + myObj.push(addArg(argType[i], defValList[i], argName[i], useDefValue)); + } + } + } + return myObj; + } + + function setFuncObj(funcArgsData, argMode, argType, argName, defValList, isUnnamedParam=false) { + let index, values, vals, funcObj=[]; + for(const argData of funcArgsData) { + index = argData['arg_id']; + if (debuggerInfo['proargmodes'] != null && + (argMode && argMode[index] == 'o' && !isEdbProc) && !isUnnamedParam) { + continue; + } + + values = []; + if (argType[index].indexOf('[]') != -1) { + vals = argData['value'].split(','); + _.each(vals, function (val) { + values.push({ + 'value': val, + }); + }); + } else { + values = argData['value']; + } + + funcObj.push({ + 'name': argName[index], + 'type': argType[index], + 'is_null': argData['is_null'] ? true : false, + 'expr': argData['is_expression'] ? true : false, + 'value': values, + 'use_default': argData['use_default'] ? true : false, + 'default_value': defValList[index], + 'disable_use_default': isUnnamedParam ? defValList[index] == DEBUGGER_ARGS.NO_DEFAULT_VALUE : defValList[index] == DEBUGGER_ARGS.NO_DEFAULT, + }); + } + + return funcObj; + } + + function setUnnamedParamNonDefVal(argType, defValList, myargname) { + let myObj= []; + for (let i = 0; i < argType.length; i++) { + myObj.push({ + 'name': myargname[i], + 'type': argType[i], + 'use_default': false, + 'default_value': DEBUGGER_ARGS.NO_DEFAULT_VALUE, + 'disable_use_default': true + }); + defValList[i] = DEBUGGER_ARGS.NO_DEFAULT_VALUE; + } + return myObj; + } + + function setUnnamedParamDefVal(myargname, argCnt, defValList, defaultArgs) { + for (let j = (myargname.length - 1); j >= 0; j--) { + if (argCnt) { + argCnt = argCnt - 1; + defValList[j] = defaultArgs[argCnt]; + } else { + defValList[j] = DEBUGGER_ARGS.NO_DEFAULT_VALUE; + } + } + + } + + function checkIsDefault(defValList) { + let useDefValue = false; + if (defValList != DEBUGGER_ARGS.NO_DEFAULT_VALUE) { + useDefValue = true; + } + + return useDefValue; + } + function setUnnamedArgs(argType, argMode, useDefValue, defValList, myargname) { + let myObj = []; + for (let i = 0; i < argType.length; i++) { + if (debuggerInfo['proargmodes'] == null) { + useDefValue = checkIsDefault(defValList[i]); + myObj.push({ + 'name': myargname[i], + 'type': argType[i], + 'use_default': useDefValue, + 'default_value': defValList[i], + 'disable_use_default': defValList[i] == DEBUGGER_ARGS.NO_DEFAULT_VALUE, + }); + } else { + if (argMode && (argMode[i] == 'i' || argMode[i] == 'b' || + (isEdbProc && argMode[i] == 'o'))) { + useDefValue = checkIsDefault(defValList[i]); + myObj.push({ + 'name': myargname[i], + 'type': argType[i], + 'use_default': useDefValue, + 'default_value': defValList[i], + 'disable_use_default': defValList[i] == DEBUGGER_ARGS.NO_DEFAULT_VALUE, + }); + } + } + } + return myObj; + } + + function generateArgsNames(argType) { + let myargname = []; + for (let i = 0; i < argType.length; i++) { + myargname[i] = 'dbgparam' + (i + 1); + } + + return myargname; + } + + function setDebuggerArgs(funcArgsData, funcObj, myObj) { + // Check if the arguments already available in the sqlite database + // then we should use the existing arguments + let initVal = { 'aregsCollection': [] }; + if (funcArgsData.length == 0) { + initVal = { 'aregsCollection': myObj }; + debuggerArgsData.current = initVal; + } else { + initVal = { 'aregsCollection': funcObj }; + debuggerArgsData.current = initVal; + } + } + + function getDebuggerArgsSchema() { + // Variables to store the data sent from sqlite database + let funcArgsData = []; + + // As we are not getting Browser.tree when we debug again + // so tree info will be updated from the server data + let baseURL = getURL(); + + api({ + url: baseURL, + method: 'GET', + }) + .then(function (res) { + funcArgsData = setArgs(res); + var argName; + + var defValList = []; + var myObj = []; + var funcObj = []; + + var [argType, argMode, argCnt, defaultArgs] = checkModesAndTypes(); + + var useDefValue; + + if (debuggerInfo['proargnames'] != null) { + argName = debuggerInfo['proargnames'].split(','); + + // It will assign default values to "Default value" column + setDefaultValues(defValList, argCnt, argName, argMode, defaultArgs); + // It wil check and add args in myObj list. + myObj = getArgsList(argType, argMode, defValList, argName, useDefValue); + + // Need to update the funcObj variable from sqlite database if available + if (funcArgsData.length != 0) { + funcObj = setFuncObj(funcArgsData, argMode, argType, argName, defValList); + } + } + else { + /* Generate the name parameter if function do not have arguments name + like dbgparam1, dbgparam2 etc. */ + var myargname = generateArgsNames(argType); + + // If there is no default arguments + if (!debuggerInfo['pronargdefaults']) { + myObj = setUnnamedParamNonDefVal(argType, defValList, myargname); + } else { + // If there is default arguments + //Below logic will assign default values to "Default value" column + setUnnamedParamDefVal(myargname, argCnt, defValList, defaultArgs); + + myObj = setUnnamedArgs(argType, argMode, useDefValue, defValList, myargname); + } + + // Need to update the funcObj variable from sqlite database if available + if (funcArgsData.length != 0) { + funcObj = setFuncObj(funcArgsData, argMode, argType, myargname, defValList, true); + } + } + + setDebuggerArgs(funcArgsData, funcObj, myObj); + debuggerArgsSchema.current = new DebuggerArgumentSchema(); + setLoadArgs(Math.floor(Math.random() * 1000)); + }) + .catch(() => { + Notify.alert( + gettext('Debugger Error'), + gettext('Unable to fetch the arguments from server') + ); + }); + } + + useEffect(() => { + getDebuggerArgsSchema(); + }, []); + + let initData = () => new Promise((resolve, reject) => { + try { + resolve(debuggerArgsData.current); + } catch (error) { + reject(error); + } + }); + + function clearArgs() { + setLoadArgs(0); + let base_url = null; + + if (restartDebug == 0) { + let selectedItem = pgAdmin.Browser.tree.selected(); + let itemData = pgAdmin.Browser.tree.itemData(selectedItem); + if (!itemData) + return; + + let treeInfo = pgAdmin.Browser.tree.getTreeNodeHierarchy(selectedItem); + + base_url = url_for('debugger.clear_arguments', { + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id, + 'scid': treeInfo.schema._id, + 'func_id': itemData._id, + }); + } else { + base_url = url_for('debugger.clear_arguments', { + 'sid': debuggerInfo.server_id, + 'did': debuggerInfo.database_id, + 'scid': debuggerInfo.schema_id, + 'func_id': debuggerInfo.function_id, + }); + } + api({ + url: base_url, + method: 'POST', + data: {}, + }).then(function () { + /* Get updated debugger arguments */ + getDebuggerArgsSchema(); + /* setTimeout required to get updated argruments as 'Clear All' will delete all saved arguments form sqlite db. */ + setTimeout(() => { + /* Reload the debugger arguments */ + setLoadArgs(Math.floor(Math.random() * 1000)); + /* Disable debug button */ + setIsDisableDebug(true); + }, 100); + }).catch(function (er) { + Notify.alert( + gettext('Clear failed'), + er.responseJSON.errormsg + ); + }); + } + + function setDebuggingArgs(argsList, argSet) { + if (argsList.length === 0) { + debuggerFinalArgs.current.changed.forEach(changedArg => { + argSet.push(changedArg.name); + argsList.push(changedArg); + }); + + debuggerArgsData.current.aregsCollection.forEach(arg => { + if (!argSet.includes(arg.name)) { + argSet.push(arg.name); + argsList.push(arg); + } + }); + } + } + + function checkArgsVal(arg, argsValueList) { + // Check if value is set to NULL then we should ignore the value field + if (arg.is_null) { + argsValueList.push({ + 'name': arg.name, + 'type': arg.type, + 'value': 'NULL', + }); + } else { + // Check if default value to be used or not + if (arg.use_default) { + argsValueList.push({ + 'name': arg.name, + 'type': arg.type, + 'value': arg.default_value, + }); + } else { + argsValueList.push({ + 'name': arg.name, + 'type': arg.type, + 'value': arg.value, + }); + } + } + } + function getFunctionID(d, treeInfo) { + var functionId; + if (d._type == 'function') { + functionId = treeInfo.function._id; + } else if (d._type == 'procedure') { + functionId = treeInfo.procedure._id; + } else if (d._type == 'edbfunc') { + functionId = treeInfo.edbfunc._id; + } else if (d._type == 'edbproc') { + functionId = treeInfo.edbproc._id; + } + return functionId; + } + + function setSqliteFuncArgs(d, treeInfo, arg, intCount, sqliteFuncArgsList) { + if (restartDebug == 0) { + var functionId = getFunctionID(d, treeInfo); + // Below will format the data to be stored in sqlite database + sqliteFuncArgsList.push({ + 'server_id': treeInfo.server._id, + 'database_id': treeInfo.database._id, + 'schema_id': treeInfo.schema._id, + 'function_id': functionId, + 'arg_id': InputArgIds.current[intCount], + 'is_null': arg.is_null ? 1 : 0, + 'is_expression': arg.expr ? 1 : 0, + 'use_default': arg.use_default ? 1 : 0, + 'value': arg.value, + }); + } else { + // Below will format the data to be stored in sqlite database + sqliteFuncArgsList.push({ + 'server_id': debuggerInfo.server_id, + 'database_id': debuggerInfo.database_id, + 'schema_id': debuggerInfo.schema_id, + 'function_id': debuggerInfo.function_id, + 'arg_id': InputArgIds.current[intCount], + 'is_null': arg.is_null ? 1 : 0, + 'is_expression': arg.expr ? 1 : 0, + 'use_default': arg.use_default ? 1 : 0, + 'value': debuggerInfo.value, + }); + } + + return sqliteFuncArgsList; + } + + function checkTypeAndGetUrl(d, treeInfo) { + var baseUrl; + if (d && d._type == 'function') { + baseUrl = url_for('debugger.initialize_target_for_function', { + 'debug_type': 'direct', + 'trans_id': transId, + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id, + 'scid': treeInfo.schema._id, + 'func_id': treeInfo.function._id, + }); + } else if (d && d._type == 'procedure') { + baseUrl = url_for('debugger.initialize_target_for_function', { + 'debug_type': 'direct', + 'trans_id': transId, + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id, + 'scid': treeInfo.schema._id, + 'func_id': treeInfo.procedure._id, + }); + } else if (d && d._type == 'edbfunc') { + baseUrl = url_for('debugger.initialize_target_for_function', { + 'debug_type': 'direct', + 'trans_id': transId, + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id, + 'scid': treeInfo.schema._id, + 'func_id': treeInfo.edbfunc._id, + }); + } else if (d && d._type == 'edbproc') { + baseUrl = url_for('debugger.initialize_target_for_function', { + 'debug_type': 'direct', + 'trans_id': transId, + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id, + 'scid': treeInfo.schema._id, + 'func_id': treeInfo.edbproc._id, + }); + } + + return baseUrl; + } + + function getSetArgsUrl(d, treeInfo) { + var baseUrl; + if (d._type == 'function') { + baseUrl = url_for('debugger.set_arguments', { + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id, + 'scid': treeInfo.schema._id, + 'func_id': treeInfo.function._id, + }); + } else if (d._type == 'procedure') { + baseUrl = url_for('debugger.set_arguments', { + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id, + 'scid': treeInfo.schema._id, + 'func_id': treeInfo.procedure._id, + }); + } else if (d._type == 'edbfunc') { + // Get the existing function parameters available from sqlite database + baseUrl = url_for('debugger.set_arguments', { + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id, + 'scid': treeInfo.schema._id, + 'func_id': treeInfo.edbfunc._id, + }); + } else if (d._type == 'edbproc') { + // Get the existing function parameters available from sqlite database + baseUrl = url_for('debugger.set_arguments', { + 'sid': treeInfo.server._id, + 'did': treeInfo.database._id, + 'scid': treeInfo.schema._id, + 'func_id': treeInfo.edbproc._id, + }); + } + + return baseUrl; + } + + function getSelectedNodeData() { + var treeInfo, d; + if (restartDebug == 0) { + var t = pgAdmin.Browser.tree, + i = t.selected(); + + d = i ? t.itemData(i) : undefined; + + if (!d) + return; + + treeInfo = t.getTreeNodeHierarchy(i); + } + return [treeInfo, d]; + } + + function startDebugging() { + var self = this; + /* Initialize the target once the debug button is clicked and create asynchronous connection + and unique transaction ID If the debugging is started again then treeInfo is already stored. */ + var [treeInfo, d] = getSelectedNodeData(); + if(!d) return; + + var argsValueList = []; + var sqliteFuncArgsList = []; + var intCount = 0; + + let argsList = debuggerFinalArgs.current?.changed ? [] : debuggerArgsData.current.aregsCollection; + let argSet = []; + + setDebuggingArgs(argsList, argSet); + + argsList.forEach(arg => { + checkArgsVal(arg, argsValueList); + setSqliteFuncArgs(d, treeInfo, arg, intCount, sqliteFuncArgsList); + intCount = intCount + 1; + }); + + var baseUrl; + + /* If debugging is not started again then we should initialize the target otherwise not */ + if (restartDebug == 0) { + baseUrl = checkTypeAndGetUrl(d, treeInfo); + + api({ + url: baseUrl, + method: 'POST', + data: JSON.stringify(argsValueList), + }) + .then(function (res_post) { + + var url = url_for( + 'debugger.direct', { + 'trans_id': res_post.data.data.debuggerTransId, + } + ); + + var browserPreferences = pgAdmin.Browser.get_preferences_for_module('browser'); + var open_new_tab = browserPreferences.new_browser_tab_open; + if (open_new_tab && open_new_tab.includes('debugger')) { + window.open(url, '_blank'); + // Send the signal to runtime, so that proper zoom level will be set. + setTimeout(function () { + pgAdmin.Browser.send_signal_to_runtime('Runtime new window opened'); + }, 500); + } else { + pgAdmin.Browser.Events.once( + 'pgadmin-browser:frame:urlloaded:frm_debugger', + function (frame) { + frame.openURL(url); + }); + + // Create the debugger panel as per the data received from user input dialog. + var propertiesPanel = pgAdmin.Browser.docker.findPanels('properties'); + var panel = pgAdmin.Browser.docker.addPanel( + 'frm_debugger', wcDocker.DOCK.STACKED, propertiesPanel[0] + ); + var browser_pref = pgAdmin.Browser.get_preferences_for_module('browser'); + var label = getAppropriateLabel(treeInfo); + setDebuggerTitle(panel, browser_pref, label, treeInfo.schema.label, treeInfo.database.label, null, pgAdmin.Browser); + panel.focus(); + + // Panel Closed event + panel.on(wcDocker.EVENT.CLOSED, function () { + var closeUrl = url_for('debugger.close', { + 'trans_id': res_post.data.data.debuggerTransId, + }); + api({ + url: closeUrl, + method: 'DELETE', + }); + }); + /* TO-DO check how to add this is new lib for wc-docker */ + commonUtils.registerDetachEvent(panel); + + // Panel Rename event + panel.on(wcDocker.EVENT.RENAME, function (panel_data) { + Alertify.prompt('', panel_data.$titleText[0].textContent, + // We will execute this function when user clicks on the OK button + function (evt, value) { + if (value) { + // Remove the leading and trailing white spaces. + value = value.trim(); + var name = getAppropriateLabel(treeInfo); + setDebuggerTitle(panel, self.preferences, name, treeInfo.schema.label, treeInfo.database.label, value, pgAdmin.Browser); + } + }, + // We will execute this function when user clicks on the Cancel + // button. Do nothing just close it. + function (evt) { evt.cancel = false; } + ).set({ 'title': gettext('Rename Panel') }); + }); + } + + var _url = getSetArgsUrl(d, treeInfo); + + api({ + url: _url, + method: 'POST', + data: JSON.stringify(sqliteFuncArgsList), + }) + .then(function () {/*This is intentional (SonarQube)*/ }) + .catch((error) => { + Notify.alert( + gettext('Error occured: '), + gettext(error.response.data) + ); + }); + /* Close the debugger modal dialog */ + props.closeModal(); + + }) + .catch(function (error) { + Notify.alert( + gettext('Debugger Target Initialization Error'), + gettext(error.response.data) + ); + }); + + } + else { + // If the debugging is started again then we should only set the + // arguments and start the listener again + baseUrl = url_for('debugger.start_listener', { + 'trans_id': transId, + }); + + api({ + url: baseUrl, + method: 'POST', + data: JSON.stringify(argsValueList), + }) + .then(function () {/*This is intentional (SonarQube)*/ }) + .catch(function (error) { + Notify.alert( + gettext('Debugger Listener Startup Error'), + gettext(error.response.data) + ); + }); + + // Set the new input arguments given by the user during debugging + var _Url = url_for('debugger.set_arguments', { + 'sid': debuggerInfo.server_id, + 'did': debuggerInfo.database_id, + 'scid': debuggerInfo.schema_id, + 'func_id': debuggerInfo.function_id, + }); + api({ + url: _Url, + method: 'POST', + data: JSON.stringify(sqliteFuncArgsList), + }) + .then(function () {/*This is intentional (SonarQube)*/ }) + .catch(function (error) { + Notify.alert( + gettext('Debugger Listener Startup Set Arguments Error'), + gettext(error.response.data) + ); + }); + } + + + } + + return ( + + + { + loadArgs > 0 && + { + let isValid = false; + let skipStep = false; + if('_sessData' in debuggerArgsSchema.current) { + isValid = true; + debuggerArgsSchema.current._sessData.aregsCollection.forEach((data)=> { + + if(skipStep) {return;} + + if((data.is_null || data.use_default || data?.value?.toString()?.length > 0) && isValid) { + isValid = true; + } else { + isValid = false; + skipStep = true; + } + }); + } + setIsDisableDebug(!isValid); + debuggerFinalArgs.current = changedData.aregsCollection; + }} + /> + } + + + + { clearArgs(); }} startIcon={ { clearArgs(); }} />}> + {gettext('Clear All')} + + + + { props.closeModal(); }} startIcon={ { props.closeModal(); }} />}> + {gettext('Cancel')} + + } + disabled={isDisableDebug} + onClick={() => { startDebugging(); }}> + {gettext('Debug')} + + + + + + ); +} + +DebuggerArgumentComponent.propTypes = { + debuggerInfo: PropTypes.object, + restartDebug: PropTypes.number, + isEdbProc: PropTypes.bool, + transId: PropTypes.string, + closeModal: PropTypes.func, +}; + diff --git a/web/pgadmin/tools/debugger/static/js/components/DebuggerComponent.jsx b/web/pgadmin/tools/debugger/static/js/components/DebuggerComponent.jsx new file mode 100644 index 000000000..7d535d3db --- /dev/null +++ b/web/pgadmin/tools/debugger/static/js/components/DebuggerComponent.jsx @@ -0,0 +1,1234 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import { Box } from '@material-ui/core'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import PropTypes from 'prop-types'; + +import gettext from 'sources/gettext'; +import url_for from 'sources/url_for'; +import Loader from 'sources/components/Loader'; + +import Layout, { LayoutHelper } from '../../../../../static/js/helpers/Layout'; +import EventBus from '../../../../../static/js/helpers/EventBus'; +import Theme from '../../../../../static/js/Theme'; +import getApiInstance from '../../../../../static/js/api_instance'; +import Notify from '../../../../../static/js/helpers/Notifier'; + +import { evalFunc } from '../../../../../static/js/utils'; +import { PANELS, DEBUGGER_EVENTS, MENUS } from '../DebuggerConstants'; +import { retrieveNodeName } from '../../../../sqleditor/static/js/show_view_data'; +import { useModal } from '../../../../../static/js/helpers/ModalProvider'; +import DebuggerEditor from './DebuggerEditor'; +import DebuggerMessages from './DebuggerMessages'; +import { ToolBar } from './ToolBar'; +import { Stack } from './Stack'; +import { Results } from './Results'; +import { LocalVariablesAndParams } from './LocalVariablesAndParams'; + +export const DebuggerContext = React.createContext(); +export const DebuggerEventsContext = React.createContext(); + +export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panel, eventBusObj, layout, params }) { + const savedLayout = layout; + const containerRef = React.useRef(null); + const docker = useRef(null); + const api = getApiInstance(); + const modal = useModal(); + const eventBus = useRef(eventBusObj || (new EventBus())); + const [loaderText, setLoaderText] = React.useState(''); + const editor = useRef(null); + let timeOut = null; + const [qtState, _setQtState] = useState({ + preferences: { + browser: {}, debugger: {}, + }, + is_new_tab: window.location == window.parent?.location, + params: { + ...params, + node_name: retrieveNodeName(selectedNodeInfo), + } + }); + const setQtState = (state) => { + _setQtState((prev) => ({ ...prev, ...evalFunc(null, state, prev) })); + }; + + const disableToolbarButtons = () => { + eventBus.current.fireEvent(DEBUGGER_EVENTS.DISABLE_MENU); + eventBus.current.fireEvent(DEBUGGER_EVENTS.GET_TOOL_BAR_BUTTON_STATUS, {disabled: true}); + }; + + const enableToolbarButtons = (key = null) => { + if (key) { + eventBus.current.fireEvent(DEBUGGER_EVENTS.ENABLE_SPECIFIC_MENU, key); + } else { + eventBus.current.fireEvent(DEBUGGER_EVENTS.ENABLE_MENU); + } + + eventBus.current.fireEvent(DEBUGGER_EVENTS.GET_TOOL_BAR_BUTTON_STATUS, {disabled: false}); + }; + + const reflectPreferences = useCallback(() => { + setQtState({ + preferences: { + browser: pgAdmin.Browser.get_preferences_for_module('browser'), + debugger: pgAdmin.Browser.get_preferences_for_module('debugger'), + } + }); + }, []); + + // Function to get the breakpoint information from the server + const getBreakpointInformation = (transId, callBackFunc) => { + var result = ''; + + // Make ajax call to listen the database message + var baseUrl = url_for('debugger.execute_query', { + 'trans_id': transId, + 'query_type': 'get_breakpoints', + }); + api({ + url: baseUrl, + method: 'GET', + }) + .then(function (res) { + if (res.data.data.status === 'Success') { + result = res.data.data.result; + if (callBackFunc) { + callBackFunc(result); + } + } else if (res.data.data.status === 'NotConnected') { + raiseFetchingBreakpointError(); + } + }) + .catch(raiseFetchingBreakpointError); + + return result; + }; + + const clearAllBreakpoint = (transId) => { + var clearBreakpoint = (br_list) => { + // If there is no break point to clear then we should return from here. + if ((br_list.length == 1) && (br_list[0].linenumber == -1)) + return; + + disableToolbarButtons(); + var breakpoint_list = getBreakpointList(br_list); + + // Make ajax call to listen the database message + var baseUrl = url_for('debugger.clear_all_breakpoint', { + 'trans_id': transId, + }); + + api({ + url: baseUrl, + method: 'POST', + data: { + 'breakpoint_list': breakpoint_list.lenght > 0 ? breakpoint_list.join() : null, + }, + }) + .then(function (res) { + if (res.data.data.status) { + executeQuery(transId); + setUnsetBreakpoint(res, breakpoint_list); + } + enableToolbarButtons(); + }) + .catch(raiseClearBrekpointError); + }; + getBreakpointInformation(transId, clearBreakpoint); + }; + + const raiseJSONError = (xhr) => { + try { + var err = xhr.response.data; + if (err.success == 0) { + Notify.alert(gettext('Debugger Error'), err.errormsg, () => { + if(panel) { + panel.close(); + } + + }); + } + } catch (e) { + alert(xhr); + Notify.alert( + gettext('Debugger Error'), + gettext('Error while starting debugging listener.') + ); + } + }; + + const raisePollingError = () => { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while polling result.') + ); + }; + + const raiseClearBrekpointError = () => { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while clearing all breakpoint.') + ); + }; + + const raiseFetchingBreakpointError = () => { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while fetching breakpoint information.') + ); + }; + + const messages = (transId) => { + // Make ajax call to listen the database message + var baseUrl = url_for('debugger.messages', { + 'trans_id': transId, + }); + + api({ + url: baseUrl, + method: 'GET', + }) + .then(function (res) { + if (res.data.data.status === 'Success') { + enableToolbarButtons(); + // If status is Success then find the port number to attach the executer. + startExecution(transId, res.data.data.result); + } else if (res.data.data.status === 'Busy') { + // If status is Busy then poll the result by recursive call to the poll function + messages(transId); + } else if (res.data.data.status === 'NotConnected') { + Notify.alert( + gettext('Not connected to server or connection with the server has been closed.'), + res.data.result + ); + } + }) + .catch(function () { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while fetching messages information.') + ); + }); + + }; + + const startExecution = (transId, port_num) => { + // Make ajax call to listen the database message + var baseUrl = url_for( + 'debugger.start_execution', { + 'trans_id': transId, + 'port_num': port_num, + }); + api({ + url: baseUrl, + method: 'GET', + }) + .then(function (res) { + if (res.data.data.status === 'Success') { + // If status is Success then find the port number to attach the executer. + executeQuery(transId); + } else if (res.data.data.status === 'NotConnected') { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while starting debugging session.') + ); + } + }) + .catch(function () { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while starting debugging session.') + ); + }); + }; + + const executeQuery = (transId) => { + // Make ajax call to listen the database message + var baseUrl = url_for( + 'debugger.execute_query', { + 'trans_id': transId, + 'query_type': 'wait_for_breakpoint', + }); + api({ + url: baseUrl, + method: 'GET', + }) + .then(function (res) { + if (res.data.data.status === 'Success') { + // set the return code to the code editor text area + if ( + res.data.data.result[0].src != null && + res.data.data.result[0].linenumber != null + ) { + editor.current.setValue(res.data.data.result[0].src); + + setActiveLine(res.data.data.result[0].linenumber - 2); + } + // Call function to create and update Stack information .... + getStackInformation(transId); + if (params.directDebugger.debug_type) { + pollEndExecutionResult(transId); + } + } else if (res.data.data.status === 'NotConnected') { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while executing requested debugging information.') + ); + } + }) + .catch(function () { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while executing requested debugging information.') + ); + }); + }; + + const setActiveLine = (lineNo) => { + /* If lineNo sent, remove active line */ + if (lineNo && editor.current.activeLineNo) { + editor.current.removeLineClass( + editor.current.activeLineNo, 'wrap', 'CodeMirror-activeline-background' + ); + } + + /* If lineNo not sent, set it to active line */ + if (!lineNo && editor.current.activeLineNo) { + lineNo = editor.current.activeLineNo; + } + + /* Set new active line only if positive */ + if (lineNo > 0) { + editor.current.activeLineNo = lineNo; + editor.current.addLineClass( + editor.current.activeLineNo, 'wrap', 'CodeMirror-activeline-background' + ); + + /* centerOnLine is codemirror extension in bundle/codemirror.js */ + editor.current.centerOnLine(editor.current.activeLineNo); + } + }; + + const selectFrame = (frameId) => { + // Make ajax call to listen the database message + var baseUrl = url_for('debugger.select_frame', { + 'trans_id': params.transId, + 'frame_id': frameId, + }); + api({ + url: baseUrl, + method: 'GET', + }) + .then(function (res) { + if (res.data.data.status) { + editor.current.setValue(res.data.data.result[0].src); + updateBreakpoint(params.transId, true); + setActiveLine(res.data.data.result[0].linenumber - 2); + } + }) + .catch(function () { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while selecting frame.') + ); + }); + }; + + useEffect(() => { + var baseUrl = ''; + if (params.transId != undefined && !params.directDebugger.debug_type) { + // Make ajax call to execute the and start the target for execution + baseUrl = url_for('debugger.start_listener', { + 'trans_id': params.transId, + }); + + api({ + url: baseUrl, + method: 'GET', + }) + .then(function (res) { + if (res.data.data.status) { + enableToolbarButtons(); + pollResult(params.transId); + } + }) + .catch(raiseJSONError); + } else if (params.transId != undefined) { + // Make api call to execute the and start the target for execution + baseUrl = url_for('debugger.start_listener', { + 'trans_id': params.transId, + }); + + api({ + url: baseUrl, + method: 'GET', + }) + .then(function (res) { + if (res.data.data.status) { + messages(params.transId); + } + }) + .catch(raiseJSONError); + } + }, []); + + const setUnsetBreakpoint = (res, breakpoint_list) => { + if (res.data.data.status) { + for (let brk_val of breakpoint_list) { + var info = editor.current.lineInfo((brk_val - 1)); + + if (info) { + if (info.gutterMarkers != undefined) { + editor.current.setGutterMarker((brk_val - 1), 'breakpoints', null); + } + } + } + } + }; + + const triggerClearBreakpoint = () => { + var clearBreakpoint = (br_list) => { + // If there is no break point to clear then we should return from here. + if ((br_list.length == 1) && (br_list[0].linenumber == -1)) + return; + + disableToolbarButtons(); + var breakpoint_list = getBreakpointList(br_list); + + // Make ajax call to listen the database message + var _baseUrl = url_for('debugger.clear_all_breakpoint', { + 'trans_id': params.transId, + }); + + api({ + url: _baseUrl, + method: 'POST', + data: { + 'breakpoint_list': breakpoint_list.join(), + }, + }) + .then(function (res) { + setUnsetBreakpoint(res, breakpoint_list); + enableToolbarButtons(); + }) + .catch(raiseClearBrekpointError); + }; + // Make ajax call to listen the database message + var baseUrl = url_for('debugger.execute_query', { + 'trans_id': params.transId, + 'query_type': 'get_breakpoints', + }); + api({ + url: baseUrl, + method: 'GET', + }) + .then(function (res) { + if (res.data.data.status === 'Success') { + let result = res.data.data.result; + clearBreakpoint(result); + } else if (res.data.data.status === 'NotConnected') { + raiseFetchingBreakpointError(); + } + }) + .catch(raiseFetchingBreakpointError); + + }; + + const debuggerMark = () => { + var marker = document.createElement('div'); + marker.style.color = '#822'; + marker.innerHTML = '●'; + return marker; + }; + + const triggerToggleBreakpoint = () => { + disableToolbarButtons(); + var info = editor.current.lineInfo(editor.current.activeLineNo); + var baseUrl = ''; + + // If gutterMarker is undefined that means there is no marker defined previously + // So we need to set the breakpoint command here... + if (info.gutterMarkers == undefined) { + baseUrl = url_for('debugger.set_breakpoint', { + 'trans_id': params.transId, + 'line_no': editor.current.activeLineNo + 1, + 'set_type': '1', + }); + } else { + baseUrl = url_for('debugger.set_breakpoint', { + 'trans_id': params.transId, + 'line_no': editor.current.activeLineNo + 1, + 'set_type': '0', + }); + } + + api({ + url: baseUrl, + method: 'GET', + }) + .then(function (res) { + if (res.data.data.status) { + // Call function to create and update local variables .... + var info_local = editor.current.lineInfo(editor.current.activeLineNo); + + if (info_local.gutterMarkers != undefined) { + editor.current.setGutterMarker(editor.current.activeLineNo, 'breakpoints', null); + } else { + editor.current.setGutterMarker(editor.current.activeLineNo, 'breakpoints', debuggerMark()); + } + + enableToolbarButtons(); + } else if (res.data.status === 'NotConnected') { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while toggling breakpoint.') + ); + } + }) + .catch(function () { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while toggling breakpoint.') + ); + }); + }; + + const stopDebugging = () => { + disableToolbarButtons(); + // Make ajax call to listen the database message + var baseUrl = url_for( + 'debugger.execute_query', { + 'trans_id': params.transId, + 'query_type': 'abort_target', + }); + api({ + url: baseUrl, + method: 'GET', + }) + .then(function (res) { + if (res.data.data.status) { + // Call function to create and update local variables .... + setActiveLine(-1); + params.directDebugger.direct_execution_completed = true; + params.directDebugger.is_user_aborted_debugging = true; + + // Stop further pooling + params.directDebugger.is_polling_required = false; + + // Restarting debugging in the same transaction do not work + // We will give same behaviour as pgAdmin3 and disable all buttons + enableToolbarButtons(MENUS.CONTINUE); + + // Set the Alertify message to inform the user that execution + // is completed. + Notify.success(res.data.info, 3000); + } else if (res.data.data.status === 'NotConnected') { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while executing stop in debugging session.') + ); + } + }) + .catch(function () { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while executing stop in debugging session.') + ); + }); + }; + + const restart = (transId) => { + var baseUrl = url_for('debugger.restart', { 'trans_id': transId }); + disableToolbarButtons(); + + api({ + url: baseUrl, + }) + .then(function (res) { + // Restart the same function debugging with previous arguments + var restart_dbg = res.data.data.restart_debug ? 1 : 0; + + // Start pooling again + params.directDebugger.polling_timeout_idle = false; + params.directDebugger.is_polling_required = true; + pollResult(transId); + + if (restart_dbg) { + params.directDebugger.debug_restarted = true; + } + + /* + Need to check if restart debugging really require to open the input + dialog? If yes then we will get the previous arguments from database + and populate the input dialog, If no then we should directly start the + listener. + */ + if (res.data.data.result.require_input) { + params.funcArgsInstance.show(res.data.data.result, restart_dbg); + } else { + // Debugging of void function is started again so we need to start + // the listener again + var base_url = url_for('debugger.start_listener', { + 'trans_id': params.transId, + }); + + api({ + url: base_url, + method: 'GET', + }) + .then(function () { + if (params.directDebugger.debug_type) { + pollEndExecutionResult(params.transId); + } + }) + .catch(raisePollingError); + } + }) + .catch(raiseJSONError); + }; + + const pollEndExecuteError = (res ) => { + params.directDebugger.direct_execution_completed = true; + setActiveLine(-1); + + //Set the notification message to inform the user that execution is + // completed with error. + if (!params.directDebugger.is_user_aborted_debugging) { + Notify.error(res.data.info, 3000); + } + + // Update the message tab of the debugger + updateMessages(res.data.data.status_message); + + eventBus.current.fireEvent(DEBUGGER_EVENTS.FOCUS_PANEL, PANELS.MESSAGES); + disableToolbarButtons(); + // If debugging is stopped by user then do not enable + // continue/restart button + if (!params.directDebugger.is_user_aborted_debugging) { + enableToolbarButtons(MENUS.CONTINUE); + params.directDebugger.is_user_aborted_debugging = false; + } + + // Stop further pooling + params.directDebugger.is_polling_required = false; + }; + + const updateResultAnsMessages = (res) => { + if (res.data.data.result != null) { + setActiveLine(-1); + // Call function to update results information and set result panel focus + eventBus.current.fireEvent(DEBUGGER_EVENTS.SET_RESULTS, res.data.data.col_info, res.data.data.result); + eventBus.current.fireEvent(DEBUGGER_EVENTS.FOCUS_PANEL, PANELS.RESULTS); + + params.directDebugger.direct_execution_completed = true; + params.directDebugger.polling_timeout_idle = true; + + //Set the alertify message to inform the user that execution is completed. + Notify.success(res.data.info, 3000); + + // Update the message tab of the debugger + updateMessages(res.data.data.status_message); + + // Execution completed so disable the buttons other than + // "Continue/Start" button because user can still + // start the same execution again. + disableToolbarButtons(); + + // Stop further pooling + params.directDebugger.is_polling_required = false; + } + }; + + /* + For the direct debugging, we need to check weather the functions execution + is completed or not. After completion of the debugging, we will stop polling + the result until new execution starts. + */ + const pollEndExecutionResult = (transId) => { + // Do we need to poll? + if (!params.directDebugger.is_polling_required) { + return; + } + + // Make ajax call to listen the database message + var baseUrl = url_for('debugger.poll_end_execution_result', { + 'trans_id': transId, + }), + poll_end_timeout; + + /* + * During the execution we should poll the result in minimum seconds + * but once the execution is completed and wait for the another + * debugging session then we should decrease the polling frequency. + */ + if (params.directDebugger.polling_timeout_idle) { + // Poll the result to check that execution is completed or not + // after 1200 ms + poll_end_timeout = 1200; + } else { + // Poll the result to check that execution is completed or not + // after 350 ms + poll_end_timeout = 250; + } + + timeOut = setTimeout( + function () { + api({ + url: baseUrl, + method: 'GET', + }) + .then(function (res) { + if (res.data.data.status === 'Success') { + if (res.data.data.result == undefined) { + /* + "result" is undefined only in case of EDB procedure. + As Once the EDB procedure execution is completed then we are + not getting any result so we need ignore the result. + */ + setActiveLine(-1); + params.directDebugger.direct_execution_completed = true; + params.directDebugger.polling_timeout_idle = true; + + //Set the alertify message to inform the user that execution is completed. + Notify.success(res.data.info, 3000); + + // Update the message tab of the debugger + updateMessages(res.data.data.status_message); + + // Execution completed so disable the buttons other than + // "Continue/Start" button because user can still + // start the same execution again. + disableToolbarButtons(); + + // Stop further polling + params.directDebugger.is_polling_required = false; + } else { + updateResultAnsMessages(res); + } + } else if (res.data.data.status === 'Busy') { + // If status is Busy then poll the result by recursive call to + // the poll function + pollEndExecutionResult(transId); + // Update the message tab of the debugger + updateMessages(res.data.data.status_message); + } else if (res.data.status === 'NotConnected') { + Notify.alert( + gettext('Debugger poll end execution error'), + res.data.result + ); + } else if (res.data.data.status === 'ERROR') { + pollEndExecuteError(res); + } + }) + .catch(raisePollingError); + }, poll_end_timeout); + + }; + + // This function will update messages tab + const updateMessages = (msg) => { + if(msg) { + // Call function to update messages information + eventBus.current.fireEvent(DEBUGGER_EVENTS.SET_MESSAGES, msg, true); + } + }; + + const continueDebugger = () => { + disableToolbarButtons(); + + //Check first if previous execution was completed or not + if (params.directDebugger.direct_execution_completed && + params.directDebugger.direct_execution_completed == params.directDebugger.polling_timeout_idle) { + restart(params.transId); + } else { + // Make ajax call to listen the database message + var baseUrl = url_for('debugger.execute_query', { + 'trans_id': params.transId, + 'query_type': 'continue', + }); + api({ + url: baseUrl, + method: 'GET', + }) + .then(function (res) { + if (res.data.data.status) { + pollResult(params.transId); + } else { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while executing continue in debugging session.') + ); + } + }) + .catch(function () { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while executing continue in debugging session.') + ); + }); + } + }; + + const stepOver = () => { + disableToolbarButtons(); + + // Make ajax call to listen the database message + var baseUrl = url_for('debugger.execute_query', { + 'trans_id': params.transId, + 'query_type': 'step_over', + }); + api({ + url: baseUrl, + method: 'GET', + }) + .then(function (res) { + if (res.data.data.status) { + pollResult(params.transId); + } else { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while executing step over in debugging session.') + ); + } + }) + .catch(function () { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while executing step over in debugging session.') + ); + }); + + }; + + const getBreakpointList = (br_list) => { + var breakpoint_list = new Array(); + for (let val of br_list) { + if (val.linenumber != -1) { + breakpoint_list.push(val.linenumber); + } + } + + return breakpoint_list; + }; + + // Function to get the latest breakpoint information and update the + // gutters of codemirror + const updateBreakpoint = (transId, updateLocalVar = false) => { + var callBackFunc = (br_list) => { + // If there is no break point to clear then we should return from here. + if ((br_list.length == 1) && (br_list[0].linenumber == -1)) + return; + + var breakpoint_list = getBreakpointList(br_list); + + + for (let brk_val of breakpoint_list) { + var info = editor.current.lineInfo((brk_val - 1)); + + if (info.gutterMarkers != undefined) { + editor.current.setGutterMarker((brk_val - 1), 'breakpoints', null); + } else { + editor.current.setGutterMarker((brk_val - 1), 'breakpoints', debuggerMark()); + } + } + if (updateLocalVar) { + // Call function to create and update local variables .... + getLocalVariables(params.transId); + } + }; + getBreakpointInformation(transId, callBackFunc); + }; + + // Get the local variable information of the functions and update the grid + const getLocalVariables = (transId) => { + // Make ajax call to listen the database message + var baseUrl = url_for( + 'debugger.execute_query', { + 'trans_id': transId, + 'query_type': 'get_variables', + }); + api({ + url: baseUrl, + method: 'GET', + }) + .then(function (res) { + if (res.data.data.status === 'Success') { + // Call function to update local variables + let variablesResult = res.data.data.result.filter((lvar) => { + return lvar.varclass == 'L'; + }); + eventBus.current.fireEvent(DEBUGGER_EVENTS.SET_LOCAL_VARIABLES, variablesResult); + + let parametersResult = res.data.data.result.filter((lvar) => { + return lvar.varclass == 'A'; + }); + // update Parameter panel data. + eventBus.current.fireEvent(DEBUGGER_EVENTS.SET_PARAMETERS, parametersResult); + // If debug function is restarted then again start listener to + // read the updated messages. + if (params.directDebugger.debug_restarted) { + if (params.directDebugger.debug_type) { + pollEndExecutionResult(transId); + } + params.directDebugger.debug_restarted = false; + } + } else if (res.data.data.status === 'NotConnected') { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while fetching variable information.') + ); + } + }) + .catch(function () { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while fetching variable information.') + ); + }); + }; + + const getStackInformation = (transId) => { + // Make ajax call to listen the database message + var baseUrl = url_for( + 'debugger.execute_query', { + 'trans_id': transId, + 'query_type': 'get_stack_info', + }); + api({ + url: baseUrl, + method: 'GET', + }) + .then(function (res) { + if (res.data.data.status === 'Success') { + // Call function to update stack information + eventBus.current.fireEvent(DEBUGGER_EVENTS.SET_STACK, res.data.data.result); + // Call function to create and update stack information + getLocalVariables(params.transId); + } else if (res.data.data.status === 'NotConnected') { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while fetching stack information.') + ); + } + }) + .catch(function () { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while fetching stack information.') + ); + }); + }; + + + const updateBreakpointInfo = (res, transId) => { + if (res.data.data.result[0].src != editor.current.getValue()) { + editor.current.setValue(res.data.data.result[0].src); + try { + updateBreakpoint(transId); + } catch (err) { + Notify.alert(gettext('Error in update'), err); + } + + } + }; + + const updateInfo = (res, transId) => { + if (!params.directDebugger.debug_type && !params.directDebugger.first_time_indirect_debug) { + setLoaderText(''); + setActiveLine(-1); + clearAllBreakpoint(transId); + + params.directDebugger.first_time_indirect_debug = true; + params.directDebugger.polling_timeout_idle = false; + } else { + params.directDebugger.polling_timeout_idle = false; + setLoaderText(''); + // If the source is really changed then only update the breakpoint information + updateBreakpointInfo(res, transId); + + setActiveLine(res.data.data.result[0].linenumber - 2); + // Update the stack, local variables and parameters information + setTimeout(function () { + getStackInformation(transId); + }, 10); + } + }; + + const checkDebuggerStatus = (transId) => { + // If status is Busy then poll the result by recursive call to the poll function + if (!params.directDebugger.debug_type) { + setLoaderText(gettext('Waiting for another session to invoke the target...')); + // As we are waiting for another session to invoke the target,disable all the buttons + disableToolbarButtons(); + params.directDebugger.first_time_indirect_debug = false; + pollResult(transId); + } else { + pollResult(transId); + } + }; + + /* poll the actual result after user has executed the "continue", "step-into", + "step-over" actions and get the other updated information from the server. + */ + const pollResult = (transId) => { + // Do we need to poll? + if (!params.directDebugger.is_polling_required) { + return; + } + + // Make ajax call to listen the database message + var baseUrl = url_for('debugger.poll_result', { + 'trans_id': transId, + }), + poll_timeout; + + /* + During the execution we should poll the result in minimum seconds but + once the execution is completed and wait for the another debugging + session then we should decrease the polling frequency. + */ + if (params.directDebugger.polling_timeout_idle) { + // Poll the result after 1 second + poll_timeout = 1000; + } else { + // Poll the result after 200 ms + poll_timeout = 200; + } + + setTimeout( + function () { + api({ + url: baseUrl, + method: 'GET', + beforeSend: function (xhr) { + xhr.setRequestHeader( + pgAdmin.csrf_token_header, pgAdmin.csrf_token + ); + }, + }) + .then(function (res) { + if (res.data.data.status === 'Success') { + // If no result then poll again to wait for results. + if (res.data.data.result == null || res.data.data.result.length == 0) { + pollResult(transId); + } else { + updateInfo(res, transId); + // Enable all the buttons as we got the results + enableToolbarButtons(); + } + } else if (res.data.data.status === 'Busy') { + params.directDebugger.polling_timeout_idle = true; + checkDebuggerStatus(transId); + } else if (res.data.data.status === 'NotConnected') { + Notify.alert( + gettext('Debugger Error: poll_result'), + gettext('Error while polling result.') + ); + } + }) + .catch(raisePollingError); + }, poll_timeout); + + }; + + const stepInto = () => { + disableToolbarButtons(); + // Make ajax call to listen the database message + var baseUrl = url_for('debugger.execute_query', { + 'trans_id': params.transId, + 'query_type': 'step_into', + }); + api({ + url: baseUrl, + method: 'GET', + }) + .then(function (res) { + if (res.data.data.status) { + pollResult(params.transId); + } else { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while executing step into in debugging session.') + ); + } + }) + .catch(function () { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while executing step into in debugging session.') + ); + }); + }; + + + const onChangesLocalVarParameters = (data) => { + // Make api call to listen the database message + var baseUrl = url_for('debugger.deposit_value', { + 'trans_id': params.transId, + }); + api({ + url: baseUrl, + method: 'POST', + data: data, + }) + .then(function (res) { + if (res.data.data.status) { + // Get the updated variables value + getLocalVariables(params.transId); + // Show the message to the user that deposit value is success or failure + if (res.data.data.result) { + Notify.success(res.data.data.info, 3000); + } else { + Notify.error(res.data.data.info, 3000); + } + } + }) + .catch(function () { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while depositing variable value.') + ); + }); + }; + + useEffect(() => { + // Register all eventes for debugger. + eventBus.current.registerListener( + DEBUGGER_EVENTS.TRIGGER_CLEAR_ALL_BREAKPOINTS, triggerClearBreakpoint); + + eventBus.current.registerListener( + DEBUGGER_EVENTS.TRIGGER_TOGGLE_BREAKPOINTS, triggerToggleBreakpoint); + + eventBus.current.registerListener( + DEBUGGER_EVENTS.TRIGGER_STOP_DEBUGGING, stopDebugging); + + eventBus.current.registerListener( + DEBUGGER_EVENTS.TRIGGER_CONTINUE_DEBUGGING, continueDebugger); + + eventBus.current.registerListener( + DEBUGGER_EVENTS.TRIGGER_STEPOVER_DEBUGGING, stepOver); + + eventBus.current.registerListener( + DEBUGGER_EVENTS.TRIGGER_STEINTO_DEBUGGING, stepInto); + + eventBus.current.registerListener( + DEBUGGER_EVENTS.SET_LOCAL_VARIABLE_VALUE_CHANGE, onChangesLocalVarParameters); + + eventBus.current.registerListener( + DEBUGGER_EVENTS.SET_PARAMETERS_VALUE_CHANGE, onChangesLocalVarParameters); + + eventBus.current.registerListener(DEBUGGER_EVENTS.SET_FRAME, (frameId) => { + selectFrame(frameId); + }); + + eventBus.current.registerListener(DEBUGGER_EVENTS.FOCUS_PANEL, (panelId) => { + LayoutHelper.focus(docker.current, panelId); + }); + + + }, []); + + + useEffect(() => { + reflectPreferences(); + pgAdmin.Browser.onPreferencesChange('debugger', function () { + reflectPreferences(); + }); + // /* Clear the timeout if unmounted */ + return () => { + clearTimeout(timeOut); + }; + }, []); + + const DebuggerContextValue = React.useMemo(() => ({ + docker: docker.current, + api: api, + modal: modal, + params: qtState.params, + preferences: qtState.preferences, + }), [qtState.params, qtState.preferences]); + + // Define the debugger layout components such as DebuggerEditor to show queries and + let defaultLayout = { + dockbox: { + mode: 'vertical', + children: [ + { + mode: 'horizontal', + children: [ + { + tabs: [ + LayoutHelper.getPanel({ + id: PANELS.DEBUGGER, title: gettext('Debugger'), content: { + editor.current = edRef; + }} params={{ transId: params.transId, debuggerDirect: params.directDebugger }} /> + }) + ], + } + ] + }, + { + mode: 'horizontal', + children: [ + { + tabs: [ + LayoutHelper.getPanel({ + id: PANELS.PARAMETERS, title: gettext('Parameters'), content: , + }), + LayoutHelper.getPanel({ + id: PANELS.LOCAL_VARIABLES, title: gettext('Local Variables'), content: , + }), + LayoutHelper.getPanel({ + id: PANELS.MESSAGES, title: gettext('Messages'), content: , + }), + LayoutHelper.getPanel({ + id: PANELS.RESULTS, title: gettext('Result'), content: , + }), + LayoutHelper.getPanel({ + id: PANELS.STACK, title: gettext('Stack'), content: , + }), + ], + } + ] + }, + ] + }, + }; + + return ( + + + + + + + docker.current = obj} + defaultLayout={defaultLayout} + layoutId="SQLEditor/Layout" + savedLayout={savedLayout} + /> + + + + + ); +} + +DebuggerComponent.propTypes = { + pgAdmin: PropTypes.object, + selectedNodeInfo: PropTypes.object, + panel: PropTypes.object, + eventBusObj: PropTypes.object, + layout: PropTypes.string, + params: PropTypes.object +}; diff --git a/web/pgadmin/tools/debugger/static/js/components/DebuggerEditor.jsx b/web/pgadmin/tools/debugger/static/js/components/DebuggerEditor.jsx new file mode 100644 index 000000000..3d236c1a3 --- /dev/null +++ b/web/pgadmin/tools/debugger/static/js/components/DebuggerEditor.jsx @@ -0,0 +1,120 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import { makeStyles } from '@material-ui/styles'; +import PropTypes from 'prop-types'; + +import React, { useContext, useEffect } from 'react'; + +import gettext from 'sources/gettext'; +import url_for from 'sources/url_for'; + +import getApiInstance from '../../../../../static/js/api_instance'; +import CodeMirror from '../../../../../static/js/components/CodeMirror'; +import Notify from '../../../../../static/js/helpers/Notifier'; +import { DEBUGGER_EVENTS } from '../DebuggerConstants'; +import { DebuggerEventsContext } from './DebuggerComponent'; + + +const useStyles = makeStyles(() => ({ + sql: { + height: '100%', + } +})); + +export default function DebuggerEditor({ getEditor, params }) { + const classes = useStyles(); + const editor = React.useRef(); + const eventBus = useContext(DebuggerEventsContext); + + const api = getApiInstance(); + + function makeMarker() { + var marker = document.createElement('div'); + marker.style.color = '#822'; + marker.innerHTML = '●'; + return marker; + } + + function setBreakpoint(lineNo, setType) { + // Make ajax call to set/clear the break point by user + var baseUrl = url_for('debugger.set_breakpoint', { + 'trans_id': params.transId, + 'line_no': lineNo, + 'set_type': setType, + }); + api({ + url: baseUrl, + method: 'GET', + }) + .then(function(res) { + if (res.data.data.status) { + // Breakpoint has been set by the user + } + }) + .catch(function() { + Notify.alert( + gettext('Debugger Error'), + gettext('Error while setting debugging breakpoint.') + ); + }); + } + + function onBreakPoint(cm, n, gutter) { + // If breakpoint gutter is clicked and execution is not completed then only set the breakpoint + if (gutter == 'breakpoints' && !params.debuggerDirect.polling_timeout_idle) { + var info = cm.lineInfo(n); + // If gutterMarker is undefined that means there is no marker defined previously + // So we need to set the breakpoint command here... + if (info.gutterMarkers == undefined) { + setBreakpoint(n + 1, 1); //set the breakpoint + } else { + if (info.gutterMarkers.breakpoints == undefined) { + setBreakpoint(n + 1, 1); //set the breakpoint + } else { + setBreakpoint(n + 1, 0); //clear the breakpoint + } + } + + // If line folding is defined then gutterMarker will be defined so + // we need to find out 'breakpoints' information + var markers = info.gutterMarkers; + if (markers != undefined && info.gutterMarkers.breakpoints == undefined) + markers = info.gutterMarkers.breakpoints; + cm.setGutterMarker(n, 'breakpoints', markers ? null : makeMarker()); + } + } + + eventBus.registerListener(DEBUGGER_EVENTS.EDITOR_SET_SQL, (value, focus = true) => { + focus && editor.current?.focus(); + editor.current?.setValue(value); + }); + + useEffect(() => { + self = this; + // Register the callback when user set/clear the breakpoint on gutter area. + editor.current.on('gutterClick', onBreakPoint); + getEditor(editor.current); + }, [editor.current]); + return ( + { + editor.current = obj; + }} + gutters={['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'breakpoints']} + value={''} + className={classes.sql} + disabled={true} + />); +} + +DebuggerEditor.propTypes = { + getEditor: PropTypes.func, + params: PropTypes.object +}; diff --git a/web/pgadmin/tools/debugger/static/js/components/DebuggerMessages.jsx b/web/pgadmin/tools/debugger/static/js/components/DebuggerMessages.jsx new file mode 100644 index 000000000..e44b3ad57 --- /dev/null +++ b/web/pgadmin/tools/debugger/static/js/components/DebuggerMessages.jsx @@ -0,0 +1,47 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// +import { makeStyles } from '@material-ui/styles'; +import React from 'react'; +import { DebuggerEventsContext } from './DebuggerComponent'; +import { DEBUGGER_EVENTS } from '../DebuggerConstants'; + + +const useStyles = makeStyles((theme)=>({ + root: { + whiteSpace: 'pre-wrap', + fontFamily: '"Source Code Pro", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace', + padding: '5px 10px', + overflow: 'auto', + height: '100%', + fontSize: '12px', + userSelect: 'text', + backgroundColor: theme.palette.background.default, + color: theme.palette.text.primary, + ...theme.mixins.fontSourceCode, + } +})); + +export default function DebuggerMessages() { + const classes = useStyles(); + const [messageText, setMessageText] = React.useState(''); + const eventBus = React.useContext(DebuggerEventsContext); + React.useEffect(()=>{ + eventBus.registerListener(DEBUGGER_EVENTS.SET_MESSAGES, (text, append=false)=>{ + setMessageText((prev)=>{ + if(append) { + return prev+text; + } + return text; + }); + }); + }, []); + return ( +
{messageText}
+ ); +} diff --git a/web/pgadmin/tools/debugger/static/js/components/LocalVariablesAndParams.jsx b/web/pgadmin/tools/debugger/static/js/components/LocalVariablesAndParams.jsx new file mode 100644 index 000000000..6d774692e --- /dev/null +++ b/web/pgadmin/tools/debugger/static/js/components/LocalVariablesAndParams.jsx @@ -0,0 +1,177 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import _ from 'lodash'; +import clsx from 'clsx'; +import PropTypes from 'prop-types'; + +import React, { useCallback, useState } from 'react'; + +import { makeStyles } from '@material-ui/styles'; +import Paper from '@material-ui/core/Paper'; +import TableContainer from '@material-ui/core/TableContainer'; + +import gettext from 'sources/gettext'; + +import { DebuggerEventsContext } from './DebuggerComponent'; +import { DEBUGGER_EVENTS } from '../DebuggerConstants'; +import { commonTableStyles } from '../../../../../static/js/Theme'; +import { InputText, InputDateTimePicker } from '../../../../../static/js/components/FormComponents'; + + +const useStyles = makeStyles(() => ({ + table: { + minWidth: 650, + }, + summaryContainer: { + flexGrow: 1, + minHeight: 0, + overflow: 'auto', + maxHeight: '100%' + }, + container: { + maxHeight: '100%' + }, + cell: { + textAlign: 'center' + } + +})); + +export function LocalVariablesAndParams({ type }) { + const classes = useStyles(); + const tableClasses = commonTableStyles(); + const eventBus = React.useContext(DebuggerEventsContext); + const [variablesData, setVariablesData] = useState([]); + const preValue = React.useRef({}); + const [disableVarChange, setDisableVarChange] = useState(false); + + + React.useEffect(() => { + /* For Parameters and Local variables use the component. + type = 1 means 'Parameters' + type = 2 means 'LocalVariables' + */ + if (type == 1) { + eventBus.registerListener(DEBUGGER_EVENTS.SET_PARAMETERS, (val) => { + setVariablesData(val); + }); + } else if (type == 2) { + eventBus.registerListener(DEBUGGER_EVENTS.SET_LOCAL_VARIABLES, (val) => { + setVariablesData(val); + }); + } + eventBus.registerListener(DEBUGGER_EVENTS.GET_TOOL_BAR_BUTTON_STATUS, (status) => { + setDisableVarChange(status.disabled); + }); + }, []); + + const changeLocalVarVal = useCallback((data) => { + if (type == 1) { + eventBus.fireEvent(DEBUGGER_EVENTS.SET_PARAMETERS_VALUE_CHANGE, data); + } else if (type == 2) { + eventBus.fireEvent(DEBUGGER_EVENTS.SET_LOCAL_VARIABLE_VALUE_CHANGE, data); + } + + }); + + const onValueChange = (name, value) => { + setVariablesData((prev) => { + let retVal = [...prev]; + let nameIndex = _.findIndex(retVal, (r) => (r.name == name)); + retVal[nameIndex].value = value; + return retVal; + }); + }; + + return ( + + + + + + + + + + + + {variablesData.map((row) => ( + + + + + + ))} + { + variablesData.length == 0 && + + + + } + + +
{gettext('Name')}{gettext('Type')}{gettext('Value')}
{row.name}{row.dtype} + {row.dtype == 'date' ? + { + onValueChange(row.name, val); + }} + onFocus={() => { + preValue.current[row.name] = row.value; + }} + onBlur={() => { + let data = [{ + name: row.name, + value: row.value, + type: row.type + }]; + if (preValue.current[row.name] != row.value && !disableVarChange) { + preValue.current[row.name] = row.value; + changeLocalVarVal(data); + } + + }} + > + : + + { + onValueChange(row.name, val); + }} + onFocus={() => { + preValue.current[row.name] = row.value; + }} + onBlur={() => { + let data = [{ + name: row.name, + value: row.value, + type: row.type + }]; + if (preValue.current[row.name] != row.value && !disableVarChange) { + preValue.current[row.name] = row.value; + changeLocalVarVal(data); + } + + }} + >}
{gettext('No data found')}
+
+
+ ); +} + +LocalVariablesAndParams.propTypes = { + type: PropTypes.number +}; diff --git a/web/pgadmin/tools/debugger/static/js/components/Results.jsx b/web/pgadmin/tools/debugger/static/js/components/Results.jsx new file mode 100644 index 000000000..90dee9042 --- /dev/null +++ b/web/pgadmin/tools/debugger/static/js/components/Results.jsx @@ -0,0 +1,71 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// +import _ from 'lodash'; +import clsx from 'clsx'; + +import React, { useState } from 'react'; + +import { makeStyles } from '@material-ui/styles'; +import Paper from '@material-ui/core/Paper'; + +import { DebuggerEventsContext } from './DebuggerComponent'; +import { DEBUGGER_EVENTS } from '../DebuggerConstants'; +import { commonTableStyles } from '../../../../../static/js/Theme'; + + +const useStyles = makeStyles(() => ({ + table: { + minWidth: 650, + }, + summaryContainer: { + flexGrow: 1, + minHeight: 0, + overflow: 'auto', + } +})); + +export function Results() { + const classes = useStyles(); + const tableClasses = commonTableStyles(); + const eventBus = React.useContext(DebuggerEventsContext); + const [resultData, setResultData] = useState([]); + const [columns, setColumns] = useState([]); + React.useEffect(() => { + eventBus.registerListener(DEBUGGER_EVENTS.SET_RESULTS, (columnsData, values) => { + setResultData(values); + setColumns(columnsData); + }); + }, []); + return ( + + + + + { + columns.map((col) => ( + + )) + } + + + + {resultData.map((row) => ( + + { + columns.map((col) => ( + + )) + } + + ))} + +
{col.name}
{row[col.name]}
+
+ ); +} diff --git a/web/pgadmin/tools/debugger/static/js/components/Stack.jsx b/web/pgadmin/tools/debugger/static/js/components/Stack.jsx new file mode 100644 index 000000000..d3728a28a --- /dev/null +++ b/web/pgadmin/tools/debugger/static/js/components/Stack.jsx @@ -0,0 +1,84 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import _ from 'lodash'; +import clsx from 'clsx'; +import gettext from 'sources/gettext'; + +import React, { useState } from 'react'; + +import { makeStyles } from '@material-ui/styles'; +import TableContainer from '@material-ui/core/TableContainer'; +import Paper from '@material-ui/core/Paper'; + +import { DebuggerEventsContext } from './DebuggerComponent'; +import { DEBUGGER_EVENTS } from '../DebuggerConstants'; +import { commonTableStyles } from '../../../../../static/js/Theme'; +import { InputText } from '../../../../../static/js/components/FormComponents'; + + +const useStyles = makeStyles(() => ({ + table: { + minWidth: 650, + }, + summaryContainer: { + flexGrow: 1, + minHeight: 0, + overflow: 'auto', + maxHeight: '100%' + }, + container: { + maxHeight: '100%' + } +})); + +export function Stack() { + const classes = useStyles(); + const tableClasses = commonTableStyles(); + const eventBus = React.useContext(DebuggerEventsContext); + const [stackData, setStackData] = useState([]); + const [disableFrameSelection, setDisableFrameSelection] = useState(false); + + React.useEffect(() => { + eventBus.registerListener(DEBUGGER_EVENTS.SET_STACK, (stackValues) => { + setStackData(stackValues); + }); + eventBus.registerListener(DEBUGGER_EVENTS.GET_TOOL_BAR_BUTTON_STATUS, (status) => { + setDisableFrameSelection(status.disabled); + }); + }, []); + return ( + + + + + + + + + + + + {stackData?.map((row, index) => ( + + + + + + ))} + +
{gettext('Name')}{gettext('Value')}{gettext('Line No.')}
+ {row.targetname} + {row.args} + { if(!disableFrameSelection)eventBus.fireEvent(DEBUGGER_EVENTS.SET_FRAME, index);}}> +
+
+
+ ); +} diff --git a/web/pgadmin/tools/debugger/static/js/components/ToolBar.jsx b/web/pgadmin/tools/debugger/static/js/components/ToolBar.jsx new file mode 100644 index 000000000..1516412d7 --- /dev/null +++ b/web/pgadmin/tools/debugger/static/js/components/ToolBar.jsx @@ -0,0 +1,140 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import React, { useCallback, useContext, useEffect, useState } from 'react'; + +import { Box, makeStyles } from '@material-ui/core'; +import FormatIndentIncreaseIcon from '@material-ui/icons/FormatIndentIncrease'; +import FormatIndentDecreaseIcon from '@material-ui/icons/FormatIndentDecrease'; +import PlayCircleFilledWhiteIcon from '@material-ui/icons/PlayCircleFilledWhite'; +import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord'; +import NotInterestedIcon from '@material-ui/icons/NotInterested'; +import StopIcon from '@material-ui/icons/Stop'; +import HelpIcon from '@material-ui/icons/HelpRounded'; + +import gettext from 'sources/gettext'; +import { shortcut_key } from 'sources/keyboard_shortcuts'; +import url_for from 'sources/url_for'; + +import { PgButtonGroup, PgIconButton } from '../../../../../static/js/components/Buttons'; +import { DebuggerContext, DebuggerEventsContext } from './DebuggerComponent'; +import { DEBUGGER_EVENTS } from '../DebuggerConstants'; + +const useStyles = makeStyles((theme) => ({ + root: { + padding: '2px 4px', + display: 'flex', + alignItems: 'center', + gap: '4px', + backgroundColor: theme.otherVars.editorToolbarBg, + flexWrap: 'wrap', + ...theme.mixins.panelBorder.bottom, + }, +})); + +export function ToolBar() { + const classes = useStyles(); + const debuggerCtx = useContext(DebuggerContext); + const eventBus = useContext(DebuggerEventsContext); + let preferences = debuggerCtx.preferences.debugger; + + const [buttonsDisabled, setButtonsDisabled] = useState({ + 'stop': true, + 'clear-all-breakpoints': true, + 'toggle-breakpoint': true, + 'start': true, + 'step-over': true, + 'step-into': true, + }); + + const setDisableButton = useCallback((name, disable = true) => { + setButtonsDisabled((prev) => ({ ...prev, [name]: disable })); + }, []); + + const clearAllBreakpoint = useCallback(() => { + eventBus.fireEvent(DEBUGGER_EVENTS.TRIGGER_CLEAR_ALL_BREAKPOINTS); + }, []); + + const toggleBreakpoint = useCallback(() => { + eventBus.fireEvent(DEBUGGER_EVENTS.TRIGGER_TOGGLE_BREAKPOINTS); + }, []); + + const stop = useCallback(() => { + eventBus.fireEvent(DEBUGGER_EVENTS.TRIGGER_STOP_DEBUGGING); + }); + + const continueDebugger = useCallback(() => { + eventBus.fireEvent(DEBUGGER_EVENTS.TRIGGER_CONTINUE_DEBUGGING); + }); + + + const stepOverDebugger = useCallback(() => { + eventBus.fireEvent(DEBUGGER_EVENTS.TRIGGER_STEPOVER_DEBUGGING); + }); + + const stepInTODebugger = useCallback(() => { + eventBus.fireEvent(DEBUGGER_EVENTS.TRIGGER_STEINTO_DEBUGGING); + }); + + const onHelpClick=()=>{ + let url = url_for('help.static', {'filename': 'debugger.html'}); + window.open(url, 'pgadmin_help'); + }; + + useEffect(() => { + eventBus.registerListener(DEBUGGER_EVENTS.DISABLE_MENU, () => { + setDisableButton('start', true); + setDisableButton('step-into', true); + setDisableButton('step-over', true); + setDisableButton('clear-all-breakpoints', true); + setDisableButton('toggle-breakpoint', true); + setDisableButton('stop', true); + }); + + eventBus.registerListener(DEBUGGER_EVENTS.ENABLE_MENU, () => { + setDisableButton('start', false); + setDisableButton('step-into', false); + setDisableButton('step-over', false); + setDisableButton('clear-all-breakpoints', false); + setDisableButton('toggle-breakpoint', false); + setDisableButton('stop', false); + }); + + eventBus.registerListener(DEBUGGER_EVENTS.ENABLE_SPECIFIC_MENU, (key) => { + setDisableButton(key, false); + }); + }, []); + + + return ( + + + } onClick={() => { stepInTODebugger(); }} + accesskey={shortcut_key(preferences?.btn_step_into)} /> + } onClick={() => { stepOverDebugger(); }} + accesskey={shortcut_key(preferences?.btn_step_over)} /> + } onClick={() => { continueDebugger(); }} + accesskey={shortcut_key(preferences?.btn_start)} /> + + + } + accesskey={shortcut_key(preferences?.btn_toggle_breakpoint)} onClick={() => { toggleBreakpoint(); }} /> + } + accesskey={shortcut_key(preferences?.btn_clear_breakpoints)} onClick={() => { clearAllBreakpoint(); }} /> + + + } disabled={buttonsDisabled['stop']} onClick={() => { stop(); }} + accesskey={shortcut_key(preferences?.btn_stop)} /> + + + } onClick={onHelpClick} /> + + + ); +} diff --git a/web/pgadmin/tools/debugger/static/js/debugger.js b/web/pgadmin/tools/debugger/static/js/debugger.js deleted file mode 100644 index 0ac8293c3..000000000 --- a/web/pgadmin/tools/debugger/static/js/debugger.js +++ /dev/null @@ -1,670 +0,0 @@ -///////////////////////////////////////////////////////////// -// -// pgAdmin 4 - PostgreSQL Tools -// -// Copyright (C) 2013 - 2022, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -////////////////////////////////////////////////////////////// - -import Notify from '../../../../static/js/helpers/Notifier'; - -define([ - 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', - 'alertify', 'sources/pgadmin', 'pgadmin.browser', - 'backbone', 'pgadmin.backgrid', 'codemirror', 'pgadmin.backform', - 'pgadmin.tools.debugger.ui', 'pgadmin.tools.debugger.utils', - 'tools/sqleditor/static/js/show_query_tool', 'sources/utils', - 'pgadmin.authenticate.kerberos', 'tools/sqleditor/static/js/sqleditor_title', - 'wcdocker', 'pgadmin.browser.frame', -], function( - gettext, url_for, $, _, Alertify, pgAdmin, pgBrowser, Backbone, Backgrid, - CodeMirror, Backform, get_function_arguments, debuggerUtils, showQueryTool, - pgadminUtils, Kerberos, panelTitleFunc -) { - var pgTools = pgAdmin.Tools = pgAdmin.Tools || {}, - wcDocker = window.wcDocker; - - /* Return back, this has been called more than once */ - if (pgAdmin.Tools.Debugger) - return pgAdmin.Tools.Debugger; - - pgTools.Debugger = { - init: function() { - // We do not want to initialize the module multiple times. - if (this.initialized) - return; - - this.initialized = true; - - // Initialize the context menu to display the debugging options when user open the context menu for functions - pgBrowser.add_menus([{ - name: 'direct_debugger', - node: 'function', - module: this, - applies: ['object', 'context'], - callback: 'get_function_information', - category: gettext('Debugging'), - priority: 10, - label: gettext('Debug'), - data: { - object: 'function', - }, - icon: 'fa fa-arrow-circle-right', - enable: 'can_debug', - }, { - name: 'global_debugger', - node: 'function', - module: this, - applies: ['object', 'context'], - callback: 'check_func_debuggable', - category: gettext('Debugging'), - priority: 10, - label: gettext('Set Breakpoint'), - data: { - object: 'function', - debug_type: 'indirect', - }, - icon: 'fa fa-arrow-circle-right', - enable: 'can_debug', - }, { - name: 'procedure_direct_debugger', - node: 'procedure', - module: this, - applies: ['object', 'context'], - callback: 'get_function_information', - category: gettext('Debugging'), - priority: 10, - label: gettext('Debug'), - data: { - object: 'procedure', - }, - icon: 'fa fa-arrow-circle-right', - enable: 'can_debug', - }, { - name: 'procedure_indirect_debugger', - node: 'procedure', - module: this, - applies: ['object', 'context'], - callback: 'check_func_debuggable', - category: gettext('Debugging'), - priority: 10, - label: gettext('Set Breakpoint'), - data: { - object: 'procedure', - debug_type: 'indirect', - }, - icon: 'fa fa-arrow-circle-right', - enable: 'can_debug', - }, { - name: 'trigger_function_indirect_debugger', - node: 'trigger_function', - module: this, - applies: ['object', 'context'], - callback: 'check_func_debuggable', - priority: 10, - label: gettext('Set Breakpoint'), - category: gettext('Debugging'), - icon: 'fa fa-arrow-circle-right', - data: { - object: 'trigger_function', - debug_type: 'indirect', - }, - enable: 'can_debug', - }, { - name: 'trigger_indirect_debugger', - node: 'trigger', - module: this, - applies: ['object', 'context'], - callback: 'check_func_debuggable', - priority: 10, - label: gettext('Set Breakpoint'), - category: gettext('Debugging'), - icon: 'fa fa-arrow-circle-right', - data: { - object: 'trigger', - debug_type: 'indirect', - }, - enable: 'can_debug', - }, { - name: 'package_function_direct_debugger', - node: 'edbfunc', - module: this, - applies: ['object', 'context'], - callback: 'get_function_information', - category: gettext('Debugging'), - priority: 10, - label: gettext('Debug'), - data: { - object: 'edbfunc', - }, - icon: 'fa fa-arrow-circle-right', - enable: 'can_debug', - }, { - name: 'package_function_global_debugger', - node: 'edbfunc', - module: this, - applies: ['object', 'context'], - callback: 'check_func_debuggable', - category: gettext('Debugging'), - priority: 10, - label: gettext('Set Breakpoint'), - data: { - object: 'edbfunc', - debug_type: 'indirect', - }, - icon: 'fa fa-arrow-circle-right', - enable: 'can_debug', - }, { - name: 'package_procedure_direct_debugger', - node: 'edbproc', - module: this, - applies: ['object', 'context'], - callback: 'get_function_information', - category: gettext('Debugging'), - priority: 10, - label: gettext('Debug'), - data: { - object: 'edbproc', - }, - icon: 'fa fa-arrow-circle-right', - enable: 'can_debug', - }, { - name: 'package_procedure_global_debugger', - node: 'edbproc', - module: this, - applies: ['object', 'context'], - callback: 'check_func_debuggable', - category: gettext('Debugging'), - priority: 10, - label: gettext('Set Breakpoint'), - data: { - object: 'edbproc', - debug_type: 'indirect', - }, - icon: 'fa fa-arrow-circle-right', - enable: 'can_debug', - }]); - - // Create and load the new frame required for debugger panel - this.frame = new pgBrowser.Frame({ - name: 'frm_debugger', - title: gettext('Debugger'), - width: 500, - isCloseable: true, - isPrivate: true, - icon: 'fa fa-bug', - url: 'about:blank', - }); - - this.frame.load(pgBrowser.docker); - - let self = this; - let cacheIntervalId = setInterval(function() { - if(pgBrowser.preference_version() > 0) { - self.preferences = pgBrowser.get_preferences_for_module('debugger'); - clearInterval(cacheIntervalId); - } - },0); - - pgBrowser.onPreferencesChange('debugger', function() { - self.preferences = pgBrowser.get_preferences_for_module('debugger'); - }); - }, - // It will check weather the function is actually debuggable or not with pre-required condition. - can_debug: function(itemData, item, data) { - var t = pgBrowser.tree, - i = item, - d = itemData; - // To iterate over tree to check parent node - while (i) { - if ('catalog' == d._type) { - //Check if we are not child of catalog - return false; - } - i = t.hasParent(i) ? t.parent(i) : null; - d = i ? t.itemData(i) : null; - } - - // Find the function is really available in database - var tree = pgBrowser.tree, - info = tree.selected(), - d_ = info ? tree.itemData(info) : undefined; - - if (!d_) - return false; - - var treeInfo = tree.getTreeNodeHierarchy(info); - - // For indirect debugging user must be super user - if (data && data.debug_type && data.debug_type == 'indirect' && - !treeInfo.server.user.is_superuser) - return false; - - // Fetch object owner - var obj_owner = treeInfo.function && treeInfo.function.funcowner || - treeInfo.procedure && treeInfo.procedure.funcowner || - treeInfo.edbfunc && treeInfo.edbfunc.funcowner || - treeInfo.edbproc && treeInfo.edbproc.funcowner; - - // Must be a super user or object owner to create breakpoints of any kind - if (!(treeInfo.server.user.is_superuser || obj_owner == treeInfo.server.user.name)) - return false; - - // For trigger node, language will be undefined - we should allow indirect debugging for trigger node - if ((d_.language == undefined && d_._type == 'trigger') || - (d_.language == undefined && d_._type == 'edbfunc') || - (d_.language == undefined && d_._type == 'edbproc')) { - return true; - } - - if (d_.language != 'plpgsql' && d_.language != 'edbspl') { - return false; - } - - return true; - }, - /* - For the direct debugging, we need to fetch the function information to display in the dialog so "generate_url" - will dynamically generate the URL from the server_id, database_id, schema_id and function id. - */ - generate_url: function(_url, treeInfo, node) { - var url = '{BASEURL}{URL}/{OBJTYPE}{REF}', - ref = ''; - - _.each( - _.sortBy( - _.values( - _.pick(treeInfo, - function(v, k) { - return (k != 'server_group'); - }) - ), - function(o) { - return o.priority; - } - ), - function(o) { - ref = pgadminUtils.sprintf('%s/%s', ref, encodeURI(o._id)); - }); - - var args = { - 'URL': _url, - 'BASEURL': url_for('debugger.index'), - 'REF': ref, - 'OBJTYPE': encodeURI(node.type), - }; - - return url.replace(/{(\w+)}/g, function(match, arg) { - return args[arg]; - }); - }, - - onFail: function(xhr) { - try { - var err = JSON.parse(xhr.responseText); - if (err.success == 0) { - Notify.alert(gettext('Debugger Error'), err.errormsg); - } - } catch (e) { - console.warn(e.stack || e); - } - }, - - check_func_debuggable: function(args, item) { - var t = pgBrowser.tree, - i = item || t.selected(), - d = i ? t.itemData(i) : undefined, - node = d && pgBrowser.Nodes[d._type]; - - if (!d) - return; - - var treeInfo = t.getTreeNodeHierarchy(i), - _url = this.generate_url('init', treeInfo, node); - - var self = this; - $.ajax({ - url: _url, - cache: false, - }) - .done(function(res) { - self.start_global_debugger(args, item, res.data.trans_id); - }) - .fail(function(xhr) { - self.onFail(xhr); - }); - }, - - panel_rename_event: function(panel_data, panel, treeInfo) { - Alertify.prompt('', panel_data.$titleText[0].textContent, - // We will execute this function when user clicks on the OK button - function(evt, value) { - if(value) { - // Remove the leading and trailing white spaces. - value = value.trim(); - let preferences = pgBrowser.get_preferences_for_module('browser'); - var name = debuggerUtils.getAppropriateLabel(treeInfo); - debuggerUtils.setDebuggerTitle(panel, preferences, name, treeInfo.schema.label, treeInfo.database.label, value, pgBrowser); - } - }, - // We will execute this function when user clicks on the Cancel - // button. Do nothing just close it. - function(evt) { evt.cancel = false; } - ).set({'title': gettext('Rename Panel')}); - }, - - //Callback function when user start the indirect debugging ( Listen to another session to invoke the target ) - start_global_debugger: function(args, item, trans_id) { - // Initialize the target and create asynchronous connection and unique transaction ID - - var self = this; - var t = pgBrowser.tree, - i = item || t.selected(), - d = i ? t.itemData(i) : undefined, - tree_data = pgBrowser.tree.translateTreeNodeIdFromReactTree(i), - db_data = pgBrowser.tree.findNode(tree_data[3]), - dbNode = db_data.domNode; - - if (!d) - return; - - var treeInfo = t.getTreeNodeHierarchy(i), - baseUrl; - - if (d._type == 'function' || d._type == 'edbfunc') { - baseUrl = url_for( - 'debugger.initialize_target_for_function', { - 'debug_type': 'indirect', - 'trans_id': trans_id, - 'sid': treeInfo.server._id, - 'did': treeInfo.database._id, - 'scid': treeInfo.schema._id, - 'func_id': debuggerUtils.getFunctionId(treeInfo), - } - ); - } else if (d._type == 'procedure' || d._type == 'edbproc') { - baseUrl = url_for( - 'debugger.initialize_target_for_function', { - 'debug_type': 'indirect', - 'trans_id': trans_id, - 'sid': treeInfo.server._id, - 'did': treeInfo.database._id, - 'scid': treeInfo.schema._id, - 'func_id': debuggerUtils.getProcedureId(treeInfo), - } - ); - } else if (d._type == 'trigger_function') { - baseUrl = url_for( - 'debugger.initialize_target_for_function', { - 'debug_type': 'indirect', - 'trans_id': trans_id, - 'sid': treeInfo.server._id, - 'did': treeInfo.database._id, - 'scid': treeInfo.schema._id, - 'func_id': treeInfo.trigger_function._id, - } - ); - } else if (d._type == 'trigger' && 'table' in treeInfo) { - baseUrl = url_for( - 'debugger.initialize_target_for_trigger', { - 'debug_type': 'indirect', - 'trans_id': trans_id, - 'sid': treeInfo.server._id, - 'did': treeInfo.database._id, - 'scid': treeInfo.schema._id, - 'func_id': treeInfo.table._id, - 'tri_id': treeInfo.trigger._id, - } - ); - } else if (d._type == 'trigger' && 'view' in treeInfo) { - baseUrl = url_for( - 'debugger.initialize_target_for_trigger', { - 'debug_type': 'indirect', - 'trans_id': trans_id, - 'sid': treeInfo.server._id, - 'did': treeInfo.database._id, - 'scid': treeInfo.schema._id, - 'func_id': treeInfo.view._id, - 'tri_id': treeInfo.trigger._id, - } - ); - } - - $.ajax({ - url: baseUrl, - method: 'GET', - }) - .done(function(res) { - var url = url_for('debugger.direct', { - 'trans_id': res.data.debuggerTransId, - }); - var browser_preferences = pgBrowser.get_preferences_for_module('browser'); - var open_new_tab = browser_preferences.new_browser_tab_open; - if (open_new_tab && open_new_tab.includes('debugger')) { - window.open(url, '_blank'); - // Send the signal to runtime, so that proper zoom level will be set. - setTimeout(function() { - pgBrowser.send_signal_to_runtime('Runtime new window opened'); - }, 500); - } else { - pgBrowser.Events.once( - 'pgadmin-browser:frame:urlloaded:frm_debugger', - function(frame) { - frame.openURL(url); - }); - - // Create the debugger panel as per the data received from user input dialog. - var dashboardPanel = pgBrowser.docker.findPanels( - 'properties' - ), - panel = pgBrowser.docker.addPanel( - 'frm_debugger', wcDocker.DOCK.STACKED, dashboardPanel[0] - ), - db_label = treeInfo.database.label; - - if(res.data.data_obj.db_name != treeInfo.database.label) { - db_label = res.data.data_obj.db_name; - var message = `Current database has been moved or renamed to ${db_label}. Click on the OK button to refresh the database name.`; - panelTitleFunc.refresh_db_node(message, dbNode); - } - - var label = debuggerUtils.getAppropriateLabel(treeInfo); - debuggerUtils.setDebuggerTitle(panel, browser_preferences, label, db_label, db_label, null, pgBrowser); - - panel.focus(); - - // Panel Closed event - panel.on(wcDocker.EVENT.CLOSED, function() { - var closeUrl = url_for('debugger.close', { - 'trans_id': res.data.debuggerTransId, - }); - $.ajax({ - url: closeUrl, - method: 'DELETE', - }); - }); - - // Panel Rename event - panel.on(wcDocker.EVENT.RENAME, function(panel_data) { - self.panel_rename_event(panel_data, panel, treeInfo); - }); - } - }) - .fail(function(xhr) { - try { - var err = JSON.parse(xhr.responseText); - if (err.errormsg.search('Ticket expired') !== -1) { - let fetchTicket = Kerberos.fetch_ticket(); - fetchTicket.then( - function() { - self.start_global_debugger(); - }, - function(error) { - Notify.alert(gettext('Debugger Error'), error); - } - ); - } else { - if (err.success == 0) { - Notify.alert(gettext('Debugger Error'), err.errormsg); - } - } - } catch (e) { - console.warn(e.stack || e); - } - }); - }, - - /* - Get the function information for the direct debugging to display the functions arguments and other informations - in the user input dialog - */ - get_function_information: function(args, item) { - - var self = this, - t = pgBrowser.tree, - i = item || t.selected(), - d = i ? t.itemData(i) : undefined, - node = d && pgBrowser.Nodes[d._type], - tree_data = pgBrowser.tree.translateTreeNodeIdFromReactTree(i), - db_data = pgBrowser.tree.findNode(tree_data[3]), - dbNode = db_data.domNode; - - if (!d) - return; - - var is_edb_proc = d._type == 'edbproc'; - - var treeInfo = t.getTreeNodeHierarchy(i), - _url = this.generate_url('init', treeInfo, node); - - $.ajax({ - url: _url, - cache: false, - }) - .done(function(res) { - - let debug_info = res.data.debug_info, - trans_id = res.data.trans_id; - // Open Alertify the dialog to take the input arguments from user if function having input arguments - if (debug_info[0]['require_input']) { - get_function_arguments(debug_info[0], 0, is_edb_proc, trans_id); - } else { - // Initialize the target and create asynchronous connection and unique transaction ID - // If there is no arguments to the functions then we should not ask for for function arguments and - // Directly open the panel - var _t = pgBrowser.tree, - _i = _t.selected(), - _d = _i ? _t.itemData(_i) : undefined; - - if (!_d) - return; - - var newTreeInfo = _t.getTreeNodeHierarchy(_i), - baseUrl; - - if (_d._type == 'function' || _d._type == 'edbfunc') { - baseUrl = url_for( - 'debugger.initialize_target_for_function', { - 'debug_type': 'direct', - 'trans_id': trans_id, - 'sid': newTreeInfo.server._id, - 'did': newTreeInfo.database._id, - 'scid': newTreeInfo.schema._id, - 'func_id': debuggerUtils.getFunctionId(newTreeInfo), - } - ); - } else if(_d._type == 'procedure' || _d._type == 'edbproc') { - baseUrl = url_for( - 'debugger.initialize_target_for_function', { - 'debug_type': 'direct', - 'trans_id': trans_id, - 'sid': newTreeInfo.server._id, - 'did': newTreeInfo.database._id, - 'scid': newTreeInfo.schema._id, - 'func_id': debuggerUtils.getProcedureId(newTreeInfo), - } - ); - } - - $.ajax({ - url: baseUrl, - method: 'GET', - }) - .done(function(result) { - - var data = result.data; - - var url = url_for('debugger.direct', { - 'trans_id': trans_id, - }); - - var browser_preferences = pgBrowser.get_preferences_for_module('browser'); - var open_new_tab = browser_preferences.new_browser_tab_open; - if (open_new_tab && open_new_tab.includes('debugger')) { - window.open(url, '_blank'); - // Send the signal to runtime, so that proper zoom level will be set. - setTimeout(function() { - pgBrowser.send_signal_to_runtime('Runtime new window opened'); - }, 500); - } else { - pgBrowser.Events.once( - 'pgadmin-browser:frame:urlloaded:frm_debugger', - function(frame) { - frame.openURL(url); - }); - - // Create the debugger panel as per the data received from user input dialog. - var dashboardPanel = pgBrowser.docker.findPanels( - 'properties' - ), - panel = pgBrowser.docker.addPanel( - 'frm_debugger', wcDocker.DOCK.STACKED, dashboardPanel[0] - ), - db_label = newTreeInfo.database.label; - pgadminUtils.registerDetachEvent(panel); - - if(data && data.data_obj && data.data_obj.db_name != newTreeInfo.database.label) { - db_label = data.data_obj.db_name; - var message = `Current database has been moved or renamed to ${db_label}. Click on the OK button to refresh the database name.`; - panelTitleFunc.refresh_db_node(message, dbNode); - } - - var label = debuggerUtils.getAppropriateLabel(newTreeInfo); - debuggerUtils.setDebuggerTitle(panel, browser_preferences, label, newTreeInfo.schema.label, db_label, null, pgBrowser); - - panel.focus(); - - // Register Panel Closed event - panel.on(wcDocker.EVENT.CLOSED, function() { - var closeUrl = url_for('debugger.close', { - 'trans_id': trans_id, - }); - $.ajax({ - url: closeUrl, - method: 'DELETE', - }); - }); - - // Panel Rename event - panel.on(wcDocker.EVENT.RENAME, function(panel_data) { - self.panel_rename_event(panel_data, panel, treeInfo); - }); - } - }) - .fail(function(e) { - Notify.alert( - gettext('Debugger Target Initialization Error'), - e.responseJSON.errormsg - ); - }); - } - }) - .fail(function(xhr) { - self.onFail(xhr); - }); - }, - }; - - return pgAdmin.Tools.Debugger; -}); diff --git a/web/pgadmin/tools/debugger/static/js/debugger_ui.js b/web/pgadmin/tools/debugger/static/js/debugger_ui.js index 149d1d5fa..5ad628de0 100644 --- a/web/pgadmin/tools/debugger/static/js/debugger_ui.js +++ b/web/pgadmin/tools/debugger/static/js/debugger_ui.js @@ -1,1053 +1,26 @@ -///////////////////////////////////////////////////////////// -// -// pgAdmin 4 - PostgreSQL Tools -// -// Copyright (C) 2013 - 2022, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////// +//// +//// pgAdmin 4 - PostgreSQL Tools +//// +//// Copyright (C) 2013 - 2022, The pgAdmin Development Team +//// This software is released under the PostgreSQL Licence +//// +//////////////////////////////////////////////////////////////// + +import React from 'react'; + +import gettext from 'sources/gettext'; +import pgAdmin from 'sources/pgadmin'; import Notify from '../../../../static/js/helpers/Notifier'; +import DebuggerArgumentComponent from './components/DebuggerArgumentComponent'; -define([ - 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone', - 'pgadmin.alertifyjs', 'sources/pgadmin', 'pgadmin.browser', - 'pgadmin.backgrid', 'sources/window', 'pgadmin.tools.debugger.utils','sources/utils', - 'wcdocker', -], function( - gettext, url_for, $, _, Backbone, Alertify, pgAdmin, pgBrowser, Backgrid, - pgWindow, debuggerUtils, commonUtils, -) { +export default class FunctionArguments { - var wcDocker = window.wcDocker; - - /* - * Function used to return the respective Backgrid control based on the data type - * of function input argument. - */ - var cellFunction = function(model) { - var variable_type = model.get('type'); - - // if variable type is an array then we need to render the custom control to take the input from user. - if (variable_type.indexOf('[]') != -1) { - var data_type = variable_type.replace('[]' ,''); - - switch (data_type) { - case 'boolean': - return Backgrid.Extension.InputBooleanArrayCell; - case 'integer': - case 'smallint': - case 'bigint': - case 'serial': - case 'smallserial': - case 'bigserial': - case 'oid': - case 'cid': - case 'xid': - case 'tid': - return Backgrid.Extension.InputIntegerArrayCell; - case 'real': - case 'numeric': - case 'double precision': - case 'decimal': - return Backgrid.Extension.InputNumberArrayCell; - default: - return Backgrid.Extension.InputStringArrayCell; - } - } else { - switch (variable_type) { - case 'boolean': - return Backgrid.BooleanCell.extend({ - formatter: Backgrid.BooleanCellFormatter, - }); - case 'integer': - case 'smallint': - case 'bigint': - case 'serial': - case 'smallserial': - case 'bigserial': - case 'oid': - case 'cid': - case 'xid': - case 'tid': - // As we are getting this value as text from sqlite database so we need to type cast it. - if (model.get('value') != undefined) { - model.set({ - 'value': isNaN(parseInt(model.get('value'))) ? null : parseInt(model.get('value')), - }, { - silent: true, - }); - } - - return Backgrid.IntegerCell; - case 'real': - case 'numeric': - case 'double precision': - case 'decimal': - // As we are getting this value as text from sqlite database so we need to type cast it. - if (model.get('value') != undefined) { - model.set({ - 'value': parseFloat(model.get('value')), - }, { - silent: true, - }); - } - return Backgrid.NumberCell; - case 'string': - return Backgrid.StringCell; - case 'date': - return Backgrid.DateCell; - default: - return Backgrid.Cell; - } - } - }; - - /* - * Function used to return the respective Backgrid string or boolean control based on the data type - * of function input argument. - */ - var cellExprControlFunction = function(model) { - var variable_type = model.get('type'); - if (variable_type.indexOf('[]') != -1) { - return Backgrid.StringCell; - } - return Backgrid.BooleanCell; - }; - - /** - * DebuggerInputArgsModel used to represent input parameters for the function to debug - * for function objects. - **/ - var DebuggerInputArgsModel = Backbone.Model.extend({ - defaults: { - name: undefined, - type: undefined, - is_null: undefined, - expr: undefined, - value: undefined, - use_default: undefined, - default_value: undefined, - }, - validate: function() { - if (_.isUndefined(this.get('value')) || - _.isNull(this.get('value')) || - String(this.get('value')).replace(/^\s+|\s+$/g, '') == '') { - var msg = gettext('Please enter a value for the parameter.'); - this.errorModel.set('value', msg); - return msg; - } else { - this.errorModel.unset('value'); - } - return null; - }, - }); - - // Collection which contains the model for function informations. - var DebuggerInputArgCollections = Backbone.Collection.extend({ - model: DebuggerInputArgsModel, - }); - - // function will enable/disable the use_default column based on the value received. - var disableDefaultCell = function(d) { - if (d instanceof Backbone.Model) { - return d.get('use_default'); - } - return false; - }; - - // Enable/Disable the control based on the array data type of the function input arguments - var disableExpressionControl = function(d) { - if (d instanceof Backbone.Model) { - var argType = d.get('type'); - if (argType.indexOf('[]') != -1) { - return false; - } - return true; - } - }; - - var raiseError = function() { - Notify.alert( - gettext('Debugger Error'), - gettext('Unable to set the arguments on the server') - ); - }; - - return function(debugInfo, restartDebug, isEdbProc, transId) { - if (!Alertify.debuggerInputArgsDialog) { - Alertify.dialog('debuggerInputArgsDialog', function factory() { - return { - main: function(title, debug_info, restart_debug, is_edb_proc, trans_id) { - this.preferences = pgWindow.default.pgAdmin.Browser.get_preferences_for_module('debugger'); - this.set('title', title); - - // setting value in alertify settings allows us to access it from - // other functions other than main function. - this.set('debug_info', debug_info); - this.set('restart_debug', restart_debug); - this.set('trans_id', trans_id); - this.set('is_edb_proc', is_edb_proc); - - // Variables to store the data sent from sqlite database - var func_args_data = this.func_args_data = []; - - // As we are not getting pgBrowser.tree when we debug again - // so tree info will be updated from the server data - if (restart_debug == 0) { - var t = pgBrowser.tree, - i = t.selected(), - d = i ? t.itemData(i) : undefined; - - if (!d) - return; - - var treeInfo = t.getTreeNodeHierarchy(i), - _Url; - - if (d._type == 'function') { - // Get the existing function parameters available from sqlite database - _Url = url_for('debugger.get_arguments', { - 'sid': treeInfo.server._id, - 'did': treeInfo.database._id, - 'scid': treeInfo.schema._id, - 'func_id': treeInfo.function._id, - }); - } else if (d._type == 'procedure') { - // Get the existing function parameters available from sqlite database - _Url = url_for('debugger.get_arguments', { - 'sid': treeInfo.server._id, - 'did': treeInfo.database._id, - 'scid': treeInfo.schema._id, - 'func_id': treeInfo.procedure._id, - }); - } else if (d._type == 'edbfunc') { - // Get the existing function parameters available from sqlite database - _Url = url_for('debugger.get_arguments', { - 'sid': treeInfo.server._id, - 'did': treeInfo.database._id, - 'scid': treeInfo.schema._id, - 'func_id': treeInfo.edbfunc._id, - }); - } else if (d._type == 'edbproc') { - // Get the existing function parameters available from sqlite database - _Url = url_for('debugger.get_arguments', { - 'sid': treeInfo.server._id, - 'did': treeInfo.database._id, - 'scid': treeInfo.schema._id, - 'func_id': treeInfo.edbproc._id, - }); - } - } else { - // Get the existing function parameters available from sqlite database - _Url = url_for('debugger.get_arguments', { - 'sid': debug_info.server_id, - 'did': debug_info.database_id, - 'scid': debug_info.schema_id, - 'func_id': debug_info.function_id, - }); - } - $.ajax({ - url: _Url, - method: 'GET', - async: false, - }) - .done(function(res_get) { - if (res_get.data.args_count != 0) { - for (i = 0; i < res_get.data.result.length; i++) { - // Below will format the data to be stored in sqlite database - func_args_data.push({ - 'arg_id': res_get.data.result[i]['arg_id'], - 'is_null': res_get.data.result[i]['is_null'], - 'is_expression': res_get.data.result[i]['is_expression'], - 'use_default': res_get.data.result[i]['use_default'], - 'value': res_get.data.result[i]['value'], - }); - } - } - }) - .fail(function() { - Notify.alert( - gettext('Debugger Error'), - gettext('Unable to fetch the arguments from server') - ); - }); - - var argname, argtype, argmode, default_args_count, default_args, arg_cnt; - - var value_header = Backgrid.HeaderCell.extend({ - // Add fixed width to the "value" column - className: 'width_percent_25', - }); - - var def_val_list = [], - gridCols = [{ - name: 'name', - label: gettext('Name'), - type: 'text', - editable: false, - cell: 'string', - }, - { - name: 'type', - label: gettext('Type'), - type: 'text', - editable: false, - cell: 'string', - }, - { - name: 'is_null', - label: gettext('Null?'), - type: 'boolean', - cell: 'boolean', - align_center: true, - }, - { - name: 'expr', - label: gettext('Expression?'), - type: 'boolean', - cellFunction: cellExprControlFunction, - editable: disableExpressionControl, - align_center: true, - }, - { - name: 'value', - label: gettext('Value'), - type: 'text', - editable: true, - cellFunction: cellFunction, - headerCell: value_header, - align_center: true, - }, - { - name: 'use_default', - label: gettext('Use Default?'), - type: 'boolean', - cell: 'boolean', - editable: disableDefaultCell, - }, - { - name: 'default_value', - label: gettext('Default'), - type: 'text', - editable: false, - cell: 'string', - }, - ]; - - var my_obj = []; - var func_obj = []; - - // Below will calculate the input argument id required to store in sqlite database - var input_arg_id = this.input_arg_id = [], - k; - if (debug_info['proargmodes'] != null) { - var argmode_1 = debug_info['proargmodes'].split(','); - for (k = 0; k < argmode_1.length; k++) { - if (argmode_1[k] == 'i' || argmode_1[k] == 'b' || - (is_edb_proc && argmode_1[k] == 'o')) { - input_arg_id.push(k); - } - } - } else { - var argtype_1 = debug_info['proargtypenames'].split(','); - for (k = 0; k < argtype_1.length; k++) { - input_arg_id.push(k); - } - } - - argtype = debug_info['proargtypenames'].split(','); - - if (debug_info['proargmodes'] != null) { - argmode = debug_info['proargmodes'].split(','); - } - - if (debug_info['pronargdefaults']) { - default_args_count = debug_info['pronargdefaults']; - default_args = debug_info['proargdefaults'].split(','); - arg_cnt = default_args_count; - } - - var vals, values, index, use_def_value, j; - - if (debug_info['proargnames'] != null) { - argname = debug_info['proargnames'].split(','); - - // It will assign default values to "Default value" column - for (j = (argname.length - 1); j >= 0; j--) { - if (debug_info['proargmodes'] != null) { - if (argmode && (argmode[j] == 'i' || argmode[j] == 'b' || - (is_edb_proc && argmode[j] == 'o'))) { - if (arg_cnt) { - arg_cnt = arg_cnt - 1; - def_val_list[j] = default_args[arg_cnt]; - } else { - def_val_list[j] = ''; - } - } - } else if (arg_cnt) { - arg_cnt = arg_cnt - 1; - def_val_list[j] = default_args[arg_cnt]; - } else { - def_val_list[j] = ''; - } - } - - if (argtype.length != 0) { - for (i = 0; i < argtype.length; i++) { - if (debug_info['proargmodes'] != null) { - if (argmode && (argmode[i] == 'i' || argmode[i] == 'b' || - (is_edb_proc && argmode[i] == 'o'))) { - use_def_value = false; - if (def_val_list[i] != '') { - use_def_value = true; - } - my_obj.push({ - 'name': argname[i], - 'type': argtype[i], - 'use_default': use_def_value, - 'default_value': def_val_list[i], - }); - } - } else { - use_def_value = false; - if (def_val_list[i] != '') { - use_def_value = true; - } - my_obj.push({ - 'name': argname[i], - 'type': argtype[i], - 'use_default': use_def_value, - 'default_value': def_val_list[i], - }); - } - } - } - - // Need to update the func_obj variable from sqlite database if available - if (func_args_data.length != 0) { - for (i = 0; i < func_args_data.length; i++) { - index = func_args_data[i]['arg_id']; - if (debug_info['proargmodes'] != null && - (argmode && argmode[index] == 'o' && !is_edb_proc)) { - continue; - } - - values = []; - if (argtype[index].indexOf('[]') != -1) { - vals = func_args_data[i]['value'].split(','); - _.each(vals, function(val) { - values.push({ - 'value': val, - }); - }); - } else { - values = func_args_data[i]['value']; - } - - func_obj.push({ - 'name': argname[index], - 'type': argtype[index], - 'is_null': func_args_data[i]['is_null'] ? true : false, - 'expr': func_args_data[i]['is_expression'] ? true : false, - 'value': values, - 'use_default': func_args_data[i]['use_default'] ? true : false, - 'default_value': def_val_list[index], - }); - } - } - } else { - /* - Generate the name parameter if function do not have arguments name - like dbgparam1, dbgparam2 etc. - */ - var myargname = []; - - for (i = 0; i < argtype.length; i++) { - myargname[i] = 'dbgparam' + (i + 1); - } - - // If there is no default arguments - if (!debug_info['pronargdefaults']) { - for (i = 0; i < argtype.length; i++) { - my_obj.push({ - 'name': myargname[i], - 'type': argtype[i], - 'use_default': false, - 'default_value': '', - }); - def_val_list[i] = ''; - } - } else { - // If there is default arguments - //Below logic will assign default values to "Default value" column - for (j = (myargname.length - 1); j >= 0; j--) { - if (arg_cnt) { - arg_cnt = arg_cnt - 1; - def_val_list[j] = default_args[arg_cnt]; - } else { - def_val_list[j] = ''; - } - } - - for (i = 0; i < argtype.length; i++) { - if (debug_info['proargmodes'] == null) { - use_def_value = false; - if (def_val_list[i] != '') { - use_def_value = true; - } - my_obj.push({ - 'name': myargname[i], - 'type': argtype[i], - 'use_default': use_def_value, - 'default_value': def_val_list[i], - }); - } else { - if (argmode && (argmode[i] == 'i' || argmode[i] == 'b' || - (is_edb_proc && argmode[i] == 'o'))) { - use_def_value = false; - if (def_val_list[i] != '') { - use_def_value = true; - } - my_obj.push({ - 'name': myargname[i], - 'type': argtype[i], - 'use_default': use_def_value, - 'default_value': def_val_list[i], - }); - } - } - } - } - - // Need to update the func_obj variable from sqlite database if available - if (func_args_data.length != 0) { - for (i = 0; i < func_args_data.length; i++) { - index = func_args_data[i]['arg_id']; - values = []; - if (argtype[index].indexOf('[]') != -1) { - vals = func_args_data[i]['value'].split(','); - _.each(vals, function(val) { - values.push({ - 'value': val, - }); - }); - } else { - values = func_args_data[i]['value']; - } - func_obj.push({ - 'name': myargname[index], - 'type': argtype[index], - 'is_null': func_args_data[i]['is_null'] ? true : false, - 'expr': func_args_data[i]['is_expression'] ? true : false, - 'value': values, - 'use_default': func_args_data[i]['use_default'] ? true : false, - 'default_value': def_val_list[index], - }); - } - } - } - - // Check if the arguments already available in the sqlite database - // then we should use the existing arguments - if (func_args_data.length == 0) { - this.debuggerInputArgsColl = - new DebuggerInputArgCollections(my_obj); - } else { - this.debuggerInputArgsColl = - new DebuggerInputArgCollections(func_obj); - } - - // Initialize a new Grid instance - if (this.grid) { - this.grid.remove(); - this.grid = null; - } - var grid = this.grid = new Backgrid.Grid({ - columns: gridCols, - collection: this.debuggerInputArgsColl, - className: 'backgrid table table-bordered table-noouter-border table-bottom-border', - }); - - grid.render(); - let wrap_div = document.createElement('div'); - wrap_div.classList.add('debugger-args'); - wrap_div.appendChild(grid.el); - $(this.elements.content).html(wrap_div); - - // For keyboard navigation in the grid - // we'll set focus on checkbox from the first row if any - var grid_checkbox = $(grid.el).find('input:checkbox').first(); - if (grid_checkbox.length) { - setTimeout(function() { - grid_checkbox.trigger('focus'); - }, 250); - } - - }, - settings: { - debug_info: undefined, - restart_debug: undefined, - trans_id: undefined, - }, - setup: function() { - return { - buttons: [{ - text: gettext('Clear All'), - className: 'btn btn-secondary pull-left fa fa-eraser pg-alertify-button', - },{ - text: gettext('Cancel'), - key: 27, - className: 'btn btn-secondary fa fa-times pg-alertify-button', - },{ - text: gettext('Debug'), - key: 13, - className: 'btn btn-primary fa fa-bug pg-alertify-button', - }], - // Set options for dialog - options: { - //disable both padding and overflow control. - padding: !1, - overflow: !1, - model: 0, - resizable: true, - maximizable: true, - pinnable: false, - closableByDimmer: false, - modal: false, - }, - }; - }, - // Callback functions when click on the buttons of the Alertify dialogs - callback: function(e) { - if (e.button.text === gettext('Debug')) { - - // Initialize the target once the debug button is clicked and - // create asynchronous connection and unique transaction ID - var self = this; - - // If the debugging is started again then treeInfo is already - // stored in this.data so we can use the same. - if (self.setting('restart_debug') == 0) { - var t = pgBrowser.tree, - i = t.selected(), - d = i ? t.itemData(i) : undefined; - - if (!d) - return; - - var treeInfo = t.getTreeNodeHierarchy(i); - } - - var args_value_list = []; - var sqlite_func_args_list = this.sqlite_func_args_list = []; - var int_count = 0; - - this.grid.collection.each(function(m) { - - // Check if value is set to NULL then we should ignore the value field - if (m.get('is_null')) { - args_value_list.push({ - 'name': m.get('name'), - 'type': m.get('type'), - 'value': 'NULL', - }); - } else { - // Check if default value to be used or not - if (m.get('use_default')) { - args_value_list.push({ - 'name': m.get('name'), - 'type': m.get('type'), - 'value': m.get('default_value'), - }); - } else { - args_value_list.push({ - 'name': m.get('name'), - 'type': m.get('type'), - 'value': m.get('value'), - }); - } - } - - if (self.setting('restart_debug') == 0) { - var f_id; - if (d._type == 'function') { - f_id = treeInfo.function._id; - } else if (d._type == 'procedure') { - f_id = treeInfo.procedure._id; - } else if (d._type == 'edbfunc') { - f_id = treeInfo.edbfunc._id; - } else if (d._type == 'edbproc') { - f_id = treeInfo.edbproc._id; - } - - // Below will format the data to be stored in sqlite database - sqlite_func_args_list.push({ - 'server_id': treeInfo.server._id, - 'database_id': treeInfo.database._id, - 'schema_id': treeInfo.schema._id, - 'function_id': f_id, - 'arg_id': self.input_arg_id[int_count], - 'is_null': m.get('is_null') ? 1 : 0, - 'is_expression': m.get('expr') ? 1 : 0, - 'use_default': m.get('use_default') ? 1 : 0, - 'value': m.get('value'), - }); - } else { - // Below will format the data to be stored in sqlite database - sqlite_func_args_list.push({ - 'server_id': self.setting('debug_info').server_id, - 'database_id': self.setting('debug_info').database_id, - 'schema_id': self.setting('debug_info').schema_id, - 'function_id': self.setting('debug_info').function_id, - 'arg_id': self.input_arg_id[int_count], - 'is_null': m.get('is_null') ? 1 : 0, - 'is_expression': m.get('expr') ? 1 : 0, - 'use_default': m.get('use_default') ? 1 : 0, - 'value': m.get('value'), - }); - } - - int_count = int_count + 1; - }); - - var baseUrl; - - // If debugging is not started again then we should initialize the target otherwise not - if (self.setting('restart_debug') == 0) { - if (d && d._type == 'function') { - baseUrl = url_for('debugger.initialize_target_for_function', { - 'debug_type': 'direct', - 'trans_id': self.setting('trans_id'), - 'sid': treeInfo.server._id, - 'did': treeInfo.database._id, - 'scid': treeInfo.schema._id, - 'func_id': treeInfo.function._id, - }); - } else if (d && d._type == 'procedure') { - baseUrl = url_for('debugger.initialize_target_for_function', { - 'debug_type': 'direct', - 'trans_id': self.setting('trans_id'), - 'sid': treeInfo.server._id, - 'did': treeInfo.database._id, - 'scid': treeInfo.schema._id, - 'func_id': treeInfo.procedure._id, - }); - } else if (d && d._type == 'edbfunc') { - baseUrl = url_for('debugger.initialize_target_for_function', { - 'debug_type': 'direct', - 'trans_id': self.setting('trans_id'), - 'sid': treeInfo.server._id, - 'did': treeInfo.database._id, - 'scid': treeInfo.schema._id, - 'func_id': treeInfo.edbfunc._id, - }); - } else if (d && d._type == 'edbproc') { - baseUrl = url_for('debugger.initialize_target_for_function', { - 'debug_type': 'direct', - 'trans_id': self.setting('trans_id'), - 'sid': treeInfo.server._id, - 'did': treeInfo.database._id, - 'scid': treeInfo.schema._id, - 'func_id': treeInfo.edbproc._id, - }); - } - - $.ajax({ - url: baseUrl, - method: 'POST', - data: { - 'data': JSON.stringify(args_value_list), - }, - }) - .done(function(res_post) { - - var url = url_for( - 'debugger.direct', { - 'trans_id': res_post.data.debuggerTransId, - } - ); - - var browserPreferences = pgWindow.default.pgAdmin.Browser.get_preferences_for_module('browser'); - var open_new_tab = browserPreferences.new_browser_tab_open; - if (open_new_tab && open_new_tab.includes('debugger')) { - window.open(url, '_blank'); - // Send the signal to runtime, so that proper zoom level will be set. - setTimeout(function() { - pgBrowser.send_signal_to_runtime('Runtime new window opened'); - }, 500); - } else { - pgBrowser.Events.once( - 'pgadmin-browser:frame:urlloaded:frm_debugger', - function(frame) { - frame.openURL(url); - }); - - // Create the debugger panel as per the data received from user input dialog. - var dashboardPanel = pgBrowser.docker.findPanels('properties'), - panel = pgBrowser.docker.addPanel( - 'frm_debugger', wcDocker.DOCK.STACKED, dashboardPanel[0] - ); - var browser_pref = pgBrowser.get_preferences_for_module('browser'); - var label = debuggerUtils.getAppropriateLabel(treeInfo); - debuggerUtils.setDebuggerTitle(panel, browser_pref, label, treeInfo.schema.label, treeInfo.database.label, null, pgBrowser); - panel.focus(); - - // Panel Closed event - panel.on(wcDocker.EVENT.CLOSED, function() { - var closeUrl = url_for('debugger.close', { - 'trans_id': res_post.data.debuggerTransId, - }); - $.ajax({ - url: closeUrl, - method: 'DELETE', - }); - }); - commonUtils.registerDetachEvent(panel); - // Panel Rename event - panel.on(wcDocker.EVENT.RENAME, function(panel_data) { - Alertify.prompt('', panel_data.$titleText[0].textContent, - // We will execute this function when user clicks on the OK button - function(evt, value) { - if(value) { - // Remove the leading and trailing white spaces. - value = value.trim(); - var name = debuggerUtils.getAppropriateLabel(treeInfo); - debuggerUtils.setDebuggerTitle(panel, self.preferences, name, treeInfo.schema.label, treeInfo.database.label, value, pgBrowser); - } - }, - // We will execute this function when user clicks on the Cancel - // button. Do nothing just close it. - function(evt) { evt.cancel = false; } - ).set({'title': gettext('Rename Panel')}); - }); - } - var _url; - - if (d._type == 'function') { - _url = url_for('debugger.set_arguments', { - 'sid': treeInfo.server._id, - 'did': treeInfo.database._id, - 'scid': treeInfo.schema._id, - 'func_id': treeInfo.function._id, - }); - } else if (d._type == 'procedure') { - _url = url_for('debugger.set_arguments', { - 'sid': treeInfo.server._id, - 'did': treeInfo.database._id, - 'scid': treeInfo.schema._id, - 'func_id': treeInfo.procedure._id, - }); - } else if (d._type == 'edbfunc') { - // Get the existing function parameters available from sqlite database - _url = url_for('debugger.set_arguments', { - 'sid': treeInfo.server._id, - 'did': treeInfo.database._id, - 'scid': treeInfo.schema._id, - 'func_id': treeInfo.edbfunc._id, - }); - } else if (d._type == 'edbproc') { - // Get the existing function parameters available from sqlite database - _url = url_for('debugger.set_arguments', { - 'sid': treeInfo.server._id, - 'did': treeInfo.database._id, - 'scid': treeInfo.schema._id, - 'func_id': treeInfo.edbproc._id, - }); - } - - $.ajax({ - url: _url, - method: 'POST', - data: { - 'data': JSON.stringify(sqlite_func_args_list), - }, - }) - .done(function() {/*This is intentional (SonarQube)*/}) - .fail(raiseError); - }) - .fail(function(er) { - Notify.alert( - gettext('Debugger Target Initialization Error'), - er.responseJSON.errormsg - ); - }); - } else { - // If the debugging is started again then we should only set the - // arguments and start the listener again - baseUrl = url_for('debugger.start_listener', { - 'trans_id': self.setting('debug_info').trans_id, - }); - - $.ajax({ - url: baseUrl, - method: 'POST', - data: { - 'data': JSON.stringify(args_value_list), - }, - }) - .done(function() {/*This is intentional (SonarQube)*/}) - .fail(function(er) { - Notify.alert( - gettext('Debugger Listener Startup Error'), - er.responseJSON.errormsg - ); - }); - - // Set the new input arguments given by the user during debugging - var _Url = url_for('debugger.set_arguments', { - 'sid': self.setting('debug_info').server_id, - 'did': self.setting('debug_info').database_id, - 'scid': self.setting('debug_info').schema_id, - 'func_id': self.setting('debug_info').function_id, - }); - $.ajax({ - url: _Url, - method: 'POST', - data: { - 'data': JSON.stringify(sqlite_func_args_list), - }, - }) - .done(function() {/*This is intentional (SonarQube)*/}) - .fail(raiseError); - } - - return true; - } - - if (e.button.text === gettext('Cancel')) { - /* Clear the trans id */ - $.ajax({ - method: 'DELETE', - url: url_for('debugger.close', {'trans_id': this.setting('trans_id')}), - }); - - return false; - } - - if (e.button.text === gettext('Clear All')) { - let _self = this; - let base_url = null; - - if (_self.setting('restart_debug') == 0) { - let selected_item = pgBrowser.tree.selected(); - let item_data = pgBrowser.tree.itemData(selected_item); - if (!item_data) - return; - - let tree_info = pgBrowser.tree.getTreeNodeHierarchy(selected_item); - - base_url = url_for('debugger.clear_arguments', { - 'sid': tree_info.server._id, - 'did': tree_info.database._id, - 'scid': tree_info.schema._id, - 'func_id': item_data._id, - }); - } else { - base_url = url_for('debugger.clear_arguments', { - 'sid': _self.setting('debug_info').server_id, - 'did': _self.setting('debug_info').database_id, - 'scid': _self.setting('debug_info').schema_id, - 'func_id': _self.setting('debug_info').function_id, - }); - } - $.ajax({ - url: base_url, - method: 'POST', - data: { - 'data': JSON.stringify(args_value_list), - }, - }).done(function() { - /* Disable debug button */ - _self.__internal.buttons[2].element.disabled = true; - _self.main(_self.setting('title'), _self.setting('debug_info'), - _self.setting('restart_debug'), _self.setting('is_edb_proc'), - _self.setting('trans_id') - ); - _self.prepare(); - }).fail(function(er) { - Notify.alert( - gettext('Clear failed'), - er.responseJSON.errormsg - ); - }); - - e.cancel = true; - return true; - } - }, - build: function() { - Alertify.pgDialogBuild.apply(this); - }, - prepare: function() { - // Add our class to alertify - $(this.elements.body.childNodes[0]).addClass( - 'alertify_tools_dialog_properties obj_properties' - ); - - /* - If we already have data available in sqlite database then we should - enable the debug button otherwise disable the debug button. - */ - if (this.func_args_data.length == 0) { - this.__internal.buttons[2].element.disabled = true; - } else { - this.__internal.buttons[2].element.disabled = false; - } - - /* - Listen to the grid change event so that if any value changed by user then we can enable/disable the - debug button. - */ - this.grid.listenTo(this.debuggerInputArgsColl, 'backgrid:edited', - (function(obj) { - - return function() { - - var enable_btn = false; - - for (var i = 0; i < this.collection.length; i++) { - - if (this.collection.models[i].get('is_null')) { - obj.__internal.buttons[2].element.disabled = false; - enable_btn = true; - continue; - } - // TODO: Need to check the "Expression" column value to - // enable/disable the "Debug" button - if (this.collection.models[i].get('value') == null || - this.collection.models[i].get('value') == undefined) { - enable_btn = true; - - if (this.collection.models[i].get('use_default')) { - obj.__internal.buttons[2].element.disabled = false; - } else { - obj.__internal.buttons[2].element.disabled = true; - break; - } - } - } - if (!enable_btn) - obj.__internal.buttons[2].element.disabled = false; - }; - })(this) - ); - - this.grid.listenTo(this.debuggerInputArgsColl, 'backgrid:error', - (function(obj) { - return function() { - obj.__internal.buttons[2].element.disabled = true; - }; - })(this) - ); - }, - }; - }); - } - - Alertify.debuggerInputArgsDialog( - gettext('Debugger'), debugInfo, restartDebug, isEdbProc, transId - ).resizeTo(pgBrowser.stdW.md,pgBrowser.stdH.md); - - }; -}); + show(debugInfo, restartDebug, isEdbProc, transId) { + // Render Debugger argument component + Notify.showModal(gettext('Debugger'), (closeModal) => { + return ; + }, { isFullScreen: false, isResizeable: true, showFullScreen: true, isFullWidth: true, dialogWidth: pgAdmin.Browser.stdW.md, dialogHeight: pgAdmin.Browser.stdH.md }); + } +} diff --git a/web/pgadmin/tools/debugger/static/js/debugger_utils.js b/web/pgadmin/tools/debugger/static/js/debugger_utils.js index fbc2f4b7c..dc10091dc 100644 --- a/web/pgadmin/tools/debugger/static/js/debugger_utils.js +++ b/web/pgadmin/tools/debugger/static/js/debugger_utils.js @@ -6,20 +6,10 @@ // This software is released under the PostgreSQL Licence // ////////////////////////////////////////////////////////////////////////// + import {generateTitle} from '../../../sqleditor/static/js/sqleditor_title'; import {_set_dynamic_tab} from '../../../sqleditor/static/js/show_query_tool'; -function setFocusToDebuggerEditor(editor, command) { - const TAB = 9; - if (!command) - return; - let key = command.which || command.keyCode; - // Keys other than Tab key - if (key !== TAB) { - editor.focus(); - } -} - function getFunctionId(treeInfoObject) { let objectId; if(treeInfoObject) { @@ -99,7 +89,6 @@ function getAppropriateLabel(treeInfo) { } module.exports = { - setFocusToDebuggerEditor: setFocusToDebuggerEditor, getFunctionId: getFunctionId, getProcedureId: getProcedureId, setDebuggerTitle: setDebuggerTitle, diff --git a/web/pgadmin/tools/debugger/static/js/direct.js b/web/pgadmin/tools/debugger/static/js/direct.js deleted file mode 100644 index 00ad8dc6b..000000000 --- a/web/pgadmin/tools/debugger/static/js/direct.js +++ /dev/null @@ -1,1956 +0,0 @@ -///////////////////////////////////////////////////////////// -// -// pgAdmin 4 - PostgreSQL Tools -// -// Copyright (C) 2013 - 2022, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -////////////////////////////////////////////////////////////// - -import Notify from '../../../../static/js/helpers/Notifier'; - -define([ - 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', - 'pgadmin.alertifyjs', 'sources/pgadmin', 'pgadmin.browser', 'backbone', - 'pgadmin.backgrid', 'pgadmin.backform', 'sources/../bundle/codemirror', - 'pgadmin.tools.debugger.ui', 'sources/keyboard_shortcuts', - 'pgadmin.tools.debugger.utils', 'sources/window', 'wcdocker', -], function( - gettext, url_for, $, _, Alertify, pgAdmin, pgBrowser, Backbone, Backgrid, - Backform, codemirror, debug_function_again, keyboardShortcuts, debuggerUtils, - pgWindow -) { - - var CodeMirror = codemirror.default, - wcDocker = window.wcDocker; - - if (pgAdmin.Browser.tree != null) { - pgAdmin = pgAdmin || window.pgAdmin || {}; - } - - var pgTools = pgAdmin.Tools = pgAdmin.Tools || {}; - - if (pgTools.DirectDebug) - return pgTools.DirectDebug; - - var controller = new(function() {/*This is intentional (SonarQube)*/}); - - _.extend( - controller, Backbone.Events, { - enable: function(btn, enable) { - // trigger the event and change the button view to enable/disable the buttons for debugging - this.trigger('pgDebugger:button:state:' + btn, enable); - }, - - enable_toolbar_buttons: function() { - var self = this; - self.enable('stop', true); - self.enable('step_over', true); - self.enable('step_into', true); - self.enable('toggle_breakpoint', true); - self.enable('clear_all_breakpoints', true); - self.enable('continue', true); - }, - - disable_toolbar_buttons: function() { - var self = this; - self.enable('stop', false); - self.enable('step_over', false); - self.enable('step_into', false); - self.enable('toggle_breakpoint', false); - self.enable('clear_all_breakpoints', false); - self.enable('continue', false); - }, - - /* - Function to set the breakpoint and send the line no. which is set to server - trans_id :- Unique Transaction ID, line_no - line no. to set the breakpoint, - set_type = 0 - clear , 1 - set - */ - set_breakpoint: function(trans_id, line_no, set_type) { - // Make ajax call to set/clear the break point by user - var baseUrl = url_for('debugger.set_breakpoint', { - 'trans_id': trans_id, - 'line_no': line_no, - 'set_type': set_type, - }); - $.ajax({ - url: baseUrl, - method: 'GET', - }) - .done(function(res) { - if (res.data.status) { - // Breakpoint has been set by the user - } - }) - .fail(function() { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while setting debugging breakpoint.') - ); - }); - }, - - // Function to get the latest breakpoint information and update the - // gutters of codemirror - UpdateBreakpoint: function(trans_id) { - var self = this; - - var br_list = self.GetBreakpointInformation(trans_id); - - // If there is no break point to clear then we should return from here. - if ((br_list.length == 1) && (br_list[0].linenumber == -1)) - return; - - var breakpoint_list = new Array(); - - for (let val of br_list) { - if (val.linenumber != -1) { - breakpoint_list.push(val.linenumber); - } - } - - for (let brk_val of breakpoint_list) { - var info = pgTools.DirectDebug.editor.lineInfo((brk_val - 1)); - - if (info.gutterMarkers != undefined) { - pgTools.DirectDebug.editor.setGutterMarker((brk_val - 1), 'breakpoints', null); - } else { - pgTools.DirectDebug.editor.setGutterMarker((brk_val - 1), 'breakpoints', function() { - var marker = document.createElement('div'); - marker.style.color = '#822'; - marker.innerHTML = '●'; - return marker; - }()); - } - } - }, - - // Function to get the breakpoint information from the server - GetBreakpointInformation: function(trans_id) { - var result = ''; - - // Make ajax call to listen the database message - var baseUrl = url_for('debugger.execute_query', { - 'trans_id': trans_id, - 'query_type': 'get_breakpoints', - }); - $.ajax({ - url: baseUrl, - method: 'GET', - async: false, - }) - .done(function(res) { - if (res.data.status === 'Success') { - result = res.data.result; - } else if (res.data.status === 'NotConnected') { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while fetching breakpoint information.') - ); - } - }) - .fail(function() { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while fetching breakpoint information.') - ); - }); - - return result; - }, - - setActiveLine: function(lineNo) { - var self = this; - let editor = pgTools.DirectDebug.editor; - - /* If lineNo sent, remove active line */ - if(lineNo && self.active_line_no) { - editor.removeLineClass( - self.active_line_no, 'wrap', 'CodeMirror-activeline-background' - ); - } - - /* If lineNo not sent, set it to active line */ - if(!lineNo && self.active_line_no) { - lineNo = self.active_line_no; - } - - /* Set new active line only if positive */ - if(lineNo > 0) { - self.active_line_no = lineNo; - editor.addLineClass( - self.active_line_no, 'wrap', 'CodeMirror-activeline-background' - ); - - /* centerOnLine is codemirror extension in bundle/codemirror.js */ - editor.centerOnLine(self.active_line_no); - } - }, - - // Function to start the executer and execute the user requested option for debugging - start_execution: function(trans_id, port_num) { - var self = this; - // Make ajax call to listen the database message - var baseUrl = url_for( - 'debugger.start_execution', { - 'trans_id': trans_id, - 'port_num': port_num, - }); - $.ajax({ - url: baseUrl, - method: 'GET', - }) - .done(function(res) { - if (res.data.status === 'Success') { - // If status is Success then find the port number to attach the executer. - self.execute_query(trans_id); - } else if (res.data.status === 'NotConnected') { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while starting debugging session.') - ); - } - }) - .fail(function() { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while starting debugging session.') - ); - }); - }, - - // Execute the query and get the first functions debug information from the server - execute_query: function(trans_id) { - var self = this; - // Make ajax call to listen the database message - var baseUrl = url_for( - 'debugger.execute_query', { - 'trans_id': trans_id, - 'query_type': 'wait_for_breakpoint', - }); - $.ajax({ - url: baseUrl, - method: 'GET', - }) - .done(function(res) { - if (res.data.status === 'Success') { - // set the return code to the code editor text area - if ( - res.data.result[0].src != null && - res.data.result[0].linenumber != null - ) { - pgTools.DirectDebug.editor.setValue(res.data.result[0].src); - - self.setActiveLine(res.data.result[0].linenumber - 2); - } - // Call function to create and update local variables .... - self.GetStackInformation(trans_id); - if (pgTools.DirectDebug.debug_type) { - self.poll_end_execution_result(trans_id); - } - } else if (res.data.status === 'NotConnected') { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while executing requested debugging information.') - ); - } - }) - .fail(function() { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while executing requested debugging information.') - ); - }); - }, - - // Get the local variable information of the functions and update the grid - GetLocalVariables: function(trans_id) { - var self = this; - - // Make ajax call to listen the database message - var baseUrl = url_for( - 'debugger.execute_query', { - 'trans_id': trans_id, - 'query_type': 'get_variables', - }); - $.ajax({ - url: baseUrl, - method: 'GET', - }) - .done(function(res) { - if (res.data.status === 'Success') { - // Call function to create and update local variables - self.AddLocalVariables(res.data.result); - self.AddParameters(res.data.result); - // If debug function is restarted then again start listener to - // read the updated messages. - if (pgTools.DirectDebug.debug_restarted) { - if (pgTools.DirectDebug.debug_type) { - self.poll_end_execution_result(trans_id); - } - pgTools.DirectDebug.debug_restarted = false; - } - } else if (res.data.status === 'NotConnected') { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while fetching variable information.') - ); - } - }) - .fail(function() { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while fetching variable information.') - ); - }); - }, - - raisePollingError: function() { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while polling result.') - ); - }, - - // Get the stack information of the functions and update the grid - GetStackInformation: function(trans_id) { - var self = this; - - // Make ajax call to listen the database message - var baseUrl = url_for( - 'debugger.execute_query', { - 'trans_id': trans_id, - 'query_type': 'get_stack_info', - }); - $.ajax({ - url: baseUrl, - method: 'GET', - }) - .done(function(res) { - if (res.data.status === 'Success') { - // Call function to create and update stack information - self.AddStackInformation(res.data.result); - self.GetLocalVariables(pgTools.DirectDebug.trans_id); - } else if (res.data.status === 'NotConnected') { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while fetching stack information.') - ); - } - }) - .fail(function() { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while fetching stack information.') - ); - }); - }, - - /* - poll the actual result after user has executed the "continue", "step-into", - "step-over" actions and get the other updated information from the server. - */ - poll_result: function(trans_id) { - var self = this; - - // Do we need to poll? - if (!pgTools.DirectDebug.is_polling_required) { - return; - } - - // Make ajax call to listen the database message - var baseUrl = url_for('debugger.poll_result', { - 'trans_id': trans_id, - }), - poll_timeout; - - /* - During the execution we should poll the result in minimum seconds but - once the execution is completed and wait for the another debugging - session then we should decrease the polling frequency. - */ - if (pgTools.DirectDebug.polling_timeout_idle) { - // Poll the result after 1 second - poll_timeout = 1000; - } else { - // Poll the result after 200 ms - poll_timeout = 200; - } - - setTimeout( - function() { - $.ajax({ - url: baseUrl, - method: 'GET', - beforeSend: function(xhr) { - xhr.setRequestHeader( - pgAdmin.csrf_token_header, pgAdmin.csrf_token - ); - // set cursor to progress before every poll. - $('.debugger-container').addClass('show_progress'); - }, - }) - .done(function(res) { - // remove progress cursor - $('.debugger-container').removeClass('show_progress'); - - if (res.data.status === 'Success') { - // If no result then poll again to wait for results. - if (res.data.result == null || res.data.result.length == 0) { - self.poll_result(trans_id); - } else { - if (!pgTools.DirectDebug.debug_type && !pgTools.DirectDebug.first_time_indirect_debug) { - pgTools.DirectDebug.docker.finishLoading(50); - self.setActiveLine(-1); - self.clear_all_breakpoint(trans_id); - self.execute_query(trans_id); - pgTools.DirectDebug.first_time_indirect_debug = true; - pgTools.DirectDebug.polling_timeout_idle = false; - } else { - pgTools.DirectDebug.polling_timeout_idle = false; - pgTools.DirectDebug.docker.finishLoading(50); - // If the source is really changed then only update the breakpoint information - if (res.data.result[0].src != pgTools.DirectDebug.editor.getValue()) { - pgTools.DirectDebug.editor.setValue(res.data.result[0].src); - self.UpdateBreakpoint(trans_id); - } - - self.setActiveLine(res.data.result[0].linenumber - 2); - // Update the stack, local variables and parameters information - self.GetStackInformation(trans_id); - } - - // Enable all the buttons as we got the results - // TODO: Fix this properly so a timeout isn't required. - setTimeout(function() { - self.enable_toolbar_buttons(); - }, 500); - } - } else if (res.data.status === 'Busy') { - pgTools.DirectDebug.polling_timeout_idle = true; - // If status is Busy then poll the result by recursive call to the poll function - if (!pgTools.DirectDebug.debug_type) { - pgTools.DirectDebug.docker.startLoading( - gettext('Waiting for another session to invoke the target...') - ); - - // As we are waiting for another session to invoke the target,disable all the buttons - self.disable_toolbar_buttons(); - pgTools.DirectDebug.first_time_indirect_debug = false; - self.poll_result(trans_id); - } else { - self.poll_result(trans_id); - } - } else if (res.data.status === 'NotConnected') { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while polling result.') - ); - } - }) - .fail(self.raisePollingError); - }, poll_timeout); - - }, - - // This function will update messages tab - update_messages: function(msg) { - // To prevent xss - msg = _.escape(msg); - - var old_msgs = '', - new_msgs = ''; - old_msgs = pgTools.DirectDebug.messages_panel.$container.find('.messages').html(); - if (old_msgs) { - new_msgs = (old_msgs + '\n' + msg) - .replace(/(?:\r\n|\r|\n)/g, '
') // Newlines with
- .replace(/()+/g, '
'); // multiple
with single
- } else { - new_msgs = msg; - } - pgTools.DirectDebug.messages_panel.$container.find('.messages').html(new_msgs); - }, - - /* - For the direct debugging, we need to check weather the functions execution - is completed or not. After completion of the debugging, we will stop polling - the result until new execution starts. - */ - poll_end_execution_result: function(trans_id) { - var self = this; - - // Do we need to poll? - if (!pgTools.DirectDebug.is_polling_required) { - return; - } - - // Make ajax call to listen the database message - var baseUrl = url_for('debugger.poll_end_execution_result', { - 'trans_id': trans_id, - }), - poll_end_timeout; - - /* - * During the execution we should poll the result in minimum seconds - * but once the execution is completed and wait for the another - * debugging session then we should decrease the polling frequency. - */ - if (pgTools.DirectDebug.polling_timeout_idle) { - // Poll the result to check that execution is completed or not - // after 1200 ms - poll_end_timeout = 1200; - } else { - // Poll the result to check that execution is completed or not - // after 350 ms - poll_end_timeout = 250; - } - - setTimeout( - function() { - $.ajax({ - url: baseUrl, - method: 'GET', - }) - .done(function(res) { - if (res.data.status === 'Success') { - if (res.data.result == undefined) { - /* - "result" is undefined only in case of EDB procedure. - As Once the EDB procedure execution is completed then we are - not getting any result so we need ignore the result. - */ - self.setActiveLine(-1); - pgTools.DirectDebug.direct_execution_completed = true; - pgTools.DirectDebug.polling_timeout_idle = true; - - //Set the alertify message to inform the user that execution is completed. - Notify.success(res.info, 3000); - - // Update the message tab of the debugger - if (res.data.status_message) { - self.update_messages(res.data.status_message); - } - - // remove progress cursor - $('.debugger-container').removeClass('show_progress'); - - // Execution completed so disable the buttons other than - // "Continue/Start" button because user can still - // start the same execution again. - setTimeout(self.disable_toolbar_buttons(), 500); - - // Stop further polling - pgTools.DirectDebug.is_polling_required = false; - } else { - // Call function to create and update local variables .... - if (res.data.result != null) { - self.setActiveLine(-1); - self.AddResults(res.data.col_info, res.data.result); - pgTools.DirectDebug.results_panel.focus(); - pgTools.DirectDebug.direct_execution_completed = true; - pgTools.DirectDebug.polling_timeout_idle = true; - - //Set the alertify message to inform the user that execution is completed. - Notify.success(res.info, 3000); - - // Update the message tab of the debugger - if (res.data.status_message) { - self.update_messages(res.data.status_message); - } - - // remove progress cursor - $('.debugger-container').removeClass('show_progress'); - - // Execution completed so disable the buttons other than - // "Continue/Start" button because user can still - // start the same execution again. - setTimeout(self.disable_toolbar_buttons(), 500); - - // Stop further pooling - pgTools.DirectDebug.is_polling_required = false; - } - } - } else if (res.data.status === 'Busy') { - // If status is Busy then poll the result by recursive call to - // the poll function - self.poll_end_execution_result(trans_id); - // Update the message tab of the debugger - if (res.data.status_message) { - self.update_messages(res.data.status_message); - } - } else if (res.data.status === 'NotConnected') { - Notify.alert( - gettext('Debugger poll end execution error'), - res.data.result - ); - } else if (res.data.status === 'ERROR') { - pgTools.DirectDebug.direct_execution_completed = true; - self.setActiveLine(-1); - - //Set the Alertify message to inform the user that execution is - // completed with error. - if (!pgTools.DirectDebug.is_user_aborted_debugging) { - Notify.error(res.info, 3000); - } - - // Update the message tab of the debugger - if (res.data.status_message) { - self.update_messages(res.data.status_message); - } - - pgTools.DirectDebug.messages_panel.focus(); - - // remove progress cursor - $('.debugger-container').removeClass('show_progress'); - - // Execution completed so disable the buttons other than - // "Continue/Start" button because user can still start the - // same execution again. - self.enable('stop', false); - self.enable('step_over', false); - self.enable('step_into', false); - self.enable('toggle_breakpoint', false); - self.enable('clear_all_breakpoints', false); - // If debugging is stopped by user then do not enable - // continue/restart button - if (!pgTools.DirectDebug.is_user_aborted_debugging) { - self.enable('continue', true); - pgTools.DirectDebug.is_user_aborted_debugging = false; - } - - // Stop further pooling - pgTools.DirectDebug.is_polling_required = false; - } - }) - .fail(self.raisePollingError); - }, poll_end_timeout); - - }, - - Restart: function(trans_id) { - - var self = this, - baseUrl = url_for('debugger.restart', {'trans_id': trans_id}); - - self.disable_toolbar_buttons(); - - // Clear msg tab - pgTools.DirectDebug - .messages_panel - .$container - .find('.messages') - .html(''); - - $.ajax({ - url: baseUrl, - }) - .done(function(res) { - // Restart the same function debugging with previous arguments - var restart_dbg = res.data.restart_debug ? 1 : 0; - - // Start pooling again - pgTools.DirectDebug.polling_timeout_idle = false; - pgTools.DirectDebug.is_polling_required = true; - self.poll_result(trans_id); - - if (restart_dbg) { - pgTools.DirectDebug.debug_restarted = true; - } - - /* - Need to check if restart debugging really require to open the input - dialog? If yes then we will get the previous arguments from database - and populate the input dialog, If no then we should directly start the - listener. - */ - if (res.data.result.require_input) { - debug_function_again(res.data.result, restart_dbg); - } else { - // Debugging of void function is started again so we need to start - // the listener again - var base_url = url_for('debugger.start_listener', { - 'trans_id': trans_id, - }); - - $.ajax({ - url: base_url, - method: 'GET', - }) - .done(function() { - if (pgTools.DirectDebug.debug_type) { - self.poll_end_execution_result(trans_id); - } - }) - .fail(self.raisePollingError); - } - }) - .fail(function(xhr) { - try { - var err = JSON.parse(xhr.responseText); - if (err.success == 0) { - Notify.alert(gettext('Debugger Error'), err.errormsg); - } - } catch (e) { - console.warn(e.stack || e); - } - }); - }, - - // Continue the execution until the next breakpoint - Continue: function(trans_id) { - var self = this; - self.disable_toolbar_buttons(); - - //Check first if previous execution was completed or not - if (pgTools.DirectDebug.direct_execution_completed && - pgTools.DirectDebug.direct_execution_completed == pgTools.DirectDebug.polling_timeout_idle) { - self.Restart(trans_id); - } else { - // Make ajax call to listen the database message - var baseUrl = url_for('debugger.execute_query', { - 'trans_id': trans_id, - 'query_type': 'continue', - }); - $.ajax({ - url: baseUrl, - method: 'GET', - }) - .done(function(res) { - if (res.data.status) { - self.poll_result(trans_id); - } else { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while executing continue in debugging session.') - ); - } - }) - .fail(function() { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while executing continue in debugging session.') - ); - }); - } - }, - - Step_over: function(trans_id) { - var self = this; - self.disable_toolbar_buttons(); - - // Make ajax call to listen the database message - var baseUrl = url_for('debugger.execute_query', { - 'trans_id': trans_id, - 'query_type': 'step_over', - }); - $.ajax({ - url: baseUrl, - method: 'GET', - }) - .done(function(res) { - if (res.data.status) { - self.poll_result(trans_id); - } else { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while executing step over in debugging session.') - ); - } - }) - .fail(function() { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while executing step over in debugging session.') - ); - }); - }, - - Step_into: function(trans_id) { - var self = this; - self.disable_toolbar_buttons(); - - // Make ajax call to listen the database message - var baseUrl = url_for('debugger.execute_query', { - 'trans_id': trans_id, - 'query_type': 'step_into', - }); - $.ajax({ - url: baseUrl, - method: 'GET', - }) - .done(function(res) { - if (res.data.status) { - self.poll_result(trans_id); - } else { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while executing step into in debugging session.') - ); - } - }) - .fail(function() { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while executing step into in debugging session.') - ); - }); - }, - - Stop: function(trans_id) { - var self = this; - self.disable_toolbar_buttons(); - - // Make ajax call to listen the database message - var baseUrl = url_for( - 'debugger.execute_query', { - 'trans_id': trans_id, - 'query_type': 'abort_target', - }); - $.ajax({ - url: baseUrl, - method: 'GET', - }) - .done(function(res) { - if (res.data.status) { - // Call function to create and update local variables .... - self.setActiveLine(-1); - pgTools.DirectDebug.direct_execution_completed = true; - pgTools.DirectDebug.is_user_aborted_debugging = true; - - // Stop further pooling - pgTools.DirectDebug.is_polling_required = false; - - // Restarting debugging in the same transaction do not work - // We will give same behaviour as pgAdmin3 and disable all buttons - self.enable('continue', false); - - // Set the Alertify message to inform the user that execution - // is completed. - Notify.success(res.info, 3000); - } else if (res.data.status === 'NotConnected') { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while executing stop in debugging session.') - ); - } - }) - .fail(function() { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while executing stop in debugging session.') - ); - }); - }, - - toggle_breakpoint: function(trans_id) { - var self = this; - self.disable_toolbar_buttons(); - - - var info = pgTools.DirectDebug.editor.lineInfo(self.active_line_no); - var baseUrl = ''; - - // If gutterMarker is undefined that means there is no marker defined previously - // So we need to set the breakpoint command here... - if (info.gutterMarkers == undefined) { - baseUrl = url_for('debugger.set_breakpoint', { - 'trans_id': trans_id, - 'line_no': self.active_line_no + 1, - 'set_type': '1', - }); - } else { - baseUrl = url_for('debugger.set_breakpoint', { - 'trans_id': trans_id, - 'line_no': self.active_line_no + 1, - 'set_type': '0', - }); - } - - $.ajax({ - url: baseUrl, - method: 'GET', - }) - .done(function(res) { - if (res.data.status) { - // Call function to create and update local variables .... - var info_local = pgTools.DirectDebug.editor.lineInfo(self.active_line_no); - - if (info_local.gutterMarkers != undefined) { - pgTools.DirectDebug.editor.setGutterMarker(self.active_line_no, 'breakpoints', null); - } else { - pgTools.DirectDebug.editor.setGutterMarker(self.active_line_no, 'breakpoints', function() { - var marker = document.createElement('div'); - marker.style.color = '#822'; - marker.innerHTML = '●'; - return marker; - }()); - } - - self.enable_toolbar_buttons(); - } else if (res.data.status === 'NotConnected') { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while toggling breakpoint.') - ); - } - }) - .fail(function() { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while toggling breakpoint.') - ); - }); - }, - - clear_all_breakpoint: function(trans_id) { - var self = this, - br_list = self.GetBreakpointInformation(trans_id); - - // If there is no break point to clear then we should return from here. - if ((br_list.length == 1) && (br_list[0].linenumber == -1)) - return; - - self.disable_toolbar_buttons(); - - var breakpoint_list = new Array(); - - for (let val of br_list) { - if (val.linenumber != -1) { - breakpoint_list.push(val.linenumber); - } - } - - // Make ajax call to listen the database message - var baseUrl = url_for('debugger.clear_all_breakpoint', { - 'trans_id': trans_id, - }); - - $.ajax({ - url: baseUrl, - method: 'POST', - data: { - 'breakpoint_list': breakpoint_list.join(), - }, - }) - .done(function(res) { - if (res.data.status) { - for (let brk_val of breakpoint_list) { - var info = pgTools.DirectDebug.editor.lineInfo((brk_val - 1)); - - if (info) { - if (info.gutterMarkers != undefined) { - pgTools.DirectDebug.editor.setGutterMarker((brk_val - 1), 'breakpoints', null); - } - } - } - } - self.enable_toolbar_buttons(); - }) - .fail(function() { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while clearing all breakpoint.') - ); - }); - }, - - AddStackInformation: function(result) { - var self = this; - - // Remove the existing created grid and update the stack values - if (self.stack_grid) { - self.stack_grid.remove(); - self.stack_grid = null; - } - - var DebuggerStackModel = Backbone.Model.extend({ - defaults: { - frame_id: 0, - name: undefined, - value: undefined, - line_no: undefined, - }, - }); - - // Collection which contains the model for function informations. - var StackCollection = Backbone.Collection.extend({ - model: DebuggerStackModel, - }); - - var stackGridCols = [{ - name: 'name', - label: gettext('Name'), - type: 'text', - editable: false, - cell: 'string', - }, - { - name: 'value', - label: gettext('Value'), - type: 'text', - editable: false, - cell: 'string', - }, - { - name: 'line_no', - label: gettext('Line No.'), - type: 'text', - editable: false, - cell: 'string', - }, - ]; - - var my_obj = []; - for (var i = 0; i < result.length; i++) { - my_obj.push({ - 'frame_id': i, - 'name': result[i].targetname, - 'value': result[i].args, - 'line_no': result[i].linenumber, - }); - } - - var stackColl = this.stackColl = new StackCollection(my_obj); - this.stackColl.on('backgrid:row:selected', self.select_frame, self); - - // Initialize a new Grid instance - var stack_grid = this.stack_grid = new Backgrid.Grid({ - emptyText: gettext('No data found'), - columns: stackGridCols, - row: Backgrid.Row.extend({ - events: { - click: 'rowClick', - }, - rowClick: function() { - //Find which row is selected and depending on that send the frame id - self.frame_id = this.model.get('frame_id'); - this.model.trigger('backgrid:row:selected', this); - self.stack_grid.$el.find('td').css( - 'background-color', this.disabledColor - ); - this.$el.find('td').css('background-color', this.highlightColor); - }, - }), - collection: stackColl, - className: 'backgrid table table-bordered table-noouter-border table-bottom-border', - }); - - stack_grid.render(); - - // Render the stack grid into stack panel - pgTools.DirectDebug.stack_pane_panel - .$container - .find('.stack_pane') - .append(stack_grid.el); - - }, - - AddResults: function(columns, result) { - var self = this; - - // Remove the existing created grid and update the result values - if (self.result_grid) { - self.result_grid.remove(); - self.result_grid = null; - } - - var DebuggerResultsModel = Backbone.Model.extend({ - defaults: { - name: undefined, - }, - }); - - // Collection which contains the model for function informations. - var ResultsCollection = Backbone.Collection.extend({ - model: DebuggerResultsModel, - }); - - var resultGridCols = []; - if (_.size(columns)) { - _.each(columns, function(c) { - var column = { - type: 'text', - editable: false, - cell: 'string', - }; - column['name'] = column['label'] = c.name; - resultGridCols.push(column); - }); - } - - // Initialize a new Grid instance - var result_grid = this.result_grid = new Backgrid.Grid({ - emptyText: gettext('No data found'), - columns: resultGridCols, - collection: new ResultsCollection(result), - className: 'backgrid table table-bordered table-noouter-border table-bottom-border', - }); - - result_grid.render(); - - // Render the result grid into result panel - pgTools.DirectDebug.results_panel - .$container - .find('.debug_results') - .append(result_grid.el); - }, - - AddLocalVariables: function(result) { - var self = this; - - // Remove the existing created grid and update the variables values - if (self.variable_grid) { - self.variable_grid.remove(); - self.variable_grid = null; - } - - var DebuggerVariablesModel = Backbone.Model.extend({ - defaults: { - name: undefined, - type: undefined, - value: undefined, - }, - }); - - // Collection which contains the model for function information. - var VariablesCollection = Backbone.Collection.extend({ - model: DebuggerVariablesModel, - }); - - VariablesCollection.prototype.on( - 'change', self.deposit_parameter_value, self - ); - - var gridCols = [{ - name: 'name', - label: gettext('Name'), - type: 'text', - editable: false, - cell: 'string', - }, - { - name: 'type', - label: gettext('Type'), - type: 'text', - editable: false, - cell: 'string', - }, - { - name: 'value', - label: gettext('Value'), - type: 'text', - cell: 'string', - }, - ]; - - var my_obj = []; - if (result.length != 0) { - for (let res_val of result) { - if (res_val.varclass == 'L') { - my_obj.push({ - 'name': res_val.name, - 'type': res_val.dtype, - 'value': res_val.value, - }); - } - } - } - - // Initialize a new Grid instance - var variable_grid = this.variable_grid = new Backgrid.Grid({ - emptyText: gettext('No data found'), - columns: gridCols, - collection: new VariablesCollection(my_obj), - className: 'backgrid table table-bordered table-noouter-border table-bottom-border', - }); - - variable_grid.collection.on( - 'backgrid:edited', (ch1, ch2, command) => { - debuggerUtils.setFocusToDebuggerEditor( - pgTools.DirectDebug.editor, command - ); - } - ); - - variable_grid.render(); - - // Render the variables grid into local variables panel - pgTools.DirectDebug.local_variables_panel - .$container - .find('.local_variables') - .append(variable_grid.el); - }, - - AddParameters: function(result) { - var self = this; - - // Remove the existing created grid and update the parameter values - if (self.param_grid) { - self.param_grid.remove(); - self.param_grid = null; - } - - var DebuggerParametersModel = Backbone.Model.extend({ - defaults: { - name: undefined, - type: undefined, - value: undefined, - }, - }); - - // Collection which contains the model for function informations. - var ParametersCollection = self.ParametersCollection = Backbone.Collection.extend({ - model: DebuggerParametersModel, - }); - - ParametersCollection.prototype.on( - 'change', self.deposit_parameter_value, self - ); - - var paramGridCols = [{ - name: 'name', - label: gettext('Name'), - type: 'text', - editable: false, - cell: 'string', - }, - { - name: 'type', - label: gettext('Type'), - type: 'text', - editable: false, - cell: 'string', - }, - { - name: 'value', - label: gettext('Value'), - type: 'text', - cell: 'string', - }, - ]; - - var param_obj = []; - if (result.length != 0) { - for (let res_val of result) { - if (res_val.varclass == 'A') { - param_obj.push({ - 'name': res_val.name, - 'type': res_val.dtype, - 'value': res_val.value, - }); - } - } - } - - // Initialize a new Grid instance - var param_grid = this.param_grid = new Backgrid.Grid({ - emptyText: gettext('No data found'), - columns: paramGridCols, - collection: new ParametersCollection(param_obj), - className: 'backgrid table table-bordered table-noouter-border table-bottom-border', - }); - - param_grid.collection.on( - 'backgrid:edited', (ch1, ch2, command) => { - debuggerUtils.setFocusToDebuggerEditor( - pgTools.DirectDebug.editor, command - ); - } - ); - - param_grid.render(); - - // Render the parameters grid into parameter panel - pgTools.DirectDebug.parameters_panel - .$container - .find('.parameters') - .append(param_grid.el); - }, - deposit_parameter_value: function(model) { - var self = this; - - // variable name and value list that is changed by user - var name_value_list = []; - - name_value_list.push({ - 'name': model.get('name'), - 'type': model.get('type'), - 'value': model.get('value'), - }); - - // Make ajax call to listen the database message - var baseUrl = url_for('debugger.deposit_value', { - 'trans_id': pgTools.DirectDebug.trans_id, - }); - $.ajax({ - url: baseUrl, - method: 'POST', - data: { - 'data': JSON.stringify(name_value_list), - }, - }) - .done(function(res) { - if (res.data.status) { - // Get the updated variables value - self.GetLocalVariables(pgTools.DirectDebug.trans_id); - // Show the message to the user that deposit value is success or failure - if (res.data.result) { - Notify.success(res.data.info, 3000); - } else { - Notify.error(res.data.info, 3000); - } - } - }) - .fail(function() { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while depositing variable value.') - ); - }); - }, - - select_frame: function() { - var self = this; - - // Make ajax call to listen the database message - var baseUrl = url_for('debugger.select_frame', { - 'trans_id': pgTools.DirectDebug.trans_id, - 'frame_id': self.frame_id, - }); - $.ajax({ - url: baseUrl, - method: 'GET', - }) - .done(function(res) { - if (res.data.status) { - pgTools.DirectDebug.editor.setValue(res.data.result[0].src); - self.UpdateBreakpoint(pgTools.DirectDebug.trans_id); - self.setActiveLine(res.data.result[0].linenumber - 2); - // Call function to create and update local variables .... - self.GetLocalVariables(pgTools.DirectDebug.trans_id); - } - }) - .fail(function() { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while selecting frame.') - ); - }); - }, - } - ); - - /* - Debugger tool var view to create the button toolbar and listen to the button click event and inform the - controller about the click and controller will take the action for the specified button click. - */ - var DebuggerToolbarView = Backbone.View.extend({ - el: '.debugger_main_container', - initialize: function() { - controller.on('pgDebugger:button:state:stop', this.enable_stop, this); - controller.on('pgDebugger:button:state:step_over', this.enable_step_over, this); - controller.on('pgDebugger:button:state:step_into', this.enable_step_into, this); - controller.on('pgDebugger:button:state:continue', this.enable_continue, this); - controller.on('pgDebugger:button:state:toggle_breakpoint', this.enable_toggle_breakpoint, this); - controller.on('pgDebugger:button:state:clear_all_breakpoints', this.enable_clear_all_breakpoints, this); - }, - events: { - 'click .btn-stop': 'on_stop', - 'click .btn-clear-breakpoint': 'on_clear_all_breakpoint', - 'click .btn-toggle-breakpoint': 'on_toggle_breakpoint', - 'click .btn-continue': 'on_continue', - 'click .btn-step-over': 'on_step_over', - 'click .btn-step-into': 'on_step_into', - 'keydown': 'keyAction', - }, - enable_stop: function(enable) { - var $btn = this.$el.find('.btn-stop'); - - if (enable) { - $btn.prop('disabled', false); - $btn.removeAttr('disabled'); - } else { - $btn.prop('disabled', true); - $btn.attr('disabled', 'disabled'); - } - }, - enable_step_over: function(enable) { - var $btn = this.$el.find('.btn-step-over'); - - if (enable) { - $btn.prop('disabled', false); - $btn.removeAttr('disabled'); - } else { - $btn.prop('disabled', true); - $btn.attr('disabled', 'disabled'); - } - }, - enable_step_into: function(enable) { - var $btn = this.$el.find('.btn-step-into'); - - if (enable) { - $btn.prop('disabled', false); - $btn.removeAttr('disabled'); - } else { - $btn.prop('disabled', true); - $btn.attr('disabled', 'disabled'); - } - }, - enable_continue: function(enable) { - var $btn = this.$el.find('.btn-continue'); - - if (enable) { - $btn.prop('disabled', false); - $btn.removeAttr('disabled'); - } else { - $btn.prop('disabled', true); - $btn.attr('disabled', 'disabled'); - } - }, - enable_toggle_breakpoint: function(enable) { - var $btn = this.$el.find('.btn-toggle-breakpoint'); - - if (enable) { - $btn.prop('disabled', false); - $btn.removeAttr('disabled'); - } else { - $btn.prop('disabled', true); - $btn.attr('disabled', 'disabled'); - } - }, - enable_clear_all_breakpoints: function(enable) { - var $btn = this.$el.find('.btn-clear-breakpoint'); - - if (enable) { - $btn.prop('disabled', false); - $btn.removeAttr('disabled'); - } else { - $btn.prop('disabled', true); - $btn.attr('disabled', 'disabled'); - } - }, - on_stop: function() { - controller.Stop(pgTools.DirectDebug.trans_id); - }, - on_clear_all_breakpoint: function() { - controller.clear_all_breakpoint(pgTools.DirectDebug.trans_id); - }, - on_toggle_breakpoint: function() { - controller.toggle_breakpoint(pgTools.DirectDebug.trans_id); - }, - on_continue: function() { - controller.Continue(pgTools.DirectDebug.trans_id); - }, - on_step_over: function() { - controller.Step_over(pgTools.DirectDebug.trans_id); - }, - on_step_into: function() { - controller.Step_into(pgTools.DirectDebug.trans_id); - }, - keyAction: function (event) { - let panel_type=''; - - panel_type = keyboardShortcuts.processEventDebugger( - this.$el, event, this.preferences, pgTools.DirectDebug.docker - ); - - - if(!_.isNull(panel_type) && !_.isUndefined(panel_type) && panel_type != '') { - setTimeout(function() { - pgBrowser.Events.trigger(`pgadmin:debugger:${panel_type}:focus`); - }, 100); - } - }, - }); - - - /* - Function is responsible to create the new wcDocker instance for debugger and - initialize the debugger panel inside the docker instance. - */ - var DirectDebug = function() {/*This is intentional (SonarQube)*/}; - - var raiseJSONError = function(xhr) { - try { - var err = JSON.parse(xhr.responseText); - if (err.success == 0) { - Notify.alert(gettext('Debugger Error'), err.errormsg); - } - } catch (e) { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while starting debugging listener.') - ); - } - }; - - _.extend(DirectDebug.prototype, { - /* We should get the transaction id from the server during initialization here */ - load: function(trans_id, debug_type, function_name_with_arguments, layout) { - // We do not want to initialize the module multiple times. - var self = this; - _.bindAll(pgTools.DirectDebug, 'messages'); - - if (this.initialized) - return; - - var baseUrl; - - this.initialized = true; - this.trans_id = trans_id; - this.debug_type = debug_type; - this.first_time_indirect_debug = false; - this.direct_execution_completed = false; - this.polling_timeout_idle = false; - this.debug_restarted = false; - this.is_user_aborted_debugging = false; - this.is_polling_required = true; // Flag to stop unwanted ajax calls - this.function_name_with_arguments = function_name_with_arguments; - this.layout = layout; - - let browser = pgWindow.default.pgAdmin.Browser; - this.preferences = browser.get_preferences_for_module('debugger'); - - this.docker = new wcDocker( - '#container', { - allowContextMenu: false, - allowCollapse: false, - loadingClass: 'pg-sp-icon', - themePath: url_for('static', { - 'filename': 'css', - }), - theme: 'webcabin.overrides.css', - }); - this.panels = []; - - // Below code will be executed for indirect debugging - // indirect debugging - 0 and for direct debugging - 1 - if (trans_id != undefined && !debug_type) { - // Make ajax call to execute the and start the target for execution - baseUrl = url_for('debugger.start_listener', { - 'trans_id': trans_id, - }); - - $.ajax({ - url: baseUrl, - method: 'GET', - }) - .done(function(res) { - if (res.data.status) { - self.intializePanels(); - controller.enable_toolbar_buttons(); - controller.poll_result(trans_id); - } - }) - .fail(raiseJSONError); - } else if (trans_id != undefined) { - // Make ajax call to execute the and start the target for execution - baseUrl = url_for('debugger.start_listener', { - 'trans_id': trans_id, - }); - - $.ajax({ - url: baseUrl, - method: 'GET', - }) - .done(function(res) { - if (res.data.status) { - self.messages(trans_id); - } - }) - .fail(raiseJSONError); - } else { - this.intializePanels(); - } - }, - - // Read the messages of the database server and get the port ID and attach - // the executer to that port. - messages: function(trans_id) { - var self = this; - // Make ajax call to listen the database message - var baseUrl = url_for('debugger.messages', { - 'trans_id': trans_id, - }); - - $.ajax({ - url: baseUrl, - method: 'GET', - }) - .done(function(res) { - if (res.data.status === 'Success') { - self.intializePanels(); - controller.enable_toolbar_buttons(); - // If status is Success then find the port number to attach the executer. - controller.start_execution(trans_id, res.data.result); - } else if (res.data.status === 'Busy') { - // If status is Busy then poll the result by recursive call to the poll function - self.messages(trans_id); - } else if (res.data.status === 'NotConnected') { - Notify.alert( - gettext('Not connected to server or connection with the server has been closed.'), - res.data.result - ); - } - }) - .fail(function() { - Notify.alert( - gettext('Debugger Error'), - gettext('Error while fetching messages information.') - ); - }); - - }, - - // Callback function when user click on gutters of codemirror to set/clear the breakpoint - onBreakPoint: function(cm, m, gutter) { - var self = this; - - // If breakpoint gutter is clicked and execution is not completed then only set the breakpoint - if (gutter == 'breakpoints' && !pgTools.DirectDebug.polling_timeout_idle) { - // We may want to check, if break-point is allowed at this moment or not - var info = cm.lineInfo(m); - - // If gutterMarker is undefined that means there is no marker defined previously - // So we need to set the breakpoint command here... - if (info.gutterMarkers == undefined) { - controller.set_breakpoint(self.trans_id, m + 1, 1); //set the breakpoint - } else { - if (info.gutterMarkers.breakpoints == undefined) { - controller.set_breakpoint(self.trans_id, m + 1, 1); //set the breakpoint - } else { - controller.set_breakpoint(self.trans_id, m + 1, 0); //clear the breakpoint - } - } - - // If line folding is defined then gutterMarker will be defined so - // we need to find out 'breakpoints' information - var markers = info.gutterMarkers; - if (markers != undefined && info.gutterMarkers.breakpoints == undefined) - markers = info.gutterMarkers.breakpoints; - - cm.setGutterMarker( - m, 'breakpoints', markers ? null : function() { - var marker = document.createElement('div'); - - marker.style.color = '#822'; - marker.innerHTML = '●'; - - return marker; - }()); - } - }, - - buildDefaultLayout: function(docker) { - let code_editor_panel = docker.addPanel('code', wcDocker.DOCK.TOP); - - let parameters_panel = docker.addPanel('parameters', wcDocker.DOCK.BOTTOM, code_editor_panel); - docker.addPanel('local_variables',wcDocker.DOCK.STACKED, parameters_panel, { - tabOrientation: wcDocker.TAB.TOP, - }); - docker.addPanel('messages', wcDocker.DOCK.STACKED, parameters_panel); - docker.addPanel('results', wcDocker.DOCK.STACKED, parameters_panel); - docker.addPanel('stack_pane', wcDocker.DOCK.STACKED, parameters_panel); - }, - - /** - * This function is responsible for adjusting the message height - * so that the message does not cut down. - * @function setMessagePanelHeight - */ - setMessagePanelHeight: function() { - var self = this, - dockerPane = self.docker.$container[0]; - if(dockerPane.children.length > 2) { - var bottomPane = dockerPane.children[2], - message_height = $(bottomPane).height() - 75; - $($(dockerPane).find('#messages')[0]).css({ - 'height': message_height + 'px', - 'padding-bottom': 5 + 'px' - }); - } - }, - - // Create the debugger layout with splitter and display the appropriate data received from server. - intializePanels: function() { - var self = this; - this.registerPanel( - 'code', self.function_name_with_arguments, '100%', '50%', - function() { - - // Create the parameters panel to display the arguments of the functions - var parameters = new pgAdmin.Browser.Panel({ - name: 'parameters', - title: gettext('Parameters'), - width: '100%', - height: '100%', - isCloseable: false, - isPrivate: true, - content: '
', - }); - - // Create the Local variables panel to display the local variables of the function. - var local_variables = new pgAdmin.Browser.Panel({ - name: 'local_variables', - title: gettext('Local variables'), - width: '100%', - height: '100%', - isCloseable: false, - isPrivate: true, - content: '
', - }); - - // Create the messages panel to display the message returned from the database server - var messages = new pgAdmin.Browser.Panel({ - name: 'messages', - title: - gettext('Messages'), - width: '100%', - height: '100%', - isCloseable: false, - isPrivate: true, - content: '
', - }); - - // Create the result panel to display the result after debugging the function - var results = new pgAdmin.Browser.Panel({ - name: 'results', - title: gettext('Results'), - width: '100%', - height: '100%', - isCloseable: false, - isPrivate: true, - content: '
', - }); - - // Create the stack pane panel to display the debugging stack information. - var stack_pane = new pgAdmin.Browser.Panel({ - name: 'stack_pane', - title: gettext('Stack'), - width: '100%', - height: '100%', - isCloseable: false, - isPrivate: true, - content: '
', - }); - - // Load all the created panels - parameters.load(self.docker); - local_variables.load(self.docker); - messages.load(self.docker); - results.load(self.docker); - stack_pane.load(self.docker); - }); - - // restore the layout if present else fallback to buildDefaultLayout - pgBrowser.restore_layout(self.docker, self.layout, this.buildDefaultLayout.bind(this)); - self.setMessagePanelHeight(); - - self.docker.on(wcDocker.EVENT.LAYOUT_CHANGED, function() { - self.setMessagePanelHeight(); - pgBrowser.save_current_layout('Debugger/Layout', self.docker); - }); - - self.code_editor_panel = self.docker.findPanels('code')[0]; - self.parameters_panel = self.docker.findPanels('parameters')[0]; - self.local_variables_panel = self.docker.findPanels('local_variables')[0]; - self.messages_panel = self.docker.findPanels('messages')[0]; - self.results_panel = self.docker.findPanels('results')[0]; - self.stack_pane_panel = self.docker.findPanels('stack_pane')[0]; - - var editor_pane = $('
'); - var code_editor_area = $('').appendTo(editor_pane); - self.code_editor_panel.layout().addItem(editor_pane); - - // To show the line-number and set breakpoint marker details by user. - self.editor = CodeMirror.fromTextArea( - code_editor_area.get(0), { - tabindex: -1, - lineNumbers: true, - foldOptions: { - widget: '\u2026', - }, - foldGutter: true, - gutters: [ - 'CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'breakpoints', - ], - mode: 'text/x-pgsql', - readOnly: true, - extraKeys: pgAdmin.Browser.editor_shortcut_keys, - indentWithTabs: pgAdmin.Browser.editor_options.indent_with_tabs, - indentUnit: pgAdmin.Browser.editor_options.tabSize, - tabSize: pgAdmin.Browser.editor_options.tabSize, - lineWrapping: pgAdmin.Browser.editor_options.wrapCode, - autoCloseBrackets: pgAdmin.Browser.editor_options.insert_pair_brackets, - matchBrackets: pgAdmin.Browser.editor_options.brace_matching, - screenReaderLabel: gettext('Debugger SQL editor'), - }); - - // Useful for keyboard navigation, when user presses escape key we will - // defocus from the codemirror editor allow user to navigate further - CodeMirror.on(self.editor, 'keydown', function(cm,event) { - if(event.keyCode==27){ - document.activeElement.blur(); - } - }); - - pgBrowser.Events.on('pgadmin:debugger:code:focus', ()=>{ - self.editor.focus(); - }); - - // On loading the docker, register the callbacks - var onLoad = function() { - self.docker.finishLoading(100); - self.docker.off(wcDocker.EVENT.LOADED); - // Register the callback when user set/clear the breakpoint on gutter area. - self.editor.on('gutterClick', self.onBreakPoint.bind(self), self); - /* Set focus to the debugger container - * Focus does not work in firefox without tabindex attr - * so, setting focus to parent of $container which is #container - */ - if(self.docker.$container){ - self.docker.$container.parent().focus(); - } - - let cacheIntervalId = setInterval(function() { - try { - let browser = pgWindow.default.pgAdmin.Browser; - if(browser.preference_version() > 0) { - clearInterval(cacheIntervalId); - self.reflectPreferences(); - - /* If debugger is in a new tab, event fired is not available - * instead, a poller is set up who will check - */ - var browser_preferences = browser.get_preferences_for_module('browser'); - var open_new_tab = browser_preferences.new_browser_tab_open; - if (open_new_tab && open_new_tab.includes('debugger')) { - pgBrowser.bind_beforeunload(); - let pollIntervalId = setInterval(()=>{ - if(pgWindow.default.pgAdmin) { - self.reflectPreferences(); - } - else { - clearInterval(pollIntervalId); - } - }, 1000); - } - } - } - catch(err) { - clearInterval(cacheIntervalId); - throw err; - } - },0); - - }; - - self.docker.startLoading(gettext('Loading...')); - self.docker.on(wcDocker.EVENT.LOADED, onLoad); - - // Create the toolbar view for debugging the function - this.toolbarView = new DebuggerToolbarView(); - - /* wcDocker focuses on window always, and all our shortcuts are - * bind to editor-panel. So when we use wcDocker focus, editor-panel - * loses focus and events don't work. - */ - $(window).on('keydown', (e)=>{ - if(self.toolbarView.keyAction) { - self.toolbarView.keyAction(e); - } - }); - - /* Cache may take time to load for the first time - * Keep trying till available - */ - - - /* Register for preference changed event broadcasted in parent - * to reload the shorcuts. - */ - pgWindow.default.pgAdmin.Browser.onPreferencesChange('debugger', function() { - self.reflectPreferences(); - }); - - /* Register to log the activity */ - pgBrowser.register_to_activity_listener(document, ()=>{ - Notify.alert(gettext('Timeout'), gettext('Your session has timed out due to inactivity. Please close the window and login again.')); - }); - - controller.poll_result = pgBrowser.override_activity_event_decorator(controller.poll_result).bind(controller); - controller.poll_end_execution_result = pgBrowser.override_activity_event_decorator(controller.poll_end_execution_result).bind(controller); - }, - reflectPreferences: function() { - let self = this, - browser = pgWindow.default.pgAdmin.Browser; - self.preferences = browser.get_preferences_for_module('debugger'); - self.toolbarView.preferences = self.preferences; - - /* Update the shortcuts of the buttons */ - self.toolbarView.$el.find('#btn-step-into') - .attr('title', keyboardShortcuts.shortcut_accesskey_title(gettext('Step into'),self.preferences.btn_step_into)) - .attr('aria-label', keyboardShortcuts.shortcut_accesskey_title(gettext('Step into'),self.preferences.btn_step_into)) - .attr('accesskey', keyboardShortcuts.shortcut_key(self.preferences.btn_step_into)); - - self.toolbarView.$el.find('#btn-step-over') - .attr('title', keyboardShortcuts.shortcut_accesskey_title(gettext('Step over'),self.preferences.btn_step_over)) - .attr('aria-label', keyboardShortcuts.shortcut_accesskey_title(gettext('Step over'),self.preferences.btn_step_over)) - .attr('accesskey', keyboardShortcuts.shortcut_key(self.preferences.btn_step_over)); - - self.toolbarView.$el.find('#btn-continue') - .attr('title', keyboardShortcuts.shortcut_accesskey_title(gettext('Continue/Start'),self.preferences.btn_start)) - .attr('aria-label', keyboardShortcuts.shortcut_accesskey_title(gettext('Continue/Start'),self.preferences.btn_start)) - .attr('accesskey', keyboardShortcuts.shortcut_key(self.preferences.btn_start)); - - self.toolbarView.$el.find('#btn-toggle-breakpoint') - .attr('title', keyboardShortcuts.shortcut_accesskey_title(gettext('Toggle breakpoint'),self.preferences.btn_toggle_breakpoint)) - .attr('aria-label', keyboardShortcuts.shortcut_accesskey_title(gettext('Toggle breakpoint'),self.preferences.btn_toggle_breakpoint)) - .attr('accesskey', keyboardShortcuts.shortcut_key(self.preferences.btn_toggle_breakpoint)); - - self.toolbarView.$el.find('#btn-clear-breakpoint') - .attr('title', keyboardShortcuts.shortcut_accesskey_title(gettext('Clear all breakpoints'),self.preferences.btn_clear_breakpoints)) - .attr('aria-label', keyboardShortcuts.shortcut_accesskey_title(gettext('Clear all breakpoints'),self.preferences.btn_clear_breakpoints)) - .attr('accesskey', keyboardShortcuts.shortcut_key(self.preferences.btn_clear_breakpoints)); - - self.toolbarView.$el.find('#btn-stop') - .attr('title', keyboardShortcuts.shortcut_accesskey_title(gettext('Stop'),self.preferences.btn_stop)) - .attr('aria-label', keyboardShortcuts.shortcut_accesskey_title(gettext('Stop'),self.preferences.btn_stop)) - .attr('accesskey', keyboardShortcuts.shortcut_key(self.preferences.btn_stop)); - }, - // Register the panel with new debugger docker instance. - registerPanel: function(name, title, width, height, onInit) { - var self = this; - - this.docker.registerPanelType(name, { - title: title, - isPrivate: true, - onCreate: function(panel) { - self.panels[name] = panel; - panel.initSize(width, height); - if (!title) - panel.title(false); - else - panel.title(title); - panel.closeable(false); - panel.layout().addItem( - $('
', { - 'class': 'pg-debugger-panel', - }) - ); - if (onInit) { - onInit.apply(self, [panel]); - } - }, - }); - }, - }); - - pgTools.DirectDebug = new DirectDebug(); - pgTools.DirectDebug['jquery'] = $; - - return pgTools.DirectDebug; -}); diff --git a/web/pgadmin/tools/debugger/static/js/index.js b/web/pgadmin/tools/debugger/static/js/index.js new file mode 100644 index 000000000..cca05364a --- /dev/null +++ b/web/pgadmin/tools/debugger/static/js/index.js @@ -0,0 +1,22 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import pgAdmin from 'sources/pgadmin'; +import pgBrowser from 'top/browser/static/js/browser'; +import Debugger from './DebuggerModule'; + +if (!pgAdmin.Tools) { + pgAdmin.Tools = {}; +} + +pgAdmin.Tools.Debugger = Debugger.getInstance(pgAdmin, pgBrowser); + +module.exports = { + Debugger: Debugger, +}; diff --git a/web/pgadmin/tools/debugger/templates/debugger/direct.html b/web/pgadmin/tools/debugger/templates/debugger/direct.html index a73f79b32..2a217aa59 100644 --- a/web/pgadmin/tools/debugger/templates/debugger/direct.html +++ b/web/pgadmin/tools/debugger/templates/debugger/direct.html @@ -4,12 +4,10 @@ try { require( - ['sources/generated/debugger_direct', 'sources/generated/browser_nodes', 'sources/generated/codemirror'], - function(pgDirectDebug) { - var pgDirectDebug = pgDirectDebug || pgAdmin.Tools.DirectDebug; - var $ = pgDirectDebug.jquery; - - pgDirectDebug.load({{ uniqueId }}, {{ debug_type }}, '{{ function_name_with_arguments }}', '{{layout|safe}}'); + ['sources/generated/debugger', 'sources/pgadmin', 'sources/generated/codemirror'], + function(pgDirectDebug, pgAdmin) { + var pgDebug = window.pgAdmin.Tools.Debugger; + pgDebug.load(document.getElementById('debugger-main-container'), {{ uniqueId }}, {{ debug_type }}, '{{ function_name_with_arguments }}', '{{layout|safe}}'); // Register unload event on window close. /* If opened in new tab, close the connection only on tab/window close and @@ -39,6 +37,13 @@ try { } {% endblock %} {% block body %} + {% if is_desktop_mode and is_linux %} {% endif %} -
- -
+
{% endblock %} @@ -114,4 +61,5 @@ try { {% for stylesheet in stylesheets %} {% endfor %} + {% endblock %} diff --git a/web/pgadmin/tools/debugger/tests/test_debugger_clear_all_breakpoint.py b/web/pgadmin/tools/debugger/tests/test_debugger_clear_all_breakpoint.py index 3af6c3c2d..125dbddf6 100644 --- a/web/pgadmin/tools/debugger/tests/test_debugger_clear_all_breakpoint.py +++ b/web/pgadmin/tools/debugger/tests/test_debugger_clear_all_breakpoint.py @@ -58,13 +58,13 @@ class DebuggerClearAllBreakpoint(BaseTestGenerator): def clear_all_breakpoint(self): if hasattr(self, 'no_breakpoint') and self.no_breakpoint: - breakpoint_data = {"breakpoint_list": ''} + breakpoint_data = {"breakpoint_list": None} else: - breakpoint_data = {"breakpoint_list": 3} + breakpoint_data = {"breakpoint_list": '3'} return self.tester.post( self.url + str(self.trans_id), - data=breakpoint_data) + data=json.dumps(breakpoint_data)) def runTest(self): """ diff --git a/web/pgadmin/tools/debugger/tests/test_debugger_set_arguments.py b/web/pgadmin/tools/debugger/tests/test_debugger_set_arguments.py index 7d51084a0..1cd03b55d 100644 --- a/web/pgadmin/tools/debugger/tests/test_debugger_set_arguments.py +++ b/web/pgadmin/tools/debugger/tests/test_debugger_set_arguments.py @@ -52,11 +52,10 @@ class DebuggerSetArguments(BaseTestGenerator): debugger_utils.initialize_target(self, utils) def set_arguments(self): - args = {"data": json.dumps([ + args = json.dumps([ {"server_id": self.server_id, "database_id": self.db_id, "schema_id": self.schema_id, "function_id": self.func_id, "arg_id": 0, "is_null": 0, "is_expression": 0, "use_default": 1}]) - } return self.tester.post( self.url + str(self.server_id) + '/' + str(self.db_id) + '/' + diff --git a/web/regression/javascript/debugger/MockDebuggerComponent.jsx b/web/regression/javascript/debugger/MockDebuggerComponent.jsx new file mode 100644 index 000000000..0ae0b5e0e --- /dev/null +++ b/web/regression/javascript/debugger/MockDebuggerComponent.jsx @@ -0,0 +1,32 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import React from 'react'; +import PropTypes from 'prop-types'; + +import Theme from 'sources/Theme'; +import {DebuggerContext, DebuggerEventsContext} from '../../../pgadmin/tools/debugger/static/js/components/DebuggerComponent'; + +export default function MockDebuggerComponent({value, eventsvalue, children}) { + return ( + + + + {children} + + + + ); +} + +MockDebuggerComponent.propTypes = { + value: PropTypes.any, + eventsvalue: PropTypes.any, + children: PropTypes.any +}; diff --git a/web/regression/javascript/debugger/debugger_input_args_spec.js b/web/regression/javascript/debugger/debugger_input_args_spec.js new file mode 100644 index 000000000..5975261f5 --- /dev/null +++ b/web/regression/javascript/debugger/debugger_input_args_spec.js @@ -0,0 +1,282 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import $ from 'jquery'; +window.jQuery = window.$ = $; + +import 'wcdocker'; +import '../helper/enzyme.helper'; + +import jasmineEnzyme from 'jasmine-enzyme'; +import MockAdapter from 'axios-mock-adapter'; +import axios from 'axios/index'; + +import url_for from 'sources/url_for'; +import pgAdmin from 'sources/pgadmin'; + +import { messages } from '../fake_messages'; +import FunctionArguments from '../../../pgadmin/tools/debugger/static/js/debugger_ui'; +import Debugger from '../../../pgadmin/tools/debugger/static/js/DebuggerModule'; +import {TreeFake} from '../tree/tree_fake'; + + +describe('Debugger Component', () => { + let funcArgs; + let debuggerInstance; + let mountDOM; + let tree; + let params; + let networkMock; + + beforeEach(() => { + jasmineEnzyme(); + // Element for mount wcDocker panel + mountDOM = $('
'); + $(document.body).append(mountDOM); + + $(document.body).append($('
')); + + /* messages used by validators */ + pgAdmin.Browser = pgAdmin.Browser || {}; + pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages; + pgAdmin.Browser.utils = pgAdmin.Browser.utils || {}; + pgAdmin.Browser.stdH.md = 100; + pgAdmin.Browser.stdW.md = 100; + funcArgs = new FunctionArguments(); + debuggerInstance = new Debugger(pgAdmin, pgAdmin.Browser); + pgAdmin.Browser.preferences_cache = [ + { + 'id': 115, + 'cid': 13, + 'name': 'btn_step_into', + 'label': 'Accesskey (Step into)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 73, + 'char': 'i' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + }, { + 'id': 116, + 'cid': 13, + 'name': 'btn_step_over', + 'label': 'Accesskey (Step over)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 79, + 'char': 'o' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + }, + { + 'id': 113, + 'cid': 13, + 'name': 'btn_start', + 'label': 'Accesskey (Continue/Start)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 67, + 'char': 'c' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + }, { + 'id': 114, + 'cid': 13, + 'name': 'btn_stop', + 'label': 'Accesskey (Stop)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 83, + 'char': 's' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + }, { + 'id': 117, + 'cid': 13, + 'name': 'btn_toggle_breakpoint', + 'label': 'Accesskey (Toggle breakpoint)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 84, + 'char': 't' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + }, { + 'id': 118, + 'cid': 13, + 'name': 'btn_clear_breakpoints', + 'label': 'Accesskey (Clear all breakpoints)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 88, + 'char': 'x' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + } + + ]; + // eslint-disable-next-line + let docker = new wcDocker( + '.dockerContainer', { + allowContextMenu: false, + allowCollapse: false, + loadingClass: 'pg-sp-icon', + }); + + tree = new TreeFake(); + pgAdmin.Browser.tree = tree; + pgAdmin.Browser.docker = docker; + + params = { + transId: 1234, + directDebugger: debuggerInstance, + funcArgsInstance: funcArgs + }; + networkMock = new MockAdapter(axios); + }); + + it('Debugger Args', () => { + params.directDebugger.debug_type = 1; + networkMock.onGet(url_for('debugger.init', {'function': 1, 'schema': 1, 'database': 1, 'server': 1})).reply(200, {'success':1,'errormsg':'','info':'','result':null,'data':{'debug_info':[{'name':'_test2','prosrc':'begin\nselect \'1\';\nend','lanname':'plpgsql','proretset':false,'prorettype':1043,'rettype':'varchar','proargtypenames':'date','proargtypes':'1082','proargnames':'test_date','proargmodes':null,'pkg':0,'pkgname':'','pkgconsoid':0,'schema':2200,'schemaname':'public','isfunc':true,'signature':'test_date date','proargdefaults':null,'pronargdefaults':0,'require_input':true}],'trans_id':'7165'}}); + + let debugInfo = { + 'name': '_test2', + 'prosrc': 'begin\nselect \'1\';\nend', + 'lanname': 'plpgsql', + 'proretset': false, + 'prorettype': 1043, + 'rettype': 'varchar', + 'proargtypenames': 'date', + 'proargtypes': '1082', + 'proargnames': 'test_date', + 'proargmodes': null, + 'pkg': 0, + 'pkgname': '', + 'pkgconsoid': 0, + 'schema': 2200, + 'schemaname': 'public', + 'isfunc': true, + 'signature': 'test_date date', + 'proargdefaults': null, + 'pronargdefaults': 0, + 'require_input': true, + }; + + funcArgs.show(debugInfo, 0, false, '123'); + }); +}); + diff --git a/web/regression/javascript/debugger/debugger_spec.js b/web/regression/javascript/debugger/debugger_spec.js new file mode 100644 index 000000000..58c423372 --- /dev/null +++ b/web/regression/javascript/debugger/debugger_spec.js @@ -0,0 +1,282 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import $ from 'jquery'; +window.jQuery = window.$ = $; + +import 'wcdocker'; +import '../helper/enzyme.helper'; + +import React from 'react'; +import { createMount } from '@material-ui/core/test-utils'; +import jasmineEnzyme from 'jasmine-enzyme'; +import MockAdapter from 'axios-mock-adapter'; +import axios from 'axios/index'; + +import url_for from 'sources/url_for'; +import pgAdmin from 'sources/pgadmin'; + +import { messages } from '../fake_messages'; +import DebuggerComponent from '../../../pgadmin/tools/debugger/static/js/components/DebuggerComponent'; +import FunctionArguments from '../../../pgadmin/tools/debugger/static/js/debugger_ui'; +import Debugger from '../../../pgadmin/tools/debugger/static/js/DebuggerModule'; +import {TreeFake} from '../tree/tree_fake'; + + +describe('Debugger Component', () => { + let mount; + let funcArgs; + let debuggerInstance; + let nodeInfo; + let mountDOM; + let tree; + let params; + let networkMock; + + /* Use createMount so that material ui components gets the required context */ + /* https://material-ui.com/guides/testing/#api */ + beforeAll(() => { + mount = createMount(); + }); + + beforeEach(() => { + jasmineEnzyme(); + // Element for mount wcDocker panel + mountDOM = $('
'); + $(document.body).append(mountDOM); + + $(document.body).append($('
')); + + /* messages used by validators */ + pgAdmin.Browser = pgAdmin.Browser || {}; + pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages; + pgAdmin.Browser.utils = pgAdmin.Browser.utils || {}; + funcArgs = new FunctionArguments(); + debuggerInstance = new Debugger(pgAdmin, pgAdmin.Browser); + nodeInfo = { parent: {} }; + pgAdmin.Browser.preferences_cache = [ + { + 'id': 115, + 'cid': 13, + 'name': 'btn_step_into', + 'label': 'Accesskey (Step into)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 73, + 'char': 'i' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + }, { + 'id': 116, + 'cid': 13, + 'name': 'btn_step_over', + 'label': 'Accesskey (Step over)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 79, + 'char': 'o' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + }, + { + 'id': 113, + 'cid': 13, + 'name': 'btn_start', + 'label': 'Accesskey (Continue/Start)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 67, + 'char': 'c' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + }, { + 'id': 114, + 'cid': 13, + 'name': 'btn_stop', + 'label': 'Accesskey (Stop)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 83, + 'char': 's' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + }, { + 'id': 117, + 'cid': 13, + 'name': 'btn_toggle_breakpoint', + 'label': 'Accesskey (Toggle breakpoint)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 84, + 'char': 't' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + }, { + 'id': 118, + 'cid': 13, + 'name': 'btn_clear_breakpoints', + 'label': 'Accesskey (Clear all breakpoints)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 88, + 'char': 'x' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + } + + ]; + // eslint-disable-next-line + let docker = new wcDocker( + '.dockerContainer', { + allowContextMenu: false, + allowCollapse: false, + loadingClass: 'pg-sp-icon', + }); + + tree = new TreeFake(); + pgAdmin.Browser.tree = tree; + pgAdmin.Browser.docker = docker; + + params = { + transId: 1234, + directDebugger: debuggerInstance, + funcArgsInstance: funcArgs + }; + networkMock = new MockAdapter(axios); + }); + + it('DebuggerInit Indirect', () => { + params.directDebugger.debug_type = 1; + networkMock.onGet(url_for('debugger.start_listener', {'trans_id': params.transId})).reply(200, {'success':1,'errormsg':'','info':'','result':null,'data':{'status':true,'result':2}}); + networkMock.onGet(url_for('debugger.messages', {'trans_id': params.transId})).reply(200, {'success':1,'errormsg':'','info':'','result':null,'data':{'status':'Success','result':'10'}}); + networkMock.onGet(url_for('debugger.execute_query', {'trans_id': params.transId, 'query_type': 'get_stack_info'})).reply(200, {'success':1,'errormsg':'','info':'','result':null,'data':{'status':'Success','result':[{'level':0,'targetname':'_test()','func':3138947,'linenumber':9,'args':''}]}}); + networkMock.onGet(url_for('debugger.poll_result', {'trans_id': params.transId})).reply(200, {'success':0,'errormsg':'','info':'','result':null,'data':{'status':'Success','result':[{'pldbg_wait_for_target':28298}]}}); + let ctrl = mount( + + + ); + + ctrl.find('PgIconButton[data-test="debugger-contiue"]').props().onClick(); + }); +}); + diff --git a/web/regression/javascript/debugger/debugger_stack_spec.js b/web/regression/javascript/debugger/debugger_stack_spec.js new file mode 100644 index 000000000..bf4ca1a5e --- /dev/null +++ b/web/regression/javascript/debugger/debugger_stack_spec.js @@ -0,0 +1,291 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import $ from 'jquery'; +window.jQuery = window.$ = $; + +import 'wcdocker'; +import '../helper/enzyme.helper'; + +import React from 'react'; +import { createMount } from '@material-ui/core/test-utils'; +import jasmineEnzyme from 'jasmine-enzyme'; +import MockAdapter from 'axios-mock-adapter'; +import axios from 'axios/index'; + +import url_for from 'sources/url_for'; +import pgAdmin from 'sources/pgadmin'; + +import { messages } from '../fake_messages'; +import FunctionArguments from '../../../pgadmin/tools/debugger/static/js/debugger_ui'; +import Debugger from '../../../pgadmin/tools/debugger/static/js/DebuggerModule'; +import { TreeFake } from '../tree/tree_fake'; +import MockDebuggerComponent from './MockDebuggerComponent'; +import EventBus from '../../../pgadmin/static/js/helpers/EventBus'; +import { Stack } from '../../../pgadmin/tools/debugger/static/js/components/Stack'; + + +describe('Debugger Stack', () => { + let mount; + let funcArgs; + let debuggerInstance; + let mountDOM; + let tree; + let params; + let networkMock; + let pref; + + /* Use createMount so that material ui components gets the required context */ + /* https://material-ui.com/guides/testing/#api */ + beforeAll(() => { + mount = createMount(); + }); + + afterAll(() => { + mount.cleanUp(); + networkMock.restore(); + }); + + afterEach(() => { + networkMock.restore(); + }); + + + beforeEach(() => { + jasmineEnzyme(); + // Element for mount wcDocker panel + mountDOM = $('
'); + $(document.body).append(mountDOM); + + $(document.body).append($('
')); + + /* messages used by validators */ + pgAdmin.Browser = pgAdmin.Browser || {}; + pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages; + pgAdmin.Browser.utils = pgAdmin.Browser.utils || {}; + funcArgs = new FunctionArguments(); + debuggerInstance = new Debugger(pgAdmin, pgAdmin.Browser); + pref = [ + { + 'id': 115, + 'cid': 13, + 'name': 'btn_step_into', + 'label': 'Accesskey (Step into)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 73, + 'char': 'i' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + }, { + 'id': 116, + 'cid': 13, + 'name': 'btn_step_over', + 'label': 'Accesskey (Step over)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 79, + 'char': 'o' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + }, + { + 'id': 113, + 'cid': 13, + 'name': 'btn_start', + 'label': 'Accesskey (Continue/Start)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 67, + 'char': 'c' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + }, { + 'id': 114, + 'cid': 13, + 'name': 'btn_stop', + 'label': 'Accesskey (Stop)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 83, + 'char': 's' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + }, { + 'id': 117, + 'cid': 13, + 'name': 'btn_toggle_breakpoint', + 'label': 'Accesskey (Toggle breakpoint)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 84, + 'char': 't' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + }, { + 'id': 118, + 'cid': 13, + 'name': 'btn_clear_breakpoints', + 'label': 'Accesskey (Clear all breakpoints)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 88, + 'char': 'x' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + } + + ]; + + pgAdmin.Browser.preferences_cache = pref; + // eslint-disable-next-line + let docker = new wcDocker( + '.dockerContainer', { + allowContextMenu: false, + allowCollapse: false, + loadingClass: 'pg-sp-icon', + }); + + tree = new TreeFake(); + pgAdmin.Browser.tree = tree; + pgAdmin.Browser.docker = docker; + + params = { + transId: 1234, + directDebugger: debuggerInstance, + funcArgsInstance: funcArgs + }; + networkMock = new MockAdapter(axios); + }); + + it('Statck Init', () => { + networkMock.onGet(url_for('debugger.select_frame', { 'trans_id': params.transId, 'frame_id': 3 })).reply(200, {'success':0,'errormsg':'','info':'','result':null,'data':{'status':true,'result':[{'func':3138947,'targetname':'_test()','linenumber':10,'src':'\nDECLARE\n v_deptno NUMERIC;\n v_empno NUMERIC;\n v_ename VARCHAR;\n v_rows INTEGER;\n r_emp_query EMP_QUERY_TYPE;\nBEGIN\n v_deptno := 30;\n v_empno := 0;\n v_ename := \'Martin\';\n r_emp_query := emp_query(v_deptno, v_empno, v_ename);\n RAISE INFO \'Department : %\', v_deptno;\n RAISE INFO \'Employee No: %\', (r_emp_query).empno;\n RAISE INFO \'Name : %\', (r_emp_query).ename;\n RAISE INFO \'Job : %\', (r_emp_query).job;\n RAISE INFO \'Hire Date : %\', (r_emp_query).hiredate;\n RAISE INFO \'Salary : %\', (r_emp_query).sal;\n RETURN \'1\';\nEXCEPTION\n WHEN OTHERS THEN\n RAISE INFO \'The following is SQLERRM : %\', SQLERRM;\n RAISE INFO \'The following is SQLSTATE: %\', SQLSTATE;\n RETURN \'1\';\nEND;\n','args':''}]}}); + mount( + + + + ); + }); +}); + diff --git a/web/regression/javascript/debugger/debugger_tool_bar_spec.js b/web/regression/javascript/debugger/debugger_tool_bar_spec.js new file mode 100644 index 000000000..4f8caf9ab --- /dev/null +++ b/web/regression/javascript/debugger/debugger_tool_bar_spec.js @@ -0,0 +1,395 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import $ from 'jquery'; +window.jQuery = window.$ = $; + +import 'wcdocker'; +import '../helper/enzyme.helper'; + +import React from 'react'; +import { createMount } from '@material-ui/core/test-utils'; +import jasmineEnzyme from 'jasmine-enzyme'; +import MockAdapter from 'axios-mock-adapter'; +import axios from 'axios/index'; + +import url_for from 'sources/url_for'; +import pgAdmin from 'sources/pgadmin'; + +import { messages } from '../fake_messages'; +import FunctionArguments from '../../../pgadmin/tools/debugger/static/js/debugger_ui'; +import Debugger from '../../../pgadmin/tools/debugger/static/js/DebuggerModule'; +import { TreeFake } from '../tree/tree_fake'; +import MockDebuggerComponent from './MockDebuggerComponent'; +import EventBus from '../../../pgadmin/static/js/helpers/EventBus'; +import { ToolBar } from '../../../pgadmin/tools/debugger/static/js/components/ToolBar'; + + +describe('Debugger Toolbar', () => { + let mount; + let funcArgs; + let debuggerInstance; + let mountDOM; + let tree; + let params; + let networkMock; + let pref; + + /* Use createMount so that material ui components gets the required context */ + /* https://material-ui.com/guides/testing/#api */ + beforeAll(() => { + mount = createMount(); + }); + + afterAll(() => { + mount.cleanUp(); + networkMock.restore(); + }); + + afterEach(() => { + networkMock.restore(); + }); + + beforeEach(() => { + jasmineEnzyme(); + // Element for mount wcDocker panel + mountDOM = $('
'); + $(document.body).append(mountDOM); + + $(document.body).append($('
')); + + /* messages used by validators */ + pgAdmin.Browser = pgAdmin.Browser || {}; + pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages; + pgAdmin.Browser.utils = pgAdmin.Browser.utils || {}; + funcArgs = new FunctionArguments(); + debuggerInstance = new Debugger(pgAdmin, pgAdmin.Browser); + pref = [ + { + 'id': 115, + 'cid': 13, + 'name': 'btn_step_into', + 'label': 'Accesskey (Step into)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 73, + 'char': 'i' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + }, { + 'id': 116, + 'cid': 13, + 'name': 'btn_step_over', + 'label': 'Accesskey (Step over)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 79, + 'char': 'o' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + }, + { + 'id': 113, + 'cid': 13, + 'name': 'btn_start', + 'label': 'Accesskey (Continue/Start)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 67, + 'char': 'c' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + }, { + 'id': 114, + 'cid': 13, + 'name': 'btn_stop', + 'label': 'Accesskey (Stop)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 83, + 'char': 's' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + }, { + 'id': 117, + 'cid': 13, + 'name': 'btn_toggle_breakpoint', + 'label': 'Accesskey (Toggle breakpoint)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 84, + 'char': 't' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + }, { + 'id': 118, + 'cid': 13, + 'name': 'btn_clear_breakpoints', + 'label': 'Accesskey (Clear all breakpoints)', + 'type': 'keyboardshortcut', + 'help_str': null, + 'control_props': {}, + 'min_val': null, + 'max_val': null, + 'options': null, + 'select': null, + 'value': { + 'key': { + 'key_code': 88, + 'char': 'x' + } + }, + 'fields': [ + { + 'name': 'key', + 'type': 'keyCode', + 'label': 'Key' + } + ], + 'disabled': false, + 'dependents': null, + 'mid': 83, + 'module': 'debugger', + } + + ]; + + pgAdmin.Browser.preferences_cache = pref; + // eslint-disable-next-line + let docker = new wcDocker( + '.dockerContainer', { + allowContextMenu: false, + allowCollapse: false, + loadingClass: 'pg-sp-icon', + }); + + tree = new TreeFake(); + pgAdmin.Browser.tree = tree; + pgAdmin.Browser.docker = docker; + + params = { + transId: 1234, + directDebugger: debuggerInstance, + funcArgsInstance: funcArgs + }; + networkMock = new MockAdapter(axios); + }); + + it('Toolbar clearbreakpoints', () => { + networkMock.onGet(url_for('debugger.clear_all_breakpoint', { 'trans_id': params.transId })).reply(200, { 'success': 1, 'errormsg': '', 'info': '', 'result': null, 'data': { 'status': true, 'result': 2 } }); + let ctrl = mount( + + + + ); + ctrl.find('PgIconButton[data-test="clear-breakpoint"]').props().onClick(); + }); + + it('Toolbar Stop Debugger', () => { + networkMock.onGet(url_for('debugger.execute_query', { 'trans_id': params.transId, 'query_type': 'abort_target'})).reply(200, {'success':1,'errormsg':'','info':'Debugging aborted successfully.','result':null,'data':{'status':'Success','result':{'columns':[{'name':'pldbg_abort_target','type_code':16,'display_size':null,'internal_size':1,'precision':null,'scale':null,'null_ok':null,'table_oid':null,'table_column':null,'display_name':'pldbg_abort_target'}],'rows':[{'pldbg_abort_target':true}]}}}); + let ctrl = mount( + + + + ); + ctrl.find('PgIconButton[data-test="stop-debugger"]').props().onClick(); + }); + + + it('Toolbar Toggle Breakpoint', () => { + networkMock.onGet(url_for('debugger.set_breakpoint', { 'trans_id': params.transId, 'line_no': '1', 'set_type': 1})).reply(200, {'success':1,'errormsg':'','info':'','result':null,'data':{'status':true,'result':[{'pldbg_set_breakpoint':true}]}}); + let ctrl = mount( + + + + ); + ctrl.find('PgIconButton[data-test="toggle-breakpoint"]').props().onClick(); + }); + + + it('Toolbar StepIn', () => { + networkMock.onGet(url_for('debugger.execute_query', { 'trans_id': params.transId, 'query_type': 'step_into'})).reply(200, {'success':1,'errormsg':'','info':'','result':null,'data':{'status':true,'result':1}}); + let ctrl = mount( + + + + ); + ctrl.find('PgIconButton[data-test="step-in"]').props().onClick(); + }); + + it('Toolbar StepOver', () => { + networkMock.onGet(url_for('debugger.execute_query', { 'trans_id': params.transId, 'query_type': 'step_over'})).reply(200, {'success':1,'errormsg':'','info':'','result':null,'data':{'status':true,'result':1}}); + let ctrl = mount( + + + + ); + ctrl.find('PgIconButton[data-test="step-over"]').props().onClick(); + }); + + it('Toolbar Contiue', () => { + networkMock.onGet(url_for('debugger.execute_query', { 'trans_id': params.transId, 'query_type': 'continue'})).reply(200, {'success':1,'errormsg':'','info':'','result':null,'data':{'status':true,'result':2}}); + let ctrl = mount( + + + + ); + ctrl.find('PgIconButton[data-test="debugger-contiue"]').props().onClick(); + }); + + it('Toolbar Help', () => { + networkMock.onGet(url_for('help.static', {'filename': 'debugger.html'})).reply(200, {}); + let ctrl = mount( + + + + ); + ctrl.find('PgIconButton[data-test="debugger-help"]').props().onClick(); + }); +}); + diff --git a/web/regression/javascript/debugger_utils_spec.js b/web/regression/javascript/debugger_utils_spec.js index 1f8bb62d6..196f3a328 100644 --- a/web/regression/javascript/debugger_utils_spec.js +++ b/web/regression/javascript/debugger_utils_spec.js @@ -8,44 +8,10 @@ ////////////////////////////////////////////////////////////////////////// import { - setFocusToDebuggerEditor, getProcedureId, + getFunctionId, } from '../../pgadmin/tools/debugger/static/js/debugger_utils'; -describe('setFocusToDebuggerEditor', function () { - let editor; - editor = jasmine.createSpyObj('editor', ['focus']); - - let tab_key = { - which: 9, - keyCode: 9, - }; - - let enter_key = { - which: 13, - keyCode: 13, - }; - - describe('setFocusToDebuggerEditor', function () { - it('returns undefined if no command is passed', function () { - expect(setFocusToDebuggerEditor(editor, null)).toEqual(undefined); - }); - }); - - describe('setFocusToDebuggerEditor', function () { - it('should call focus on editor', function () { - setFocusToDebuggerEditor(editor, enter_key); - expect(editor.focus).toHaveBeenCalled(); - }); - }); - - describe('setFocusToDebuggerEditor', function () { - it('should not call focus on editor and returns undefined', function () { - expect(setFocusToDebuggerEditor(editor, tab_key)).toEqual(undefined); - }); - }); -}); - describe('getProcedureId', function () { let treeInfroProc = { 'procedure': { @@ -83,3 +49,31 @@ describe('getProcedureId', function () { }); }); +describe('getFunctionId', function () { + let treeInfroFunc = { + 'function': { + '_id': 123, + }, + }; + let treeInfroInvalidFuncId = { + 'function': { + '_id': null, + }, + }; + + let fakeTreeInfro; + + describe('Should return proper object id', function () { + it('returns valid function id', function () { + expect(getFunctionId(treeInfroFunc)).toEqual(123); + }); + + it('returns undefined for fake tree info', function () { + expect(getFunctionId(fakeTreeInfro)).toEqual(undefined); + }); + + it('returns undefined for invalid function id', function () { + expect(getFunctionId(treeInfroInvalidFuncId)).toEqual(undefined); + }); + }); +}); diff --git a/web/regression/javascript/schema_ui_files/debugger_args.ui.spec.js b/web/regression/javascript/schema_ui_files/debugger_args.ui.spec.js new file mode 100644 index 000000000..dc3a1b2d5 --- /dev/null +++ b/web/regression/javascript/schema_ui_files/debugger_args.ui.spec.js @@ -0,0 +1,51 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2022, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import '../helper/enzyme.helper'; + +import React from 'react'; +import { createMount } from '@material-ui/core/test-utils'; + +import SchemaView from '../../../pgadmin/static/js/SchemaView'; +import {DebuggerArgumentSchema} from '../../../pgadmin/tools/debugger/static/js/components/DebuggerArgs.ui'; +import {genericBeforeEach} from '../genericFunctions'; + +describe('DebuggerArgs', () => { + let mount; + let schemaObj = new DebuggerArgumentSchema( + ); + + /* 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(() => { + genericBeforeEach(); + }); + + it('create', () => { + mount( {/*This is intentional (SonarQube)*/}} + showFooter={false} + isTabView={false} + />); + }); +}); + diff --git a/web/regression/python_test_utils/test_utils.py b/web/regression/python_test_utils/test_utils.py index d7dfc5f1b..db4192476 100644 --- a/web/regression/python_test_utils/test_utils.py +++ b/web/regression/python_test_utils/test_utils.py @@ -358,6 +358,7 @@ def create_debug_function(server, db_name, function_name="test_func"): connection.set_isolation_level(0) pg_cursor = connection.cursor() pg_cursor.execute(''' + CREATE EXTENSION pldbgapi; CREATE OR REPLACE FUNCTION public."%s"() RETURNS text LANGUAGE 'plpgsql' diff --git a/web/webpack.config.js b/web/webpack.config.js index 4818f028f..533a2682e 100644 --- a/web/webpack.config.js +++ b/web/webpack.config.js @@ -378,7 +378,7 @@ module.exports = [{ codemirror: sourceDir + '/bundle/codemirror.js', slickgrid: sourceDir + '/bundle/slickgrid.js', sqleditor: './pgadmin/tools/sqleditor/static/js/index.js', - debugger_direct: './pgadmin/tools/debugger/static/js/direct.js', + debugger: './pgadmin/tools/debugger/static/js/index.js', schema_diff: './pgadmin/tools/schema_diff/static/js/schema_diff_hook.js', erd_tool: './pgadmin/tools/erd/static/js/erd_tool_hook.js', psql_tool: './pgadmin/tools/psql/static/js/index.js', @@ -543,8 +543,7 @@ module.exports = [{ 'pure|pgadmin.tools.maintenance', 'pure|pgadmin.tools.import_export', 'pure|pgadmin.tools.import_export_servers', - 'pure|pgadmin.tools.debugger.controller', - 'pure|pgadmin.tools.debugger.direct', + 'pure|pgadmin.tools.debugger', 'pure|pgadmin.node.pga_job', 'pure|pgadmin.tools.schema_diff', 'pure|pgadmin.tools.storage_manager', diff --git a/web/webpack.shim.js b/web/webpack.shim.js index 39ba3fc37..4d5d13d9b 100644 --- a/web/webpack.shim.js +++ b/web/webpack.shim.js @@ -275,8 +275,7 @@ var webpackShimConfig = { 'pgadmin.server.supported_servers': '/browser/server/supported_servers', 'pgadmin.tables.js': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/'), 'pgadmin.tools.backup': path.join(__dirname, './pgadmin/tools/backup/static/js/backup'), - 'pgadmin.tools.debugger.controller': path.join(__dirname, './pgadmin/tools/debugger/static/js/debugger'), - 'pgadmin.tools.debugger.direct': path.join(__dirname, './pgadmin/tools/debugger/static/js/direct'), + 'pgadmin.tools.debugger': path.join(__dirname, './pgadmin/tools/debugger/static/js/'), 'pgadmin.tools.debugger.ui': path.join(__dirname, './pgadmin/tools/debugger/static/js/debugger_ui'), 'pgadmin.tools.debugger.utils': path.join(__dirname, './pgadmin/tools/debugger/static/js/debugger_utils'), 'pgadmin.tools.grant_wizard': path.join(__dirname, './pgadmin/tools/grant_wizard/static/js/grant_wizard'),