From 4597d63860d657ed88f124a6e5345c6aca2959a9 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 11 Oct 2026 09:23:15 +0300 Subject: [PATCH] =?UTF-8?q?fix(workspace):=20the=20status=20bar=20does=20n?= =?UTF-8?q?ot=20keep=20'Running=20query=E2=80=A6'=20after=20a=20failed=20r?= =?UTF-8?q?un?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit endBusy kept the message beginBusy had set, so a failed query (and every explain) left 'Running query…' / 'Explaining query…' in the status bar, next to the row counts of the previous result. A busy message now goes away with the busy state, and failures report 'Query failed' / 'Query timed out' / 'Statement n of m failed' / 'Explain failed' without the stale counts. --- lib/core/ui/querya_shell_status.dart | 33 ++++++++++- .../workspace/generic_sql_workspace.dart | 13 ++++- test/core/ui/querya_shell_status_test.dart | 58 +++++++++++++++++++ 3 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 test/core/ui/querya_shell_status_test.dart diff --git a/lib/core/ui/querya_shell_status.dart b/lib/core/ui/querya_shell_status.dart index 43fd0fd6..9979ceca 100644 --- a/lib/core/ui/querya_shell_status.dart +++ b/lib/core/ui/querya_shell_status.dart @@ -15,6 +15,10 @@ class QueryaShellStatus extends ChangeNotifier { int? _columnCount; Duration? _lastQueryDuration; + /// Whether [_statusMessage] only describes the running work ("Running + /// query…"): such a message goes away with the busy state. + bool _messageIsBusy = false; + bool get isBusy => _isBusy; String? get statusMessage => _statusMessage; int? get rowCount => _rowCount; @@ -24,13 +28,22 @@ class QueryaShellStatus extends ChangeNotifier { /// Marks the shell busy (spinner + optional message). void beginBusy({String? message}) { _isBusy = true; - if (message != null) _statusMessage = message; + if (message != null) { + _statusMessage = message; + _messageIsBusy = true; + } notifyListeners(); } /// Clears busy; keeps last query metrics unless [clearMetrics] is true. + /// + /// A message set by [beginBusy] is dropped: it described work that is over. void endBusy({bool clearMetrics = false}) { _isBusy = false; + if (_messageIsBusy) { + _statusMessage = null; + _messageIsBusy = false; + } if (clearMetrics) { _statusMessage = null; _rowCount = null; @@ -52,6 +65,23 @@ class QueryaShellStatus extends ChangeNotifier { if (rowCount != null) _rowCount = rowCount; if (columnCount != null) _columnCount = columnCount; if (message != null) _statusMessage = message; + _messageIsBusy = false; + notifyListeners(); + } + + /// Publishes a query (or command) that failed and clears busy. The row and + /// column counts of an earlier result are dropped so they are not read as + /// this run's. + void reportQueryFailure({ + Duration? duration, + String message = 'Query failed', + }) { + _isBusy = false; + _statusMessage = message; + _messageIsBusy = false; + _rowCount = null; + _columnCount = null; + if (duration != null) _lastQueryDuration = duration; notifyListeners(); } @@ -59,6 +89,7 @@ class QueryaShellStatus extends ChangeNotifier { void clear() { _isBusy = false; _statusMessage = null; + _messageIsBusy = false; _rowCount = null; _columnCount = null; _lastQueryDuration = null; diff --git a/lib/features/workspace/generic_sql_workspace.dart b/lib/features/workspace/generic_sql_workspace.dart index 283fd219..de069bdc 100644 --- a/lib/features/workspace/generic_sql_workspace.dart +++ b/lib/features/workspace/generic_sql_workspace.dart @@ -663,7 +663,10 @@ class GenericSqlWorkspaceState extends material.State { 'Statement ${failedAt + 1} of ${texts.length} failed (line $line): $failure'; session.running = false; }); - QueryaShellStatus.instance.endBusy(); + QueryaShellStatus.instance.reportQueryFailure( + duration: sw.elapsed, + message: 'Statement ${failedAt + 1} of ${texts.length} failed', + ); return; } @@ -749,7 +752,10 @@ class GenericSqlWorkspaceState extends material.State { session.error = statementTimeoutText(e); session.running = false; }); - QueryaShellStatus.instance.endBusy(); + QueryaShellStatus.instance.reportQueryFailure( + duration: sw.elapsed, + message: 'Query timed out', + ); } } catch (e) { if (mounted) { @@ -758,7 +764,7 @@ class GenericSqlWorkspaceState extends material.State { session.error = e.toString(); session.running = false; }); - QueryaShellStatus.instance.endBusy(); + QueryaShellStatus.instance.reportQueryFailure(duration: sw.elapsed); } } finally { // Autocommit: after a read that succeeded while no transaction is known @@ -869,6 +875,7 @@ class GenericSqlWorkspaceState extends material.State { session.error = e.toString(); session.running = false; }); + QueryaShellStatus.instance.reportQueryFailure(message: 'Explain failed'); } } finally { QueryaShellStatus.instance.endBusy(); diff --git a/test/core/ui/querya_shell_status_test.dart b/test/core/ui/querya_shell_status_test.dart new file mode 100644 index 00000000..85318a55 --- /dev/null +++ b/test/core/ui/querya_shell_status_test.dart @@ -0,0 +1,58 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:querya_desktop/core/ui/querya_shell_status.dart'; + +void main() { + final status = QueryaShellStatus.instance; + + setUp(status.resetForTest); + tearDown(status.resetForTest); + + test('the busy message goes away with the busy state', () { + status.beginBusy(message: 'Running query…'); + expect(status.isBusy, isTrue); + expect(status.statusMessage, 'Running query…'); + + status.endBusy(); + + expect(status.isBusy, isFalse); + expect(status.statusMessage, isNull); + }); + + test('a failure replaces the busy message and drops old counts', () { + status.reportQueryResult( + duration: const Duration(milliseconds: 7), + rowCount: 12, + columnCount: 8, + message: '12 row(s).', + ); + status.beginBusy(message: 'Running query…'); + + status.reportQueryFailure( + duration: const Duration(milliseconds: 3), + message: 'Query failed', + ); + + expect(status.isBusy, isFalse); + expect(status.statusMessage, 'Query failed'); + expect(status.rowCount, isNull); + expect(status.columnCount, isNull); + expect(status.lastQueryDuration, const Duration(milliseconds: 3)); + }); + + test('endBusy after a failure keeps the failure message', () { + status.beginBusy(message: 'Explaining query…'); + status.reportQueryFailure(message: 'Explain failed'); + + status.endBusy(); + + expect(status.statusMessage, 'Explain failed'); + }); + + test('a result message is not cleared by a later endBusy', () { + status.reportQueryResult(rowCount: 1, columnCount: 1, message: '1 row(s).'); + + status.endBusy(); + + expect(status.statusMessage, '1 row(s).'); + }); +}