jirametrics 2.6 → 2.12pre9

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/lib/jirametrics/aggregate_config.rb +9 -4
  3. data/lib/jirametrics/aging_work_bar_chart.rb +13 -11
  4. data/lib/jirametrics/aging_work_in_progress_chart.rb +105 -41
  5. data/lib/jirametrics/aging_work_table.rb +54 -7
  6. data/lib/jirametrics/blocked_stalled_change.rb +1 -1
  7. data/lib/jirametrics/board.rb +47 -13
  8. data/lib/jirametrics/board_config.rb +7 -2
  9. data/lib/jirametrics/board_movement_calculator.rb +155 -0
  10. data/lib/jirametrics/change_item.rb +19 -8
  11. data/lib/jirametrics/chart_base.rb +63 -27
  12. data/lib/jirametrics/css_variable.rb +1 -1
  13. data/lib/jirametrics/cycletime_config.rb +59 -8
  14. data/lib/jirametrics/cycletime_histogram.rb +68 -3
  15. data/lib/jirametrics/cycletime_scatterplot.rb +3 -6
  16. data/lib/jirametrics/daily_wip_by_age_chart.rb +2 -4
  17. data/lib/jirametrics/daily_wip_by_blocked_stalled_chart.rb +2 -2
  18. data/lib/jirametrics/daily_wip_by_parent_chart.rb +0 -4
  19. data/lib/jirametrics/daily_wip_chart.rb +7 -9
  20. data/lib/jirametrics/data_quality_report.rb +219 -41
  21. data/lib/jirametrics/dependency_chart.rb +3 -4
  22. data/lib/jirametrics/download_config.rb +2 -2
  23. data/lib/jirametrics/downloader.rb +57 -37
  24. data/lib/jirametrics/estimate_accuracy_chart.rb +35 -12
  25. data/lib/jirametrics/estimation_configuration.rb +25 -0
  26. data/lib/jirametrics/examples/aggregated_project.rb +3 -6
  27. data/lib/jirametrics/examples/standard_project.rb +14 -13
  28. data/lib/jirametrics/expedited_chart.rb +7 -8
  29. data/lib/jirametrics/exporter.rb +28 -13
  30. data/lib/jirametrics/file_config.rb +23 -6
  31. data/lib/jirametrics/file_system.rb +41 -4
  32. data/lib/jirametrics/flow_efficiency_scatterplot.rb +111 -0
  33. data/lib/jirametrics/groupable_issue_chart.rb +1 -3
  34. data/lib/jirametrics/html/aging_work_bar_chart.erb +3 -12
  35. data/lib/jirametrics/html/aging_work_in_progress_chart.erb +22 -5
  36. data/lib/jirametrics/html/aging_work_table.erb +6 -4
  37. data/lib/jirametrics/html/cycletime_histogram.erb +74 -0
  38. data/lib/jirametrics/html/cycletime_scatterplot.erb +1 -10
  39. data/lib/jirametrics/html/daily_wip_chart.erb +1 -10
  40. data/lib/jirametrics/html/expedited_chart.erb +1 -10
  41. data/lib/jirametrics/html/flow_efficiency_scatterplot.erb +85 -0
  42. data/lib/jirametrics/html/hierarchy_table.erb +1 -1
  43. data/lib/jirametrics/html/index.css +28 -5
  44. data/lib/jirametrics/html/index.erb +8 -4
  45. data/lib/jirametrics/html/sprint_burndown.erb +1 -10
  46. data/lib/jirametrics/html/throughput_chart.erb +1 -10
  47. data/lib/jirametrics/html_report_config.rb +33 -23
  48. data/lib/jirametrics/issue.rb +170 -54
  49. data/lib/jirametrics/jira_gateway.rb +16 -3
  50. data/lib/jirametrics/project_config.rb +251 -135
  51. data/lib/jirametrics/self_or_issue_dispatcher.rb +2 -0
  52. data/lib/jirametrics/sprint_burndown.rb +38 -36
  53. data/lib/jirametrics/sprint_issue_change_data.rb +3 -3
  54. data/lib/jirametrics/status.rb +81 -26
  55. data/lib/jirametrics/status_collection.rb +77 -39
  56. data/lib/jirametrics/throughput_chart.rb +1 -1
  57. data/lib/jirametrics/value_equality.rb +2 -2
  58. data/lib/jirametrics.rb +23 -6
  59. metadata +11 -13
  60. data/lib/jirametrics/discard_changes_before.rb +0 -37
  61. data/lib/jirametrics/html/data_quality_report.erb +0 -126
@@ -6,7 +6,7 @@ new Chart(document.getElementById(<%= chart_id.inspect %>).getContext('2d'),
6
6
  {
7
7
  type: 'bar',
8
8
  data: {
9
- labels: [<%= column_headings.collect(&:inspect).join(',') %>],
9
+ labels: [<%= @board_columns.collect { |c| c.name.inspect }.join(',') %>],
10
10
  datasets: <%= JSON.generate(data_sets) %>
11
11
  },
12
12
  options: {
@@ -22,8 +22,10 @@ new Chart(document.getElementById(<%= chart_id.inspect %>).getContext('2d'),
22
22
  labelString: 'Date Completed'
23
23
  },
24
24
  grid: {
25
- color: <%= CssVariable['--grid-line-color'].to_json %>
25
+ color: <%= CssVariable['--grid-line-color'].to_json %>,
26
+ z: 1 // draw the grid lines on top of the bars
26
27
  },
28
+ stacked: true
27
29
  },
28
30
  y: {
29
31
  scaleLabel: {
@@ -35,8 +37,11 @@ new Chart(document.getElementById(<%= chart_id.inspect %>).getContext('2d'),
35
37
  text: 'Age in days'
36
38
  },
37
39
  grid: {
38
- color: <%= CssVariable['--grid-line-color'].to_json %>
40
+ color: <%= CssVariable['--grid-line-color'].to_json %>,
41
+ z: 1 // draw the grid lines on top of the bars
39
42
  },
43
+ stacked: true,
44
+ max: <%= (@max_age * 1.1).to_i %>
40
45
  }
41
46
  },
42
47
  plugins: {
@@ -44,14 +49,26 @@ new Chart(document.getElementById(<%= chart_id.inspect %>).getContext('2d'),
44
49
  callbacks: {
45
50
  label: function(context) {
46
51
  if( typeof(context.dataset.data[context.dataIndex]) == "number" ) {
47
- return "85% of the issues, leave this column in "+context.dataset.data[context.dataIndex]+" days";
52
+ let full_data = <%= @bar_data.inspect %>;
53
+ let columnIndex = context.dataIndex;
54
+ let rowIndex = context.datasetIndex - <%= @row_index_offset %>;
55
+ return context.dataset.label + " of completed work items left this column in " +full_data[rowIndex][columnIndex] + " days or less";
48
56
  }
49
57
  else {
50
- return context.dataset.data[context.dataIndex].title
58
+ return context.dataset.data[context.dataIndex].title;
51
59
  }
52
60
  }
53
61
  }
62
+ },
63
+ legend: {
64
+ labels: {
65
+ filter: function(item, chart) {
66
+ // Logic to remove a particular legend item goes here
67
+ return !item.text.includes('%');
68
+ }
69
+ }
54
70
  }
71
+
55
72
  }
56
73
  }
57
74
  });
@@ -1,11 +1,12 @@
1
1
  <table class='standard'>
2
2
  <thead>
3
3
  <tr>
4
- <th>Age (days)</th>
5
- <th>E</th>
6
- <th>B</th>
4
+ <th title="Age in days">Age</th>
5
+ <th title="Expedited">E</th>
6
+ <th title="Blocked / Stalled">B/S</th>
7
7
  <th>Issue</th>
8
8
  <th>Status</th>
9
+ <th>Forecast</th>
9
10
  <th>Fix versions</th>
10
11
  <% if any_scrum_boards %>
11
12
  <th>Sprints</th>
@@ -40,7 +41,8 @@
40
41
  </div>
41
42
  <% end %>
42
43
  </td>
43
- <td><%= format_status issue.status.name, board: issue.board %></td>
44
+ <td><%= format_status issue.status, board: issue.board %></td>
45
+ <td><%= dates_text(issue) %></td>
44
46
  <td><%= fix_versions_text(issue) %></td>
45
47
  <% if any_scrum_boards %>
46
48
  <td><%= sprints_text(issue) %></td>
@@ -1,6 +1,57 @@
1
1
  <div class="chart">
2
2
  <canvas id="<%= chart_id %>" width="<%= canvas_width %>" height="<%= canvas_height %>"></canvas>
3
3
  </div>
4
+ <%
5
+ if show_stats
6
+ link_id = next_id
7
+ issues_id = next_id
8
+ %>
9
+ [<a id='<%= link_id %>' href="#" onclick='expand_collapse("<%= link_id %>", "<%= issues_id %>"); return false;'>Show details</a>]
10
+ <div id="<%= issues_id %>" style="display: none;">
11
+ <div>
12
+ <table class="standard">
13
+ <tr>
14
+ <th>Issue Type</th>
15
+ <th>Min</th>
16
+ <th>Max</th>
17
+ <th>Avg</th>
18
+ <th>Mode</th>
19
+ <% percentiles.each do |p| %>
20
+ <th><%= p %>th</th>
21
+ <% end %>
22
+ </tr>
23
+ <% the_stats.each do |k, v| %>
24
+ <tr>
25
+ <td><%= k %></td>
26
+ <td style="text-align: right;"><%= v[:min] %></td>
27
+ <td style="text-align: right;"><%= v[:max] %></td>
28
+ <td style="text-align: right;"><%= sprintf('%.2f', v[:average]) %></td>
29
+ <td><%= v[:mode].join(', ') %></td>
30
+ <% percentiles.each do |p| %>
31
+ <td style="text-align: right;"><%= v[:percentiles][p] %></td>
32
+ <% end %>
33
+ </tr>
34
+ <% end %>
35
+ </table>
36
+ </div>
37
+ <div>
38
+ <p>These statistics help understand the <i>"shape"</i> of the cycletime histogram distribution, to help us with predictions.</p>
39
+ <ul>
40
+ <li><b>Min & Max:</b> the observed spread for the data set. Useful to judge how wide the variation is. </li>
41
+ <li><b>Average:</b> the arithmetic mean of the data set. Useful as a <i>"typical representative"</i> of the complete set.</li>
42
+ <li><b>Mode:</b> the most repeated value(s) in the data set. This is the value we're most likely to remember. </li>
43
+ <li><b>Percentiles:</b> they partition the data set. If X is the Nth percentile, it means that N% of cycletime values are X or less. Typical percentiles of interest are:</li>
44
+ <ul>
45
+ <li><b>50%</b>: also known as the <b>Median</b>. Useful to establish short feedback loops, to monitor that it's not drifting to the right.</li>
46
+ <li><b>85%</b>: useful to establish service level expectations, accounting for rare events..</li>
47
+ <li><b>98% (or higher)</b>: useful to gauge worst case expectations..</li>
48
+ </ul>
49
+ </ul>
50
+ </div>
51
+ </div>
52
+ <%
53
+ end
54
+ %>
4
55
  <script>
5
56
  new Chart(document.getElementById('<%= chart_id %>').getContext('2d'),
6
57
  {
@@ -21,6 +72,8 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'),
21
72
  grid: {
22
73
  color: <%= CssVariable['--grid-line-color'].to_json %>
23
74
  },
75
+ min: 0,
76
+ offset: false, // Gets rid of the ugly padding on left.
24
77
  },
25
78
  y: {
26
79
  stacked: true,
@@ -34,6 +87,27 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'),
34
87
  }
35
88
  },
36
89
  plugins: {
90
+ annotation: {
91
+ annotations: {
92
+ <%
93
+ results = the_stats[:all][:percentiles]
94
+ results.each do |percentile, value|
95
+ %>
96
+ percentile<%= percentile.to_s %>: {
97
+ type: 'line',
98
+ scaleID: 'x',
99
+ value: <%= value %>,
100
+ borderWidth: 1,
101
+ drawTime: 'beforeDatasetsDraw',
102
+ label: {
103
+ enabled: true,
104
+ content: '<%= "#{percentile}%" %>',
105
+ position: 'start',
106
+ }
107
+ },
108
+ <% end %>
109
+ },
110
+ },
37
111
  tooltip: {
38
112
  callbacks: {
39
113
  label: function(context) {
@@ -53,16 +53,7 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
53
53
  autocolors: false,
54
54
  annotation: {
55
55
  annotations: {
56
- <% holidays.each_with_index do |range, index| %>
57
- holiday<%= index %>: {
58
- drawTime: 'beforeDraw',
59
- type: 'box',
60
- xMin: '<%= range.begin %>T00:00:00',
61
- xMax: '<%= range.end %>T23:59:59',
62
- backgroundColor: <%= CssVariable.new('--non-working-days-color').to_json %>,
63
- borderColor: <%= CssVariable.new('--non-working-days-color').to_json %>
64
- },
65
- <% end %>
56
+ <%= working_days_annotation %>
66
57
 
67
58
  <% @percentage_lines.each_with_index do |args, index| %>
68
59
  <% percent, color = args %>
@@ -50,16 +50,7 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'),
50
50
  },
51
51
  annotation: {
52
52
  annotations: {
53
- <% holidays.each_with_index do |range, index| %>
54
- holiday<%= index %>: {
55
- drawTime: 'beforeDraw',
56
- type: 'box',
57
- xMin: '<%= range.begin %>T00:00:00',
58
- xMax: '<%= range.end %>T23:59:59',
59
- backgroundColor: <%= CssVariable.new('--non-working-days-color').to_json %>,
60
- borderColor: <%= CssVariable.new('--non-working-days-color').to_json %>
61
- },
62
- <% end %>
53
+ <%= working_days_annotation %>
63
54
  }
64
55
  },
65
56
  legend: {
@@ -55,16 +55,7 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
55
55
  autocolors: false,
56
56
  annotation: {
57
57
  annotations: {
58
- <% holidays.each_with_index do |range, index| %>
59
- holiday<%= index %>: {
60
- drawTime: 'beforeDraw',
61
- type: 'box',
62
- xMin: '<%= range.begin %>T00:00:00',
63
- xMax: '<%= range.end %>T23:59:59',
64
- backgroundColor: <%= CssVariable.new('--non-working-days-color').to_json %>,
65
- borderColor: <%= CssVariable.new('--non-working-days-color').to_json %>
66
- },
67
- <% end %>
58
+ <%= working_days_annotation %>
68
59
  }
69
60
  }
70
61
  }
@@ -0,0 +1,85 @@
1
+ <div class="chart">
2
+ <canvas id="<%= chart_id %>" width="<%= canvas_width %>" height="<%= canvas_height %>"></canvas>
3
+ </div>
4
+ <script>
5
+ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
6
+ type: 'scatter',
7
+ data: {
8
+ datasets: <%= JSON.generate(data_sets) %>
9
+ },
10
+ options: {
11
+ title: {
12
+ display: true,
13
+ text: "Cycletime Scatterplot"
14
+ },
15
+ responsive: <%= canvas_responsive? %>, // If responsive is true then it fills the screen
16
+ scales: {
17
+ x: {
18
+ scaleLabel: {
19
+ display: true,
20
+ labelString: 'Days'
21
+ },
22
+ title: {
23
+ display: true,
24
+ text: 'Total time (days)'
25
+ },
26
+ grid: {
27
+ color: <%= CssVariable['--grid-line-color'].to_json %>
28
+ },
29
+
30
+ },
31
+ y: {
32
+ scaleLabel: {
33
+ display: true,
34
+ labelString: 'Percentage',
35
+ min: 0,
36
+ max: <%= @highest_cycletime %>
37
+ },
38
+ title: {
39
+ display: true,
40
+ text: 'Time adding value (days)'
41
+ },
42
+ grid: {
43
+ color: <%= CssVariable['--grid-line-color'].to_json %>
44
+ },
45
+ }
46
+ },
47
+ plugins: {
48
+ tooltip: {
49
+ callbacks: {
50
+ label: function(context) {
51
+ return context.dataset.data[context.dataIndex].title
52
+ }
53
+ }
54
+ },
55
+ autocolors: false,
56
+ legend: {
57
+ onClick: (evt, legendItem, legend) => {
58
+ // Find the datasetMeta that corresponds to the item clicked
59
+ var i = 0
60
+ while(legendItem.text != legend.chart.getDatasetMeta(i).label) {
61
+ i++;
62
+ }
63
+ nextVisibility = !!legend.chart.getDatasetMeta(i).hidden;
64
+
65
+ // Hide/show the 85% line for that dataset
66
+ legend.chart.options.plugins.annotation.annotations["line"+(i/2)].display = nextVisibility;
67
+
68
+ // Hide/show the trendline for this dataset, if they were enabled. The trendline is always
69
+ // there but not always visible.
70
+ legend.chart.setDatasetVisibility(i+1, <%= !!@show_trend_lines %> && nextVisibility);
71
+
72
+ // Still run the default behaviour
73
+ Chart.defaults.plugins.legend.onClick(evt, legendItem, legend);
74
+ },
75
+ labels: {
76
+ filter: function(item, chart) {
77
+ // Logic to remove a particular legend item goes here
78
+ return !item.text.includes('Trendline');
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
84
+ });
85
+ </script>
@@ -22,7 +22,7 @@
22
22
  </span>
23
23
  </td>
24
24
  <td><span style="color: <%= color %>; font-style: italic;"><%= issue.summary[0..80] %></span></td>
25
- <td><%= format_status issue.status.name, board: issue.board %></td>
25
+ <td><%= format_status issue.status, board: issue.board %></td>
26
26
  </tr>
27
27
  <% end %>
28
28
  </tbody>
@@ -2,6 +2,7 @@
2
2
  --body-background: white;
3
3
  --default-text-color: black;
4
4
  --grid-line-color: lightgray;
5
+ --warning-banner: yellow;
5
6
 
6
7
  --cycletime-scatterplot-overall-trendline-color: gray;
7
8
 
@@ -19,6 +20,7 @@
19
20
  --status-category-todo-color: gray;
20
21
  --status-category-inprogress-color: #2663ff;
21
22
  --status-category-done-color: #00ff00;
23
+ --status-category-unknown-color: black;
22
24
 
23
25
  --aging-work-bar-chart-percentage-line-color: red;
24
26
  --aging-work-bar-chart-separator-color: white;
@@ -26,8 +28,16 @@
26
28
  --throughput_chart_total_line_color: gray;
27
29
 
28
30
  --aging-work-in-progress-chart-shading-color: lightgray;
31
+ --aging-work-in-progress-chart-shading-50-color: #2E8BC0; // green;
32
+ --aging-work-in-progress-chart-shading-85-color: #ADD8E6; // yellow;
33
+ --aging-work-in-progress-chart-shading-98-color: #FF8A8A; // orange;
34
+ --aging-work-in-progress-chart-shading-100-color: #FF2E2E; // red;
35
+
29
36
  --aging-work-in-progress-by-age-trend-line-color: gray;
30
37
 
38
+ --aging-work-table-date-in-jeopardy: yellow;
39
+ --aging-work-table-date-overdue: red;
40
+
31
41
  --hierarchy-table-inactive-item-text-color: gray;
32
42
 
33
43
  --wip-chart-completed-color: #00ff00;
@@ -99,9 +109,6 @@ table.standard {
99
109
  background-color: #eee;
100
110
  }
101
111
  }
102
- .quality_note_bullet {
103
- color: red;
104
- }
105
112
 
106
113
  .chart {
107
114
  background-color: white;
@@ -119,8 +126,26 @@ div.color_block {
119
126
  border: 1px solid black;
120
127
  }
121
128
 
129
+ ul.quality_report {
130
+ list-style-type: '⮕';
131
+ ::marker {
132
+ color: red;
133
+ }
134
+ li {
135
+ padding: 0.2em;
136
+ }
137
+ }
138
+
139
+ #footer {
140
+ text-align: center;
141
+ margin-top: 1em;
142
+ border-top: 1px solid gray;
143
+ }
144
+
122
145
  @media screen and (prefers-color-scheme: dark) {
123
146
  :root {
147
+ --warning-banner: #9F2B00;
148
+
124
149
  --non-working-days-color: #2f2f2f;
125
150
  --type-story-color: #6fb86f;
126
151
  --type-task-color: #0021b3;
@@ -136,8 +161,6 @@ div.color_block {
136
161
  --dead-color: black;
137
162
  --wip-chart-active-color: #2551c1;
138
163
 
139
- --aging-work-in-progress-chart-shading-color: #b4b4b4;
140
-
141
164
  --status-category-inprogress-color: #1c49bb;
142
165
 
143
166
  --cycletime-scatterplot-overall-trendline-color: gray;
@@ -23,16 +23,20 @@
23
23
  window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
24
24
  location.reload()
25
25
  })
26
-
27
26
  </script>
28
27
  <style>
29
28
  <%= css %>
30
29
  </style>
30
+ <script type="text/javascript">
31
+ Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--default-text-color');
32
+ </script>
31
33
  </head>
32
34
  <body>
33
- <div>
34
- Page generated <%= (timezone_offset.nil? ? DateTime.now : DateTime.now.new_offset(timezone_offset)).strftime('%Y-%b-%d at %I:%M:%S%P') %>
35
- </div>
35
+ <noscript>
36
+ <div style="padding: 1em; background: gray; color: white; font-size: 2em;">
37
+ Javascript is currently disabled and that means that almost all of the charts in this report won't render. If you'd loaded this from a folder on SharePoint then save it locally and load it again.
38
+ </div>
39
+ </noscript>
36
40
  <%= "\n" + @sections.collect { |text, type| text if type == :header }.compact.join("\n\n") %>
37
41
  <%= "\n" + @sections.collect { |text, type| text if type == :body }.compact.join("\n\n") %>
38
42
  </body>
@@ -56,16 +56,7 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
56
56
  },
57
57
  annotation: {
58
58
  annotations: {
59
- <% holidays().each_with_index do |range, index| %>
60
- holiday<%= index %>: {
61
- drawTime: 'beforeDraw',
62
- type: 'box',
63
- xMin: '<%= range.begin %>T00:00:00',
64
- xMax: '<%= range.end %>T23:59:59',
65
- backgroundColor: <%= CssVariable.new('--non-working-days-color').to_json %>,
66
- borderColor: <%= CssVariable.new('--non-working-days-color').to_json %>
67
- },
68
- <% end %>
59
+ <%= working_days_annotation %>
69
60
  }
70
61
  }
71
62
  }
@@ -52,16 +52,7 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
52
52
  },
53
53
  annotation: {
54
54
  annotations: {
55
- <% holidays.each_with_index do |range, index| %>
56
- holiday<%= index %>: {
57
- drawTime: 'beforeDraw',
58
- type: 'box',
59
- xMin: '<%= range.begin %>T00:00:00',
60
- xMax: '<%= range.end %>T23:59:59',
61
- backgroundColor: <%= CssVariable.new('--non-working-days-color').to_json %>,
62
- borderColor: <%= CssVariable.new('--non-working-days-color').to_json %>
63
- },
64
- <% end %>
55
+ <%= working_days_annotation %>
65
56
  }
66
57
  }
67
58
  }
@@ -5,16 +5,15 @@ require 'jirametrics/self_or_issue_dispatcher'
5
5
 
6
6
  class HtmlReportConfig
7
7
  include SelfOrIssueDispatcher
8
- include DiscardChangesBefore
9
8
 
10
- attr_reader :file_config, :sections
9
+ attr_reader :file_config, :sections, :charts
11
10
 
12
11
  def self.define_chart name:, classname:, deprecated_warning: nil, deprecated_date: nil
13
12
  lines = []
14
13
  lines << "def #{name} &block"
15
14
  lines << ' block = ->(_) {} unless block'
16
15
  if deprecated_warning
17
- lines << " deprecated date: #{deprecated_date.inspect}, message: #{deprecated_warning.inspect}"
16
+ lines << " file_system.deprecated date: #{deprecated_date.inspect}, message: #{deprecated_warning.inspect}"
18
17
  end
19
18
  lines << " execute_chart #{classname}.new(block)"
20
19
  lines << 'end'
@@ -33,6 +32,7 @@ class HtmlReportConfig
33
32
  define_chart name: 'cycletime_histogram', classname: 'CycletimeHistogram'
34
33
  define_chart name: 'estimate_accuracy_chart', classname: 'EstimateAccuracyChart'
35
34
  define_chart name: 'hierarchy_table', classname: 'HierarchyTable'
35
+ define_chart name: 'flow_efficiency_scatterplot', classname: 'FlowEfficiencyScatterplot'
36
36
 
37
37
  define_chart name: 'daily_wip_by_type', classname: 'DailyWipChart',
38
38
  deprecated_warning: 'This is the same as daily_wip_chart. Please use that one', deprecated_date: '2024-05-23'
@@ -42,14 +42,15 @@ class HtmlReportConfig
42
42
  def initialize file_config:, block:
43
43
  @file_config = file_config
44
44
  @block = block
45
- @sections = []
45
+ @sections = [] # Where we store the chunks of text that will be assembled into the HTML
46
+ @charts = [] # Where we store all the charts we executed so we can assert against them.
46
47
  end
47
48
 
48
49
  def cycletime label = nil, &block
49
50
  @file_config.project_config.all_boards.each_value do |board|
50
51
  raise 'Multiple cycletimes not supported' if board.cycletime
51
52
 
52
- board.cycletime = CycleTimeConfig.new(parent_config: self, label: label, block: block)
53
+ board.cycletime = CycleTimeConfig.new(parent_config: self, label: label, block: block, file_system: file_system)
53
54
  end
54
55
  end
55
56
 
@@ -63,9 +64,11 @@ class HtmlReportConfig
63
64
 
64
65
  # The quality report has to be generated last because otherwise cycletime won't have been
65
66
  # set. Then we have to rotate it to the first position so it's at the top of the report.
66
- execute_chart DataQualityReport.new(@original_issue_times || {})
67
+ execute_chart DataQualityReport.new(file_config.project_config.discarded_changes_data)
67
68
  @sections.rotate!(-1)
68
69
 
70
+ html create_footer
71
+
69
72
  html_directory = "#{Pathname.new(File.realpath(__FILE__)).dirname}/html"
70
73
  css = load_css html_directory: html_directory
71
74
  erb = ERB.new file_system.load(File.join(html_directory, 'index.erb'))
@@ -98,9 +101,8 @@ class HtmlReportConfig
98
101
  base_css
99
102
  end
100
103
 
101
- def board_id id = nil
102
- @board_id = id unless id.nil?
103
- @board_id
104
+ def board_id id
105
+ @board_id = id
104
106
  end
105
107
 
106
108
  def timezone_offset
@@ -140,19 +142,6 @@ class HtmlReportConfig
140
142
  end
141
143
  end
142
144
 
143
- def discard_changes_before_hook issues_cutoff_times
144
- # raise 'Cycletime must be defined before using discard_changes_before' unless @cycletime
145
-
146
- @original_issue_times = {}
147
- issues_cutoff_times.each do |issue, cutoff_time|
148
- started = issue.board.cycletime.started_time(issue)
149
- if started && started <= cutoff_time
150
- # We only need to log this if data was discarded
151
- @original_issue_times[issue] = { cutoff_time: cutoff_time, started_time: started }
152
- end
153
- end
154
- end
155
-
156
145
  def dependency_chart &block
157
146
  execute_chart DependencyChart.new block
158
147
  end
@@ -172,7 +161,7 @@ class HtmlReportConfig
172
161
  chart.settings = settings
173
162
 
174
163
  chart.all_boards = project_config.all_boards
175
- chart.board_id = find_board_id if chart.respond_to? :board_id=
164
+ chart.board_id = find_board_id
176
165
  chart.holiday_dates = project_config.exporter.holiday_dates
177
166
 
178
167
  time_range = @file_config.project_config.time_range
@@ -181,6 +170,7 @@ class HtmlReportConfig
181
170
 
182
171
  after_init_block&.call chart
183
172
 
173
+ @charts << chart
184
174
  html chart.run
185
175
  end
186
176
 
@@ -201,4 +191,24 @@ class HtmlReportConfig
201
191
  def boards
202
192
  @file_config.project_config.board_configs.collect(&:id).collect { |id| find_board id }
203
193
  end
194
+
195
+ def create_footer now: DateTime.now
196
+ now = now.new_offset(timezone_offset)
197
+ version = Gem.loaded_specs['jirametrics']&.version || 'Next'
198
+
199
+ <<~HTML
200
+ <section id="footer">
201
+ Report generated on <b>#{now.strftime('%Y-%b-%d')}</b> at <b>#{now.strftime('%I:%M:%S%P %Z')}</b>
202
+ with <a href="https://jirametrics.org">JiraMetrics</a> <b>v#{version}</b>
203
+ </section>
204
+ HTML
205
+ end
206
+
207
+ def discard_changes_before status_becomes: nil, &block
208
+ file_system.deprecated(
209
+ date: '2025-01-09',
210
+ message: 'discard_changes_before is now only supported at the project level'
211
+ )
212
+ file_config.project_config.discard_changes_before status_becomes: status_becomes, &block
213
+ end
204
214
  end