ductwork 1.4.0 → 1.5.0

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/app/assets/javascripts/ductwork/application.js +50 -41
  4. data/app/assets/stylesheets/ductwork/application.css +859 -146
  5. data/app/controllers/ductwork/application_controller.rb +39 -8
  6. data/app/controllers/ductwork/dashboards_controller.rb +44 -11
  7. data/app/controllers/ductwork/pipelines_controller.rb +47 -7
  8. data/app/controllers/ductwork/step_errors_controller.rb +9 -5
  9. data/app/helpers/ductwork/application_helper.rb +107 -18
  10. data/app/views/ductwork/dashboards/show.html.erb +75 -126
  11. data/app/views/ductwork/pipelines/_graph.html.erb +37 -0
  12. data/app/views/ductwork/pipelines/_step.html.erb +76 -0
  13. data/app/views/ductwork/pipelines/index.html.erb +11 -101
  14. data/app/views/ductwork/pipelines/show.html.erb +62 -285
  15. data/app/views/ductwork/shared/_error.html.erb +7 -0
  16. data/app/views/ductwork/shared/_filter_menu.html.erb +13 -0
  17. data/app/views/ductwork/shared/_pager.html.erb +16 -0
  18. data/app/views/ductwork/shared/_runs_table.html.erb +30 -0
  19. data/app/views/ductwork/shared/_status_tabs.html.erb +11 -0
  20. data/app/views/ductwork/step_errors/index.html.erb +52 -96
  21. data/app/views/layouts/ductwork/application.html.erb +19 -23
  22. data/lib/ductwork/engine.rb +0 -2
  23. data/lib/ductwork/models/pipeline.rb +52 -31
  24. data/lib/ductwork/pipeline_graph/layout.rb +161 -0
  25. data/lib/ductwork/pipeline_graph.rb +231 -0
  26. data/lib/ductwork/version.rb +1 -1
  27. metadata +11 -5
  28. data/app/assets/images/ductwork/octagon-x.svg +0 -1
  29. data/app/assets/stylesheets/ductwork/vendor/flexboxgrid.min.css +0 -1
  30. data/app/assets/stylesheets/ductwork/vendor/pico.min.css +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 01b3ce0336857fcaf0dcaf6beb6b02f002186bd5a94df0c05761cfcd49e8a7e9
4
- data.tar.gz: cbef9120711ddf5a42819f68dd44871965681317695c6244e732e8e0228b4218
3
+ metadata.gz: 9ef52f554cf6549026270cbe5f80e7d7b84b01824b5839d1c5161e1080bdc94a
4
+ data.tar.gz: 7c83791f6a2a555babc3fda4774e2ef5cbd4e821c180b4a8f8627c900c366f76
5
5
  SHA512:
6
- metadata.gz: 315615bb86d68fdcb29bf8dd96d987c79be645a551a85e45825f1a623de9099a8a8ec6e9ef64465eaab259406aa82151a5f76959d10cde2f43bfd7381d5b34bd
7
- data.tar.gz: 11b3af06290fa9c3261ef1cdc5ee98e45e2e103988c3b2b11fe07dc9d873c441b82ddfe3a091f1e5da6b487d0af1343e1d3b021049e01705f1c7f2bb94788050
6
+ metadata.gz: 2492fab3347973f79cd0e2e7d63fa2850ed44567e9ce8ec3370bdb6f7041ee744c2ea01c065d403c379ac85c0f3cc80d2ed2d30409cb1644775e0a67cb6dab4d
7
+ data.tar.gz: 9d3640e4e883e215fbfc0c37acbdbf0fca432b38bb93d9926f8916649486fa442029e22c7ac22713946243c6c8f823fb204d5e36312dfa3bf797fa370953ce85
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Ductwork Changelog
2
2
 
3
+ ## [1.5.0]
4
+
5
+ - fix: re-enqueue the failed step when reviving a branch halted with `job_crashes_exhausted` - revive re-advanced it as if the job had succeeded, passing a missing return value downstream
6
+ - fix: copy each succeeded step's job when reviving a pipeline so a `combine` or `collapse` after the revive receives every sibling's return value - the copied steps had no jobs, so the fan-in silently dropped siblings that had already completed
7
+ - feat: redesign the dashboard with top-level navigation, an overview of halted pipelines and pipelines by class, status tabs with counts, compact expandable step and error rows, and a phone layout
8
+ - feat: draw the workflow on the dashboard's pipeline page in place of the raw definition, with each node showing its step counts by status, retried and crash-recovered steps, and halt reasons, and linking to the matching steps
9
+ - feat: add a node filter to the steps list on the dashboard's pipeline page and show the page position, disabling the next button on the last page
10
+ - fix: stop the dashboard's runtime timer on halted runs and failed steps
11
+ - fix: print error backtraces once on the dashboard - they were followed by a dump of the same lines as an array
12
+ - fix: count dashboard errors per run instead of per pipeline, without loading every step of every listed run
13
+ - fix: filter the dashboard's pipeline page steps by `klass` - the shared filter queried `pipeline_klass`, which only exists on runs, so choosing a class raised `ActiveRecord::StatementInvalid`
14
+ - fix: order the steps list by `started_at` then `id` so steps sharing a timestamp are not repeated or skipped between pages
15
+ - fix: clamp the dashboard's page number at zero - the previous link on the first page pointed at `page=-1`, a negative `OFFSET` that PostgreSQL rejects
16
+ - chore: replace the dashboard's vendored Pico and flexboxgrid stylesheets with a single stylesheet and remove all inline styles
17
+
3
18
  ## [1.4.0]
4
19
 
5
20
  - feat: validate steps and pipelines/workflows during boot of main process
@@ -1,53 +1,62 @@
1
- document.addEventListener('DOMContentLoaded', function() {
2
- const timerElements = document.querySelectorAll('[data-started-at]');
3
-
4
- timerElements.forEach(function(element) {
5
- const startedAt = new Date(element.dataset.startedAt);
6
- const displayElement = element.querySelector('#elapsed-timer');
7
-
8
- function updateTimer() {
9
- const now = new Date();
10
- const elapsed = Math.floor(Math.abs(now - startedAt) / 1000);
11
-
12
- const hours = Math.floor(elapsed / 3600);
13
- const minutes = Math.floor((elapsed % 3600) / 60);
14
- const seconds = elapsed % 60;
1
+ function formatElapsed(milliseconds) {
2
+ const elapsed = Math.floor(Math.max(milliseconds, 0) / 1000);
3
+ const hours = Math.floor(elapsed / 3600);
4
+ const minutes = Math.floor((elapsed % 3600) / 60);
5
+ const seconds = elapsed % 60;
6
+ const parts = [];
7
+
8
+ if (hours > 0) parts.push(`${hours}h`);
9
+ if (minutes > 0) parts.push(`${minutes}m`);
10
+ if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);
11
+
12
+ return parts.join(' ');
13
+ }
14
+
15
+ function startTimers() {
16
+ const timers = document.querySelectorAll('[data-started-at]');
17
+
18
+ function tick() {
19
+ timers.forEach(function(timer) {
20
+ timer.textContent = formatElapsed(Date.now() - new Date(timer.dataset.startedAt));
21
+ });
22
+ }
15
23
 
16
- const hoursStr = hours == 0 ? '' : `${String(hours)}h `;
17
- const minutesStr = minutes == 0 ? '' : `${String(minutes)}m `;
18
- const secondsStr = seconds == 0 ? '' : `${String(seconds)}s`;
24
+ if (timers.length > 0) {
25
+ tick();
26
+ setInterval(tick, 1000);
27
+ }
28
+ }
19
29
 
20
- if (now > startedAt) {
21
- displayElement.textContent = hoursStr + minutesStr + secondsStr;
22
- } else {
23
- displayElement.textContent = 'In ' + hoursStr + minutesStr + secondsStr;
24
- }
25
- }
30
+ function linkRows() {
31
+ document.querySelectorAll('tr[data-href]').forEach(function(row) {
32
+ row.addEventListener('click', function(event) {
33
+ const selectingText = !window.getSelection().isCollapsed;
26
34
 
27
- updateTimer();
28
- setInterval(updateTimer, 1000);
29
- });
35
+ if (selectingText || event.target.closest('a')) return;
30
36
 
31
- document.querySelectorAll('[data-href]').forEach(function(element) {
32
- element.addEventListener('click', function() {
33
- window.location.href = element.dataset.href;
37
+ window.location.href = row.dataset.href;
34
38
  });
35
39
  });
40
+ }
36
41
 
37
- document.querySelectorAll('[data-open-modal]').forEach(function(element) {
38
- element.addEventListener('click', function() {
39
- const modalId = element.dataset.openModal;
40
-
41
- document.getElementById(modalId).setAttribute('open', '');
42
- });
42
+ function closeMenus(except) {
43
+ document.querySelectorAll('details.menu[open]').forEach(function(menu) {
44
+ if (menu !== except) menu.removeAttribute('open');
43
45
  });
46
+ }
44
47
 
45
- document.querySelectorAll('[data-close-modal]').forEach(function(element) {
46
- element.addEventListener('click', function() {
47
- const modalId = element.dataset.closeModal;
48
+ function dismissMenus() {
49
+ document.addEventListener('click', function(event) {
50
+ closeMenus(event.target.closest('details.menu'));
51
+ });
48
52
 
49
- document.getElementById(modalId).removeAttribute('open');
50
- });
53
+ document.addEventListener('keydown', function(event) {
54
+ if (event.key === 'Escape') closeMenus();
51
55
  });
52
- });
56
+ }
53
57
 
58
+ document.addEventListener('DOMContentLoaded', function() {
59
+ startTimers();
60
+ linkRows();
61
+ dismissMenus();
62
+ });