Skip to content

Commit bd3acea

Browse files
committed
Defer dashboard refreshes until the charts panel is active
Introduces activation logic to NamespaceDashboardViewModel to ensure that data fetching and auto-refresh cycles only run when the dashboard is visible. MainWindowViewModel now manages this lifecycle, deactivating refreshes when switching to other panels like Live Stream or Alerts to reduce unnecessary background operations.
1 parent f3eb8d7 commit bd3acea

3 files changed

Lines changed: 169 additions & 24 deletions

File tree

BusLane.Tests/ViewModels/MainWindowViewModelTests.cs

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,79 @@ public void ActiveWorkspaceModeLabel_WithConnectionStringTab_ReturnsConnectionTy
142142
sut.ActiveWorkspaceModeLabel.Should().Be("Queue connection");
143143
}
144144

145+
[Fact]
146+
public async Task ConnectionStringTab_DoesNotStartDashboardRefreshUntilChartsAreOpened()
147+
{
148+
// Arrange
149+
var preferences = new TestPreferencesService();
150+
var operationsFactory = Substitute.For<IServiceBusOperationsFactory>();
151+
var operations = Substitute.For<IConnectionStringOperations>();
152+
var dashboardRefreshService = Substitute.For<IDashboardRefreshService>();
153+
using var sut = CreateSut(
154+
preferences,
155+
operationsFactory: operationsFactory,
156+
dashboardRefreshService: dashboardRefreshService);
157+
158+
operationsFactory.CreateFromConnectionString(Arg.Any<string>()).Returns(operations);
159+
operations.GetQueueInfoAsync("orders", Arg.Any<CancellationToken>())
160+
.Returns(new QueueInfo(
161+
"orders",
162+
12,
163+
10,
164+
2,
165+
0,
166+
1024,
167+
DateTimeOffset.UtcNow,
168+
false,
169+
TimeSpan.FromDays(14),
170+
TimeSpan.FromMinutes(1)));
171+
172+
var tab = CreateTab("tab-1", preferences);
173+
var connection = SavedConnection.Create(
174+
"Orders",
175+
"Endpoint=sb://orders.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=test",
176+
ConnectionType.Queue,
177+
entityName: "orders");
178+
179+
await tab.ConnectWithConnectionStringAsync(connection, operationsFactory);
180+
sut.ConnectionTabs.Add(tab);
181+
182+
// Act
183+
sut.ActiveTab = tab;
184+
185+
// Assert
186+
_ = dashboardRefreshService.DidNotReceive().RefreshAsync(
187+
"Orders",
188+
operations,
189+
Arg.Any<CancellationToken>());
190+
dashboardRefreshService.DidNotReceive().StartAutoRefresh(
191+
"Orders",
192+
operations,
193+
TimeSpan.FromSeconds(30));
194+
195+
// Act
196+
sut.OpenChartsCommand.Execute(null);
197+
198+
// Assert
199+
_ = dashboardRefreshService.Received(1).RefreshAsync(
200+
"Orders",
201+
operations,
202+
Arg.Any<CancellationToken>());
203+
dashboardRefreshService.Received(1).StartAutoRefresh(
204+
"Orders",
205+
operations,
206+
TimeSpan.FromSeconds(30));
207+
sut.FeaturePanels.ShowCharts.Should().BeTrue();
208+
dashboardRefreshService.ClearReceivedCalls();
209+
210+
// Act
211+
sut.CloseChartsCommand.Execute(null);
212+
213+
// Assert
214+
dashboardRefreshService.Received(1).StopAutoRefresh();
215+
sut.FeaturePanels.ShowCharts.Should().BeFalse();
216+
}
217+
145218
[Fact]
146219
public void ShowNamespaceSelectionPrompt_IsTrueOnlyWhenAzureIsReadyWithoutActiveConnection()
147220
{
@@ -431,11 +504,13 @@ private static MainWindowViewModel CreateSut(
431504
IConnectionStorageService? connectionStorage = null,
432505
IUpdateService? updateService = null,
433506
IAppLockService? appLockService = null,
434-
IBiometricAuthService? biometricAuthService = null)
507+
IBiometricAuthService? biometricAuthService = null,
508+
IServiceBusOperationsFactory? operationsFactory = null,
509+
IDashboardRefreshService? dashboardRefreshService = null)
435510
{
436511
auth ??= Substitute.For<IAzureAuthService>();
437512
var azureResources = Substitute.For<IAzureResourceService>();
438-
var operationsFactory = Substitute.For<IServiceBusOperationsFactory>();
513+
operationsFactory ??= Substitute.For<IServiceBusOperationsFactory>();
439514
connectionStorage ??= Substitute.For<IConnectionStorageService>();
440515
var connectionBackupService = Substitute.For<IConnectionBackupService>();
441516
var versionService = Substitute.For<IVersionService>();
@@ -452,7 +527,7 @@ private static MainWindowViewModel CreateSut(
452527
var logSink = CreateLogSink();
453528

454529
var dashboardPersistenceService = Substitute.For<IDashboardPersistenceService>();
455-
var dashboardRefreshService = Substitute.For<IDashboardRefreshService>();
530+
dashboardRefreshService ??= Substitute.For<IDashboardRefreshService>();
456531
var inboxScoringService = Substitute.For<INamespaceInboxScoringService>();
457532
var inboxReviewStore = Substitute.For<INamespaceInboxReviewStore>();
458533

BusLane/ViewModels/Dashboard/NamespaceDashboardViewModel.cs

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public partial class NamespaceDashboardViewModel : ObservableObject
1616
private readonly IAlertService _alertService;
1717
private IServiceBusOperations? _operations;
1818
private readonly List<NamespaceDashboardSummary> _summaryHistory = [];
19+
private bool _isActive;
1920

2021
[ObservableProperty]
2122
private string _selectedTimeRange = "1 Hour";
@@ -105,17 +106,17 @@ partial void OnSelectedTimeRangeChanged(string value)
105106
}
106107

107108
UpdateCharts();
108-
_ = RefreshAsync();
109+
if (_isActive)
110+
{
111+
_ = RefreshAsync();
112+
}
109113
}
110114

111115
partial void OnAutoRefreshEnabledChanged(bool value)
112116
{
113-
if (value && _operations != null)
117+
if (_isActive && value && _operations != null)
114118
{
115-
_refreshService.StartAutoRefresh(
116-
CurrentNamespaceId ?? "current-namespace",
117-
_operations,
118-
TimeSpan.FromSeconds(RefreshIntervalSeconds));
119+
StartAutoRefresh();
119120
}
120121
else
121122
{
@@ -125,12 +126,9 @@ partial void OnAutoRefreshEnabledChanged(bool value)
125126

126127
partial void OnRefreshIntervalSecondsChanged(int value)
127128
{
128-
if (AutoRefreshEnabled && _operations != null)
129+
if (_isActive && AutoRefreshEnabled && _operations != null)
129130
{
130-
_refreshService.StartAutoRefresh(
131-
CurrentNamespaceId ?? "current-namespace",
132-
_operations,
133-
TimeSpan.FromSeconds(value));
131+
StartAutoRefresh();
134132
}
135133
}
136134

@@ -165,14 +163,18 @@ public void SetOperations(IServiceBusOperations? operations, string? namespaceId
165163

166164
if (_operations != null)
167165
{
168-
_ = RefreshAsync();
166+
if (_isActive)
167+
{
168+
_ = RefreshAsync();
169169

170-
if (AutoRefreshEnabled)
170+
if (AutoRefreshEnabled)
171+
{
172+
StartAutoRefresh();
173+
}
174+
}
175+
else
171176
{
172-
_refreshService.StartAutoRefresh(
173-
CurrentNamespaceId ?? "current-namespace",
174-
_operations,
175-
TimeSpan.FromSeconds(RefreshIntervalSeconds));
177+
_refreshService.StopAutoRefresh();
176178
}
177179
}
178180
else
@@ -181,6 +183,45 @@ public void SetOperations(IServiceBusOperations? operations, string? namespaceId
181183
}
182184
}
183185

186+
/// <summary>
187+
/// Activates dashboard refresh while the dashboard overlay is visible.
188+
/// </summary>
189+
public void Activate()
190+
{
191+
if (_isActive)
192+
{
193+
return;
194+
}
195+
196+
_isActive = true;
197+
198+
if (_operations == null)
199+
{
200+
return;
201+
}
202+
203+
_ = RefreshAsync();
204+
205+
if (AutoRefreshEnabled)
206+
{
207+
StartAutoRefresh();
208+
}
209+
}
210+
211+
/// <summary>
212+
/// Deactivates dashboard refresh when the dashboard overlay is hidden.
213+
/// </summary>
214+
public void Deactivate()
215+
{
216+
if (!_isActive)
217+
{
218+
return;
219+
}
220+
221+
_isActive = false;
222+
_refreshService.StopAutoRefresh();
223+
}
224+
184225
private void OnSummaryUpdated(object? sender, NamespaceDashboardSummary summary)
185226
{
186227
if (!Dispatcher.UIThread.CheckAccess())
@@ -247,6 +288,19 @@ private void OnChartTimeRangeChanged(object? sender, string value)
247288
UpdateCharts();
248289
}
249290

291+
private void StartAutoRefresh()
292+
{
293+
if (_operations == null)
294+
{
295+
return;
296+
}
297+
298+
_refreshService.StartAutoRefresh(
299+
CurrentNamespaceId ?? "current-namespace",
300+
_operations,
301+
TimeSpan.FromSeconds(RefreshIntervalSeconds));
302+
}
303+
250304
private void PruneHistory()
251305
{
252306
var threshold = DateTimeOffset.UtcNow - TimeSpan.FromHours(24);

BusLane/ViewModels/MainWindowViewModel.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,19 +1268,35 @@ private async Task ToggleDeadLetterViewAsync()
12681268
#region Feature Panels (delegated)
12691269

12701270
[RelayCommand]
1271-
private async Task OpenLiveStream() => await FeaturePanels.OpenLiveStream();
1271+
private async Task OpenLiveStream()
1272+
{
1273+
NamespaceDashboard.Deactivate();
1274+
await FeaturePanels.OpenLiveStream();
1275+
}
12721276

12731277
[RelayCommand]
12741278
private void CloseLiveStream() => FeaturePanels.CloseLiveStream();
12751279

12761280
[RelayCommand]
1277-
private void OpenCharts() => FeaturePanels.OpenCharts();
1281+
private void OpenCharts()
1282+
{
1283+
FeaturePanels.OpenCharts();
1284+
NamespaceDashboard.Activate();
1285+
}
12781286

12791287
[RelayCommand]
1280-
private void CloseCharts() => FeaturePanels.CloseCharts();
1288+
private void CloseCharts()
1289+
{
1290+
FeaturePanels.CloseCharts();
1291+
NamespaceDashboard.Deactivate();
1292+
}
12811293

12821294
[RelayCommand]
1283-
private void OpenAlerts() => FeaturePanels.OpenAlerts();
1295+
private void OpenAlerts()
1296+
{
1297+
NamespaceDashboard.Deactivate();
1298+
FeaturePanels.OpenAlerts();
1299+
}
12841300

12851301
[RelayCommand]
12861302
private void CloseAlerts() => FeaturePanels.CloseAlerts();

0 commit comments

Comments
 (0)