Class: WipByColumnChart

Inherits:
ChartBase show all
Defined in:
lib/jirametrics/wip_by_column_chart.rb

Defined Under Namespace

Classes: ColumnStats

Constant Summary

Constants inherited from ChartBase

ChartBase::LABEL_POSITIONS

Instance Attribute Summary collapse

Attributes inherited from ChartBase

#aggregated_project, #all_boards, #atlassian_document_format, #canvas_height, #canvas_width, #data_quality, #date_range, #file_system, #fix_versions, #holiday_dates, #issues, #settings, #time_range, #timezone_offset, #x_axis_title, #y_axis_title

Instance Method Summary collapse

Methods inherited from ChartBase

#aggregated_project?, #before_run, #call_before_run, #canvas, #canvas_responsive?, #chart_format, #collapsible_issues_panel, #color_block, #color_for, #completed_issues_in_range, #current_board, #cycletime, #cycletime_for_issue, #daily_chart_dataset, #date_annotation, #describe_non_working_days, #description_text, #format_integer, #format_status, #header_text, #holidays, #html_directory, #icon_span, #label_days, #label_hours, #label_issues, #label_minutes, #link_to_issue, #next_id, #normalize_annotation_datetime, #not_visible_text, #random_color, #render, #render_axis_title, #render_top_text, #seam_end, #seam_start, #stagger_label_positions, #status_category_color, #to_human_readable, #working_days_annotation, #wrap_and_render

Constructor Details

#initialize(block) ⇒ WipByColumnChart

Returns a new instance of WipByColumnChart.



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/jirametrics/wip_by_column_chart.rb', line 10

def initialize block
  super()
  header_text 'WIP by column'
  description_text <<-HTML
    <p>
      This chart shows how much time each board column has spent at different WIP (Work in Progress) levels.
    </p>
    <p>
      Each row on the Y axis is a WIP level (the number of items in that column at the same time).
      Each column on the X axis is a board column.
      The horizontal bars show what percentage of the total time that column spent at that WIP level —
      a wider bar means more time was spent there.
    </p>
    <p>
      A column whose widest bar is at WIP&nbsp;1 was almost always working on one item at a time, often called
      single-piece-flow. This team is likely collaborating very well and might have been
      <a href="https://blog.mikebowler.ca/2021/06/19/pair-programming/">pairing</a> or
      <a href="https://blog.mikebowler.ca/2023/04/22/ensemble-programming/">mobbing/ensembling</a>
      and these teams tend to be very effective.
    </p>
    <p>
      A column with wide bars at high WIP levels usually indicates a team that is highly siloed. Where each person
      is working by themselves.
    </p>
    <p>
      The dashed lines show the minimum and maximum WIP limits configured on the board.
      If the widest bar sits well above the maximum limit, the limit may be set too low or not being respected.
      If the widest bar sits below the minimum limit, consider whether that limit is still meaningful.
    </p>
    <p>
      Hover over any bar to see the exact percentage.
    </p>
    <% if @all_boards[@board_id].team_managed_kanban? %>
      <p>
        If the data looks a bit off then that's probably because you're using a Team Managed project in "kanban mode".
        For this specific case, we are unable to tell if an item is actually visible on the board and so we may
        be reporting more items started than you actually see on the board. See
        <a href="https://jirametrics.org/faq/#team-managed-kanban-backlog">the FAQ</a>.
      </p>
    <% end %>
  HTML

  instance_eval(&block)
end

Instance Attribute Details

#board_idObject

Returns the value of attribute board_id.



6
7
8
# File 'lib/jirametrics/wip_by_column_chart.rb', line 6

def board_id
  @board_id
end

#possible_statusesObject

Returns the value of attribute possible_statuses.



6
7
8
# File 'lib/jirametrics/wip_by_column_chart.rb', line 6

def possible_statuses
  @possible_statuses
end

Instance Method Details

#column_statsObject



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/jirametrics/wip_by_column_chart.rb', line 79

def column_stats
  board = current_board
  columns = board.visible_columns
  status_to_column = build_status_to_column_map(columns)
  relevant_issues = @issues.select { |issue| issue.board.id == @board_id }

  current_column = initial_column_state(relevant_issues, status_to_column)
  events = events_within_range(relevant_issues, status_to_column)
  column_wip_seconds = compute_wip_seconds(columns, current_column, events)

  columns.collect.with_index do |column, index|
    ColumnStats.new(
      name: column.name,
      min_wip_limit: column.min,
      max_wip_limit: column.max,
      wip_history: column_wip_seconds[index].sort.to_a
    )
  end
end

#runObject



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/jirametrics/wip_by_column_chart.rb', line 59

def run
  @header_text += " on board: #{current_board.name}"
  stats = column_stats
  @column_names = stats.collect(&:name)
  @wip_data = stats.collect do |stat|
    total = stat.wip_history.sum { |_wip, seconds| seconds }.to_f
    next [] if total.zero?

    stat.wip_history.collect { |wip, seconds| { 'wip' => wip, 'pct' => format_pct(seconds, total) } }
  end
  @max_wip = stats.flat_map { |s| s.wip_history.collect { |wip, _| wip } }.max || 0
  @wip_limits = stats.collect { |s| { 'min' => s.min_wip_limit, 'max' => s.max_wip_limit } }
  @recommendations = @show_recommendations ? compute_recommendations(stats) : Array.new(stats.size)

  trim_zero_end_columns
  @recommendation_texts = @show_recommendations ? build_recommendation_texts : []

  wrap_and_render(binding, __FILE__)
end

#show_recommendationsObject



55
56
57
# File 'lib/jirametrics/wip_by_column_chart.rb', line 55

def show_recommendations
  @show_recommendations = true
end