jirametrics 3.3 → 3.3.1
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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6a2003eb5e447333b282b856e553c7f3f68792da373ce3b563c7f9e4e56dec45
|
|
4
|
+
data.tar.gz: 50b443fbcad6fe9dff8b247a7e0662cfed8c41724bb1e8dbd5d17c33a58ff416
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bdaa2c39f21752d0a1a815dc39dd10235cea508f873d89a32d722ea5db4d0d59eef1738febdffde062eaa31cb9b8c24598e2e0b96ccce478a296fe255c0b7997
|
|
7
|
+
data.tar.gz: 8a9013ed9d3bd5ebef3d2a70c1b870ec5624125d0f2fb5989d375e4d005b7259646d66b24d9babcf49d794671dbc65505770b16c5438624406e4dd21e182b98a
|
|
@@ -32,9 +32,40 @@ class DependencyChart < ChartBase
|
|
|
32
32
|
attr_accessor :color, :label
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
# A fill and the label colour that goes on top of it, kept together because they are not
|
|
36
|
+
# independent choices: some of these fills are light and take black text, others are dark and
|
|
37
|
+
# take white, and a node that took its fill from one and its label from another would be
|
|
38
|
+
# unreadable. Always hand them out as a pair.
|
|
39
|
+
Palette = Struct.new :fill, :label
|
|
40
|
+
|
|
41
|
+
def self.palette_entry name
|
|
42
|
+
Palette.new CssVariable["--dependency-chart-#{name}-color"],
|
|
43
|
+
CssVariable["--dependency-chart-#{name}-label-color"]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
PALETTE = {
|
|
47
|
+
story: palette_entry('story'),
|
|
48
|
+
task: palette_entry('task'),
|
|
49
|
+
bug: palette_entry('bug'),
|
|
50
|
+
epic: palette_entry('epic'),
|
|
51
|
+
spike: palette_entry('spike')
|
|
52
|
+
}.freeze
|
|
53
|
+
|
|
35
54
|
def initialize rules_block
|
|
36
55
|
super()
|
|
37
56
|
|
|
57
|
+
# Not the inherited type colours, because these are fills with label text sitting on top of
|
|
58
|
+
# them and those are line colours, judged against the page rather than against the text. See
|
|
59
|
+
# index.css for which colours these are and why.
|
|
60
|
+
@palette_by_type = {
|
|
61
|
+
'Story' => PALETTE[:story],
|
|
62
|
+
'Task' => PALETTE[:task],
|
|
63
|
+
'Bug' => PALETTE[:bug],
|
|
64
|
+
'Defect' => PALETTE[:bug],
|
|
65
|
+
'Epic' => PALETTE[:epic],
|
|
66
|
+
'Spike' => PALETTE[:spike]
|
|
67
|
+
}
|
|
68
|
+
|
|
38
69
|
header_text 'Dependencies'
|
|
39
70
|
description_text <<-HTML
|
|
40
71
|
<p>
|
|
@@ -57,7 +88,7 @@ class DependencyChart < ChartBase
|
|
|
57
88
|
'<div>No data matched the selected criteria. Nothing to show.</div>'
|
|
58
89
|
end
|
|
59
90
|
|
|
60
|
-
svg = execute_graphviz(dot_graph.join("\n"))
|
|
91
|
+
svg = restore_css_variables execute_graphviz(dot_graph.join("\n"))
|
|
61
92
|
"<h1 class='foldable'>#{@header_text}</h1><div>#{@description_text}#{shrink_svg svg}</div>"
|
|
62
93
|
end
|
|
63
94
|
|
|
@@ -77,6 +108,42 @@ class DependencyChart < ChartBase
|
|
|
77
108
|
result
|
|
78
109
|
end
|
|
79
110
|
|
|
111
|
+
# Graphviz has never heard of CSS variables. Handed one it emits a warning nobody sees and
|
|
112
|
+
# falls back to black, which is how a node ends up as black text on a black background. So a
|
|
113
|
+
# variable is swapped for a placeholder colour here and mapped back to the variable in the
|
|
114
|
+
# generated SVG, by #restore_css_variables. Anything that isn't a variable is left alone.
|
|
115
|
+
#
|
|
116
|
+
# The placeholders only need to be colours that nobody would ever choose deliberately, so that
|
|
117
|
+
# the CSS selectors matching them in the SVG cannot hit anything else.
|
|
118
|
+
def graphviz_color color
|
|
119
|
+
return color unless color.is_a? CssVariable
|
|
120
|
+
|
|
121
|
+
css_variable_placeholders[color.name] ||= format '#fe00%02x', css_variable_placeholders.size + 1
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def css_variable_placeholders
|
|
125
|
+
@css_variable_placeholders ||= {}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Turns the placeholders from #graphviz_color back into the variables they stood for, by way of
|
|
129
|
+
# a stylesheet that selects on the placeholder value. Rewriting the attributes in place would be
|
|
130
|
+
# the obvious move, but var() is only legal in a CSS value and not in an SVG presentation
|
|
131
|
+
# attribute, so the colour has to arrive as a real CSS rule. Author rules beat presentation
|
|
132
|
+
# attributes, so the placeholder never wins.
|
|
133
|
+
#
|
|
134
|
+
# fillcolor and fontcolor come out of graphviz as fill and color comes out as stroke, and an
|
|
135
|
+
# arrowhead uses both, so every placeholder gets a rule for each.
|
|
136
|
+
def restore_css_variables svg
|
|
137
|
+
return svg if css_variable_placeholders.empty?
|
|
138
|
+
|
|
139
|
+
rules = css_variable_placeholders.map do |name, placeholder|
|
|
140
|
+
%([fill="#{placeholder}"]{fill:var(#{name})}[stroke="#{placeholder}"]{stroke:var(#{name})})
|
|
141
|
+
end
|
|
142
|
+
svg.sub(/(?<opening_tag><svg\b[^>]*>)/) do
|
|
143
|
+
"#{Regexp.last_match[:opening_tag]}<style>#{rules.join}</style>"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
80
147
|
def make_dot_link issue_link:, link_rules:
|
|
81
148
|
result = +''
|
|
82
149
|
result << issue_link.origin.key.inspect
|
|
@@ -84,8 +151,9 @@ class DependencyChart < ChartBase
|
|
|
84
151
|
result << issue_link.other_issue.key.inspect
|
|
85
152
|
result << '['
|
|
86
153
|
result << 'label=' << (link_rules.label || issue_link.label).inspect
|
|
87
|
-
|
|
88
|
-
result << ',
|
|
154
|
+
line_color = graphviz_color(link_rules.line_color || default_link_color)
|
|
155
|
+
result << ',color=' << line_color.inspect
|
|
156
|
+
result << ',fontcolor=' << line_color.inspect
|
|
89
157
|
result << ',dir=both' if link_rules.bidirectional_arrows?
|
|
90
158
|
result << '];'
|
|
91
159
|
result
|
|
@@ -102,24 +170,47 @@ class DependencyChart < ChartBase
|
|
|
102
170
|
tooltip = "#{issue.key}: #{issue.summary}"
|
|
103
171
|
result << ",tooltip=#{tooltip[0..80].inspect}"
|
|
104
172
|
unless issue_rules.color == :none
|
|
105
|
-
|
|
173
|
+
fill_color = graphviz_color(issue_rules.color || color_for(type: issue.type))
|
|
174
|
+
result << %(,style=filled,fillcolor="#{fill_color}")
|
|
106
175
|
end
|
|
176
|
+
result << %(,fontcolor="#{graphviz_color label_color(issue: issue, issue_rules: issue_rules)}")
|
|
107
177
|
result << ']'
|
|
108
178
|
result
|
|
109
179
|
end
|
|
110
180
|
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
|
|
181
|
+
# A filled node is its own background, so its label is measured against the fill rather than
|
|
182
|
+
# against the page.
|
|
183
|
+
def label_color issue:, issue_rules:
|
|
184
|
+
return CssVariable['--default-text-color'] if issue_rules.color == :none
|
|
185
|
+
|
|
186
|
+
# A colour somebody configured is one we know nothing about, so guessing that a particular
|
|
187
|
+
# type's label would suit it is worse than falling back to the general one.
|
|
188
|
+
return CssVariable['--dependency-chart-label-color'] if issue_rules.color
|
|
189
|
+
|
|
190
|
+
palette_for(issue.type).label
|
|
191
|
+
end
|
|
192
|
+
|
|
114
193
|
def color_for type:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
194
|
+
palette_for(type).fill
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def palette_for type
|
|
198
|
+
@palette_by_type[type] ||= next_palette_entry
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def default_link_color
|
|
202
|
+
CssVariable['--dependency-chart-link-color']
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# An issue type we have no colour for cannot come from the shared palette, the way it does on
|
|
206
|
+
# every other chart. That palette holds line colours, judged against the page, and its first slot
|
|
207
|
+
# is Okabe-Ito blue, which leaves black label text at 4.05:1 once it becomes the fill behind it.
|
|
208
|
+
# These rotate this chart's own entries, which are all known to be comfortable. Two unknown types
|
|
209
|
+
# can therefore land on the same colour as each other or as a known type, which costs little:
|
|
210
|
+
# every node already names its type in the label.
|
|
211
|
+
def next_palette_entry
|
|
212
|
+
@fallback_color_index = (@fallback_color_index || -1) + 1
|
|
213
|
+
PALETTE.values[@fallback_color_index % PALETTE.size]
|
|
123
214
|
end
|
|
124
215
|
|
|
125
216
|
def build_dot_graph
|
|
@@ -33,6 +33,41 @@
|
|
|
33
33
|
--type-bug-color: #D55E00; /* Okabe-Ito vermilion */
|
|
34
34
|
--type-spike-color: #CC79A7; /* Okabe-Ito reddish purple */
|
|
35
35
|
|
|
36
|
+
/* The dependency chart is drawn by graphviz, which sets the label text inside the node rather
|
|
37
|
+
than beside it, so unlike --type-*-color above these are judged against the text and not
|
|
38
|
+
against the page. They are opaque, so the fill is the background the text sits on and there
|
|
39
|
+
is nothing for a dark theme to override.
|
|
40
|
+
|
|
41
|
+
These are Okabe-Ito hues rather than Okabe-Ito itself. At full strength they sit in a middle
|
|
42
|
+
band of lightness where NEITHER black nor white text is comfortable on them, which is the
|
|
43
|
+
thing to understand before changing any of this. Contrast ratio alone will mislead you here:
|
|
44
|
+
two earlier attempts cleared 7:1 and then 9:1, both of which WCAG calls comfortable, and both
|
|
45
|
+
still read as muddy in a real report. A mid-toned fill with dark text on it looks wrong at
|
|
46
|
+
almost any ratio. Going DARK and putting white text on it is what actually reads, so most of
|
|
47
|
+
these fills are dark, and each carries its own label colour: they are a pair, and changing a
|
|
48
|
+
fill without rechecking its label is how you get unreadable nodes.
|
|
49
|
+
|
|
50
|
+
They are not all dark, because the lightness spread is what does most of the work of keeping
|
|
51
|
+
them apart. Levelling them all to the same darkness measures about 3, which is unusable;
|
|
52
|
+
pastelling them all measures about 4.7, which is what the original colours here did. Run
|
|
53
|
+
`rake check_colors` for what they currently measure, against the full Okabe-Ito set as a
|
|
54
|
+
reference ceiling. */
|
|
55
|
+
--dependency-chart-story-color: #015C41; /* Okabe-Ito bluish green, darkened */
|
|
56
|
+
--dependency-chart-story-label-color: white;
|
|
57
|
+
--dependency-chart-task-color: #56B4E9; /* Okabe-Ito sky blue, unchanged */
|
|
58
|
+
--dependency-chart-task-label-color: black;
|
|
59
|
+
--dependency-chart-bug-color: #783200; /* Okabe-Ito vermilion, darkened. Also Defect */
|
|
60
|
+
--dependency-chart-bug-label-color: white;
|
|
61
|
+
--dependency-chart-epic-color: #F0E442; /* Okabe-Ito yellow, unchanged */
|
|
62
|
+
--dependency-chart-epic-label-color: black;
|
|
63
|
+
--dependency-chart-spike-color: #762A58; /* Okabe-Ito reddish purple, darkened */
|
|
64
|
+
--dependency-chart-spike-label-color: white;
|
|
65
|
+
|
|
66
|
+
/* For a node whose fill was set in the config rather than coming from the palette above, where
|
|
67
|
+
we cannot know what would read on it. */
|
|
68
|
+
--dependency-chart-label-color: black;
|
|
69
|
+
--dependency-chart-link-color: gray;
|
|
70
|
+
|
|
36
71
|
--status-category-todo-color: gray;
|
|
37
72
|
--status-category-inprogress-color: #0072B2; /* Okabe-Ito blue */
|
|
38
73
|
--status-category-done-color: #009E73; /* Okabe-Ito bluish green */
|
|
@@ -248,6 +283,10 @@ div.child_issue {
|
|
|
248
283
|
html[data-theme="dark"] {
|
|
249
284
|
--warning-banner: #9F2B00;
|
|
250
285
|
--non-working-days-color: #2f2f2f;
|
|
286
|
+
|
|
287
|
+
/* See the matching comment in the prefers-color-scheme block below. */
|
|
288
|
+
--dependency-chart-link-color: #999999;
|
|
289
|
+
|
|
251
290
|
--type-story-color: #2DCB9A; /* lighter bluish green for dark bg */
|
|
252
291
|
--type-task-color: #56B4E9; /* sky blue for dark bg */
|
|
253
292
|
--type-bug-color: #E69F00; /* orange instead of vermilion for dark bg */
|
|
@@ -400,6 +439,12 @@ html[data-theme="light"] {
|
|
|
400
439
|
--warning-banner: #9F2B00;
|
|
401
440
|
|
|
402
441
|
--non-working-days-color: #2f2f2f;
|
|
442
|
+
|
|
443
|
+
/* The only dependency chart colour needing a dark variant. The fills do not, because they
|
|
444
|
+
are opaque and are their own background, but link lines and their labels sit on the page.
|
|
445
|
+
Plain gray manages only 2.4:1 here against 3.94:1 in light mode, so it is lifted to match. */
|
|
446
|
+
--dependency-chart-link-color: #999999;
|
|
447
|
+
|
|
403
448
|
--type-story-color: #2DCB9A; /* lighter bluish green for dark bg */
|
|
404
449
|
--type-task-color: #56B4E9; /* sky blue for dark bg */
|
|
405
450
|
--type-bug-color: #E69F00; /* orange instead of vermilion for dark bg */
|
|
@@ -30,6 +30,22 @@ html[data-theme="light"] {
|
|
|
30
30
|
--type-bug-color: orange;
|
|
31
31
|
--type-spike-color: #9400D3;
|
|
32
32
|
|
|
33
|
+
/* The dependency chart's original pastels. They are pleasant but they are not safe: pale
|
|
34
|
+
colours sit close together, and green, peach and pale yellow are hard to separate for anyone
|
|
35
|
+
with red-green colour vision deficiency. Every one of them is light, so the label colours all
|
|
36
|
+
go back to black; the shipped set has dark fills that need white. */
|
|
37
|
+
--dependency-chart-story-color: #90EE90;
|
|
38
|
+
--dependency-chart-story-label-color: black;
|
|
39
|
+
--dependency-chart-task-color: #87CEFA;
|
|
40
|
+
--dependency-chart-task-label-color: black;
|
|
41
|
+
--dependency-chart-bug-color: #ffdab9;
|
|
42
|
+
--dependency-chart-bug-label-color: black;
|
|
43
|
+
--dependency-chart-epic-color: #fafad2;
|
|
44
|
+
--dependency-chart-epic-label-color: black;
|
|
45
|
+
--dependency-chart-spike-color: #DDA0DD;
|
|
46
|
+
--dependency-chart-spike-label-color: black;
|
|
47
|
+
--dependency-chart-link-color: gray;
|
|
48
|
+
|
|
33
49
|
--status-category-todo-color: gray;
|
|
34
50
|
--status-category-inprogress-color: #2663ff;
|
|
35
51
|
--status-category-done-color: #00ff00;
|
|
@@ -106,6 +122,7 @@ html[data-theme="light"] {
|
|
|
106
122
|
html[data-theme="dark"] {
|
|
107
123
|
--warning-banner: #9F2B00;
|
|
108
124
|
--non-working-days-color: #2f2f2f;
|
|
125
|
+
--dependency-chart-link-color: gray; /* the chart had no dark variant originally */
|
|
109
126
|
--type-story-color: #6fb86f;
|
|
110
127
|
--type-task-color: #0021b3;
|
|
111
128
|
--type-bug-color: #bb5603;
|
|
@@ -139,6 +156,7 @@ html[data-theme="dark"] {
|
|
|
139
156
|
:root {
|
|
140
157
|
--warning-banner: #9F2B00;
|
|
141
158
|
--non-working-days-color: #2f2f2f;
|
|
159
|
+
--dependency-chart-link-color: gray; /* the chart had no dark variant originally */
|
|
142
160
|
--type-story-color: #6fb86f;
|
|
143
161
|
--type-task-color: #0021b3;
|
|
144
162
|
--type-bug-color: #bb5603;
|
|
@@ -100,16 +100,20 @@ if show_stats
|
|
|
100
100
|
<% the_stats.each do |k, v| %>
|
|
101
101
|
<tr>
|
|
102
102
|
<td><%= k %></td>
|
|
103
|
-
<td style="text-align: right;"><%= v[:min] %></td>
|
|
104
|
-
<td style="text-align: right;"><%= v[:max] %></td>
|
|
105
|
-
<td style="text-align: right;"><%=
|
|
106
|
-
<td><%= v[:mode].join
|
|
103
|
+
<td style="text-align: right;"><%= stats_cell v[:min] %></td>
|
|
104
|
+
<td style="text-align: right;"><%= stats_cell v[:max] %></td>
|
|
105
|
+
<td style="text-align: right;"><%= stats_cell(v[:average]) { |average| format '%.2f', average } %></td>
|
|
106
|
+
<td><%= stats_cell(v[:mode]) { |modes| modes.join ', ' } %></td>
|
|
107
107
|
<% percentiles.each do |percentile| %>
|
|
108
|
-
<td style="text-align: right;"><%= v
|
|
108
|
+
<td style="text-align: right;"><%= stats_cell v.dig(:percentiles, percentile) %></td>
|
|
109
109
|
<% end %>
|
|
110
110
|
</tr>
|
|
111
111
|
<% end %>
|
|
112
112
|
</table>
|
|
113
|
+
<% if any_empty_stats? the_stats %>
|
|
114
|
+
<p><b>–</b> means no usable data for that group. Every item in it was excluded from the histogram
|
|
115
|
+
because it had no measurable cycle time. The Data Quality report has the details.</p>
|
|
116
|
+
<% end %>
|
|
113
117
|
</div>
|
|
114
118
|
<div>
|
|
115
119
|
<p>These statistics help understand the <i>"shape"</i> of the histogram distribution, to help us with predictions.</p>
|
|
@@ -94,6 +94,20 @@ class TimeBasedHistogram < TimeBasedChart
|
|
|
94
94
|
items_hash
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
+
# One cell of the statistics table. A group can survive grouping and still have nothing to plot,
|
|
98
|
+
# when every item in it was excluded for having no measurable cycle time, and then there are no
|
|
99
|
+
# statistics to show for it at all. Dashing the cells keeps the group visible: dropping the row
|
|
100
|
+
# would make it look like the group had never existed.
|
|
101
|
+
def stats_cell value
|
|
102
|
+
return '–' if value.nil?
|
|
103
|
+
|
|
104
|
+
block_given? ? yield(value) : value
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def any_empty_stats? the_stats
|
|
108
|
+
the_stats.any? { |_label, stats| stats.empty? }
|
|
109
|
+
end
|
|
110
|
+
|
|
97
111
|
def stats_for histogram_data:, percentiles:
|
|
98
112
|
return {} if histogram_data.empty?
|
|
99
113
|
|