ruby-grafana-reporter 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +144 -11
  3. data/lib/VERSION.rb +2 -2
  4. data/lib/grafana/abstract_datasource.rb +9 -3
  5. data/lib/grafana/dashboard.rb +6 -1
  6. data/lib/grafana/errors.rb +4 -11
  7. data/lib/grafana/grafana.rb +25 -1
  8. data/lib/grafana/grafana_environment_datasource.rb +56 -0
  9. data/lib/grafana/grafana_property_datasource.rb +8 -1
  10. data/lib/grafana/image_rendering_datasource.rb +5 -1
  11. data/lib/grafana/influxdb_datasource.rb +87 -3
  12. data/lib/grafana/panel.rb +1 -1
  13. data/lib/grafana/prometheus_datasource.rb +11 -2
  14. data/lib/grafana/sql_datasource.rb +3 -9
  15. data/lib/grafana/variable.rb +67 -36
  16. data/lib/grafana/webrequest.rb +1 -0
  17. data/lib/grafana_reporter/abstract_query.rb +65 -39
  18. data/lib/grafana_reporter/abstract_report.rb +19 -4
  19. data/lib/grafana_reporter/abstract_table_format_strategy.rb +74 -0
  20. data/lib/grafana_reporter/alerts_table_query.rb +6 -1
  21. data/lib/grafana_reporter/annotations_table_query.rb +6 -1
  22. data/lib/grafana_reporter/application/application.rb +7 -2
  23. data/lib/grafana_reporter/application/webservice.rb +41 -32
  24. data/lib/grafana_reporter/asciidoctor/adoc_plain_table_format_strategy.rb +27 -0
  25. data/lib/grafana_reporter/asciidoctor/alerts_table_include_processor.rb +3 -2
  26. data/lib/grafana_reporter/asciidoctor/annotations_table_include_processor.rb +3 -2
  27. data/lib/grafana_reporter/asciidoctor/help.rb +470 -0
  28. data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +7 -5
  29. data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +7 -5
  30. data/lib/grafana_reporter/asciidoctor/panel_property_inline_macro.rb +5 -1
  31. data/lib/grafana_reporter/asciidoctor/panel_query_table_include_processor.rb +6 -2
  32. data/lib/grafana_reporter/asciidoctor/panel_query_value_inline_macro.rb +3 -0
  33. data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +3 -2
  34. data/lib/grafana_reporter/asciidoctor/report.rb +15 -13
  35. data/lib/grafana_reporter/asciidoctor/show_environment_include_processor.rb +37 -6
  36. data/lib/grafana_reporter/asciidoctor/show_help_include_processor.rb +1 -1
  37. data/lib/grafana_reporter/asciidoctor/sql_table_include_processor.rb +6 -2
  38. data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +5 -1
  39. data/lib/grafana_reporter/asciidoctor/value_as_variable_include_processor.rb +0 -5
  40. data/lib/grafana_reporter/configuration.rb +27 -0
  41. data/lib/grafana_reporter/console_configuration_wizard.rb +5 -3
  42. data/lib/grafana_reporter/csv_table_format_strategy.rb +25 -0
  43. data/lib/grafana_reporter/demo_report_wizard.rb +3 -7
  44. data/lib/grafana_reporter/erb/demo_report_builder.rb +46 -0
  45. data/lib/grafana_reporter/erb/report.rb +13 -7
  46. data/lib/grafana_reporter/errors.rb +9 -7
  47. data/lib/grafana_reporter/panel_image_query.rb +1 -1
  48. data/lib/grafana_reporter/query_value_query.rb +7 -1
  49. data/lib/grafana_reporter/report_webhook.rb +12 -8
  50. data/lib/grafana_reporter/reporter_environment_datasource.rb +24 -0
  51. metadata +9 -3
  52. data/lib/grafana_reporter/help.rb +0 -443
@@ -7,8 +7,8 @@ module GrafanaReporter
7
7
  # Implements the hook
8
8
  # grafana_panel_image:<panel_id>[<options>]
9
9
  #
10
- # Stores the queried panel as a temporary image file and returns an asciidoctor link
11
- # to be included in the report.
10
+ # Stores the queried panel as a temporary image file and returns a relative asciidoctor link
11
+ # to the storage location, which can then be included in the report.
12
12
  #
13
13
  # == Used document parameters
14
14
  # +grafana_default_instance+ - name of grafana instance, 'default' if not specified
@@ -20,8 +20,6 @@ module GrafanaReporter
20
20
  # +to+ - 'to' time for the sql query
21
21
  #
22
22
  # == Supported options
23
- # +field+ - property to query for, e.g. +description+ or +title+ (*mandatory*)
24
- #
25
23
  # +instance+ - name of grafana instance, 'default' if not specified
26
24
  #
27
25
  # +dashboard+ - uid of grafana dashboard to use
@@ -48,10 +46,14 @@ module GrafanaReporter
48
46
  begin
49
47
  # set alt text to a default, because otherwise asciidoctor fails
50
48
  attrs['alt'] = '' unless attrs['alt']
51
- query = PanelImageQuery.new(@report.grafana(instance).dashboard(dashboard).panel(target), variables: build_attribute_hash(parent.document.attributes, attrs))
49
+ query = PanelImageQuery.new(@report.grafana(instance).dashboard(dashboard).panel(target),
50
+ variables: build_attribute_hash(parent.document.attributes, attrs))
52
51
 
53
52
  image = query.execute
54
53
  image_path = @report.save_image_file(image)
54
+ rescue Grafana::GrafanaError => e
55
+ @report.logger.error(e.message)
56
+ return create_inline(parent, :quoted, e.message)
55
57
  rescue GrafanaReporterError => e
56
58
  @report.logger.error(e.message)
57
59
  return create_inline(parent, :quoted, e.message)
@@ -38,10 +38,14 @@ module GrafanaReporter
38
38
  " panel: #{target}, property: #{attrs[:field]})")
39
39
 
40
40
  begin
41
- query = PanelPropertyQuery.new(@report.grafana(instance).dashboard(dashboard).panel(target), variables: build_attribute_hash(parent.document.attributes, attrs))
41
+ query = PanelPropertyQuery.new(@report.grafana(instance).dashboard(dashboard).panel(target),
42
+ variables: build_attribute_hash(parent.document.attributes, attrs))
42
43
  query.raw_query = { property_name: attrs[:field] }
43
44
 
44
45
  description = query.execute
46
+ rescue Grafana::GrafanaError => e
47
+ @report.logger.error(e.message)
48
+ return create_inline(parent, :quoted, e.message)
45
49
  rescue GrafanaReporterError => e
46
50
  @report.logger.error(e.message)
47
51
  return create_inline(parent, :quoted, e.message)
@@ -59,9 +59,13 @@ module GrafanaReporter
59
59
 
60
60
  begin
61
61
  panel = @report.grafana(instance).dashboard(dashboard).panel(panel_id)
62
- query = QueryValueQuery.new(panel, variables: build_attribute_hash(doc.attributes, attrs))
62
+ vars = { 'table_formatter' => 'adoc_plain' }.merge(build_attribute_hash(doc.attributes, attrs))
63
+ query = QueryValueQuery.new(panel, variables: vars)
63
64
 
64
- reader.unshift_lines query.execute
65
+ reader.unshift_lines query.execute.split("\n")
66
+ rescue Grafana::GrafanaError => e
67
+ @report.logger.error(e.message)
68
+ reader.unshift_line "|#{e.message}"
65
69
  rescue GrafanaReporterError => e
66
70
  @report.logger.error(e.message)
67
71
  reader.unshift_line "|#{e.message}"
@@ -59,6 +59,9 @@ module GrafanaReporter
59
59
  query = QueryValueQuery.new(panel, variables: build_attribute_hash(parent.document.attributes, attrs))
60
60
 
61
61
  create_inline(parent, :quoted, query.execute)
62
+ rescue Grafana::GrafanaError => e
63
+ @report.logger.error(e.message)
64
+ create_inline(parent, :quoted, e.message)
62
65
  rescue GrafanaReporterError => e
63
66
  @report.logger.error(e.message)
64
67
  create_inline(parent, :quoted, e.message)
@@ -38,8 +38,9 @@ module GrafanaReporter
38
38
  # TODO: specify accepted options for each processor class individually
39
39
  k =~ /^(?:var-|render-)/ ||
40
40
  k =~ /^(?:timeout|from|to)$/ ||
41
- k =~ /filter_columns|format|replace_values_.*|transpose|column_divider|
42
- row_divider|from_timezone|to_timezone|result_type|query/x
41
+ k =~ /filter_columns|format|replace_values_.*|transpose|from_timezone|
42
+ to_timezone|result_type|query|table_formatter|include_headline|
43
+ column_divider|row_divider/x
43
44
  end)
44
45
 
45
46
  result
@@ -15,7 +15,7 @@ module GrafanaReporter
15
15
  # Starts to create an asciidoctor report. It utilizes all extensions in the {GrafanaReporter::Asciidoctor}
16
16
  # namespace to realize the conversion.
17
17
  # @see AbstractReport#build
18
- def build(template, destination_file_or_path, custom_attributes)
18
+ def build
19
19
  attrs = { 'convert-backend' => 'pdf' }.merge(@config.default_document_attributes.merge(@custom_attributes))
20
20
  logger.debug("Document attributes: #{attrs}")
21
21
 
@@ -41,22 +41,14 @@ module GrafanaReporter
41
41
  ::Asciidoctor.convert_file(@template, extension_registry: registry, backend: attrs['convert-backend'],
42
42
  to_file: path, attributes: attrs, header_footer: true)
43
43
 
44
- @destination_file_or_path.close if @destination_file_or_path.is_a?(File)
45
-
46
44
  # store report including als images as ZIP file, if the result is not a PDF
47
45
  if attrs['convert-backend'] != 'pdf'
48
- dest_path = if @destination_file_or_path.is_a?(File) || @destination_file_or_path.is_a?(Tempfile)
49
- @destination_file_or_path.path
50
- else
51
- @destination_file_or_path
52
- end
53
-
54
46
  # build zip file
55
47
  zip_file = Tempfile.new('gf_zip')
56
48
  buffer = Zip::OutputStream.write_buffer do |zipfile|
57
49
  # add report file
58
- zipfile.put_next_entry("#{dest_path.gsub(@config.reports_folder, '')}.#{attrs['convert-backend']}")
59
- zipfile.write File.read(dest_path)
50
+ zipfile.put_next_entry("#{path.gsub(@config.reports_folder, '')}.#{attrs['convert-backend']}")
51
+ zipfile.write File.read(path)
60
52
 
61
53
  # add image files
62
54
  @image_files.each do |file|
@@ -71,9 +63,9 @@ module GrafanaReporter
71
63
  # replace original file with zip file
72
64
  zip_file.rewind
73
65
  begin
74
- File.write(dest_path, zip_file.read)
66
+ File.write(path, zip_file.read)
75
67
  rescue StandardError => e
76
- logger.fatal("Could not overwrite report file '#{dest_path}' with ZIP file. (#{e.message}).")
68
+ logger.fatal("Could not overwrite report file '#{path}' with ZIP file. (#{e.message}).")
77
69
  end
78
70
 
79
71
  # cleanup temporary zip file
@@ -100,6 +92,16 @@ module GrafanaReporter
100
92
  path
101
93
  end
102
94
 
95
+ # @see AbstractReport#default_template_extension
96
+ def self.default_template_extension
97
+ 'adoc'
98
+ end
99
+
100
+ # @see AbstractReport#default_result_extension
101
+ def self.default_result_extension
102
+ 'pdf'
103
+ end
104
+
103
105
  # @see AbstractReport#demo_report_classes
104
106
  def self.demo_report_classes
105
107
  [AlertsTableIncludeProcessor, AnnotationsTableIncludeProcessor, PanelImageBlockMacro, PanelImageInlineMacro,
@@ -13,6 +13,9 @@ module GrafanaReporter
13
13
  #
14
14
  # == Used document parameters
15
15
  # All, to be listed as the available environment.
16
+ #
17
+ # == Supported options
18
+ # +instance+ - grafana instance name, if extended information about the grafana instance shall be printed
16
19
  class ShowEnvironmentIncludeProcessor < ::Asciidoctor::Extensions::IncludeProcessor
17
20
  include ProcessorMixin
18
21
 
@@ -22,19 +25,47 @@ module GrafanaReporter
22
25
  end
23
26
 
24
27
  # :nodoc:
25
- def process(doc, reader, _target, _attrs)
28
+ def process(doc, reader, _target, attrs)
26
29
  # return if @report.cancel
27
30
  @report.next_step
31
+ instance = attrs['instance'] || doc.attr('grafana_default_instance') || 'default'
32
+ attrs['result_type'] = 'sql_table'
28
33
  @report.logger.debug('Processing ShowEnvironmentIncludeProcessor')
34
+ grafana = @report.grafana(instance)
35
+
36
+ vars = { 'table_formatter' => 'adoc_plain', 'include_headline' => 'true'}
37
+ vars = vars.merge(build_attribute_hash(doc.attributes, attrs))
38
+
39
+ # query reporter environment
40
+ result = ['== Reporter', '|===']
41
+ query = QueryValueQuery.new(grafana, variables: vars.merge({'transpose' => 'true'}))
42
+ query.datasource = ::GrafanaReporter::ReporterEnvironmentDatasource.new(nil)
43
+ result += query.execute.split("\n")
44
+
45
+ # query grafana environment
46
+ result += ['|===', '',
47
+ '== Grafana Instance', '|===']
48
+ query = QueryValueQuery.new(grafana, variables: vars.merge({'transpose' => 'true'}))
49
+ query.raw_query = {grafana: grafana, mode: 'general'}
50
+ query.datasource = ::Grafana::GrafanaEnvironmentDatasource.new(nil)
51
+ result += query.execute.split("\n")
52
+
53
+ result += ['|===', '',
54
+ '== Accessible Dashboards', '|===']
55
+ query = QueryValueQuery.new(grafana, variables: vars)
56
+ query.raw_query = {grafana: grafana, mode: 'dashboards'}
57
+ query.datasource = Grafana::GrafanaEnvironmentDatasource.new(nil)
58
+ result += query.execute.split("\n")
29
59
 
30
- vars = ['== Accessible Variables',
31
- '|===']
60
+ result += ['|===', '',
61
+ '== Accessible Variables',
62
+ '|===']
32
63
  doc.attributes.sort.each do |k, v|
33
- vars << "| `+{#{k}}+` | #{v}"
64
+ result << "| `+{#{k}}+` | #{v}"
34
65
  end
35
- vars << '|==='
66
+ result << '|==='
36
67
 
37
- reader.unshift_lines vars
68
+ reader.unshift_lines result
38
69
  end
39
70
 
40
71
  # @see ProcessorMixin#build_demo_entry
@@ -23,7 +23,7 @@ module GrafanaReporter
23
23
  @report.next_step
24
24
  @report.logger.debug('Processing ShowHelpIncludeProcessor')
25
25
 
26
- reader.unshift_lines GrafanaReporter::Help.new.asciidoctor.split("\n")
26
+ reader.unshift_lines Help.new.asciidoctor.split("\n")
27
27
  end
28
28
 
29
29
  # @see ProcessorMixin#build_demo_entry
@@ -51,11 +51,15 @@ module GrafanaReporter
51
51
 
52
52
  begin
53
53
  # catch properly if datasource could not be identified
54
- query = QueryValueQuery.new(@report.grafana(instance), variables: build_attribute_hash(doc.attributes, attrs))
54
+ vars = { 'table_formatter' => 'adoc_plain' }.merge(build_attribute_hash(doc.attributes, attrs))
55
+ query = QueryValueQuery.new(@report.grafana(instance), variables: vars)
55
56
  query.datasource = @report.grafana(instance).datasource_by_id(target.split(':')[1].to_i)
56
57
  query.raw_query = attrs['sql']
57
58
 
58
- reader.unshift_lines query.execute
59
+ reader.unshift_lines query.execute.split("\n")
60
+ rescue Grafana::GrafanaError => e
61
+ @report.logger.error(e.message)
62
+ reader.unshift_line "|#{e.message}"
59
63
  rescue GrafanaReporterError => e
60
64
  @report.logger.error(e.message)
61
65
  reader.unshift_line "|#{e.message}"
@@ -49,11 +49,15 @@ module GrafanaReporter
49
49
 
50
50
  begin
51
51
  # catch properly if datasource could not be identified
52
- query = QueryValueQuery.new(@report.grafana(instance), variables: build_attribute_hash(parent.document.attributes, attrs))
52
+ query = QueryValueQuery.new(@report.grafana(instance),
53
+ variables: build_attribute_hash(parent.document.attributes, attrs))
53
54
  query.datasource = @report.grafana(instance).datasource_by_id(target)
54
55
  query.raw_query = attrs['sql']
55
56
 
56
57
  create_inline(parent, :quoted, query.execute)
58
+ rescue Grafana::GrafanaError => e
59
+ @report.logger.error(e.message)
60
+ create_inline(parent, :quoted, e.message)
57
61
  rescue GrafanaReporterError => e
58
62
  @report.logger.error(e.message)
59
63
  create_inline(parent, :quoted, e.message)
@@ -58,11 +58,6 @@ module GrafanaReporter
58
58
  return reader
59
59
  end
60
60
 
61
- # TODO: remove dirty hack to allow the document as parameter for other processors
62
- def doc.document
63
- self
64
- end
65
-
66
61
  # TODO: properly show error messages also in document
67
62
  ext = doc.extensions.find_inline_macro_extension(call) if doc.extensions.inline_macros?
68
63
  if !ext
@@ -152,6 +152,32 @@ module GrafanaReporter
152
152
  get_config('default-document-attributes') || {}
153
153
  end
154
154
 
155
+ # Checks if this is the latest ruby-grafana-reporter version. If and how often the check if
156
+ # performed, depends on the configuration setting `check-for-updates`. By default this is
157
+ # 0 (=disabled). If a number >0 is specified, the checks are performed once every n-days on
158
+ # report creation or call of overview webpage.
159
+ # @return [Boolean] true, if is ok, false if a newer version exists
160
+ def latest_version_check_ok?
161
+ return false if @newer_version_exists
162
+
163
+ value = get_config('grafana-reporter:check-for-updates') || 0
164
+ return true if value <= 0
165
+
166
+ # repeat check only every n-th day
167
+ if @last_version_check
168
+ return true if (Time.now - @last_version_check) < (value * 24*60*60)
169
+ end
170
+
171
+ # check for newer version
172
+ @last_version_check = Time.now
173
+ url = 'https://github.com/divinity666/ruby-grafana-reporter/releases/latest'
174
+ response = Grafana::WebRequest.new(url).execute
175
+ return true if response['location'] =~ /.*[\/v]#{GRAFANA_REPORTER_VERSION.join('.')}$/
176
+
177
+ @newer_version_exists = true
178
+ return false
179
+ end
180
+
155
181
  # This function shall be called, before the configuration object is used in the
156
182
  # {Application::Application#run}. It ensures, that everything is setup properly
157
183
  # and all necessary folders exist. Appropriate errors are raised in case of errors.
@@ -304,6 +330,7 @@ module GrafanaReporter
304
330
  [
305
331
  Hash, 1,
306
332
  {
333
+ 'check-for-updates' => [Integer, 0],
307
334
  'debug-level' => [String, 0],
308
335
  'run-mode' => [String, 0],
309
336
  'test-instance' => [String, 0],
@@ -6,7 +6,6 @@ module GrafanaReporter
6
6
  class ConsoleConfigurationWizard
7
7
  # Provides a command line configuration wizard for setting up the necessary configuration
8
8
  # file.
9
- # TODO: refactor class
10
9
  def start_wizard(config_file, console_config)
11
10
  action = overwrite_or_use_config_file(config_file)
12
11
  return if action == 'abort'
@@ -39,7 +38,7 @@ module GrafanaReporter
39
38
  puts 'Now everything is setup properly. Create your reports as required in the templates '\
40
39
  'folder and run the reporter either standalone with e.g. the following command:'
41
40
  puts
42
- puts " #{program_call}#{config_param} -t #{demo_report} -o demo_report_with_help.pdf"
41
+ puts " #{program_call}#{config_param} -t #{demo_report} -o demo_report.#{config.report_class.default_result_extension}"
43
42
  puts
44
43
  puts 'or run it as a service using the following command:'
45
44
  puts
@@ -73,6 +72,9 @@ module GrafanaReporter
73
72
  #{grafana}
74
73
 
75
74
  grafana-reporter:
75
+ # Specifies how often the reporter shall check for newer versions [number of days].
76
+ # You may set check-for-updates to 0 to disable
77
+ check-for-updates: 1
76
78
  report-class: GrafanaReporter::Asciidoctor::Report
77
79
  templates-folder: #{templates}
78
80
  reports-folder: #{reports}
@@ -120,7 +122,7 @@ default-document-attributes:
120
122
  return nil unless create =~ /^(?:y|Y)$/
121
123
 
122
124
  demo_report = 'demo_report'
123
- demo_report_file = "#{config.templates_folder}#{demo_report}.adoc"
125
+ demo_report_file = "#{config.templates_folder}#{demo_report}.#{config.report_class.default_template_extension}"
124
126
 
125
127
  # ask to overwrite file
126
128
  if File.exist?(demo_report_file)
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrafanaReporter
4
+ # Implements a default table format strategy, which will return tables
5
+ # as CSV formatted strings.
6
+ class CsvTableFormatStrategy < AbstractTableFormatStrategy
7
+ # @see AbstractTableFormatStrategy#abbreviation
8
+ def self.abbreviation
9
+ 'csv'
10
+ end
11
+
12
+ # @see AbstractTableFormatStrategy#format_rules
13
+ def format_rules
14
+ {
15
+ row_start: '',
16
+ row_end: "\n",
17
+ cell_start: '',
18
+ between_cells: ', ',
19
+ cell_end: '',
20
+ replace_string_or_regex: ',',
21
+ replacement: '\\,'
22
+ }
23
+ end
24
+ end
25
+ end
@@ -55,12 +55,6 @@ module GrafanaReporter
55
55
  results = {}
56
56
 
57
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
58
  query_classes.each do |query_class|
65
59
  unless query_class.public_instance_methods.include?(:build_demo_entry)
66
60
  results[query_class] = "Method 'build_demo_entry' not implemented for #{query_class.name}"
@@ -77,6 +71,9 @@ module GrafanaReporter
77
71
  # currently not allowed
78
72
  rescue StandardError => e
79
73
  puts "#{e.message}\n#{e.backtrace.join("\n")}"
74
+ rescue NotImplementedError
75
+ # Ignore these errors, as it only means, that a class does not implement
76
+ # the demo entry
80
77
  end
81
78
  end
82
79
  end
@@ -84,7 +81,6 @@ module GrafanaReporter
84
81
  results
85
82
  end
86
83
 
87
- # TODO: move this method to Asciidoctor::Report
88
84
  def format_results(raw_results)
89
85
  results = ['= Demo report',
90
86
  "Created by `+ruby-grafana-reporter+` version #{GRAFANA_REPORTER_VERSION.join('.')}",
@@ -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
@@ -10,20 +10,26 @@ module GrafanaReporter
10
10
  # Starts to create an asciidoctor report. It utilizes all extensions in the {GrafanaReporter::Asciidoctor}
11
11
  # namespace to realize the conversion.
12
12
  # @see AbstractReport#build
13
- def build(template, destination_file_or_path, custom_attributes)
14
- attrs = @config.default_document_attributes.merge(@custom_attributes)
13
+ def build
14
+ attrs = @config.default_document_attributes.merge(@custom_attributes).merge({ 'grafana_report_timestamp' => ::Grafana::Variable.new(Time.now.to_s) })
15
15
  logger.debug("Document attributes: #{attrs}")
16
16
 
17
- # TODO: if path is true, a default filename has to be generated. check if this should be a general function instead
18
- File.write(path, ::ERB.new(File.read(template)).result(ReportJail.new(self, attrs).bind))
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
19
24
 
20
- # TODO: check if closing output file is correct here, or maybe can be moved to AbstractReport.done!
21
- @destination_file_or_path.close if @destination_file_or_path.is_a?(File)
25
+ # @see AbstractReport#default_result_extension
26
+ def self.default_result_extension
27
+ 'txt'
22
28
  end
23
29
 
24
30
  # @see AbstractReport#demo_report_classes
25
31
  def self.demo_report_classes
26
- []
32
+ [DemoReportBuilder]
27
33
  end
28
34
  end
29
35
  end
@@ -10,23 +10,25 @@ module GrafanaReporter
10
10
 
11
11
  # Raised if a datasource shall be queried, which is not (yet) supported by the reporter
12
12
  class DatasourceNotSupportedError < GrafanaReporterError
13
- def initialize(ds, query)
14
- super("The datasource '#{ds.name}' is of type '#{ds.type}' which is currently not supported for "\
15
- "the query type '#{query}'.")
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
16
  end
17
17
  end
18
18
 
19
19
  # Raised if some unhandled exception is raised during a datasource request execution.
20
20
  class DatasourceRequestInternalError < GrafanaReporterError
21
- def initialize(ds, message)
22
- super("The datasource request to '#{ds.name}' (#{ds.class}) failed with an internal error: #{message}")
21
+ def initialize(datasource, message)
22
+ super("The datasource request to '#{datasource.name}' (#{datasource.class}) failed with "\
23
+ "an internal error: #{message}")
23
24
  end
24
25
  end
25
26
 
26
27
  # Raised if the return value of a datasource request does not match the expected return hash.
27
28
  class DatasourceRequestInvalidReturnValueError < GrafanaReporterError
28
- def initialize(ds, message)
29
- super("The datasource request to '#{ds.name}' (#{ds.class}) returned an invalid value: '#{message}'")
29
+ def initialize(datasource, message)
30
+ super("The datasource request to '#{datasource.name}' (#{datasource.class})"\
31
+ "returned an invalid value: '#{message}'")
30
32
  end
31
33
  end
32
34
 
@@ -5,6 +5,7 @@ module GrafanaReporter
5
5
  class PanelImageQuery < AbstractQuery
6
6
  # Sets the proper render variables.
7
7
  def pre_process
8
+ # TODO: properly show error, if a (maybe a repeated template) panel can not be rendered
8
9
  # TODO: ensure that in case of timezones are specified, that they are also forwarded to the image renderer
9
10
  # rename "render-" variables
10
11
  @variables = @variables.each_with_object({}) { |(k, v), h| h[k.gsub(/^render-/, '')] = v }
@@ -14,7 +15,6 @@ module GrafanaReporter
14
15
  # Returns the body of the http query, which contains the raw image.
15
16
  def post_process
16
17
  @result = @result[:content].first
17
- raise ::Grafana::ImageCouldNotBeRenderedError, @panel if @result.include?('<html')
18
18
  end
19
19
 
20
20
  # @see AbstractQuery#raw_query
@@ -19,8 +19,14 @@ module GrafanaReporter
19
19
  modify_results
20
20
 
21
21
  case @variables['result_type'].raw_value
22
+ when 'object'
23
+
22
24
  when /(?:panel_table|sql_table)/
23
- @result = format_table_output(@result, row_divider: @variables['row_divider'], column_divider: @variables['column_divider'])
25
+ @result = format_table_output(@result, row_divider: @variables['row_divider'],
26
+ column_divider: @variables['column_divider'],
27
+ table_formatter: @variables['table_formatter'],
28
+ include_headline: @variables['include_headline'],
29
+ transpose: @variables['transpose'])
24
30
 
25
31
  when /(?:panel_value|sql_value)/
26
32
  tmp = @result[:content] || []
@@ -12,14 +12,18 @@ module GrafanaReporter
12
12
  # Implements the call of the configured webhook.
13
13
  # Provides the following report information in JSON format:
14
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 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
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.
23
27
  def callback(event, report)
24
28
  # build report information as JSON
25
29
  data = { object_id: report.object_id, path: report.path, status: report.status,
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrafanaReporter
4
+ # Implements a datasource to return environment related information about the reporter in a tabular format.
5
+ class ReporterEnvironmentDatasource < ::Grafana::AbstractDatasource
6
+ # @see AbstractDatasource#request
7
+ def request(query_description)
8
+ {
9
+ header: ['Version', 'Release Date'],
10
+ content: [[GRAFANA_REPORTER_VERSION.join('.'), GRAFANA_REPORTER_RELEASE_DATE]]
11
+ }
12
+ end
13
+
14
+ # @see AbstractDatasource#default_variable_format
15
+ def default_variable_format
16
+ nil
17
+ end
18
+
19
+ # @see AbstractDatasource#name
20
+ def name
21
+ self.class.to_s
22
+ end
23
+ end
24
+ end