Class: Exporter

Inherits:
Object show all
Defined in:
lib/jirametrics/examples/standard_project.rb,
lib/jirametrics/exporter.rb,
lib/jirametrics/examples/aggregated_project.rb

Overview

This file is really intended to give you ideas about how you might configure your own reports, not as a complete setup that will work in every case.

See github.com/mikebowler/jirametrics/wiki/Examples-folder for more details

The point of an AGGREGATED report is that we’re now looking at a higher level. We might use this in a S2 meeting (Scrum of Scrums) to talk about the things that are happening across teams, not within a single team. For that reason, we look at slightly different things that we would on a single team board.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file_system: FileSystem.new) ⇒ Exporter

Returns a new instance of Exporter.



33
34
35
36
37
38
39
40
41
# File 'lib/jirametrics/exporter.rb', line 33

def initialize file_system: FileSystem.new
  @project_configs = []
  @target_path = '.'
  @holiday_dates = []
  @downloading = false
  @file_system = file_system

  timezone_offset '+00:00'
end

Instance Attribute Details

#file_systemObject (readonly)

Returns the value of attribute file_system.



16
17
18
# File 'lib/jirametrics/exporter.rb', line 16

def file_system
  @file_system
end

#project_configsObject (readonly)

Returns the value of attribute project_configs.



16
17
18
# File 'lib/jirametrics/exporter.rb', line 16

def project_configs
  @project_configs
end

Class Method Details

.configure(&block) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/jirametrics/exporter.rb', line 18

def self.configure &block
  logfile_name = 'jirametrics.log'
  logfile = File.open logfile_name, 'w'
  file_system = FileSystem.new
  file_system.logfile = logfile
  file_system.logfile_name = logfile_name

  exporter = Exporter.new file_system: file_system

  exporter.instance_eval(&block)
  @@instance = exporter
end

.instanceObject



31
# File 'lib/jirametrics/exporter.rb', line 31

def self.instance = @@instance

Instance Method Details

#aggregated_project(name:, project_names:, settings: {}) ⇒ Object



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
54
55
56
57
58
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/examples/aggregated_project.rb', line 13

def aggregated_project name:, project_names:, settings: {}
  project name: name do
    puts name
    self.settings.merge! settings

    aggregate do
      project_names.each do |project_name|
        include_issues_from project_name
      end
    end

    file_prefix name

    file do
      file_suffix '.html'
      issues.reject! do |issue|
        %w[Sub-task Epic].include? issue.type
      end

      html_report do
        html '<h1>Boards included in this report</h1><ul>', type: :header
        board_lines = []
        included_projects.each do |project|
          project.all_boards.values.each do |board|
            board_lines << "<a href='#{project.file_prefix}.html'>#{board.name}</a> from project #{project.name}"
          end
        end
        board_lines.sort.each { |line| html "<li>#{line}</li>", type: :header }
        html '</ul>', type: :header

        cycletime_scatterplot do
          show_trend_lines
          # For an aggregated report we group by board rather than by type
          grouping_rules do |issue, rules|
            rules.label = issue.board.name
          end
        end
        # aging_work_in_progress_chart
        daily_wip_by_parent_chart do
          # When aggregating, the chart tends to need more vertical space
          canvas height: 400, width: 800
        end
        aging_work_table do
          # In an aggregated report, we likely only care about items that are old so exclude anything
          # under 21 days.
          age_cutoff 21
        end

        dependency_chart do
          header_text 'Dependencies across boards'
          description_text 'We are only showing dependencies across boards.'

          # By default, the issue doesn't show what board it's on and this is important for an
          # aggregated view
          issue_rules do |issue, rules|
            key = issue.key
            key = "<S>#{key} </S> " if issue.status.category_name == 'Done'
            rules.label = "<#{key} [#{issue.type}]<BR/>#{issue.board.name}<BR/>#{word_wrap issue.summary}>"
          end

          link_rules do |link, rules|
            # By default, the dependency chart shows everything. Clean it up a bit.
            case link.name
            when 'Cloners'
              # We don't want to see any clone links at all.
              rules.ignore
            when 'Blocks'
              # For blocks, by default Jira will have links going both
              # ways and we want them only going one way. Also make the
              # link red.
              rules.merge_bidirectional keep: 'outward'
              rules.line_color = 'red'
            end

            # Because this is the aggregated view, let's hide any link that doesn't cross boards.
            rules.ignore if link.origin.board == link.other_issue.board
          end
        end
      end
    end
  end
end

#download(name_filter:) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/jirametrics/exporter.rb', line 50

def download name_filter:
  @downloading = true
  each_project_config(name_filter: name_filter) do |project|
    project.evaluate_next_level
    next if project.aggregated_project?

    unless project.download_config
      raise "Project #{project.name.inspect} is missing a download section in the config. " \
        'That is required in order to download'
    end

    project.download_config.run
    downloader = Downloader.new(
      download_config: project.download_config,
      file_system: file_system,
      jira_gateway: JiraGateway.new(file_system: file_system)
    )
    downloader.run
  end
  puts "Full output from downloader in #{file_system.logfile_name}"
end

#downloading?Boolean

Returns:

  • (Boolean)


78
79
80
# File 'lib/jirametrics/exporter.rb', line 78

def downloading?
  @downloading
end

#each_project_config(name_filter:) ⇒ Object



72
73
74
75
76
# File 'lib/jirametrics/exporter.rb', line 72

def each_project_config name_filter:
  @project_configs.each do |project|
    yield project if project.name.nil? || File.fnmatch(name_filter, project.name)
  end
end

#export(name_filter:) ⇒ Object



43
44
45
46
47
48
# File 'lib/jirametrics/exporter.rb', line 43

def export name_filter:
  each_project_config(name_filter: name_filter) do |project|
    project.evaluate_next_level
    project.run
  end
end

#holiday_dates(*args) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/jirametrics/exporter.rb', line 111

def holiday_dates *args
  unless args.empty?
    dates = []
    args.each do |arg|
      if arg =~ /^(\d{4}-\d{2}-\d{2})\.\.(\d{4}-\d{2}-\d{2})$/
        Date.parse($1).upto(Date.parse($2)).each { |date| dates << date }
      else
        dates << Date.parse(arg)
      end
    end
    @holiday_dates = dates
  end
  @holiday_dates
end

#jira_config(filename = nil) ⇒ Object



101
102
103
104
# File 'lib/jirametrics/exporter.rb', line 101

def jira_config filename = nil
  @jira_config = file_system.load_json(filename) unless filename.nil?
  @jira_config
end

#project(name: nil, &block) ⇒ Object



82
83
84
85
86
87
88
# File 'lib/jirametrics/exporter.rb', line 82

def project name: nil, &block
  raise 'jira_config not set' if @jira_config.nil?

  @project_configs << ProjectConfig.new(
    exporter: self, target_path: @target_path, jira_config: @jira_config, block: block, name: name
  )
end

#standard_project(name:, file_prefix:, ignore_issues: nil, starting_status: nil, boards: {}, default_board: nil, anonymize: false, settings: {}, status_category_mappings: {}) ⇒ Object



8
9
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
54
55
56
57
58
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/jirametrics/examples/standard_project.rb', line 8

def standard_project name:, file_prefix:, ignore_issues: nil, starting_status: nil, boards: {},
    default_board: nil, anonymize: false, settings: {}, status_category_mappings: {}

  project name: name do
    puts name
    self.anonymize if anonymize

    settings['blocked_link_text'] = ['is blocked by']
    self.settings.merge! settings

    status_category_mappings.each do |status, category|
      status_category_mapping status: status, category: category
    end

    file_prefix file_prefix
    download do
      rolling_date_count 90
    end

    boards.each_key do |board_id|
      block = boards[board_id]
      if block == :default
        block = lambda do |_|
          start_at first_time_in_status_category('In Progress')
          stop_at still_in_status_category('Done')
        end
      end
      board id: board_id do
        cycletime(&block)
        expedited_priority_names 'Critical', 'Highest', 'Immediate Gating'
      end
    end

    file do
      file_suffix '.html'
      issues.reject! do |issue|
        %w[Sub-task Epic].include? issue.type
      end

      issues.reject! { |issue| ignore_issues.include? issue.key } if ignore_issues

      html_report do
        board_id default_board if default_board

        html "<H1>#{name}</H1>", type: :header
        boards.each_key do |id|
          board = find_board id
          html "<div><a href='#{board.url}'>#{id} #{board.name}</a></div>",
               type: :header
        end

        discard_changes_before status_becomes: (starting_status || :backlog) # rubocop:disable Style/RedundantParentheses

        cycletime_scatterplot do
          show_trend_lines
        end
        cycletime_histogram

        throughput_chart do
          description_text '<h2>Number of items completed, grouped by issue type</h2>'
        end
        throughput_chart do
          header_text nil
          description_text '<h2>Number of items completed, grouped by completion status and resolution</h2>'
          grouping_rules do |issue, rules|
            if issue.resolution
              rules.label = "#{issue.status.name}:#{issue.resolution}"
            else
              rules.label = issue.status.name
            end
          end
        end

        aging_work_in_progress_chart
        aging_work_bar_chart
        aging_work_table
        daily_wip_by_age_chart
        daily_wip_by_blocked_stalled_chart
        daily_wip_by_parent_chart
        expedited_chart
        sprint_burndown
        estimate_accuracy_chart

        dependency_chart do
          link_rules do |link, rules|
            case link.name
            when 'Cloners'
              rules.ignore
            when 'Dependency', 'Blocks', 'Parent/Child', 'Cause', 'Satisfy Requirement', 'Relates'
              rules.merge_bidirectional keep: 'outward'
              rules.merge_bidirectional keep: 'outward'
            when 'Sync'
              rules.use_bidirectional_arrows
            else
              # This is a link type that we don't recognize. Dump it to standard out to draw attention
              # to it.
              puts "name=#{link.name}, label=#{link.label}"
            end
          end
        end
      end
    end
  end
end

#target_path(path = nil) ⇒ Object



92
93
94
95
96
97
98
99
# File 'lib/jirametrics/exporter.rb', line 92

def target_path path = nil
  unless path.nil?
    @target_path = path
    @target_path += File::SEPARATOR unless @target_path.end_with? File::SEPARATOR
    FileUtils.mkdir_p @target_path
  end
  @target_path
end

#timezone_offset(offset = nil) ⇒ Object



106
107
108
109
# File 'lib/jirametrics/exporter.rb', line 106

def timezone_offset offset = nil
  @timezone_offset = offset unless offset.nil?
  @timezone_offset
end

#xproject(*args) ⇒ Object



90
# File 'lib/jirametrics/exporter.rb', line 90

def xproject *args; end