mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
1240 lines
39 KiB
JavaScript
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
|
|
};
|