ruby-grafana-reporter 0.4.5 → 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 +2 -2
- data/lib/VERSION.rb +2 -2
- data/lib/grafana/abstract_datasource.rb +3 -0
- data/lib/grafana/dashboard.rb +6 -1
- data/lib/grafana/errors.rb +2 -1
- data/lib/grafana/grafana.rb +24 -0
- data/lib/grafana/grafana_environment_datasource.rb +56 -0
- data/lib/grafana/image_rendering_datasource.rb +5 -1
- data/lib/grafana/influxdb_datasource.rb +0 -2
- data/lib/grafana/sql_datasource.rb +2 -0
- data/lib/grafana/variable.rb +46 -23
- data/lib/grafana/webrequest.rb +1 -0
- data/lib/grafana_reporter/abstract_query.rb +31 -23
- data/lib/grafana_reporter/abstract_report.rb +2 -0
- data/lib/grafana_reporter/abstract_table_format_strategy.rb +44 -4
- data/lib/grafana_reporter/alerts_table_query.rb +2 -1
- data/lib/grafana_reporter/annotations_table_query.rb +2 -1
- data/lib/grafana_reporter/application/webservice.rb +8 -4
- data/lib/grafana_reporter/asciidoctor/adoc_plain_table_format_strategy.rb +11 -9
- data/lib/grafana_reporter/asciidoctor/help.rb +24 -12
- data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +2 -4
- data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +2 -4
- data/lib/grafana_reporter/asciidoctor/show_environment_include_processor.rb +37 -6
- 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 +3 -1
- data/lib/grafana_reporter/csv_table_format_strategy.rb +11 -9
- data/lib/grafana_reporter/demo_report_wizard.rb +3 -6
- data/lib/grafana_reporter/panel_image_query.rb +0 -1
- data/lib/grafana_reporter/query_value_query.rb +7 -1
- data/lib/grafana_reporter/reporter_environment_datasource.rb +24 -0
- data/lib/ruby_grafana_reporter.rb +0 -11
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '06912db0a0635560dca75c00c01c047deac42741c7c6ba2ee717cbccdf26cb19'
|
4
|
+
data.tar.gz: 8d09e509266737bf1b73c3f6ad5c03255de461c116a6e5ec7b45bfaef56520b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 66e81686e253f8e98101f97b0b08db97f955cd1c274f2ed683f597b675c228adf198cd2c66f9ce3df05506a760564643e43b94c40fa952b13748122ecc8d6890
|
7
|
+
data.tar.gz: 916ec07231acf19c20b0f01d7bfe9a47d5825a8e0c3a6c67a9fa615e2b993343d1506fb129cc72b66e2be9b02102c15e338a6e8b5746286494de4164a9678c38
|
data/README.md
CHANGED
@@ -30,7 +30,7 @@ professional reporting functionality. And this is, where the ruby grafana report
|
|
30
30
|
steps in.
|
31
31
|
|
32
32
|
The key functionality of the reporter is to capture data and images from grafana
|
33
|
-
dashboards and to use it in your custom templates to finally create reports in PDF
|
33
|
+
dashboards and to use it in your custom templates to finally create reports in PDF
|
34
34
|
(default), HTML, or any other format.
|
35
35
|
|
36
36
|
By default (an extended version of) Asciidoctor is enabled as template language.
|
@@ -65,7 +65,7 @@ Database | Image rendering | Raw queries | Composed queries
|
|
65
65
|
------------------------- | :-------------: | :-----------: | :------------:
|
66
66
|
all SQL based datasources | supported | supported | supported
|
67
67
|
Graphite | supported | supported | supported
|
68
|
-
InfluxDB | supported | supported |
|
68
|
+
InfluxDB | supported | supported | supported
|
69
69
|
Prometheus | supported | supported | n/a in grafana
|
70
70
|
other datasources | supported | not supported | not supported
|
71
71
|
|
data/lib/VERSION.rb
CHANGED
@@ -128,6 +128,9 @@ module Grafana
|
|
128
128
|
repeat_count += 1
|
129
129
|
|
130
130
|
variables.each do |name, variable|
|
131
|
+
# do not replace with non grafana variables
|
132
|
+
next unless name =~ /^var-/
|
133
|
+
|
131
134
|
# only set ticks if value is string
|
132
135
|
var_name = name.gsub(/^var-/, '')
|
133
136
|
next unless var_name =~ /^\w+$/
|
data/lib/grafana/dashboard.rb
CHANGED
@@ -35,6 +35,11 @@ module Grafana
|
|
35
35
|
@model['uid']
|
36
36
|
end
|
37
37
|
|
38
|
+
# @return [String] dashboard title
|
39
|
+
def title
|
40
|
+
@model['title']
|
41
|
+
end
|
42
|
+
|
38
43
|
# @return [Panel] panel for the specified ID
|
39
44
|
def panel(id)
|
40
45
|
panels = @panels.select { |item| item.field('id') == id.to_i }
|
@@ -53,7 +58,7 @@ module Grafana
|
|
53
58
|
list = @model['templating']['list']
|
54
59
|
return unless list.is_a? Array
|
55
60
|
|
56
|
-
list.each { |item| @variables << Variable.new(item) }
|
61
|
+
list.each { |item| @variables << Variable.new(item, self) }
|
57
62
|
end
|
58
63
|
|
59
64
|
# read panels
|
data/lib/grafana/errors.rb
CHANGED
@@ -53,7 +53,8 @@ module Grafana
|
|
53
53
|
class ImageCouldNotBeRenderedError < GrafanaError
|
54
54
|
def initialize(panel)
|
55
55
|
super("The specified panel '#{panel.id}' from dashboard '#{panel.dashboard.id}' could not be "\
|
56
|
-
'rendered to an image.'
|
56
|
+
'rendered to an image. Check if rendering is possible manually by selecting "Share" and then '\
|
57
|
+
'"Direct link rendered image" from a panel\'s options menu.')
|
57
58
|
end
|
58
59
|
end
|
59
60
|
|
data/lib/grafana/grafana.rb
CHANGED
@@ -25,6 +25,30 @@ module Grafana
|
|
25
25
|
initialize_datasources unless @base_uri.empty?
|
26
26
|
end
|
27
27
|
|
28
|
+
# @return [Hash] Information about the current organization
|
29
|
+
def organization
|
30
|
+
return @organization if @organization
|
31
|
+
|
32
|
+
response = prepare_request({ relative_url: '/api/org/' }).execute
|
33
|
+
if response.is_a?(Net::HTTPOK)
|
34
|
+
@organization = JSON.parse(response.body)
|
35
|
+
end
|
36
|
+
|
37
|
+
@organization
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [String] grafana version
|
41
|
+
def version
|
42
|
+
return @version if @version
|
43
|
+
|
44
|
+
response = prepare_request({ relative_url: '/api/health' }).execute
|
45
|
+
if response.is_a?(Net::HTTPOK)
|
46
|
+
@version = JSON.parse(response.body)['version']
|
47
|
+
end
|
48
|
+
|
49
|
+
@version
|
50
|
+
end
|
51
|
+
|
28
52
|
# Used to test a connection to the grafana instance.
|
29
53
|
#
|
30
54
|
# Running this function also determines, if the API configured here has Admin or NON-Admin privileges,
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grafana
|
4
|
+
# Implements a datasource to return environment related information about the grafana instance in a tabular format.
|
5
|
+
class GrafanaEnvironmentDatasource < ::Grafana::AbstractDatasource
|
6
|
+
# +:raw_query+ needs to contain a Hash with the following structure:
|
7
|
+
#
|
8
|
+
# {
|
9
|
+
# grafana: {Grafana} object to query
|
10
|
+
# mode: 'general' (default) or 'dashboards' for receiving different environment information
|
11
|
+
# }
|
12
|
+
# @see AbstractDatasource#request
|
13
|
+
def request(query_description)
|
14
|
+
raise MissingSqlQueryError if query_description[:raw_query].nil?
|
15
|
+
raw_query = {mode: 'general'}.merge(query_description[:raw_query])
|
16
|
+
|
17
|
+
return dashboards_data(raw_query[:grafana]) if raw_query[:mode] == 'dashboards'
|
18
|
+
|
19
|
+
general_data(raw_query[:grafana])
|
20
|
+
end
|
21
|
+
|
22
|
+
# @see AbstractDatasource#default_variable_format
|
23
|
+
def default_variable_format
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
# @see AbstractDatasource#name
|
28
|
+
def name
|
29
|
+
self.class.to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def general_data(grafana)
|
35
|
+
{
|
36
|
+
header: ['Version', 'Organization Name', 'Organization ID', 'Access permissions'],
|
37
|
+
content: [[grafana.version,
|
38
|
+
grafana.organization['name'],
|
39
|
+
grafana.organization['id'],
|
40
|
+
grafana.test_connection]]
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def dashboards_data(grafana)
|
45
|
+
content = []
|
46
|
+
grafana.dashboard_ids.each do |id|
|
47
|
+
content << [id, grafana.dashboard(id).title, grafana.dashboard(id).panels.length]
|
48
|
+
end
|
49
|
+
|
50
|
+
{
|
51
|
+
header: ['Dashboard ID', 'Dashboard Name', '# Panels'],
|
52
|
+
content: content
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -10,12 +10,16 @@ module Grafana
|
|
10
10
|
# }
|
11
11
|
# @see AbstractDatasource#request
|
12
12
|
def request(query_description)
|
13
|
+
panel = query_description[:raw_query][:panel]
|
14
|
+
|
13
15
|
webrequest = query_description[:prepared_request]
|
14
|
-
webrequest.relative_url =
|
16
|
+
webrequest.relative_url = panel.render_url + url_params(query_description)
|
15
17
|
webrequest.options.merge!({ accept: 'image/png' })
|
16
18
|
|
17
19
|
result = webrequest.execute
|
18
20
|
|
21
|
+
raise ImageCouldNotBeRenderedError, panel if result.body.include?('<html')
|
22
|
+
|
19
23
|
{ header: ['image'], content: [result.body] }
|
20
24
|
end
|
21
25
|
|
@@ -24,8 +24,6 @@ module Grafana
|
|
24
24
|
query = query.gsub(/\$timeFilter(?=\W|$)/, "time >= #{query_description[:from]}ms and time <= #{query_description[:to]}ms")
|
25
25
|
|
26
26
|
# replace grafana variables $__interval and $__interval_ms in query
|
27
|
-
# TODO: influx datasource currently uses a fixed values of 1000 width for interval variables specified in a query - it should be possible to calculate this according to grafana
|
28
|
-
# TODO: check where calculation and replacement of interval variable should take place
|
29
27
|
query = query.gsub(/\$(?:__)?interval(?=\W|$)/, "#{((query_description[:to].to_i - query_description[:from].to_i) / 1000 / 1000).to_i}s")
|
30
28
|
query = query.gsub(/\$(?:__)?interval_ms(?=\W|$)/, "#{((query_description[:to].to_i - query_description[:from].to_i) / 1000).to_i}")
|
31
29
|
|
data/lib/grafana/variable.rb
CHANGED
@@ -29,15 +29,14 @@ module Grafana
|
|
29
29
|
|
30
30
|
# @param config_or_value [Hash, Object] configuration hash of a variable out of an {Dashboard} instance
|
31
31
|
# or a value of any kind.
|
32
|
-
|
32
|
+
# @param dashboard [Dashboard] parent dashboard, if applicable; especially needed for query variable
|
33
|
+
# evaluation.
|
34
|
+
def initialize(config_or_value, dashboard = nil)
|
33
35
|
if config_or_value.is_a? Hash
|
36
|
+
@dashboard = dashboard
|
34
37
|
@config = config_or_value
|
35
38
|
@name = @config['name']
|
36
|
-
|
37
|
-
unless @config['current'].nil?
|
38
|
-
@raw_value = @config['current']['value']
|
39
|
-
@text = @config['current']['text']
|
40
|
-
end
|
39
|
+
init_values
|
41
40
|
else
|
42
41
|
@config = {}
|
43
42
|
@raw_value = config_or_value
|
@@ -68,14 +67,19 @@ module Grafana
|
|
68
67
|
if value == '$__all'
|
69
68
|
if !@config['options'].empty?
|
70
69
|
# this query contains predefined values, so capture them and format the values accordingly
|
71
|
-
# this happens either
|
72
|
-
value = @config['options'].map { |item| item['value'] }
|
70
|
+
# this happens either for type='custom' or type 'query', if it is never updated
|
71
|
+
value = @config['options'].reject { |item| item['value'] == '$__all' }.map { |item| item['value'] }
|
73
72
|
|
74
73
|
elsif @config['type'] == 'query' && !@config['query'].empty?
|
75
|
-
#
|
76
|
-
#
|
77
|
-
|
78
|
-
|
74
|
+
# variables in this configuration are not stored in grafana, i.e. if all is selected here,
|
75
|
+
# the values have to be fetched from the datasource
|
76
|
+
query = ::GrafanaReporter::QueryValueQuery.new(@dashboard)
|
77
|
+
query.datasource = @dashboard.grafana.datasource_by_name(@config['datasource'])
|
78
|
+
query.variables['result_type'] = Variable.new('object')
|
79
|
+
query.raw_query = @config['query']
|
80
|
+
result = query.execute
|
81
|
+
|
82
|
+
value = result[:content].map { |item| item[0].to_s }
|
79
83
|
|
80
84
|
else
|
81
85
|
# TODO: add support for variable type: 'datasource' and 'adhoc'
|
@@ -149,8 +153,10 @@ module Grafana
|
|
149
153
|
value.gsub(%r{[" |=/\\]}, '\\\\\0')
|
150
154
|
|
151
155
|
when /^date(?::(?<format>.*))?$/
|
152
|
-
|
153
|
-
|
156
|
+
if multi? && value.is_a?(Array)
|
157
|
+
raise GrafanaError, "Date format cannot be specified for a variable containing an array of values"
|
158
|
+
end
|
159
|
+
Variable.format_as_date(value, Regexp.last_match(1))
|
154
160
|
|
155
161
|
when ''
|
156
162
|
# default
|
@@ -168,8 +174,9 @@ module Grafana
|
|
168
174
|
end
|
169
175
|
end
|
170
176
|
|
171
|
-
# @return [Boolean] true, if the value can contain multiple selections, i.e. can contain an Array
|
177
|
+
# @return [Boolean] true, if the value can contain multiple selections, i.e. can contain an Array or does contain all
|
172
178
|
def multi?
|
179
|
+
return true if @raw_value == '$__all'
|
173
180
|
return @config['multi'] unless @config['multi'].nil?
|
174
181
|
|
175
182
|
@raw_value.is_a? Array
|
@@ -187,12 +194,13 @@ module Grafana
|
|
187
194
|
@text = new_text
|
188
195
|
end
|
189
196
|
|
190
|
-
|
191
|
-
|
192
|
-
# Realize time formatting according
|
197
|
+
# Applies the date format according
|
193
198
|
# {https://grafana.com/docs/grafana/latest/variables/variable-types/global-variables/#__from-and-__to}
|
194
|
-
# and {https://momentjs.com/docs/#/displaying/}.
|
195
|
-
|
199
|
+
# and {https://momentjs.com/docs/#/displaying/} to a given value.
|
200
|
+
# @param value [String] time as milliseconds to be formatted
|
201
|
+
# @param format [String] format string in which the time value shall be returned
|
202
|
+
# @return [String] time converted to the specified time format
|
203
|
+
def self.format_as_date(value, format)
|
196
204
|
return (Float(value) / 1000).to_i.to_s if format == 'seconds'
|
197
205
|
return Time.at((Float(value) / 1000).to_i).utc.iso8601(3) if !format || (format == 'iso')
|
198
206
|
|
@@ -203,13 +211,12 @@ module Grafana
|
|
203
211
|
tmp = work_string.scan(/^(?:M{1,4}|D{1,4}|d{1,4}|e|E|w{1,2}|W{1,2}|Y{4}|Y{2}|A|a|H{1,2}|
|
204
212
|
h{1,2}|k{1,2}|m{1,2}|s{1,2}|S+|X)/x)
|
205
213
|
|
206
|
-
# TODO: add test for sub! and switch to non-modifying frozen string action
|
207
214
|
if tmp.empty?
|
208
215
|
matches << work_string[0]
|
209
|
-
work_string.sub
|
216
|
+
work_string = work_string.sub(/^#{work_string[0]}/, '')
|
210
217
|
else
|
211
218
|
matches << tmp[0]
|
212
|
-
work_string.sub
|
219
|
+
work_string = work_string.sub(/^#{tmp[0]}/, '')
|
213
220
|
end
|
214
221
|
end
|
215
222
|
|
@@ -221,5 +228,21 @@ module Grafana
|
|
221
228
|
|
222
229
|
Time.at((Float(value) / 1000).to_i).strftime(format_string)
|
223
230
|
end
|
231
|
+
|
232
|
+
private
|
233
|
+
|
234
|
+
def init_values
|
235
|
+
case @config['type']
|
236
|
+
when 'constant'
|
237
|
+
self.raw_value = @config['query']
|
238
|
+
|
239
|
+
else
|
240
|
+
if !@config['current'].nil?
|
241
|
+
self.raw_value = @config['current']['value']
|
242
|
+
else
|
243
|
+
raise GrafanaError.new("Grafana variable with type '#{@config['type']}' and name '#{@config['name']}' could not be handled properly. Please raise a ticket.")
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
224
247
|
end
|
225
248
|
end
|
data/lib/grafana/webrequest.rb
CHANGED
@@ -50,6 +50,7 @@ module Grafana
|
|
50
50
|
@logger.debug("Requesting #{uri} with '#{@options[:body]}' and timeout '#{timeout}'")
|
51
51
|
response = @http.request(request)
|
52
52
|
@logger.debug("Received response #{response}")
|
53
|
+
@logger.debug("HTTP response body: #{response.body}") unless response.code =~ /^2.*/
|
53
54
|
|
54
55
|
response
|
55
56
|
end
|
@@ -44,6 +44,7 @@ module GrafanaReporter
|
|
44
44
|
else
|
45
45
|
raise GrafanaReporterError, "Internal error in AbstractQuery: given object is of type #{grafana_obj.class.name}, which is not supported"
|
46
46
|
end
|
47
|
+
@logger = @grafana ? @grafana.logger : ::Logger.new($stderr, level: :info)
|
47
48
|
@variables = {}
|
48
49
|
@variables['from'] = Grafana::Variable.new(nil)
|
49
50
|
@variables['to'] = Grafana::Variable.new(nil)
|
@@ -79,7 +80,7 @@ module GrafanaReporter
|
|
79
80
|
raise DatasourceNotSupportedError.new(@datasource, self) if @datasource.is_a?(Grafana::UnsupportedDatasource)
|
80
81
|
|
81
82
|
begin
|
82
|
-
@result = @datasource.request(from: from, to: to, raw_query: raw_query, variables:
|
83
|
+
@result = @datasource.request(from: from, to: to, raw_query: raw_query, variables: @variables,
|
83
84
|
prepared_request: @grafana.prepare_request, timeout: timeout)
|
84
85
|
rescue ::Grafana::GrafanaError
|
85
86
|
# grafana errors will be directly passed through
|
@@ -143,6 +144,8 @@ module GrafanaReporter
|
|
143
144
|
#
|
144
145
|
# Multiple columns may be filtered. Therefore the column titles have to be named in the
|
145
146
|
# {Grafana::Variable#raw_value} and have to be separated by +,+ (comma).
|
147
|
+
#
|
148
|
+
# Commas can be used in a format string, but need to be escaped by using +_,+.
|
146
149
|
# @param result [Hash] preformatted sql hash, (see {Grafana::AbstractDatasource#request})
|
147
150
|
# @param filter_columns_variable [Grafana::Variable] column names, which shall be removed in the query result
|
148
151
|
# @return [Hash] filtered query result
|
@@ -150,8 +153,8 @@ module GrafanaReporter
|
|
150
153
|
return result unless filter_columns_variable
|
151
154
|
|
152
155
|
filter_columns = filter_columns_variable.raw_value
|
153
|
-
filter_columns.split(
|
154
|
-
pos = result[:header].index(filter_column)
|
156
|
+
filter_columns.split(/(?<!_),/).each do |filter_column|
|
157
|
+
pos = result[:header].index(filter_column.gsub("_,", ","))
|
155
158
|
|
156
159
|
unless pos.nil?
|
157
160
|
result[:header].delete_at(pos)
|
@@ -167,23 +170,33 @@ module GrafanaReporter
|
|
167
170
|
# The formatting will be applied separately for every column. Therefore the column formats have to be named
|
168
171
|
# in the {Grafana::Variable#raw_value} and have to be separated by +,+ (comma). If no value is specified for
|
169
172
|
# a column, no change will happen.
|
173
|
+
#
|
174
|
+
# It is also possible to format milliseconds as dates by specifying date formats, e.g. +date:iso+. It is
|
175
|
+
# possible to use any date format according
|
176
|
+
# {https://grafana.com/docs/grafana/latest/variables/variable-types/global-variables/#from-and-to}
|
177
|
+
#
|
178
|
+
# Commas can be used in a format string, but need to be escaped by using +_,+.
|
170
179
|
# @param result [Hash] preformatted sql hash, (see {Grafana::AbstractDatasource#request})
|
171
180
|
# @param formats [Grafana::Variable] formats, which shall be applied to the columns in the query result
|
172
181
|
# @return [Hash] formatted query result
|
173
182
|
def format_columns(result, formats)
|
174
183
|
return result unless formats
|
175
184
|
|
176
|
-
formats.text.split(
|
177
|
-
format = formats.text.split(
|
185
|
+
formats.text.split(/(?<!_),/).each_index do |i|
|
186
|
+
format = formats.text.split(/(?<!_),/)[i].gsub("_,", ",")
|
178
187
|
next if format.empty?
|
179
188
|
|
180
189
|
result[:content].map do |row|
|
181
190
|
next unless row.length > i
|
182
191
|
|
183
192
|
begin
|
184
|
-
|
193
|
+
if format =~ /^date:/
|
194
|
+
row[i] = ::Grafana::Variable.format_as_date(row[i], format.sub(/^date:/, '')) if row[i]
|
195
|
+
else
|
196
|
+
row[i] = format % row[i] if row[i]
|
197
|
+
end
|
185
198
|
rescue StandardError => e
|
186
|
-
@
|
199
|
+
@logger.error(e.message)
|
187
200
|
row[i] = e.message
|
188
201
|
end
|
189
202
|
end
|
@@ -248,7 +261,7 @@ module GrafanaReporter
|
|
248
261
|
begin
|
249
262
|
row[i] = row[i].to_s.gsub(/#{k}/, v) if row[i].to_s =~ /#{k}/
|
250
263
|
rescue StandardError => e
|
251
|
-
@
|
264
|
+
@logger.error(e.message)
|
252
265
|
row[i] = e.message
|
253
266
|
end
|
254
267
|
|
@@ -273,7 +286,7 @@ module GrafanaReporter
|
|
273
286
|
end
|
274
287
|
end
|
275
288
|
rescue StandardError => e
|
276
|
-
@
|
289
|
+
@logger.error(e.message)
|
277
290
|
row[i] = e.message
|
278
291
|
end
|
279
292
|
end
|
@@ -298,17 +311,19 @@ module GrafanaReporter
|
|
298
311
|
# @option opts [Grafana::Variable] :column_divider requested row divider for the result table, only to be used with table_formatter `adoc_deprecated`
|
299
312
|
# @option opts [Grafana::Variable] :include_headline specifies if table should contain headline, defaults to false
|
300
313
|
# @option opts [Grafana::Variable] :table_formatter specifies which formatter shall be used, defaults to 'csv'
|
314
|
+
# @option opts [Grafana::Variable] :transposed specifies whether the result table is transposed
|
301
315
|
# @return [String] table in custom output format
|
302
316
|
def format_table_output(result, opts)
|
303
317
|
opts = { include_headline: Grafana::Variable.new('false'),
|
304
318
|
table_formatter: Grafana::Variable.new('csv'),
|
305
319
|
row_divider: Grafana::Variable.new('| '),
|
306
|
-
column_divider: Grafana::Variable.new(' | ')
|
320
|
+
column_divider: Grafana::Variable.new(' | '),
|
321
|
+
transpose: Grafana::Variable.new('false') }.merge(opts.delete_if {|_k, v| v.nil? })
|
307
322
|
|
308
323
|
if opts[:table_formatter].raw_value == 'adoc_deprecated'
|
309
|
-
@
|
310
|
-
|
311
|
-
|
324
|
+
@logger.warn("You are using deprecated 'table_formatter' named 'adoc_deprecated', which will be "\
|
325
|
+
"removed in a future version. Start using 'adoc_plain' or register your own "\
|
326
|
+
"implementation of AbstractTableFormatStrategy.")
|
312
327
|
return result[:content].map do |row|
|
313
328
|
opts[:row_divider].raw_value + row.map do |item|
|
314
329
|
item.to_s.gsub('|', '\\|')
|
@@ -316,7 +331,7 @@ module GrafanaReporter
|
|
316
331
|
end.join("\n")
|
317
332
|
end
|
318
333
|
|
319
|
-
AbstractTableFormatStrategy.get(opts[:table_formatter].raw_value).format(result, opts[:include_headline].raw_value.downcase == 'true')
|
334
|
+
AbstractTableFormatStrategy.get(opts[:table_formatter].raw_value).format(result, opts[:include_headline].raw_value.downcase == 'true', opts[:transpose].raw_value.downcase == 'true')
|
320
335
|
end
|
321
336
|
|
322
337
|
# Used to translate the relative date strings used by grafana, e.g. +now-5d/w+ to the
|
@@ -334,7 +349,7 @@ module GrafanaReporter
|
|
334
349
|
# @param timezone [Grafana::Variable] timezone to use, if not system timezone
|
335
350
|
# @return [String] translated date as timestamp string
|
336
351
|
def translate_date(orig_date, report_time, is_to_time, timezone = nil)
|
337
|
-
@
|
352
|
+
@logger.warn("#translate_date has been called without 'report_time' - using current time as fallback.") unless report_time
|
338
353
|
report_time ||= ::Grafana::Variable.new(Time.now.to_s)
|
339
354
|
orig_date = orig_date.raw_value if orig_date.is_a?(Grafana::Variable)
|
340
355
|
|
@@ -405,8 +420,7 @@ module GrafanaReporter
|
|
405
420
|
def datasource_response_valid?
|
406
421
|
return false if @result.nil?
|
407
422
|
return false unless @result.is_a?(Hash)
|
408
|
-
|
409
|
-
return true if @result.empty?
|
423
|
+
return false if @result.empty?
|
410
424
|
return false unless @result.key?(:header)
|
411
425
|
return false unless @result.key?(:content)
|
412
426
|
return false unless @result[:header].is_a?(Array)
|
@@ -415,12 +429,6 @@ module GrafanaReporter
|
|
415
429
|
true
|
416
430
|
end
|
417
431
|
|
418
|
-
# @return [Hash<String, Variable>] all grafana variables stored in this query, i.e. the variable name
|
419
|
-
# is prefixed with +var-+
|
420
|
-
def grafana_variables
|
421
|
-
@variables.select { |k, _v| k =~ /^var-.+/ }
|
422
|
-
end
|
423
|
-
|
424
432
|
def delta_date(date, delta_count, time_letter)
|
425
433
|
# substract specified time
|
426
434
|
case time_letter
|
@@ -143,6 +143,8 @@ module GrafanaReporter
|
|
143
143
|
notify(:on_before_create)
|
144
144
|
@start_time = Time.new
|
145
145
|
logger.info("Report started at #{@start_time}")
|
146
|
+
logger.info("You are running ruby-grafana-reporter version #{GRAFANA_REPORTER_VERSION.join('.')}.")
|
147
|
+
logger.info("A newer version is released. Check out https://github.com/divinity666/ruby-grafana-reporter/releases/latest") unless @config.latest_version_check_ok?
|
146
148
|
build
|
147
149
|
rescue MissingTemplateError => e
|
148
150
|
@logger.error(e.message)
|
@@ -23,12 +23,52 @@ module GrafanaReporter
|
|
23
23
|
raise NotImplementedError
|
24
24
|
end
|
25
25
|
|
26
|
-
#
|
27
|
-
#
|
26
|
+
# Used to format a given content array to the desired output format. The default
|
27
|
+
# implementation applies the {#format_rules} to create a custom string export. If
|
28
|
+
# this is not sufficient for a desired table format, you may simply overwrite this
|
29
|
+
# function to have full freedom about the desired output.
|
30
|
+
# @param content [Hash] datasource table result
|
28
31
|
# @param include_headline [Boolean] true, if headline should be included in result
|
32
|
+
# @param transposed [Boolean] true, if result array is in transposed format
|
29
33
|
# @return [String] formatted in table format
|
30
|
-
def format(content, include_headline)
|
31
|
-
|
34
|
+
def format(content, include_headline, transposed)
|
35
|
+
result = content[:content]
|
36
|
+
|
37
|
+
# add the headline at the correct position to the content array
|
38
|
+
if include_headline
|
39
|
+
if transposed
|
40
|
+
result.each_index do |i|
|
41
|
+
result[i] = [content[:header][i]] + result[i]
|
42
|
+
end
|
43
|
+
else
|
44
|
+
result = result.unshift(content[:header])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# translate the content to a table
|
49
|
+
result.map do |row|
|
50
|
+
format_rules[:row_start] + row.map do |item|
|
51
|
+
value = item.to_s
|
52
|
+
if format_rules[:replace_string_or_regex]
|
53
|
+
value = value.gsub(format_rules[:replace_string_or_regex], format_rules[:replacement])
|
54
|
+
end
|
55
|
+
|
56
|
+
format_rules[:cell_start] + value + format_rules[:cell_end]
|
57
|
+
end.join(format_rules[:between_cells])
|
58
|
+
end.join(format_rules[:row_end])
|
59
|
+
end
|
60
|
+
|
61
|
+
# Formatting rules, which are applied to build the table output format.
|
62
|
+
def format_rules
|
63
|
+
{
|
64
|
+
row_start: '',
|
65
|
+
row_end: '',
|
66
|
+
cell_start: '',
|
67
|
+
between_cells: '',
|
68
|
+
cell_end: '',
|
69
|
+
replace_string_or_regex: nil,
|
70
|
+
replacement: ''
|
71
|
+
}
|
32
72
|
end
|
33
73
|
end
|
34
74
|
end
|
@@ -37,7 +37,8 @@ module GrafanaReporter
|
|
37
37
|
row_divider: @variables['row_divider'],
|
38
38
|
column_divider: @variables['column_divider'],
|
39
39
|
table_formatter: @variables['table_formatter'],
|
40
|
-
include_headline: @variables['include_headline']
|
40
|
+
include_headline: @variables['include_headline'],
|
41
|
+
transpose: @variables['transpose'])
|
41
42
|
end
|
42
43
|
end
|
43
44
|
end
|
@@ -36,7 +36,8 @@ module GrafanaReporter
|
|
36
36
|
row_divider: @variables['row_divider'],
|
37
37
|
column_divider: @variables['column_divider'],
|
38
38
|
table_formatter: @variables['table_formatter'],
|
39
|
-
include_headline: @variables['include_headline']
|
39
|
+
include_headline: @variables['include_headline'],
|
40
|
+
transpose: @variables['transpose'])
|
40
41
|
end
|
41
42
|
end
|
42
43
|
end
|
@@ -26,7 +26,13 @@ module GrafanaReporter
|
|
26
26
|
@progress_reporter = Thread.new {}
|
27
27
|
|
28
28
|
@status = :running
|
29
|
-
|
29
|
+
begin
|
30
|
+
accept_requests_loop
|
31
|
+
rescue SystemExit, Interrupt
|
32
|
+
@logger.info("Server shutting down.")
|
33
|
+
stop!
|
34
|
+
retry
|
35
|
+
end
|
30
36
|
@status = :stopped
|
31
37
|
end
|
32
38
|
|
@@ -57,8 +63,6 @@ module GrafanaReporter
|
|
57
63
|
# step 1) accept incoming connection
|
58
64
|
socket = @server.accept
|
59
65
|
|
60
|
-
# TODO: shutdown properly on SIGINT/SIGHUB
|
61
|
-
|
62
66
|
# stop webservice properly, if shall be shutdown
|
63
67
|
if @status == :stopping
|
64
68
|
socket.close
|
@@ -252,7 +256,7 @@ module GrafanaReporter
|
|
252
256
|
<% end.join('') %>
|
253
257
|
<tbody>
|
254
258
|
</table>
|
255
|
-
<p style="font-size: small; color:grey">You are running ruby-grafana-reporter version <%= GRAFANA_REPORTER_VERSION.join('.')
|
259
|
+
<p style="font-size: small; color:grey">You are running ruby-grafana-reporter version <%= GRAFANA_REPORTER_VERSION.join('.') %>.<%= @config.latest_version_check_ok? ? '' : ' Check out the latest version <a href="https://github.com/divinity666/ruby-grafana-reporter/releases/latest">here</a>.' %></p>
|
256
260
|
</body>
|
257
261
|
</html>
|
258
262
|
HTML_TEMPLATE
|
@@ -10,15 +10,17 @@ module GrafanaReporter
|
|
10
10
|
'adoc_plain'
|
11
11
|
end
|
12
12
|
|
13
|
-
# @see AbstractTableFormatStrategy#
|
14
|
-
def
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
13
|
+
# @see AbstractTableFormatStrategy#format_rules
|
14
|
+
def format_rules
|
15
|
+
{
|
16
|
+
row_start: '| ',
|
17
|
+
row_end: "\n",
|
18
|
+
cell_start: '',
|
19
|
+
between_cells: ' | ',
|
20
|
+
cell_end: '',
|
21
|
+
replace_string_or_regex: '|',
|
22
|
+
replacement: '\\|'
|
23
|
+
}
|
22
24
|
end
|
23
25
|
end
|
24
26
|
end
|
@@ -82,7 +82,7 @@ Usage: #{opts[:code_begin]}#{v[:call]}#{opts[:code_end]}
|
|
82
82
|
#{v[:description]}#{"\n\nSee also: #{v[:see]}" if v[:see]}#{unless v[:options].empty?
|
83
83
|
%(
|
84
84
|
#{opts[:table_begin]}| Option | Description#{"\n#{opts[:head_postfix_col] * 2}" if opts[:head_postfix_col]}
|
85
|
-
#{v[:options].sort.map { |_opt_k, opt_v| "| #{opts[:code_begin]}#{opt_v[:call]}#{opts[:code_end]} | #{opt_v[:description].gsub('|', '\|')}" }.join("\n") }#{opts[:table_end]})
|
85
|
+
#{v[:options].sort.map { |_opt_k, opt_v| "| #{opts[:code_begin]}#{opt_v[:call]}#{opts[:code_end]} | #{opt_v[:description].gsub('|', '\|')}#{"\nSee also: #{opt_v[:see]}" if opt_v[:see]}" }.join("\n") }#{opts[:table_end]})
|
86
86
|
end}
|
87
87
|
)
|
88
88
|
end
|
@@ -109,12 +109,12 @@ end}
|
|
109
109
|
res_item[:description] = item['description']
|
110
110
|
res_item[:see] = item['see']
|
111
111
|
|
112
|
-
opts = ((item['options'] ? item['options'].keys : [])
|
112
|
+
opts = ((item['options'] ? item['options'].keys : []) +
|
113
113
|
(item['standard_options'] ? item['standard_options'].keys : [])).sort
|
114
114
|
opts.each do |opt_key|
|
115
115
|
res_item[:options][opt_key] = {}
|
116
116
|
|
117
|
-
if
|
117
|
+
if std_opts.key?(opt_key)
|
118
118
|
res_item[:options][opt_key][:call] = std_opts[opt_key]['call']
|
119
119
|
res_item[:options][opt_key][:description] = "#{std_opts[opt_key]['description']} "\
|
120
120
|
"#{item['standard_options'][opt_key]}".chop
|
@@ -196,8 +196,12 @@ end}
|
|
196
196
|
format:
|
197
197
|
call: format="<format_col1>,<format_col2>,..."
|
198
198
|
description: >-
|
199
|
-
Specify format in which the results shall be returned, e.g. `%.2f` for only
|
200
|
-
float. Several
|
199
|
+
Specify format in which the results in a specific column shall be returned, e.g. `%.2f` for only
|
200
|
+
two digit decimals of a float. Several column formats are separated by `,`, i.e. `%.2f,%.3f` would
|
201
|
+
apply `%.2f` to the first column and `%.3f` to the second column. All other columns would not be
|
202
|
+
formatted. You may also format time in milliseconds to a time format by specifying e.g. `date:iso`.
|
203
|
+
Commas in format strings are supported, but have to be escaped by useing `_,`.
|
204
|
+
Execution of related functions is applied in the following order `format`,
|
201
205
|
`replace_values`, `filter_columns`, `transpose`.
|
202
206
|
see: 'https://ruby-doc.org/core/Kernel.html#method-i-sprintf'
|
203
207
|
|
@@ -207,21 +211,24 @@ end}
|
|
207
211
|
Specify result values which shall be replaced, e.g. `2:OK` will replace query values `2` with value `OK`.
|
208
212
|
Replacing several values is possible by separating by `,`. Matches with regular expressions are also
|
209
213
|
supported, but must be full matches, i.e. have to start with `^` and end with `$`, e.g. `^[012]$:OK`.
|
210
|
-
Number replacements can also be performed, e.g. `<8.2` or `<>3`. Execution
|
214
|
+
Number replacements can also be performed, e.g. `<8.2` or `<>3`. Execution of related functions is
|
215
|
+
applied in the following order `format`,
|
211
216
|
`replace_values`, `filter_columns`, `transpose`.
|
212
217
|
see: https://ruby-doc.org/core/Regexp.html#class-Regexp-label-Character+Classes
|
213
218
|
|
214
219
|
filter_columns:
|
215
220
|
call: filter_columns="<column_name_1>,<column_name_2>,..."
|
216
221
|
description: >-
|
217
|
-
Removes specified columns from result.
|
218
|
-
`
|
222
|
+
Removes specified columns from result. Commas in format strings are supported, but have to be
|
223
|
+
escaped by useing `_,`. Execution of related functions is applied in the following order
|
224
|
+
`format`, `replace_values`, `filter_columns`, `transpose`.
|
219
225
|
|
220
226
|
transpose:
|
221
227
|
call: transpose="true"
|
222
228
|
description: >-
|
223
|
-
Transposes the query result, i.e. columns become rows and rows become columnns. Execution
|
224
|
-
following order `format`, `replace_values`, `filter_columns`,
|
229
|
+
Transposes the query result, i.e. columns become rows and rows become columnns. Execution of related
|
230
|
+
functions is applied in the following order `format`, `replace_values`, `filter_columns`,
|
231
|
+
`transpose`.
|
225
232
|
|
226
233
|
column_divider:
|
227
234
|
call: column_divider="<divider>"
|
@@ -234,7 +241,7 @@ end}
|
|
234
241
|
call: row_divider="<divider>"
|
235
242
|
description: >-
|
236
243
|
Replace the default row divider with another one, when used in conjunction with `table_formatter` set to
|
237
|
-
`adoc_deprecated`.
|
244
|
+
`adoc_deprecated`. Defaults to `| ` for being interpreted as a asciidoctor row. DEPRECATED: switch to
|
238
245
|
`table_formatter` named `adoc_plain`, or implement a custom table formatter.
|
239
246
|
|
240
247
|
table_formatter:
|
@@ -258,8 +265,13 @@ end}
|
|
258
265
|
call: 'include::grafana_help[]'
|
259
266
|
|
260
267
|
grafana_environment:
|
261
|
-
description:
|
268
|
+
description: >-
|
269
|
+
Shows all available variables in the rendering context which can be used in the asciidoctor template.
|
270
|
+
If optional `instance` is specified, additional information about the configured grafana instance will be provided.
|
271
|
+
This is especially helpful for debugging.
|
262
272
|
call: 'include::grafana_environment[]'
|
273
|
+
standard_options:
|
274
|
+
instance:
|
263
275
|
|
264
276
|
grafana_alerts:
|
265
277
|
description: >-
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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'
|
@@ -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}
|
@@ -9,15 +9,17 @@ module GrafanaReporter
|
|
9
9
|
'csv'
|
10
10
|
end
|
11
11
|
|
12
|
-
# @see AbstractTableFormatStrategy#
|
13
|
-
def
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
+
}
|
21
23
|
end
|
22
24
|
end
|
23
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
|
@@ -15,7 +15,6 @@ module GrafanaReporter
|
|
15
15
|
# Returns the body of the http query, which contains the raw image.
|
16
16
|
def post_process
|
17
17
|
@result = @result[:content].first
|
18
|
-
raise ::Grafana::ImageCouldNotBeRenderedError, @panel if @result.include?('<html')
|
19
18
|
end
|
20
19
|
|
21
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] || []
|
@@ -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
|
@@ -18,17 +18,6 @@ require 'asciidoctor-pdf'
|
|
18
18
|
require 'zip'
|
19
19
|
require_relative 'VERSION'
|
20
20
|
|
21
|
-
# TODO: add test to see if datasource default formats are applied
|
22
|
-
# TODO: add test for render-height and render-width, as they are not forwarded
|
23
|
-
# TODO: show convert-backend, template language and all variables in webservice overview
|
24
|
-
# TODO: add automated test against grafana playground before building a new release
|
25
|
-
# TODO: allow registration of files to be defined in config file
|
26
|
-
# TODO: PRIO: check in cloud - variables fetched from queries are replaced with SQL query instead of the resolved values, which can lead to issues, e.g. when using that in a function as SUBSTRING
|
27
|
-
# TODO: PRIO: allow multiple report classes in configuration file including possibility to decide for the individual class for each rendering call
|
28
|
-
# TODO: add configuration example to README
|
29
|
-
# TODO: find a way, how to automatically update test grafana responses with _real_ grafana responses
|
30
|
-
# TODO: append necessary variables on demo report creation for plain SQL queries, as they are lacking the grafana reference
|
31
|
-
|
32
21
|
folders = [
|
33
22
|
%w[grafana],
|
34
23
|
%w[grafana_reporter logger],
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-grafana-reporter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christian Kohlmeyer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-11-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: asciidoctor
|
@@ -118,6 +118,7 @@ files:
|
|
118
118
|
- "./lib/grafana/grafana.rb"
|
119
119
|
- "./lib/grafana/grafana_alerts_datasource.rb"
|
120
120
|
- "./lib/grafana/grafana_annotations_datasource.rb"
|
121
|
+
- "./lib/grafana/grafana_environment_datasource.rb"
|
121
122
|
- "./lib/grafana/grafana_property_datasource.rb"
|
122
123
|
- "./lib/grafana/graphite_datasource.rb"
|
123
124
|
- "./lib/grafana/image_rendering_datasource.rb"
|
@@ -165,6 +166,7 @@ files:
|
|
165
166
|
- "./lib/grafana_reporter/panel_property_query.rb"
|
166
167
|
- "./lib/grafana_reporter/query_value_query.rb"
|
167
168
|
- "./lib/grafana_reporter/report_webhook.rb"
|
169
|
+
- "./lib/grafana_reporter/reporter_environment_datasource.rb"
|
168
170
|
- "./lib/ruby_grafana_extension.rb"
|
169
171
|
- "./lib/ruby_grafana_reporter.rb"
|
170
172
|
- LICENSE
|