Skip to content

Commit af84d6b

Browse files
Handle result grid data changes in View/Edit Data mode by automatically reconnecting to the server if a disconnection occurs. #8608
1 parent 2cb69f0 commit af84d6b

File tree

3 files changed

+94
-25
lines changed

3 files changed

+94
-25
lines changed

web/pgadmin/tools/sqleditor/__init__.py

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,9 @@ def start_view_data(trans_id):
826826
if not status and error_msg and type(error_msg) is Response:
827827
return error_msg
828828

829+
# Check if connect is passed in the request.
830+
connect = 'connect' in request.args and request.args['connect'] == '1'
831+
829832
# get the default connection as current connection which is attached to
830833
# trans id holds the cursor which has query result so we cannot use that
831834
# connection to execute another query otherwise we'll lose query result.
@@ -845,19 +848,21 @@ def start_view_data(trans_id):
845848

846849
# Connect to the Server if not connected.
847850
if not conn.connected() or not default_conn.connected():
848-
# This will check if view/edit data tool connection is lost or not,
849-
# if lost then it will reconnect
850-
status, error_msg, conn, trans_obj, session_obj, response = \
851-
query_tool_connection_check(trans_id)
852-
# This is required for asking user to enter password
853-
# when password is not saved for the server
854-
if response is not None:
855-
return response
851+
if connect:
852+
# This will check if view/edit data tool connection is lost or not,
853+
# if lost then it will reconnect
854+
status, error_msg, conn, trans_obj, session_obj, response = \
855+
query_tool_connection_check(trans_id)
856+
# This is required for asking user to enter password
857+
# when password is not saved for the server
858+
if response is not None:
859+
return response
856860

857861
status, msg = default_conn.connect()
858862
if not status:
859-
return make_json_response(
860-
data={'status': status, 'result': "{}".format(msg)}
863+
return service_unavailable(
864+
gettext("Connection to the server has been lost."),
865+
info="CONNECTION_LOST"
861866
)
862867

863868
if status and conn is not None and \
@@ -1402,6 +1407,8 @@ def save(trans_id):
14021407
changed_data = json.loads(request.data)
14031408
else:
14041409
changed_data = request.args or request.form
1410+
# Check if connect is passed in the request.
1411+
connect = 'connect' in request.args and request.args['connect'] == '1'
14051412

14061413
# Check the transaction and connection status
14071414
status, error_msg, conn, trans_obj, session_obj = \
@@ -1427,10 +1434,21 @@ def save(trans_id):
14271434
}
14281435
)
14291436

1437+
if connect:
1438+
# This will check if view/edit data tool connection is lost or not,
1439+
# if lost then it will reconnect
1440+
status, error_msg, conn, trans_obj, session_obj, response = \
1441+
query_tool_connection_check(trans_id)
1442+
# This is required for asking user to enter password
1443+
# when password is not saved for the server
1444+
if response is not None:
1445+
return response
1446+
14301447
is_error, errmsg, conn = _check_and_connect(trans_obj)
14311448
if is_error:
1432-
return make_json_response(
1433-
data={'status': status, 'result': "{}".format(errmsg)}
1449+
return service_unavailable(
1450+
gettext("Connection to the server has been lost."),
1451+
info="CONNECTION_LOST"
14341452
)
14351453

14361454
status, res, query_results, _rowid = trans_obj.save(

web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -543,9 +543,8 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
543543
<br />
544544
<span>{gettext('Do you want to continue and establish a new session')}</span>
545545
</p>,
546-
function() {
547-
handleParams?.connectionLostCallback?.();
548-
}, null,
546+
() => handleParams?.connectionLostCallback?.(),
547+
() => handleParams?.cancelCallback?.(),
549548
gettext('Continue'),
550549
gettext('Cancel')
551550
);

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

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export class ResultSetUtils {
5151
this.historyQuerySource = null;
5252
this.hasQueryCommitted = false;
5353
this.queryToolCtx = queryToolCtx;
54+
this.setLoaderText = null;
5455
}
5556

5657
static generateURLReconnectionFlag(baseUrl, transId, shouldReconnect) {
@@ -445,23 +446,72 @@ export class ResultSetUtils {
445446
);
446447
}
447448

448-
saveData(reqData) {
449+
saveData(reqData, shouldReconnect) {
450+
// Generate the URL with the optional `connect=1` parameter.
451+
const url = ResultSetUtils.generateURLReconnectionFlag('sqleditor.save', this.transId, shouldReconnect);
452+
449453
return this.api.post(
450-
url_for('sqleditor.save', {
451-
'trans_id': this.transId
452-
}),
454+
url,
453455
JSON.stringify(reqData)
454-
).then(response => {
456+
).then((response) => {
455457
if (response.data?.data?.status) {
456458
// Set the commit flag to true if the save was successful
457459
this.hasQueryCommitted = true;
458460
}
459461
return response;
460-
}).catch((error) => {
461-
// Set the commit flag to false if there was an error
462-
this.hasQueryCommitted = false;
463-
throw error;
464-
});
462+
})
463+
.catch(async (error) => {
464+
if (error.response?.status === 428) {
465+
// Handle 428: Show password dialog.
466+
return new Promise((resolve, reject) => {
467+
this.connectServerModal(
468+
error.response?.data?.result,
469+
async (formData) => {
470+
try {
471+
await this.connectServer(
472+
this.queryToolCtx.params.sid,
473+
this.queryToolCtx.params.user,
474+
formData,
475+
async () => {
476+
let retryRespData = await this.saveData(reqData);
477+
// Set the commit flag to true if the save was successful
478+
this.hasQueryCommitted = true;
479+
pgAdmin.Browser.notifier.success(gettext('Server Connected.'));
480+
resolve(retryRespData);
481+
}
482+
);
483+
484+
} catch (retryError) {
485+
reject(retryError);
486+
}
487+
},
488+
() => this.setLoaderText(null)
489+
);
490+
});
491+
} else if (error.response?.status === 503) {
492+
// Handle 503: Fire HANDLE_API_ERROR and wait for connectionLostCallback.
493+
return new Promise((resolve, reject) => {
494+
this.eventBus.fireEvent(QUERY_TOOL_EVENTS.HANDLE_API_ERROR, error, {
495+
connectionLostCallback: async () => {
496+
try {
497+
// Retry saveData with connect=1
498+
let retryRespData = await this.saveData(reqData, true);
499+
resolve(retryRespData);
500+
} catch (retryError) {
501+
reject(retryError);
502+
}
503+
},
504+
checkTransaction: true,
505+
cancelCallback: () => this.setLoaderText(null)
506+
},
507+
);
508+
});
509+
} else {
510+
// Set the commit flag to false if there was an error
511+
this.hasQueryCommitted = false;
512+
throw error;
513+
}
514+
});
465515
}
466516

467517
async saveResultsToFile(fileName) {
@@ -861,6 +911,8 @@ export function ResultSet() {
861911

862912
rsu.current.setEventBus(eventBus);
863913
rsu.current.setQtPref(queryToolCtx.preferences?.sqleditor);
914+
// To use setLoaderText to the ResultSetUtils.
915+
rsu.current.setLoaderText = setLoaderText;
864916

865917
const isDataChanged = ()=>{
866918
return Boolean(_.size(dataChangeStore.updated) || _.size(dataChangeStore.added) || _.size(dataChangeStore.deleted));

0 commit comments

Comments
 (0)