Skip to content

Commit 7c36eab

Browse files
Warn user of unsaved data output edits before page navigation. #8916
1 parent e0ba1dd commit 7c36eab

File tree

6 files changed

+53
-29
lines changed

6 files changed

+53
-29
lines changed
2.01 KB
Loading

docs/en_US/query_tool_toolbar.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,8 @@ Pagination Options
241241
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
242242
| *Edit Range* | Click to open the from and to rows range inputs to allow setting them. | |
243243
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
244+
| *Show Entire Range* | Click to get all the rows and set the from and to rows range | |
245+
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
244246
| *Page No* | Enter the page no you want to jump to out of total shown next to this input | |
245247
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
246248
| *First Page* | Click to go to the first page. | |

web/pgadmin/static/js/custom_hooks.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,3 +286,10 @@ export function useBeforeUnload({ enabled, isNewTab, beforeClose, closePanel })
286286

287287
return {forceClose};
288288
}
289+
290+
export function useLatestFunc(fn) {
291+
const fnRef = useRef(fn);
292+
fnRef.current = fn;
293+
294+
return useCallback(((...args) => fnRef.current(...args)), []);
295+
}

web/pgadmin/static/js/helpers/ModalProvider.jsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,19 +86,14 @@ function alert(title, text, onOkClick, okLabel = gettext('OK')) {
8686
function confirm(title, text, onOkClick, onCancelClick, okLabel = gettext('Yes'), cancelLabel = gettext('No'), okIcon = 'default', modalId=null) {
8787
// bind the modal provider before calling
8888
this.showModal(title, (closeModal) => {
89-
const onCancelClickClose = () => {
90-
onCancelClick?.();
91-
closeModal();
92-
};
93-
9489
const onOkClickClose = () => {
9590
onOkClick?.();
9691
closeModal();
9792
};
9893
return (
99-
<AlertContent text={text} confirm onOkClick={onOkClickClose} onCancelClick={onCancelClickClose} okLabel={okLabel} cancelLabel={cancelLabel} okIcon={okIcon}/>
94+
<AlertContent text={text} confirm onOkClick={onOkClickClose} onCancelClick={closeModal} okLabel={okLabel} cancelLabel={cancelLabel} okIcon={okIcon}/>
10095
);
101-
}, {id: modalId});
96+
}, {id: modalId, onClose: onCancelClick});
10297
}
10398

10499
function confirmDelete(title, text, onDeleteClick, onCancelClick, deleteLabel = gettext('Delete'), cancelLabel = gettext('Cancel')) {

web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { GraphVisualiser } from './GraphVisualiser';
3131
import { usePgAdmin } from '../../../../../../static/js/PgAdminProvider';
3232
import pgAdmin from 'sources/pgadmin';
3333
import { connectServer, connectServerModal } from '../connectServer';
34+
import { useLatestFunc } from '../../../../../../static/js/custom_hooks';
3435

3536
const StyledBox = styled(Box)(({theme}) => ({
3637
display: 'flex',
@@ -846,7 +847,7 @@ export function ResultSet() {
846847
const modalId = MODAL_DIALOGS.QT_CONFIRMATIONS;
847848
// We'll use this track if any changes were saved.
848849
// It will help to decide whether results refresh is required or not on page change.
849-
const pageDataDirty = useRef(false);
850+
const pageDataOutOfSync = useRef(false);
850851

851852
const selectedCell = useRef([]);
852853
const selectedRange = useRef(null);
@@ -873,9 +874,10 @@ export function ResultSet() {
873874
// To use setLoaderText to the ResultSetUtils.
874875
rsu.current.setLoaderText = setLoaderText;
875876

876-
const isDataChanged = ()=>{
877-
return Boolean(_.size(dataChangeStore.updated) || _.size(dataChangeStore.added) || _.size(dataChangeStore.deleted));
878-
};
877+
const isDataChangedRef = useRef(false);
878+
useEffect(()=>{
879+
isDataChangedRef.current = Boolean(_.size(dataChangeStore.updated) || _.size(dataChangeStore.added) || _.size(dataChangeStore.deleted));
880+
}, [dataChangeStore]);
879881

880882
const fireRowsColsCellChanged = ()=>{
881883
eventBus.fireEvent(QUERY_TOOL_EVENTS.SELECTED_ROWS_COLS_CELL_CHANGED, selectedRows.size, selectedColumns.size, selectedRange.current, selectedCell.current?.length);
@@ -948,7 +950,7 @@ export function ResultSet() {
948950
});
949951
};
950952

951-
if(isDataChanged() && !refreshData) {
953+
if(isDataChangedRef.current && !refreshData) {
952954
queryToolCtx.modal.confirm(
953955
gettext('Unsaved changes'),
954956
gettext('The data has been modified, but not saved. Are you sure you wish to discard the changes?'),
@@ -1093,6 +1095,7 @@ export function ResultSet() {
10931095
setLoaderText(gettext('Fetching rows...'));
10941096
try {
10951097
res = await rsu.current.getWindowRows(fromRownum, toRownum);
1098+
resetSelectionAndChanges();
10961099
const newRows = rsu.current.processRows(res.data.data.result, columns);
10971100
setRows([...newRows]);
10981101
setQueryData((prev)=>({
@@ -1119,18 +1122,33 @@ export function ResultSet() {
11191122
useEffect(()=>{
11201123
let deregExecEnd;
11211124
const deregFetch = eventBus.registerListener(QUERY_TOOL_EVENTS.FETCH_WINDOW, (...args)=>{
1122-
if(pageDataDirty.current) {
1123-
deregExecEnd = eventBus.registerListener(QUERY_TOOL_EVENTS.EXECUTION_END, (success)=>{
1124-
if(!success) return;
1125-
pageDataDirty.current = false;
1125+
const impl = ()=> {
1126+
if(pageDataOutOfSync.current) {
1127+
deregExecEnd = eventBus.registerListener(QUERY_TOOL_EVENTS.EXECUTION_END, (success)=>{
1128+
if(!success) return;
1129+
pageDataOutOfSync.current = false;
1130+
fetchWindow(...args);
1131+
}, true);
1132+
eventBus.fireEvent(QUERY_TOOL_EVENTS.EXECUTION_START, rsu.current.query, {refreshData: true});
1133+
// executionStartCallback(rsu.current.query, {refreshData: true});
1134+
} else {
1135+
pageDataOutOfSync.current = false;
11261136
fetchWindow(...args);
1127-
}, true);
1128-
executionStartCallback(rsu.current.query, {refreshData: true});
1137+
}
1138+
};
1139+
1140+
if(isDataChangedRef.current) {
1141+
queryToolCtx.modal.confirm(
1142+
gettext('Unsaved changes'),
1143+
gettext('The data has been modified, but not saved. Are you sure you wish to discard the changes?'),
1144+
impl,
1145+
function() {
1146+
/* Do nothing */
1147+
}
1148+
);
11291149
} else {
1130-
pageDataDirty.current = false;
1131-
fetchWindow(...args);
1150+
impl();
11321151
}
1133-
resetSelectionAndChanges();
11341152
});
11351153
return ()=>{
11361154
deregFetch();
@@ -1144,7 +1162,7 @@ export function ResultSet() {
11441162

11451163
const warnSaveDataClose = ()=>{
11461164
// No changes.
1147-
if(!isDataChanged() || !queryToolCtx.preferences?.sqleditor.prompt_save_data_changes) {
1165+
if(!isDataChangedRef.current || !queryToolCtx.preferences?.sqleditor.prompt_save_data_changes) {
11481166
eventBus.fireEvent(QUERY_TOOL_EVENTS.WARN_SAVE_TEXT_CLOSE);
11491167
return;
11501168
}
@@ -1172,7 +1190,7 @@ export function ResultSet() {
11721190
};
11731191
}, [dataChangeStore]);
11741192

1175-
const triggerSaveData = async ()=>{
1193+
const triggerSaveData = useLatestFunc(async ()=>{
11761194
if(!_.size(dataChangeStore.updated) && !_.size(dataChangeStore.added) && !_.size(dataChangeStore.deleted)) {
11771195
return;
11781196
}
@@ -1210,7 +1228,7 @@ export function ResultSet() {
12101228
} catch {/* History errors should not bother others */}
12111229

12121230
if(!respData.data.status) {
1213-
pageDataDirty.current = false;
1231+
pageDataOutOfSync.current = false;
12141232
eventBus.fireEvent(QUERY_TOOL_EVENTS.SET_MESSAGE, respData.data.result);
12151233
pgAdmin.Browser.notifier.error(respData.data.result, 20000);
12161234
// If the transaction is not idle, notify the user that previous queries are not rolled back,
@@ -1223,7 +1241,7 @@ export function ResultSet() {
12231241
return;
12241242
}
12251243

1226-
pageDataDirty.current = true;
1244+
pageDataOutOfSync.current = true;
12271245
if(_.size(dataChangeStore.added)) {
12281246
// Update the rows in a grid after addition
12291247
respData.data.query_results.forEach((qr)=>{
@@ -1258,18 +1276,19 @@ export function ResultSet() {
12581276
resetSelectionAndChanges();
12591277
eventBus.fireEvent(QUERY_TOOL_EVENTS.SET_CONNECTION_STATUS, respData.data.transaction_status);
12601278
eventBus.fireEvent(QUERY_TOOL_EVENTS.SET_MESSAGE, '');
1279+
setLoaderText(null);
12611280
pgAdmin.Browser.notifier.success(gettext('Data saved successfully.'));
12621281
if(respData.data.transaction_status > CONNECTION_STATUS.TRANSACTION_STATUS_IDLE) {
12631282
pgAdmin.Browser.notifier.info(gettext('Auto-commit is off. You still need to commit changes to the database.'));
12641283
}
12651284
} catch (error) {
1266-
pageDataDirty.current = false;
1285+
pageDataOutOfSync.current = false;
12671286
eventBus.fireEvent(QUERY_TOOL_EVENTS.HANDLE_API_ERROR, error, {
12681287
checkTransaction: true,
12691288
});
1289+
setLoaderText(null);
12701290
}
1271-
setLoaderText(null);
1272-
};
1291+
});
12731292

12741293
useEffect(()=>{
12751294
eventBus.registerListener(QUERY_TOOL_EVENTS.TRIGGER_SAVE_DATA, triggerSaveData);

web/pgadmin/utils/driver/psycopg3/server_manager.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,8 @@ def create_connection_string(self, database, user, password=None):
690690
if key == 'hostaddr' and self.use_ssh_tunnel:
691691
continue
692692

693-
# Convert boolean connection parameters to integer for libpq compatibility
693+
# Convert boolean connection parameters to integer for
694+
# libpq compatibility
694695
if key in ('sslcompression', 'sslsni'):
695696
value = 1 if value else 0
696697

0 commit comments

Comments
 (0)