pgadmin4/web/pgadmin/tools/debugger/static/js/components/DebuggerComponent.jsx
2022-06-27 16:34:04 +05:30

1240 lines
39 KiB
JavaScript

/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import { Box } from '@material-ui/core';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import gettext from 'sources/gettext';
import url_for from 'sources/url_for';
import Loader from 'sources/components/Loader';
import Layout, { LayoutHelper } from '../../../../../static/js/helpers/Layout';
import EventBus from '../../../../../static/js/helpers/EventBus';
import Theme from '../../../../../static/js/Theme';
import getApiInstance from '../../../../../static/js/api_instance';
import Notify from '../../../../../static/js/helpers/Notifier';
import { evalFunc } from '../../../../../static/js/utils';
import { PANELS, DEBUGGER_EVENTS, MENUS } from '../DebuggerConstants';
import { retrieveNodeName } from '../../../../sqleditor/static/js/show_view_data';
import { useModal } from '../../../../../static/js/helpers/ModalProvider';
import DebuggerEditor from './DebuggerEditor';
import DebuggerMessages from './DebuggerMessages';
import { ToolBar } from './ToolBar';
import { Stack } from './Stack';
import { Results } from './Results';
import { LocalVariablesAndParams } from './LocalVariablesAndParams';
export const DebuggerContext = React.createContext();
export const DebuggerEventsContext = React.createContext();
export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panel, eventBusObj, layout, params }) {
const savedLayout = layout;
const containerRef = React.useRef(null);
const docker = useRef(null);
const api = getApiInstance();
const modal = useModal();
const eventBus = useRef(eventBusObj || (new EventBus()));
const [loaderText, setLoaderText] = React.useState('');
const editor = useRef(null);
let timeOut = null;
const [qtState, _setQtState] = useState({
preferences: {
browser: {}, debugger: {},
},
is_new_tab: window.location == window.parent?.location,
params: {
...params,
node_name: retrieveNodeName(selectedNodeInfo),
}
});
const setQtState = (state) => {
_setQtState((prev) => ({ ...prev, ...evalFunc(null, state, prev) }));
};
const disableToolbarButtons = () => {
eventBus.current.fireEvent(DEBUGGER_EVENTS.DISABLE_MENU);
eventBus.current.fireEvent(DEBUGGER_EVENTS.GET_TOOL_BAR_BUTTON_STATUS, {disabled: true});
};
const enableToolbarButtons = (key = null) => {
if (key) {
eventBus.current.fireEvent(DEBUGGER_EVENTS.ENABLE_SPECIFIC_MENU, key);
} else {
eventBus.current.fireEvent(DEBUGGER_EVENTS.ENABLE_MENU);
}
eventBus.current.fireEvent(DEBUGGER_EVENTS.GET_TOOL_BAR_BUTTON_STATUS, {disabled: false});
};
const reflectPreferences = useCallback(() => {
setQtState({
preferences: {
browser: pgAdmin.Browser.get_preferences_for_module('browser'),
debugger: pgAdmin.Browser.get_preferences_for_module('debugger'),
}
});
}, []);
// Function to get the breakpoint information from the server
const getBreakpointInformation = (transId, callBackFunc) => {
var result = '';
// Make ajax call to listen the database message
var baseUrl = url_for('debugger.execute_query', {
'trans_id': transId,
'query_type': 'get_breakpoints',
});
api({
url: baseUrl,
method: 'GET',
})
.then(function (res) {
if (res.data.data.status === 'Success') {
result = res.data.data.result;
if (callBackFunc) {
callBackFunc(result);
}
} else if (res.data.data.status === 'NotConnected') {
raiseFetchingBreakpointError();
}
})
.catch(raiseFetchingBreakpointError);
return result;
};
const clearAllBreakpoint = (transId) => {
var clearBreakpoint = (br_list) => {
// If there is no break point to clear then we should return from here.
if ((br_list.length == 1) && (br_list[0].linenumber == -1))
return;
disableToolbarButtons();
var breakpoint_list = getBreakpointList(br_list);
// Make ajax call to listen the database message
var baseUrl = url_for('debugger.clear_all_breakpoint', {
'trans_id': transId,
});
api({
url: baseUrl,
method: 'POST',
data: {
'breakpoint_list': breakpoint_list.lenght > 0 ? breakpoint_list.join() : null,
},
})
.then(function (res) {
if (res.data.data.status) {
executeQuery(transId);
setUnsetBreakpoint(res, breakpoint_list);
}
enableToolbarButtons();
})
.catch(raiseClearBrekpointError);
};
getBreakpointInformation(transId, clearBreakpoint);
};
const raiseJSONError = (xhr) => {
try {
var err = xhr.response.data;
if (err.success == 0) {
Notify.alert(gettext('Debugger Error'), err.errormsg, () => {
if(panel) {
panel.close();
}
});
}
} catch (e) {
alert(xhr);
Notify.alert(
gettext('Debugger Error'),
gettext('Error while starting debugging listener.')
);
}
};
const raisePollingError = () => {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while polling result.')
);
};
const raiseClearBrekpointError = () => {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while clearing all breakpoint.')
);
};
const raiseFetchingBreakpointError = () => {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while fetching breakpoint information.')
);
};
const messages = (transId) => {
// Make ajax call to listen the database message
var baseUrl = url_for('debugger.messages', {
'trans_id': transId,
});
api({
url: baseUrl,
method: 'GET',
})
.then(function (res) {
if (res.data.data.status === 'Success') {
enableToolbarButtons();
// If status is Success then find the port number to attach the executer.
startExecution(transId, res.data.data.result);
} else if (res.data.data.status === 'Busy') {
// If status is Busy then poll the result by recursive call to the poll function
messages(transId);
} else if (res.data.data.status === 'NotConnected') {
Notify.alert(
gettext('Not connected to server or connection with the server has been closed.'),
res.data.result
);
}
})
.catch(function () {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while fetching messages information.')
);
});
};
const startExecution = (transId, port_num) => {
// Make ajax call to listen the database message
var baseUrl = url_for(
'debugger.start_execution', {
'trans_id': transId,
'port_num': port_num,
});
api({
url: baseUrl,
method: 'GET',
})
.then(function (res) {
if (res.data.data.status === 'Success') {
// If status is Success then find the port number to attach the executer.
executeQuery(transId);
} else if (res.data.data.status === 'NotConnected') {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while starting debugging session.')
);
}
})
.catch(function () {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while starting debugging session.')
);
});
};
const executeQuery = (transId) => {
// Make ajax call to listen the database message
var baseUrl = url_for(
'debugger.execute_query', {
'trans_id': transId,
'query_type': 'wait_for_breakpoint',
});
api({
url: baseUrl,
method: 'GET',
})
.then(function (res) {
if (res.data.data.status === 'Success') {
// set the return code to the code editor text area
if (
res.data.data.result[0].src != null &&
res.data.data.result[0].linenumber != null
) {
editor.current.setValue(res.data.data.result[0].src);
setActiveLine(res.data.data.result[0].linenumber - 2);
}
// Call function to create and update Stack information ....
getStackInformation(transId);
if (params.directDebugger.debug_type) {
pollEndExecutionResult(transId);
}
} else if (res.data.data.status === 'NotConnected') {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while executing requested debugging information.')
);
}
})
.catch(function () {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while executing requested debugging information.')
);
});
};
const setActiveLine = (lineNo) => {
/* If lineNo sent, remove active line */
if (lineNo && editor.current.activeLineNo) {
editor.current.removeLineClass(
editor.current.activeLineNo, 'wrap', 'CodeMirror-activeline-background'
);
}
/* If lineNo not sent, set it to active line */
if (!lineNo && editor.current.activeLineNo) {
lineNo = editor.current.activeLineNo;
}
/* Set new active line only if positive */
if (lineNo > 0) {
editor.current.activeLineNo = lineNo;
editor.current.addLineClass(
editor.current.activeLineNo, 'wrap', 'CodeMirror-activeline-background'
);
/* centerOnLine is codemirror extension in bundle/codemirror.js */
editor.current.centerOnLine(editor.current.activeLineNo);
}
};
const selectFrame = (frameId) => {
// Make ajax call to listen the database message
var baseUrl = url_for('debugger.select_frame', {
'trans_id': params.transId,
'frame_id': frameId,
});
api({
url: baseUrl,
method: 'GET',
})
.then(function (res) {
if (res.data.data.status) {
editor.current.setValue(res.data.data.result[0].src);
updateBreakpoint(params.transId, true);
setActiveLine(res.data.data.result[0].linenumber - 2);
}
})
.catch(function () {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while selecting frame.')
);
});
};
useEffect(() => {
var baseUrl = '';
if (params.transId != undefined && !params.directDebugger.debug_type) {
// Make ajax call to execute the and start the target for execution
baseUrl = url_for('debugger.start_listener', {
'trans_id': params.transId,
});
api({
url: baseUrl,
method: 'GET',
})
.then(function (res) {
if (res.data.data.status) {
enableToolbarButtons();
pollResult(params.transId);
}
})
.catch(raiseJSONError);
} else if (params.transId != undefined) {
// Make api call to execute the and start the target for execution
baseUrl = url_for('debugger.start_listener', {
'trans_id': params.transId,
});
api({
url: baseUrl,
method: 'GET',
})
.then(function (res) {
if (res.data.data.status) {
messages(params.transId);
}
})
.catch(raiseJSONError);
}
}, []);
const setUnsetBreakpoint = (res, breakpoint_list) => {
if (res.data.data.status) {
for (let brk_val of breakpoint_list) {
var info = editor.current.lineInfo((brk_val - 1));
if (info) {
if (info.gutterMarkers != undefined) {
editor.current.setGutterMarker((brk_val - 1), 'breakpoints', null);
}
}
}
}
};
const triggerClearBreakpoint = () => {
var clearBreakpoint = (br_list) => {
// If there is no break point to clear then we should return from here.
if ((br_list.length == 1) && (br_list[0].linenumber == -1))
return;
disableToolbarButtons();
var breakpoint_list = getBreakpointList(br_list);
// Make ajax call to listen the database message
var _baseUrl = url_for('debugger.clear_all_breakpoint', {
'trans_id': params.transId,
});
api({
url: _baseUrl,
method: 'POST',
data: {
'breakpoint_list': breakpoint_list.join(),
},
})
.then(function (res) {
setUnsetBreakpoint(res, breakpoint_list);
enableToolbarButtons();
})
.catch(raiseClearBrekpointError);
};
// Make ajax call to listen the database message
var baseUrl = url_for('debugger.execute_query', {
'trans_id': params.transId,
'query_type': 'get_breakpoints',
});
api({
url: baseUrl,
method: 'GET',
})
.then(function (res) {
if (res.data.data.status === 'Success') {
let result = res.data.data.result;
clearBreakpoint(result);
} else if (res.data.data.status === 'NotConnected') {
raiseFetchingBreakpointError();
}
})
.catch(raiseFetchingBreakpointError);
};
const debuggerMark = () => {
var marker = document.createElement('div');
marker.style.color = '#822';
marker.innerHTML = '●';
return marker;
};
const triggerToggleBreakpoint = () => {
disableToolbarButtons();
var info = editor.current.lineInfo(editor.current.activeLineNo);
var baseUrl = '';
// If gutterMarker is undefined that means there is no marker defined previously
// So we need to set the breakpoint command here...
if (info.gutterMarkers == undefined) {
baseUrl = url_for('debugger.set_breakpoint', {
'trans_id': params.transId,
'line_no': editor.current.activeLineNo + 1,
'set_type': '1',
});
} else {
baseUrl = url_for('debugger.set_breakpoint', {
'trans_id': params.transId,
'line_no': editor.current.activeLineNo + 1,
'set_type': '0',
});
}
api({
url: baseUrl,
method: 'GET',
})
.then(function (res) {
if (res.data.data.status) {
// Call function to create and update local variables ....
var info_local = editor.current.lineInfo(editor.current.activeLineNo);
if (info_local.gutterMarkers != undefined) {
editor.current.setGutterMarker(editor.current.activeLineNo, 'breakpoints', null);
} else {
editor.current.setGutterMarker(editor.current.activeLineNo, 'breakpoints', debuggerMark());
}
enableToolbarButtons();
} else if (res.data.status === 'NotConnected') {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while toggling breakpoint.')
);
}
})
.catch(function () {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while toggling breakpoint.')
);
});
};
const stopDebugging = () => {
disableToolbarButtons();
// Make ajax call to listen the database message
var baseUrl = url_for(
'debugger.execute_query', {
'trans_id': params.transId,
'query_type': 'abort_target',
});
api({
url: baseUrl,
method: 'GET',
})
.then(function (res) {
if (res.data.data.status) {
// Call function to create and update local variables ....
setActiveLine(-1);
params.directDebugger.direct_execution_completed = true;
params.directDebugger.is_user_aborted_debugging = true;
// Stop further pooling
params.directDebugger.is_polling_required = false;
// Restarting debugging in the same transaction do not work
// We will give same behaviour as pgAdmin3 and disable all buttons
enableToolbarButtons(MENUS.CONTINUE);
// Set the Alertify message to inform the user that execution
// is completed.
Notify.success(res.data.info, 3000);
} else if (res.data.data.status === 'NotConnected') {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while executing stop in debugging session.')
);
}
})
.catch(function () {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while executing stop in debugging session.')
);
});
};
const restart = (transId) => {
var baseUrl = url_for('debugger.restart', { 'trans_id': transId });
disableToolbarButtons();
api({
url: baseUrl,
})
.then(function (res) {
// Restart the same function debugging with previous arguments
var restart_dbg = res.data.data.restart_debug ? 1 : 0;
// Start pooling again
params.directDebugger.polling_timeout_idle = false;
params.directDebugger.is_polling_required = true;
pollResult(transId);
if (restart_dbg) {
params.directDebugger.debug_restarted = true;
}
/*
Need to check if restart debugging really require to open the input
dialog? If yes then we will get the previous arguments from database
and populate the input dialog, If no then we should directly start the
listener.
*/
if (res.data.data.result.require_input) {
params.funcArgsInstance.show(res.data.data.result, restart_dbg);
} else {
// Debugging of void function is started again so we need to start
// the listener again
var base_url = url_for('debugger.start_listener', {
'trans_id': params.transId,
});
api({
url: base_url,
method: 'GET',
})
.then(function () {
if (params.directDebugger.debug_type) {
pollEndExecutionResult(params.transId);
}
})
.catch(raisePollingError);
}
})
.catch(raiseJSONError);
};
const pollEndExecuteError = (res ) => {
params.directDebugger.direct_execution_completed = true;
setActiveLine(-1);
//Set the notification message to inform the user that execution is
// completed with error.
if (!params.directDebugger.is_user_aborted_debugging) {
Notify.error(res.data.info, 3000);
}
// Update the message tab of the debugger
updateMessages(res.data.data.status_message);
eventBus.current.fireEvent(DEBUGGER_EVENTS.FOCUS_PANEL, PANELS.MESSAGES);
disableToolbarButtons();
// If debugging is stopped by user then do not enable
// continue/restart button
if (!params.directDebugger.is_user_aborted_debugging) {
enableToolbarButtons(MENUS.CONTINUE);
params.directDebugger.is_user_aborted_debugging = false;
}
// Stop further pooling
params.directDebugger.is_polling_required = false;
};
const updateResultAnsMessages = (res) => {
if (res.data.data.result != null) {
setActiveLine(-1);
// Call function to update results information and set result panel focus
eventBus.current.fireEvent(DEBUGGER_EVENTS.SET_RESULTS, res.data.data.col_info, res.data.data.result);
eventBus.current.fireEvent(DEBUGGER_EVENTS.FOCUS_PANEL, PANELS.RESULTS);
params.directDebugger.direct_execution_completed = true;
params.directDebugger.polling_timeout_idle = true;
//Set the alertify message to inform the user that execution is completed.
Notify.success(res.data.info, 3000);
// Update the message tab of the debugger
updateMessages(res.data.data.status_message);
// Execution completed so disable the buttons other than
// "Continue/Start" button because user can still
// start the same execution again.
disableToolbarButtons();
// Stop further pooling
params.directDebugger.is_polling_required = false;
}
};
/*
For the direct debugging, we need to check weather the functions execution
is completed or not. After completion of the debugging, we will stop polling
the result until new execution starts.
*/
const pollEndExecutionResult = (transId) => {
// Do we need to poll?
if (!params.directDebugger.is_polling_required) {
return;
}
// Make ajax call to listen the database message
var baseUrl = url_for('debugger.poll_end_execution_result', {
'trans_id': transId,
}),
poll_end_timeout;
/*
* During the execution we should poll the result in minimum seconds
* but once the execution is completed and wait for the another
* debugging session then we should decrease the polling frequency.
*/
if (params.directDebugger.polling_timeout_idle) {
// Poll the result to check that execution is completed or not
// after 1200 ms
poll_end_timeout = 1200;
} else {
// Poll the result to check that execution is completed or not
// after 350 ms
poll_end_timeout = 250;
}
timeOut = setTimeout(
function () {
api({
url: baseUrl,
method: 'GET',
})
.then(function (res) {
if (res.data.data.status === 'Success') {
if (res.data.data.result == undefined) {
/*
"result" is undefined only in case of EDB procedure.
As Once the EDB procedure execution is completed then we are
not getting any result so we need ignore the result.
*/
setActiveLine(-1);
params.directDebugger.direct_execution_completed = true;
params.directDebugger.polling_timeout_idle = true;
//Set the alertify message to inform the user that execution is completed.
Notify.success(res.data.info, 3000);
// Update the message tab of the debugger
updateMessages(res.data.data.status_message);
// Execution completed so disable the buttons other than
// "Continue/Start" button because user can still
// start the same execution again.
disableToolbarButtons();
// Stop further polling
params.directDebugger.is_polling_required = false;
} else {
updateResultAnsMessages(res);
}
} else if (res.data.data.status === 'Busy') {
// If status is Busy then poll the result by recursive call to
// the poll function
pollEndExecutionResult(transId);
// Update the message tab of the debugger
updateMessages(res.data.data.status_message);
} else if (res.data.status === 'NotConnected') {
Notify.alert(
gettext('Debugger poll end execution error'),
res.data.result
);
} else if (res.data.data.status === 'ERROR') {
pollEndExecuteError(res);
}
})
.catch(raisePollingError);
}, poll_end_timeout);
};
// This function will update messages tab
const updateMessages = (msg) => {
if(msg) {
// Call function to update messages information
eventBus.current.fireEvent(DEBUGGER_EVENTS.SET_MESSAGES, msg, true);
}
};
const continueDebugger = () => {
disableToolbarButtons();
//Check first if previous execution was completed or not
if (params.directDebugger.direct_execution_completed &&
params.directDebugger.direct_execution_completed == params.directDebugger.polling_timeout_idle) {
restart(params.transId);
} else {
// Make ajax call to listen the database message
var baseUrl = url_for('debugger.execute_query', {
'trans_id': params.transId,
'query_type': 'continue',
});
api({
url: baseUrl,
method: 'GET',
})
.then(function (res) {
if (res.data.data.status) {
pollResult(params.transId);
} else {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while executing continue in debugging session.')
);
}
})
.catch(function () {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while executing continue in debugging session.')
);
});
}
};
const stepOver = () => {
disableToolbarButtons();
// Make ajax call to listen the database message
var baseUrl = url_for('debugger.execute_query', {
'trans_id': params.transId,
'query_type': 'step_over',
});
api({
url: baseUrl,
method: 'GET',
})
.then(function (res) {
if (res.data.data.status) {
pollResult(params.transId);
} else {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while executing step over in debugging session.')
);
}
})
.catch(function () {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while executing step over in debugging session.')
);
});
};
const getBreakpointList = (br_list) => {
var breakpoint_list = new Array();
for (let val of br_list) {
if (val.linenumber != -1) {
breakpoint_list.push(val.linenumber);
}
}
return breakpoint_list;
};
// Function to get the latest breakpoint information and update the
// gutters of codemirror
const updateBreakpoint = (transId, updateLocalVar = false) => {
var callBackFunc = (br_list) => {
// If there is no break point to clear then we should return from here.
if ((br_list.length == 1) && (br_list[0].linenumber == -1))
return;
var breakpoint_list = getBreakpointList(br_list);
for (let brk_val of breakpoint_list) {
var info = editor.current.lineInfo((brk_val - 1));
if (info.gutterMarkers != undefined) {
editor.current.setGutterMarker((brk_val - 1), 'breakpoints', null);
} else {
editor.current.setGutterMarker((brk_val - 1), 'breakpoints', debuggerMark());
}
}
if (updateLocalVar) {
// Call function to create and update local variables ....
getLocalVariables(params.transId);
}
};
getBreakpointInformation(transId, callBackFunc);
};
// Get the local variable information of the functions and update the grid
const getLocalVariables = (transId) => {
// Make ajax call to listen the database message
var baseUrl = url_for(
'debugger.execute_query', {
'trans_id': transId,
'query_type': 'get_variables',
});
api({
url: baseUrl,
method: 'GET',
})
.then(function (res) {
if (res.data.data.status === 'Success') {
// Call function to update local variables
let variablesResult = res.data.data.result.filter((lvar) => {
return lvar.varclass == 'L';
});
eventBus.current.fireEvent(DEBUGGER_EVENTS.SET_LOCAL_VARIABLES, variablesResult);
let parametersResult = res.data.data.result.filter((lvar) => {
return lvar.varclass == 'A';
});
// update Parameter panel data.
eventBus.current.fireEvent(DEBUGGER_EVENTS.SET_PARAMETERS, parametersResult);
// If debug function is restarted then again start listener to
// read the updated messages.
if (params.directDebugger.debug_restarted) {
if (params.directDebugger.debug_type) {
pollEndExecutionResult(transId);
}
params.directDebugger.debug_restarted = false;
}
} else if (res.data.data.status === 'NotConnected') {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while fetching variable information.')
);
}
})
.catch(function () {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while fetching variable information.')
);
});
};
const getStackInformation = (transId) => {
// Make ajax call to listen the database message
var baseUrl = url_for(
'debugger.execute_query', {
'trans_id': transId,
'query_type': 'get_stack_info',
});
api({
url: baseUrl,
method: 'GET',
})
.then(function (res) {
if (res.data.data.status === 'Success') {
// Call function to update stack information
eventBus.current.fireEvent(DEBUGGER_EVENTS.SET_STACK, res.data.data.result);
// Call function to create and update stack information
getLocalVariables(params.transId);
} else if (res.data.data.status === 'NotConnected') {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while fetching stack information.')
);
}
})
.catch(function () {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while fetching stack information.')
);
});
};
const updateBreakpointInfo = (res, transId) => {
if (res.data.data.result[0].src != editor.current.getValue()) {
editor.current.setValue(res.data.data.result[0].src);
try {
updateBreakpoint(transId);
} catch (err) {
Notify.alert(gettext('Error in update'), err);
}
}
};
const updateInfo = (res, transId) => {
if (!params.directDebugger.debug_type && !params.directDebugger.first_time_indirect_debug) {
setLoaderText('');
setActiveLine(-1);
clearAllBreakpoint(transId);
params.directDebugger.first_time_indirect_debug = true;
params.directDebugger.polling_timeout_idle = false;
} else {
params.directDebugger.polling_timeout_idle = false;
setLoaderText('');
// If the source is really changed then only update the breakpoint information
updateBreakpointInfo(res, transId);
setActiveLine(res.data.data.result[0].linenumber - 2);
// Update the stack, local variables and parameters information
setTimeout(function () {
getStackInformation(transId);
}, 10);
}
};
const checkDebuggerStatus = (transId) => {
// If status is Busy then poll the result by recursive call to the poll function
if (!params.directDebugger.debug_type) {
setLoaderText(gettext('Waiting for another session to invoke the target...'));
// As we are waiting for another session to invoke the target,disable all the buttons
disableToolbarButtons();
params.directDebugger.first_time_indirect_debug = false;
pollResult(transId);
} else {
pollResult(transId);
}
};
/* poll the actual result after user has executed the "continue", "step-into",
"step-over" actions and get the other updated information from the server.
*/
const pollResult = (transId) => {
// Do we need to poll?
if (!params.directDebugger.is_polling_required) {
return;
}
// Make ajax call to listen the database message
var baseUrl = url_for('debugger.poll_result', {
'trans_id': transId,
}),
poll_timeout;
/*
During the execution we should poll the result in minimum seconds but
once the execution is completed and wait for the another debugging
session then we should decrease the polling frequency.
*/
if (params.directDebugger.polling_timeout_idle) {
// Poll the result after 1 second
poll_timeout = 1000;
} else {
// Poll the result after 200 ms
poll_timeout = 200;
}
setTimeout(
function () {
api({
url: baseUrl,
method: 'GET',
beforeSend: function (xhr) {
xhr.setRequestHeader(
pgAdmin.csrf_token_header, pgAdmin.csrf_token
);
},
})
.then(function (res) {
if (res.data.data.status === 'Success') {
// If no result then poll again to wait for results.
if (res.data.data.result == null || res.data.data.result.length == 0) {
pollResult(transId);
} else {
updateInfo(res, transId);
// Enable all the buttons as we got the results
enableToolbarButtons();
}
} else if (res.data.data.status === 'Busy') {
params.directDebugger.polling_timeout_idle = true;
checkDebuggerStatus(transId);
} else if (res.data.data.status === 'NotConnected') {
Notify.alert(
gettext('Debugger Error: poll_result'),
gettext('Error while polling result.')
);
}
})
.catch(raisePollingError);
}, poll_timeout);
};
const stepInto = () => {
disableToolbarButtons();
// Make ajax call to listen the database message
var baseUrl = url_for('debugger.execute_query', {
'trans_id': params.transId,
'query_type': 'step_into',
});
api({
url: baseUrl,
method: 'GET',
})
.then(function (res) {
if (res.data.data.status) {
pollResult(params.transId);
} else {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while executing step into in debugging session.')
);
}
})
.catch(function () {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while executing step into in debugging session.')
);
});
};
const onChangesLocalVarParameters = (data) => {
// Make api call to listen the database message
var baseUrl = url_for('debugger.deposit_value', {
'trans_id': params.transId,
});
api({
url: baseUrl,
method: 'POST',
data: data,
})
.then(function (res) {
if (res.data.data.status) {
// Get the updated variables value
getLocalVariables(params.transId);
// Show the message to the user that deposit value is success or failure
if (res.data.data.result) {
Notify.success(res.data.data.info, 3000);
} else {
Notify.error(res.data.data.info, 3000);
}
}
})
.catch(function () {
Notify.alert(
gettext('Debugger Error'),
gettext('Error while depositing variable value.')
);
});
};
useEffect(() => {
// Register all eventes for debugger.
eventBus.current.registerListener(
DEBUGGER_EVENTS.TRIGGER_CLEAR_ALL_BREAKPOINTS, triggerClearBreakpoint);
eventBus.current.registerListener(
DEBUGGER_EVENTS.TRIGGER_TOGGLE_BREAKPOINTS, triggerToggleBreakpoint);
eventBus.current.registerListener(
DEBUGGER_EVENTS.TRIGGER_STOP_DEBUGGING, stopDebugging);
eventBus.current.registerListener(
DEBUGGER_EVENTS.TRIGGER_CONTINUE_DEBUGGING, continueDebugger);
eventBus.current.registerListener(
DEBUGGER_EVENTS.TRIGGER_STEPOVER_DEBUGGING, stepOver);
eventBus.current.registerListener(
DEBUGGER_EVENTS.TRIGGER_STEINTO_DEBUGGING, stepInto);
eventBus.current.registerListener(
DEBUGGER_EVENTS.SET_LOCAL_VARIABLE_VALUE_CHANGE, onChangesLocalVarParameters);
eventBus.current.registerListener(
DEBUGGER_EVENTS.SET_PARAMETERS_VALUE_CHANGE, onChangesLocalVarParameters);
eventBus.current.registerListener(DEBUGGER_EVENTS.SET_FRAME, (frameId) => {
selectFrame(frameId);
});
eventBus.current.registerListener(DEBUGGER_EVENTS.FOCUS_PANEL, (panelId) => {
LayoutHelper.focus(docker.current, panelId);
});
eventBus.current.registerListener(
DEBUGGER_EVENTS.TRIGGER_RESET_LAYOUT, () => {
docker.current?.resetLayout();
});
}, []);
useEffect(() => {
reflectPreferences();
pgAdmin.Browser.onPreferencesChange('debugger', function () {
reflectPreferences();
});
// /* Clear the timeout if unmounted */
return () => {
clearTimeout(timeOut);
};
}, []);
const DebuggerContextValue = React.useMemo(() => ({
docker: docker.current,
api: api,
modal: modal,
params: qtState.params,
preferences: qtState.preferences,
}), [qtState.params, qtState.preferences]);
// Define the debugger layout components such as DebuggerEditor to show queries and
let defaultLayout = {
dockbox: {
mode: 'vertical',
children: [
{
mode: 'horizontal',
children: [
{
tabs: [
LayoutHelper.getPanel({
id: PANELS.DEBUGGER, title: gettext('Debugger'), content: <DebuggerEditor getEditor={(edRef) => {
editor.current = edRef;
}} params={{ transId: params.transId, debuggerDirect: params.directDebugger }} />
})
],
}
]
},
{
mode: 'horizontal',
children: [
{
tabs: [
LayoutHelper.getPanel({
id: PANELS.PARAMETERS, title: gettext('Parameters'), content: <LocalVariablesAndParams type={1} />,
}),
LayoutHelper.getPanel({
id: PANELS.LOCAL_VARIABLES, title: gettext('Local Variables'), content: <LocalVariablesAndParams type={2} />,
}),
LayoutHelper.getPanel({
id: PANELS.MESSAGES, title: gettext('Messages'), content: <DebuggerMessages />,
}),
LayoutHelper.getPanel({
id: PANELS.RESULTS, title: gettext('Result'), content: <Results />,
}),
LayoutHelper.getPanel({
id: PANELS.STACK, title: gettext('Stack'), content: <Stack />,
}),
],
}
]
},
]
},
};
return (
<DebuggerContext.Provider value={DebuggerContextValue}>
<DebuggerEventsContext.Provider value={eventBus.current}>
<Theme>
<Loader message={loaderText} />
<Box width="100%" height="100%" display="flex" flexDirection="column" flexGrow="1" tabIndex="0" ref={containerRef}>
<ToolBar
containerRef={containerRef}
/>
<Layout
getLayoutInstance={(obj) => docker.current = obj}
defaultLayout={defaultLayout}
layoutId="Debugger/Layout"
savedLayout={savedLayout}
/>
</Box>
</Theme>
</DebuggerEventsContext.Provider>
</DebuggerContext.Provider>
);
}
DebuggerComponent.propTypes = {
pgAdmin: PropTypes.object,
selectedNodeInfo: PropTypes.object,
panel: PropTypes.object,
eventBusObj: PropTypes.object,
layout: PropTypes.string,
params: PropTypes.object
};