ruby-grafana-reporter 0.4.5 → 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 +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
|