Skip to content

Commit

Permalink
Merge pull request ManageIQ#19873 from jrafanie/move_report_formatter…
Browse files Browse the repository at this point in the history
…_charting_to_core

Move report formatter and charting to core
  • Loading branch information
chessbyte authored Feb 27, 2020
2 parents 3185440 + bf0bfd4 commit c771f4e
Show file tree
Hide file tree
Showing 27 changed files with 2,553 additions and 6 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,5 @@
/content @bdunne
/config @jrafanie
/db @agrare @carboni @bdunne
/lib/manageiq/reporting @panspagetka @jrafanie
/product* @hkataria
2 changes: 1 addition & 1 deletion app/models/miq_report/formatters/graph.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def graph_options(options = nil)
end

def to_chart(theme = nil, show_title = false, graph_options = nil)
ReportFormatter::ReportRenderer.render(Charting.format) do |e|
ManageIQ::Reporting::Formatter::ReportRenderer.render(ManageIQ::Reporting::Charting.format) do |e|
e.options.mri = self
e.options.show_title = show_title
e.options.graph_options = graph_options unless graph_options.nil?
Expand Down
2 changes: 1 addition & 1 deletion app/models/miq_widget/chart_content.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ def generate(user_or_group)
theme ||= "MIQ"

report.to_chart(theme, false, MiqReport.graph_options)
Charting.serialized(report.chart)
ManageIQ::Reporting::Charting.serialized(report.chart)
end
end
6 changes: 6 additions & 0 deletions lib/charting.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# If code uses the old constant name:
# * Rails will autoload it and start here.
# * We assign the old toplevel constant to the new constant.
# * We can't include rails deprecate_constant globally, so we use ruby's.
Charting = ManageIQ::Reporting::Charting
Object.deprecate_constant :Charting
40 changes: 40 additions & 0 deletions lib/manageiq/reporting/charting.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module ManageIQ
module Reporting
class Charting
class << self
extend Forwardable
delegate [
:backend, # charting backend name; FIXME: remove this method
:render_format,
:format, # format for Ruport renderer
:load_helpers,
:data_ok?,
:sample_chart,
:chart_names_for_select,
:chart_themes_for_select,
:serialized,
:deserialized,
:js_load_statement # javascript statement to reload charts
] => :instance
end

# discovery
#
#
def self.instance
@instance ||= new
end

def self.new
self == ManageIQ::Reporting::Charting ? detect_available_plugin.new : super
end

def self.detect_available_plugin
subclasses.select(&:available?).max_by(&:priority)
end
end
end
end

# load all plugins
Dir.glob(File.join(File.dirname(__FILE__), "charting/*.rb")).each { |f| require_dependency f }
96 changes: 96 additions & 0 deletions lib/manageiq/reporting/charting/c3_charting.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
module ManageIQ
module Reporting
class C3Charting < ManageIQ::Reporting::Charting
# for Charting.detect_available_plugin
def self.available?
true
end

# for Charting.detect_available_plugin
def self.priority
1000
end

# backend identifier
def backend
:c3
end

# format for rails' render
def render_format
:json
end

# formatter for Rupport::Controller#render - see lib/report_formatter/...
def format
:c3
end

# called from each ApplicationController instance
def load_helpers(klass)
klass.instance_eval do
helper ManageIQ::Reporting::Formatter::C3Helper
end
end

def data_ok?(data)
obj = YAML.load(data)
!!obj && obj.kind_of?(Hash) && !obj[:options]
rescue Psych::SyntaxError, ArgumentError
false
end

def sample_chart(_options, _report_theme)
sample = {
:data => {
:axis => {},
:tooltip => {},
:columns => [
['data1', 30, 200, 100, 400, 150, 250],
['data2', 50, 20, 10, 40, 15, 25],
['data3', 10, 25, 10, 250, 10, 30]
],
},
:miqChart => _options[:graph_type],
:miq => { :zoomed => false }
}
sample[:data][:groups] = [['data1','data2', 'data3']] if _options[:graph_type].include? 'Stacked'
sample
end

def js_load_statement(delayed = false)
delayed ? 'setTimeout(function(){ load_c3_charts(); }, 100);' : 'load_c3_charts();'
end

# list of available chart types - in options_for_select format
def chart_names_for_select
CHART_NAMES
end

# list of themes - in options_for_select format
def chart_themes_for_select
[%w(Default default)]
end

def serialized(data)
data.try(:to_yaml)
end

def deserialized(data)
YAML.load(data)
end

CHART_NAMES = [
["Bars (2D)", "Bar"],
["Bars, Stacked (2D)", "StackedBar"],
["Columns (2D)", "Column"],
["Columns, Stacked (2D)", "StackedColumn"],
["Donut (2D)", "Donut"],
["Pie (2D)", "Pie"],
["Line (2D)", "Line"],
["Area (2D)", "Area"],
["Area, Stacked (2D)", "StackedArea"],
]
end
end
end
39 changes: 39 additions & 0 deletions lib/manageiq/reporting/formatter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
include ActionView::Helpers::NumberHelper

require_dependency 'manageiq/reporting/formatter/report_renderer'
require_dependency 'manageiq/reporting/formatter/c3'
require_dependency 'manageiq/reporting/formatter/converter'
require_dependency 'manageiq/reporting/formatter/html'
require_dependency 'manageiq/reporting/formatter/text'
require_dependency 'manageiq/reporting/formatter/timeline'

module ManageIQ
module Reporting
module Formatter
BLANK_VALUE = "Unknown" # Chart constant for nil or blank key values
CRLF = "\r\n"
LEGEND_LENGTH = 11 # Top legend text limit
LABEL_LENGTH = 21 # Chart label text limit
end
end
end

# Deprecate the constants within ReportFormatter with a helpful replacement.
module ReportFormatter
include ActiveSupport::Deprecation::DeprecatedConstantAccessor
deprecate_constant 'BLANK_VALUE', 'ManageIQ::Reporting::Formatter::BLANK_VALUE'
deprecate_constant 'CRLF', 'ManageIQ::Reporting::Formatter::CRLF'
deprecate_constant 'LABEL_LENGTH', 'ManageIQ::Reporting::Formatter::LABEL_LENGTH'
deprecate_constant 'LEGEND_LENGTH', 'ManageIQ::Reporting::Formatter::LEGEND_LENGTH'

deprecate_constant 'C3Formatter', 'ManageIQ::Reporting::Formatter::C3'
deprecate_constant 'C3Series', 'ManageIQ::Reporting::Formatter::C3Series'
deprecate_constant 'C3Charting', 'ManageIQ::Reporting::Formatter::C3Charting'
deprecate_constant 'ChartCommon', 'ManageIQ::Reporting::Formatter::ChartCommon'
deprecate_constant 'Converter', 'ManageIQ::Reporting::Formatter::Converter'
deprecate_constant 'ReportHTML', 'ManageIQ::Reporting::Formatter::HTML'
deprecate_constant 'ReportRenderer', 'ManageIQ::Reporting::Formatter::ReportRenderer'
deprecate_constant 'ReportText', 'ManageIQ::Reporting::Formatter::Text'
deprecate_constant 'ReportTimeline', 'ManageIQ::Reporting::Formatter::Timeline'
deprecate_constant 'TimelineMessage', 'ManageIQ::Reporting::Formatter::TimelineMessage'
end
189 changes: 189 additions & 0 deletions lib/manageiq/reporting/formatter/c3.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
require_dependency 'manageiq/reporting/formatter/c3_series'

module ManageIQ
module Reporting
module Formatter
class C3 < Ruport::Formatter
include ActionView::Helpers::UrlHelper
include ChartCommon
include MiqReport::Formatting
renders :c3, :for => ReportRenderer

# series handling methods
def series_class
ManageIQ::Reporting::Formatter::C3Series
end

CONVERT_TYPES = {
"ColumnThreed" => "Column",
"ParallelThreedColumn" => "Column",
"StackedThreedColumn" => "StackedColumn",
"PieThreed" => "Pie",
"AreaThreed" => "Area",
"StackedAreaThreed" => "StackedArea"
}
def add_series(label, data)
@counter ||= 0
@counter += 1
series_id = @counter.to_s
limit = pie_type? ? LEGEND_LENGTH : LABEL_LENGTH

if chart_is_2d?
mri.chart[:data][:columns] << [series_id, *data.map { |a| a[:value] }]
mri.chart[:data][:names][series_id] = slice_legend(_(label), limit)
mri.chart[:miq][:name_table][series_id] = label
else
data.each_with_index do |a, index|
id = index.to_s
mri.chart[:data][:columns].push([id, a[:value]])
mri.chart[:data][:names][id] = slice_legend(_(a[:tooltip]), limit)
mri.chart[:miq][:name_table][id] = a[:tooltip]
end
end

if chart_is_stacked?
mri.chart[:data][:groups][0] << series_id
end
end

def add_axis_category_text(categories)
if chart_is_2d?
category_labels = categories.collect { |c| c.kind_of?(Array) ? c.first : c }
limit = pie_type? ? LEGEND_LENGTH : LABEL_LENGTH
mri.chart[:axis][:x][:categories] = category_labels.collect { |c| slice_legend(c, limit) }
mri.chart[:miq][:category_table] = category_labels
end
end

# report building methods
def build_document_header
super
type = c3_convert_type(mri.graph[:type].to_s)
mri.chart = {
:miqChart => type,
:data => {:columns => [], :names => {}, :empty => {:label => {:text => _('No data available.')}}},
:axis => {:x => {:tick => {}}, :y => {:tick => {}, :padding => {:bottom => 0}}},
:tooltip => {:format => {}},
:miq => {:name_table => {}, :category_table => {}},
:legend => {}
}

if chart_is_2d?
mri.chart[:axis][:x] = {
:categories => [],
:tick => {}
}
end

if chart_is_stacked?
mri.chart[:data][:groups] = [[]]
end

# chart is numeric
if mri.graph[:mode] == 'values'
custom_format = Array(mri[:col_formats])[Array(mri[:col_order]).index(raw_column_name)]
format, options = javascript_format(mri.graph[:column].split(/(?<!:):(?!:)/)[0], custom_format)

if format
axis_formatter = {:function => format, :options => options}
mri.chart[:axis][:y] = {:tick => {:format => axis_formatter}}
end
end

# C&U chart
if graph_options[:chart_type] == :performance
unless mri.graph[:type] == 'Donut' || mri.graph[:type] == 'Pie'
mri.chart[:legend] = {:position => 'bottom'}
end

return if mri.graph[:columns].blank?
column = grouped_by_tag_category? ? mri.graph[:columns][0].split(/_+/)[0..-2].join('_') : mri.graph[:columns][0]
format, options = javascript_format(column, nil)
return unless format

axis_formatter = {:function => format, :options => options}
mri.chart[:axis][:y][:tick] = {:format => axis_formatter}
mri.chart[:miq][:format] = axis_formatter
end
end

def c3_convert_type(type)
CONVERT_TYPES[type] || type
end

def chart_is_2d?
['Bar', 'Column', 'StackedBar', 'StackedColumn', 'Line', 'Area', 'StackedArea'].include?(c3_convert_type(mri.graph[:type]))
end

def chart_is_stacked?
%w(StackedBar StackedColumn StackedArea).include?(mri.graph[:type])
end

# change structure of chart JSON to performance chart with timeseries data
def build_performance_chart_area(maxcols)
super
change_structure_to_timeseries
end

def no_records_found_chart(*)
mri.chart = {
:axis => {:y => {:show => false}},
:data => {:columns => [], :empty => {:label => {:text => _('No data available.')}}},
:miq => {:empty => true},
}
end

def finalize_document
mri.chart
end

private

# change structure of hash from standard chart to timeseries chart
def change_structure_to_timeseries
# add 'x' as first element and move mri.chart[:axis][:x][:categories] to mri.chart[:data][:columns] as first column
x = mri.chart[:axis][:x][:categories]
x.unshift('x')
mri.chart[:data][:columns].unshift(x)
mri.chart[:data][:x] = 'x'
# set x axis type to timeseries and remove categories
mri.chart[:axis][:x] = {:type => 'timeseries', :tick => {}}
# set flag for performance chart
mri.chart[:miq][:performance_chart] = true
# this conditions are taken from build_performance_chart_area method from chart_commons.rb
if mri.db.include?("Daily") || (mri.where_clause && mri.where_clause.include?("daily"))
# set format for parsing
mri.chart[:data][:xFormat] = '%m/%d'
# set format for labels
mri.chart[:axis][:x][:tick][:format] = '%m/%d'
elsif mri.extras[:realtime] == true
mri.chart[:data][:xFormat] = '%H:%M:%S'
mri.chart[:axis][:x][:tick][:format] = '%H:%M:%S'
else
mri.chart[:data][:xFormat] = '%H:%M'
mri.chart[:axis][:x][:tick][:format] = '%H:%M'
end
end

def build_reporting_chart(_maxcols)
mri.chart[:miq][:expand_tooltip] = true
super
end

def build_reporting_chart_numeric(_maxcols)
mri.chart[:miq][:expand_tooltip] = true
super
end

def build_performance_chart_pie(_maxcols)
mri.chart[:miq][:expand_tooltip] = true
super
end

def grouped_by_tag_category?
!!(mri.performance && mri.performance.fetch_path(:group_by_category))
end
end
end
end
end
Loading

0 comments on commit c771f4e

Please sign in to comment.