Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions robotframework_dashboard/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ def generate_dashboard(
'"placeholder_keywords"',
f'"{self._compress_and_encode(data["keywords"])}"',
)
dashboard_data = dashboard_data.replace(
'"placeholder_exceptions"',
f'"{self._compress_and_encode(data.get("exceptions", []))}"',
)
dashboard_data = dashboard_data.replace(
'"placeholder_amount"', str(quantity)
)
Expand Down
40 changes: 39 additions & 1 deletion robotframework_dashboard/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,15 @@ def get_keywords_length():
self.connection.cursor().execute(KEYWORD_TABLE_UPDATE_OWNER)
self.connection.commit()
keyword_table_length = get_keywords_length()
# exceptions table: added in 1.9.x, safe to create if missing
self.connection.cursor().execute(CREATE_EXCEPTIONS)
self.connection.commit()
else:
self.connection.cursor().execute(CREATE_RUNS)
self.connection.cursor().execute(CREATE_SUITES)
self.connection.cursor().execute(CREATE_TESTS)
self.connection.cursor().execute(CREATE_KEYWORDS)
self.connection.cursor().execute(CREATE_EXCEPTIONS)
self.connection.commit()

def close_database(self):
Expand All @@ -149,6 +153,7 @@ def insert_output_data(
self._insert_suites(output_data["suites"], run_alias, timezone)
self._insert_tests(output_data["tests"], run_alias, timezone)
self._insert_keywords(output_data["keywords"], run_alias, timezone)
self._insert_exceptions(output_data.get("exceptions", []), run_alias, timezone)
except Exception as error:
print(f" ERROR: something went wrong with the database: {error}")

Expand Down Expand Up @@ -216,6 +221,18 @@ def _insert_keywords(self, keywords: list, run_alias: str, timezone: str = ""):
self.connection.executemany(INSERT_INTO_KEYWORDS, full_keywords)
self.connection.commit()

def _insert_exceptions(self, exceptions: list, run_alias: str, timezone: str = ""):
"""Helper function to insert the exception data"""
full_exceptions = []
for exc in exceptions:
exc = list(exc)
if timezone:
exc[0] = f"{exc[0]}{timezone}"
exc.append(run_alias)
full_exceptions.append(tuple(exc))
self.connection.executemany(INSERT_INTO_EXCEPTIONS, full_exceptions)
self.connection.commit()

@staticmethod
def _get_local_timezone_offset():
"""Helper function to get the local machine's timezone offset as a string like +01:00"""
Expand All @@ -239,7 +256,7 @@ def _has_timezone_offset(run_start: str):

def get_data(self):
"""This function gets all the data in the database"""
data, runs, suites, tests, keywords, aliases = {}, [], [], [], [], {}
data, runs, suites, tests, keywords, exceptions, aliases = {}, [], [], [], [], [], {}
name_labels = {}
local_tz = self._get_local_timezone_offset()
alias_counter = 1
Expand Down Expand Up @@ -338,6 +355,21 @@ def get_data(self):
name_prefix_lookup.get(row["run_start"][:19], ""))
keywords.append(row)
data["keywords"] = keywords
# Get exceptions from exceptions table
try:
exception_rows = self.connection.cursor().execute(SELECT_FROM_EXCEPTIONS).fetchall()
for exception_row in exception_rows:
row = self._dict_from_row(exception_row)
if not self._has_timezone_offset(row["run_start"]):
row["run_start"] = f"{row['run_start']}{local_tz}"
row["run_alias"] = aliases.get(row["run_start"],
alias_prefix_lookup.get(row["run_start"][:19], ""))
row["run_name"] = name_labels.get(row["run_start"],
name_prefix_lookup.get(row["run_start"][:19], ""))
exceptions.append(row)
except Exception:
pass # table may not exist in older databases
data["exceptions"] = exceptions
return data

def _dict_from_row(self, row: sqlite3.Row):
Expand Down Expand Up @@ -490,6 +522,12 @@ def _remove_run(self, run_start: str):
self.connection.cursor().execute(
DELETE_FROM_KEYWORDS.format(run_start=run_start)
)
try:
self.connection.cursor().execute(
DELETE_FROM_EXCEPTIONS.format(run_start=run_start)
)
except Exception:
pass # table may not exist in older databases
self.connection.commit()

def vacuum_database(self):
Expand Down
8 changes: 7 additions & 1 deletion robotframework_dashboard/js/filter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { settings, get_run_label } from './variables/settings.js';
import { compareRunIds } from './variables/graphs.js';
import { runs, suites, tests, keywords, unified_dashboard_title } from './variables/data.js';
import { runs, suites, tests, keywords, exceptions, unified_dashboard_title } from './variables/data.js';
import { show_loading_overlay, hide_loading_overlay, strip_tz_suffix } from './common.js';
import { set_local_storage_item } from './localstorage.js';
import {
Expand All @@ -9,6 +9,7 @@ import {
filteredSuites,
filteredTests,
filteredKeywords,
filteredExceptions,
selectedRunSetting,
selectedTagSetting
} from './variables/globals.js';
Expand All @@ -29,16 +30,19 @@ function setup_filtered_data_and_filters() {
filteredSuites = remove_milliseconds(suites)
filteredTests = remove_milliseconds(tests)
filteredKeywords = remove_milliseconds(keywords)
filteredExceptions = remove_milliseconds(exceptions)
// convert timezones if enabled (must run before remove_timezones so the offset is still present)
filteredRuns = convert_timezone(filteredRuns);
filteredSuites = convert_timezone(filteredSuites);
filteredTests = convert_timezone(filteredTests);
filteredKeywords = convert_timezone(filteredKeywords);
filteredExceptions = convert_timezone(filteredExceptions);
// remove timezone display if disabled
filteredRuns = remove_timezones(filteredRuns);
filteredSuites = remove_timezones(filteredSuites);
filteredTests = remove_timezones(filteredTests);
filteredKeywords = remove_timezones(filteredKeywords);
filteredExceptions = remove_timezones(filteredExceptions);
// filter run data
filteredRuns = filter_runs(filteredRuns);
filteredRuns = filter_runtags(filteredRuns);
Expand All @@ -50,13 +54,15 @@ function setup_filtered_data_and_filters() {
filteredSuites = filter_data(filteredSuites);
filteredTests = filter_data(filteredTests);
filteredKeywords = filter_data(filteredKeywords);
filteredExceptions = filter_data(filteredExceptions);
// re-sort all filtered data by wall-clock run_start so mixed-timezone datasets
// appear in the correct chronological order on graphs (timestamps may have been
// converted or had their offsets stripped above, so re-sort here is the source of truth)
filteredRuns = sort_wall_clock(filteredRuns);
filteredSuites = sort_wall_clock(filteredSuites);
filteredTests = sort_wall_clock(filteredTests);
filteredKeywords = sort_wall_clock(filteredKeywords);
filteredExceptions = sort_wall_clock(filteredExceptions);
// set titles with amount of filtered items
const runAmount = Object.keys(filteredRuns).length
const message = `<h6>showing ${runAmount} of ${filteredAmount} runs</h6>`
Expand Down
12 changes: 10 additions & 2 deletions robotframework_dashboard/js/graph_creation/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
create_keyword_most_failed_graph,
create_keyword_most_time_consuming_graph,
create_keyword_most_used_graph,
create_keyword_exceptions_graph,
update_keyword_statistics_graph,
update_keyword_times_run_graph,
update_keyword_total_duration_graph,
Expand All @@ -71,7 +72,8 @@ import {
update_keyword_max_duration_graph,
update_keyword_most_failed_graph,
update_keyword_most_time_consuming_graph,
update_keyword_most_used_graph
update_keyword_most_used_graph,
update_keyword_exceptions_graph
} from "./keyword.js";
import {
create_compare_statistics_graph,
Expand All @@ -86,10 +88,12 @@ import {
create_suite_table,
create_test_table,
create_keyword_table,
create_exception_table,
update_run_table,
update_suite_table,
update_test_table,
update_keyword_table
update_keyword_table,
update_exception_table
} from "./tables.js";

// function that creates all graphs from scratch - used on first load of each tab
Expand Down Expand Up @@ -129,6 +133,7 @@ function create_dashboard_graphs() {
create_keyword_most_failed_graph();
create_keyword_most_time_consuming_graph();
create_keyword_most_used_graph();
create_keyword_exceptions_graph();
} else if (settings.menu.compare) {
create_compare_statistics_graph();
create_compare_suite_duration_graph();
Expand All @@ -138,6 +143,7 @@ function create_dashboard_graphs() {
create_suite_table();
create_test_table();
create_keyword_table();
create_exception_table();
}
}

Expand Down Expand Up @@ -179,6 +185,7 @@ function update_dashboard_graphs() {
update_keyword_most_failed_graph();
update_keyword_most_time_consuming_graph();
update_keyword_most_used_graph();
update_keyword_exceptions_graph();
} else if (settings.menu.compare) {
update_compare_statistics_graph();
update_compare_suite_duration_graph();
Expand All @@ -188,6 +195,7 @@ function update_dashboard_graphs() {
update_suite_table();
update_test_table();
update_keyword_table();
update_exception_table();
}
}

Expand Down
87 changes: 85 additions & 2 deletions robotframework_dashboard/js/graph_creation/keyword.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { settings } from "../variables/settings.js";
import { inFullscreen, inFullscreenGraph } from "../variables/globals.js";
import { inFullscreen, inFullscreenGraph, filteredExceptions } from "../variables/globals.js";
import { get_statistics_graph_data } from "../graph_data/statistics.js";
import { get_duration_graph_data } from "../graph_data/duration.js";
import { get_graph_config } from "../graph_data/graph_config.js";
import { get_exceptions_data } from "../graph_data/exceptions.js";
import { create_chart, update_chart } from "./chart_factory.js";
import { build_most_failed_config, build_most_time_consuming_config } from "./config_helpers.js";
import { update_height } from "../graph_data/helpers.js";

// build functions
function _build_keyword_statistics_config() {
Expand Down Expand Up @@ -51,6 +53,83 @@ function _build_keyword_most_used_config() {
return build_most_time_consuming_config("keywordMostUsed", "keyword", "Keyword", filteredKeywords, "onlyLastRunKeywordMostUsed", "Most Used", true, (info, name) => `${name}: ran ${info.timesRun} times`);
}

function _build_keyword_exceptions_config() {
const data = get_exceptions_data(settings.graphTypes.keywordExceptionsGraphType, filteredExceptions);
const graphData = data[0];
const callbackData = data[1];
const pointMeta = data[2] || null;
var config;
const limit = inFullscreen && inFullscreenGraph.includes("keywordExceptions") ? 50 : 10;
if (settings.graphTypes.keywordExceptionsGraphType == "bar") {
config = get_graph_config("bar", graphData, `Top ${limit}`, "Exception", "Count");
config.options.plugins.legend = { display: false };
config.options.plugins.tooltip = {
callbacks: {
label: function (tooltipItem) {
return callbackData[tooltipItem.label];
},
},
};
config.options.scales.x = {
ticks: {
minRotation: 45,
maxRotation: 45,
callback: function (value, index) {
return this.getLabelForValue(value).slice(0, 40);
},
},
title: {
display: settings.show.axisTitles,
text: "Exception",
},
};
delete config.options.onClick;
} else if (settings.graphTypes.keywordExceptionsGraphType == "timeline") {
config = get_graph_config("timeline", graphData, `Top ${limit}`, "Run", "Exception");
config.options.plugins.tooltip = {
callbacks: {
label: function (context) {
const runLabel = callbackData[context.raw.x[0]];
const exceptionLabel = context.raw.y;
const key = `${exceptionLabel}::${context.raw.x[0]}`;
const meta = pointMeta ? pointMeta[key] : null;
if (!meta) return `Run: ${runLabel}`;
return [
`Run: ${runLabel}`,
`Count: ${meta.amount}`,
`Message: ${meta.message.length > 120 ? meta.message.substring(0, 120) + "..." : meta.message}`,
];
},
},
};
config.options.scales.x = {
ticks: {
minRotation: 45,
maxRotation: 45,
stepSize: 1,
callback: function (value, index, ticks) {
return callbackData[this.getLabelForValue(value)];
},
},
title: {
display: settings.show.axisTitles,
text: "Run",
},
type: "timelineScale",
};
config.options.scales.y.ticks = {
callback: function (value, index, ticks) {
return this.getLabelForValue(value).slice(0, 40);
},
autoSkip: false,
};
delete config.options.onClick;
if (!settings.show.dateLabels) { config.options.scales.x.ticks.display = false }
}
update_height("keywordExceptionsVertical", config.data.labels.length, settings.graphTypes.keywordExceptionsGraphType);
return config;
}

// create functions
function create_keyword_statistics_graph() { create_chart("keywordStatisticsGraph", _build_keyword_statistics_config); }
function create_keyword_times_run_graph() { create_chart("keywordTimesRunGraph", _build_keyword_times_run_config); }
Expand All @@ -61,6 +140,7 @@ function create_keyword_max_duration_graph() { create_chart("keywordMaxDurationG
function create_keyword_most_failed_graph() { create_chart("keywordMostFailedGraph", _build_keyword_most_failed_config); }
function create_keyword_most_time_consuming_graph() { create_chart("keywordMostTimeConsumingGraph", _build_keyword_most_time_consuming_config); }
function create_keyword_most_used_graph() { create_chart("keywordMostUsedGraph", _build_keyword_most_used_config); }
function create_keyword_exceptions_graph() { create_chart("keywordExceptionsGraph", _build_keyword_exceptions_config); }

// update functions
function update_keyword_statistics_graph() { update_chart("keywordStatisticsGraph", _build_keyword_statistics_config); }
Expand All @@ -72,6 +152,7 @@ function update_keyword_max_duration_graph() { update_chart("keywordMaxDurationG
function update_keyword_most_failed_graph() { update_chart("keywordMostFailedGraph", _build_keyword_most_failed_config); }
function update_keyword_most_time_consuming_graph() { update_chart("keywordMostTimeConsumingGraph", _build_keyword_most_time_consuming_config); }
function update_keyword_most_used_graph() { update_chart("keywordMostUsedGraph", _build_keyword_most_used_config); }
function update_keyword_exceptions_graph() { update_chart("keywordExceptionsGraph", _build_keyword_exceptions_config); }

export {
create_keyword_statistics_graph,
Expand All @@ -83,6 +164,7 @@ export {
create_keyword_most_failed_graph,
create_keyword_most_time_consuming_graph,
create_keyword_most_used_graph,
create_keyword_exceptions_graph,
update_keyword_statistics_graph,
update_keyword_times_run_graph,
update_keyword_total_duration_graph,
Expand All @@ -91,5 +173,6 @@ export {
update_keyword_max_duration_graph,
update_keyword_most_failed_graph,
update_keyword_most_time_consuming_graph,
update_keyword_most_used_graph
update_keyword_most_used_graph,
update_keyword_exceptions_graph
};
Loading
Loading