diff --git a/CHANGELOG.md b/CHANGELOG.md index cbdf41a2..60cdbb0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). --- -## [Unreleased] +## [2.5.0](https://github.com/MarketSquare/robotframework-dashboard/releases/tag/v2.5.0) - 2026-10-17 + +### Added +- Custom graphs — build your own graph in any dashboard section (Run, Suite, Test, Keyword and Unified) from the **+** in the section header in Customize mode. Start from one of 11 presets (statistics per run, duration trend, most failed, most flaky, slowest tests, pass rate per version or tag, keyword time per library, top failure messages, failure heatmap, retried tests per run) or from scratch. The builder has a live preview and lets you pick the data (runs, suites, tests, keywords or exceptions), filter it, choose the X axis, a split, a metric (count, sum, average, min, max, median, p90, p95, distinct count, pass rate, passed/failed/skipped counts or status flips), order and limit, and the chart type (line, bar, stacked bar, horizontal bar, donut, heatmap or table, optionally as percentages). Graphs can follow the global and section filters, open in fullscreen, switch to a data table, and be edited, duplicated, removed, resized and shared as JSON. They are stored with the layout, so undo/redo and `--jsonconfig` work for them too +- Notes on test results (opt-in, Settings > Defaults > Test Notes) — right-click a test in the test graphs, or use the pencil in the Tests table or the new Test Notes widget, to note why it failed. A note has a text (links become clickable) and an optional category with a color. Graph bars of noted tests get a border in the category color, and the tooltip shows the note. The Notes manager lists every note, deletes them in bulk, manages categories, exports and imports notes as JSON, and keeps notes of other dashboards or removed runs under **Unmatched** until you delete them. Notes are stored in the browser and a banner warns when its storage is nearly full +- Printing and Save as PDF — every page prints as it looks on screen (theme, filters, layout, custom graphs, widgets and notes) without the on-screen controls. Section filters print as plain values, tiles keep their position and are never split over two pages, tables print every row of their current page, and a graph open in fullscreen prints alone. Pages print in landscape on white paper + +### Fixed +- `/log` on the server only serves logs from the server's log folder or the log next to an output stored in the database, and log resources only from the log's own folder; before, any file the server could read was served. File names in requests must be plain names, uploads are processed in a private temporary folder so they can never overwrite or delete files in the working folder, the 404 page escapes the requested path, and the database queries for removing runs and updating log paths use bound parameters +- Overview project bars no longer go missing when the overview is first opened with a filter active +- The "Rerun" line on the Overview run cards no longer disappears when the timezone conversion or millisecond settings are on +- Clicking a bar in the date histogram of the filter modal no longer leaves out runs from that bar's last minute +- The **AND** mode of custom filters now keeps only runs that have every selected value, instead of acting like **OR** +- Tests whose `rebot --merge` rerun was skipped keep the attempt history before the skip, and their message no longer contains rebot's `Original message:` prefix and HTML escaping +- Server log files with "report" in their name (e.g. `log-reporting-api.html`) are linked to their run again; only a `report` prefix marks a report +- The listener no longer crashes with `--log NONE` and `uploadlog=true`, gives a clear error with `--output NONE`, and prints its normal error instead of a traceback when `limit=` can't reach the server ### Changed -- Uploads via the listener or `/add-output-file` (and `output_data` on `/add-outputs`) are processed in a private temporary folder and no longer touch the server's working folder. A run uploaded without its log therefore no longer links to a `log.html` that happens to sit in the server's working folder; to link logs, upload them with `uploadlog=true` (listener) or `/add-log-file`, or use `--logurl` +- Uploads through the listener or `/add-output-file` (and `output_data` on `/add-outputs`) no longer touch the server's working folder. A run uploaded without its log therefore no longer links to a `log.html` that happens to sit in the server's working folder; to link logs, upload them with `uploadlog=true` (listener) or `/add-log-file`, or use `--logurl` +- `/log` returns status 404 instead of 200 for a log it doesn't serve --- diff --git a/example/robot_dashboard.html b/example/robot_dashboard.html index 61b17139..9d2d82e0 100644 --- a/example/robot_dashboard.html +++ b/example/robot_dashboard.html @@ -136,6 +136,12 @@ .add-stat-widget-header:hover svg, .add-link-widget-header:hover svg, .add-section-header:hover svg, +.add-custom-graph-header:hover svg, +.edit-custom-graph:hover svg, +.duplicate-custom-graph:hover svg, +.delete-custom-graph:hover svg, +.data-custom-graph:hover svg, +.copy-custom-graph:hover svg, .delete-custom-stat-widget:hover svg, .delete-custom-link-widget:hover svg, .delete-custom-section:hover svg, @@ -180,6 +186,11 @@ overflow: hidden; scrollbar-gutter: stable; } +/* Bootstrap's overflow:hidden on would make the body a scroll container, so the sticky navbar +stops pinning to the viewport and jumps up by the current scroll offset. The lock on is enough. */ +body.modal-open { +overflow: visible !important; +} h4, h5, h6 { @@ -210,6 +221,12 @@ .add-stat-widget-header, .add-link-widget-header, .add-section-header, +.add-custom-graph-header, +.edit-custom-graph, +.duplicate-custom-graph, +.delete-custom-graph, +.data-custom-graph, +.copy-custom-graph, .delete-custom-stat-widget, .delete-custom-link-widget, .delete-custom-section, @@ -1205,23 +1222,6 @@ border: none; background-color: transparent; } -@media print { -table { -width: 100%; -page-break-inside: auto; -} -.grid-stack-item-contentvas { -width: 100%; -overflow: hidden; -page-break-inside: avoid; -break-inside: avoid; -display: block; -} -canvas { -height: auto !important; -max-width: 100% !important; -} -} /* === 07-loading.css === */ .loader-wrapper { position: fixed; @@ -1352,161 +1352,667 @@ .list-group-item { background-color: var(--color-card); } -/* === dark.css === */ -.modal-dialog { +/* === 09-custom-graphs.css === */ +/* Custom graphs: grid tiles, data table view and the builder modal */ +.custom-graph-body { +position: relative; +min-height: 0; +} +.custom-graph-message { +margin: auto; +padding: 1rem; +text-align: center; +font-size: 0.85rem; +color: var(--color-text-muted); +} +.custom-graph-table-wrapper { +width: 100%; +overflow: auto; +} +.custom-graph-table { +margin: 0; +font-size: 0.8rem; color: var(--color-text); +--bs-table-bg: transparent; +--bs-table-color: var(--color-text); } -.modal-content { -background: var(--color-modal-bg); +.custom-graph-table th { +position: sticky; +top: 0; +background: var(--color-card); } -.dark-mode .list-group-item:not(.disabled), -.dark-mode .form-label, -.dark-mode .form-control, -.dark-mode .form-select { +/* Builder modal */ +.custom-graph-gallery { +display: grid; +grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); +gap: 0.75rem; +} +.custom-graph-preset { +display: flex; +flex-direction: column; +align-items: flex-start; +gap: 0.25rem; +padding: 0.75rem; +text-align: left; color: var(--color-text); +background: var(--color-section-card-bg); +border: 1px solid var(--color-border); +border-radius: 6px; } -.dark-mode .list-group-item .disabled { -color: var(--color-disabled-text); +.custom-graph-preset:hover, +.custom-graph-preset:focus-visible { +border-color: var(--color-highlight); +background: var(--color-highlight-tint); } -.dark-mode .collapse-icon { +.custom-graph-preset-title { +font-weight: 600; +} +.custom-graph-preset-description { +font-size: 0.8rem; +color: var(--color-text-muted); +} +.custom-graph-preset-chart { +margin-top: auto; +font-size: 0.75rem; +color: var(--color-highlight); +} +.custom-graph-filter-row, +.custom-graph-group { +display: flex; +gap: 0.25rem; +margin-bottom: 0.25rem; +} +.custom-graph-filter-row > .form-select, +.custom-graph-filter-row > .form-control, +.custom-graph-group > .form-select, +.custom-graph-group > .form-control { +flex: 1 1 0; +min-width: 0; +} +.custom-graph-group > .custom-graph-limit { +flex: 0 0 6rem; +} +.custom-graph-viz-btn.active { +border-color: var(--color-highlight); +background: var(--color-highlight); +color: var(--color-on-highlight); +} +.custom-graph-preview { +display: flex; +height: 360px; +padding: 0.5rem; +background: var(--color-section-card-bg); +border: 1px solid var(--color-border); +border-radius: 6px; +} +.custom-graph-preview .custom-graph-body { +flex: 1 1 auto; +display: flex; +width: 100%; +overflow: auto; +} +.custom-graph-errors { +margin-top: 0.5rem; +font-size: 0.8rem; +color: var(--color-failed); +} +.custom-graph-json { +font-size: 0.8rem; +} +/* === 10-notes.css === */ +/* Notes: storage warning, note cells and badges, the Test Notes widget, right-click menu, modals */ +.notes-storage-warning { +position: relative; +padding-right: 3rem; +font-size: 0.875rem; +} +.notes-storage-warning .btn-close { +position: absolute; +top: 50%; +right: 1rem; +transform: translateY(-50%); +} +.note-cell { +display: flex; +align-items: center; +gap: 0.375rem; +min-width: 0; +} +.note-cell .note-text { +max-width: 32rem; +overflow: hidden; +text-overflow: ellipsis; +white-space: nowrap; +} +.note-edit-button, +.note-list-edit-button { +flex: 0 0 auto; +padding: 0.125rem; +line-height: 1; +border: none; color: var(--color-text); } -.dark-mode .stat-label { -font-size: 0.85rem; +.note-edit-button:hover, +.note-list-edit-button:hover { +color: var(--bs-primary); +} +.note-edit-button svg, +.note-list-edit-button svg { +width: 16px; +height: 16px; +} +.note-category-badge { +flex: 0 0 auto; +display: inline-flex; +align-items: center; +gap: 0.25rem; +padding: 0.1rem 0.45rem; +border: 1px solid var(--note-color); +border-radius: 999px; +font-size: 0.75rem; +white-space: nowrap; +} +.note-category-badge::before, +.note-color-dot { +content: ""; +display: inline-block; +width: 0.55rem; +height: 0.55rem; +border-radius: 50%; +background: var(--note-color); +} +.grid-stack-item-content .test-notes-body { +flex-direction: column; +overflow-x: auto; +} +.test-notes-summary { +display: flex; +flex-wrap: wrap; +align-items: center; +gap: 0.375rem; +margin-bottom: 0.5rem; +font-size: 0.8rem; +} +.note-summary-item { +display: inline-flex; +align-items: center; +gap: 0.3rem; +} +.note-summary-separator { color: var(--color-text-muted); } - - - - - -
-
-
- - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
- -