diff --git a/docs/en_US/editgrid.rst b/docs/en_US/editgrid.rst
index c7a5e5fb73f..663c75fb352 100644
--- a/docs/en_US/editgrid.rst
+++ b/docs/en_US/editgrid.rst
@@ -222,3 +222,18 @@ To delete a row from the grid, click the trash icon.
:maxdepth: 2
viewdata_filter
+
+
+
+Promote View/Edit Data to Query Tool
+************************************
+
+A View/Edit Data tab can be converted to a Query Tool Tab just by editing the query. Once you start editing, it will ask if you really want to move away from View/Edit.
+
+.. image:: images/promote_view_edit_data_warning.png
+ :alt: Promote View/Edit Data tab to Query Tool tab warning
+ :align: center
+
+You can disable the dialog by selecting the "Don't Ask again" checkbox. If you wish to resume the confirmation dialog, you can do it from "Prefrences -> Query Tool -> Editor -> Show View/Edit Data Promotion Warning?"
+
+Once you chose to continue, you won't be able to use the features of View/Edit mode like the filter and sorting options, limit, etc. It is a one-way conversion. It will be a query tool now.
\ No newline at end of file
diff --git a/docs/en_US/images/promote_view_edit_data_warning.png b/docs/en_US/images/promote_view_edit_data_warning.png
new file mode 100644
index 00000000000..6b3a43dd1f2
Binary files /dev/null and b/docs/en_US/images/promote_view_edit_data_warning.png differ
diff --git a/web/pgadmin/preferences/__init__.py b/web/pgadmin/preferences/__init__.py
index d45bdb8d961..fd4229208ff 100644
--- a/web/pgadmin/preferences/__init__.py
+++ b/web/pgadmin/preferences/__init__.py
@@ -20,7 +20,7 @@
from pgadmin.utils import PgAdminModule
from pgadmin.utils.ajax import success_return, \
make_response as ajax_response, internal_server_error
-from pgadmin.utils.menu import MenuItem
+from pgadmin.utils.ajax import make_json_response
from pgadmin.utils.preferences import Preferences
from pgadmin.utils.constants import MIMETYPE_APP_JS
from pgadmin.browser.server_groups import ServerGroupModule as sgm
@@ -47,7 +47,9 @@ def get_exposed_url_endpoints(self):
return [
'preferences.index',
'preferences.get_by_name',
- 'preferences.get_all'
+ 'preferences.get_all',
+ 'preferences.update_pref'
+
]
@@ -245,3 +247,30 @@ def save():
**domain)
return response
+
+
+@blueprint.route("/update", methods=["PUT"], endpoint="update_pref")
+@login_required
+def update():
+ """
+ Update a specific preference.
+ """
+ pref_data = get_data()
+ pref_data = json.loads(pref_data['pref_data'])
+
+ for data in pref_data:
+ if data['name'] in ['vw_edt_tab_title_placeholder',
+ 'qt_tab_title_placeholder',
+ 'debugger_tab_title_placeholder'] \
+ and data['value'].isspace():
+ data['value'] = ''
+
+ pref_module = Preferences.module(data['module'])
+ pref = pref_module.preference(data['name'])
+ # set user preferences
+ pref.set(data['value'])
+
+ return make_json_response(
+ data={'data': 'Success'},
+ status=200
+ )
diff --git a/web/pgadmin/preferences/static/js/store.js b/web/pgadmin/preferences/static/js/store.js
index f94c5dcb4c2..7804a0ce898 100644
--- a/web/pgadmin/preferences/static/js/store.js
+++ b/web/pgadmin/preferences/static/js/store.js
@@ -13,6 +13,12 @@ const usePreferences = create((set, get)=>({
get().data, {'module': module, 'name': preference}
);
},
+ setPreference: (data)=> {
+ // Update Preferences and then refresh cache.
+ getApiInstance().put(url_for('preferences.update_pref'), data).then(()=> {
+ preferenceChangeBroadcast.postMessage('refresh');
+ });
+ },
getPreferencesForModule: function(module) {
let preferences = {};
_.forEach(
@@ -62,6 +68,9 @@ export function setupPreferenceBroadcast() {
if(ev.data == 'sync') {
broadcast(usePreferences.getState());
}
+ if(ev.data == 'refresh') {
+ usePreferences.getState().cache();
+ }
};
}
diff --git a/web/pgadmin/preferences/tests/preferences_test_data.json b/web/pgadmin/preferences/tests/preferences_test_data.json
index a4327dd4dda..294d56c8a47 100644
--- a/web/pgadmin/preferences/tests/preferences_test_data.json
+++ b/web/pgadmin/preferences/tests/preferences_test_data.json
@@ -18,10 +18,22 @@
"url": "/preferences/",
"is_positive_test": true,
"mocking_required": false,
+ "update_spec_pref": false,
"mock_data": {},
"expected_data": {
"status_code": 200
}
+ },{
+ "name": "Update specific preference",
+ "url": "/preferences/update_pref",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "update_spec_pref": true,
+ "expected_data": {
+ "status_code": 200
+ }
}
+
]
}
diff --git a/web/pgadmin/preferences/tests/test_preferences_update.py b/web/pgadmin/preferences/tests/test_preferences_update.py
index 170f813fb35..3cd878670ac 100644
--- a/web/pgadmin/preferences/tests/test_preferences_update.py
+++ b/web/pgadmin/preferences/tests/test_preferences_update.py
@@ -38,7 +38,10 @@ def setUp(self):
parent_node_dict['preferences'] = response.data
def runTest(self):
- self.update_preferences()
+ if self.update_spec_pref:
+ self.update_preference()
+ else:
+ self.update_preferences()
def update_preferences(self):
if 'preferences' in parent_node_dict:
@@ -58,3 +61,12 @@ def update_preferences(self):
self.assertTrue(response.status_code, 200)
else:
self.fail('Preferences not found')
+
+ def update_preference(self):
+ updated_data = [{'name': 'view_edit_promotion_warning',
+ 'value': False,
+ 'module': 'sqleditor'}]
+ response = self.tester.put(self.url,
+ data=json.dumps(updated_data),
+ content_type='html/json')
+ self.assertTrue(response.status_code, 200)
diff --git a/web/pgadmin/static/js/helpers/ModalProvider.jsx b/web/pgadmin/static/js/helpers/ModalProvider.jsx
index e05ef352a16..75771750f25 100644
--- a/web/pgadmin/static/js/helpers/ModalProvider.jsx
+++ b/web/pgadmin/static/js/helpers/ModalProvider.jsx
@@ -271,7 +271,7 @@ function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose
return;
}
useModalRef.closeModal(id);
- if(reason == 'escapeKeyDown') {
+ if(reason == 'escapeKeyDown' || reason == undefined) {
onClose?.();
}
};
diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py
index acf6f54a811..0fdf3357239 100644
--- a/web/pgadmin/tools/sqleditor/__init__.py
+++ b/web/pgadmin/tools/sqleditor/__init__.py
@@ -415,7 +415,11 @@ def _connect(conn, **kwargs):
def _init_sqleditor(trans_id, connect, sgid, sid, did, dbname=None, **kwargs):
# Create asynchronous connection using random connection id.
- conn_id = str(secrets.choice(range(1, 9999999)))
+ conn_id = kwargs['conn_id'] if 'conn_id' in kwargs else str(
+ secrets.choice(range(1, 9999999)))
+ if 'conn_id' in kwargs:
+ kwargs.pop('conn_id')
+
conn_id_ac = str(secrets.choice(range(1, 9999999)))
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
@@ -425,7 +429,7 @@ def _init_sqleditor(trans_id, connect, sgid, sid, did, dbname=None, **kwargs):
try:
command_obj = ObjectRegistry.get_object(
'query_tool', conn_id=conn_id, sgid=sgid, sid=sid, did=did,
- conn_id_ac=conn_id_ac
+ conn_id_ac=conn_id_ac, **kwargs
)
except Exception as e:
current_app.logger.error(e)
@@ -868,6 +872,24 @@ def start_query_tool(trans_id):
)
connect = 'connect' in request.args and request.args['connect'] == '1'
+ if 'gridData' in session and str(trans_id) in session['gridData']:
+ data = pickle.loads(session['gridData'][str(trans_id)]['command_obj'])
+ if data.object_type == 'table':
+ manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(
+ data.sid)
+ default_conn = manager.connection(conn_id=data.conn_id,
+ did=data.did)
+ kwargs = {
+ 'user': default_conn.manager.user,
+ 'role': default_conn.manager.role,
+ 'password': default_conn.manager.password,
+ 'conn_id': data.conn_id
+ }
+ is_error, errmsg, conn_id, version = _init_sqleditor(
+ trans_id, connect, data.sgid, data.sid, data.did, **kwargs)
+
+ if is_error:
+ return errmsg
return StartRunningQuery(blueprint, current_app.logger).execute(
sql, trans_id, session, connect
diff --git a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx
index f5a8fb74d57..62dc1392026 100644
--- a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx
+++ b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx
@@ -102,6 +102,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
title: _.unescape(params.title),
is_query_tool: params.is_query_tool == 'true' ? true : false,
node_name: retrieveNodeName(selectedNodeInfo),
+ dbname: _.unescape(params.database_name) || getDatabaseLabel(selectedNodeInfo)
},
connection_list: [{
sgid: params.sgid,
@@ -746,7 +747,37 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
modal: modal,
params: qtState.params,
preferences: qtState.preferences,
- mainContainerRef: containerRef
+ mainContainerRef: containerRef,
+ toggleQueryTool: () => setQtState((prev)=>{
+ return {
+ ...prev,
+ params: {
+ ...prev.params,
+ is_query_tool: true
+ }
+ };
+ }),
+ updateTitle: (title) => {
+ setPanelTitle(qtPanelDocker, qtPanelId, title, qtState, isDirtyRef.current);
+ setQtState((prev) => {
+ // Update connection Title
+ let newConnList = [...prev.connection_list];
+ newConnList.forEach((conn) => {
+ if (conn.sgid == params.sgid && conn.sid == params.sid && conn.did == params.did) {
+ conn.title = title;
+ conn.conn_title = title;
+ }
+ });
+ return {
+ ...prev,
+ params: {
+ ...prev.params,
+ title: title
+ },
+ connection_list: newConnList,
+ };
+ });
+ },
}), [qtState.params, qtState.preferences, containerRef.current]);
const queryToolConnContextValue = React.useMemo(()=>({
diff --git a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolConstants.js b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolConstants.js
index e959223ca71..1a5d4e30fcc 100644
--- a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolConstants.js
+++ b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolConstants.js
@@ -29,6 +29,7 @@ export const QUERY_TOOL_EVENTS = {
COPY_DATA: 'COPY_DATA',
SET_LIMIT_VALUE: 'SET_LIMIT_VALUE',
+ PROMOTE_TO_QUERY_TOOL: 'PROMOTE_TO_QUERY_TOOL',
SET_CONNECTION_STATUS: 'SET_CONNECTION_STATUS',
EXECUTION_START: 'EXECUTION_START',
EXECUTION_END: 'EXECUTION_END',
diff --git a/web/pgadmin/tools/sqleditor/static/js/components/dialogs/ConfirmPromotionContent.jsx b/web/pgadmin/tools/sqleditor/static/js/components/dialogs/ConfirmPromotionContent.jsx
new file mode 100644
index 00000000000..df40aa560ac
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/static/js/components/dialogs/ConfirmPromotionContent.jsx
@@ -0,0 +1,63 @@
+import React, { useState } from 'react';
+import { useModalStyles } from '../../../../../../static/js/helpers/ModalProvider';
+import gettext from 'sources/gettext';
+import { Box, makeStyles } from '@material-ui/core';
+import { DefaultButton, PrimaryButton } from '../../../../../../static/js/components/Buttons';
+import CloseIcon from '@material-ui/icons/CloseRounded';
+import HTMLReactParser from 'html-react-parser';
+import PropTypes from 'prop-types';
+import CheckRounded from '@material-ui/icons/CheckRounded';
+import { InputCheckbox } from '../../../../../../static/js/components/FormComponents';
+
+
+const useStyles = makeStyles(() => ({
+ saveChoice: {
+ margin: '10px 0 10px 10px',
+ }
+}));
+
+
+export default function ConfirmPromotionContent({ onContinue, onClose, closeModal, text }) {
+ const [formData, setFormData] = useState({
+ save_user_choice: false
+ });
+
+ const onDataChange = (e, id) => {
+ let val = e;
+ if (e?.target) {
+ val = e.target.value;
+ }
+ setFormData((prev) => ({ ...prev, [id]: val }));
+ };
+ const modalClasses = useModalStyles();
+ const classes = useStyles();
+
+ return (
+
+ {typeof (text) == 'string' ? HTMLReactParser(text) : text}
+
+ onDataChange(e.target.checked, 'save_user_choice')} />
+
+
+ } onClick={() => {
+ onClose?.();
+ closeModal();
+ }} >{gettext('Cancel')}
+ } onClick={() => {
+ let postFormData = new FormData();
+ postFormData.append('pref_data', JSON.stringify([{ 'name': 'view_edit_promotion_warning', 'value': !formData.save_user_choice, 'module': 'sqleditor' }]));
+ onContinue?.(postFormData);
+ closeModal();
+ }} autoFocus={true} >{gettext('Continue')}
+
+
+ );
+}
+
+ConfirmPromotionContent.propTypes = {
+ closeModal: PropTypes.func,
+ text: PropTypes.string,
+ onContinue: PropTypes.func,
+ onClose: PropTypes.func
+};
diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/MainToolBar.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/MainToolBar.jsx
index f72ff7c53a5..be35bf41891 100644
--- a/web/pgadmin/tools/sqleditor/static/js/components/sections/MainToolBar.jsx
+++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/MainToolBar.jsx
@@ -269,6 +269,16 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
eventBus.registerListener(QUERY_TOOL_EVENTS.SET_LIMIT_VALUE, (l)=>{
setLimit(l);
});
+
+ eventBus.registerListener(QUERY_TOOL_EVENTS.PROMOTE_TO_QUERY_TOOL, ()=>{
+ setDisableButton('filter', true);
+ setDisableButton('limit', true);
+
+ setDisableButton('execute', false);
+ setDisableButton('execute-options', false);
+ });
+
+
}, []);
useEffect(()=>{
diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx
index d01defc0e3a..9f01c73a080 100644
--- a/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx
+++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx
@@ -20,6 +20,10 @@ import { isMac } from '../../../../../../static/js/keyboard_shortcuts';
import { checkTrojanSource } from '../../../../../../static/js/utils';
import { parseApiError } from '../../../../../../static/js/api_instance';
import { usePgAdmin } from '../../../../../../static/js/BrowserComponent';
+import ConfirmPromotionContent from '../dialogs/ConfirmPromotionContent';
+import usePreferences from '../../../../../../preferences/static/js/store';
+import { getTitle } from '../../sqleditor_title';
+
const useStyles = makeStyles(()=>({
sql: {
@@ -246,6 +250,7 @@ export default function Query() {
const markedLine = React.useRef(0);
const marker = React.useRef();
const pgAdmin = usePgAdmin();
+ const preferencesStore = usePreferences();
const removeHighlightError = (cmObj)=>{
// Remove already existing marker
@@ -340,7 +345,7 @@ export default function Query() {
query = query || editor.current?.getValue() || '';
}
if(query) {
- eventBus.fireEvent(QUERY_TOOL_EVENTS.EXECUTION_START, query, explainObject, external);
+ eventBus.fireEvent(QUERY_TOOL_EVENTS.EXECUTION_START, query, explainObject, external, null);
}
} else {
eventBus.fireEvent(QUERY_TOOL_EVENTS.EXECUTION_START, null, null);
@@ -427,6 +432,9 @@ export default function Query() {
});
eventBus.registerListener(QUERY_TOOL_EVENTS.EDITOR_SET_SQL, (value, focus=true)=>{
focus && editor.current?.focus();
+ if(!queryToolCtx.params.is_query_tool){
+ lastSavedText.current = value;
+ }
editor.current?.setValue(value);
if (value == '' && editor.current) {
editor.current.state.autoCompleteList = [];
@@ -470,7 +478,7 @@ export default function Query() {
};
eventBus.registerListener(QUERY_TOOL_EVENTS.EDITOR_LAST_FOCUS, lastFocus);
setTimeout(()=>{
- editor.current.focus();
+ (queryToolCtx.params.is_query_tool|| queryToolCtx.preferences.view_edit_promotion_warning) && editor.current.focus();
}, 250);
}, []);
@@ -507,7 +515,7 @@ export default function Query() {
);
}, [queryToolCtx.params.trans_id]);
- const isDirty = ()=>(queryToolCtx.params.is_query_tool && lastSavedText.current !== editor.current.getValue());
+ const isDirty = ()=>(lastSavedText.current !== editor.current.getValue());
const cursorActivity = useCallback(_.debounce((cmObj)=>{
const c = cmObj.getCursor();
@@ -517,8 +525,58 @@ export default function Query() {
const change = useCallback(()=>{
eventBus.fireEvent(QUERY_TOOL_EVENTS.QUERY_CHANGED, isDirty());
+
+ if(!queryToolCtx.params.is_query_tool && isDirty()){
+ if(queryToolCtx.preferences.sqleditor.view_edit_promotion_warning){
+ checkViewEditDataPromotion();
+ } else {
+ promoteToQueryTool();
+ }
+ }
}, []);
+ const closePromotionWarning = (closeModal)=>{
+ if(isDirty()) {
+ editor.current.undo();
+ closeModal?.();
+ }
+ };
+
+ const checkViewEditDataPromotion = () => {
+ queryToolCtx.modal.showModal(gettext('Promote to Query Tool'), (closeModal) =>{
+ return ( Do you wish to continue?'}
+ onContinue={(formData)=>{
+ promoteToQueryTool();
+ let cursor = editor.current.getCursor();
+ editor.current.setValue(editor.current.getValue());
+ editor.current.setCursor(cursor);
+ editor.current.focus();
+ let title = getTitle(pgAdmin, queryToolCtx.preferences.browser, null,null,queryToolCtx.params.server_name, queryToolCtx.params.dbname, queryToolCtx.params.user);
+ queryToolCtx.updateTitle(title);
+ preferencesStore.setPreference(formData);
+ return true;
+ }}
+ onClose={()=>{
+ closePromotionWarning(closeModal);
+ }}
+ />);
+ }, {
+ onClose:()=>{
+ closePromotionWarning();
+ }
+ });
+ };
+
+ const promoteToQueryTool = () => {
+ if(!queryToolCtx.params.is_query_tool){
+ queryToolCtx.toggleQueryTool();
+ queryToolCtx.params.is_query_tool = true;
+ eventBus.fireEvent(QUERY_TOOL_EVENTS.PROMOTE_TO_QUERY_TOOL);
+ }
+ };
+
return {
editor.current=obj;
@@ -530,7 +588,6 @@ export default function Query() {
'cursorActivity': cursorActivity,
'change': change,
}}
- disabled={!queryToolCtx.params.is_query_tool}
autocomplete={true}
/>;
}
diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx
index 533dad8300f..1a7e9fb2cc6 100644
--- a/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx
+++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx
@@ -182,7 +182,7 @@ export class ResultSetUtils {
}
async startExecution(query, explainObject, onIncorrectSQL, flags={
- isQueryTool: true, external: false, reconnect: false,
+ isQueryTool: true, external: false, reconnect: false
}) {
let startTime = new Date();
this.eventBus.fireEvent(QUERY_TOOL_EVENTS.SET_MESSAGE, '');
diff --git a/web/pgadmin/tools/sqleditor/static/js/show_query_tool.js b/web/pgadmin/tools/sqleditor/static/js/show_query_tool.js
index 80fe53ce429..9c9aaeecdb9 100644
--- a/web/pgadmin/tools/sqleditor/static/js/show_query_tool.js
+++ b/web/pgadmin/tools/sqleditor/static/js/show_query_tool.js
@@ -47,7 +47,7 @@ function hasServerInformations(parentData) {
return parentData.server === undefined;
}
-function generateTitle(pgBrowser, treeIdentifier) {
+export function generateTitle(pgBrowser, treeIdentifier) {
return getPanelTitle(pgBrowser, treeIdentifier);
}
diff --git a/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py b/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py
index 20bca9ad907..86f3f961f2a 100644
--- a/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py
+++ b/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py
@@ -181,6 +181,17 @@ def register_query_tool_preferences(self):
)
)
+ self.view_edit_promotion_warning = self.preference.register(
+ 'Editor', 'view_edit_promotion_warning',
+ gettext("Show View/Edit Data Promotion Warning?"),
+ 'boolean', True,
+ category_label=PREF_LABEL_OPTIONS,
+ help_str=gettext(
+ 'If set to True, View/Edit Data tool will show promote to '
+ 'Query tool confirm dialog on query edit.'
+ )
+ )
+
self.csv_quoting = self.preference.register(
'CSV_output', 'csv_quoting',
gettext("CSV quoting"), 'options', 'strings',
diff --git a/web/pgadmin/tools/sqleditor/utils/start_running_query.py b/web/pgadmin/tools/sqleditor/utils/start_running_query.py
index 5ce45cd7110..9b910c79a7d 100644
--- a/web/pgadmin/tools/sqleditor/utils/start_running_query.py
+++ b/web/pgadmin/tools/sqleditor/utils/start_running_query.py
@@ -66,12 +66,14 @@ def execute(self, sql, trans_id, http_session, connect=False):
manager = get_driver(
PG_DEFAULT_DRIVER).connection_manager(
transaction_object.sid)
- conn = manager.connection(did=transaction_object.did,
- database=transaction_object.dbname,
- conn_id=self.connection_id,
- auto_reconnect=False,
- use_binary_placeholder=True,
- array_to_string=True)
+ conn = manager.connection(
+ did=transaction_object.did,
+ conn_id=self.connection_id,
+ auto_reconnect=False,
+ use_binary_placeholder=True,
+ array_to_string=True,
+ **({"database": transaction_object.dbname} if hasattr(
+ transaction_object,'dbname') else {}))
except (ConnectionLost, SSHTunnelConnectionLost, CryptKeyMissing):
raise
except Exception as e:
@@ -126,7 +128,8 @@ def __retrieve_connection_id(self, trans_obj):
def __execute_query(self, conn, session_obj, sql, trans_id, trans_obj):
# on successful connection set the connection id to the
# transaction object
- trans_obj.set_connection_id(self.connection_id)
+ if hasattr(trans_obj, 'set_connection_id'):
+ trans_obj.set_connection_id(self.connection_id)
StartRunningQuery.save_transaction_in_session(session_obj,
trans_id, trans_obj)
diff --git a/web/regression/javascript/__mocks__/bundled_codemirror.js b/web/regression/javascript/__mocks__/bundled_codemirror.js
index c71311f0bfa..b6846cc6d6c 100644
--- a/web/regression/javascript/__mocks__/bundled_codemirror.js
+++ b/web/regression/javascript/__mocks__/bundled_codemirror.js
@@ -31,6 +31,7 @@ const fromTextAreaRet = {
'scrollIntoView': jest.fn(),
'getWrapperElement': ()=>document.createElement('div'),
'on': jest.fn(),
+ 'off': jest.fn(),
'toTextArea': jest.fn(),
};
module.exports = {