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
@ -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
|
||||
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 107 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 132 KiB |
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -114,6 +114,11 @@
|
||||
width: .9em;
|
||||
}
|
||||
|
||||
.breakpoints {
|
||||
width: .9em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.CodeMirror-foldgutter-open,
|
||||
.CodeMirror-foldgutter-folded {
|
||||
cursor: pointer;
|
||||
|
@ -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)):
|
||||
|
57
web/pgadmin/tools/debugger/static/js/DebuggerConstants.js
Normal 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>',
|
||||
};
|
727
web/pgadmin/tools/debugger/static/js/DebuggerModule.js
Normal 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') });
|
||||
}
|
||||
}
|
@ -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(),
|
||||
}];
|
||||
}
|
||||
}
|
@ -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,
|
||||
};
|
||||
|
@ -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
|
||||
};
|
@ -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>
|
||||
);
|
||||
}
|
@ -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
|
||||
};
|
71
web/pgadmin/tools/debugger/static/js/components/Results.jsx
Normal 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>
|
||||
);
|
||||
}
|
84
web/pgadmin/tools/debugger/static/js/components/Stack.jsx
Normal 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>
|
||||
);
|
||||
}
|
140
web/pgadmin/tools/debugger/static/js/components/ToolBar.jsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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;
|
||||
});
|
@ -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,
|
||||
|
22
web/pgadmin/tools/debugger/static/js/index.js
Normal 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,
|
||||
};
|
@ -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 %}
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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) + '/' +
|
||||
|
32
web/regression/javascript/debugger/MockDebuggerComponent.jsx
Normal 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
|
||||
};
|
282
web/regression/javascript/debugger/debugger_input_args_spec.js
Normal 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');
|
||||
});
|
||||
});
|
||||
|
282
web/regression/javascript/debugger/debugger_spec.js
Normal 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();
|
||||
});
|
||||
});
|
||||
|
291
web/regression/javascript/debugger/debugger_stack_spec.js
Normal 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>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
395
web/regression/javascript/debugger/debugger_tool_bar_spec.js
Normal 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();
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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}
|
||||
/>);
|
||||
});
|
||||
});
|
||||
|
@ -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'
|
||||
|
@ -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',
|
||||
|
@ -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'),
|
||||
|