robotframework-dashboard 2.2.0__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. {robotframework_dashboard-2.2.0/robotframework_dashboard.egg-info → robotframework_dashboard-2.3.0}/PKG-INFO +4 -3
  2. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/README.md +3 -0
  3. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/arguments.py +2 -3
  4. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/css/base.css +0 -2
  5. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/css/components.css +39 -5
  6. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/dashboard.py +7 -1
  7. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/database.py +53 -4
  8. robotframework_dashboard-2.3.0/robotframework_dashboard/dependencies/bootstrap.css +6 -0
  9. robotframework_dashboard-2.3.0/robotframework_dashboard/dependencies/bootstrap.js +7 -0
  10. robotframework_dashboard-2.3.0/robotframework_dashboard/dependencies/chart.js +14 -0
  11. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/dependencies/chartjs-adapter-date-fns.js +1 -1
  12. robotframework_dashboard-2.3.0/robotframework_dashboard/dependencies/chartjs-chart-boxplot.js +2 -0
  13. robotframework_dashboard-2.3.0/robotframework_dashboard/dependencies/chartjs-chart-matrix.js +8 -0
  14. robotframework_dashboard-2.3.0/robotframework_dashboard/dependencies/chartjs-plugin-datalabels.js +7 -0
  15. robotframework_dashboard-2.3.0/robotframework_dashboard/dependencies/datatables.css +21 -0
  16. robotframework_dashboard-2.3.0/robotframework_dashboard/dependencies/datatables.js +22 -0
  17. robotframework_dashboard-2.3.0/robotframework_dashboard/dependencies/gridstack.css +1 -0
  18. robotframework_dashboard-2.3.0/robotframework_dashboard/dependencies/gridstack.js +9 -0
  19. robotframework_dashboard-2.3.0/robotframework_dashboard/dependencies/pako.js +2 -0
  20. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/dependencies.py +29 -25
  21. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/admin_page/admin_api.js +21 -3
  22. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/admin_page/admin_eventlisteners.js +0 -1
  23. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/admin_page/admin_information.js +1 -1
  24. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/common.js +24 -15
  25. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/customsections.js +0 -9
  26. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/eventlisteners.js +50 -62
  27. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/filter.js +52 -157
  28. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_creation/all.js +14 -3
  29. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_creation/chart_factory.js +0 -2
  30. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_creation/compare.js +2 -4
  31. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_creation/config_helpers.js +4 -5
  32. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_creation/keyword.js +84 -4
  33. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_creation/overview.js +85 -224
  34. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_creation/run.js +0 -3
  35. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_creation/suite.js +0 -4
  36. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_creation/tables.js +19 -6
  37. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_creation/test.js +3 -4
  38. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_data/duration.js +0 -2
  39. robotframework_dashboard-2.3.0/robotframework_dashboard/js/graph_data/exceptions.js +94 -0
  40. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_data/failed.js +7 -2
  41. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_data/flaky.js +30 -49
  42. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_data/graph_config.js +6 -1
  43. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_data/helpers.js +80 -10
  44. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_data/messages.js +6 -2
  45. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_data/statistics.js +31 -17
  46. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_data/stats.js +6 -0
  47. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_data/tooltip_helpers.js +0 -1
  48. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/information.js +1 -1
  49. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/layout.js +8 -27
  50. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/linkwidgets.js +0 -15
  51. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/localstorage.js +3 -9
  52. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/log.js +1 -1
  53. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/menu.js +10 -40
  54. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/statwidgets.js +0 -25
  55. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/theme.js +0 -9
  56. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/variables/chartconfig.js +5 -0
  57. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/variables/data.js +3 -1
  58. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/variables/globals.js +2 -2
  59. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/variables/graphmetadata.js +17 -7
  60. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/variables/information.js +9 -5
  61. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/variables/settings.js +3 -3
  62. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/variables/statwidgetdefs.js +3 -0
  63. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/licenses/THIRD_PARTY_LICENSES.txt +65 -56
  64. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/processors.py +114 -11
  65. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/queries.py +7 -2
  66. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/robotdashboardlistener.py +0 -6
  67. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/server.py +2 -10
  68. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/templates/admin.html +1 -1
  69. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/templates/dashboard.html +44 -17
  70. robotframework_dashboard-2.3.0/robotframework_dashboard/version.py +1 -0
  71. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0/robotframework_dashboard.egg-info}/PKG-INFO +4 -3
  72. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard.egg-info/SOURCES.txt +1 -0
  73. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/setup.py +4 -3
  74. robotframework_dashboard-2.2.0/robotframework_dashboard/dependencies/bootstrap.css +0 -6
  75. robotframework_dashboard-2.2.0/robotframework_dashboard/dependencies/bootstrap.js +0 -7
  76. robotframework_dashboard-2.2.0/robotframework_dashboard/dependencies/chart.js +0 -20
  77. robotframework_dashboard-2.2.0/robotframework_dashboard/dependencies/chartjs-chart-boxplot.js +0 -2
  78. robotframework_dashboard-2.2.0/robotframework_dashboard/dependencies/chartjs-chart-matrix.js +0 -8
  79. robotframework_dashboard-2.2.0/robotframework_dashboard/dependencies/chartjs-plugin-datalabels.js +0 -7
  80. robotframework_dashboard-2.2.0/robotframework_dashboard/dependencies/datatables.css +0 -18
  81. robotframework_dashboard-2.2.0/robotframework_dashboard/dependencies/datatables.js +0 -25
  82. robotframework_dashboard-2.2.0/robotframework_dashboard/dependencies/gridstack.css +0 -1
  83. robotframework_dashboard-2.2.0/robotframework_dashboard/dependencies/gridstack.js +0 -9
  84. robotframework_dashboard-2.2.0/robotframework_dashboard/dependencies/pako.js +0 -2
  85. robotframework_dashboard-2.2.0/robotframework_dashboard/version.py +0 -1
  86. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/LICENSE +0 -0
  87. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/MANIFEST.in +0 -0
  88. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/pyproject.toml +0 -0
  89. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/__init__.py +0 -0
  90. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/abstractdb.py +0 -0
  91. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/css/colors.css +0 -0
  92. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/css/dark.css +0 -0
  93. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/admin_page/admin_common.js +0 -0
  94. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/admin_page/admin_main.js +0 -0
  95. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/admin_page/admin_theme.js +0 -0
  96. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/database.js +0 -0
  97. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_data/donut.js +0 -0
  98. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_data/duration_deviation.js +0 -0
  99. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_data/heatmap.js +0 -0
  100. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/graph_data/time_consuming.js +0 -0
  101. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/main.js +0 -0
  102. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/variables/graphs.js +0 -0
  103. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/js/variables/svg.js +0 -0
  104. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/main.py +0 -0
  105. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard/robotdashboard.py +0 -0
  106. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard.egg-info/dependency_links.txt +0 -0
  107. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard.egg-info/entry_points.txt +0 -0
  108. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard.egg-info/not-zip-safe +0 -0
  109. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard.egg-info/requires.txt +0 -0
  110. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/robotframework_dashboard.egg-info/top_level.txt +0 -0
  111. {robotframework_dashboard-2.2.0 → robotframework_dashboard-2.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robotframework-dashboard
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: Output processor and dashboard generator for Robot Framework output files
5
5
  Home-page: https://github.com/marketsquare/robotframework-dashboard
6
6
  Author: Tim de Groot
@@ -90,8 +90,9 @@ For all available CLI options see the [Basic CLI docs](https://marketsquare.gith
90
90
  - 🛠️ **Custom Database Classes** - Extend or replace the database processor for custom backends.
91
91
  - 🖥️ **Server Mode** - Host your dashboard for multi-user access and automatic updates.
92
92
  - 🎧 **Listener Integration** - Automatically updates dashboard after every test run.
93
- - 📝 **Message Config Support** - Group similar test failures using regex-based patterns.
94
- - ⚙️ **Configurable Defaults** - Preload dashboard settings via JSON for consistent appearance.
93
+ - 📝 **Message Config Support** - Group similar test failures using regex-based patterns.
94
+ - 🚨 **Exception Tracking** - Track exception messages caught by TRY/EXCEPT blocks across runs, with a dedicated graph and table.
95
+ - ⚙️ **Configurable Defaults** - Preload dashboard settings via JSON for consistent appearance.
95
96
 
96
97
  …and many more advanced features to help you visualize, analyze, and manage your Robot Framework test results with ease!
97
98
 
@@ -57,10 +57,12 @@ For all available CLI options see the [Basic CLI docs](https://marketsquare.gith
57
57
  - 🔎 **Global Filters** - Filter runs by name, tags, date, metadata, or quantity.
58
58
  - ⚖️ **Comparison Mode** - Compare up to 4 runs side by side with visual statistics.
59
59
  - 🔗 **Automatic Log Linking** - Open Robot Framework logs directly from the dashboard.
60
+ - 🔁 **Rerun History** - Merged `--rerunfailed` outputs keep the status of every attempt per test.
60
61
  - 🛠️ **Custom Database Classes** - Extend or replace the database processor for custom backends.
61
62
  - 🖥️ **Server Mode** - Host your dashboard for multi-user access and automatic updates.
62
63
  - 🎧 **Listener Integration** - Automatically updates dashboard after every test run.
63
64
  - 📝 **Message Config Support** - Group similar test failures using regex-based patterns.
65
+ - 🚨 **Exception Tracking** - Track exception messages caught by TRY/EXCEPT blocks across runs, with a dedicated graph and table.
64
66
  - ⚙️ **Configurable Defaults** - Preload dashboard settings via JSON for consistent appearance.
65
67
 
66
68
  …and many more advanced features to help you visualize, analyze, and manage your Robot Framework test results with ease!
@@ -83,6 +85,7 @@ For detailed usage instructions, advanced examples, and full documentation, visi
83
85
  - 🗄️ [**Custom Database Class**](https://marketsquare.github.io/robotframework-dashboard/custom-database-class.html) - Extend or replace the default database backend to suit your storage needs, including SQLite, MySQL, or custom implementations.
84
86
  - 🔔 [**Listener Integration**](https://marketsquare.github.io/robotframework-dashboard/listener-integration.html) - Use a listener to automatically push test results to the dashboard for every executed run, integrating seamlessly into CI/CD pipelines.
85
87
  - 📂 [**Log Linking**](https://marketsquare.github.io/robotframework-dashboard/log-linking.html) - Enable clickable log navigation from dashboard graphs, covering file naming conventions, local and server usage, and remote log uploads.
88
+ - 🔁 [**Reruns**](https://marketsquare.github.io/robotframework-dashboard/reruns.html) - Feed merged `--rerunfailed` outputs to the dashboard and see the attempt history of every re-executed test.
86
89
  - 📈 [**Performance**](https://marketsquare.github.io/robotframework-dashboard/performance.html) - Performance results across different workload sizes, guidance on scaling to hundreds of runs, and tips for large result sets.
87
90
 
88
91
 
@@ -78,9 +78,8 @@ class ArgumentParser:
78
78
  lets users write the combo with the regular comma separator (e.g.
79
79
  '-r "limit=10,tag=nightly"') instead of ';'.
80
80
 
81
- Scope note (matches issue #309): only 'limit' can be scoped by tags.
82
- 'age' + 'tags' is intentionally NOT supported here — combining them
83
- just runs as two independent operations, same as on main.
81
+ Only 'limit' can be scoped by tags; 'age' + 'tags' run as two
82
+ independent operations.
84
83
 
85
84
  Rules:
86
85
  - No tags, or tags without a 'limit' partner -> no combination, every
@@ -213,7 +213,6 @@ body.lock-scroll {
213
213
  cursor: pointer;
214
214
  }
215
215
 
216
- /* ── Info label + small icon (tooltip on label hover) ── */
217
216
  .info-label {
218
217
  display: inline-flex !important;
219
218
  align-items: center;
@@ -283,7 +282,6 @@ body.lock-scroll {
283
282
  display: block;
284
283
  }
285
284
 
286
- /* ── Sidebar (off-canvas menu for overflowed items) ── */
287
285
  .sidenav-backdrop {
288
286
  position: fixed;
289
287
  top: 0; left: 0; right: 0; bottom: 0;
@@ -37,7 +37,7 @@
37
37
  flex-wrap: wrap;
38
38
  align-items: center;
39
39
  column-gap: 0.75rem;
40
- row-gap: 0.35rem;
40
+ row-gap: 6px;
41
41
  }
42
42
 
43
43
  .section-filters .filter-group {
@@ -115,6 +115,37 @@
115
115
  display: block;
116
116
  }
117
117
 
118
+ .filter-search-wrapper {
119
+ position: relative;
120
+ }
121
+
122
+ .filter-search-wrapper .form-control {
123
+ padding-right: 1.5rem;
124
+ }
125
+
126
+ .filter-search-clear {
127
+ position: absolute;
128
+ top: 0;
129
+ right: 0;
130
+ bottom: 0;
131
+ width: 1.5rem;
132
+ display: flex;
133
+ align-items: center;
134
+ justify-content: center;
135
+ padding: 0 0 0.15rem 0;
136
+ border: none;
137
+ background: none;
138
+ color: inherit;
139
+ opacity: 0.6;
140
+ font-size: 1.4rem;
141
+ line-height: 1;
142
+ cursor: pointer;
143
+ }
144
+
145
+ .filter-search-clear:hover {
146
+ opacity: 1;
147
+ }
148
+
118
149
  .filter-profile-bar {
119
150
  border-bottom: 1px solid var(--bs-border-color);
120
151
  }
@@ -295,8 +326,6 @@
295
326
  .grid-stack-item-content.red-bg { background-color: rgba(206, 62, 1, 0.2); }
296
327
  .grid-stack-item-content.yellow-bg { background-color: rgba(254, 216, 79, 0.2); }
297
328
 
298
- /* ─── Custom link widget ─────────────────────────────────────────────────── */
299
-
300
329
  /* Inner layout — mirrors stat-widget-inner */
301
330
  .link-widget-inner {
302
331
  position: relative;
@@ -379,8 +408,6 @@
379
408
  margin-right: 96px;
380
409
  }
381
410
 
382
- /* ─────────────────────────────────────────────────────────────────────────── */
383
-
384
411
  .fullscreen .stat-value {
385
412
  font-size: 4rem;
386
413
  }
@@ -557,6 +584,13 @@ canvas {
557
584
  filter: brightness(0.5);
558
585
  }
559
586
 
587
+ /* Bootstrap >= 5.3.1 no longer spaces the modal header itself (only its .btn-close gets
588
+ margin-left: auto); the dashboard modals use their own button groups, so keep the
589
+ title left and the buttons right */
590
+ .modal-header {
591
+ justify-content: space-between;
592
+ }
593
+
560
594
  #runTag {
561
595
  max-height: 400px;
562
596
  overflow: auto;
@@ -68,6 +68,10 @@ class DashboardGenerator:
68
68
  '"placeholder_keywords"',
69
69
  f'"{self._compress_and_encode(data["keywords"])}"',
70
70
  )
71
+ dashboard_data = dashboard_data.replace(
72
+ '"placeholder_exceptions"',
73
+ f'"{self._compress_and_encode(data.get("exceptions", []))}"',
74
+ )
71
75
  dashboard_data = dashboard_data.replace(
72
76
  '"placeholder_amount"', str(quantity)
73
77
  )
@@ -93,9 +97,11 @@ class DashboardGenerator:
93
97
  f"Robot Framework Dashboard - {str(generation_datetime)[:-7]}",
94
98
  )
95
99
  if message_config:
100
+ # the placeholder sits inside a single-quoted JS string literal that is
101
+ # JSON.parse'd at runtime, so escape backslashes and single quotes
96
102
  dashboard_data = dashboard_data.replace(
97
103
  '"placeholder_message_config"',
98
- str(message_config).replace("'", '"'),
104
+ dumps(message_config).replace("\\", "\\\\").replace("'", "\\'"),
99
105
  )
100
106
  if json_config:
101
107
  dashboard_data = dashboard_data.replace(
@@ -75,6 +75,7 @@ class DatabaseProcessor(AbstractDatabaseProcessor):
75
75
  # run: metadata was added in 1.0.0
76
76
  # keyword: owner was added in 1.2.0
77
77
  # run: project_version was added in 1.3.0
78
+ # test: attempts (rebot --merge rerun history) was added in 2.3.0
78
79
  run_table_length = get_runs_length()
79
80
  if run_table_length == 10: # -> column alias not present
80
81
  self.connection.cursor().execute(RUN_TABLE_UPDATE_ALIAS)
@@ -119,6 +120,10 @@ class DatabaseProcessor(AbstractDatabaseProcessor):
119
120
  self.connection.cursor().execute(TEST_TABLE_UPDATE_ID)
120
121
  self.connection.commit()
121
122
  test_table_length = get_tests_length()
123
+ if test_table_length == 12:
124
+ self.connection.cursor().execute(TEST_TABLE_UPDATE_ATTEMPTS)
125
+ self.connection.commit()
126
+ test_table_length = get_tests_length()
122
127
 
123
128
  keyword_table_length = get_keywords_length()
124
129
  if keyword_table_length == 10:
@@ -129,11 +134,15 @@ class DatabaseProcessor(AbstractDatabaseProcessor):
129
134
  self.connection.cursor().execute(KEYWORD_TABLE_UPDATE_OWNER)
130
135
  self.connection.commit()
131
136
  keyword_table_length = get_keywords_length()
137
+ # exceptions table: added later, safe to create if missing
138
+ self.connection.cursor().execute(CREATE_EXCEPTIONS)
139
+ self.connection.commit()
132
140
  else:
133
141
  self.connection.cursor().execute(CREATE_RUNS)
134
142
  self.connection.cursor().execute(CREATE_SUITES)
135
143
  self.connection.cursor().execute(CREATE_TESTS)
136
144
  self.connection.cursor().execute(CREATE_KEYWORDS)
145
+ self.connection.cursor().execute(CREATE_EXCEPTIONS)
137
146
  self.connection.commit()
138
147
 
139
148
  def close_database(self):
@@ -159,6 +168,7 @@ class DatabaseProcessor(AbstractDatabaseProcessor):
159
168
  self._insert_suites(output_data["suites"], run_alias, timezone)
160
169
  self._insert_tests(output_data["tests"], run_alias, timezone)
161
170
  self._insert_keywords(output_data["keywords"], run_alias, timezone)
171
+ self._insert_exceptions(output_data.get("exceptions", []), run_alias, timezone)
162
172
  except Exception as error:
163
173
  print(f" ERROR: something went wrong with the database: {error}")
164
174
 
@@ -227,6 +237,18 @@ class DatabaseProcessor(AbstractDatabaseProcessor):
227
237
  self.connection.executemany(INSERT_INTO_KEYWORDS, full_keywords)
228
238
  self.connection.commit()
229
239
 
240
+ def _insert_exceptions(self, exceptions: list, run_alias: str, timezone: str = ""):
241
+ """Helper function to insert the exception data"""
242
+ full_exceptions = []
243
+ for exc in exceptions:
244
+ exc = list(exc)
245
+ if timezone:
246
+ exc[0] = f"{exc[0]}{timezone}"
247
+ exc.append(run_alias)
248
+ full_exceptions.append(tuple(exc))
249
+ self.connection.executemany(INSERT_INTO_EXCEPTIONS, full_exceptions)
250
+ self.connection.commit()
251
+
230
252
  @staticmethod
231
253
  def _get_local_timezone_offset():
232
254
  """Helper function to get the local machine's timezone offset as a string like +01:00"""
@@ -250,7 +272,7 @@ class DatabaseProcessor(AbstractDatabaseProcessor):
250
272
 
251
273
  def get_data(self):
252
274
  """This function gets all the data in the database"""
253
- data, runs, suites, tests, keywords, aliases = {}, [], [], [], [], {}
275
+ data, runs, suites, tests, keywords, exceptions, aliases = {}, [], [], [], [], [], {}
254
276
  name_labels = {}
255
277
  local_tz = self._get_local_timezone_offset()
256
278
  alias_counter = 1
@@ -327,6 +349,8 @@ class DatabaseProcessor(AbstractDatabaseProcessor):
327
349
  row["tags"] = ""
328
350
  if row["id"] == None:
329
351
  row["id"] == ""
352
+ if row.get("attempts") == None:
353
+ row["attempts"] = ""
330
354
  # For older entries without timezone in run_start, append current local timezone
331
355
  if not self._has_timezone_offset(row["run_start"]):
332
356
  row["run_start"] = f"{row['run_start']}{local_tz}"
@@ -349,6 +373,21 @@ class DatabaseProcessor(AbstractDatabaseProcessor):
349
373
  name_prefix_lookup.get(row["run_start"][:19], ""))
350
374
  keywords.append(row)
351
375
  data["keywords"] = keywords
376
+ # Get exceptions from exceptions table
377
+ try:
378
+ exception_rows = self.connection.cursor().execute(SELECT_FROM_EXCEPTIONS).fetchall()
379
+ for exception_row in exception_rows:
380
+ row = self._dict_from_row(exception_row)
381
+ if not self._has_timezone_offset(row["run_start"]):
382
+ row["run_start"] = f"{row['run_start']}{local_tz}"
383
+ row["run_alias"] = aliases.get(row["run_start"],
384
+ alias_prefix_lookup.get(row["run_start"][:19], ""))
385
+ row["run_name"] = name_labels.get(row["run_start"],
386
+ name_prefix_lookup.get(row["run_start"][:19], ""))
387
+ exceptions.append(row)
388
+ except Exception:
389
+ pass # table may not exist in older/custom databases
390
+ data["exceptions"] = exceptions
352
391
  return data
353
392
 
354
393
  def _dict_from_row(self, row: sqlite3.Row):
@@ -527,6 +566,15 @@ class DatabaseProcessor(AbstractDatabaseProcessor):
527
566
  console = ""
528
567
  parts = run.split(";")
529
568
  limit = int(parts[0].replace("limit=", ""))
569
+ # A limit below 1 would slice past the start of the candidate list and
570
+ # remove every run (issue #333); removing everything is a separate,
571
+ # explicit action.
572
+ if limit < 1:
573
+ print(
574
+ f" ERROR: no runs were removed as the provided limit ({limit}) must be at least 1"
575
+ )
576
+ console += f" ERROR: no runs were removed as the provided limit ({limit}) must be at least 1\n"
577
+ return console
530
578
  tag_filters = [
531
579
  part.replace("tag=", "") for part in parts[1:] if part.startswith("tag=")
532
580
  ]
@@ -557,9 +605,6 @@ class DatabaseProcessor(AbstractDatabaseProcessor):
557
605
  return console
558
606
 
559
607
  def _remove_by_age(self, run_query: str, run_starts: list):
560
- # NOTE: issue #309 / PR #313 only asked for tag-scoped retention on
561
- # "limit" (see _remove_by_limit); age intentionally has no tag scoping
562
- # here, matching the original issue's proposed direction.
563
608
  console = ""
564
609
  try:
565
610
  clean_query = run_query.replace("age=", "")
@@ -604,6 +649,10 @@ class DatabaseProcessor(AbstractDatabaseProcessor):
604
649
  cursor.execute(DELETE_FROM_SUITES.format(run_start=run_start))
605
650
  cursor.execute(DELETE_FROM_TESTS.format(run_start=run_start))
606
651
  cursor.execute(DELETE_FROM_KEYWORDS.format(run_start=run_start))
652
+ try:
653
+ cursor.execute(DELETE_FROM_EXCEPTIONS.format(run_start=run_start))
654
+ except Exception:
655
+ pass # table may not exist in older/custom databases
607
656
  # Log inside the transaction: if the write fails, the transaction
608
657
  # rolls back and the run is not deleted.
609
658
  if self.log_removed_path and entry: