Skip to content

Commit acbd0f6

Browse files
Merge pull request #750 from erikdarlingdata/fix/drill-down-timezone-v2
Fix drill-down timezone and picker display issues
2 parents d2eb877 + 786b8f8 commit acbd0f6

3 files changed

Lines changed: 61 additions & 24 deletions

File tree

Dashboard/Controls/QueryPerformanceContent.xaml.cs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ private static (DateTime start, DateTime end) GetSlicerTimeRange(
5757
/// <summary>Raised when actual plan execution finishes (success or failure).</summary>
5858
public event Action? ActualPlanFinished;
5959

60+
/// <summary>Raised when a drill-down needs the parent to set custom time pickers. Args: (fromUtc, toUtc)</summary>
61+
public event Action<DateTime, DateTime>? DrillDownTimeRangeRequested;
62+
6063
private CancellationTokenSource? _actualPlanCts;
6164

6265
/// <summary>Cancels the in-flight actual plan execution, if any.</summary>
@@ -219,10 +222,16 @@ public QueryPerformanceContent()
219222
{
220223
if (heatmapDrillDown.Tag is DateTime bucketTime)
221224
{
222-
var fromDate = bucketTime.AddMinutes(-5);
223-
var toDate = bucketTime.AddMinutes(10);
225+
// bucketTime is already server time (SQL Server stores collection_time in server local time)
226+
var serverFrom = bucketTime.AddMinutes(-5);
227+
var serverTo = bucketTime.AddMinutes(10);
228+
DrillDownTimeRangeRequested?.Invoke(serverFrom, serverTo);
229+
230+
// Query also uses server time (same as collection_time in SQL Server)
231+
var queryFrom = serverFrom;
232+
var queryTo = serverTo;
224233
SubTabControl.SelectedIndex = 1; // Active Queries
225-
await RefreshActiveQueriesWithRangeAsync(fromDate, toDate);
234+
await RefreshActiveQueriesWithRangeAsync(queryFrom, queryTo);
226235
}
227236
};
228237
}
@@ -324,9 +333,19 @@ private async Task LoadActiveQueriesSlicerAsync()
324333
if (_databaseService == null) return;
325334
try
326335
{
336+
// For narrow time ranges (drill-downs), pad the query by ±1 hour
337+
// so hourly slicer buckets overlap the display range
338+
var queryFrom = _activeQueriesFromDate;
339+
var queryTo = _activeQueriesToDate;
340+
if (queryFrom.HasValue && queryTo.HasValue && (queryTo.Value - queryFrom.Value).TotalHours < 2)
341+
{
342+
queryFrom = queryFrom.Value.AddHours(-1);
343+
queryTo = queryTo.Value.AddHours(1);
344+
}
345+
327346
var data = await _databaseService.GetActiveQuerySlicerDataAsync(
328-
_activeQueriesHoursBack, _activeQueriesFromDate, _activeQueriesToDate);
329-
var (slicerStart, slicerEnd) = GetSlicerTimeRange(_activeQueriesHoursBack, _activeQueriesFromDate, _activeQueriesToDate);
347+
_activeQueriesHoursBack, queryFrom, queryTo);
348+
var (slicerStart, slicerEnd) = GetSlicerTimeRange(_activeQueriesHoursBack, queryFrom, queryTo);
330349
if (data.Count > 0)
331350
ActiveQueriesSlicer.LoadData(data, "Sessions", slicerStart, slicerEnd);
332351
}

Dashboard/ServerTab.xaml.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ public ServerTab(ServerConnection serverConnection, int utcOffsetMinutes = 0)
152152
{
153153
HidePlanLoading();
154154
};
155+
PerformanceTab.DrillDownTimeRangeRequested += (from, to) =>
156+
{
157+
SetDrillDownGlobalRange(from, to);
158+
};
155159
SystemEventsContent.Initialize(_databaseService);
156160
ResourceMetricsContent.Initialize(_databaseService);
157161
ResourceMetricsContent.ChartDrillDownRequested += OnChildChartDrillDown;
@@ -318,11 +322,11 @@ private void InitializeTimeComboBoxes()
318322

319323
private void SetPickersFromDateTime(DateTime serverTime, DatePicker datePicker, ComboBox hourCombo, ComboBox minuteCombo)
320324
{
321-
// Convert server time to local time for display in UI pickers
322-
var localTime = Helpers.ServerTimeHelper.ToLocalTime(serverTime);
323-
datePicker.SelectedDate = localTime.Date;
324-
hourCombo.SelectedIndex = localTime.Hour;
325-
minuteCombo.SelectedIndex = localTime.Minute / 15; // Round down to nearest 15-min interval
325+
// Display in the current time mode (server time, local time, or UTC)
326+
var displayTime = Helpers.ServerTimeHelper.ConvertForDisplay(serverTime, Helpers.ServerTimeHelper.CurrentDisplayMode);
327+
datePicker.SelectedDate = displayTime.Date;
328+
hourCombo.SelectedIndex = displayTime.Hour;
329+
minuteCombo.SelectedIndex = displayTime.Minute / 15;
326330
}
327331

328332
private void SetupAutoRefresh()

Lite/Controls/ServerTab.xaml.cs

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3179,18 +3179,23 @@ private async void OnDeadlockDrillDown(DateTime time)
31793179

31803180
private async void OnHeatmapDrillDown(DateTime bucketTimeUtc)
31813181
{
3182-
// The time bucket is in UTC — convert to server time for the drill-down
31833182
var serverTime = bucketTimeUtc.AddMinutes(UtcOffsetMinutes);
31843183
var fromDate = serverTime.AddMinutes(-5);
3185-
var toDate = serverTime.AddMinutes(10); // 5-min bucket + 5-min padding
3184+
var toDate = serverTime.AddMinutes(10);
3185+
3186+
AppLogger.Info("DrillDown", $"OnHeatmapDrillDown: bucketTimeUtc={bucketTimeUtc:O}, UtcOffsetMinutes={UtcOffsetMinutes}, serverTime={serverTime:O}, fromDate={fromDate:O}, toDate={toDate:O}");
31863187

31873188
SetDrillDownTimeRange(fromDate, toDate);
31883189

31893190
MainTabControl.SelectedIndex = 2; // Queries
31903191
QueriesSubTabControl.SelectedIndex = 1; // Active Queries
3192+
3193+
AppLogger.Info("DrillDown", $"Calling GetLatestQuerySnapshotsAsync with fromDate={fromDate:O}, toDate={toDate:O}");
31913194
var snapshots = await _dataService.GetLatestQuerySnapshotsAsync(_serverId, 0, fromDate, toDate);
3195+
AppLogger.Info("DrillDown", $"Got {snapshots.Count} snapshots");
3196+
31923197
_querySnapshotsFilterMgr!.UpdateData(snapshots);
3193-
LiveSnapshotIndicator.Text = $"Drill-down: {ServerTimeHelper.FormatServerTime(fromDate.AddMinutes(-UtcOffsetMinutes), "HH:mm")} \u2192 {ServerTimeHelper.FormatServerTime(toDate.AddMinutes(-UtcOffsetMinutes), "HH:mm")}";
3198+
LiveSnapshotIndicator.Text = $"Drill-down: {fromDate:HH:mm} \u2192 {toDate:HH:mm} (server time)";
31943199
_ = LoadActiveQueriesSlicerAsync();
31953200
}
31963201

@@ -3200,21 +3205,21 @@ private async void OnHeatmapDrillDown(DateTime bucketTimeUtc)
32003205
/// </summary>
32013206
private void SetDrillDownTimeRange(DateTime fromServer, DateTime toServer)
32023207
{
3203-
// Convert server time to local time for the pickers
3204-
var fromLocal = ServerTimeHelper.ToLocalTime(fromServer);
3205-
var toLocal = ServerTimeHelper.ToLocalTime(toServer);
3208+
// Display in the current time mode (server time, local time, or UTC)
3209+
var fromDisplay = ServerTimeHelper.ConvertForDisplay(fromServer, ServerTimeHelper.CurrentDisplayMode);
3210+
var toDisplay = ServerTimeHelper.ConvertForDisplay(toServer, ServerTimeHelper.CurrentDisplayMode);
32063211

32073212
// Switch to Custom without triggering a refresh
32083213
_isRefreshing = true;
32093214
try
32103215
{
32113216
TimeRangeCombo.SelectedIndex = 5; // Custom
3212-
FromDatePicker.SelectedDate = fromLocal.Date;
3213-
FromHourCombo.SelectedIndex = fromLocal.Hour;
3214-
FromMinuteCombo.SelectedIndex = fromLocal.Minute / 15;
3215-
ToDatePicker.SelectedDate = toLocal.Date;
3216-
ToHourCombo.SelectedIndex = toLocal.Hour;
3217-
ToMinuteCombo.SelectedIndex = toLocal.Minute / 15;
3217+
FromDatePicker.SelectedDate = fromDisplay.Date;
3218+
FromHourCombo.SelectedIndex = fromDisplay.Hour;
3219+
FromMinuteCombo.SelectedIndex = fromDisplay.Minute / 15;
3220+
ToDatePicker.SelectedDate = toDisplay.Date;
3221+
ToHourCombo.SelectedIndex = toDisplay.Hour;
3222+
ToMinuteCombo.SelectedIndex = toDisplay.Minute / 15;
32183223

32193224
// Make pickers visible
32203225
var visibility = Visibility.Visible;
@@ -4615,10 +4620,19 @@ private async System.Threading.Tasks.Task LoadActiveQueriesSlicerAsync()
46154620
}
46164621
}
46174622

4618-
var data = await _dataService.GetActiveQuerySlicerDataAsync(_serverId, hoursBack, fromDate, toDate);
4623+
// For narrow time ranges (drill-downs), pad the query by ±1 hour
4624+
// so hourly slicer buckets overlap the display range
4625+
DateTime? queryFrom = fromDate, queryTo = toDate;
4626+
if (fromDate.HasValue && toDate.HasValue && (toDate.Value - fromDate.Value).TotalHours < 2)
4627+
{
4628+
queryFrom = fromDate.Value.AddHours(-1);
4629+
queryTo = toDate.Value.AddHours(1);
4630+
}
4631+
4632+
var data = await _dataService.GetActiveQuerySlicerDataAsync(_serverId, hoursBack, queryFrom, queryTo);
46194633
_activeQueriesSlicerData = data;
46204634
_activeQueriesSlicerMetric = "Sessions";
4621-
var (slicerStart, slicerEnd) = GetSlicerTimeRange(hoursBack, fromDate, toDate);
4635+
var (slicerStart, slicerEnd) = GetSlicerTimeRange(hoursBack, queryFrom, queryTo);
46224636
if (data.Count > 0)
46234637
ActiveQueriesSlicer.LoadData(data, "Sessions", slicerStart, slicerEnd);
46244638
}

0 commit comments

Comments
 (0)