Module: PredictabilityEngine::ReportGenerator

Defined in:
lib/predictability_engine/report_generator.rb

Overview

Logic for generating and writing reports.

Constant Summary collapse

FORMAT_TO_EXT =
{
  markdown: 'md', md: 'md',
  confluence: 'conf', conf: 'conf',
  landscape: 'html', dashboard: 'html',
  a3_landscape: 'pdf',
  ppt: 'pptx',
  png: 'png',
  raw_csv: 'csv',
  xlsx: 'xlsx'
}.freeze

Class Method Summary collapse

Class Method Details



66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/predictability_engine/report_generator.rb', line 66

def self.build_nav_links(format, reports, current_slot)
  return unless html_format?(format)

  links = [nav_entry(:all, current_slot, label: 'All')]
  Report::FACETS.each do |facet|
    values = (reports[facet[:key]] || {}).keys
    next if values.empty?

    links << { separator: true }
    values.each { |value| links << nav_entry([facet[:key], value], current_slot, label: value) }
  end
  links + export_links_for(current_slot)
end

.clean_report_dir(file) ⇒ Object



141
142
143
144
# File 'lib/predictability_engine/report_generator.rb', line 141

def self.clean_report_dir(file, **)
  dir = report_dir(file, **)
  FileUtils.rm_rf(dir)
end

.dashboard_filename(format, ext) ⇒ Object



146
147
148
149
150
151
152
153
# File 'lib/predictability_engine/report_generator.rb', line 146

def self.dashboard_filename(format, ext)
  case format
  when :html, :landscape, :dashboard then 'dashboard.html'
  when :a3_landscape then 'dashboard_a3.pdf'
  when :png then 'dashboard.png'
  else "dashboard.#{ext}"
  end
end

.each_facet_entry(reports) {|:all, reports[:all]| ... } ⇒ Object

Yields:

  • (:all, reports[:all])


23
24
25
26
27
28
# File 'lib/predictability_engine/report_generator.rb', line 23

def self.each_facet_entry(reports)
  yield :all, reports[:all]
  Report::FACETS.each do |facet|
    (reports[facet[:key]] || {}).each { |value, report| yield [facet[:key], value], report }
  end
end


84
85
86
87
88
# File 'lib/predictability_engine/report_generator.rb', line 84

def self.export_links_for(slot)
  prefix = slot.is_a?(Array) ? '../' : ''
  [{ label: 'CSV', url: "#{prefix}dashboard.csv", download: true },
   { label: 'XLSX', url: "#{prefix}dashboard.xlsx", download: true }]
end

.facet_dirname(facet_key) ⇒ Object



109
110
111
# File 'lib/predictability_engine/report_generator.rb', line 109

def self.facet_dirname(facet_key)
  Report::FACETS.find { |f| f[:key] == facet_key }[:dirname]
end

.facet_total(reports) ⇒ Object



19
20
21
# File 'lib/predictability_engine/report_generator.rb', line 19

def self.facet_total(reports)
  Report::FACETS.sum { |f| (reports[f[:key]] || {}).size }
end

.format_to_ext(format) ⇒ Object



166
167
168
# File 'lib/predictability_engine/report_generator.rb', line 166

def self.format_to_ext(format)
  FORMAT_TO_EXT[format] || format.to_s
end

.generate_images_if_needed(file, format, report) ⇒ Object



57
58
59
60
61
62
63
64
# File 'lib/predictability_engine/report_generator.rb', line 57

def self.generate_images_if_needed(file, format, report, **)
  return unless %i[markdown md confluence conf].include?(format)

  dir = report_dir(file, **)
  report.generate_chart_images(dir)
rescue StandardError => e
  PredictabilityEngine.warn_chart_failure(e, context: 'Inline images will be omitted.')
end

.generate_multi_reports(file, format, reports, **opts) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/predictability_engine/report_generator.rb', line 43

def self.generate_multi_reports(file, format, reports, **opts)
  fmt  = format.to_sym
  msgs = []
  each_facet_entry(reports) do |slot, report|
    generate_images_if_needed(file, fmt, report, **opts)
    links   = build_nav_links(fmt, reports, slot)
    content = report.render(fmt, sub_reports: links, **opts)
    msg     = write_report(file, format, content, opts[:output], slot: slot, **opts)
    PredictabilityEngine.logger.info { msg }
    msgs << msg
  end
  "#{msgs.size} reports generated"
end

.generate_single_report(file, format, report, **opts) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/predictability_engine/report_generator.rb', line 30

def self.generate_single_report(file, format, report, **opts)
  fmt = format.to_sym
  generate_images_if_needed(file, fmt, report, **opts)
  opts = opts.merge(sub_reports: export_links_for(:all)) if html_format?(fmt) && !opts[:sub_reports]

  content = report.render(fmt, **opts)
  if opts[:output] || fmt != :terminal
    write_report(file, format, content, opts[:output], **opts)
  else
    content
  end
end

.html_format?(format) ⇒ Boolean

Returns:

  • (Boolean)


80
81
82
# File 'lib/predictability_engine/report_generator.rb', line 80

def self.html_format?(format)
  %i[html landscape].include?(format)
end

.main_dashboard_url(current_slot) ⇒ Object



105
106
107
# File 'lib/predictability_engine/report_generator.rb', line 105

def self.main_dashboard_url(current_slot)
  current_slot.is_a?(Array) ? '../dashboard.html' : 'dashboard.html'
end

.nav_entry(slot, current_slot, label:) ⇒ Object



90
91
92
# File 'lib/predictability_engine/report_generator.rb', line 90

def self.nav_entry(slot, current_slot, label:)
  { label: label, url: nav_url(slot, current_slot), active: slot == current_slot }
end


94
95
96
97
98
99
100
101
102
103
# File 'lib/predictability_engine/report_generator.rb', line 94

def self.nav_url(slot, current_slot)
  return main_dashboard_url(current_slot) if slot == :all

  target_facet_key, value = slot
  target_dir = facet_dirname(target_facet_key)
  return "#{value}.html" if current_slot.is_a?(Array) && current_slot[0] == target_facet_key
  return "../#{target_dir}/#{value}.html" if current_slot.is_a?(Array)

  "#{target_dir}/#{value}.html"
end

.report_dir(file, **opts) ⇒ Object



131
132
133
134
135
136
137
138
139
# File 'lib/predictability_engine/report_generator.rb', line 131

def self.report_dir(file, **opts)
  base_dir = if opts[:output_dir]
               opts[:output_dir]
             else
               input_dir = File.dirname(file)
               input_dir == '.' ? 'reports' : File.join(input_dir, 'reports')
             end
  Pathname.new(File.join(base_dir, File.basename(file, '.*'))).cleanpath.to_s
end

.run_report(file, format, items: nil, reports: nil) ⇒ Object

rubocop:disable Metrics/ModuleLength



8
9
10
11
12
13
14
15
16
17
# File 'lib/predictability_engine/report_generator.rb', line 8

def self.run_report(file, format, items: nil, reports: nil, **)
  items   ||= PredictabilityEngine.load_items(file, **)
  reports ||= Report.generate_all(items)

  if facet_total(reports).zero? || format.to_sym == :terminal
    generate_single_report(file, format, reports[:all], **)
  else
    generate_multi_reports(file, format, reports, **)
  end
end

.write_report(file, format, content, output, slot: :all) ⇒ Object

rubocop:disable Metrics/ParameterLists



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/predictability_engine/report_generator.rb', line 113

def self.write_report(file, format, content, output, slot: :all, **) # rubocop:disable Metrics/ParameterLists
  ext = format_to_ext(format.to_sym)
  dir = report_dir(file, **)

  if slot == :all
    FileUtils.mkdir_p(dir) unless output || File.exist?(dir)
    output ||= File.join(dir, dashboard_filename(format.to_sym, ext))
  else
    facet_key, value = slot
    dir = File.join(dir, facet_dirname(facet_key))
    FileUtils.mkdir_p(dir)
    output = File.join(dir, "#{value}.#{ext}")
  end

  File.binwrite(output, content)
  "Report generated at #{output}"
end