Class: Exporter
- Inherits:
-
Object
- Object
- Exporter
- 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.
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
-
#file_system ⇒ Object
Returns the value of attribute file_system.
-
#project_configs ⇒ Object
readonly
Returns the value of attribute project_configs.
Class Method Summary collapse
Instance Method Summary collapse
- #aggregated_project(name:, project_names:, settings: {}) ⇒ Object
- #download(name_filter:) ⇒ Object
- #downloading? ⇒ Boolean
- #each_project_config(name_filter:) ⇒ Object
- #export(name_filter:) ⇒ Object
-
#filter_issues(issues, ignore_issues) ⇒ Object
Extracted as a separate method so it can be tested independently, without needing to invoke the full standard_project DSL setup.
- #holiday_dates(*args) ⇒ Object
- #info(key, name_filter:) ⇒ Object
-
#initialize(file_system: FileSystem.new) ⇒ Exporter
constructor
A new instance of Exporter.
- #jira_config(filename = nil) ⇒ Object
- #project(name: nil, &block) ⇒ Object
- #standard_project(name:, file_prefix:, ignore_issues: nil, starting_status: nil, boards: {}, default_board: nil, anonymize: false, settings: {}, status_category_mappings: {}, rolling_date_count: 90, no_earlier_than: nil, ignore_types: %w[Sub-task Subtask Epic],, show_experimental_charts: false, github_repos: nil) ⇒ Object
- #stitch(stitch_file) ⇒ Object
- #target_path(path = nil) ⇒ Object
- #timezone_offset(offset = nil) ⇒ Object
- #xproject(*args) ⇒ Object
Constructor Details
#initialize(file_system: FileSystem.new) ⇒ Exporter
Returns a new instance of Exporter.
24 25 26 27 28 29 30 31 32 |
# File 'lib/jirametrics/exporter.rb', line 24 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_system ⇒ Object
Returns the value of attribute file_system.
7 8 9 |
# File 'lib/jirametrics/exporter.rb', line 7 def file_system @file_system end |
#project_configs ⇒ Object (readonly)
Returns the value of attribute project_configs.
6 7 8 |
# File 'lib/jirametrics/exporter.rb', line 6 def project_configs @project_configs end |
Class Method Details
.configure(&block) ⇒ Object
9 10 11 12 13 14 15 16 17 18 19 20 |
# File 'lib/jirametrics/exporter.rb', line 9 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 |
.instance ⇒ Object
22 |
# File 'lib/jirametrics/exporter.rb', line 22 def self.instance = @@instance |
Instance Method Details
#aggregated_project(name:, project_names:, settings: {}) ⇒ Object
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 |
# File 'lib/jirametrics/examples/aggregated_project.rb', line 11 def aggregated_project name:, project_names:, settings: {} project name: name do file_system.log name file_prefix name self.settings.merge! stringify_keys(settings) aggregate do project_names.each do |project_name| include_issues_from project_name end end 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.each_value do |board| board_lines << "<a href='#{project.get_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 chart = self issue_rules do |issue, rules| chart.default_issue_rules.call(issue, rules) rules.label = rules.label.split('<BR/>').insert(1, "Board: #{issue.board.name}").join('<BR/>') end link_rules do |link, rules| chart.default_link_rules.call(link, rules) # 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
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 |
# File 'lib/jirametrics/exporter.rb', line 41 def download name_filter: @downloading = true github_pr_cache = {} 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 gateway = JiraGateway.new( file_system: file_system, jira_config: project.jira_config, settings: project.settings ) downloader = Downloader.create( download_config: project.download_config, file_system: file_system, jira_gateway: gateway, github_pr_cache: github_pr_cache ) downloader.run end puts "Full output from downloader in #{file_system.logfile_name}" end |
#downloading? ⇒ Boolean
103 104 105 |
# File 'lib/jirametrics/exporter.rb', line 103 def downloading? @downloading end |
#each_project_config(name_filter:) ⇒ Object
97 98 99 100 101 |
# File 'lib/jirametrics/exporter.rb', line 97 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
34 35 36 37 38 39 |
# File 'lib/jirametrics/exporter.rb', line 34 def export name_filter: each_project_config(name_filter: name_filter) do |project| project.evaluate_next_level project.run end end |
#filter_issues(issues, ignore_issues) ⇒ Object
Extracted as a separate method so it can be tested independently, without needing to invoke the full standard_project DSL setup.
104 105 106 107 108 109 110 |
# File 'lib/jirametrics/examples/standard_project.rb', line 104 def filter_issues issues, ignore_issues return unless ignore_issues issues.reject! do |issue| ignore_issues.is_a?(Proc) ? ignore_issues.call(issue) : ignore_issues.include?(issue.key) end end |
#holiday_dates(*args) ⇒ Object
141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/jirametrics/exporter.rb', line 141 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 |
#info(key, name_filter:) ⇒ Object
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/jirametrics/exporter.rb', line 68 def info key, name_filter: selected = [] each_project_config(name_filter: name_filter) do |project| project.evaluate_next_level project.run load_only: true project.issues.each do |issue| selected << [project, issue] if key == issue.key end rescue => e # rubocop:disable Style/RescueStandardError # This happens when we're attempting to load an aggregated project because it hasn't been # properly initialized. Since we don't care about aggregated projects, we just ignore it. raise unless e..start_with? 'This is an aggregated project and issues should have been included' end if selected.empty? file_system.log "No issues found to match #{key.inspect}" else selected.each do |project, issue| file_system.log "\nProject #{project.name}", also_write_to_stderr: true file_system.log issue.dump, also_write_to_stderr: true end end end |
#jira_config(filename = nil) ⇒ Object
126 127 128 129 130 131 132 133 134 |
# File 'lib/jirametrics/exporter.rb', line 126 def jira_config filename = nil if filename @jira_config = file_system.load_json(filename, fail_on_error: false) raise "Unable to load Jira configuration file and cannot continue: #{filename.inspect}" if @jira_config.nil? @jira_config['url'] = $1 if @jira_config['url'] =~ /^(.+)\/+$/ end @jira_config end |
#project(name: nil, &block) ⇒ Object
107 108 109 110 111 112 113 |
# File 'lib/jirametrics/exporter.rb', line 107 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: {}, rolling_date_count: 90, no_earlier_than: nil, ignore_types: %w[Sub-task Subtask Epic],, show_experimental_charts: false, github_repos: nil) ⇒ Object
6 7 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 |
# File 'lib/jirametrics/examples/standard_project.rb', line 6 def standard_project name:, file_prefix:, ignore_issues: nil, starting_status: nil, boards: {}, default_board: nil, anonymize: false, settings: {}, status_category_mappings: {}, rolling_date_count: 90, no_earlier_than: nil, ignore_types: %w[Sub-task Subtask Epic], show_experimental_charts: false, github_repos: nil exporter = self project name: name do file_system.log name, also_write_to_stderr: true file_prefix file_prefix self.anonymize if anonymize self.settings.merge! stringify_keys(settings) boards.each_key do |board_id| block = boards[board_id] if block == :default block = lambda do |_| start_at first_time_in_status_category(:indeterminate) stop_at still_in_status_category(:done) end end board id: board_id do cycletime(&block) end end status_category_mappings.each do |status, category| status_category_mapping status: status, category: category end download do self.rolling_date_count(rolling_date_count) if rolling_date_count self.no_earlier_than(no_earlier_than) if no_earlier_than github_repo *github_repos if github_repos end issues.reject! do |issue| ignore_types.include? issue.type end exporter.filter_issues issues, ignore_issues discard_changes_before status_becomes: (starting_status || :backlog) # rubocop:disable Style/RedundantParentheses file do file_suffix '.html' 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> (#{board.board_type})</div>", type: :header end daily_view cumulative_flow_diagram cycletime_scatterplot do show_trend_lines end cycletime_histogram throughput_chart do description_text <<~TEXT <div>Throughput data is very useful for#{' '} <a href="https://blog.mikebowler.ca/2024/06/02/probabilistic-forecasting/">probabilistic forecasting</a>, to determine when we'll be done. Try it now with the <a href="<%= throughput_forecaster_url %>" target="_blank" rel="noopener noreferrer"> Focused Objective throughput forecaster,</a> to see how long it would take to complete all of the <%= @not_started_count %> items you currently have in your backlog. </div> <h2>Number of items completed, grouped by issue type</h2>' TEXT end throughput_by_completed_resolution_chart do description_text '<h2>Number of items completed, grouped by completion status and resolution</h2>' end aging_work_in_progress_chart wip_by_column_chart do show_recommendations end aging_work_table daily_wip_by_age_chart daily_wip_by_blocked_stalled_chart daily_wip_by_parent_chart flow_efficiency_scatterplot if show_experimental_charts sprint_burndown estimate_accuracy_chart dependency_chart end end end end |
#stitch(stitch_file) ⇒ Object
93 94 95 |
# File 'lib/jirametrics/exporter.rb', line 93 def stitch stitch_file Stitcher.new(file_system: file_system).run(stitch_file: stitch_file) end |
#target_path(path = nil) ⇒ Object
117 118 119 120 121 122 123 124 |
# File 'lib/jirametrics/exporter.rb', line 117 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
136 137 138 139 |
# File 'lib/jirametrics/exporter.rb', line 136 def timezone_offset offset = nil @timezone_offset = offset unless offset.nil? @timezone_offset end |
#xproject(*args) ⇒ Object
115 |
# File 'lib/jirametrics/exporter.rb', line 115 def xproject *args; end |