1) Added mouse over indication for breakpoint area in the Debugger. Fixes #2647

2) Added search text option to the Debugger panel. Fixes #2648
3) Port Debugger to React. Fixes #6132
This commit is contained in:
Nikhil Mohite 2022-06-15 11:37:54 +05:30 committed by Akshay Joshi
parent 7e1e068370
commit 2adca6bbc4
44 changed files with 5162 additions and 3841 deletions

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 132 KiB

View File

@ -9,6 +9,8 @@ This release contains a number of bug fixes and new features since the release o
New features
************
| `Issue #2647 <https://redmine.postgresql.org/issues/2647>`_ - Added mouse over indication for breakpoint area in the Debugger.
| `Issue #2648 <https://redmine.postgresql.org/issues/2648>`_ - Added search text option to the Debugger panel.
| `Issue #7178 <https://redmine.postgresql.org/issues/7178>`_ - Added capability to deploy PostgreSQL servers on Microsoft Azure.
| `Issue #7332 <https://redmine.postgresql.org/issues/7332>`_ - Added support for passing password using Docker Secret to Docker images.
| `Issue #7351 <https://redmine.postgresql.org/issues/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 <https://redmine.postgresql.org/issues/6132>`_ - Port Debugger to React.
| `Issue #7315 <https://redmine.postgresql.org/issues/7315>`_ - Updates documentation for the Traefik v2 container deployment.
| `Issue #7411 <https://redmine.postgresql.org/issues/7411>`_ - Update pgcli to latest release 3.4.1.
| `Issue #7469 <https://redmine.postgresql.org/issues/7469>`_ - Upgrade Chartjs to the latest 3.8.0.

View File

@ -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)

View File

@ -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
};

View File

@ -114,6 +114,11 @@
width: .9em;
}
.breakpoints {
width: .9em;
cursor: pointer;
}
.CodeMirror-foldgutter-open,
.CodeMirror-foldgutter-folded {
cursor: pointer;

View File

@ -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)):

View File

@ -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>',
NO_DEFAULT_VALUE: '<No default value>',
};

View File

@ -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(
<ModalProvider>
<DebuggerComponent pgAdmin={pgWindow.pgAdmin} selectedNodeInfo={selectedNodeInfo} panel={panel} layout={layout} params={{
transId: trans_id,
directDebugger: this,
funcArgsInstance: this.funcArgs
}} />
</ModalProvider>,
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') });
}
}

View File

@ -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(),
}];
}
}

View File

@ -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 (
<Box className={classes.root}>
<Box className={classes.body}>
{
loadArgs > 0 &&
<SchemaView
formType={'dialog'}
getInitData={initData}
viewHelperProps={{ mode: 'edit' }}
schema={debuggerArgsSchema.current}
showFooter={false}
isTabView={false}
onDataChange={(isChanged, changedData) => {
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;
}}
/>
}
</Box>
<Box className={classes.footer}>
<Box>
<DefaultButton className={classes.buttonMargin} onClick={() => { clearArgs(); }} startIcon={<DeleteSweepIcon onClick={() => { clearArgs(); }} />}>
{gettext('Clear All')}
</DefaultButton>
</Box>
<Box className={classes.actionBtn} marginLeft="auto">
<DefaultButton className={classes.buttonMargin} onClick={() => { props.closeModal(); }} startIcon={<CloseSharpIcon onClick={() => { props.closeModal(); }} />}>
{gettext('Cancel')}
</DefaultButton>
<PrimaryButton className={classes.buttonMargin} startIcon={<BugReportRoundedIcon className={classes.debugBtn} />}
disabled={isDisableDebug}
onClick={() => { startDebugging(); }}>
{gettext('Debug')}
</PrimaryButton>
</Box>
</Box>
</Box>
);
}
DebuggerArgumentComponent.propTypes = {
debuggerInfo: PropTypes.object,
restartDebug: PropTypes.number,
isEdbProc: PropTypes.bool,
transId: PropTypes.string,
closeModal: PropTypes.func,
};

File diff suppressed because it is too large Load Diff

View File

@ -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 (
<CodeMirror
currEditor={(obj) => {
editor.current = obj;
}}
gutters={['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'breakpoints']}
value={''}
className={classes.sql}
disabled={true}
/>);
}
DebuggerEditor.propTypes = {
getEditor: PropTypes.func,
params: PropTypes.object
};

View File

@ -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 (
<div className={classes.root} tabIndex="0" id='debugger-msg'>{messageText}</div>
);
}

View File

@ -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 (
<Paper variant="outlined" elevation={0} className={classes.summaryContainer}>
<TableContainer className={classes.container}>
<table className={clsx(tableClasses.table)} aria-label="sticky table">
<thead>
<tr>
<th>{gettext('Name')}</th>
<th>{gettext('Type')}</th>
<th>{gettext('Value')}</th>
</tr>
</thead>
<tbody>
{variablesData.map((row) => (
<tr key={row.name}>
<td>{row.name}</td>
<td>{row.dtype}</td>
<td>
{row.dtype == 'date' ?
<InputDateTimePicker
value={row.value}
controlProps={{
placeholder: gettext('YYYY-MM-DD'),
autoOk: true, pickerType: 'Date', ampm: false,
}}
onChange={(val) => {
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);
}
}}
></InputDateTimePicker>
:
<InputText value={row.value} type={row.dtype}
disabled={disableVarChange}
onChange={(val) => {
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);
}
}}
></InputText>}</td>
</tr>
))}
{
variablesData.length == 0 &&
<tr key={_.uniqueId('c')} className={classes.cell}>
<td colSpan={3} >{gettext('No data found')}</td>
</tr>
}
</tbody>
</table>
</TableContainer>
</Paper>
);
}
LocalVariablesAndParams.propTypes = {
type: PropTypes.number
};

View File

@ -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 (
<Paper variant="outlined" elevation={0} className={classes.summaryContainer}>
<table className={clsx(tableClasses.table)}>
<thead>
<tr key={_.uniqueId('c')}>
{
columns.map((col) => (
<th key={col.name}>{col.name}</th>
))
}
</tr>
</thead>
<tbody>
{resultData.map((row) => (
<tr key={_.uniqueId('c')}>
{
columns.map((col) => (
<td key={_.uniqueId('c')}>{row[col.name]}</td>
))
}
</tr>
))}
</tbody>
</table>
</Paper>
);
}

View File

@ -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 (
<Paper variant="outlined" elevation={0} className={classes.summaryContainer}>
<TableContainer className={classes.container}>
<table className={clsx(tableClasses.table)} aria-label="sticky table">
<thead>
<tr>
<th>{gettext('Name')}</th>
<th>{gettext('Value')}</th>
<th>{gettext('Line No.')}</th>
</tr>
</thead>
<tbody>
{stackData?.map((row, index) => (
<tr key={_.uniqueId('c')}>
<td>
{row.targetname}
</td>
<td>{row.args}</td>
<td>
<InputText data-test='stack-select-frame' value={row.linenumber} readonly={true} disabled={disableFrameSelection} onClick={() => { if(!disableFrameSelection)eventBus.fireEvent(DEBUGGER_EVENTS.SET_FRAME, index);}}></InputText>
</td>
</tr>
))}
</tbody>
</table>
</TableContainer>
</Paper>
);
}

View File

@ -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 (
<Box className={classes.root}>
<PgButtonGroup size="small">
<PgIconButton data-test='step-in' title={gettext('Step into')} disabled={buttonsDisabled['step-into']} icon={<FormatIndentIncreaseIcon />} onClick={() => { stepInTODebugger(); }}
accesskey={shortcut_key(preferences?.btn_step_into)} />
<PgIconButton data-test='step-over' title={gettext('Step over')} disabled={buttonsDisabled['step-over']} icon={<FormatIndentDecreaseIcon />} onClick={() => { stepOverDebugger(); }}
accesskey={shortcut_key(preferences?.btn_step_over)} />
<PgIconButton data-test='debugger-contiue' title={gettext('Continue/Start')} disabled={buttonsDisabled['start']} icon={<PlayCircleFilledWhiteIcon />} onClick={() => { continueDebugger(); }}
accesskey={shortcut_key(preferences?.btn_start)} />
</PgButtonGroup>
<PgButtonGroup size="small">
<PgIconButton data-test='toggle-breakpoint' title={gettext('Toggle breakpoint')} disabled={buttonsDisabled['toggle-breakpoint']} icon={<FiberManualRecordIcon />}
accesskey={shortcut_key(preferences?.btn_toggle_breakpoint)} onClick={() => { toggleBreakpoint(); }} />
<PgIconButton data-test='clear-breakpoint' title={gettext('Clear all breakpoints')} disabled={buttonsDisabled['clear-all-breakpoints']} icon={<NotInterestedIcon />}
accesskey={shortcut_key(preferences?.btn_clear_breakpoints)} onClick={() => { clearAllBreakpoint(); }} />
</PgButtonGroup>
<PgButtonGroup size="small">
<PgIconButton data-test='stop-debugger' title={gettext('Stop')} icon={<StopIcon />} disabled={buttonsDisabled['stop']} onClick={() => { stop(); }}
accesskey={shortcut_key(preferences?.btn_stop)} />
</PgButtonGroup>
<PgButtonGroup size="small">
<PgIconButton data-test='debugger-help' title={gettext('Help')} icon={<HelpIcon />} onClick={onHelpClick} />
</PgButtonGroup>
</Box>
);
}

View File

@ -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;
});

File diff suppressed because it is too large Load Diff

View File

@ -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,

File diff suppressed because it is too large Load Diff

View File

@ -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,
};

View File

@ -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 %}
<style>
#debugger-main-container {
display: flex;
flex-direction: column;
height: 100%;
}
</style>
{% if is_desktop_mode and is_linux %}
<style>
body
@ -48,65 +53,7 @@ try {
.alertify .ajs-dialog.ajs-shake{-webkit-animation-name: none;}
</style>
{% endif %}
<div class="debugger_main_container" tabindex="0">
<div id="btn-toolbar" class="editor-toolbar" role="toolbar" aria-label="">
<div class="btn-group" role="group" aria-label="">
<button type="button" class="btn btn-sm btn-secondary btn-step-into" id="btn-step-into"
title=""
accesskey=""
tabindex="0"
autofocus="autofocus"
aria-label="{{ gettext('Step into') }}"
disabled>
<i class="fa fa-indent sql-icon-lg" aria-hidden="true"></i>
</button>
<button type="button" class="btn btn-sm btn-secondary btn-step-over" id="btn-step-over"
title=""
accesskey=""
tabindex="0"
aria-label="{{ gettext('Step over') }}"
disabled>
<i class="fa fa-outdent sql-icon-lg" aria-hidden="true"></i>
</button>
<button type="button" class="btn btn-sm btn-secondary btn-continue" id="btn-continue"
title=""
accesskey=""
tabindex="0"
aria-label="{{ gettext('Continue/Start') }}"
disabled>
<i class="fa fa-play-circle sql-icon-lg" aria-hidden="true"></i>
</button>
</div>
<div class="btn-group" role="group" aria-label="">
<button type="button" class="btn btn-sm btn-secondary btn-toggle-breakpoint" id="btn-toggle-breakpoint"
title=""
accesskey=""
tabindex="0"
aria-label="{{ gettext('Toggle breakpoint') }}"
disabled>
<i class="fa fa-circle sql-icon-lg" aria-hidden="true"></i>
</button>
<button type="button" class="btn btn-sm btn-secondary btn-clear-breakpoint" id="btn-clear-breakpoint"
title=""
accesskey=""
tabindex="0"
aria-label="{{ gettext('Clear all breakpoints') }}"
disabled>
<i class="fa fa-ban sql-icon-lg" aria-hidden="true"></i>
</button>
</div>
<div class="btn-group" role="group" aria-label="">
<button type="button" class="btn btn-sm btn-secondary btn-stop" id="btn-stop"
accesskey=""
title=""
tabindex="0"
aria-label="{{ gettext('Stop') }}"
disabled>
<i class="fa fa-stop-circle sql-icon-lg" aria-hidden="true"></i>
</button>
</div>
</div>
<div id="container" class="debugger-container" tabindex="0"></div>
<div id="debugger-main-container" tabindex="0">
</div>
{% endblock %}
@ -114,4 +61,5 @@ try {
{% for stylesheet in stylesheets %}
<link type="text/css" rel="stylesheet" href="{{ stylesheet }}"/>
{% endfor %}
<link type="text/css" rel="stylesheet" href="{{ url_for('browser.browser_css')}}"/>
{% endblock %}

View File

@ -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):
"""

View File

@ -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) + '/' +

View File

@ -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 (
<DebuggerContext.Provider value={value}>
<DebuggerEventsContext.Provider value={eventsvalue}>
<Theme>
{children}
</Theme>
</DebuggerEventsContext.Provider>
</DebuggerContext.Provider>
);
}
MockDebuggerComponent.propTypes = {
value: PropTypes.any,
eventsvalue: PropTypes.any,
children: PropTypes.any
};

View File

@ -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 = $('<div class="dockerContainer">');
$(document.body).append(mountDOM);
$(document.body).append($('<div id="debugger-main-container">'));
/* 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');
});
});

View File

@ -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 = $('<div class="dockerContainer">');
$(document.body).append(mountDOM);
$(document.body).append($('<div id="debugger-main-container">'));
/* 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(
<DebuggerComponent
pgAdmin={pgAdmin}
panel={document.getElementById('debugger-main-container')}
selectedNodeInfo={nodeInfo}
layout={''}
params={params}
>
</DebuggerComponent>
);
ctrl.find('PgIconButton[data-test="debugger-contiue"]').props().onClick();
});
});

View File

@ -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 = $('<div class="dockerContainer">');
$(document.body).append(mountDOM);
$(document.body).append($('<div id="debugger-main-container">'));
/* 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(
<MockDebuggerComponent value={{
docker: '',
api: networkMock,
modal: {},
params: params,
preferences: pgAdmin.Browser.preferences_cache,
}}
eventsvalue={new EventBus()}>
<Stack></Stack>
</MockDebuggerComponent>
);
});
});

View File

@ -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 = $('<div class="dockerContainer">');
$(document.body).append(mountDOM);
$(document.body).append($('<div id="debugger-main-container">'));
/* 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(
<MockDebuggerComponent value={{
docker: '',
api: networkMock,
modal: {},
params: params,
preferences: pgAdmin.Browser.preferences_cache,
}}
eventsvalue={new EventBus()}>
<ToolBar></ToolBar>
</MockDebuggerComponent>
);
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(
<MockDebuggerComponent value={{
docker: '',
api: networkMock,
modal: {},
params: params,
preferences: pgAdmin.Browser.preferences_cache,
}}
eventsvalue={new EventBus()}>
<ToolBar></ToolBar>
</MockDebuggerComponent>
);
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(
<MockDebuggerComponent value={{
docker: '',
api: networkMock,
modal: {},
params: params,
preferences: pgAdmin.Browser.preferences_cache,
}}
eventsvalue={new EventBus()}>
<ToolBar></ToolBar>
</MockDebuggerComponent>
);
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(
<MockDebuggerComponent value={{
docker: '',
api: networkMock,
modal: {},
params: params,
preferences: pgAdmin.Browser.preferences_cache,
}}
eventsvalue={new EventBus()}>
<ToolBar></ToolBar>
</MockDebuggerComponent>
);
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(
<MockDebuggerComponent value={{
docker: '',
api: networkMock,
modal: {},
params: params,
preferences: pgAdmin.Browser.preferences_cache,
}}
eventsvalue={new EventBus()}>
<ToolBar></ToolBar>
</MockDebuggerComponent>
);
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(
<MockDebuggerComponent value={{
docker: '',
api: networkMock,
modal: {},
params: params,
preferences: pgAdmin.Browser.preferences_cache,
}}
eventsvalue={new EventBus()}>
<ToolBar></ToolBar>
</MockDebuggerComponent>
);
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(
<MockDebuggerComponent value={{
docker: '',
api: networkMock,
modal: {},
params: params,
preferences: pgAdmin.Browser.preferences_cache,
}}
eventsvalue={new EventBus()}>
<ToolBar></ToolBar>
</MockDebuggerComponent>
);
ctrl.find('PgIconButton[data-test="debugger-help"]').props().onClick();
});
});

View File

@ -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);
});
});
});

View File

@ -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(<SchemaView
formType='dialog'
schema={schemaObj}
viewHelperProps={{
mode: 'create',
}}
onDataChange={() => {/*This is intentional (SonarQube)*/}}
showFooter={false}
isTabView={false}
/>);
});
});

View File

@ -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'

View File

@ -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',

View File

@ -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'),