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.
- checksums.yaml +4 -4
- data/README.md +144 -11
- data/lib/VERSION.rb +2 -2
- data/lib/grafana/abstract_datasource.rb +9 -3
- data/lib/grafana/dashboard.rb +6 -1
- data/lib/grafana/errors.rb +4 -11
- data/lib/grafana/grafana.rb +25 -1
- data/lib/grafana/grafana_environment_datasource.rb +56 -0
- data/lib/grafana/grafana_property_datasource.rb +8 -1
- data/lib/grafana/image_rendering_datasource.rb +5 -1
- data/lib/grafana/influxdb_datasource.rb +87 -3
- data/lib/grafana/panel.rb +1 -1
- data/lib/grafana/prometheus_datasource.rb +11 -2
- data/lib/grafana/sql_datasource.rb +3 -9
- data/lib/grafana/variable.rb +67 -36
- data/lib/grafana/webrequest.rb +1 -0
- data/lib/grafana_reporter/abstract_query.rb +65 -39
- data/lib/grafana_reporter/abstract_report.rb +19 -4
- data/lib/grafana_reporter/abstract_table_format_strategy.rb +74 -0
- data/lib/grafana_reporter/alerts_table_query.rb +6 -1
- data/lib/grafana_reporter/annotations_table_query.rb +6 -1
- data/lib/grafana_reporter/application/application.rb +7 -2
- data/lib/grafana_reporter/application/webservice.rb +41 -32
- data/lib/grafana_reporter/asciidoctor/adoc_plain_table_format_strategy.rb +27 -0
- data/lib/grafana_reporter/asciidoctor/alerts_table_include_processor.rb +3 -2
- data/lib/grafana_reporter/asciidoctor/annotations_table_include_processor.rb +3 -2
- data/lib/grafana_reporter/asciidoctor/help.rb +470 -0
- data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +7 -5
- data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +7 -5
- data/lib/grafana_reporter/asciidoctor/panel_property_inline_macro.rb +5 -1
- data/lib/grafana_reporter/asciidoctor/panel_query_table_include_processor.rb +6 -2
- data/lib/grafana_reporter/asciidoctor/panel_query_value_inline_macro.rb +3 -0
- data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +3 -2
- data/lib/grafana_reporter/asciidoctor/report.rb +15 -13
- data/lib/grafana_reporter/asciidoctor/show_environment_include_processor.rb +37 -6
- data/lib/grafana_reporter/asciidoctor/show_help_include_processor.rb +1 -1
- data/lib/grafana_reporter/asciidoctor/sql_table_include_processor.rb +6 -2
- data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +5 -1
- data/lib/grafana_reporter/asciidoctor/value_as_variable_include_processor.rb +0 -5
- data/lib/grafana_reporter/configuration.rb +27 -0
- data/lib/grafana_reporter/console_configuration_wizard.rb +5 -3
- data/lib/grafana_reporter/csv_table_format_strategy.rb +25 -0
- data/lib/grafana_reporter/demo_report_wizard.rb +3 -7
- data/lib/grafana_reporter/erb/demo_report_builder.rb +46 -0
- data/lib/grafana_reporter/erb/report.rb +13 -7
- data/lib/grafana_reporter/errors.rb +9 -7
- data/lib/grafana_reporter/panel_image_query.rb +1 -1
- data/lib/grafana_reporter/query_value_query.rb +7 -1
- data/lib/grafana_reporter/report_webhook.rb +12 -8
- data/lib/grafana_reporter/reporter_environment_datasource.rb +24 -0
- metadata +9 -3
- 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
|
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),
|
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),
|
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
|
-
|
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|
|
42
|
-
|
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
|
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("#{
|
59
|
-
zipfile.write File.read(
|
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(
|
66
|
+
File.write(path, zip_file.read)
|
75
67
|
rescue StandardError => e
|
76
|
-
logger.fatal("Could not overwrite report file '#{
|
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,
|
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
|
-
|
31
|
-
|
60
|
+
result += ['|===', '',
|
61
|
+
'== Accessible Variables',
|
62
|
+
'|===']
|
32
63
|
doc.attributes.sort.each do |k, v|
|
33
|
-
|
64
|
+
result << "| `+{#{k}}+` | #{v}"
|
34
65
|
end
|
35
|
-
|
66
|
+
result << '|==='
|
36
67
|
|
37
|
-
reader.unshift_lines
|
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
|
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
|
-
|
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),
|
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
|
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}.
|
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
|
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
|
-
|
18
|
-
|
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
|
-
|
21
|
-
|
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(
|
14
|
-
super("The datasource '#{
|
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(
|
22
|
-
super("The datasource request to '#{
|
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(
|
29
|
-
super("The datasource request to '#{
|
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'],
|
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
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
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
|