From f07eabedbe36ca8b40a932601e5687f928d04990 Mon Sep 17 00:00:00 2001 From: Yogesh Mahajan Date: Thu, 19 Jun 2025 12:12:54 +0530 Subject: [PATCH] Ensure that restored query tool tabs display the correct title. #3319 --- web/pgadmin/browser/static/js/browser.js | 36 +++++++++-------- web/pgadmin/settings/__init__.py | 40 ++++++++++++++++--- .../static/ApplicationStateProvider.jsx | 32 ++++++++++++++- .../static/js/helpers/Layout/index.jsx | 4 +- .../static/js/erd_tool/components/ERDTool.jsx | 2 + .../sqleditor/static/js/SQLEditorModule.js | 6 +-- .../js/components/QueryToolComponent.jsx | 23 ++++++----- .../js/components/QueryToolConstants.js | 1 + .../static/js/components/sections/Query.jsx | 19 +++++---- .../sqleditor/static/js/show_query_tool.js | 4 +- 10 files changed, 117 insertions(+), 50 deletions(-) diff --git a/web/pgadmin/browser/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js index 81afb051d..9aae207a5 100644 --- a/web/pgadmin/browser/static/js/browser.js +++ b/web/pgadmin/browser/static/js/browser.js @@ -16,7 +16,6 @@ import getApiInstance, {parseApiError} from '../../../static/js/api_instance'; import usePreferences, { setupPreferenceBroadcast } from '../../../preferences/static/js/store'; import checkNodeVisibility from '../../../static/js/check_node_visibility'; import * as showQueryTool from '../../../tools/sqleditor/static/js/show_query_tool'; -import {getRandomInt} from 'sources/utils'; define('pgadmin.browser', [ 'sources/gettext', 'sources/url_for', 'sources/pgadmin', @@ -304,30 +303,35 @@ define('pgadmin.browser', [ url_for('settings.get_application_state') ).then((res)=> { if(res.data.success && res.data.data.result.length > 0){ + let deleteToolDataIds = []; _.each(res.data.data.result, function(toolState){ let toolNme = toolState.tool_name; - let toolDataId = `${toolNme}-${getRandomInt(1, 9999999)}`; + let toolDataId = `${toolNme}-${toolState.id}`; let connectionInfo = toolState.connection_info; - localStorage.setItem(toolDataId, toolState.tool_data); - + if (toolNme == 'sqleditor'){ showQueryTool.relaunchSqlTool(connectionInfo, toolDataId); - }else if(toolNme == 'psql'){ - pgAdmin.Tools.Psql.openPsqlTool(null, null, connectionInfo); - }else if(toolNme == 'ERD'){ - pgAdmin.Tools.ERD.showErdTool(null, null, false, connectionInfo, toolDataId); - }else if(toolNme == 'schema_diff'){ - pgAdmin.Tools.SchemaDiff.launchSchemaDiff(toolDataId); + }else{ + localStorage.setItem(toolDataId, toolState.tool_data); + deleteToolDataIds.push(toolState.id); + if(toolNme == 'psql'){ + pgAdmin.Tools.Psql.openPsqlTool(null, null, connectionInfo); + }else if(toolNme == 'ERD'){ + pgAdmin.Tools.ERD.showErdTool(null, null, false, connectionInfo, toolDataId); + }else if(toolNme == 'schema_diff'){ + pgAdmin.Tools.SchemaDiff.launchSchemaDiff(toolDataId); + } } }); // call clear application state data. - try { - getApiInstance().delete(url_for('settings.delete_application_state'), {}); - } catch (error) { - console.error(error); - pgAdmin.Browser.notifier.error(gettext('Failed to remove query data.') + parseApiError(error)); - } + if(deleteToolDataIds.length > 0){ + try { + getApiInstance().delete(url_for('settings.delete_application_state'), {data:{'trans_ids': deleteToolDataIds}}); + } catch (error) { + console.error(error); + pgAdmin.Browser.notifier.error(gettext('Failed to remove query data.') + parseApiError(error)); + }} } }).catch(function(error) { pgAdmin.Browser.notifier.pgRespErrorNotify(error); diff --git a/web/pgadmin/settings/__init__.py b/web/pgadmin/settings/__init__.py index d3b903f82..3135906e0 100644 --- a/web/pgadmin/settings/__init__.py +++ b/web/pgadmin/settings/__init__.py @@ -57,7 +57,8 @@ class SettingsModule(PgAdminModule): 'settings.get_file_format_setting', 'settings.save_application_state', 'settings.get_application_state', - 'settings.delete_application_state' + 'settings.delete_application_state', + 'settings.get_tool_data' ] @@ -326,8 +327,7 @@ def get_last_saved_file_hash(file_path, trans_id): @blueprint.route( '/get_application_state', - methods=["GET"], endpoint='get_application_state' -) + methods=["GET"], endpoint='get_application_state') @pga_login_required def get_application_state(): """ @@ -370,16 +370,44 @@ def get_application_state(): ) +@blueprint.route( + '/get_tool_data/', + methods=["GET"], endpoint='get_tool_data') +@pga_login_required +def get_tool_data(trans_id): + fernet = Fernet(current_app.config['SECRET_KEY'].encode()) + result = db.session \ + .query(ApplicationState) \ + .filter(ApplicationState.uid == current_user.id, + ApplicationState.id == trans_id) \ + .first() + + return make_json_response( + data={ + 'status': True, + 'msg': '', + 'result': { + 'tool_data': fernet.decrypt(result.tool_data).decode(), + } + } + ) + + @blueprint.route( '/delete_application_state/', methods=["DELETE"], endpoint='delete_application_state') @pga_login_required def delete_application_state(): - trans_id = None + status = False + msg = gettext('Unable to delete application state data.') if request.data: data = json.loads(request.data) - trans_id = int(data['panelId'].split('_')[-1]) - status, msg = delete_tool_data(trans_id) + if 'trans_ids' in data: + for trans_id in data['trans_ids']: + status, msg = delete_tool_data(trans_id) + else: + trans_id = int(data['panelId'].split('_')[-1]) + status, msg = delete_tool_data(trans_id) return make_json_response( data={ 'status': status, diff --git a/web/pgadmin/settings/static/ApplicationStateProvider.jsx b/web/pgadmin/settings/static/ApplicationStateProvider.jsx index 6af331b95..8f7e91351 100644 --- a/web/pgadmin/settings/static/ApplicationStateProvider.jsx +++ b/web/pgadmin/settings/static/ApplicationStateProvider.jsx @@ -37,7 +37,7 @@ export function deleteToolData(panelId, closePanelId){ } }; -export function ApplicationStateProvider({children}){ +export function ApplicationStateProvider({children, toolDataId}){ const preferencesStore = usePreferences(); const saveAppState = preferencesStore?.getPreferencesForModule('misc')?.save_app_state; const openNewTab = preferencesStore?.getPreferencesForModule('browser')?.new_browser_tab_open; @@ -63,9 +63,36 @@ export function ApplicationStateProvider({children}){ return saveAppState; }; + async function getQueryToolContent() { + try { + let transId = toolDataId.split('-')[1]; + const res = await getApiInstance({'Content-Encoding': 'gzip'}).get( + url_for('settings.get_tool_data', { + 'trans_id': transId, + }) + ); + return res.data.success? JSON.parse(res.data.data.result.tool_data): null; + } catch (error) { + pgAdmin.Browser.notifier.pgRespErrorNotify(error); + return null; + } + } + + const deleteToolData = ()=>{ + let transId = toolDataId.split('-')[1]; + getApiInstance().delete( + url_for('settings.delete_application_state'), {data:{'panelId': transId}} + ).then(()=> { /* Sonar Qube */}).catch(function(error) { + pgAdmin.Browser.notifier.pgRespErrorNotify(error); + }); + + }; + const value = useMemo(()=>({ saveToolData, isSaveToolDataEnabled, + getQueryToolContent, + deleteToolData }), []); return @@ -75,5 +102,6 @@ export function ApplicationStateProvider({children}){ } ApplicationStateProvider.propTypes = { - children: PropTypes.object + children: PropTypes.object, + toolDataId: PropTypes.string }; \ No newline at end of file diff --git a/web/pgadmin/static/js/helpers/Layout/index.jsx b/web/pgadmin/static/js/helpers/Layout/index.jsx index fd8688878..eb5d7c807 100644 --- a/web/pgadmin/static/js/helpers/Layout/index.jsx +++ b/web/pgadmin/static/js/helpers/Layout/index.jsx @@ -45,7 +45,7 @@ function TabTitle({id, closable, defaultInternal}) { }, []); useEffect(()=>{ - const deregister = layoutDocker.eventBus.registerListener(LAYOUT_EVENTS.REFRESH_TITLE, _.debounce((panelId)=>{ + const deregister = layoutDocker.eventBus.registerListener(LAYOUT_EVENTS.REFRESH_TITLE, (panelId)=>{ if(panelId == id) { const internal = layoutDocker?.find(id)?.internal??{}; setAttrs({ @@ -54,7 +54,7 @@ function TabTitle({id, closable, defaultInternal}) { tooltip: internal.tooltip ?? internal.title, }); } - }, 100)); + }); return ()=>deregister?.(); }, []); diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/components/ERDTool.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/components/ERDTool.jsx index 89f0fcf3c..4db86dd34 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/components/ERDTool.jsx +++ b/web/pgadmin/tools/erd/static/js/erd_tool/components/ERDTool.jsx @@ -360,6 +360,8 @@ export default class ERDTool extends React.Component { this.diagram.deserialize(sqlValue); this.diagram.clearSelection(); this.registerModelEvents(); + this.setState({dirty: true}); + this.eventBus.fireEvent(ERD_EVENTS.DIRTY, true, this.serializeFile()); } } else if(this.props.params.gen) { diff --git a/web/pgadmin/tools/sqleditor/static/js/SQLEditorModule.js b/web/pgadmin/tools/sqleditor/static/js/SQLEditorModule.js index b50c89b74..f73041e1f 100644 --- a/web/pgadmin/tools/sqleditor/static/js/SQLEditorModule.js +++ b/web/pgadmin/tools/sqleditor/static/js/SQLEditorModule.js @@ -220,9 +220,7 @@ export default class SQLEditor { let browser_preferences = usePreferences.getState().getPreferencesForModule('browser'); let open_new_tab = browser_preferences.new_browser_tab_open; const [icon, tooltip] = panelTitleFunc.getQueryToolIcon(panel_title, is_query_tool); - let selectedNodeInfo = pgAdmin.Browser.tree?.getTreeNodeHierarchy( - pgAdmin.Browser.tree.selected() - ); + let selectedNodeInfo = pgAdmin.Browser.tree?.selected() ? pgAdmin.Browser.tree?.getTreeNodeHierarchy(pgAdmin.Browser.tree.selected()) : null; pgAdmin.Browser.Events.trigger( 'pgadmin:tool:show', @@ -248,7 +246,7 @@ export default class SQLEditor { root.render( - + { params.error ? diff --git a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx index a0d450aff..3a3920a82 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx @@ -166,7 +166,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN const docker = useRef(null); const api = useMemo(()=>getApiInstance(), []); const modal = useModal(); - const {isSaveToolDataEnabled} = useApplicationState(); + const {isSaveToolDataEnabled, deleteToolData} = useApplicationState(); /* Connection status poller */ let pollTime = qtState.preferences.sqleditor.connection_status_fetch_time > 0 @@ -283,7 +283,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN eventBus.current.fireEvent(QUERY_TOOL_EVENTS.HANDLE_API_ERROR, err); setQtStatePartial({ editor_disabled: true }); }); - } else if (qtState.params.sql_id) { + } else if (qtState.params.toolDataId) { populateEditorData(); } else { setQtStatePartial({ editor_disabled: false }); @@ -291,23 +291,24 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN }; const populateEditorData = () =>{ - let sqlId = qtState.params.sql_id, - loadSqlFromLocalStorage = true; - + let populateQueryContent = true; + if(qtState.params.open_file_name){ if(qtState.params.file_deleted == 'false' && qtState.params.is_editor_dirty == 'false'){ - // call load file from disk as no fil changes + // call load file from disk as no file changes eventBus.current.fireEvent(QUERY_TOOL_EVENTS.LOAD_FILE, qtState.params.open_file_name, qtState.params?.storage); + populateQueryContent = false; + deleteToolData(); }else if(qtState.params.file_deleted != 'true'){ if(qtState.params.external_file_changes == 'true'){ - loadSqlFromLocalStorage = false; - eventBus.current.fireEvent(QUERY_TOOL_EVENTS.WARN_RELOAD_FILE, qtState.params.open_file_name, sqlId); + populateQueryContent = false; + eventBus.current.fireEvent(QUERY_TOOL_EVENTS.WARN_RELOAD_FILE, qtState.params.open_file_name); }else{ eventBus.current.fireEvent(QUERY_TOOL_EVENTS.LOAD_FILE_DONE, qtState.params.open_file_name, true); } } } - if(loadSqlFromLocalStorage) eventBus.current.fireEvent(QUERY_TOOL_EVENTS.LOAD_SQL_FROM_LOCAL_STORAGE, sqlId); + if(populateQueryContent) eventBus.current.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_GET_QUERY_CONTENT); setQtStatePartial({ editor_disabled: false }); }; @@ -545,7 +546,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN }); isDirtyRef.current = false; setPanelTitle(qtPanelDocker, qtPanelId, fileName, {...qtState, current_file: fileName}, isDirtyRef.current); - + if(isSaveToolDataEnabled('sqleditor'))eventBus.current.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_SAVE_QUERY_TOOL_DATA); } eventBus.current.fireEvent(QUERY_TOOL_EVENTS.EDITOR_LAST_FOCUS); @@ -584,6 +585,8 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN [QUERY_TOOL_EVENTS.LOAD_FILE_DONE, fileDone], [QUERY_TOOL_EVENTS.SAVE_FILE_DONE, fileDone], [QUERY_TOOL_EVENTS.QUERY_CHANGED, (isDirty)=>{ + if(isDirtyRef.current === isDirty) return; + isDirtyRef.current = isDirty; if(qtState.params.is_query_tool) { setPanelTitle(qtPanelDocker, qtPanelId, null, qtState, isDirty); diff --git a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolConstants.js b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolConstants.js index a60d2bf11..f168407f6 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 = { TRIGGER_GRAPH_VISUALISER: 'TRIGGER_GRAPH_VISUALISER', TRIGGER_SELECT_ALL: 'TRIGGER_SELECT_ALL', TRIGGER_SAVE_QUERY_TOOL_DATA: 'TRIGGER_SAVE_QUERY_TOOL_DATA', + TRIGGER_GET_QUERY_CONTENT: 'TRIGGER_GET_QUERY_CONTENT', COPY_DATA: 'COPY_DATA', SET_LIMIT_VALUE: 'SET_LIMIT_VALUE', 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 9b5e0aa92..5b25c632a 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx @@ -23,10 +23,9 @@ import ConfirmExecuteQueryContent from '../dialogs/ConfirmExecuteQueryContent'; import usePreferences from '../../../../../../preferences/static/js/store'; import { getTitle } from '../../sqleditor_title'; import PropTypes from 'prop-types'; -import { useApplicationState, getToolData } from '../../../../../../settings/static/ApplicationStateProvider'; +import { useApplicationState } from '../../../../../../settings/static/ApplicationStateProvider'; import { useDelayDebounce } from '../../../../../../static/js/custom_hooks'; - async function registerAutocomplete(editor, api, transId) { editor.registerAutocomplete((context, onAvailable)=>{ return new Promise((resolve, reject)=>{ @@ -64,7 +63,7 @@ export default function Query({onTextSelect, setQtStatePartial}) { const layoutDocker = useContext(LayoutDockerContext); const lastCursorPos = React.useRef(); const pgAdmin = usePgAdmin(); - const {saveToolData, isSaveToolDataEnabled} = useApplicationState(); + const {saveToolData, isSaveToolDataEnabled, getQueryToolContent, deleteToolData} = useApplicationState(); const preferencesStore = usePreferences(); const modalId = MODAL_DIALOGS.QT_CONFIRMATIONS; @@ -160,15 +159,16 @@ export default function Query({onTextSelect, setQtStatePartial}) { } }; - const warnReloadFile = (fileName, sqlId, storage=null)=>{ + const warnReloadFile = (fileName, storage=null)=>{ queryToolCtx.modal.confirm( gettext('Reload file?'), gettext('The file has been modified by another program. Do you want to reload it and loose changes made in pgadmin?'), function() { eventBus.fireEvent(QUERY_TOOL_EVENTS.LOAD_FILE, fileName); + deleteToolData(); }, function() { - eventBus.fireEvent(QUERY_TOOL_EVENTS.LOAD_SQL_FROM_LOCAL_STORAGE, sqlId); + eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_GET_QUERY_CONTENT); eventBus.fireEvent(QUERY_TOOL_EVENTS.LOAD_FILE_DONE, fileName, true, storage); } ); @@ -244,6 +244,7 @@ export default function Query({onTextSelect, setQtStatePartial}) { focus && editor.current?.focus(); editor.current?.setValue(value, !queryToolCtx.params.is_query_tool); }); + eventBus.registerListener(QUERY_TOOL_EVENTS.TRIGGER_QUERY_CHANGE, ()=>{ change(); }); @@ -283,10 +284,12 @@ export default function Query({onTextSelect, setQtStatePartial}) { setSaveQtData(true); }); - eventBus.registerListener(QUERY_TOOL_EVENTS.LOAD_SQL_FROM_LOCAL_STORAGE, (sqlId)=>{ - let sqlValue = getToolData(sqlId); - if (sqlValue) { + eventBus.registerListener(QUERY_TOOL_EVENTS.TRIGGER_GET_QUERY_CONTENT, async ()=>{ + let sqlValue = await getQueryToolContent(); + if(sqlValue){ eventBus.fireEvent(QUERY_TOOL_EVENTS.EDITOR_SET_SQL, sqlValue); + // call delete appplication state api + deleteToolData(); } }); }, []); 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 02b0e7441..3dd22fe4a 100644 --- a/web/pgadmin/tools/sqleditor/static/js/show_query_tool.js +++ b/web/pgadmin/tools/sqleditor/static/js/show_query_tool.js @@ -114,7 +114,7 @@ export function showERDSqlTool(parentData, erdSqlId, queryToolTitle, queryToolMo launchQueryTool(queryToolMod, transId, gridUrl, queryToolTitle, {}); } -export function relaunchSqlTool(connectionInfo, sqlId){ +export function relaunchSqlTool(connectionInfo, toolDataId){ let browserPref = usePreferences.getState().getPreferencesForModule('browser'); let parentData = { server_group: { @@ -135,7 +135,7 @@ export function relaunchSqlTool(connectionInfo, sqlId){ const qtUrl = generateUrl(transId, parentData, null); const title = getTitle(pgAdmin, browserPref, parentData, false, connectionInfo.server_name, connectionInfo.database_name, connectionInfo.role || connectionInfo.user); launchQueryTool(pgWindow.pgAdmin.Tools.SQLEditor, transId, qtUrl, title, { - sql_id: sqlId, + toolDataId: toolDataId, ...connectionInfo, });