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
This commit is contained in:
Nikhil Mohite 2022-06-20 19:05:26 +05:30 committed by Akshay Joshi
parent 2adca6bbc4
commit 4e3783c544
9 changed files with 173 additions and 97 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
<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)=> {
<>
<Loader message={loaderText} />
<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(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;
}}
/>
</>
}
</Box>
<Box className={classes.footer}>
@ -895,7 +915,7 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug,
<DefaultButton className={classes.buttonMargin} onClick={() => { props.closeModal(); }} startIcon={<CloseSharpIcon onClick={() => { props.closeModal(); }} />}>
{gettext('Cancel')}
</DefaultButton>
<PrimaryButton className={classes.buttonMargin} startIcon={<BugReportRoundedIcon className={classes.debugBtn} />}
<PrimaryButton className={classes.buttonMargin} startIcon={<BugReportRoundedIcon className={classes.debugBtn} />}
disabled={isDisableDebug}
onClick={() => { startDebugging(); }}>
{gettext('Debug')}

View File

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

View File

@ -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)} />
</PgButtonGroup>
<PgButtonGroup size="small">
<PgIconButton data-test='toggle-breakpoint' title={gettext('Toggle breakpoint')} disabled={buttonsDisabled['toggle-breakpoint']} icon={<FiberManualRecordIcon />}
<PgIconButton data-test='toggle-breakpoint' title={gettext('Toggle breakpoint')} disabled={buttonsDisabled['toggle-breakpoint']} icon={<FiberManualRecordIcon style={{height: '2rem'}} />}
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(); }}
<PgIconButton data-test='stop-debugger' title={gettext('Stop')} icon={<StopIcon style={{height: '2rem'}} />} 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>
<PgButtonGroup size="small" variant="text" style={{marginLeft: 'auto'}}>
<PgIconButton title={gettext('Reset layout')} icon={<RotateLeftRoundedIcon />}
onClick={onResetLayout} />
</PgButtonGroup>
</Box>
);
}

View File

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

View File

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