ruby-grafana-reporter 0.3.0 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +337 -170
  3. data/bin/ruby-grafana-reporter +5 -5
  4. data/lib/VERSION.rb +3 -2
  5. data/lib/grafana/abstract_datasource.rb +149 -0
  6. data/lib/grafana/dashboard.rb +1 -3
  7. data/lib/grafana/errors.rb +20 -5
  8. data/lib/grafana/grafana.rb +52 -57
  9. data/lib/grafana/grafana_alerts_datasource.rb +57 -0
  10. data/lib/grafana/grafana_annotations_datasource.rb +56 -0
  11. data/lib/grafana/grafana_property_datasource.rb +37 -0
  12. data/lib/grafana/graphite_datasource.rb +72 -0
  13. data/lib/grafana/image_rendering_datasource.rb +44 -0
  14. data/lib/grafana/influxdb_datasource.rb +70 -0
  15. data/lib/grafana/panel.rb +10 -4
  16. data/lib/grafana/prometheus_datasource.rb +67 -0
  17. data/lib/grafana/sql_datasource.rb +70 -0
  18. data/lib/grafana/unsupported_datasource.rb +7 -0
  19. data/lib/grafana/variable.rb +27 -21
  20. data/lib/grafana/webrequest.rb +71 -0
  21. data/lib/grafana_reporter/abstract_query.rb +478 -0
  22. data/lib/grafana_reporter/abstract_report.rb +152 -18
  23. data/lib/grafana_reporter/abstract_table_format_strategy.rb +34 -0
  24. data/lib/grafana_reporter/alerts_table_query.rb +43 -0
  25. data/lib/grafana_reporter/annotations_table_query.rb +42 -0
  26. data/lib/grafana_reporter/application/application.rb +28 -25
  27. data/lib/grafana_reporter/application/webservice.rb +80 -39
  28. data/lib/grafana_reporter/asciidoctor/adoc_plain_table_format_strategy.rb +25 -0
  29. data/lib/grafana_reporter/asciidoctor/alerts_table_include_processor.rb +92 -0
  30. data/lib/grafana_reporter/asciidoctor/annotations_table_include_processor.rb +91 -0
  31. data/lib/grafana_reporter/asciidoctor/help.rb +336 -313
  32. data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +78 -0
  33. data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +80 -0
  34. data/lib/grafana_reporter/asciidoctor/panel_property_inline_macro.rb +74 -0
  35. data/lib/grafana_reporter/asciidoctor/panel_query_table_include_processor.rb +99 -0
  36. data/lib/grafana_reporter/asciidoctor/panel_query_value_inline_macro.rb +93 -0
  37. data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +50 -0
  38. data/lib/grafana_reporter/asciidoctor/report.rb +41 -82
  39. data/lib/grafana_reporter/asciidoctor/show_environment_include_processor.rb +46 -0
  40. data/lib/grafana_reporter/asciidoctor/show_help_include_processor.rb +35 -0
  41. data/lib/grafana_reporter/asciidoctor/sql_table_include_processor.rb +94 -0
  42. data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +90 -0
  43. data/lib/grafana_reporter/asciidoctor/value_as_variable_include_processor.rb +90 -0
  44. data/lib/grafana_reporter/configuration.rb +26 -8
  45. data/lib/grafana_reporter/console_configuration_wizard.rb +109 -67
  46. data/lib/grafana_reporter/csv_table_format_strategy.rb +23 -0
  47. data/lib/grafana_reporter/demo_report_wizard.rb +104 -0
  48. data/lib/grafana_reporter/erb/demo_report_builder.rb +46 -0
  49. data/lib/grafana_reporter/erb/report.rb +36 -0
  50. data/lib/grafana_reporter/erb/report_jail.rb +21 -0
  51. data/lib/grafana_reporter/errors.rb +57 -0
  52. data/lib/grafana_reporter/logger/{two_way_logger.rb → two_way_delegate_logger.rb} +1 -1
  53. data/lib/grafana_reporter/panel_image_query.rb +25 -0
  54. data/lib/grafana_reporter/panel_property_query.rb +22 -0
  55. data/lib/grafana_reporter/query_value_query.rb +61 -0
  56. data/lib/grafana_reporter/report_webhook.rb +39 -0
  57. data/lib/ruby_grafana_extension.rb +8 -0
  58. data/lib/{ruby-grafana-reporter.rb → ruby_grafana_reporter.rb} +1 -3
  59. metadata +49 -38
  60. data/lib/grafana/abstract_panel_query.rb +0 -22
  61. data/lib/grafana/abstract_query.rb +0 -132
  62. data/lib/grafana/abstract_sql_query.rb +0 -51
  63. data/lib/grafana/panel_image_query.rb +0 -52
  64. data/lib/grafana_reporter/asciidoctor/alerts_table_query.rb +0 -101
  65. data/lib/grafana_reporter/asciidoctor/annotations_table_query.rb +0 -96
  66. data/lib/grafana_reporter/asciidoctor/errors.rb +0 -40
  67. data/lib/grafana_reporter/asciidoctor/extensions/alerts_table_include_processor.rb +0 -92
  68. data/lib/grafana_reporter/asciidoctor/extensions/annotations_table_include_processor.rb +0 -91
  69. data/lib/grafana_reporter/asciidoctor/extensions/panel_image_block_macro.rb +0 -69
  70. data/lib/grafana_reporter/asciidoctor/extensions/panel_image_inline_macro.rb +0 -68
  71. data/lib/grafana_reporter/asciidoctor/extensions/panel_property_inline_macro.rb +0 -61
  72. data/lib/grafana_reporter/asciidoctor/extensions/panel_query_table_include_processor.rb +0 -78
  73. data/lib/grafana_reporter/asciidoctor/extensions/panel_query_value_inline_macro.rb +0 -73
  74. data/lib/grafana_reporter/asciidoctor/extensions/processor_mixin.rb +0 -20
  75. data/lib/grafana_reporter/asciidoctor/extensions/show_environment_include_processor.rb +0 -43
  76. data/lib/grafana_reporter/asciidoctor/extensions/show_help_include_processor.rb +0 -30
  77. data/lib/grafana_reporter/asciidoctor/extensions/sql_table_include_processor.rb +0 -70
  78. data/lib/grafana_reporter/asciidoctor/extensions/sql_value_inline_macro.rb +0 -66
  79. data/lib/grafana_reporter/asciidoctor/extensions/value_as_variable_include_processor.rb +0 -88
  80. data/lib/grafana_reporter/asciidoctor/panel_first_value_query.rb +0 -36
  81. data/lib/grafana_reporter/asciidoctor/panel_image_query.rb +0 -28
  82. data/lib/grafana_reporter/asciidoctor/panel_property_query.rb +0 -44
  83. data/lib/grafana_reporter/asciidoctor/panel_table_query.rb +0 -40
  84. data/lib/grafana_reporter/asciidoctor/query_mixin.rb +0 -312
  85. data/lib/grafana_reporter/asciidoctor/sql_first_value_query.rb +0 -42
  86. data/lib/grafana_reporter/asciidoctor/sql_table_query.rb +0 -44
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrafanaReporter
4
+ # This class is used to build a demo report based on a real grafana instance. Therefore
5
+ # it checks available grafana dashboards and panels and returns a final template file as
6
+ # string, which can then be used as a template.
7
+ class DemoReportWizard
8
+ # @param query_classes [Array] class objects, for which a demo report shall be created
9
+ def initialize(query_classes)
10
+ @query_classes = query_classes
11
+ end
12
+
13
+ # Invokes the build process for the given +grafana+ object. Progress is printed to
14
+ # STDOUT.
15
+ # @param grafana [Grafana] grafana instance, for which the demo report shall be built
16
+ # @return [String] demo template as string
17
+ def build(grafana)
18
+ results = {}
19
+
20
+ grafana.dashboard_ids.sample(15).each do |dashboard_id|
21
+ print "Evaluating dashboard '#{dashboard_id}' for building a demo report..."
22
+ dashboard = grafana.dashboard(dashboard_id)
23
+
24
+ results = evaluate_dashboard(dashboard, @query_classes - results.keys).merge(results)
25
+
26
+ puts "done - #{(@query_classes - results.keys).length} examples to go"
27
+ break if (@query_classes - results.keys).empty?
28
+ end
29
+
30
+ if grafana.dashboard_ids.length > 15 && !(@query_classes - results.keys).empty?
31
+ puts 'Aborting evaluating further dashboards after 15 samples.'
32
+ end
33
+
34
+ unless (@query_classes - results.keys).empty?
35
+ puts "For #{(@query_classes - results.keys).length} reporter functionalities no appropriate "\
36
+ 'examples could be found in the configured grafana instance.'
37
+ end
38
+
39
+ format_results(default_result(@query_classes - results.keys).merge(results))
40
+ end
41
+
42
+ private
43
+
44
+ def default_result(query_classes)
45
+ results = {}
46
+
47
+ query_classes.each do |query_class|
48
+ results[query_class] = "No example found for #{query_class.name} in the dashboards."
49
+ end
50
+
51
+ results
52
+ end
53
+
54
+ def evaluate_dashboard(dashboard, query_classes)
55
+ results = {}
56
+
57
+ dashboard.panels.shuffle.each do |panel|
58
+ begin
59
+ next if panel.datasource.is_a?(Grafana::UnsupportedDatasource)
60
+ rescue Grafana::DatasourceDoesNotExistError
61
+ next
62
+ end
63
+
64
+ query_classes.each do |query_class|
65
+ unless query_class.public_instance_methods.include?(:build_demo_entry)
66
+ results[query_class] = "Method 'build_demo_entry' not implemented for #{query_class.name}"
67
+ next
68
+ end
69
+
70
+ begin
71
+ result = query_class.new.build_demo_entry(panel)
72
+ results[query_class] = result if result
73
+ rescue Grafana::DatasourceDoesNotExistError
74
+ # properly catch DatasourceDoesNotExist errors here, as they don't lead to a real issue
75
+ # during demo report creation
76
+ # This may e.g. happen if a panel asks e.g. for datasource '-- Dashboard --' which is
77
+ # currently not allowed
78
+ rescue StandardError => e
79
+ puts "#{e.message}\n#{e.backtrace.join("\n")}"
80
+ end
81
+ end
82
+ end
83
+
84
+ results
85
+ end
86
+
87
+ def format_results(raw_results)
88
+ results = ['= Demo report',
89
+ "Created by `+ruby-grafana-reporter+` version #{GRAFANA_REPORTER_VERSION.join('.')}",
90
+ '== Examples']
91
+
92
+ raw_results.each do |k, v|
93
+ results += if v =~ /^[A-Z]/
94
+ ["=== #{k.to_s.gsub(/.*::/, '')}", v.to_s]
95
+ else
96
+ ["=== #{k.to_s.gsub(/.*::/, '')}", 'Sample call:', " #{v.gsub(/\n/, "\n ")}",
97
+ 'Result:', v.to_s]
98
+ end
99
+ end
100
+
101
+ results.join("\n\n")
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrafanaReporter
4
+ module ERB
5
+ # This class builds a demo report for ERB templates
6
+ class DemoReportBuilder
7
+ # This method is called if a demo report shall be built for the given {Grafana::Panel}.
8
+ # @param panel [Grafana::Panel] panel object, for which a demo entry shall be created.
9
+ # @return [String] String containing the entry, or nil if not possible for given panel
10
+ def build_demo_entry(panel)
11
+ return nil unless panel
12
+ return nil unless panel.model['type'].include?('table')
13
+
14
+ ref_id = nil
15
+ panel.model['targets'].each do |item|
16
+ if !item['hide'] && !panel.query(item['refId']).to_s.empty?
17
+ ref_id = item['refId']
18
+ break
19
+ end
20
+ end
21
+ return nil unless ref_id
22
+
23
+ <<~DEMO_ERB_TEMPLATE
24
+ <%
25
+ dashboard = '#{panel.dashboard.id}'
26
+ instance = 'default'
27
+ # load the panel object from grafana instance
28
+ panel = @report.grafana(instance).dashboard(dashboard).panel(#{panel.id})
29
+ # build a complete attributes hash, including the variables set for this report call
30
+ # e.g. including command line parameters etc.
31
+ attrs = @attributes.merge({ 'result_type' => 'panel_table', 'query' => '#{ref_id}' })
32
+ query = QueryValueQuery.new(panel, variables: attrs)
33
+ %>
34
+
35
+ This is a test table for panel <%= panel.id %>:
36
+
37
+ <%= query.execute %>
38
+
39
+ For detailed API documentation you may start with:
40
+ 1) the AbstractReport (https://rubydoc.info/gems/ruby-grafana-reporter/GrafanaReporter/AbstractReport), or
41
+ 2) subclasses of the AbstractQuery (https://rubydoc.info/gems/ruby-grafana-reporter/GrafanaReporter/AbstractQuery)
42
+ DEMO_ERB_TEMPLATE
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'erb'
4
+
5
+ module GrafanaReporter
6
+ module ERB
7
+ # Implementation of a specific {AbstractReport}. It is used to
8
+ # build reports specifically for erb templates.
9
+ class Report < ::GrafanaReporter::AbstractReport
10
+ # Starts to create an asciidoctor report. It utilizes all extensions in the {GrafanaReporter::Asciidoctor}
11
+ # namespace to realize the conversion.
12
+ # @see AbstractReport#build
13
+ def build
14
+ attrs = @config.default_document_attributes.merge(@custom_attributes).merge({ 'grafana_report_timestamp' => ::Grafana::Variable.new(Time.now.to_s) })
15
+ logger.debug("Document attributes: #{attrs}")
16
+
17
+ File.write(path, ::ERB.new(File.read(@template)).result(ReportJail.new(self, attrs).bind))
18
+ end
19
+
20
+ # @see AbstractReport#default_template_extension
21
+ def self.default_template_extension
22
+ 'erb'
23
+ end
24
+
25
+ # @see AbstractReport#default_result_extension
26
+ def self.default_result_extension
27
+ 'txt'
28
+ end
29
+
30
+ # @see AbstractReport#demo_report_classes
31
+ def self.demo_report_classes
32
+ [DemoReportBuilder]
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrafanaReporter
4
+ module ERB
5
+ # An instance of this class is used as binding for the ERB execution, i.e.
6
+ # this class contains everything known within the ERB template
7
+ class ReportJail
8
+ attr_reader :report, :attributes
9
+
10
+ def initialize(report, attributes)
11
+ @report = report
12
+ @attributes = attributes
13
+ end
14
+
15
+ # @return binding to this object
16
+ def bind
17
+ binding
18
+ end
19
+ end
20
+ end
21
+ end
@@ -8,6 +8,30 @@ module GrafanaReporter
8
8
  end
9
9
  end
10
10
 
11
+ # Raised if a datasource shall be queried, which is not (yet) supported by the reporter
12
+ class DatasourceNotSupportedError < GrafanaReporterError
13
+ def initialize(datasource, query)
14
+ super("The datasource '#{datasource.name}' is of type '#{datasource.type}' which is currently "\
15
+ "not supported for the query type '#{query}'.")
16
+ end
17
+ end
18
+
19
+ # Raised if some unhandled exception is raised during a datasource request execution.
20
+ class DatasourceRequestInternalError < GrafanaReporterError
21
+ def initialize(datasource, message)
22
+ super("The datasource request to '#{datasource.name}' (#{datasource.class}) failed with "\
23
+ "an internal error: #{message}")
24
+ end
25
+ end
26
+
27
+ # Raised if the return value of a datasource request does not match the expected return hash.
28
+ class DatasourceRequestInvalidReturnValueError < GrafanaReporterError
29
+ def initialize(datasource, message)
30
+ super("The datasource request to '#{datasource.name}' (#{datasource.class})"\
31
+ "returned an invalid value: '#{message}'")
32
+ end
33
+ end
34
+
11
35
  # Thrown, if the requested grafana instance does not have the mandatory 'host'
12
36
  # setting configured.
13
37
  class GrafanaInstanceWithoutHostError < GrafanaReporterError
@@ -45,4 +69,37 @@ module GrafanaReporter
45
69
  "but was '#{currently}'.")
46
70
  end
47
71
  end
72
+
73
+ # Thrown, if the value configuration in {AbstractQuery#replace_values} is
74
+ # invalid.
75
+ class MalformedReplaceValuesStatementError < GrafanaReporterError
76
+ def initialize(statement)
77
+ super("The specified replace_values statement '#{statement}' is invalid. Make sure it contains"\
78
+ " exactly one not escaped ':' symbol.")
79
+ end
80
+ end
81
+
82
+ # Thrown, if a configured parameter is malformed.
83
+ class MalformedAttributeContentError < GrafanaReporterError
84
+ def initialize(message, attribute, content)
85
+ super("The content '#{content}' in attribute '#{attribute}' is malformed: #{message}")
86
+ end
87
+ end
88
+
89
+ # Thrown, if a configured time range is not supported by the reporter.
90
+ #
91
+ # If this happens, most likely the reporter has to implement the new
92
+ # time range definition.
93
+ class TimeRangeUnknownError < GrafanaReporterError
94
+ def initialize(time_range)
95
+ super("The specified time range '#{time_range}' is unknown.")
96
+ end
97
+ end
98
+
99
+ # Thrown, if a mandatory attribute is not set.
100
+ class MissingMandatoryAttributeError < GrafanaReporterError
101
+ def initialize(attribute)
102
+ super("Missing mandatory attribute '#{attribute}'.")
103
+ end
104
+ end
48
105
  end
@@ -25,7 +25,7 @@ module GrafanaReporter
25
25
  end
26
26
 
27
27
  # Sets the severity level of the additional logger to the given severity.
28
- # @param severity one of {Logger::Severity}
28
+ # @param severity one of Logger::Severity
29
29
  def level=(severity)
30
30
  @additional_logger.level = severity
31
31
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrafanaReporter
4
+ # This class is used to create an image out of a {Grafana::Panel}.
5
+ class PanelImageQuery < AbstractQuery
6
+ # Sets the proper render variables.
7
+ def pre_process
8
+ # TODO: ensure that in case of timezones are specified, that they are also forwarded to the image renderer
9
+ # rename "render-" variables
10
+ @variables = @variables.each_with_object({}) { |(k, v), h| h[k.gsub(/^render-/, '')] = v }
11
+ @datasource = Grafana::ImageRenderingDatasource.new(nil)
12
+ end
13
+
14
+ # Returns the body of the http query, which contains the raw image.
15
+ def post_process
16
+ @result = @result[:content].first
17
+ raise ::Grafana::ImageCouldNotBeRenderedError, @panel if @result.include?('<html')
18
+ end
19
+
20
+ # @see AbstractQuery#raw_query
21
+ def raw_query
22
+ { panel: @panel }
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrafanaReporter
4
+ # This class is used to query properties from a {Grafana::Panel}, such as +description+,
5
+ # +title+ etc.
6
+ class PanelPropertyQuery < AbstractQuery
7
+ # @see Grafana::AbstractQuery#pre_process
8
+ def pre_process
9
+ @datasource = Grafana::GrafanaPropertyDatasource.new(nil)
10
+ end
11
+
12
+ # @see Grafana::AbstractQuery#post_process
13
+ def post_process
14
+ @result = @result[:content].first
15
+ end
16
+
17
+ # @see Grafana::AbstractQuery#raw_query
18
+ def raw_query
19
+ @raw_query.merge({ panel: @panel })
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrafanaReporter
4
+ # This class provides a general query implementation for any kind of single value and table queries.
5
+ class QueryValueQuery < AbstractQuery
6
+ # @see Grafana::AbstractQuery#pre_process
7
+ def pre_process
8
+ @datasource = @panel.datasource if @panel
9
+
10
+ @variables['result_type'] ||= Variable.new('')
11
+ end
12
+
13
+ # Executes {AbstractQuery#format_columns}, {AbstractQuery#replace_values} and
14
+ # {AbstractQuery#filter_columns} on the query results.
15
+ #
16
+ # Finally the results are formatted as a asciidoctor table.
17
+ # @see Grafana::AbstractQuery#post_process
18
+ def post_process
19
+ modify_results
20
+
21
+ case @variables['result_type'].raw_value
22
+ when /(?:panel_table|sql_table)/
23
+ @result = format_table_output(@result, row_divider: @variables['row_divider'], column_divider: @variables['column_divider'], table_formatter: @variables['table_formatter'], include_headline: @variables['include_headline'])
24
+
25
+ when /(?:panel_value|sql_value)/
26
+ tmp = @result[:content] || []
27
+ @result = tmp.flatten.first
28
+
29
+ else
30
+ raise StandardError, "Unsupported 'result_type' received: '#{@variables['result_type'].raw_value}'"
31
+
32
+ end
33
+ end
34
+
35
+ # @see Grafana::AbstractQuery#raw_query
36
+ def raw_query
37
+ return @raw_query if @raw_query
38
+
39
+ case @variables['result_type'].raw_value
40
+ when /(?:panel_table|panel_value)/
41
+ @variables['query'] ? @panel.query(@variables['query'].raw_value) : @panel.query(nil)
42
+
43
+ when /(?:sql_table|sql_value)/
44
+ nil
45
+
46
+ else
47
+ raise StandardError, "Unsupported 'result_type' received: '#{@variables['result_type'].raw_value}'"
48
+
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def modify_results
55
+ @result = format_columns(@result, @variables['format'])
56
+ @result = replace_values(@result, @variables.select { |k, _v| k =~ /^replace_values_\d+/ })
57
+ @result = filter_columns(@result, @variables['filter_columns'])
58
+ @result = transpose(@result, @variables['transpose'])
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrafanaReporter
4
+ # This class provides a default webhook implementation for report events. It sends out
5
+ # a webrequest to the configured +callback_url+ with all necessary information about the
6
+ # event and the report.
7
+ class ReportWebhook
8
+ def initialize(callback_url)
9
+ @callback_url = callback_url
10
+ end
11
+
12
+ # Implements the call of the configured webhook.
13
+ # Provides the following report information in JSON format:
14
+ #
15
+ # :object_id - id of the current report
16
+ # :path - file path to the report
17
+ # :status - report status as string, e.g. `cancelled`, `finished` or `in progress`
18
+ # :execution_time - execution time in seconds of the report
19
+ # :template - name of the used template
20
+ # :start_time - time when the report creation started
21
+ # :end_time - time when the report creation ended
22
+ # :event - event, which has happened, e.g. `on-before-create`
23
+ #
24
+ # Please note that this callback is a non-blocking event, i.e. the report
25
+ # generation is proceeding, no matter if the callback is successfull and
26
+ # no matter how long the execution of the callback does take.
27
+ def callback(event, report)
28
+ # build report information as JSON
29
+ data = { object_id: report.object_id, path: report.path, status: report.status,
30
+ execution_time: report.execution_time, template: report.template,
31
+ start_time: report.start_time, end_time: report.end_time, event: event }
32
+
33
+ request = { body: JSON.generate(data), accept: nil, content_type: nil }
34
+ res = ::Grafana::WebRequest.new(@callback_url, request).execute
35
+
36
+ "#{res} - Body: #{res.body}"
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ruby_grafana_reporter'
4
+
5
+ config = GrafanaReporter::Configuration.new
6
+ config.config = GrafanaReporter::Application::Application.load_config
7
+
8
+ GrafanaReporter::Asciidoctor::Report.new(config).register