ruby-grafana-reporter 0.4.2 → 0.5.0
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.
- 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
|