ruby-grafana-reporter 0.1.6
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 +7 -0
- data/LICENSE +20 -0
- data/README.md +248 -0
- data/lib/VERSION.rb +3 -0
- data/lib/grafana/abstract_panel_query.rb +20 -0
- data/lib/grafana/abstract_query.rb +127 -0
- data/lib/grafana/abstract_sql_query.rb +42 -0
- data/lib/grafana/dashboard.rb +66 -0
- data/lib/grafana/errors.rb +61 -0
- data/lib/grafana/grafana.rb +131 -0
- data/lib/grafana/panel.rb +39 -0
- data/lib/grafana/panel_image_query.rb +49 -0
- data/lib/grafana/variable.rb +259 -0
- data/lib/grafana_reporter/abstract_report.rb +109 -0
- data/lib/grafana_reporter/application/application.rb +229 -0
- data/lib/grafana_reporter/application/errors.rb +30 -0
- data/lib/grafana_reporter/asciidoctor/alerts_table_query.rb +99 -0
- data/lib/grafana_reporter/asciidoctor/annotations_table_query.rb +96 -0
- data/lib/grafana_reporter/asciidoctor/errors.rb +37 -0
- data/lib/grafana_reporter/asciidoctor/extensions/alerts_table_include_processor.rb +86 -0
- data/lib/grafana_reporter/asciidoctor/extensions/annotations_table_include_processor.rb +86 -0
- data/lib/grafana_reporter/asciidoctor/extensions/panel_image_block_macro.rb +67 -0
- data/lib/grafana_reporter/asciidoctor/extensions/panel_image_inline_macro.rb +65 -0
- data/lib/grafana_reporter/asciidoctor/extensions/panel_property_inline_macro.rb +58 -0
- data/lib/grafana_reporter/asciidoctor/extensions/panel_query_table_include_processor.rb +75 -0
- data/lib/grafana_reporter/asciidoctor/extensions/panel_query_value_inline_macro.rb +70 -0
- data/lib/grafana_reporter/asciidoctor/extensions/processor_mixin.rb +18 -0
- data/lib/grafana_reporter/asciidoctor/extensions/show_environment_include_processor.rb +41 -0
- data/lib/grafana_reporter/asciidoctor/extensions/show_help_include_processor.rb +202 -0
- data/lib/grafana_reporter/asciidoctor/extensions/sql_table_include_processor.rb +67 -0
- data/lib/grafana_reporter/asciidoctor/extensions/sql_value_inline_macro.rb +65 -0
- data/lib/grafana_reporter/asciidoctor/extensions/value_as_variable_include_processor.rb +57 -0
- data/lib/grafana_reporter/asciidoctor/panel_first_value_query.rb +32 -0
- data/lib/grafana_reporter/asciidoctor/panel_image_query.rb +23 -0
- data/lib/grafana_reporter/asciidoctor/panel_property_query.rb +43 -0
- data/lib/grafana_reporter/asciidoctor/panel_table_query.rb +36 -0
- data/lib/grafana_reporter/asciidoctor/query_mixin.rb +309 -0
- data/lib/grafana_reporter/asciidoctor/report.rb +159 -0
- data/lib/grafana_reporter/asciidoctor/sql_first_value_query.rb +34 -0
- data/lib/grafana_reporter/asciidoctor/sql_table_query.rb +32 -0
- data/lib/grafana_reporter/configuration.rb +326 -0
- data/lib/grafana_reporter/errors.rb +38 -0
- data/lib/grafana_reporter/logger/two_way_logger.rb +52 -0
- data/lib/ruby-grafana-reporter.rb +27 -0
- metadata +88 -0
@@ -0,0 +1,109 @@
|
|
1
|
+
module GrafanaReporter
|
2
|
+
# @abstract Override {#create_report} and {#progress}.
|
3
|
+
#
|
4
|
+
# This class is used to build a report on basis of a given configuration and
|
5
|
+
# template.
|
6
|
+
#
|
7
|
+
# Objects of this class are also stored in {Application::Application}, unless
|
8
|
+
# the retention time is over.
|
9
|
+
class AbstractReport
|
10
|
+
# @return [String] path to the template
|
11
|
+
attr_reader :template
|
12
|
+
|
13
|
+
# @return [Time] time, when the report generation started
|
14
|
+
attr_reader :start_time
|
15
|
+
|
16
|
+
# @return [Time] time, when the report generation ended
|
17
|
+
attr_reader :end_time
|
18
|
+
|
19
|
+
# @return [Logger] logger object used during report generation
|
20
|
+
attr_reader :logger
|
21
|
+
|
22
|
+
# @return [Boolean] true, if the report is or shall be cancelled
|
23
|
+
attr_reader :cancel
|
24
|
+
|
25
|
+
# @return [Boolen] true, if the report generation is finished (successfull or not)
|
26
|
+
attr_reader :done
|
27
|
+
|
28
|
+
# @param config [Configuration] configuration object
|
29
|
+
# @param template [String] path to the template to be used
|
30
|
+
# @param destination_file_or_path [String or File] path to the destination report or file object to use
|
31
|
+
# @param custom_attributes [Hash] custom attributes, which shall be merged with priority over the configuration
|
32
|
+
def initialize(config, template, destination_file_or_path = nil, custom_attributes = {})
|
33
|
+
@config = config
|
34
|
+
@logger = Logger::TwoWayDelegateLogger.new
|
35
|
+
@logger.additional_logger = @config.logger
|
36
|
+
@done = false
|
37
|
+
@template = template
|
38
|
+
@destination_file_or_path = destination_file_or_path
|
39
|
+
@custom_attributes = custom_attributes
|
40
|
+
@start_time = nil
|
41
|
+
@end_time = nil
|
42
|
+
@cancel = false
|
43
|
+
raise MissingTemplateError, @template.to_s unless File.exist?(@template.to_s)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Call to request cancelling the report generation.
|
47
|
+
# @return [void]
|
48
|
+
def cancel!
|
49
|
+
@cancel = true
|
50
|
+
logger.info('Cancelling report generation invoked.')
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [String] path to the report destination file
|
54
|
+
def path
|
55
|
+
@destination_file_or_path.respond_to?(:path) ? @destination_file_or_path.path : @destination_file_or_path
|
56
|
+
end
|
57
|
+
|
58
|
+
# Deletes the report file object.
|
59
|
+
# @return [void]
|
60
|
+
def delete_file
|
61
|
+
if @destination_file_or_path.is_a?(Tempfile)
|
62
|
+
@destination_file_or_path.unlink
|
63
|
+
elsif @destination_file_or_path.is_a?(File)
|
64
|
+
@destination_file_or_path.delete
|
65
|
+
end
|
66
|
+
@destination_file_or_path = nil
|
67
|
+
end
|
68
|
+
|
69
|
+
# @return [Float] time in seconds, that the report generation took
|
70
|
+
def execution_time
|
71
|
+
return nil if start_time.nil?
|
72
|
+
return end_time - start_time unless end_time.nil?
|
73
|
+
|
74
|
+
Time.now - start_time
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [Array] error messages during report generation.
|
78
|
+
def error
|
79
|
+
@error || []
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [String] status of the report, one of 'in progress', 'cancelled', 'died' or 'finished'.
|
83
|
+
def status
|
84
|
+
return 'cancelled' if done && cancel
|
85
|
+
return 'finished' if done && error.empty?
|
86
|
+
return 'died' if done && !error.empty?
|
87
|
+
|
88
|
+
'in progress'
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [String] string containing all messages ([Logger::Severity::DEBUG]) of the logger during report generation.
|
92
|
+
def full_log
|
93
|
+
logger.internal_messages
|
94
|
+
end
|
95
|
+
|
96
|
+
# @abstract
|
97
|
+
# Is being called to start the report generation.
|
98
|
+
# @return [void]
|
99
|
+
def create_report
|
100
|
+
raise NotImplementedError
|
101
|
+
end
|
102
|
+
|
103
|
+
# @abstract
|
104
|
+
# @return [Integer] number between 0 and 100, representing the current progress of the report creation.
|
105
|
+
def progress
|
106
|
+
raise NotImplementedError
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
module GrafanaReporter
|
2
|
+
# This module contains all classes, which are used by the grafana reporter
|
3
|
+
# application. The application is a set of classes, which allows to run the
|
4
|
+
# reporter in several ways.
|
5
|
+
#
|
6
|
+
# If you intend to use the reporter functionality, without the application,
|
7
|
+
# it might be helpful to not use the classes from here.
|
8
|
+
module Application
|
9
|
+
# This class contains the main application to run the grafana reporter.
|
10
|
+
#
|
11
|
+
# It can be run to test the grafana connection, render a single template
|
12
|
+
# or run as a service.
|
13
|
+
class Application
|
14
|
+
def initialize
|
15
|
+
@logger = ::Logger.new(STDERR, level: :unknown)
|
16
|
+
@reports = []
|
17
|
+
end
|
18
|
+
|
19
|
+
# Can be used to set a {Configuration} object to the application.
|
20
|
+
#
|
21
|
+
# This is mainly helpful in testing the application or in an
|
22
|
+
# integrated use.
|
23
|
+
# @param config {Configuration} configuration to be used by the application
|
24
|
+
# @return [void]
|
25
|
+
def config=(config)
|
26
|
+
@logger = config.logger || @logger
|
27
|
+
@config = config
|
28
|
+
end
|
29
|
+
|
30
|
+
# This is the main method, which is called, if the application is
|
31
|
+
# run in standalone mode.
|
32
|
+
# @param params [Array] normally the ARGV command line parameters
|
33
|
+
# @return [Integer] see {#run}
|
34
|
+
def configure_and_run(params = [])
|
35
|
+
config = GrafanaReporter::Configuration.new
|
36
|
+
config.logger.level = ::Logger::Severity::INFO
|
37
|
+
result = config.configure_by_command_line(params)
|
38
|
+
return result if result != 0
|
39
|
+
|
40
|
+
self.config = config
|
41
|
+
run
|
42
|
+
end
|
43
|
+
|
44
|
+
# Runs the application with the current set {Configuration} object.
|
45
|
+
# @return [Integer] value smaller than 0, if error. 0 if successfull
|
46
|
+
def run
|
47
|
+
begin
|
48
|
+
@config.validate
|
49
|
+
rescue ConfigurationError => e
|
50
|
+
puts e.message
|
51
|
+
return -2
|
52
|
+
end
|
53
|
+
|
54
|
+
case @config.mode
|
55
|
+
when Configuration::MODE_CONNECTION_TEST
|
56
|
+
res = Grafana::Grafana.new(@config.grafana_host(@config.test_instance), @config.grafana_api_key(@config.test_instance), logger: @logger).test_connection
|
57
|
+
puts res
|
58
|
+
|
59
|
+
when Configuration::MODE_SINGLE_RENDER
|
60
|
+
@config.report_class.new(@config, @config.template, @config.to_file).create_report
|
61
|
+
|
62
|
+
when Configuration::MODE_SERVICE
|
63
|
+
run_webserver
|
64
|
+
end
|
65
|
+
0
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def clean_outdated_temporary_reports
|
71
|
+
clean_time = Time.now - 60 * 60 * @config.report_retention
|
72
|
+
@reports.select { |report| report.done && clean_time > report.end_time }.each do |report|
|
73
|
+
@reports.delete(report).delete_file
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def run_webserver
|
78
|
+
# start webserver
|
79
|
+
server = TCPServer.new(@config.webserver_port)
|
80
|
+
@logger.info("Server listening on port #{@config.webserver_port}...")
|
81
|
+
|
82
|
+
@progress_reporter = Thread.new {}
|
83
|
+
|
84
|
+
loop do
|
85
|
+
# step 1) accept incoming connection
|
86
|
+
socket = server.accept
|
87
|
+
|
88
|
+
# step 2) print the request headers (separated by a blank line e.g. \r\n)
|
89
|
+
request = ''
|
90
|
+
line = ''
|
91
|
+
begin
|
92
|
+
until line == "\r\n"
|
93
|
+
line = socket.readline
|
94
|
+
request += line
|
95
|
+
end
|
96
|
+
rescue EOFError => e
|
97
|
+
@logger.debug("Webserver EOFError: #{e.message}")
|
98
|
+
end
|
99
|
+
|
100
|
+
begin
|
101
|
+
response = handle_request(request)
|
102
|
+
socket.write response
|
103
|
+
rescue WebserviceUnknownPathError => e
|
104
|
+
@logger.debug(e.message)
|
105
|
+
socket.write http_response(404, '', e.message)
|
106
|
+
rescue MissingTemplateError => e
|
107
|
+
@logger.error(e.message)
|
108
|
+
socket.write http_response(400, 'Bad Request', e.message)
|
109
|
+
rescue WebserviceGeneralRenderingError => e
|
110
|
+
@logger.fatal(e.message)
|
111
|
+
socket.write http_response(400, 'Bad Request', e.message)
|
112
|
+
rescue StandardError => e
|
113
|
+
@logger.fatal(e.message)
|
114
|
+
socket.write http_response(400, 'Bad Request', e.message)
|
115
|
+
ensure
|
116
|
+
socket.close
|
117
|
+
end
|
118
|
+
|
119
|
+
unless @progress_reporter.alive?
|
120
|
+
@progress_reporter = Thread.new do
|
121
|
+
running_reports = @reports.reject(&:done)
|
122
|
+
until running_reports.empty?
|
123
|
+
@logger.info("#{running_reports.length} report(s) in progress: #{running_reports.map { |report| (report.progress * 100).to_i.to_s + '% (running ' + report.execution_time.to_i.to_s + ' secs)' }.join(', ')}") unless running_reports.empty?
|
124
|
+
sleep 5
|
125
|
+
running_reports = @reports.reject(&:done)
|
126
|
+
end
|
127
|
+
# puts "no more running reports - stopping to report progress"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
clean_outdated_temporary_reports
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def handle_request(request)
|
136
|
+
raise WebserviceUnknownPathError, request.split("\r\n")[0] if request.nil?
|
137
|
+
raise WebserviceUnknownPathError, request.split("\r\n")[0] if request.split("\r\n")[0].nil?
|
138
|
+
|
139
|
+
query_string = request.split("\r\n")[0].gsub(%r{(?:[^\?]+[\?])(.*)(?: HTTP/.*)$}, '\1')
|
140
|
+
query_parameters = CGI.parse(query_string)
|
141
|
+
|
142
|
+
@logger.debug("Received request: #{request.split("\r\n")[0]}")
|
143
|
+
@logger.debug('query_parameters: ' + query_parameters.to_s)
|
144
|
+
|
145
|
+
# read URL parameters
|
146
|
+
attrs = {}
|
147
|
+
query_parameters.each do |k, v|
|
148
|
+
attrs[k] = v.length == 1 ? v[0] : v
|
149
|
+
end
|
150
|
+
|
151
|
+
if request.split("\r\n")[0] =~ %r{^GET /render[\? ]}
|
152
|
+
# build report
|
153
|
+
template_file = @config.templates_folder.to_s + attrs['var-template'].to_s + '.adoc'
|
154
|
+
|
155
|
+
file = Tempfile.new('gf_pdf_', @config.reports_folder)
|
156
|
+
begin
|
157
|
+
FileUtils.chmod('+r', file.path)
|
158
|
+
rescue StandardError => e
|
159
|
+
@logger.debug("File permissions could not be set for #{file.path}: #{e.message}")
|
160
|
+
end
|
161
|
+
|
162
|
+
report = @config.report_class.new(@config, template_file, file, attrs)
|
163
|
+
Thread.new do
|
164
|
+
report.create_report
|
165
|
+
end
|
166
|
+
@reports << report
|
167
|
+
|
168
|
+
return http_response(302, 'Found', nil, Location: "/view_report?report_id=#{report.object_id}")
|
169
|
+
|
170
|
+
elsif request.split("\r\n")[0] =~ %r{^GET /overview[\? ]}
|
171
|
+
# show overview for current reports
|
172
|
+
return get_reports_status_as_html(@reports)
|
173
|
+
|
174
|
+
elsif request.split("\r\n")[0] =~ %r{^GET /view_report[\? ]}
|
175
|
+
# view report if already available, or show status view
|
176
|
+
report = @reports.select { |r| r.object_id.to_s == attrs['report_id'].to_s }.first
|
177
|
+
raise WebserviceGeneralRenderingError, 'view_report has been called without valid id' if report.nil?
|
178
|
+
|
179
|
+
# show report status
|
180
|
+
return get_reports_status_as_html([report]) if !report.done || !report.error.empty?
|
181
|
+
|
182
|
+
# provide report
|
183
|
+
@logger.debug("Returning PDF report at #{report.path}")
|
184
|
+
content = File.read(report.path)
|
185
|
+
return http_response(200, 'OK', content, "Content-Type": 'application/pdf') if content.start_with?("%PDF")
|
186
|
+
# TODO properly provide file as zip
|
187
|
+
return http_response(200, 'OK', content, "Content-Type": 'application/octet-stream', "Content-Disposition": "attachment; filename=report.zip")
|
188
|
+
|
189
|
+
elsif request.split("\r\n")[0] =~ %r{^GET /cancel_report[\? ]}
|
190
|
+
# view report if already available, or show status view
|
191
|
+
report = @reports.select { |r| r.object_id.to_s == attrs['report_id'].to_s }.first
|
192
|
+
raise WebserviceGeneralRenderingError, 'cancel_report has been called without valid id' if report.nil?
|
193
|
+
|
194
|
+
report.cancel! unless report.done
|
195
|
+
|
196
|
+
# redirect to view_report page
|
197
|
+
return http_response(302, 'Found', nil, Location: "/view_report?report_id=#{report.object_id}")
|
198
|
+
|
199
|
+
elsif request.split("\r\n")[0] =~ %r{^GET /view_log[\? ]}
|
200
|
+
# view report if already available, or show status view
|
201
|
+
report = @reports.select { |r| r.object_id.to_s == attrs['report_id'].to_s }.first
|
202
|
+
raise WebserviceGeneralRenderingError, 'view_log has been called without valid id' if report.nil?
|
203
|
+
|
204
|
+
content = report.full_log
|
205
|
+
|
206
|
+
return http_response(200, 'OK', content, "Content-Type": 'text/plain')
|
207
|
+
end
|
208
|
+
|
209
|
+
raise WebserviceUnknownPathError, request.split("\r\n")[0]
|
210
|
+
end
|
211
|
+
|
212
|
+
def get_reports_status_as_html(reports)
|
213
|
+
i = reports.length
|
214
|
+
|
215
|
+
content = '<html><head></head><body><table><thead><th>#</th><th>Start Time</th><th>End Time</th><th>Template</th><th>Execution time</th><th>Status</th><th>Error</th><th>Action</th></thead>' +
|
216
|
+
reports.reverse.map do |report|
|
217
|
+
"<tr><td>#{(i -= 1)}</td><td>#{report.start_time}</td><td>#{report.end_time}</td><td>#{report.template}</td><td>#{report.execution_time.to_i} secs</td><td>#{report.status} (#{(report.progress * 100).to_i}%)</td><td>#{report.error.join('<br>')}</td><td>#{!report.done && !report.cancel ? "<a href=\"/cancel_report?report_id=#{report.object_id}\">Cancel</a> " : ''}#{(report.status == 'finished') || (report.status == 'cancelled') ? "<a href=\"/view_report?report_id=#{report.object_id}\">View</a> " : ' '}<a href=\"/view_log?report_id=#{report.object_id}\">Log</a></td></tr>"
|
218
|
+
end.join('') +
|
219
|
+
'</table></body></html>'
|
220
|
+
|
221
|
+
http_response(200, 'OK', content, "Content-Type": 'text/html')
|
222
|
+
end
|
223
|
+
|
224
|
+
def http_response(code, text, body, opts = {})
|
225
|
+
"HTTP/1.1 #{code} #{text}\r\n#{opts.map { |k, v| "#{k}: #{v}" }.join("\r\n")}#{body ? "\r\nContent-Length: #{body.to_s.bytesize}" : ''}\r\n\r\n#{body}"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module GrafanaReporter
|
2
|
+
module Application
|
3
|
+
# General grafana application error, from which the specific errors
|
4
|
+
# inherit.
|
5
|
+
class ApplicationError < GrafanaReporterError
|
6
|
+
end
|
7
|
+
|
8
|
+
# Thrown if a non existing template has been specified.
|
9
|
+
class MissingTemplateError < ApplicationError
|
10
|
+
def initialize(template)
|
11
|
+
super("Given report template '#{template}' is not a valid template.")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Thrown, if a webservice request has been requested, which could not be
|
16
|
+
# handled.
|
17
|
+
class WebserviceUnknownPathError < ApplicationError
|
18
|
+
def initialize(request)
|
19
|
+
super("Request '#{request}' calls an unknown path for this webservice.")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Thrown, if an internal error appeared during creation of the report.
|
24
|
+
class WebserviceGeneralRenderingError < ApplicationError
|
25
|
+
def initialize(error)
|
26
|
+
super("Could not render report because of internal error: #{error}")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require_relative 'query_mixin'
|
2
|
+
|
3
|
+
module GrafanaReporter
|
4
|
+
module Asciidoctor
|
5
|
+
# This class is used to query alerts from grafana.
|
6
|
+
class AlertsTableQuery < Grafana::AbstractQuery
|
7
|
+
include QueryMixin
|
8
|
+
|
9
|
+
# @option opts [Grafana::Dashboard] :dashboard dashboard, if alerts shall be filtered for a dashboard
|
10
|
+
# @option opts [Grafnaa::Oanel] :panel panel, if alerts shall be filtered for a panel
|
11
|
+
def initialize(opts = {})
|
12
|
+
super()
|
13
|
+
|
14
|
+
@dashboard = opts[:dashboard]
|
15
|
+
@panel = opts[:panel]
|
16
|
+
@dashboard = @panel.dashboard if @panel
|
17
|
+
|
18
|
+
extract_dashboard_variables(@dashboard) if @dashboard
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [String] URL for querying alerts
|
22
|
+
def url
|
23
|
+
'/api/alerts' + url_parameters
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Hash] empty hash object
|
27
|
+
def request
|
28
|
+
{}
|
29
|
+
end
|
30
|
+
|
31
|
+
# Check if mandatory {Grafana::Variable} +columns+ is specified in variables.
|
32
|
+
#
|
33
|
+
# The value of the +columns+ variable has to be a comma separated list of column titles, which
|
34
|
+
# need to be included in the following list:
|
35
|
+
# - limit
|
36
|
+
# - dashboardId
|
37
|
+
# - panelId
|
38
|
+
# - query
|
39
|
+
# - state
|
40
|
+
# - folderId
|
41
|
+
# - dashboardQuery
|
42
|
+
# - dashboardTag
|
43
|
+
# @return [void]
|
44
|
+
def pre_process(_grafana)
|
45
|
+
raise MissingMandatoryAttributeError, 'columns' unless @variables['columns']
|
46
|
+
|
47
|
+
@from = translate_date(@from, @variables['grafana-report-timestamp'], false)
|
48
|
+
@to = translate_date(@to, @variables['grafana-report-timestamp'], true)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Filter the query result for the given columns and sets the result in the preformatted SQL
|
52
|
+
# result stlye.
|
53
|
+
|
54
|
+
# Additionally it applies {QueryMixin#format_columns}, {QueryMixin#replace_values} and
|
55
|
+
# {QueryMixin#filter_columns}.
|
56
|
+
# @return [void]
|
57
|
+
def post_process
|
58
|
+
# extract data from returned json
|
59
|
+
result = JSON.parse(@result.body)
|
60
|
+
content = []
|
61
|
+
begin
|
62
|
+
result.each { |item| content << item.fetch_values(*@variables['columns'].raw_value.split(',')) }
|
63
|
+
rescue KeyError => e
|
64
|
+
raise MalformedAttributeContentError.new(e.message, 'columns', @variables['columns'])
|
65
|
+
end
|
66
|
+
|
67
|
+
result = {}
|
68
|
+
result[:header] = [@variables['columns'].raw_value.split(',')]
|
69
|
+
result[:content] = content
|
70
|
+
|
71
|
+
result = format_columns(result, @variables['format'])
|
72
|
+
result = replace_values(result, @variables.select { |k, _v| k =~ /^replace_values_\d+/ })
|
73
|
+
result = filter_columns(result, @variables['filter_columns'])
|
74
|
+
if @variables['filter_column']
|
75
|
+
@report.logger.warn("DEPRECATED: Call of no longer supported function 'filter_column' has been found. Rename to 'filter_columns'")
|
76
|
+
result = filter_columns(result, @variables['filter_column'])
|
77
|
+
end
|
78
|
+
|
79
|
+
@result = result[:content].map { |row| '| ' + row.map { |item| item.to_s.gsub('|', '\\|') }.join(' | ') }
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def url_parameters
|
85
|
+
url_vars = {}
|
86
|
+
url_vars['dashboardId'] = ::Grafana::Variable.new(@dashboard.id) if @dashboard
|
87
|
+
url_vars['panelId'] = ::Grafana::Variable.new(@panel.id) if @panel
|
88
|
+
|
89
|
+
url_vars.merge!(variables.select { |k, _v| k =~ /^(?:limit|dashboardId|panelId|query|state|folderId|dashboardQuery|dashboardTag)/ })
|
90
|
+
url_vars['from'] = ::Grafana::Variable.new(@from) if @from
|
91
|
+
url_vars['to'] = ::Grafana::Variable.new(@to) if @to
|
92
|
+
url_params = URI.encode_www_form(url_vars.map { |k, v| [k, v.raw_value.to_s] })
|
93
|
+
return '' if url_params.empty?
|
94
|
+
|
95
|
+
'?' + url_params
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|