Class: AgingWorkInProgressChart

Inherits:
ChartBase show all
Includes:
GroupableIssueChart
Defined in:
lib/jirametrics/aging_work_in_progress_chart.rb

Instance Attribute Summary collapse

Attributes inherited from ChartBase

#aggregated_project, #all_boards, #canvas_height, #canvas_width, #data_quality, #date_range, #file_system, #holiday_dates, #issues, #settings, #time_range, #timezone_offset

Instance Method Summary collapse

Methods included from GroupableIssueChart

#group_issues, #grouping_rules, #init_configuration_block

Methods inherited from ChartBase

#aggregated_project?, #canvas, #canvas_responsive?, #chart_format, #collapsible_issues_panel, #color_block, #color_for, #completed_issues_in_range, #current_board, #daily_chart_dataset, #describe_non_working_days, #description_text, #format_integer, #format_status, #header_text, #holidays, #icon_span, #label_days, #label_issues, #link_to_issue, #next_id, #random_color, #render, #render_top_text, #status_category_color, #wrap_and_render

Constructor Details

#initialize(block) ⇒ AgingWorkInProgressChart

Returns a new instance of AgingWorkInProgressChart.



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/jirametrics/aging_work_in_progress_chart.rb', line 11

def initialize block
  super()
  header_text 'Aging Work in Progress'
  description_text <<-HTML
    <p>
      This chart shows only work items that have started but not completed, grouped by the column
      they're currently in. Hovering over a dot will show you the ID of that work item.
    </p>
    <div>
      The #{color_block '--non-working-days-color'} shaded area indicates the 85%
      mark for work items that have passed through here; 85% of
      previous work items left this column while still inside the shaded area. Any work items above
      the shading are outliers and they are the items that you should pay special attention to.
    </div>
  HTML
  init_configuration_block(block) do
    grouping_rules do |issue, rule|
      rule.label = issue.type
      rule.color = color_for type: issue.type
    end
  end
end

Instance Attribute Details

#board_columnsObject (readonly)

Returns the value of attribute board_columns.



9
10
11
# File 'lib/jirametrics/aging_work_in_progress_chart.rb', line 9

def board_columns
  @board_columns
end

#board_idObject

Returns the value of attribute board_id.



8
9
10
# File 'lib/jirametrics/aging_work_in_progress_chart.rb', line 8

def board_id
  @board_id
end

#possible_statusesObject

Returns the value of attribute possible_statuses.



8
9
10
# File 'lib/jirametrics/aging_work_in_progress_chart.rb', line 8

def possible_statuses
  @possible_statuses
end

Instance Method Details

#accumulated_status_ids_per_columnObject



104
105
106
107
108
109
110
111
112
# File 'lib/jirametrics/aging_work_in_progress_chart.rb', line 104

def accumulated_status_ids_per_column
  accumulated_status_ids = []
  @board_columns.reverse.filter_map do |column|
    next if column == @fake_column

    accumulated_status_ids += column.status_ids
    [column.name, accumulated_status_ids.dup]
  end.reverse
end

#adjust_visibility_of_unmapped_status_column(data_sets:, column_headings:) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/jirametrics/aging_work_in_progress_chart.rb', line 133

def adjust_visibility_of_unmapped_status_column data_sets:, column_headings:
  column_name = @fake_column.name

  has_unmapped = data_sets.any? do |set|
    set['data'].any? do |data|
      data['x'] == column_name if data.is_a? Hash
    end
  end

  if has_unmapped
    @description_text += "<p>The items shown in #{column_name.inspect} are not visible on the " \
      'board but are still active. Most likely everyone has forgotten about them.</p>'
  else
    column_headings.pop
    @board_columns.pop
  end
end

#ages_of_issues_that_crossed_column_boundary(issues:, status_ids:) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/jirametrics/aging_work_in_progress_chart.rb', line 114

def ages_of_issues_that_crossed_column_boundary issues:, status_ids:
  issues.filter_map do |issue|
    stop = issue.first_time_in_status(*status_ids)
    start = issue.board.cycletime.started_time(issue)

    # Skip if either it hasn't crossed the boundary or we can't tell when it started.
    next if stop.nil? || start.nil?
    next if stop < start

    (stop.to_date - start.to_date).to_i + 1
  end
end

#column_for(issue:) ⇒ Object



127
128
129
130
131
# File 'lib/jirametrics/aging_work_in_progress_chart.rb', line 127

def column_for issue:
  @board_columns.find do |board_column|
    board_column.status_ids.include? issue.status.id
  end
end

#days_at_percentage_threshold_for_all_columns(percentage:, issues:) ⇒ Object



96
97
98
99
100
101
102
# File 'lib/jirametrics/aging_work_in_progress_chart.rb', line 96

def days_at_percentage_threshold_for_all_columns percentage:, issues:
  accumulated_status_ids_per_column.collect do |_column, status_ids|
    ages = ages_of_issues_that_crossed_column_boundary issues: issues, status_ids: status_ids
    index = ages.size * percentage / 100
    ages.sort[index.to_i] || 0
  end
end

#determine_board_columnsObject



46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/jirametrics/aging_work_in_progress_chart.rb', line 46

def determine_board_columns
  unmapped_statuses = current_board.possible_statuses.collect(&:id)

  columns = current_board.visible_columns
  columns.each { |c| unmapped_statuses -= c.status_ids }

  @fake_column = BoardColumn.new({
    'name' => '[Unmapped Statuses]',
    'statuses' => unmapped_statuses.collect { |id| { 'id' => id.to_s } }.uniq
  })
  @board_columns = columns + [@fake_column]
end

#make_data_setsObject



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/jirametrics/aging_work_in_progress_chart.rb', line 59

def make_data_sets
  aging_issues = @issues.select do |issue|
    board = issue.board
    board.id == @board_id && board.cycletime.in_progress?(issue)
  end

  percentage = 85
  rules_to_issues = group_issues aging_issues
  data_sets = rules_to_issues.keys.collect do |rules|
    {
      'type' => 'line',
      'label' => rules.label,
      'data' => rules_to_issues[rules].filter_map do |issue|
          age = issue.board.cycletime.age(issue, today: date_range.end)
          column = column_for issue: issue
          next if column.nil?

          { 'y' => age,
            'x' => column.name,
            'title' => ["#{issue.key} : #{issue.summary} (#{label_days age})"]
          }
        end,
      'fill' => false,
      'showLine' => false,
      'backgroundColor' => rules.color
    }
  end
  data_sets << {
    'type' => 'bar',
    'label' => "#{percentage}%",
    'barPercentage' => 1.0,
    'categoryPercentage' => 1.0,
    'backgroundColor' => CssVariable['--aging-work-in-progress-chart-shading-color'],
    'data' => days_at_percentage_threshold_for_all_columns(percentage: percentage, issues: @issues).drop(1)
  }
end

#runObject



34
35
36
37
38
39
40
41
42
43
44
# File 'lib/jirametrics/aging_work_in_progress_chart.rb', line 34

def run
  determine_board_columns

  @header_text += " on board: #{@all_boards[@board_id].name}"
  data_sets = make_data_sets
  column_headings = @board_columns.collect(&:name)

  adjust_visibility_of_unmapped_status_column data_sets: data_sets, column_headings: column_headings

  wrap_and_render(binding, __FILE__)
end