Skip to content

Commit 122eb39

Browse files
Add timezone handling improvements and related functionality across multiple modules
1 parent c5077c7 commit 122eb39

File tree

9 files changed

+58
-20
lines changed

9 files changed

+58
-20
lines changed

robotframework_dashboard/js/common.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,13 @@ function hide_loading_overlay() {
208208
}
209209
}
210210

211+
// Strips the ±HH:MM timezone offset suffix from a run_start string.
212+
// Returns the wall-clock portion (YYYY-MM-DD HH:MM:SS[.fff]) without the tz offset.
213+
function strip_tz_suffix(s) {
214+
const suffix = s.slice(-6);
215+
return /^[+-]\d{2}:\d{2}$/.test(suffix) ? s.slice(0, -6) : s;
216+
}
217+
211218
export {
212219
camelcase_to_underscore,
213220
get_next_folder_level,
@@ -225,5 +232,6 @@ export {
225232
hide_graph_loading,
226233
update_graphs_with_loading,
227234
show_loading_overlay,
228-
hide_loading_overlay
235+
hide_loading_overlay,
236+
strip_tz_suffix
229237
};

robotframework_dashboard/js/eventlisteners.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,9 +325,10 @@ function setup_settings_modal() {
325325
handler(true);
326326
document.getElementById(def.elementId).addEventListener(def.event || "click", () => handler());
327327
});
328-
// Re-populate the date filter pickers when the timezone conversion toggle changes,
328+
// Re-populate the date filter pickers when either timezone toggle changes,
329329
// since the displayed timestamps change and the defaults need to match.
330330
document.getElementById("toggleTimezone").addEventListener("click", () => setup_lowest_highest_dates());
331+
document.getElementById("toggleTimezones").addEventListener("click", () => setup_lowest_highest_dates());
331332
document.getElementById("themeLight").addEventListener("click", () => toggle_theme());
332333
document.getElementById("themeDark").addEventListener("click", () => toggle_theme());
333334

robotframework_dashboard/js/filter.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { settings } from './variables/settings.js';
22
import { compareRunIds } from './variables/graphs.js';
33
import { runs, suites, tests, keywords, unified_dashboard_title } from './variables/data.js';
4-
import { show_loading_overlay, hide_loading_overlay } from './common.js';
4+
import { show_loading_overlay, hide_loading_overlay, strip_tz_suffix } from './common.js';
55
import { set_local_storage_item } from './localstorage.js';
66
import {
77
filteredAmount,
@@ -13,6 +13,16 @@ import {
1313
selectedTagSetting
1414
} from './variables/globals.js';
1515

16+
// Sort an array of run objects by wall-clock run_start (timezone offset stripped),
17+
// ensuring correct chronological order when timestamps span mixed timezone offsets.
18+
function sort_wall_clock(data) {
19+
return [...data].sort((a, b) => {
20+
const ak = strip_tz_suffix(a.run_start);
21+
const bk = strip_tz_suffix(b.run_start);
22+
return ak < bk ? -1 : ak > bk ? 1 : 0;
23+
});
24+
}
25+
1626
// function updates the data in the graphs whenever filters are updated
1727
function setup_filtered_data_and_filters() {
1828
filteredRuns = remove_milliseconds(runs)
@@ -40,6 +50,13 @@ function setup_filtered_data_and_filters() {
4050
filteredSuites = filter_data(filteredSuites);
4151
filteredTests = filter_data(filteredTests);
4252
filteredKeywords = filter_data(filteredKeywords);
53+
// re-sort all filtered data by wall-clock run_start so mixed-timezone datasets
54+
// appear in the correct chronological order on graphs (timestamps may have been
55+
// converted or had their offsets stripped above, so re-sort here is the source of truth)
56+
filteredRuns = sort_wall_clock(filteredRuns);
57+
filteredSuites = sort_wall_clock(filteredSuites);
58+
filteredTests = sort_wall_clock(filteredTests);
59+
filteredKeywords = sort_wall_clock(filteredKeywords);
4360
// set titles with amount of filtered items
4461
const runAmount = Object.keys(filteredRuns).length
4562
const message = `<h6>showing ${runAmount} of ${filteredAmount} runs</h6>`
@@ -220,7 +237,13 @@ function filter_dates(runs) {
220237
return runs; // Return all runs if invalid range
221238
}
222239
return runs.filter(run => {
223-
const runStart = new Date(run.run_start.replace(" ", "T"));
240+
// When not converting timezones, strip any timezone offset so the run_start is treated
241+
// as a plain wall-clock time matching the date picker values (which are also wall-clock).
242+
let rs = run.run_start.replace(" ", "T");
243+
if (!settings.show.convertTimezone) {
244+
rs = strip_tz_suffix(rs);
245+
}
246+
const runStart = new Date(rs);
224247
return runStart >= fromDateTime && runStart <= toDateTime;
225248
});
226249
}

robotframework_dashboard/js/graph_data/duration.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { inFullscreen, inFullscreenGraph } from "../variables/globals.js";
33
import { barConfig, lineConfig } from "../variables/chartconfig.js";
44
import { compareRunIds } from "../variables/graphs.js";
55
import { exclude_from_suite_data } from "./helpers.js";
6+
import { strip_tz_suffix } from "../common.js";
67

78
// function to prepare the data in the correct format for duration graphs
89
function get_duration_graph_data(dataType, graphType, objectDataAttribute, filteredData) {
@@ -61,7 +62,6 @@ function get_duration_graph_data(dataType, graphType, objectDataAttribute, filte
6162
map.set(label, (map.get(label) || 0) + duration);
6263
}
6364
return Array.from(map.entries())
64-
.sort(([a], [b]) => new Date(a) - new Date(b))
6565
.slice(-limit)
6666
.map(([label, total]) => [label, Math.round(total * 100) / 100]);
6767
};
@@ -126,7 +126,10 @@ function get_duration_graph_data(dataType, graphType, objectDataAttribute, filte
126126
for (const value of filteredData) {
127127
if (!should_include(value)) continue;
128128
const name = suiteSelectSuitesCombined && dataType === "suite" ? "All Suites Combined" : value.name;
129-
const run_start = new Date(value.run_start.replace(" ", "T"));
129+
// When not converting timezones, strip any tz offset so x positions are wall-clock
130+
// based and consistent with what the user sees in run_start labels.
131+
const rs = strip_tz_suffix(value.run_start);
132+
const run_start = new Date(rs.replace(" ", "T"));
130133
const val = Math.round(value[objectDataAttribute] * 100) / 100;
131134
if (!sets.has(name)) sets.set(name, [{ x: run_start, y: val, _run_start: value.run_start }]);
132135
else sets.get(name).push({ x: run_start, y: val, _run_start: value.run_start });

robotframework_dashboard/js/graph_data/failed.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { settings } from "../variables/settings.js";
22
import { inFullscreen, inFullscreenGraph } from "../variables/globals.js";
33
import { convert_timeline_data } from "./helpers.js";
44
import { failedConfig } from "../variables/chartconfig.js";
5+
import { strip_tz_suffix } from "../common.js";
56

67
// function to prepare the data in the correct format for most failed graphs
78
function get_most_failed_data(dataType, graphType, filteredData, recent) {
@@ -81,7 +82,7 @@ function get_most_failed_data(dataType, graphType, filteredData, recent) {
8182
runStarts.forEach(runStart => runStartsSet.add(runStart));
8283
count++;
8384
}
84-
const runStarts = Array.from(runStartsSet).sort((a, b) => new Date(a).getTime() - new Date(b).getTime());
85+
const runStarts = Array.from(runStartsSet).sort((a, b) => new Date(strip_tz_suffix(a)).getTime() - new Date(strip_tz_suffix(b)).getTime());
8586
let datasets = [];
8687
let runAxis = 0;
8788
const pointMeta = {};

robotframework_dashboard/js/graph_data/flaky.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { settings } from "../variables/settings.js";
22
import { passedConfig, failedConfig, skippedConfig } from "../variables/chartconfig.js";
33
import { convert_timeline_data } from "./helpers.js";
4+
import { strip_tz_suffix } from "../common.js";
45

56
// function to prepare the data in the correct format for (recent) most flaky test graph
67
function get_most_flaky_data(dataType, graphType, filteredData, ignore, recent, limit) {
@@ -60,7 +61,7 @@ function get_most_flaky_data(dataType, graphType, filteredData, ignore, recent,
6061
});
6162
if (recent) { // do extra filtering to get most recent flaky tests at the top
6263
sortedData.sort(function (a, b) {
63-
return new Date(b[1].failed_run_starts[b[1].failed_run_starts.length - 1].replace(" ", "T")).getTime() - new Date(a[1].failed_run_starts[a[1].failed_run_starts.length - 1].replace(" ", "T")).getTime()
64+
return new Date(strip_tz_suffix(b[1].failed_run_starts[b[1].failed_run_starts.length - 1].replace(" ", "T"))).getTime() - new Date(strip_tz_suffix(a[1].failed_run_starts[a[1].failed_run_starts.length - 1].replace(" ", "T"))).getTime()
6465
})
6566
}
6667

@@ -99,7 +100,7 @@ function get_most_flaky_data(dataType, graphType, filteredData, ignore, recent,
99100
var datasets = [];
100101
var runAxis = 0;
101102
const pointMeta = {};
102-
runStarts = runStarts.sort((a, b) => new Date(a).getTime() - new Date(b).getTime())
103+
runStarts = runStarts.sort((a, b) => new Date(strip_tz_suffix(a)).getTime() - new Date(strip_tz_suffix(b)).getTime())
103104
for (const runStart of runStarts) {
104105
for (const label of labels) {
105106
var foundValues = [];

robotframework_dashboard/js/graph_data/messages.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { inFullscreen, inFullscreenGraph } from "../variables/globals.js";
33
import { failedConfig } from "../variables/chartconfig.js";
44
import { message_config } from "../variables/data.js";
55
import { convert_timeline_data } from "./helpers.js";
6+
import { strip_tz_suffix } from "../common.js";
67

78
// function to prepare the data in the correct format for messages graphs
89
function get_messages_data(dataType, graphType, filteredData) {
@@ -78,7 +79,7 @@ function get_messages_data(dataType, graphType, filteredData) {
7879
runStarts.forEach(runStart => runStartsSet.add(runStart));
7980
count++;
8081
}
81-
const runStarts = Array.from(runStartsSet).sort((a, b) => new Date(a).getTime() - new Date(b).getTime());
82+
const runStarts = Array.from(runStartsSet).sort((a, b) => new Date(strip_tz_suffix(a)).getTime() - new Date(strip_tz_suffix(b)).getTime());
8283
var datasets = [];
8384
let runAxis = 0;
8485
const pointMeta = {};

robotframework_dashboard/js/variables/data.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// prepare input data
2-
const runs = decode_and_decompress("placeholder_runs").sort((a, b) => new Date(a.run_start.replace(" ", "T")).getTime() - new Date(b.run_start.replace(" ", "T")).getTime());
3-
const suites = decode_and_decompress("placeholder_suites").sort((a, b) => new Date(a.run_start.replace(" ", "T")).getTime() - new Date(b.run_start.replace(" ", "T")).getTime());
4-
const tests = decode_and_decompress("placeholder_tests").sort((a, b) => new Date(a.run_start.replace(" ", "T")).getTime() - new Date(b.run_start.replace(" ", "T")).getTime());
5-
const keywords = decode_and_decompress("placeholder_keywords").sort((a, b) => new Date(a.run_start.replace(" ", "T")).getTime() - new Date(b.run_start.replace(" ", "T")).getTime());
2+
const runs = decode_and_decompress("placeholder_runs");
3+
const suites = decode_and_decompress("placeholder_suites");
4+
const tests = decode_and_decompress("placeholder_tests");
5+
const keywords = decode_and_decompress("placeholder_keywords");
66

77
function decode_and_decompress(base64Str) {
88
if (base64Str.includes("placeholder_")) return [];

scripts/example.bat

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ robotdashboard -g -l -o .\atest\resources\outputs\output-20250313-002338.xml:pro
66
robotdashboard -g -l -o .\atest\resources\outputs\output-20250313-002400.xml:dev:project_2 --projectversion 1.2 --timezone=+00:00
77
robotdashboard -g -l -o .\atest\resources\outputs\output-20250313-002431.xml:prod:project_1 --projectversion 1.2
88
robotdashboard -g -l -o .\atest\resources\outputs\output-20250313-002457.xml:dev:project_2 --projectversion 1.2
9-
robotdashboard -g -l -o .\atest\resources\outputs\output-20250313-002528.xml:prod:project_1 --projectversion 2.0 -z -03:00
10-
robotdashboard -g -l -o .\atest\resources\outputs\output-20250313-002549.xml:dev:project_2 --projectversion 2.3 -z -05:00
11-
robotdashboard -g -l -o .\atest\resources\outputs\output-20250313-002636.xml:prod:project_1 --projectversion 2.3 -z -05:00
9+
robotdashboard -g -l -o .\atest\resources\outputs\output-20250313-002528.xml:prod:project_1 --projectversion 2.0 --timezone=-03:00
10+
robotdashboard -g -l -o .\atest\resources\outputs\output-20250313-002549.xml:dev:project_2 --projectversion 2.3 --timezone=-05:00
11+
robotdashboard -g -l -o .\atest\resources\outputs\output-20250313-002636.xml:prod:project_1 --projectversion 2.3 --timezone=-05:00
1212
robotdashboard -g -l -o .\atest\resources\outputs\output-20250313-002703.xml:dev:project_2 --projectversion 2.3 --timezone=-07:00
13-
robotdashboard -g -l -o .\atest\resources\outputs\output-20250313-002739.xml:prod:project_1 -z -07:00
14-
robotdashboard -g -l -o .\atest\resources\outputs\output-20250313-002915.xml:prod:project_1 --projectversion 2.0 -z -08:00
15-
robotdashboard -n robot_dashboard -o .\atest\resources\outputs\output-20250313-003006.xml:prod:project_1 --uselogs -z -09:00
13+
robotdashboard -g -l -o .\atest\resources\outputs\output-20250313-002739.xml:prod:project_1 --timezone=-07:00
14+
robotdashboard -g -l -o .\atest\resources\outputs\output-20250313-002915.xml:prod:project_1 --projectversion 2.0 --timezone=-08:00
15+
robotdashboard -n robot_dashboard -o .\atest\resources\outputs\output-20250313-003006.xml:prod:project_1 --uselogs --timezone=-09:00

0 commit comments

Comments
 (0)