jirametrics 2.4 → 2.30

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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/bin/jirametrics-mcp +5 -0
  3. data/lib/jirametrics/aggregate_config.rb +16 -3
  4. data/lib/jirametrics/aging_work_bar_chart.rb +193 -133
  5. data/lib/jirametrics/aging_work_in_progress_chart.rb +138 -42
  6. data/lib/jirametrics/aging_work_table.rb +63 -19
  7. data/lib/jirametrics/anonymizer.rb +81 -6
  8. data/lib/jirametrics/atlassian_document_format.rb +160 -0
  9. data/lib/jirametrics/bar_chart_range.rb +17 -0
  10. data/lib/jirametrics/blocked_stalled_change.rb +6 -4
  11. data/lib/jirametrics/board.rb +74 -22
  12. data/lib/jirametrics/board_config.rb +11 -3
  13. data/lib/jirametrics/board_feature.rb +14 -0
  14. data/lib/jirametrics/board_movement_calculator.rb +155 -0
  15. data/lib/jirametrics/cfd_data_builder.rb +108 -0
  16. data/lib/jirametrics/change_item.rb +54 -18
  17. data/lib/jirametrics/chart_base.rb +203 -30
  18. data/lib/jirametrics/css_variable.rb +2 -2
  19. data/lib/jirametrics/cumulative_flow_diagram.rb +208 -0
  20. data/lib/jirametrics/cycle_time_config.rb +137 -0
  21. data/lib/jirametrics/cycletime_histogram.rb +17 -38
  22. data/lib/jirametrics/cycletime_scatterplot.rb +18 -87
  23. data/lib/jirametrics/daily_view.rb +306 -0
  24. data/lib/jirametrics/daily_wip_by_age_chart.rb +5 -8
  25. data/lib/jirametrics/daily_wip_by_blocked_stalled_chart.rb +15 -5
  26. data/lib/jirametrics/daily_wip_by_parent_chart.rb +4 -6
  27. data/lib/jirametrics/daily_wip_chart.rb +36 -16
  28. data/lib/jirametrics/data_quality_report.rb +251 -42
  29. data/lib/jirametrics/dependency_chart.rb +42 -12
  30. data/lib/jirametrics/download_config.rb +27 -0
  31. data/lib/jirametrics/downloader.rb +185 -110
  32. data/lib/jirametrics/downloader_for_cloud.rb +287 -0
  33. data/lib/jirametrics/downloader_for_data_center.rb +95 -0
  34. data/lib/jirametrics/estimate_accuracy_chart.rb +75 -14
  35. data/lib/jirametrics/estimation_configuration.rb +25 -0
  36. data/lib/jirametrics/examples/aggregated_project.rb +9 -23
  37. data/lib/jirametrics/examples/standard_project.rb +57 -58
  38. data/lib/jirametrics/expedited_chart.rb +11 -10
  39. data/lib/jirametrics/exporter.rb +51 -14
  40. data/lib/jirametrics/file_config.rb +21 -6
  41. data/lib/jirametrics/file_system.rb +96 -4
  42. data/lib/jirametrics/fix_version.rb +13 -0
  43. data/lib/jirametrics/flow_efficiency_scatterplot.rb +115 -0
  44. data/lib/jirametrics/github_gateway.rb +115 -0
  45. data/lib/jirametrics/groupable_issue_chart.rb +12 -4
  46. data/lib/jirametrics/grouping_rules.rb +26 -4
  47. data/lib/jirametrics/html/aging_work_bar_chart.erb +8 -17
  48. data/lib/jirametrics/html/aging_work_in_progress_chart.erb +24 -5
  49. data/lib/jirametrics/html/aging_work_table.erb +13 -4
  50. data/lib/jirametrics/html/collapsible_issues_panel.erb +2 -2
  51. data/lib/jirametrics/html/cumulative_flow_diagram.erb +503 -0
  52. data/lib/jirametrics/html/daily_wip_chart.erb +41 -15
  53. data/lib/jirametrics/html/estimate_accuracy_chart.erb +4 -12
  54. data/lib/jirametrics/html/expedited_chart.erb +7 -24
  55. data/lib/jirametrics/html/flow_efficiency_scatterplot.erb +81 -0
  56. data/lib/jirametrics/html/hierarchy_table.erb +1 -1
  57. data/lib/jirametrics/html/index.css +336 -62
  58. data/lib/jirametrics/html/index.erb +16 -21
  59. data/lib/jirametrics/html/index.js +164 -0
  60. data/lib/jirametrics/html/legacy_colors.css +174 -0
  61. data/lib/jirametrics/html/sprint_burndown.erb +18 -25
  62. data/lib/jirametrics/html/throughput_chart.erb +43 -21
  63. data/lib/jirametrics/html/time_based_histogram.erb +123 -0
  64. data/lib/jirametrics/html/{cycletime_scatterplot.erb → time_based_scatterplot.erb} +16 -21
  65. data/lib/jirametrics/html/wip_by_column_chart.erb +250 -0
  66. data/lib/jirametrics/html_generator.rb +32 -0
  67. data/lib/jirametrics/html_report_config.rb +83 -76
  68. data/lib/jirametrics/issue.rb +499 -91
  69. data/lib/jirametrics/issue_collection.rb +33 -0
  70. data/lib/jirametrics/issue_printer.rb +97 -0
  71. data/lib/jirametrics/jira_gateway.rb +96 -16
  72. data/lib/jirametrics/mcp_server.rb +531 -0
  73. data/lib/jirametrics/project_config.rb +374 -130
  74. data/lib/jirametrics/pull_request.rb +30 -0
  75. data/lib/jirametrics/pull_request_cycle_time_histogram.rb +77 -0
  76. data/lib/jirametrics/pull_request_cycle_time_scatterplot.rb +88 -0
  77. data/lib/jirametrics/pull_request_review.rb +13 -0
  78. data/lib/jirametrics/raw_javascript.rb +17 -0
  79. data/lib/jirametrics/rules.rb +2 -2
  80. data/lib/jirametrics/self_or_issue_dispatcher.rb +2 -0
  81. data/lib/jirametrics/settings.json +10 -2
  82. data/lib/jirametrics/sprint.rb +13 -0
  83. data/lib/jirametrics/sprint_burndown.rb +47 -39
  84. data/lib/jirametrics/sprint_issue_change_data.rb +3 -3
  85. data/lib/jirametrics/status.rb +84 -19
  86. data/lib/jirametrics/status_collection.rb +83 -38
  87. data/lib/jirametrics/stitcher.rb +81 -0
  88. data/lib/jirametrics/throughput_by_completed_resolution_chart.rb +22 -0
  89. data/lib/jirametrics/throughput_chart.rb +73 -23
  90. data/lib/jirametrics/time_based_histogram.rb +139 -0
  91. data/lib/jirametrics/time_based_scatterplot.rb +107 -0
  92. data/lib/jirametrics/user.rb +12 -0
  93. data/lib/jirametrics/value_equality.rb +2 -2
  94. data/lib/jirametrics/wip_by_column_chart.rb +236 -0
  95. data/lib/jirametrics.rb +101 -66
  96. metadata +72 -16
  97. data/lib/jirametrics/cycletime_config.rb +0 -69
  98. data/lib/jirametrics/discard_changes_before.rb +0 -37
  99. data/lib/jirametrics/html/cycletime_histogram.erb +0 -47
  100. data/lib/jirametrics/html/data_quality_report.erb +0 -126
@@ -1,3 +1,4 @@
1
+ <%= seam_start %>
1
2
  <div class="chart">
2
3
  <canvas id="<%= chart_id %>" width="<%= canvas_width %>" height="<%= canvas_height %>"></canvas>
3
4
  </div>
@@ -19,25 +20,15 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
19
20
  scales: {
20
21
  x: {
21
22
  type: "time",
22
- scaleLabel: {
23
- display: true,
24
- labelString: 'Date Completed'
25
- },
23
+ <%= render_axis_title :x %>
26
24
  min: "<%= date_range.begin.to_s %>",
27
- max: "<%= date_range.end.to_s %>",
25
+ max: "<%= (date_range.end + 1).to_s %>",
28
26
  grid: {
29
27
  color: <%= CssVariable['--grid-line-color'].to_json %>
30
28
  },
31
29
  },
32
30
  y: {
33
- scaleLabel: {
34
- display: true,
35
- labelString: 'Days'
36
- },
37
- title: {
38
- display: true,
39
- text: 'Age in days'
40
- },
31
+ <%= render_axis_title :y %>
41
32
  min: 0,
42
33
  grid: {
43
34
  color: <%= CssVariable['--grid-line-color'].to_json %>
@@ -55,19 +46,11 @@ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
55
46
  autocolors: false,
56
47
  annotation: {
57
48
  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 %>
49
+ <%= working_days_annotation %>
68
50
  }
69
51
  }
70
52
  }
71
53
  }
72
54
  });
73
- </script>
55
+ </script>
56
+ <%= seam_end %>
@@ -0,0 +1,81 @@
1
+ <%= seam_start %>
2
+ <div class="chart">
3
+ <canvas id="<%= chart_id %>" width="<%= canvas_width %>" height="<%= canvas_height %>"></canvas>
4
+ </div>
5
+ <script>
6
+ new Chart(document.getElementById('<%= chart_id %>').getContext('2d'), {
7
+ type: 'scatter',
8
+ data: {
9
+ datasets: <%= JSON.generate(data_sets) %>
10
+ },
11
+ options: {
12
+ title: {
13
+ display: true,
14
+ text: "Cycletime Scatterplot"
15
+ },
16
+ responsive: <%= canvas_responsive? %>, // If responsive is true then it fills the screen
17
+ scales: {
18
+ x: {
19
+ scaleLabel: {
20
+ display: true,
21
+ labelString: 'Days'
22
+ },
23
+ <%= render_axis_title :x %>
24
+ grid: {
25
+ color: <%= CssVariable['--grid-line-color'].to_json %>
26
+ },
27
+
28
+ },
29
+ y: {
30
+ scaleLabel: {
31
+ display: true,
32
+ labelString: 'Percentage',
33
+ min: 0,
34
+ max: <%= @highest_cycletime %>
35
+ },
36
+ <%= render_axis_title :y %>
37
+ grid: {
38
+ color: <%= CssVariable['--grid-line-color'].to_json %>
39
+ },
40
+ }
41
+ },
42
+ plugins: {
43
+ tooltip: {
44
+ callbacks: {
45
+ label: function(context) {
46
+ return context.dataset.data[context.dataIndex].title
47
+ }
48
+ }
49
+ },
50
+ autocolors: false,
51
+ legend: {
52
+ onClick: (evt, legendItem, legend) => {
53
+ // Find the datasetMeta that corresponds to the item clicked
54
+ var i = 0
55
+ while(legendItem.text != legend.chart.getDatasetMeta(i).label) {
56
+ i++;
57
+ }
58
+ nextVisibility = !!legend.chart.getDatasetMeta(i).hidden;
59
+
60
+ // Hide/show the 85% line for that dataset
61
+ legend.chart.options.plugins.annotation.annotations["line"+(i/2)].display = nextVisibility;
62
+
63
+ // Hide/show the trendline for this dataset, if they were enabled. The trendline is always
64
+ // there but not always visible.
65
+ legend.chart.setDatasetVisibility(i+1, <%= !!@show_trend_lines %> && nextVisibility);
66
+
67
+ // Still run the default behaviour
68
+ Chart.defaults.plugins.legend.onClick(evt, legendItem, legend);
69
+ },
70
+ labels: {
71
+ filter: function(item, chart) {
72
+ // Logic to remove a particular legend item goes here
73
+ return !item.text.includes('Trendline');
74
+ }
75
+ }
76
+ }
77
+ }
78
+ }
79
+ });
80
+ </script>
81
+ <%= seam_start %>
@@ -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,74 +2,103 @@
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
 
8
9
  --non-working-days-color: #F0F0F0;
9
- --expedited-color: red;
10
- --blocked-color: #FF7400;
11
- --stalled-color: orange;
10
+ --expedited-color: #D55E00; /* Okabe-Ito vermilion */
11
+ --blocked-color: #D55E00; /* Okabe-Ito vermilion */
12
+ --stalled-color: #E69F00; /* Okabe-Ito orange */
12
13
  --dead-color: black;
13
14
 
14
- --type-story-color: #4bc14b;
15
- --type-task-color: blue;
16
- --type-bug-color: orange;
17
- --type-spike-color: #9400D3;
15
+ --type-story-color: #009E73; /* Okabe-Ito bluish green */
16
+ --type-task-color: #0072B2; /* Okabe-Ito blue */
17
+ --type-bug-color: #D55E00; /* Okabe-Ito vermilion */
18
+ --type-spike-color: #CC79A7; /* Okabe-Ito reddish purple */
18
19
 
19
20
  --status-category-todo-color: gray;
20
- --status-category-inprogress-color: #2663ff;
21
- --status-category-done-color: #00ff00;
21
+ --status-category-inprogress-color: #0072B2; /* Okabe-Ito blue */
22
+ --status-category-done-color: #009E73; /* Okabe-Ito bluish green */
23
+ --status-category-unknown-color: black;
22
24
 
23
- --aging-work-bar-chart-percentage-line-color: red;
25
+ --aging-work-bar-chart-percentage-line-color: #D55E00; /* Okabe-Ito vermilion */
24
26
  --aging-work-bar-chart-separator-color: white;
25
27
 
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: #56B4E9; /* Okabe-Ito sky blue */
32
+ --aging-work-in-progress-chart-shading-85-color: #F0E442; /* Okabe-Ito yellow */
33
+ --aging-work-in-progress-chart-shading-98-color: #E69F00; /* Okabe-Ito orange */
34
+ --aging-work-in-progress-chart-shading-100-color: #D55E00; /* Okabe-Ito vermilion */
35
+
29
36
  --aging-work-in-progress-by-age-trend-line-color: gray;
30
-
37
+
38
+ --aging-work-table-date-in-jeopardy: #F0E442; /* Okabe-Ito yellow */
39
+ --aging-work-table-date-overdue: #D55E00; /* Okabe-Ito vermilion */
40
+
31
41
  --hierarchy-table-inactive-item-text-color: gray;
32
42
 
33
- --wip-chart-completed-color: #00ff00;
34
- --wip-chart-completed-but-not-started-color: #99FF99;
35
- --wip-chart-duration-less-than-day-color: #ffef41;
36
- --wip-chart-duration-week-or-less-color: #dcc900;
37
- --wip-chart-duration-two-weeks-or-less-color: #dfa000;
38
- --wip-chart-duration-four-weeks-or-less-color: #eb7200;
39
- --wip-chart-duration-more-than-four-weeks-color: #e70000;
40
- --wip-chart-active-color: #326cff;
43
+ --wip-chart-completed-color: #009E73; /* Okabe-Ito bluish green */
44
+ --wip-chart-completed-but-not-started-color: #92D9C0; /* light bluish green */
45
+ --wip-chart-duration-less-than-day-color: #56B4E9; /* Okabe-Ito sky blue */
46
+ --wip-chart-duration-week-or-less-color: #F0E442; /* Okabe-Ito yellow */
47
+ --wip-chart-duration-two-weeks-or-less-color: #E69F00; /* Okabe-Ito orange */
48
+ --wip-chart-duration-four-weeks-or-less-color: #D55E00; /* Okabe-Ito vermilion */
49
+ --wip-chart-duration-more-than-four-weeks-color: #CC79A7; /* Okabe-Ito reddish purple */
50
+ --wip-chart-active-color: #0072B2; /* Okabe-Ito blue */
41
51
  --wip-chart-border-color: gray;
42
52
 
43
- --estimate-accuracy-chart-completed-fill-color: #00ff00;
44
- --estimate-accuracy-chart-completed-border-color: green;
45
- --estimate-accuracy-chart-active-fill-color: #FFCCCB;
46
- --estimate-accuracy-chart-active-border-color: red;
53
+ --wip-by-column-chart-bar-fill-color: #0072B2; /* Okabe-Ito blue */
54
+ --wip-by-column-chart-bar-text-color: #ffffff;
55
+ --wip-by-column-chart-limit-line-color: #D55E00; /* Okabe-Ito vermilion */
56
+ --wip-by-column-chart-recommendation-color: #009E73; /* Okabe-Ito bluish green */
57
+
58
+ --estimate-accuracy-chart-completed-fill-color: #92D9C0; /* light bluish green */
59
+ --estimate-accuracy-chart-completed-border-color: #009E73; /* Okabe-Ito bluish green */
60
+ --estimate-accuracy-chart-active-fill-color: #F4C6AD; /* light vermilion */
61
+ --estimate-accuracy-chart-active-border-color: #D55E00; /* Okabe-Ito vermilion */
47
62
 
48
63
  --expedited-chart-no-longer-expedited: gray;
49
- --expedited-chart-dot-issue-started-color: orange;
50
- --expedited-chart-dot-issue-stopped-color: green;
51
- --expedited-chart-dot-expedite-started-color: red;
52
- --expedited-chart-dot-expedite-stopped-color: green;
64
+ --expedited-chart-dot-issue-started-color: #E69F00; /* Okabe-Ito orange */
65
+ --expedited-chart-dot-issue-stopped-color: #009E73; /* Okabe-Ito bluish green */
66
+ --expedited-chart-dot-expedite-started-color: #D55E00; /* Okabe-Ito vermilion */
67
+ --expedited-chart-dot-expedite-stopped-color: #009E73; /* Okabe-Ito bluish green */
53
68
 
54
- --sprint-burndown-sprint-color-1: blue;
55
- --sprint-burndown-sprint-color-2: orange;
56
- --sprint-burndown-sprint-color-3: green;
57
- --sprint-burndown-sprint-color-4: red;
58
- --sprint-burndown-sprint-color-5: brown;
69
+ --sprint-burndown-sprint-color-1: #0072B2; /* Okabe-Ito blue */
70
+ --sprint-burndown-sprint-color-2: #E69F00; /* Okabe-Ito orange */
71
+ --sprint-burndown-sprint-color-3: #009E73; /* Okabe-Ito bluish green */
72
+ --sprint-burndown-sprint-color-4: #D55E00; /* Okabe-Ito vermilion */
73
+ --sprint-burndown-sprint-color-5: #CC79A7; /* Okabe-Ito reddish purple */
74
+ --sprint-burndown-sprint-color-6: #56B4E9; /* Okabe-Ito sky blue */
75
+ --sprint-burndown-sprint-color-7: #F0E442; /* Okabe-Ito yellow */
59
76
 
77
+ --sprint-color: #56B4E9; /* Okabe-Ito sky blue */
60
78
 
79
+ --daily-view-selected-issue-background: lightgray;
80
+ --daily-view-issue-border: #009E73; /* Okabe-Ito bluish green */
81
+ --daily-view-selected-issue-border: #D55E00; /* Okabe-Ito vermilion */
82
+
83
+ /* The first five are the standard priorities that Jira creates by default. */
84
+ --priority-color-highest: #dc2626; /* red-600 - urgent red */
85
+ --priority-color-high: #ea580c; /* orange-600 - warning orange */
86
+ --priority-color-medium: #9ca3af; /* gray-400 - neutral light gray */
87
+ --priority-color-low: #0891b2; /* cyan-600 - calm blue */
88
+ --priority-color-lowest: #64748b; /* slate-500 - muted slate */
89
+ /* Then here are some values we've seen in multiple instances. */
90
+ --priority-color-notset: gray;
91
+ --priority-color-critical: red;
61
92
  }
62
93
 
63
94
  body {
64
95
  background-color: var(--body-background);
65
96
  color: var(--default-text-color);
97
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
98
+ font-size: 14px;
99
+ line-height: 1.5;
66
100
  }
67
101
 
68
- h1 {
69
- border: 1px solid black;
70
- background: lightgray;
71
- padding-left: 0.2em;
72
- }
73
102
  dl, dd, dt {
74
103
  padding: 0;
75
104
  margin: 0;
@@ -99,9 +128,6 @@ table.standard {
99
128
  background-color: #eee;
100
129
  }
101
130
  }
102
- .quality_note_bullet {
103
- color: red;
104
- }
105
131
 
106
132
  .chart {
107
133
  background-color: white;
@@ -119,43 +145,283 @@ div.color_block {
119
145
  border: 1px solid black;
120
146
  }
121
147
 
148
+ ul.quality_report {
149
+ list-style-type: '⮕';
150
+ ::marker {
151
+ color: red;
152
+ }
153
+ li {
154
+ padding: 0.2em;
155
+ }
156
+ }
157
+
158
+ #footer {
159
+ text-align: center;
160
+ margin-top: 1em;
161
+ border-top: 1px solid gray;
162
+ }
163
+
164
+ div.daily_issue:hover {
165
+ background: var(--daily-view-selected-issue-background);
166
+ border-color: var(--daily-view-selected-issue-border);
167
+ }
168
+
169
+ div.daily_issue {
170
+ border: 1px solid var(--daily-view-issue-border);
171
+ padding: 0.5em;
172
+ .heading {
173
+ vertical-align: middle;
174
+ display: flex;
175
+ flex-wrap: wrap;
176
+ column-gap: 0.5em;
177
+ align-items: center;
178
+ }
179
+ table {
180
+ margin-left: 1em;
181
+ td {
182
+ vertical-align: top;
183
+ }
184
+ .time {
185
+ white-space: nowrap;
186
+ font-size: 0.8em;
187
+ }
188
+ }
189
+ .icon {
190
+ width: 1em;
191
+ height: 1em;
192
+ }
193
+ .account_id {
194
+ font-weight: bold;
195
+ }
196
+ .field {
197
+ border: 1px solid black;
198
+ color: white;
199
+ background: black;
200
+ padding-left: 0.2em;
201
+ padding-right: 0.2em;
202
+ border-radius: 0.2em;
203
+ }
204
+ .label {
205
+ border: 1px solid black;
206
+ padding-left: 0.2em;
207
+ padding-right: 0.2em;
208
+ border-radius: 0.2em;
209
+ }
210
+ h1 {
211
+ border: none;
212
+ background: none;
213
+ padding-left: 0;
214
+ }
215
+ margin-bottom: 0.5em;
216
+ }
217
+ div.child_issue:hover {
218
+ background: var(--body-background);
219
+ }
220
+ div.child_issue {
221
+ border: 1px dashed green;
222
+ margin: 0.2em;
223
+ margin-left: 1.5em;
224
+ padding: 0.5em;
225
+ }
226
+
227
+ /* Dark CSS variables — shared by the media query and the forced dark theme */
228
+ html[data-theme="dark"] {
229
+ --warning-banner: #9F2B00;
230
+ --non-working-days-color: #2f2f2f;
231
+ --type-story-color: #2DCB9A; /* lighter bluish green for dark bg */
232
+ --type-task-color: #56B4E9; /* sky blue for dark bg */
233
+ --type-bug-color: #E69F00; /* orange instead of vermilion for dark bg */
234
+ --body-background: #343434;
235
+ --default-text-color: #aaa;
236
+ --grid-line-color: #424242;
237
+ --expedited-color: #E69F00; /* Okabe-Ito orange for dark bg */
238
+ --blocked-color: #E69F00; /* Okabe-Ito orange for dark bg */
239
+ --stalled-color: #F0E442; /* Okabe-Ito yellow — distinct from blocked */
240
+ --wip-chart-active-color: #56B4E9; /* sky blue for dark bg */
241
+ --status-category-inprogress-color: #56B4E9; /* sky blue for dark bg */
242
+ --status-category-done-color: #2DCB9A; /* lighter bluish green for dark bg */
243
+ --hierarchy-table-inactive-item-text-color: #939393;
244
+ --wip-by-column-chart-bar-fill-color: #56B4E9; /* Okabe-Ito sky blue */
245
+ --wip-by-column-chart-bar-text-color: #000000;
246
+ --wip-by-column-chart-limit-line-color: #E69F00; /* Okabe-Ito orange */
247
+ --wip-by-column-chart-recommendation-color: #2DCB9A; /* lighter bluish green for dark bg */
248
+ --wip-chart-completed-color: #2DCB9A; /* lighter bluish green */
249
+ --wip-chart-duration-more-than-four-weeks-color: #DE9AC4; /* lighter reddish purple */
250
+ --estimate-accuracy-chart-completed-border-color: #2DCB9A;
251
+ --estimate-accuracy-chart-active-border-color: #E69F00;
252
+ --expedited-chart-dot-issue-stopped-color: #2DCB9A;
253
+ --expedited-chart-dot-expedite-started-color: #E69F00;
254
+ --expedited-chart-dot-expedite-stopped-color: #2DCB9A;
255
+ --sprint-burndown-sprint-color-1: #56B4E9; /* sky blue */
256
+ --sprint-burndown-sprint-color-3: #2DCB9A; /* lighter bluish green */
257
+ --sprint-burndown-sprint-color-4: #CC79A7; /* reddish purple (vermilion → orange conflicts with color-2) */
258
+ --sprint-burndown-sprint-color-5: #F0E442; /* yellow */
259
+ --sprint-burndown-sprint-color-6: #D55E00; /* vermilion (sky blue conflicts with color-1) */
260
+ --sprint-burndown-sprint-color-7: #92D9C0; /* light teal (yellow conflicts with color-5) */
261
+ --daily-view-selected-issue-background: #474747;
262
+ --daily-view-issue-border: #2DCB9A;
263
+ --daily-view-selected-issue-border: #E69F00;
264
+ --priority-color-highest: #ef4444;
265
+ --priority-color-high: #f97316;
266
+ --priority-color-low: #06b6d4;
267
+ --priority-color-lowest: #94a3b8;
268
+
269
+ a[href] { color: #1e8ad6; }
270
+ a[href]:hover { color: #3ba0e6; }
271
+ .chart { background: var(--body-background); }
272
+ table.standard {
273
+ th { background: var(--body-background); }
274
+ tr:nth-child(odd) { background-color: #656565; }
275
+ }
276
+ div.color_block { border: 1px solid lightgray; }
277
+ div.daily_issue {
278
+ .field { color: var(--default-text-color); }
279
+ }
280
+ }
281
+
282
+ /* Force light mode — overrides the dark media query when user explicitly picks light */
283
+ html[data-theme="light"] {
284
+ --warning-banner: yellow;
285
+ --non-working-days-color: #F0F0F0;
286
+ --type-story-color: #009E73; /* Okabe-Ito bluish green */
287
+ --type-task-color: #0072B2; /* Okabe-Ito blue */
288
+ --type-bug-color: #D55E00; /* Okabe-Ito vermilion */
289
+ --body-background: white;
290
+ --default-text-color: black;
291
+ --grid-line-color: lightgray;
292
+ --expedited-color: #D55E00; /* Okabe-Ito vermilion */
293
+ --blocked-color: #D55E00; /* Okabe-Ito vermilion */
294
+ --stalled-color: #E69F00; /* Okabe-Ito orange */
295
+ --wip-chart-active-color: #0072B2; /* Okabe-Ito blue */
296
+ --status-category-inprogress-color: #0072B2; /* Okabe-Ito blue */
297
+ --status-category-done-color: #009E73; /* Okabe-Ito bluish green */
298
+ --hierarchy-table-inactive-item-text-color: gray;
299
+ --wip-by-column-chart-bar-fill-color: #0072B2;
300
+ --wip-by-column-chart-bar-text-color: #ffffff;
301
+ --wip-by-column-chart-limit-line-color: #D55E00;
302
+ --wip-by-column-chart-recommendation-color: #009E73;
303
+ --wip-chart-completed-color: #009E73; /* Okabe-Ito bluish green */
304
+ --wip-chart-completed-but-not-started-color: #92D9C0;
305
+ --wip-chart-duration-less-than-day-color: #56B4E9;
306
+ --wip-chart-duration-week-or-less-color: #F0E442;
307
+ --wip-chart-duration-two-weeks-or-less-color: #E69F00;
308
+ --wip-chart-duration-four-weeks-or-less-color: #D55E00;
309
+ --wip-chart-duration-more-than-four-weeks-color: #CC79A7;
310
+ --daily-view-selected-issue-background: lightgray;
311
+ --daily-view-issue-border: #009E73;
312
+ --daily-view-selected-issue-border: #D55E00;
313
+ --priority-color-highest: #dc2626;
314
+ --priority-color-high: #ea580c;
315
+ --priority-color-low: #0891b2;
316
+ --priority-color-lowest: #64748b;
317
+
318
+ a[href] { color: revert; }
319
+ a[href]:hover { color: revert; }
320
+ .chart { background: white; }
321
+ table.standard {
322
+ th { background: white; }
323
+ tr:nth-child(odd) { background-color: #eee; }
324
+ }
325
+ div.color_block { border: 1px solid black; }
326
+ }
327
+
328
+ /* Theme toggle widget */
329
+ #theme-toggle {
330
+ position: fixed;
331
+ top: 0.5rem;
332
+ right: 0.5rem;
333
+ display: flex;
334
+ gap: 2px;
335
+ background: var(--body-background);
336
+ border: 1px solid var(--grid-line-color);
337
+ border-radius: 6px;
338
+ padding: 3px;
339
+ z-index: 1000;
340
+
341
+ button {
342
+ background: none;
343
+ border: none;
344
+ cursor: pointer;
345
+ padding: 2px 6px;
346
+ border-radius: 4px;
347
+ font-size: 1rem;
348
+ color: var(--default-text-color);
349
+ opacity: 0.5;
350
+ }
351
+
352
+ button:hover {
353
+ opacity: 1;
354
+ background: var(--grid-line-color);
355
+ }
356
+
357
+ button.active {
358
+ opacity: 1;
359
+ background: var(--grid-line-color);
360
+ }
361
+ }
362
+
122
363
  @media screen and (prefers-color-scheme: dark) {
123
364
  :root {
365
+ --warning-banner: #9F2B00;
366
+
124
367
  --non-working-days-color: #2f2f2f;
125
- --type-story-color: #6fb86f;
126
- --type-task-color: #0021b3;
127
- --type-bug-color: #bb5603;
368
+ --type-story-color: #2DCB9A; /* lighter bluish green for dark bg */
369
+ --type-task-color: #56B4E9; /* sky blue for dark bg */
370
+ --type-bug-color: #E69F00; /* orange instead of vermilion for dark bg */
128
371
 
129
372
  --body-background: #343434;
130
373
  --default-text-color: #aaa;
131
374
  --grid-line-color: #424242;
132
375
 
133
- --expedited-color: #b90000;
134
- --blocked-color: #c75b02;
135
- --stalled-color: #ae7202;
376
+ --expedited-color: #E69F00; /* Okabe-Ito orange for dark bg */
377
+ --blocked-color: #E69F00; /* Okabe-Ito orange for dark bg */
378
+ --stalled-color: #F0E442; /* Okabe-Ito yellow — distinct from blocked */
136
379
  --dead-color: black;
137
- --wip-chart-active-color: #2551c1;
380
+ --wip-chart-active-color: #56B4E9; /* sky blue for dark bg */
138
381
 
139
- --aging-work-in-progress-chart-shading-color: #b4b4b4;
382
+ --status-category-inprogress-color: #56B4E9; /* sky blue for dark bg */
383
+ --status-category-done-color: #2DCB9A; /* lighter bluish green for dark bg */
140
384
 
141
- --status-category-inprogress-color: #1c49bb;
385
+ --wip-by-column-chart-bar-fill-color: #56B4E9;
386
+ --wip-by-column-chart-bar-text-color: #000000;
387
+ --wip-by-column-chart-limit-line-color: #E69F00;
388
+ --wip-by-column-chart-recommendation-color: #2DCB9A;
142
389
 
143
390
  --cycletime-scatterplot-overall-trendline-color: gray;
144
391
 
145
392
  --hierarchy-table-inactive-item-text-color: #939393;
146
393
 
147
- --wip-chart-completed-color: #03cb03;
148
- --wip-chart-completed-but-not-started-color: #99FF99;
149
- --wip-chart-duration-less-than-day-color: #d2d988;
150
- --wip-chart-duration-week-or-less-color: #dfcd00;
151
- --wip-chart-duration-two-weeks-or-less-color: #cf9400;
152
- --wip-chart-duration-four-weeks-or-less-color: #c25e00;
153
- --wip-chart-duration-more-than-four-weeks-color: #8e0000;
154
- }
394
+ --wip-chart-completed-color: #2DCB9A; /* lighter bluish green */
395
+ --wip-chart-completed-but-not-started-color: #92D9C0;
396
+ --wip-chart-duration-less-than-day-color: #56B4E9;
397
+ --wip-chart-duration-week-or-less-color: #F0E442;
398
+ --wip-chart-duration-two-weeks-or-less-color: #E69F00;
399
+ --wip-chart-duration-four-weeks-or-less-color: #D55E00;
400
+ --wip-chart-duration-more-than-four-weeks-color: #DE9AC4; /* lighter reddish purple */
155
401
 
156
- h1 {
157
- color: #e0e0e0;
158
- background-color: #656565;
402
+ --estimate-accuracy-chart-completed-border-color: #2DCB9A;
403
+ --estimate-accuracy-chart-active-border-color: #E69F00;
404
+
405
+ --expedited-chart-dot-issue-stopped-color: #2DCB9A;
406
+ --expedited-chart-dot-expedite-started-color: #E69F00;
407
+ --expedited-chart-dot-expedite-stopped-color: #2DCB9A;
408
+
409
+ --sprint-burndown-sprint-color-1: #56B4E9; /* sky blue */
410
+ --sprint-burndown-sprint-color-3: #2DCB9A; /* lighter bluish green */
411
+ --sprint-burndown-sprint-color-4: #CC79A7; /* reddish purple (vermilion → orange conflicts with color-2) */
412
+ --sprint-burndown-sprint-color-5: #F0E442; /* yellow */
413
+ --sprint-burndown-sprint-color-6: #D55E00; /* vermilion (sky blue conflicts with color-1) */
414
+ --sprint-burndown-sprint-color-7: #92D9C0; /* light teal (yellow conflicts with color-5) */
415
+
416
+ --daily-view-selected-issue-background: #474747;
417
+ --daily-view-issue-border: #2DCB9A;
418
+ --daily-view-selected-issue-border: #E69F00;
419
+
420
+ --priority-color-highest: #ef4444; /* red-500 - bright urgent red */
421
+ --priority-color-high: #f97316; /* orange-500 - bright orange */
422
+ --priority-color-medium: #9ca3af; /* gray-400 - neutral light gray */
423
+ --priority-color-low: #06b6d4; /* cyan-500 - bright calm blue */
424
+ --priority-color-lowest: #94a3b8; /* slate-400 - muted light slate */
159
425
  }
160
426
 
161
427
  a[href] {
@@ -183,4 +449,12 @@ div.color_block {
183
449
  div.color_block {
184
450
  border: 1px solid lightgray;
185
451
  }
186
- }
452
+
453
+ div.daily_issue {
454
+ .field {
455
+ color: var(--default-text-color);
456
+ }
457
+ }
458
+ }
459
+
460
+ }
@@ -1,39 +1,34 @@
1
1
  <html>
2
2
  <head>
3
3
  <meta charset="UTF-8">
4
+ <title><%= project_name.empty? ? 'JiraMetrics' : "JiraMetrics - #{project_name}" %></title>
4
5
  <link rel="icon" type="image/png" href="https://github.com/mikebowler/jirametrics/blob/main/favicon.png?raw=true" />
5
6
  <script src="https://cdn.jsdelivr.net/npm/moment@2.29.1/moment.js"></script>
6
7
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
7
8
  <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment@^1"></script>
8
- <script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-annotation/1.2.2/chartjs-plugin-annotation.min.js" integrity="sha512-HycvvBSFvDEVyJ0tjE2rPmymkt6XqsP/Zo96XgLRjXwn6SecQqsn+6V/7KYev66OshZZ9+f9AttCGmYqmzytiw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-annotation/3.1.0/chartjs-plugin-annotation.min.js"></script>
9
10
  <script type="text/javascript">
10
- function expand_collapse(link_id, issues_id) {
11
- link_text = document.getElementById(link_id).textContent
12
- if( link_text == 'Show details') {
13
- document.getElementById(link_id).textContent = 'Hide details'
14
- document.getElementById(issues_id).style.display = 'block'
15
- }
16
- else {
17
- document.getElementById(link_id).textContent = 'Show details'
18
- document.getElementById(issues_id).style.display = 'none'
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
- })
26
-
11
+ <%= javascript %>
27
12
  </script>
28
13
  <style>
29
14
  <%= css %>
30
15
  </style>
16
+ <script type="text/javascript">
17
+ Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--default-text-color');
18
+ </script>
31
19
  </head>
32
20
  <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') %>
21
+ <div id="theme-toggle">
22
+ <button id="theme-btn-system" title="Use system preference">⊙</button>
23
+ <button id="theme-btn-light" title="Light mode">☀</button>
24
+ <button id="theme-btn-dark" title="Dark mode">☾</button>
35
25
  </div>
26
+ <noscript>
27
+ <div style="padding: 1em; background: red; color: white; font-size: 2em;">
28
+ Javascript is currently disabled and that means that almost all of the charts in this report won't render. If you've loaded this from a folder on SharePoint then save it locally and load it again.
29
+ </div>
30
+ </noscript>
36
31
  <%= "\n" + @sections.collect { |text, type| text if type == :header }.compact.join("\n\n") %>
37
- <%= "\n" + @sections.collect { |text, type| text if type == :body }.compact.join("\n\n") %>
32
+ <%= "\n" + @sections.collect { |text, type| text if type != :header }.compact.join("\n\n") %>
38
33
  </body>
39
34
  </html>