jirametrics 2.0 → 2.11
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.
- checksums.yaml +4 -4
- data/lib/jirametrics/aggregate_config.rb +19 -26
- data/lib/jirametrics/aging_work_bar_chart.rb +79 -54
- data/lib/jirametrics/aging_work_in_progress_chart.rb +106 -40
- data/lib/jirametrics/aging_work_table.rb +78 -43
- data/lib/jirametrics/anonymizer.rb +6 -5
- data/lib/jirametrics/blocked_stalled_change.rb +24 -4
- data/lib/jirametrics/board.rb +44 -15
- data/lib/jirametrics/board_config.rb +8 -4
- data/lib/jirametrics/board_movement_calculator.rb +147 -0
- data/lib/jirametrics/change_item.rb +31 -10
- data/lib/jirametrics/chart_base.rb +102 -61
- data/lib/jirametrics/columns_config.rb +4 -0
- data/lib/jirametrics/css_variable.rb +33 -0
- data/lib/jirametrics/cycletime_config.rb +59 -8
- data/lib/jirametrics/cycletime_histogram.rb +69 -4
- data/lib/jirametrics/cycletime_scatterplot.rb +11 -15
- data/lib/jirametrics/daily_wip_by_age_chart.rb +44 -20
- data/lib/jirametrics/daily_wip_by_blocked_stalled_chart.rb +37 -35
- data/lib/jirametrics/daily_wip_by_parent_chart.rb +38 -0
- data/lib/jirametrics/daily_wip_chart.rb +61 -14
- data/lib/jirametrics/data_quality_report.rb +222 -41
- data/lib/jirametrics/dependency_chart.rb +54 -23
- data/lib/jirametrics/download_config.rb +12 -0
- data/lib/jirametrics/downloader.rb +76 -57
- data/lib/jirametrics/{story_point_accuracy_chart.rb → estimate_accuracy_chart.rb} +48 -33
- data/lib/jirametrics/examples/aggregated_project.rb +22 -39
- data/lib/jirametrics/examples/standard_project.rb +25 -49
- data/lib/jirametrics/expedited_chart.rb +28 -25
- data/lib/jirametrics/exporter.rb +59 -32
- data/lib/jirametrics/file_config.rb +34 -13
- data/lib/jirametrics/file_system.rb +48 -3
- data/lib/jirametrics/flow_efficiency_scatterplot.rb +111 -0
- data/lib/jirametrics/groupable_issue_chart.rb +2 -6
- data/lib/jirametrics/grouping_rules.rb +7 -1
- data/lib/jirametrics/hierarchy_table.rb +4 -4
- data/lib/jirametrics/html/aging_work_bar_chart.erb +13 -16
- data/lib/jirametrics/html/aging_work_in_progress_chart.erb +28 -5
- data/lib/jirametrics/html/aging_work_table.erb +19 -25
- data/lib/jirametrics/html/cycletime_histogram.erb +83 -3
- data/lib/jirametrics/html/cycletime_scatterplot.erb +9 -12
- data/lib/jirametrics/html/daily_wip_chart.erb +17 -13
- data/lib/jirametrics/html/{story_point_accuracy_chart.erb → estimate_accuracy_chart.erb} +9 -4
- data/lib/jirametrics/html/expedited_chart.erb +10 -13
- data/lib/jirametrics/html/flow_efficiency_scatterplot.erb +85 -0
- data/lib/jirametrics/html/hierarchy_table.erb +2 -2
- data/lib/jirametrics/html/index.css +209 -0
- data/lib/jirametrics/html/index.erb +16 -39
- data/lib/jirametrics/html/sprint_burndown.erb +10 -14
- data/lib/jirametrics/html/throughput_chart.erb +10 -13
- data/lib/jirametrics/html_report_config.rb +108 -86
- data/lib/jirametrics/issue.rb +357 -96
- data/lib/jirametrics/jira_gateway.rb +29 -11
- data/lib/jirametrics/project_config.rb +256 -144
- data/lib/jirametrics/rules.rb +2 -2
- data/lib/jirametrics/self_or_issue_dispatcher.rb +2 -0
- data/lib/jirametrics/settings.json +10 -0
- data/lib/jirametrics/sprint_burndown.rb +24 -7
- data/lib/jirametrics/status.rb +84 -19
- data/lib/jirametrics/status_collection.rb +80 -39
- data/lib/jirametrics/throughput_chart.rb +12 -4
- data/lib/jirametrics/value_equality.rb +2 -2
- data/lib/jirametrics.rb +25 -7
- metadata +16 -17
- data/lib/jirametrics/discard_changes_before.rb +0 -37
- data/lib/jirametrics/experimental/generator.rb +0 -210
- data/lib/jirametrics/experimental/info.rb +0 -77
- data/lib/jirametrics/html/data_quality_report.erb +0 -126
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--body-background: white;
|
|
3
|
+
--default-text-color: black;
|
|
4
|
+
--grid-line-color: lightgray;
|
|
5
|
+
--warning-banner: yellow;
|
|
6
|
+
|
|
7
|
+
--cycletime-scatterplot-overall-trendline-color: gray;
|
|
8
|
+
|
|
9
|
+
--non-working-days-color: #F0F0F0;
|
|
10
|
+
--expedited-color: red;
|
|
11
|
+
--blocked-color: #FF7400;
|
|
12
|
+
--stalled-color: orange;
|
|
13
|
+
--dead-color: black;
|
|
14
|
+
|
|
15
|
+
--type-story-color: #4bc14b;
|
|
16
|
+
--type-task-color: blue;
|
|
17
|
+
--type-bug-color: orange;
|
|
18
|
+
--type-spike-color: #9400D3;
|
|
19
|
+
|
|
20
|
+
--status-category-todo-color: gray;
|
|
21
|
+
--status-category-inprogress-color: #2663ff;
|
|
22
|
+
--status-category-done-color: #00ff00;
|
|
23
|
+
--status-category-unknown-color: black;
|
|
24
|
+
|
|
25
|
+
--aging-work-bar-chart-percentage-line-color: red;
|
|
26
|
+
--aging-work-bar-chart-separator-color: white;
|
|
27
|
+
|
|
28
|
+
--throughput_chart_total_line_color: gray;
|
|
29
|
+
|
|
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
|
+
|
|
36
|
+
--aging-work-in-progress-by-age-trend-line-color: gray;
|
|
37
|
+
|
|
38
|
+
--aging-work-table-date-in-jeopardy: yellow;
|
|
39
|
+
--aging-work-table-date-overdue: red;
|
|
40
|
+
|
|
41
|
+
--hierarchy-table-inactive-item-text-color: gray;
|
|
42
|
+
|
|
43
|
+
--wip-chart-completed-color: #00ff00;
|
|
44
|
+
--wip-chart-completed-but-not-started-color: #99FF99;
|
|
45
|
+
--wip-chart-duration-less-than-day-color: #ffef41;
|
|
46
|
+
--wip-chart-duration-week-or-less-color: #dcc900;
|
|
47
|
+
--wip-chart-duration-two-weeks-or-less-color: #dfa000;
|
|
48
|
+
--wip-chart-duration-four-weeks-or-less-color: #eb7200;
|
|
49
|
+
--wip-chart-duration-more-than-four-weeks-color: #e70000;
|
|
50
|
+
--wip-chart-active-color: #326cff;
|
|
51
|
+
--wip-chart-border-color: gray;
|
|
52
|
+
|
|
53
|
+
--estimate-accuracy-chart-completed-fill-color: #00ff00;
|
|
54
|
+
--estimate-accuracy-chart-completed-border-color: green;
|
|
55
|
+
--estimate-accuracy-chart-active-fill-color: #FFCCCB;
|
|
56
|
+
--estimate-accuracy-chart-active-border-color: red;
|
|
57
|
+
|
|
58
|
+
--expedited-chart-no-longer-expedited: gray;
|
|
59
|
+
--expedited-chart-dot-issue-started-color: orange;
|
|
60
|
+
--expedited-chart-dot-issue-stopped-color: green;
|
|
61
|
+
--expedited-chart-dot-expedite-started-color: red;
|
|
62
|
+
--expedited-chart-dot-expedite-stopped-color: green;
|
|
63
|
+
|
|
64
|
+
--sprint-burndown-sprint-color-1: blue;
|
|
65
|
+
--sprint-burndown-sprint-color-2: orange;
|
|
66
|
+
--sprint-burndown-sprint-color-3: green;
|
|
67
|
+
--sprint-burndown-sprint-color-4: red;
|
|
68
|
+
--sprint-burndown-sprint-color-5: brown;
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
body {
|
|
74
|
+
background-color: var(--body-background);
|
|
75
|
+
color: var(--default-text-color);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
h1 {
|
|
79
|
+
border: 1px solid black;
|
|
80
|
+
background: lightgray;
|
|
81
|
+
padding-left: 0.2em;
|
|
82
|
+
}
|
|
83
|
+
dl, dd, dt {
|
|
84
|
+
padding: 0;
|
|
85
|
+
margin: 0;
|
|
86
|
+
}
|
|
87
|
+
dd {
|
|
88
|
+
margin-bottom: 0.4em;
|
|
89
|
+
}
|
|
90
|
+
span.highlight {
|
|
91
|
+
background: #FDD5B1;
|
|
92
|
+
}
|
|
93
|
+
a.issue_key {
|
|
94
|
+
white-space: nowrap;
|
|
95
|
+
}
|
|
96
|
+
table.standard {
|
|
97
|
+
th {
|
|
98
|
+
border-bottom: 1px solid gray;
|
|
99
|
+
position: sticky;
|
|
100
|
+
top: 0;
|
|
101
|
+
background: white;
|
|
102
|
+
}
|
|
103
|
+
td {
|
|
104
|
+
padding-left: 0.5em;
|
|
105
|
+
padding-right: 0.5em;
|
|
106
|
+
vertical-align: top;
|
|
107
|
+
}
|
|
108
|
+
tr:nth-child(odd){
|
|
109
|
+
background-color: #eee;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.chart {
|
|
114
|
+
background-color: white;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
div.p {
|
|
118
|
+
margin: 0.5em 0;
|
|
119
|
+
padding: 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
div.color_block {
|
|
123
|
+
display: inline-block;
|
|
124
|
+
width: 0.9em;
|
|
125
|
+
height: 0.9em;
|
|
126
|
+
border: 1px solid black;
|
|
127
|
+
}
|
|
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
|
+
|
|
145
|
+
@media screen and (prefers-color-scheme: dark) {
|
|
146
|
+
:root {
|
|
147
|
+
--warning-banner: #9F2B00;
|
|
148
|
+
|
|
149
|
+
--non-working-days-color: #2f2f2f;
|
|
150
|
+
--type-story-color: #6fb86f;
|
|
151
|
+
--type-task-color: #0021b3;
|
|
152
|
+
--type-bug-color: #bb5603;
|
|
153
|
+
|
|
154
|
+
--body-background: #343434;
|
|
155
|
+
--default-text-color: #aaa;
|
|
156
|
+
--grid-line-color: #424242;
|
|
157
|
+
|
|
158
|
+
--expedited-color: #b90000;
|
|
159
|
+
--blocked-color: #c75b02;
|
|
160
|
+
--stalled-color: #ae7202;
|
|
161
|
+
--dead-color: black;
|
|
162
|
+
--wip-chart-active-color: #2551c1;
|
|
163
|
+
|
|
164
|
+
--status-category-inprogress-color: #1c49bb;
|
|
165
|
+
|
|
166
|
+
--cycletime-scatterplot-overall-trendline-color: gray;
|
|
167
|
+
|
|
168
|
+
--hierarchy-table-inactive-item-text-color: #939393;
|
|
169
|
+
|
|
170
|
+
--wip-chart-completed-color: #03cb03;
|
|
171
|
+
--wip-chart-completed-but-not-started-color: #99FF99;
|
|
172
|
+
--wip-chart-duration-less-than-day-color: #d2d988;
|
|
173
|
+
--wip-chart-duration-week-or-less-color: #dfcd00;
|
|
174
|
+
--wip-chart-duration-two-weeks-or-less-color: #cf9400;
|
|
175
|
+
--wip-chart-duration-four-weeks-or-less-color: #c25e00;
|
|
176
|
+
--wip-chart-duration-more-than-four-weeks-color: #8e0000;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
h1 {
|
|
180
|
+
color: #e0e0e0;
|
|
181
|
+
background-color: #656565;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
a[href] {
|
|
185
|
+
color: #1e8ad6;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
a[href]:hover {
|
|
189
|
+
color: #3ba0e6;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.chart {
|
|
193
|
+
background: var(--body-background);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
table.standard {
|
|
197
|
+
th {
|
|
198
|
+
border-bottom: 1px solid gray;
|
|
199
|
+
background: var(--body-background);
|
|
200
|
+
}
|
|
201
|
+
tr:nth-child(odd){
|
|
202
|
+
background-color: #656565;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
div.color_block {
|
|
207
|
+
border: 1px solid lightgray;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<html>
|
|
2
2
|
<head>
|
|
3
3
|
<meta charset="UTF-8">
|
|
4
|
+
<link rel="icon" type="image/png" href="https://github.com/mikebowler/jirametrics/blob/main/favicon.png?raw=true" />
|
|
4
5
|
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.1/moment.js"></script>
|
|
5
6
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
6
7
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment@^1"></script>
|
|
@@ -17,49 +18,25 @@
|
|
|
17
18
|
document.getElementById(issues_id).style.display = 'none'
|
|
18
19
|
}
|
|
19
20
|
}
|
|
21
|
+
// If we switch between light/dark mode then force a refresh so all charts will redraw correctly
|
|
22
|
+
// in the other colour scheme.
|
|
23
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
|
24
|
+
location.reload()
|
|
25
|
+
})
|
|
20
26
|
</script>
|
|
21
27
|
<style>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
dl, dd, dt {
|
|
28
|
-
padding: 0;
|
|
29
|
-
margin: 0;
|
|
30
|
-
}
|
|
31
|
-
dd {
|
|
32
|
-
margin-bottom: 0.4em;
|
|
33
|
-
}
|
|
34
|
-
span.highlight {
|
|
35
|
-
background: #FDD5B1;
|
|
36
|
-
}
|
|
37
|
-
a.issue_key {
|
|
38
|
-
white-space: nowrap;
|
|
39
|
-
}
|
|
40
|
-
table.standard thead tr th {
|
|
41
|
-
border-bottom: 1px solid gray;
|
|
42
|
-
position: sticky;
|
|
43
|
-
top: 0;
|
|
44
|
-
background: white;
|
|
45
|
-
}
|
|
46
|
-
table.standard tbody tr td {
|
|
47
|
-
padding-left: 0.5em;
|
|
48
|
-
padding-right: 0.5em;
|
|
49
|
-
vertical-align: top;
|
|
50
|
-
}
|
|
51
|
-
table.standard tbody tr:nth-child(odd){
|
|
52
|
-
background-color: #eee;
|
|
53
|
-
}
|
|
54
|
-
.quality_note_bullet {
|
|
55
|
-
color: red;
|
|
56
|
-
}
|
|
57
|
-
</style>
|
|
28
|
+
<%= css %>
|
|
29
|
+
</style>
|
|
30
|
+
<script type="text/javascript">
|
|
31
|
+
Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--default-text-color');
|
|
32
|
+
</script>
|
|
58
33
|
</head>
|
|
59
34
|
<body>
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
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>
|
|
63
40
|
<%= "\n" + @sections.collect { |text, type| text if type == :header }.compact.join("\n\n") %>
|
|
64
41
|
<%= "\n" + @sections.collect { |text, type| text if type == :body }.compact.join("\n\n") %>
|
|
65
42
|
</body>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<h2>Burndown by <%= y_axis_title %></h2>
|
|
2
2
|
|
|
3
|
-
<div>
|
|
3
|
+
<div class="chart">
|
|
4
4
|
<canvas id="<%= chart_id %>" width="<%= canvas_width %>" height="<%= canvas_height %>"></canvas>
|
|
5
5
|
</div>
|
|
6
6
|
<script>
|
|
@@ -26,8 +26,10 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
|
|
|
26
26
|
labelString: 'Date'
|
|
27
27
|
},
|
|
28
28
|
min: "<%= date_range.begin.to_s %>",
|
|
29
|
-
max: "<%= (date_range.end + 1).to_s %>"
|
|
30
|
-
|
|
29
|
+
max: "<%= (date_range.end + 1).to_s %>",
|
|
30
|
+
grid: {
|
|
31
|
+
color: <%= CssVariable['--grid-line-color'].to_json %>
|
|
32
|
+
},
|
|
31
33
|
},
|
|
32
34
|
y: {
|
|
33
35
|
scaleLabel: {
|
|
@@ -38,7 +40,10 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
|
|
|
38
40
|
display: true,
|
|
39
41
|
text: "<%= y_axis_title %>"
|
|
40
42
|
},
|
|
41
|
-
min: 0.0
|
|
43
|
+
min: 0.0,
|
|
44
|
+
grid: {
|
|
45
|
+
color: <%= CssVariable['--grid-line-color'].to_json %>
|
|
46
|
+
},
|
|
42
47
|
}
|
|
43
48
|
},
|
|
44
49
|
plugins: {
|
|
@@ -51,16 +56,7 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
|
|
|
51
56
|
},
|
|
52
57
|
annotation: {
|
|
53
58
|
annotations: {
|
|
54
|
-
|
|
55
|
-
holiday<%= index %>: {
|
|
56
|
-
drawTime: 'beforeDraw',
|
|
57
|
-
type: 'box',
|
|
58
|
-
xMin: '<%= range.begin %>T00:00:00',
|
|
59
|
-
xMax: '<%= range.end %>T23:59:59',
|
|
60
|
-
backgroundColor: '#F0F0F0',
|
|
61
|
-
borderColor: '#F0F0F0'
|
|
62
|
-
},
|
|
63
|
-
<% end %>
|
|
59
|
+
<%= working_days_annotation %>
|
|
64
60
|
}
|
|
65
61
|
}
|
|
66
62
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
<div>
|
|
2
|
+
<div class="chart">
|
|
3
3
|
<canvas id="<%= chart_id %>" width="<%= canvas_width %>" height="<%= canvas_height %>"></canvas>
|
|
4
4
|
</div>
|
|
5
5
|
<script>
|
|
@@ -23,7 +23,10 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
|
|
|
23
23
|
scaleLabel: {
|
|
24
24
|
display: true,
|
|
25
25
|
labelString: 'Date Completed'
|
|
26
|
-
}
|
|
26
|
+
},
|
|
27
|
+
grid: {
|
|
28
|
+
color: <%= CssVariable['--grid-line-color'].to_json %>
|
|
29
|
+
},
|
|
27
30
|
},
|
|
28
31
|
y: {
|
|
29
32
|
scaleLabel: {
|
|
@@ -33,7 +36,10 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
|
|
|
33
36
|
display: true,
|
|
34
37
|
text: 'Count of items'
|
|
35
38
|
},
|
|
36
|
-
min: 0
|
|
39
|
+
min: 0,
|
|
40
|
+
grid: {
|
|
41
|
+
color: <%= CssVariable['--grid-line-color'].to_json %>
|
|
42
|
+
},
|
|
37
43
|
},
|
|
38
44
|
},
|
|
39
45
|
plugins: {
|
|
@@ -46,16 +52,7 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
|
|
|
46
52
|
},
|
|
47
53
|
annotation: {
|
|
48
54
|
annotations: {
|
|
49
|
-
|
|
50
|
-
holiday<%= index %>: {
|
|
51
|
-
drawTime: 'beforeDraw',
|
|
52
|
-
type: 'box',
|
|
53
|
-
xMin: '<%= range.begin %>T00:00:00',
|
|
54
|
-
xMax: '<%= range.end %>T23:59:59',
|
|
55
|
-
backgroundColor: '#F0F0F0',
|
|
56
|
-
borderColor: '#F0F0F0'
|
|
57
|
-
},
|
|
58
|
-
<% end %>
|
|
55
|
+
<%= working_days_annotation %>
|
|
59
56
|
}
|
|
60
57
|
}
|
|
61
58
|
}
|
|
@@ -5,45 +5,104 @@ 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
|
|
10
|
+
|
|
11
|
+
def self.define_chart name:, classname:, deprecated_warning: nil, deprecated_date: nil
|
|
12
|
+
lines = []
|
|
13
|
+
lines << "def #{name} &block"
|
|
14
|
+
lines << ' block = ->(_) {} unless block'
|
|
15
|
+
if deprecated_warning
|
|
16
|
+
lines << " file_system.deprecated date: #{deprecated_date.inspect}, message: #{deprecated_warning.inspect}"
|
|
17
|
+
end
|
|
18
|
+
lines << " execute_chart #{classname}.new(block)"
|
|
19
|
+
lines << 'end'
|
|
20
|
+
module_eval lines.join("\n"), __FILE__, __LINE__
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
define_chart name: 'aging_work_bar_chart', classname: 'AgingWorkBarChart'
|
|
24
|
+
define_chart name: 'aging_work_table', classname: 'AgingWorkTable'
|
|
25
|
+
define_chart name: 'cycletime_scatterplot', classname: 'CycletimeScatterplot'
|
|
26
|
+
define_chart name: 'daily_wip_chart', classname: 'DailyWipChart'
|
|
27
|
+
define_chart name: 'daily_wip_by_age_chart', classname: 'DailyWipByAgeChart'
|
|
28
|
+
define_chart name: 'daily_wip_by_blocked_stalled_chart', classname: 'DailyWipByBlockedStalledChart'
|
|
29
|
+
define_chart name: 'daily_wip_by_parent_chart', classname: 'DailyWipByParentChart'
|
|
30
|
+
define_chart name: 'throughput_chart', classname: 'ThroughputChart'
|
|
31
|
+
define_chart name: 'expedited_chart', classname: 'ExpeditedChart'
|
|
32
|
+
define_chart name: 'cycletime_histogram', classname: 'CycletimeHistogram'
|
|
33
|
+
define_chart name: 'estimate_accuracy_chart', classname: 'EstimateAccuracyChart'
|
|
34
|
+
define_chart name: 'hierarchy_table', classname: 'HierarchyTable'
|
|
35
|
+
define_chart name: 'flow_efficiency_scatterplot', classname: 'FlowEfficiencyScatterplot'
|
|
36
|
+
|
|
37
|
+
define_chart name: 'daily_wip_by_type', classname: 'DailyWipChart',
|
|
38
|
+
deprecated_warning: 'This is the same as daily_wip_chart. Please use that one', deprecated_date: '2024-05-23'
|
|
39
|
+
define_chart name: 'story_point_accuracy_chart', classname: 'EstimateAccuracyChart',
|
|
40
|
+
deprecated_warning: 'Renamed to estimate_accuracy_chart. Please use that one', deprecated_date: '2024-05-23'
|
|
11
41
|
|
|
12
42
|
def initialize file_config:, block:
|
|
13
43
|
@file_config = file_config
|
|
14
44
|
@block = block
|
|
15
|
-
|
|
16
|
-
@
|
|
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.
|
|
17
47
|
end
|
|
18
48
|
|
|
19
49
|
def cycletime label = nil, &block
|
|
20
|
-
# TODO: This is about to become deprecated
|
|
21
|
-
|
|
22
50
|
@file_config.project_config.all_boards.each_value do |board|
|
|
23
|
-
raise 'Multiple cycletimes not supported
|
|
51
|
+
raise 'Multiple cycletimes not supported' if board.cycletime
|
|
24
52
|
|
|
25
|
-
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)
|
|
26
54
|
end
|
|
27
55
|
end
|
|
28
56
|
|
|
57
|
+
# Mostly this is its own method so it can be called from the config
|
|
58
|
+
def included_projects
|
|
59
|
+
@file_config.project_config.aggregate_config.included_projects
|
|
60
|
+
end
|
|
61
|
+
|
|
29
62
|
def run
|
|
30
63
|
instance_eval(&@block)
|
|
31
64
|
|
|
32
65
|
# The quality report has to be generated last because otherwise cycletime won't have been
|
|
33
66
|
# set. Then we have to rotate it to the first position so it's at the top of the report.
|
|
34
|
-
execute_chart DataQualityReport.new(
|
|
67
|
+
execute_chart DataQualityReport.new(file_config.project_config.discarded_changes_data)
|
|
35
68
|
@sections.rotate!(-1)
|
|
36
69
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
70
|
+
html create_footer
|
|
71
|
+
|
|
72
|
+
html_directory = "#{Pathname.new(File.realpath(__FILE__)).dirname}/html"
|
|
73
|
+
css = load_css html_directory: html_directory
|
|
74
|
+
erb = ERB.new file_system.load(File.join(html_directory, 'index.erb'))
|
|
75
|
+
file_system.save_file content: erb.result(binding), filename: @file_config.output_filename
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def file_system
|
|
79
|
+
@file_config.project_config.exporter.file_system
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def log message
|
|
83
|
+
file_system.log message
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def load_css html_directory:
|
|
87
|
+
base_css_filename = File.join(html_directory, 'index.css')
|
|
88
|
+
base_css = file_system.load(base_css_filename)
|
|
89
|
+
log("Loaded CSS: #{base_css_filename}")
|
|
90
|
+
|
|
91
|
+
extra_css_filename = settings['include_css']
|
|
92
|
+
if extra_css_filename
|
|
93
|
+
if File.exist?(extra_css_filename)
|
|
94
|
+
base_css << "\n\n" << file_system.load(extra_css_filename)
|
|
95
|
+
log("Loaded CSS: #{extra_css_filename}")
|
|
96
|
+
else
|
|
97
|
+
log("Unable to find specified CSS file: #{extra_css_filename}")
|
|
98
|
+
end
|
|
41
99
|
end
|
|
100
|
+
|
|
101
|
+
base_css
|
|
42
102
|
end
|
|
43
103
|
|
|
44
|
-
def board_id id
|
|
45
|
-
@board_id = id
|
|
46
|
-
@board_id
|
|
104
|
+
def board_id id
|
|
105
|
+
@board_id = id
|
|
47
106
|
end
|
|
48
107
|
|
|
49
108
|
def timezone_offset
|
|
@@ -51,6 +110,8 @@ class HtmlReportConfig
|
|
|
51
110
|
end
|
|
52
111
|
|
|
53
112
|
def aging_work_in_progress_chart board_id: nil, &block
|
|
113
|
+
block ||= ->(_) {}
|
|
114
|
+
|
|
54
115
|
if board_id.nil?
|
|
55
116
|
ids = issues.collect { |i| i.board.id }.uniq.sort
|
|
56
117
|
else
|
|
@@ -64,52 +125,13 @@ class HtmlReportConfig
|
|
|
64
125
|
end
|
|
65
126
|
end
|
|
66
127
|
|
|
67
|
-
def aging_work_bar_chart &block
|
|
68
|
-
execute_chart AgingWorkBarChart.new(block)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def aging_work_table &block
|
|
72
|
-
execute_chart AgingWorkTable.new(block)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def cycletime_scatterplot &block
|
|
76
|
-
execute_chart CycletimeScatterplot.new block
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def daily_wip_chart &block
|
|
80
|
-
execute_chart DailyWipChart.new(block)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def daily_wip_by_age_chart &block
|
|
84
|
-
execute_chart DailyWipByAgeChart.new block
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def daily_wip_by_type &block
|
|
88
|
-
execute_chart DailyWipChart.new block
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def daily_wip_by_blocked_stalled_chart
|
|
92
|
-
execute_chart DailyWipByBlockedStalledChart.new
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def throughput_chart &block
|
|
96
|
-
execute_chart ThroughputChart.new(block)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def expedited_chart
|
|
100
|
-
execute_chart ExpeditedChart.new
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def cycletime_histogram &block
|
|
104
|
-
execute_chart CycletimeHistogram.new block
|
|
105
|
-
end
|
|
106
|
-
|
|
107
128
|
def random_color
|
|
108
129
|
"##{Random.bytes(3).unpack1('H*')}"
|
|
109
130
|
end
|
|
110
131
|
|
|
111
132
|
def html string, type: :body
|
|
112
|
-
|
|
133
|
+
allowed_types = %i[body header]
|
|
134
|
+
raise "Unexpected type: #{type} allowed_types: #{allowed_types.inspect}" unless allowed_types.include? type
|
|
113
135
|
|
|
114
136
|
@sections << [string, type]
|
|
115
137
|
end
|
|
@@ -120,41 +142,26 @@ class HtmlReportConfig
|
|
|
120
142
|
end
|
|
121
143
|
end
|
|
122
144
|
|
|
123
|
-
def story_point_accuracy_chart &block
|
|
124
|
-
execute_chart StoryPointAccuracyChart.new block
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def hierarchy_table &block
|
|
128
|
-
execute_chart HierarchyTable.new block
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def discard_changes_before_hook issues_cutoff_times
|
|
132
|
-
# raise 'Cycletime must be defined before using discard_changes_before' unless @cycletime
|
|
133
|
-
|
|
134
|
-
@original_issue_times = {}
|
|
135
|
-
issues_cutoff_times.each do |issue, cutoff_time|
|
|
136
|
-
started = issue.board.cycletime.started_time(issue)
|
|
137
|
-
if started && started <= cutoff_time
|
|
138
|
-
# We only need to log this if data was discarded
|
|
139
|
-
@original_issue_times[issue] = { cutoff_time: cutoff_time, started_time: started }
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
|
|
144
145
|
def dependency_chart &block
|
|
145
146
|
execute_chart DependencyChart.new block
|
|
146
147
|
end
|
|
147
148
|
|
|
149
|
+
# have an explicit method here so that index.erb can call 'settings' just as any other erb can.
|
|
150
|
+
def settings
|
|
151
|
+
@file_config.project_config.settings
|
|
152
|
+
end
|
|
153
|
+
|
|
148
154
|
def execute_chart chart, &after_init_block
|
|
149
155
|
project_config = @file_config.project_config
|
|
150
156
|
|
|
157
|
+
chart.file_system = file_system
|
|
151
158
|
chart.issues = issues
|
|
152
159
|
chart.time_range = project_config.time_range
|
|
153
160
|
chart.timezone_offset = timezone_offset
|
|
154
|
-
chart.settings =
|
|
161
|
+
chart.settings = settings
|
|
155
162
|
|
|
156
163
|
chart.all_boards = project_config.all_boards
|
|
157
|
-
chart.board_id = find_board_id
|
|
164
|
+
chart.board_id = find_board_id
|
|
158
165
|
chart.holiday_dates = project_config.exporter.holiday_dates
|
|
159
166
|
|
|
160
167
|
time_range = @file_config.project_config.time_range
|
|
@@ -163,6 +170,7 @@ class HtmlReportConfig
|
|
|
163
170
|
|
|
164
171
|
after_init_block&.call chart
|
|
165
172
|
|
|
173
|
+
@charts << chart
|
|
166
174
|
html chart.run
|
|
167
175
|
end
|
|
168
176
|
|
|
@@ -174,19 +182,33 @@ class HtmlReportConfig
|
|
|
174
182
|
@file_config.issues
|
|
175
183
|
end
|
|
176
184
|
|
|
185
|
+
# For use by the user config
|
|
177
186
|
def find_board id
|
|
178
187
|
@file_config.project_config.all_boards[id]
|
|
179
188
|
end
|
|
180
189
|
|
|
181
|
-
|
|
182
|
-
@file_config.project_config.name
|
|
183
|
-
end
|
|
184
|
-
|
|
190
|
+
# For use by the user config
|
|
185
191
|
def boards
|
|
186
192
|
@file_config.project_config.board_configs.collect(&:id).collect { |id| find_board id }
|
|
187
193
|
end
|
|
188
194
|
|
|
189
|
-
def
|
|
190
|
-
|
|
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
|
|
191
213
|
end
|
|
192
214
|
end
|