ruby-grafana-reporter 0.1.7 → 0.2.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 +166 -339
- data/bin/ruby-grafana-reporter +5 -4
- data/lib/VERSION.rb +5 -3
- data/lib/grafana/abstract_panel_query.rb +22 -20
- data/lib/grafana/abstract_query.rb +132 -127
- data/lib/grafana/abstract_sql_query.rb +51 -42
- data/lib/grafana/dashboard.rb +77 -66
- data/lib/grafana/errors.rb +66 -61
- data/lib/grafana/grafana.rb +130 -131
- data/lib/grafana/panel.rb +41 -39
- data/lib/grafana/panel_image_query.rb +52 -49
- data/lib/grafana/variable.rb +217 -259
- data/lib/grafana_reporter/abstract_report.rb +112 -109
- data/lib/grafana_reporter/application/application.rb +404 -229
- data/lib/grafana_reporter/application/errors.rb +33 -30
- data/lib/grafana_reporter/application/webservice.rb +231 -0
- data/lib/grafana_reporter/asciidoctor/alerts_table_query.rb +104 -99
- data/lib/grafana_reporter/asciidoctor/annotations_table_query.rb +99 -96
- data/lib/grafana_reporter/asciidoctor/errors.rb +40 -37
- data/lib/grafana_reporter/asciidoctor/extensions/alerts_table_include_processor.rb +92 -86
- data/lib/grafana_reporter/asciidoctor/extensions/annotations_table_include_processor.rb +91 -86
- data/lib/grafana_reporter/asciidoctor/extensions/panel_image_block_macro.rb +69 -67
- data/lib/grafana_reporter/asciidoctor/extensions/panel_image_inline_macro.rb +68 -65
- data/lib/grafana_reporter/asciidoctor/extensions/panel_property_inline_macro.rb +61 -58
- data/lib/grafana_reporter/asciidoctor/extensions/panel_query_table_include_processor.rb +78 -75
- data/lib/grafana_reporter/asciidoctor/extensions/panel_query_value_inline_macro.rb +73 -70
- data/lib/grafana_reporter/asciidoctor/extensions/processor_mixin.rb +20 -18
- data/lib/grafana_reporter/asciidoctor/extensions/show_environment_include_processor.rb +43 -41
- data/lib/grafana_reporter/asciidoctor/extensions/sql_table_include_processor.rb +70 -67
- data/lib/grafana_reporter/asciidoctor/extensions/sql_value_inline_macro.rb +66 -65
- data/lib/grafana_reporter/asciidoctor/extensions/value_as_variable_include_processor.rb +61 -57
- data/lib/grafana_reporter/asciidoctor/panel_first_value_query.rb +34 -32
- data/lib/grafana_reporter/asciidoctor/panel_image_query.rb +25 -23
- data/lib/grafana_reporter/asciidoctor/panel_property_query.rb +44 -43
- data/lib/grafana_reporter/asciidoctor/panel_table_query.rb +38 -36
- data/lib/grafana_reporter/asciidoctor/query_mixin.rb +310 -309
- data/lib/grafana_reporter/asciidoctor/report.rb +177 -159
- data/lib/grafana_reporter/asciidoctor/sql_first_value_query.rb +37 -34
- data/lib/grafana_reporter/asciidoctor/sql_table_query.rb +39 -32
- data/lib/grafana_reporter/configuration.rb +257 -326
- data/lib/grafana_reporter/errors.rb +48 -38
- data/lib/grafana_reporter/logger/two_way_logger.rb +58 -52
- data/lib/ruby-grafana-reporter.rb +29 -27
- metadata +10 -23
@@ -1,159 +1,177 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
@
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
#
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
registry
|
33
|
-
|
34
|
-
registry.inline_macro Extensions::
|
35
|
-
registry.inline_macro Extensions::
|
36
|
-
registry.
|
37
|
-
registry.
|
38
|
-
registry.
|
39
|
-
registry.include_processor Extensions::
|
40
|
-
registry.include_processor Extensions::
|
41
|
-
registry.include_processor Extensions::
|
42
|
-
registry.include_processor Extensions::
|
43
|
-
registry.include_processor Extensions::
|
44
|
-
|
45
|
-
::
|
46
|
-
|
47
|
-
@
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
@
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
#
|
130
|
-
# @param
|
131
|
-
# @return [
|
132
|
-
def
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GrafanaReporter
|
4
|
+
module Asciidoctor
|
5
|
+
# Implementation of a specific {AbstractReport}. It is used to
|
6
|
+
# build reports specifically for asciidoctor results.
|
7
|
+
class Report < GrafanaReporter::AbstractReport
|
8
|
+
# (see AbstractReport#initialize)
|
9
|
+
def initialize(config, template, destination_file_or_path = nil, custom_attributes = {})
|
10
|
+
super
|
11
|
+
@current_pos = 0
|
12
|
+
@image_files = []
|
13
|
+
@grafana_instances = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
# Starts to create an asciidoctor report. It utilizes all {Extensions} to
|
17
|
+
# realize the conversion.
|
18
|
+
# @see AbstractReport#create_report
|
19
|
+
# @return [void]
|
20
|
+
def create_report
|
21
|
+
@start_time = Time.now
|
22
|
+
attrs = { 'convert-backend' => 'pdf' }.merge(@config.default_document_attributes.merge(@custom_attributes))
|
23
|
+
attrs['grafana-report-timestamp'] = @start_time.to_s
|
24
|
+
logger.info("Report started at #{@start_time}")
|
25
|
+
logger.debug("Document attributes: #{attrs}")
|
26
|
+
|
27
|
+
initialize_step_counter
|
28
|
+
|
29
|
+
# register necessary extensions for the current report
|
30
|
+
::Asciidoctor::LoggerManager.logger = logger
|
31
|
+
|
32
|
+
registry = ::Asciidoctor::Extensions::Registry.new
|
33
|
+
# TODO: dynamically register macros, which is also needed when supporting custom macros
|
34
|
+
registry.inline_macro Extensions::PanelImageInlineMacro.new.current_report(self)
|
35
|
+
registry.inline_macro Extensions::PanelQueryValueInlineMacro.new.current_report(self)
|
36
|
+
registry.inline_macro Extensions::PanelPropertyInlineMacro.new.current_report(self)
|
37
|
+
registry.inline_macro Extensions::SqlValueInlineMacro.new.current_report(self)
|
38
|
+
registry.block_macro Extensions::PanelImageBlockMacro.new.current_report(self)
|
39
|
+
registry.include_processor Extensions::ValueAsVariableIncludeProcessor.new.current_report(self)
|
40
|
+
registry.include_processor Extensions::PanelQueryTableIncludeProcessor.new.current_report(self)
|
41
|
+
registry.include_processor Extensions::SqlTableIncludeProcessor.new.current_report(self)
|
42
|
+
registry.include_processor Extensions::ShowEnvironmentIncludeProcessor.new.current_report(self)
|
43
|
+
registry.include_processor Extensions::ShowHelpIncludeProcessor.new.current_report(self)
|
44
|
+
registry.include_processor Extensions::AnnotationsTableIncludeProcessor.new.current_report(self)
|
45
|
+
registry.include_processor Extensions::AlertsTableIncludeProcessor.new.current_report(self)
|
46
|
+
|
47
|
+
::Asciidoctor.convert_file(@template, extension_registry: registry, backend: attrs['convert-backend'],
|
48
|
+
to_file: path, attributes: attrs, header_footer: true)
|
49
|
+
|
50
|
+
@destination_file_or_path.close if @destination_file_or_path.is_a?(File)
|
51
|
+
|
52
|
+
# store report including als images as ZIP file, if the result is not a PDF
|
53
|
+
if attrs['convert-backend'] != 'pdf'
|
54
|
+
dest_path = nil
|
55
|
+
case
|
56
|
+
when @destination_file_or_path.is_a?(File)
|
57
|
+
dest_path = @destination_file_or_path.path
|
58
|
+
when @destination_file_or_path.is_a?(Tempfile)
|
59
|
+
dest_path = @destination_file_or_path.path
|
60
|
+
else
|
61
|
+
dest_path = @destination_file_or_path
|
62
|
+
end
|
63
|
+
|
64
|
+
# build zip file
|
65
|
+
zip_file = Tempfile.new('gf_zip')
|
66
|
+
Zip::File.open(zip_file.path, Zip::File::CREATE) do |zipfile|
|
67
|
+
# add report file
|
68
|
+
zipfile.get_output_stream("#{dest_path.gsub(@config.reports_folder, '')}.#{attrs['convert-backend']}") do |f|
|
69
|
+
f.puts File.read(dest_path)
|
70
|
+
end
|
71
|
+
|
72
|
+
# add image files
|
73
|
+
@image_files.each do |file|
|
74
|
+
zipfile.get_output_stream(file.path.gsub(@config.images_folder, '')) { |f| f.puts File.read(file.path) }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# replace original file with zip file
|
79
|
+
zip_file.rewind
|
80
|
+
begin
|
81
|
+
File.write(dest_path, zip_file.read)
|
82
|
+
rescue StandardError => e
|
83
|
+
logger.fatal("Could not overwrite report file '#{dest_path}' with ZIP file. (#{e.message}).")
|
84
|
+
end
|
85
|
+
|
86
|
+
# cleanup temporary zip file
|
87
|
+
zip_file.close
|
88
|
+
zip_file.unlink
|
89
|
+
end
|
90
|
+
|
91
|
+
clean_image_files
|
92
|
+
@end_time = Time.now
|
93
|
+
logger.info("Report finished after #{@end_time - @start_time} seconds.")
|
94
|
+
@done = true
|
95
|
+
rescue StandardError => e
|
96
|
+
# catch all errors during execution
|
97
|
+
died_with_error(e)
|
98
|
+
raise e
|
99
|
+
end
|
100
|
+
|
101
|
+
# @see AbstractReport#progress
|
102
|
+
# @return [Float] number between 0 and 1 reflecting the current progress.
|
103
|
+
def progress
|
104
|
+
return 0 if @total_steps.to_i.zero?
|
105
|
+
|
106
|
+
@current_pos.to_f / @total_steps
|
107
|
+
end
|
108
|
+
|
109
|
+
# @param instance [String] requested grafana instance
|
110
|
+
# @return [Grafana::Grafana] the requested grafana instance.
|
111
|
+
def grafana(instance)
|
112
|
+
unless @grafana_instances[instance]
|
113
|
+
@grafana_instances[instance] = ::Grafana::Grafana.new(@config.grafana_host(instance),
|
114
|
+
@config.grafana_api_key(instance),
|
115
|
+
logger: @logger,
|
116
|
+
datasources: @config.grafana_datasources(instance))
|
117
|
+
end
|
118
|
+
@grafana_instances[instance]
|
119
|
+
end
|
120
|
+
|
121
|
+
# Increments the progress.
|
122
|
+
# @return [Integer] number of the current progress position.
|
123
|
+
def next_step
|
124
|
+
@current_pos += 1
|
125
|
+
@current_pos
|
126
|
+
end
|
127
|
+
|
128
|
+
# Called to save a temporary image file. After the final generation of the
|
129
|
+
# report, these temporary files will automatically be removed.
|
130
|
+
# @param img_data [String] image file raw data, which shall be saved
|
131
|
+
# @return [String] path to the temporary file.
|
132
|
+
def save_image_file(img_data)
|
133
|
+
file = Tempfile.new(['gf_image_', '.png'], @config.images_folder.to_s)
|
134
|
+
file.write(img_data)
|
135
|
+
path = file.path.gsub(/#{@config.images_folder}/, '')
|
136
|
+
|
137
|
+
@image_files << file
|
138
|
+
file.close
|
139
|
+
|
140
|
+
path
|
141
|
+
end
|
142
|
+
|
143
|
+
# Called, if the report generation has died with an error.
|
144
|
+
# @param error [StandardError] occured error
|
145
|
+
# @return [void]
|
146
|
+
def died_with_error(error)
|
147
|
+
@error = [error.message] << [error.backtrace]
|
148
|
+
@end_time = Time.now
|
149
|
+
@done = true
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
def clean_image_files
|
155
|
+
@image_files.each(&:unlink)
|
156
|
+
@image_files = []
|
157
|
+
end
|
158
|
+
|
159
|
+
def initialize_step_counter
|
160
|
+
@total_steps = 0
|
161
|
+
File.readlines(@template).each do |line|
|
162
|
+
begin
|
163
|
+
@total_steps += line.gsub(%r{//.*}, '').scan(/(?:grafana_panel_image|grafana_panel_query_value|
|
164
|
+
grafana_panel_query_table|grafana_sql_value|
|
165
|
+
grafana_sql_table|grafana_environment|grafana_help|
|
166
|
+
grafana_panel_property|grafana_annotations|grafana_alerts|
|
167
|
+
grafana_value_as_variable)/x).length
|
168
|
+
rescue StandardError => e
|
169
|
+
logger.error("Could not process line '#{line}' (Error: #{e.message})")
|
170
|
+
raise e
|
171
|
+
end
|
172
|
+
end
|
173
|
+
logger.debug("Template #{@template} contains #{@total_steps} calls of grafana reporter functions.")
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -1,34 +1,37 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
class
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
results =
|
18
|
-
results =
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GrafanaReporter
|
4
|
+
module Asciidoctor
|
5
|
+
# This class is being used to execute a SQL query against a grafana datasource.
|
6
|
+
# Only the first result in the first column will be returned as a single value.
|
7
|
+
class SqlFirstValueQuery < Grafana::AbstractSqlQuery
|
8
|
+
include QueryMixin
|
9
|
+
|
10
|
+
# Executes {QueryMixin#format_columns}, {QueryMixin#replace_values} and
|
11
|
+
# {QueryMixin#filter_columns} on the query results.
|
12
|
+
#
|
13
|
+
# Finally only the first value in the first row and the first column of
|
14
|
+
# will be returned.
|
15
|
+
# @return [void]
|
16
|
+
def post_process
|
17
|
+
results = preformat_sql_result(@result.body)
|
18
|
+
results = format_columns(results, @variables['format'])
|
19
|
+
results = replace_values(results, @variables.select { |k, _v| k =~ /^replace_values_\d+/ })
|
20
|
+
results = filter_columns(results, @variables['filter_columns'])
|
21
|
+
if @variables['filter_column']
|
22
|
+
@report.logger.warn("DEPRECATED: Call of no longer supported function 'filter_column' has been found."\
|
23
|
+
" Rename to 'filter_columns'")
|
24
|
+
results = filter_columns(results, @variables['filter_column'])
|
25
|
+
end
|
26
|
+
|
27
|
+
unless results[:content].empty?
|
28
|
+
unless results[:content][0].empty?
|
29
|
+
@result = results[:content][0][0]
|
30
|
+
return
|
31
|
+
end
|
32
|
+
end
|
33
|
+
@result = ''
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -1,32 +1,39 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
class
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
results =
|
17
|
-
results =
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GrafanaReporter
|
4
|
+
module Asciidoctor
|
5
|
+
# This class is being used to execute a SQL query against a grafana datasource.
|
6
|
+
# The results will be formatted as as asciidoctor table.
|
7
|
+
class SqlTableQuery < Grafana::AbstractSqlQuery
|
8
|
+
include QueryMixin
|
9
|
+
|
10
|
+
# Executes {QueryMixin#format_columns}, {QueryMixin#replace_values} and
|
11
|
+
# {QueryMixin#filter_columns} on the query results.
|
12
|
+
#
|
13
|
+
# Finally the results are formatted as a asciidoctor table.
|
14
|
+
# @return [void]
|
15
|
+
def post_process
|
16
|
+
results = preformat_sql_result(@result.body)
|
17
|
+
results = format_columns(results, @variables['format'])
|
18
|
+
results = replace_values(results, @variables.select { |k, _v| k =~ /^replace_values_\d+/ })
|
19
|
+
results = filter_columns(results, @variables['filter_columns'])
|
20
|
+
if @variables['filter_column']
|
21
|
+
@report.logger.warn("DEPRECATED: Call of no longer supported function 'filter_column' has been found."\
|
22
|
+
" Rename to 'filter_columns'")
|
23
|
+
results = filter_columns(results, @variables['filter_column'])
|
24
|
+
end
|
25
|
+
results = transpose(results, @variables['transpose'])
|
26
|
+
row_divider = '| '
|
27
|
+
row_divider = @variables['row_divider'].raw_value if @variables['row_divider'].is_a?(Grafana::Variable)
|
28
|
+
column_divider = ' | '
|
29
|
+
column_divider = @variables['column_divider'].raw_value if @variables['column_divider'].is_a?(Grafana::Variable)
|
30
|
+
|
31
|
+
@result = results[:content].map do |row|
|
32
|
+
row_divider + row.map do |item|
|
33
|
+
column_divider == ' | ' ? item.to_s.gsub('|', '\\|') : item.to_s
|
34
|
+
end.join(column_divider)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|