jirametrics 2.0.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/lib/jirametrics/aggregate_config.rb +1 -1
  3. data/lib/jirametrics/aging_work_bar_chart.rb +18 -13
  4. data/lib/jirametrics/aging_work_in_progress_chart.rb +8 -6
  5. data/lib/jirametrics/aging_work_table.rb +21 -16
  6. data/lib/jirametrics/anonymizer.rb +6 -5
  7. data/lib/jirametrics/chart_base.rb +35 -19
  8. data/lib/jirametrics/css_variable.rb +33 -0
  9. data/lib/jirametrics/cycletime_scatterplot.rb +8 -9
  10. data/lib/jirametrics/daily_wip_by_age_chart.rb +43 -17
  11. data/lib/jirametrics/daily_wip_by_blocked_stalled_chart.rb +32 -17
  12. data/lib/jirametrics/daily_wip_by_parent_chart.rb +42 -0
  13. data/lib/jirametrics/daily_wip_chart.rb +67 -6
  14. data/lib/jirametrics/data_quality_report.rb +1 -1
  15. data/lib/jirametrics/dependency_chart.rb +18 -14
  16. data/lib/jirametrics/downloader.rb +13 -11
  17. data/lib/jirametrics/examples/aggregated_project.rb +17 -20
  18. data/lib/jirametrics/examples/standard_project.rb +10 -18
  19. data/lib/jirametrics/expedited_chart.rb +17 -15
  20. data/lib/jirametrics/exporter.rb +26 -20
  21. data/lib/jirametrics/grouping_rules.rb +7 -1
  22. data/lib/jirametrics/html/aging_work_bar_chart.erb +12 -6
  23. data/lib/jirametrics/html/aging_work_in_progress_chart.erb +8 -2
  24. data/lib/jirametrics/html/aging_work_table.erb +11 -19
  25. data/lib/jirametrics/html/cycletime_histogram.erb +9 -3
  26. data/lib/jirametrics/html/cycletime_scatterplot.erb +10 -4
  27. data/lib/jirametrics/html/daily_wip_chart.erb +18 -5
  28. data/lib/jirametrics/html/expedited_chart.erb +11 -5
  29. data/lib/jirametrics/html/hierarchy_table.erb +1 -1
  30. data/lib/jirametrics/html/index.css +186 -0
  31. data/lib/jirametrics/html/index.erb +8 -36
  32. data/lib/jirametrics/html/sprint_burndown.erb +11 -6
  33. data/lib/jirametrics/html/story_point_accuracy_chart.erb +9 -4
  34. data/lib/jirametrics/html/throughput_chart.erb +11 -5
  35. data/lib/jirametrics/html_report_config.rb +28 -3
  36. data/lib/jirametrics/issue.rb +5 -3
  37. data/lib/jirametrics/jira_gateway.rb +5 -2
  38. data/lib/jirametrics/project_config.rb +14 -19
  39. data/lib/jirametrics/settings.json +7 -0
  40. data/lib/jirametrics/sprint_burndown.rb +10 -4
  41. data/lib/jirametrics/status_collection.rb +1 -1
  42. data/lib/jirametrics/story_point_accuracy_chart.rb +20 -10
  43. data/lib/jirametrics/throughput_chart.rb +10 -2
  44. data/lib/jirametrics.rb +2 -0
  45. metadata +7 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 26b8d8955807f88acf9f1cce8443d183a9fb72fce98424aba3c7c29e876a6a38
4
- data.tar.gz: '039eb9fd005ed0a036f20c72e8f9a8c4cb4bad19f030ed3cf0445bdb6afb0902'
3
+ metadata.gz: 0f5551ca8d38a0b12579429080055e21bb161066e7bb2237ada5d47628a212de
4
+ data.tar.gz: 7bd667226812044854dde31dfac4f347380b077369ec711bec5d6cb98a0082e8
5
5
  SHA512:
6
- metadata.gz: ecf2036e99a4bed4275da79e58740bac58c00d8a61120872abb0d51459631d7b7f5c80898bf4bb1212a87042c54cdb83ace95c1f86d2f2df43b8611626bbf1a0
7
- data.tar.gz: 5f01cabf487497389977fab8d341d5173e3c5835c8e42422d232aa3ba5b8aaa40af0cb975235374bf953cecf45b2615c5f6b6391560fcdcf6c0f1d77d6ae9e3f
6
+ metadata.gz: 397a3c4eb84497df209c702858a77def1d9606eecf84d08a8ab7b4f0a6decd968f77ed617f8189acb4916e372742bfc8352bb0fcd2b5ad74e04880c03680fece
7
+ data.tar.gz: 8aa2cbc8d7e60ed80749bf58c48d78689adcb65892ad133a378956b308fdb3d326a665eb3a884bd9d29783142639813f696c77f393697e285a0b7769fbe0ad18
@@ -3,7 +3,7 @@
3
3
  require 'date'
4
4
 
5
5
  class AggregateConfig
6
- attr_reader :project_config
6
+ attr_reader :project_config, :included_projects
7
7
 
8
8
  def initialize project_config:, block:
9
9
  @project_config = project_config
@@ -17,15 +17,17 @@ class AgingWorkBarChart < ChartBase
17
17
  <p>
18
18
  There are potentially three bars for each issue, although a bar may be missing if the issue has no
19
19
  information relevant to that. Hovering over any of the bars will provide more details.
20
- <ol><li>The top bar tells you what status the issue is in at any time.</li>
21
- <li>The middle bar indicates blocked and stalled states. A lighter orange is stalled and a darker,
22
- reddish colour is blocked.</li>
23
- <li>The bottom bar indicated an expedited state.</li></ol>
24
- </p>
25
- <p>
26
- The gray backgrounds indicate weekends and the red vertical line indicates the 85% point for all
27
- items in this time period. Anything that started to the left of that is now an outlier.
20
+ <ol>
21
+ <li>The top bar tells you what status the issue is in at any time. The colour indicates the
22
+ status category, which will be one of #{color_block '--status-category-todo-color'} To Do,
23
+ #{color_block '--status-category-inprogress-color'} In Progress,
24
+ or #{color_block '--status-category-done-color'} Done</li>
25
+ <li>The middle bar indicates #{color_block '--blocked-color'} blocked
26
+ or #{color_block '--stalled-color'} stalled.</li>
27
+ <li>The bottom bar indicated #{color_block '--expedited-color'} expedited.</li>
28
+ </ol>
28
29
  </p>
30
+ #{ describe_non_working_days }
29
31
  HTML
30
32
 
31
33
  # Because this one will size itself as needed, we start with a smaller default size
@@ -65,7 +67,7 @@ class AgingWorkBarChart < ChartBase
65
67
  issue_label: issue_label,
66
68
  title_label: 'Expedited',
67
69
  stack: 'expedited',
68
- color: 'red',
70
+ color: CssVariable['--expedited-color'],
69
71
  start_date: issue_start_date
70
72
  ) { |day| issue.expedited_on_date?(day) }
71
73
  ].compact.flatten.each do |data|
@@ -109,7 +111,7 @@ class AgingWorkBarChart < ChartBase
109
111
  title: "#{issue.type} : #{change.value}"
110
112
  }],
111
113
  backgroundColor: status_category_color(status),
112
- borderColor: 'white',
114
+ borderColor: CssVariable['--aging-work-bar-chart-separator-color'],
113
115
  borderWidth: {
114
116
  top: 0,
115
117
  right: 1,
@@ -144,10 +146,13 @@ class AgingWorkBarChart < ChartBase
144
146
  end
145
147
 
146
148
  def one_block_change_data_set starting_change:, ending_time:, issue_label:, stack:, issue_start_time:
147
- color = settings['colors']['blocked']
148
- color = settings['colors']['stalled'] if starting_change.stalled?
149
+ deprecated message: 'blocked color should be set via css now', date: '2024-05-03' if settings['blocked_color']
150
+ deprecated message: 'blocked color should be set via css now', date: '2024-05-03' if settings['stalled_color']
151
+
152
+ color = settings['blocked_color'] || '--blocked-color'
153
+ color = settings['stalled_color'] || '--stalled-color' if starting_change.stalled?
149
154
  {
150
- backgroundColor: color,
155
+ backgroundColor: CssVariable[color],
151
156
  data: [
152
157
  {
153
158
  title: starting_change.reasons,
@@ -14,13 +14,14 @@ class AgingWorkInProgressChart < ChartBase
14
14
  description_text <<-HTML
15
15
  <p>
16
16
  This chart shows only work items that have started but not completed, grouped by the column
17
- they're currently in. Hovering over a dot will show you the ID of that work item.
18
- </p>
19
- <p>
20
- The gray area indicates the 85% mark for work items that have passed through here - 85% of
21
- previous work items left this column while still inside the gray area. Any work items above
22
- the gray area are outliers and they are the items that you should pay special attention to.
17
+ they're currently in. Hovering over a dot will show you the ID of that work item.
23
18
  </p>
19
+ <div>
20
+ The #{color_block '--non-working-days-color'} shaded area indicates the 85%
21
+ mark for work items that have passed through here; 85% of
22
+ previous work items left this column while still inside the shaded area. Any work items above
23
+ the shading are outliers and they are the items that you should pay special attention to.
24
+ </div>
24
25
  HTML
25
26
  init_configuration_block(block) do
26
27
  grouping_rules do |issue, rule|
@@ -87,6 +88,7 @@ class AgingWorkInProgressChart < ChartBase
87
88
  'label' => "#{percentage}%",
88
89
  'barPercentage' => 1.0,
89
90
  'categoryPercentage' => 1.0,
91
+ 'backgroundColor' => CssVariable['--aging-work-in-progress-chart-shading-color'],
90
92
  'data' => days_at_percentage_threshold_for_all_columns(percentage: percentage, issues: @issues).drop(1)
91
93
  }
92
94
  end
@@ -7,14 +7,22 @@ class AgingWorkTable < ChartBase
7
7
 
8
8
  def initialize block
9
9
  super()
10
- @blocked_icon = '🛑'
11
- @expedited_icon = '🔥'
12
- @stalled_icon = '🟧'
13
10
  @stalled_threshold = 5
14
- @dead_icon = '⬛'
15
11
  @dead_threshold = 45
16
12
  @age_cutoff = 0
17
13
 
14
+ header_text 'Aging Work Table'
15
+ description_text <<-TEXT
16
+ <p>
17
+ This chart shows all active (started but not completed) work, ordered from oldest at the top to
18
+ newest at the bottom.
19
+ </p>
20
+ <p>
21
+ If there are expedited items that haven't yet started then they're at the bottom of the table.
22
+ By the very definition of expedited, if we haven't started them already, we'd better get on that.
23
+ </p>
24
+ TEXT
25
+
18
26
  instance_eval(&block) if block
19
27
  end
20
28
 
@@ -28,7 +36,7 @@ class AgingWorkTable < ChartBase
28
36
  end
29
37
  aging_issues += expedited_but_not_started.sort_by(&:created)
30
38
 
31
- render(binding, __FILE__)
39
+ wrap_and_render(binding, __FILE__)
32
40
  end
33
41
 
34
42
  def select_aging_issues
@@ -54,7 +62,7 @@ class AgingWorkTable < ChartBase
54
62
  return unless issue.expedited?
55
63
 
56
64
  name = issue.raw['fields']['priority']['name']
57
- icon_span(title: "Expedited: Has a priority of &quot;#{name}&quot;", icon: @expedited_icon)
65
+ color_block '--expedited-color', title: "Expedited: Has a priority of &quot;#{name}&quot;"
58
66
  end
59
67
 
60
68
  def blocked_text issue
@@ -63,27 +71,24 @@ class AgingWorkTable < ChartBase
63
71
 
64
72
  current = issue.blocked_stalled_changes(end_time: time_range.end)[-1]
65
73
  if current.blocked?
66
- icon_span title: current.reasons, icon: @blocked_icon
74
+ color_block '--blocked-color', title: current.reasons
67
75
  elsif current.stalled?
68
76
  if current.stalled_days && current.stalled_days > @dead_threshold
69
- icon_span(
77
+ color_block(
78
+ '--dead-color',
70
79
  title: "Dead? Hasn&apos;t had any activity in #{label_days current.stalled_days}. " \
71
- 'Does anyone still care about this?',
72
- icon: @dead_icon
80
+ 'Does anyone still care about this?'
73
81
  )
74
82
  else
75
- icon_span(
76
- title: current.reasons,
77
- icon: @stalled_icon
78
- )
83
+ color_block '--stalled-color', title: current.reasons
79
84
  end
80
85
  end
81
86
  end
82
87
 
83
88
  def unmapped_status_text issue
84
89
  icon_span(
85
- title: "The status #{issue.status.name.inspect} is not mapped to any column and will not be visible",
86
- icon: ' ⁉️'
90
+ title: "Not visible: The status #{issue.status.name.inspect} is not mapped to any column and will not be visible",
91
+ icon: ' 👀'
87
92
  )
88
93
  end
89
94
 
@@ -12,6 +12,7 @@ class Anonymizer
12
12
  @all_boards = @project_config.all_boards
13
13
  @possible_statuses = @project_config.possible_statuses
14
14
  @date_adjustment = date_adjustment
15
+ @file_system = project_config.exporter.file_system
15
16
  end
16
17
 
17
18
  def run
@@ -20,7 +21,7 @@ class Anonymizer
20
21
  # anonymize_issue_statuses
21
22
  anonymize_board_names
22
23
  shift_all_dates unless @date_adjustment.zero?
23
- puts 'Anonymize done'
24
+ @file_system.log 'Anonymize done'
24
25
  end
25
26
 
26
27
  def random_phrase
@@ -30,7 +31,7 @@ class Anonymizer
30
31
  5.times do |i|
31
32
  return RandomWord.phrases.next.tr('_', ' ')
32
33
  rescue # rubocop:disable Style/RescueStandardError We don't care what exception was thrown.
33
- puts "Random word blew up on attempt #{i + 1}"
34
+ @file_system.log "Random word blew up on attempt #{i + 1}"
34
35
  end
35
36
  end
36
37
 
@@ -55,7 +56,7 @@ class Anonymizer
55
56
 
56
57
  def anonymize_column_names
57
58
  @all_boards.each_key do |board_id|
58
- puts "Anonymizing column names for board #{board_id}"
59
+ @file_system.log "Anonymizing column names for board #{board_id}"
59
60
 
60
61
  column_name = 'Column-A'
61
62
  @all_boards[board_id].visible_columns.each do |column|
@@ -93,7 +94,7 @@ class Anonymizer
93
94
  end
94
95
 
95
96
  def anonymize_issue_statuses
96
- puts 'Anonymizing issue statuses and status categories'
97
+ @file_system.log 'Anonymizing issue statuses and status categories'
97
98
  status_name_hash = build_status_name_hash
98
99
 
99
100
  @issues.each do |issue|
@@ -130,7 +131,7 @@ class Anonymizer
130
131
  end
131
132
 
132
133
  def shift_all_dates
133
- puts "Shifting all dates by #{@date_adjustment} days"
134
+ @file_system.log "Shifting all dates by #{@date_adjustment} days"
134
135
  @issues.each do |issue|
135
136
  issue.changes.each do |change|
136
137
  change.time = change.time + @date_adjustment
@@ -10,17 +10,11 @@ class ChartBase
10
10
 
11
11
  def initialize
12
12
  @chart_colors = {
13
- 'dark:Story' => 'green',
14
- 'dark:Task' => 'blue',
15
- 'dark:Bug' => 'orange',
16
- 'dark:Defect' => 'orange',
17
- 'dark:Spike' => '#9400D3', # dark purple
18
- 'light:Story' => '#90EE90',
19
- 'light:Task' => '#87CEFA',
20
- 'light:Bug' => '#ffdab9',
21
- 'light:Defect' => 'orange',
22
- 'light:Epic' => '#fafad2',
23
- 'light:Spike' => '#DDA0DD' # light purple
13
+ 'Story' => CssVariable['--type-story-color'],
14
+ 'Task' => CssVariable['--type-task-color'],
15
+ 'Bug' => CssVariable['--type-bug-color'],
16
+ 'Defect' => CssVariable['--type-bug-color'],
17
+ 'Spike' => CssVariable['--type-spike-color']
24
18
  }
25
19
  @canvas_width = 800
26
20
  @canvas_height = 200
@@ -44,11 +38,16 @@ class ChartBase
44
38
  erb.result(caller_binding)
45
39
  end
46
40
 
47
- # Render the file and then wrap it with standard headers and quality checks.
48
- def wrap_and_render caller_binding, file
41
+ def render_top_text caller_binding
49
42
  result = +''
50
43
  result << "<h1>#{@header_text}</h1>" if @header_text
51
44
  result << ERB.new(@description_text).result(caller_binding) if @description_text
45
+ end
46
+
47
+ # Render the file and then wrap it with standard headers and quality checks.
48
+ def wrap_and_render caller_binding, file
49
+ result = +''
50
+ result << render_top_text(caller_binding)
52
51
  result << render(caller_binding, file)
53
52
  result
54
53
  end
@@ -57,8 +56,8 @@ class ChartBase
57
56
  @@chart_counter += 1
58
57
  end
59
58
 
60
- def color_for type:, shade: :dark
61
- @chart_colors["#{shade}:#{type}"] ||= random_color
59
+ def color_for type:
60
+ @chart_colors[type] ||= random_color
62
61
  end
63
62
 
64
63
  def label_days days
@@ -199,15 +198,15 @@ class ChartBase
199
198
  color = status_category_color status
200
199
 
201
200
  text = is_category ? status.category_name : status.name
202
- "<span style='color: #{color}'>#{text}</span>"
201
+ "<span title='Category: #{status.category_name}'>#{color_block color.name} #{text}</span>"
203
202
  end
204
203
 
205
204
  def status_category_color status
206
205
  case status.category_name
207
206
  when nil then 'black'
208
- when 'To Do' then 'gray'
209
- when 'In Progress' then 'blue'
210
- when 'Done' then 'green'
207
+ when 'To Do' then CssVariable['--status-category-todo-color']
208
+ when 'In Progress' then CssVariable['--status-category-inprogress-color']
209
+ when 'Done' then CssVariable['--status-category-done-color']
211
210
  end
212
211
  end
213
212
 
@@ -236,4 +235,21 @@ class ChartBase
236
235
  @issues = issues.filter_map { |i| @filter_issues_block.call(i) }.uniq
237
236
  puts @issues.collect(&:key).join(', ')
238
237
  end
238
+
239
+ def color_block color, title: nil
240
+ result = +''
241
+ result << "<div class='color_block' style='background: var(#{color});'"
242
+ result << " title=#{title.inspect}" if title
243
+ result << '></div>'
244
+ result
245
+ end
246
+
247
+ def describe_non_working_days
248
+ <<-TEXT
249
+ <div class='p'>
250
+ The #{color_block '--non-working-days-color'} vertical bars indicate non-working days; weekends
251
+ and any other holidays mentioned in the configuration.
252
+ </div>
253
+ TEXT
254
+ end
239
255
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CssVariable
4
+ attr_reader :name
5
+
6
+ def self.[](name)
7
+ if name.start_with? '--'
8
+ CssVariable.new name
9
+ else
10
+ name
11
+ end
12
+ end
13
+
14
+ def initialize name
15
+ @name = name
16
+ end
17
+
18
+ def to_json(*_args)
19
+ "getComputedStyle(document.body).getPropertyValue('#{@name}')"
20
+ end
21
+
22
+ def to_s
23
+ "var(#{@name})"
24
+ end
25
+
26
+ def inspect
27
+ "CssVariable['#{@name}']"
28
+ end
29
+
30
+ def == other
31
+ self.class == other.class && @name == other.name
32
+ end
33
+ end
@@ -12,20 +12,19 @@ class CycletimeScatterplot < ChartBase
12
12
 
13
13
  header_text 'Cycletime Scatterplot'
14
14
  description_text <<-HTML
15
- <p>
15
+ <div class="p">
16
16
  This chart shows only completed work and indicates both what day it completed as well as
17
17
  how many days it took to get done. Hovering over a dot will show you the ID of the work item.
18
- </p>
19
- <p>
20
- The gray line indicates the 85th percentile (<%= overall_percent_line %> days). 85% of all
18
+ </div>
19
+ <div class="p">
20
+ The #{color_block '--cycletime-scatterplot-overall-trendline-color'} line indicates the 85th
21
+ percentile (<%= overall_percent_line %> days). 85% of all
21
22
  items on this chart fall on or below the line and the remaining 15% are above the line. 85%
22
23
  is a reasonable proxy for "most" so that we can say that based on this data set, we can
23
24
  predict that most work of this type will complete in <%= overall_percent_line %> days or
24
25
  less. The other lines reflect the 85% line for that respective type of work.
25
- </p>
26
- <p>
27
- The gray vertical bars indicate weekends, when theoretically we aren't working.
28
- </p>
26
+ </div>
27
+ #{ describe_non_working_days }
29
28
  HTML
30
29
 
31
30
  init_configuration_block block do
@@ -44,7 +43,7 @@ class CycletimeScatterplot < ChartBase
44
43
 
45
44
  data_sets = create_datasets completed_issues
46
45
  overall_percent_line = calculate_percent_line(completed_issues)
47
- @percentage_lines << [overall_percent_line, 'gray']
46
+ @percentage_lines << [overall_percent_line, CssVariable['--cycletime-scatterplot-overall-trendline-color']]
48
47
 
49
48
  return "<h1>#{@header_text}</h1>No data matched the selected criteria. Nothing to show." if data_sets.empty?
50
49
 
@@ -3,23 +3,48 @@
3
3
  require 'jirametrics/daily_wip_chart'
4
4
 
5
5
  class DailyWipByAgeChart < DailyWipChart
6
+ def initialize block = nil
7
+ super(block)
8
+
9
+ add_trend_line line_color: '--aging-work-in-progress-by-age-trend-line-color', group_labels: [
10
+ 'Less than a day',
11
+ 'A week or less',
12
+ 'Two weeks or less',
13
+ 'Four weeks or less',
14
+ 'More than four weeks'
15
+ ]
16
+ end
17
+
6
18
  def default_header_text
7
19
  'Daily WIP grouped by Age'
8
20
  end
9
21
 
10
22
  def default_description_text
11
23
  <<-HTML
12
- <p>
24
+ <div class="p">
13
25
  This chart shows the highest WIP on each given day. The WIP is color coded so you can see
14
26
  how old it is and hovering over the bar will show you exactly which work items it relates
15
- to. The green bar underneath, shows how many items completed on that day.
16
- </p>
17
- <p>
18
- "Completed without being started" reflects the fact that while we know that it completed
19
- that day, we were unable to determine when it had started. These items will show up in
20
- white at the top. Note that the white is approximate because we don't know exactly when
21
- it started so we're guessing.
22
- </p>
27
+ to. The #{color_block '--wip-chart-completed-color'}
28
+ #{color_block '--wip-chart-completed-but-not-started-color'}
29
+ bars underneath, show how many items completed on that day.
30
+ </div>
31
+ <% if @has_completed_but_not_started %>
32
+ <div class="p">
33
+ #{color_block '--wip-chart-completed-but-not-started-color'} "Completed but not started"
34
+ reflects the fact that while we know that it completed that day, we were unable to determine when
35
+ it had started; it had moved directly from a To Do status to a Done status.
36
+ The #{color_block '--body-background'} shading at the top shows when they might
37
+ have been active. Note that the this grouping is approximate as we just don't know for sure.
38
+ </div>
39
+ <% end %>
40
+ #{describe_non_working_days}
41
+ <div class="p">
42
+ The #{color_block '--aging-work-in-progress-by-age-trend-line-color'} dashed line is a general trend line.
43
+ <% if @has_completed_but_not_started %>
44
+ Note that this trend line only includes items where we know both the start and end times of
45
+ the work so it may not be as accurate as we hope.
46
+ <% end %>
47
+ </div>
23
48
  HTML
24
49
  end
25
50
 
@@ -31,6 +56,7 @@ class DailyWipByAgeChart < DailyWipChart
31
56
  rules.issue_hint = "(age: #{label_days (rules.current_date - started + 1).to_i})" if started
32
57
 
33
58
  if stopped && started.nil? # We can't tell when it started
59
+ @has_completed_but_not_started = true
34
60
  not_started stopped: stopped, rules: rules, created: issue.created.to_date
35
61
  elsif stopped == rules.current_date
36
62
  stopped_today rules: rules
@@ -42,11 +68,11 @@ class DailyWipByAgeChart < DailyWipChart
42
68
  def not_started stopped:, rules:, created:
43
69
  if stopped == rules.current_date
44
70
  rules.label = 'Completed but not started'
45
- rules.color = '#66FF66'
71
+ rules.color = '--wip-chart-completed-but-not-started-color'
46
72
  rules.group_priority = -1
47
73
  else
48
74
  rules.label = 'Start date unknown'
49
- rules.color = 'white'
75
+ rules.color = '--body-background'
50
76
  rules.group_priority = 11
51
77
  created_days = rules.current_date - created + 1
52
78
  rules.issue_hint = "(created: #{label_days created_days.to_i} earlier, stopped on #{stopped})"
@@ -55,7 +81,7 @@ class DailyWipByAgeChart < DailyWipChart
55
81
 
56
82
  def stopped_today rules:
57
83
  rules.label = 'Completed'
58
- rules.color = '#009900'
84
+ rules.color = '--wip-chart-completed-color'
59
85
  rules.group_priority = -2
60
86
  end
61
87
 
@@ -65,23 +91,23 @@ class DailyWipByAgeChart < DailyWipChart
65
91
  case age
66
92
  when 1
67
93
  rules.label = 'Less than a day'
68
- rules.color = '#aaaaaa'
94
+ rules.color = '--wip-chart-duration-less-than-day-color'
69
95
  rules.group_priority = 10 # Highest is top
70
96
  when 2..7
71
97
  rules.label = 'A week or less'
72
- rules.color = '#80bfff'
98
+ rules.color = '--wip-chart-duration-week-or-less-color'
73
99
  rules.group_priority = 9
74
100
  when 8..14
75
101
  rules.label = 'Two weeks or less'
76
- rules.color = '#ffd700'
102
+ rules.color = '--wip-chart-duration-two-weeks-or-less-color'
77
103
  rules.group_priority = 8
78
104
  when 15..28
79
105
  rules.label = 'Four weeks or less'
80
- rules.color = '#ce6300'
106
+ rules.color = '--wip-chart-duration-four-weeks-or-less-color'
81
107
  rules.group_priority = 7
82
108
  else
83
109
  rules.label = 'More than four weeks'
84
- rules.color = '#990000'
110
+ rules.color = '--wip-chart-duration-more-than-four-weeks-color'
85
111
  rules.group_priority = 6
86
112
  end
87
113
  end
@@ -9,18 +9,32 @@ class DailyWipByBlockedStalledChart < DailyWipChart
9
9
 
10
10
  def default_description_text
11
11
  <<-HTML
12
- <p>
13
- This chart highlights work that is blocked or stalled on each given day. In Jira terms, blocked
14
- means that the issue has been "flagged". Stalled indicates that the item hasn't had any updates in 5 days.
15
- </p>
16
- <p>
17
- Note that if an item tracks as both blocked and stalled, it will only show up in the flagged totals.
12
+ <div class="p">
13
+ This chart highlights work that is #{color_block '--blocked-color'} blocked,
14
+ #{color_block '--stalled-color'} stalled, or
15
+ #{color_block '--wip-chart-active-color'} active on each given day.
16
+ <ul>
17
+ <li>#{color_block '--blocked-color'} Blocked could mean that the item has been flagged or it's
18
+ in a status that is configured as blocked, or it could have a link showing that it is blocked
19
+ by another item. It all depends how the report has been configured.</li>
20
+ <li>#{color_block '--stalled-color'} Stalled indicates that there has been no activity on this
21
+ item in five days.</li>
22
+ </ul>
23
+ </div>
24
+ <div class="p">
25
+ Note that if an item tracks as both blocked and stalled, it will only show up in the blocked totals.
18
26
  It will not be double counted.
19
- </p>
20
- <p>
21
- The white section reflects items that have stopped but for which we can't identify the start date. As
22
- a result, we are unable to properly show the WIP for these items.
23
- </p>
27
+ </div>
28
+ <% if @has_completed_but_not_started %>
29
+ <div class="p">
30
+ #{color_block '--wip-chart-completed-but-not-started-color'} "Completed but not started"
31
+ reflects the fact that while we know that it completed that day, we were unable to determine when
32
+ it had started; it had moved directly from a To Do status to a Done status.
33
+ The #{color_block '--body-background'} shading at the top shows when they might
34
+ have been active. Note that the this grouping is approximate as we just don't know for sure.
35
+ </div>
36
+ <% end %>
37
+ #{describe_non_working_days}
24
38
  HTML
25
39
  end
26
40
 
@@ -47,30 +61,31 @@ class DailyWipByBlockedStalledChart < DailyWipChart
47
61
  stopped_today = stopped_date == rules.current_date
48
62
 
49
63
  if stopped_today && started.nil?
64
+ @has_completed_but_not_started = true
50
65
  rules.label = 'Completed but not started'
51
- rules.color = '#66FF66'
66
+ rules.color = '--wip-chart-completed-but-not-started-color'
52
67
  rules.group_priority = -1
53
68
  elsif stopped_today
54
69
  rules.label = 'Completed'
55
- rules.color = '#009900'
70
+ rules.color = '--wip-chart-completed-color'
56
71
  rules.group_priority = -2
57
72
  elsif started.nil?
58
73
  rules.label = 'Start date unknown'
59
- rules.color = 'white'
74
+ rules.color = '--body-background'
60
75
  rules.group_priority = 4
61
76
  elsif change&.blocked?
62
77
  rules.label = 'Blocked'
63
- rules.color = 'red'
78
+ rules.color = '--blocked-color'
64
79
  rules.group_priority = 1
65
80
  rules.issue_hint = "(#{change.reasons})"
66
81
  elsif change&.stalled?
67
82
  rules.label = 'Stalled'
68
- rules.color = 'orange'
83
+ rules.color = '--stalled-color'
69
84
  rules.group_priority = 2
70
85
  rules.issue_hint = "(#{change.reasons})"
71
86
  else
72
87
  rules.label = 'Active'
73
- rules.color = 'lightgray'
88
+ rules.color = '--wip-chart-active-color'
74
89
  rules.group_priority = 3
75
90
  end
76
91
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jirametrics/daily_wip_chart'
4
+
5
+ class DailyWipByParentChart < DailyWipChart
6
+ def initialize block
7
+ super(block)
8
+ end
9
+
10
+ def default_header_text
11
+ 'Daily WIP, grouped by the parent ticket (Epic, Feature, etc)'
12
+ end
13
+
14
+ def default_description_text
15
+ <<-HTML
16
+ <div class="p">
17
+ How much work is in progress, grouped by the parent of the issue. This will give us an
18
+ indication of how focused we are on higher level objectives. If there are many parent
19
+ tickets in progress at the same time, either this team has their focus scattered or we
20
+ aren't doing a good job of
21
+ <a href="https://improvingflow.com/2024/02/21/slicing-epics.html">splitting those parent
22
+ tickets</a>. Neither of those is desirable.
23
+ </div>
24
+ <div class="p">
25
+ The #{color_block '--body-background'} shading at the top shows items that don't have a parent
26
+ at all.
27
+ </div>
28
+ #{describe_non_working_days}
29
+ HTML
30
+ end
31
+
32
+ def default_grouping_rules issue:, rules:
33
+ parent = issue.parent&.key
34
+ if parent
35
+ rules.label = parent
36
+ else
37
+ rules.label = 'No parent'
38
+ rules.group_priority = 1000
39
+ rules.color = '--body-background'
40
+ end
41
+ end
42
+ end