From 4e3783c544ef0b706c292d33a7000a4ed5fa44d5 Mon Sep 17 00:00:00 2001 From: Nikhil Mohite Date: Mon, 20 Jun 2022 19:05:26 +0530 Subject: [PATCH] Fixed the following issues in the debugger: 1. If debugger arguments are array debugger sets the null value to parameters. 2. Popup screen is not being closed if debug Package procedure/Function and select Null option. 3. Updated validation for debugger argument of the array type parameter. refs #6132 --- web/pgadmin/tools/debugger/__init__.py | 64 ++++------- .../debugger/static/js/DebuggerConstants.js | 1 + .../debugger/static/js/DebuggerModule.js | 1 - .../static/js/components/DebuggerArgs.ui.js | 69 +++++++++-- .../components/DebuggerArgumentComponent.jsx | 108 +++++++++++------- .../js/components/DebuggerComponent.jsx | 5 + .../debugger/static/js/components/ToolBar.jsx | 13 ++- .../debugger/static/js/debugger_utils.js | 7 +- web/webpack.config.js | 2 +- 9 files changed, 173 insertions(+), 97 deletions(-) diff --git a/web/pgadmin/tools/debugger/__init__.py b/web/pgadmin/tools/debugger/__init__.py index 05ae5625c..275fccb69 100644 --- a/web/pgadmin/tools/debugger/__init__.py +++ b/web/pgadmin/tools/debugger/__init__.py @@ -12,6 +12,7 @@ import simplejson as json import random import re +import copy from flask import url_for, Response, render_template, request, \ current_app @@ -266,31 +267,6 @@ def index(): ) -@blueprint.route("/js/debugger_ui.js") -@login_required -def script_debugger_js(): - """render the debugger UI javascript file""" - return Response( - response=render_template("debugger/js/debugger_ui.js", _=gettext), - status=200, - mimetype=MIMETYPE_APP_JS - ) - - -@blueprint.route("/js/debugger.js") -@login_required -def script_debugger_direct_js(): - """ - Render the javascript file required send and receive the response - from server for debugging - """ - return Response( - response=render_template("debugger/js/debugger.js", _=gettext), - status=200, - mimetype=MIMETYPE_APP_JS - ) - - def execute_dict_search_path(conn, sql, search_path): sql = "SET search_path={0};".format(search_path) + sql status, res = conn.execute_dict(sql) @@ -1110,6 +1086,21 @@ def start_debugger_listener(trans_id): else: arg_type = de_inst.function_data['args_type'].split(",") + debugger_args_values = [] + if de_inst.function_data['args_value']: + debugger_args_values = copy.deepcopy( + de_inst.function_data['args_value']) + for arg in debugger_args_values: + if arg['type'].endswith('[]'): + if arg['value'] and arg['value'] != 'NULL': + val_list = arg['value'][1:-1].split(',') + debugger_args_data = [] + for _val in val_list: + debugger_args_data.append({ + 'value': _val + }) + arg['value'] = debugger_args_data + # Below are two different template to execute and start executer if manager.server_type != 'pg' and manager.version < 90300: str_query = render_template( @@ -1118,7 +1109,7 @@ def start_debugger_listener(trans_id): is_func=de_inst.function_data['is_func'], lan_name=de_inst.function_data['language'], ret_type=de_inst.function_data['return_type'], - data=de_inst.function_data['args_value'], + data=debugger_args_values, arg_type=arg_type, args_mode=arg_mode ) @@ -1128,7 +1119,7 @@ def start_debugger_listener(trans_id): func_name=func_name, is_func=de_inst.function_data['is_func'], ret_type=de_inst.function_data['return_type'], - data=de_inst.function_data['args_value'], + data=debugger_args_values, is_ppas_database=de_inst.function_data['is_ppas_database'] ) @@ -1797,18 +1788,10 @@ def get_array_string(data, i): :return: Array string. """ array_string = '' - if data[i]['value'].__class__.__name__ in ( - 'list') and data[i]['value']: - for k in range(0, len(data[i]['value'])): - if data[i]['value'][k]['value'] is None: - array_string += 'NULL' - else: - array_string += str(data[i]['value'][k]['value']) - if k != (len(data[i]['value']) - 1): - array_string += ',' - elif data[i]['value'].__class__.__name__ in ( - 'list') and not data[i]['value']: - array_string = '' + + if data[i]['value']: + array_string = data[i]['value'][1:-1].split(',') + array_string = ','.join(array_string) else: array_string = data[i]['value'] @@ -1853,7 +1836,8 @@ def set_arguments_sqlite(sid, did, scid, func_id): # handle the Array list sent from the client array_string = '' - if 'value' in data[i]: + if 'is_array_value' in data[i] and 'value' in data[i] and data[i][ + 'is_array_value']: array_string = get_array_string(data, i) # Check if data is already available in database then update the diff --git a/web/pgadmin/tools/debugger/static/js/DebuggerConstants.js b/web/pgadmin/tools/debugger/static/js/DebuggerConstants.js index 64e7c2e37..379409f73 100644 --- a/web/pgadmin/tools/debugger/static/js/DebuggerConstants.js +++ b/web/pgadmin/tools/debugger/static/js/DebuggerConstants.js @@ -14,6 +14,7 @@ export const DEBUGGER_EVENTS = { TRIGGER_CONTINUE_DEBUGGING: 'TRIGGER_CONTINUE_DEBUGGING', TRIGGER_STEPOVER_DEBUGGING: 'TRIGGER_STEPOVER_DEBUGGING', TRIGGER_STEINTO_DEBUGGING: 'TRIGGER_STEINTO_DEBUGGING', + TRIGGER_RESET_LAYOUT: 'TRIGGER_RESET_LAYOUT', SET_STACK: 'SET_STACK', SET_MESSAGES: 'SET_MESSAGES', diff --git a/web/pgadmin/tools/debugger/static/js/DebuggerModule.js b/web/pgadmin/tools/debugger/static/js/DebuggerModule.js index cc5016a6e..56a111979 100644 --- a/web/pgadmin/tools/debugger/static/js/DebuggerModule.js +++ b/web/pgadmin/tools/debugger/static/js/DebuggerModule.js @@ -431,7 +431,6 @@ export default class Debugger { 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', { diff --git a/web/pgadmin/tools/debugger/static/js/components/DebuggerArgs.ui.js b/web/pgadmin/tools/debugger/static/js/components/DebuggerArgs.ui.js index ef889f643..b2c41a954 100644 --- a/web/pgadmin/tools/debugger/static/js/components/DebuggerArgs.ui.js +++ b/web/pgadmin/tools/debugger/static/js/components/DebuggerArgs.ui.js @@ -20,9 +20,9 @@ class ArgementsCollectionSchema extends BaseUISchema { value: undefined, use_default: false, default_value: undefined, + isArrayType: false, + isValid: true }); - - this.isValid = true; } get baseFields() { @@ -63,11 +63,43 @@ class ArgementsCollectionSchema extends BaseUISchema { label: gettext('Value'), type: 'text', cell: (state) => { - let dtype = 'text'; - if(state.type == 'date') { - dtype = 'datetimepicker'; - } else if(state.type == 'numeric') { - dtype = 'numeric'; + let dtype = ''; + state.isArrayType = false; + if(state.type.indexOf('[]') != -1) { + state.isArrayType = true; + dtype = 'text'; + } else { + switch (state.type) { + case 'boolean': + dtype = 'checkbox'; + break; + case 'integer': + case 'smallint': + case 'bigint': + case 'serial': + case 'smallserial': + case 'bigserial': + case 'oid': + case 'cid': + case 'xid': + case 'tid': + dtype = 'int'; + break; + case 'real': + case 'numeric': + case 'double precision': + case 'decimal': + dtype = 'numeric'; + break; + case 'string': + dtype = 'text'; + break; + case 'date': + dtype = 'datetimepicker'; + break; + default: + dtype = 'text'; + } } return { @@ -77,7 +109,7 @@ class ArgementsCollectionSchema extends BaseUISchema { placeholder: gettext('YYYY-MM-DD'), autoOk: true, pickerType: 'Date', ampm: false, }) - } + }, }; }, editable: true, @@ -101,6 +133,27 @@ class ArgementsCollectionSchema extends BaseUISchema { ]; } + isValidArray(val) { + val = val?.trim(); + return !(val != '' && (val.charAt(0) != '{' || val.charAt(val.length - 1) != '}')); + } + + validate(state, setError) { + if(state.isArrayType && state.value && state.value != '') { + let isValid = this.isValidArray(state.value); + state.isValid = isValid; + if(isValid) { + setError('value', null); + } else { + setError('value', 'Arrays must start with "{" and end with "}"'); + return true; + } + } else { + state.isValid = true; + } + return false; + } + } export class DebuggerArgumentSchema extends BaseUISchema { diff --git a/web/pgadmin/tools/debugger/static/js/components/DebuggerArgumentComponent.jsx b/web/pgadmin/tools/debugger/static/js/components/DebuggerArgumentComponent.jsx index f553aa840..29d0a3f20 100644 --- a/web/pgadmin/tools/debugger/static/js/components/DebuggerArgumentComponent.jsx +++ b/web/pgadmin/tools/debugger/static/js/components/DebuggerArgumentComponent.jsx @@ -7,7 +7,6 @@ // ////////////////////////////////////////////////////////////// -import _ from 'lodash'; import PropTypes from 'prop-types'; import React, { useEffect, useRef } from 'react'; @@ -22,6 +21,7 @@ import url_for from 'sources/url_for'; import gettext from 'sources/gettext'; import * as commonUtils from 'sources/utils'; import pgAdmin from 'sources/pgadmin'; +import Loader from 'sources/components/Loader'; import Alertify from 'pgadmin.alertifyjs'; import SchemaView from '../../../../../static/js/SchemaView'; @@ -76,6 +76,7 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug, const debuggerArgsData = useRef([]); const [loadArgs, setLoadArgs] = React.useState(0); const [isDisableDebug, setIsDisableDebug] = React.useState(true); + const [loaderText, setLoaderText] = React.useState(''); const debuggerFinalArgs = useRef([]); const InputArgIds = useRef([]); const wcDocker = window.wcDocker; @@ -246,7 +247,7 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug, } function setFuncObj(funcArgsData, argMode, argType, argName, defValList, isUnnamedParam=false) { - let index, values, vals, funcObj=[]; + let index, values, funcObj=[]; for(const argData of funcArgsData) { index = argData['arg_id']; if (debuggerInfo['proargmodes'] != null && @@ -255,13 +256,8 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug, } values = []; - if (argType[index].indexOf('[]') != -1) { - vals = argData['value'].split(','); - _.each(vals, function (val) { - values.push({ - 'value': val, - }); - }); + if (argType[index].indexOf('[]') != -1 && argData['value'].length > 0) { + values = `{${argData['value']}}`; } else { values = argData['value']; } @@ -451,6 +447,7 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug, }); function clearArgs() { + setLoaderText('Loading...'); setLoadArgs(0); let base_url = null; @@ -486,11 +483,13 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug, /* setTimeout required to get updated argruments as 'Clear All' will delete all saved arguments form sqlite db. */ setTimeout(() => { /* Reload the debugger arguments */ + setLoaderText(''); setLoadArgs(Math.floor(Math.random() * 1000)); /* Disable debug button */ setIsDisableDebug(true); }, 100); }).catch(function (er) { + setLoaderText(''); Notify.alert( gettext('Clear failed'), er.responseJSON.errormsg @@ -500,17 +499,23 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug, function setDebuggingArgs(argsList, argSet) { if (argsList.length === 0) { - debuggerFinalArgs.current.changed.forEach(changedArg => { - argSet.push(changedArg.name); - argsList.push(changedArg); - }); - + // Add all parameters debuggerArgsData.current.aregsCollection.forEach(arg => { if (!argSet.includes(arg.name)) { argSet.push(arg.name); argsList.push(arg); } }); + + // Update values if any change in the args. + debuggerFinalArgs.current.changed.forEach(changedArg => { + argsList.forEach((el, _index) => { + if(changedArg.name == el.name) { + argsList[_index] = changedArg; + } + }); + }); + } } @@ -567,6 +572,7 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug, 'is_expression': arg.expr ? 1 : 0, 'use_default': arg.use_default ? 1 : 0, 'value': arg.value, + 'is_array_value': arg?.isArrayType, }); } else { // Below will format the data to be stored in sqlite database @@ -580,6 +586,7 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug, 'is_expression': arg.expr ? 1 : 0, 'use_default': arg.use_default ? 1 : 0, 'value': debuggerInfo.value, + 'is_array_value': arg?.isArrayType, }); } @@ -684,6 +691,7 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug, function startDebugging() { var self = this; + setLoaderText('Starting debugger.'); /* 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(); @@ -693,7 +701,7 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug, var sqliteFuncArgsList = []; var intCount = 0; - let argsList = debuggerFinalArgs.current?.changed ? [] : debuggerArgsData.current.aregsCollection; + let argsList = []; //debuggerFinalArgs.current?.changed ? [] : debuggerArgsData.current.aregsCollection; let argSet = []; setDebuggingArgs(argsList, argSet); @@ -789,6 +797,7 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug, }) .then(function () {/*This is intentional (SonarQube)*/ }) .catch((error) => { + setLoaderText(''); Notify.alert( gettext('Error occured: '), gettext(error.response.data) @@ -796,9 +805,10 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug, }); /* Close the debugger modal dialog */ props.closeModal(); - + setLoaderText(''); }) .catch(function (error) { + setLoaderText(''); Notify.alert( gettext('Debugger Target Initialization Error'), gettext(error.response.data) @@ -825,7 +835,7 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug, gettext(error.response.data) ); }); - + setLoaderText(''); // Set the new input arguments given by the user during debugging var _Url = url_for('debugger.set_arguments', { 'sid': debuggerInfo.server_id, @@ -840,6 +850,7 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug, }) .then(function () {/*This is intentional (SonarQube)*/ }) .catch(function (error) { + setLoaderText(''); Notify.alert( gettext('Debugger Listener Startup Set Arguments Error'), gettext(error.response.data) @@ -855,34 +866,43 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug, { loadArgs > 0 && - { - let isValid = false; - let skipStep = false; - if('_sessData' in debuggerArgsSchema.current) { - isValid = true; - debuggerArgsSchema.current._sessData.aregsCollection.forEach((data)=> { + <> + + { + let isValid = false; + let skipStep = false; + if('_sessData' in debuggerArgsSchema.current) { + isValid = true; + debuggerArgsSchema.current._sessData.aregsCollection.forEach((data)=> { - if(skipStep) {return;} + 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; - }} - /> + if((data.is_null || data.use_default || data?.value?.toString()?.length > 0) && isValid) { + isValid = true; + } else { + isValid = false; + skipStep = true; + } + + if(!data.isValid) { + isValid = false; + skipStep = true; + } + + }); + } + setIsDisableDebug(!isValid); + debuggerFinalArgs.current = changedData.aregsCollection; + }} + /> + } @@ -895,7 +915,7 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug, { props.closeModal(); }} startIcon={ { props.closeModal(); }} />}> {gettext('Cancel')} - } + } disabled={isDisableDebug} onClick={() => { startDebugging(); }}> {gettext('Debug')} diff --git a/web/pgadmin/tools/debugger/static/js/components/DebuggerComponent.jsx b/web/pgadmin/tools/debugger/static/js/components/DebuggerComponent.jsx index 7d535d3db..291c1256d 100644 --- a/web/pgadmin/tools/debugger/static/js/components/DebuggerComponent.jsx +++ b/web/pgadmin/tools/debugger/static/js/components/DebuggerComponent.jsx @@ -1132,6 +1132,11 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panel, ev LayoutHelper.focus(docker.current, panelId); }); + eventBus.current.registerListener( + DEBUGGER_EVENTS.TRIGGER_RESET_LAYOUT, () => { + docker.current?.resetLayout(); + }); + }, []); diff --git a/web/pgadmin/tools/debugger/static/js/components/ToolBar.jsx b/web/pgadmin/tools/debugger/static/js/components/ToolBar.jsx index 1516412d7..6718a0541 100644 --- a/web/pgadmin/tools/debugger/static/js/components/ToolBar.jsx +++ b/web/pgadmin/tools/debugger/static/js/components/ToolBar.jsx @@ -17,6 +17,7 @@ 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 RotateLeftRoundedIcon from '@material-ui/icons/RotateLeftRounded'; import gettext from 'sources/gettext'; import { shortcut_key } from 'sources/keyboard_shortcuts'; @@ -87,6 +88,10 @@ export function ToolBar() { window.open(url, 'pgadmin_help'); }; + const onResetLayout=() => { + eventBus.fireEvent(DEBUGGER_EVENTS.TRIGGER_RESET_LAYOUT); + }; + useEffect(() => { eventBus.registerListener(DEBUGGER_EVENTS.DISABLE_MENU, () => { setDisableButton('start', true); @@ -123,18 +128,22 @@ export function ToolBar() { accesskey={shortcut_key(preferences?.btn_start)} /> - } + } accesskey={shortcut_key(preferences?.btn_toggle_breakpoint)} onClick={() => { toggleBreakpoint(); }} /> } accesskey={shortcut_key(preferences?.btn_clear_breakpoints)} onClick={() => { clearAllBreakpoint(); }} /> - } disabled={buttonsDisabled['stop']} onClick={() => { stop(); }} + } disabled={buttonsDisabled['stop']} onClick={() => { stop(); }} accesskey={shortcut_key(preferences?.btn_stop)} /> } onClick={onHelpClick} /> + + } + onClick={onResetLayout} /> + ); } diff --git a/web/pgadmin/tools/debugger/static/js/debugger_utils.js b/web/pgadmin/tools/debugger/static/js/debugger_utils.js index dc10091dc..f1f99d1bd 100644 --- a/web/pgadmin/tools/debugger/static/js/debugger_utils.js +++ b/web/pgadmin/tools/debugger/static/js/debugger_utils.js @@ -83,7 +83,12 @@ function getAppropriateLabel(treeInfo) { return treeInfo.trigger_function.label; } else if (treeInfo.trigger) { return treeInfo.trigger.label; - } else { + } else if(treeInfo.edbfunc) { + return treeInfo.edbfunc.label; + } else if(treeInfo.edbproc) { + return treeInfo.edbproc.label; + } + else { return treeInfo.procedure.label; } } diff --git a/web/webpack.config.js b/web/webpack.config.js index 533a2682e..210627608 100644 --- a/web/webpack.config.js +++ b/web/webpack.config.js @@ -378,11 +378,11 @@ module.exports = [{ codemirror: sourceDir + '/bundle/codemirror.js', slickgrid: sourceDir + '/bundle/slickgrid.js', sqleditor: './pgadmin/tools/sqleditor/static/js/index.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', file_utils: './pgadmin/misc/file_manager/static/js/utility.js', + debugger: './pgadmin/tools/debugger/static/js/index.js', 'pgadmin.style': pgadminCssStyles, pgadmin: pgadminScssStyles, style: './pgadmin/static/css/style.css',