ruby-grafana-reporter 0.6.3 → 0.7.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 +48 -10
- data/bin/ruby-grafana-reporter +1 -1
- data/lib/VERSION.rb +2 -2
- data/lib/grafana/grafana.rb +19 -0
- data/lib/grafana/image_rendering_datasource.rb +2 -2
- data/lib/grafana/influxdb_datasource.rb +30 -5
- data/lib/grafana_reporter/abstract_query.rb +1 -1
- data/lib/grafana_reporter/abstract_report.rb +1 -1
- data/lib/grafana_reporter/application/application.rb +2 -2
- data/lib/grafana_reporter/asciidoctor/help.rb +11 -0
- data/lib/grafana_reporter/asciidoctor/panel_query_table_include_processor.rb +0 -1
- data/lib/grafana_reporter/asciidoctor/panel_query_value_inline_macro.rb +0 -1
- data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +1 -1
- data/lib/grafana_reporter/asciidoctor/sql_table_include_processor.rb +2 -3
- data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +2 -3
- data/lib/grafana_reporter/console_configuration_wizard.rb +1 -1
- data/lib/grafana_reporter/errors.rb +10 -1
- data/lib/grafana_reporter/query_value_query.rb +24 -1
- data/lib/ruby_grafana_reporter.rb +2 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 49639b5da410d46b0ad6cf16a187a3f30389fac610497de09d1e1466a66f22f5
|
4
|
+
data.tar.gz: 673791b6d38cd46e54292903f07e0fdd16070f5e0abf999565fecf04b930efbb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d4bb03253d7681c580c96f11e5e86112e036087b927ff01e0e490bebeb4aeadd5f213f9de7202007523b8bf384ed36f6fcc040d5b81aa5aceef88c7cb191ab50
|
7
|
+
data.tar.gz: d3695d4d6ce451c01e7ad33a28a942f697d019215cca603827bda44af488e292c4befaf658d266e19b5ca3e84ab1a06cbc5f4f5e189d262b5c8bb415c4ae1013
|
data/README.md
CHANGED
@@ -20,7 +20,20 @@ Reporting Service for Grafana
|
|
20
20
|
* [Using webhooks](#using-webhooks)
|
21
21
|
* [Developing your own plugin](#developing-your-own-plugin)
|
22
22
|
* [Roadmap](#roadmap)
|
23
|
-
* [
|
23
|
+
* [Contributing](#contributing)
|
24
|
+
* [Licensing](#licensing)
|
25
|
+
* [Acknowledgements](#acknowledgements)
|
26
|
+
|
27
|
+
## Your support is appreciated!
|
28
|
+
|
29
|
+
Hey there! I provide you this software free of charge. I have already
|
30
|
+
spend a lot of my private time in developing, maintaining and supporting it.
|
31
|
+
|
32
|
+
If you enjoy my work, feel free to
|
33
|
+
|
34
|
+
[](https://ko-fi.com/divinity666)
|
35
|
+
|
36
|
+
Thanks for your support and keeping this project alive!
|
24
37
|
|
25
38
|
## About the project
|
26
39
|
|
@@ -35,6 +48,10 @@ dashboards and to use it in your custom templates to finally create reports in P
|
|
35
48
|
|
36
49
|
By default (an extended version of) Asciidoctor is enabled as template language.
|
37
50
|
|
51
|
+
## Getting started
|
52
|
+
|
53
|
+

|
54
|
+
|
38
55
|
## Features
|
39
56
|
|
40
57
|
* Supports creation of reports for multiple [grafana](https://github.com/grafana/grafana)
|
@@ -136,8 +153,10 @@ asciidoctor:
|
|
136
153
|
|
137
154
|
### Grafana integration
|
138
155
|
|
139
|
-
For using the reporter directly from grafana,
|
140
|
-
|
156
|
+
For using the reporter directly from grafana, the reporter has to run as webservice, i.e. it has to be
|
157
|
+
called without the `-t` parameter.
|
158
|
+
|
159
|
+
If this is the case, you simply simply need add a link to your grafana dashboard:
|
141
160
|
|
142
161
|
* Open the dashboard configuration
|
143
162
|
* Select `Links`
|
@@ -168,7 +187,32 @@ a variable and forward it to the reporter.
|
|
168
187
|
|
169
188
|
## Advanced information
|
170
189
|
|
171
|
-
###
|
190
|
+
### Use grafana variables in templates
|
191
|
+
It is common practice to use dashboard variables in grafana, to allow users to show
|
192
|
+
the dashboard for a specific set of data only. This is where grafana variables are
|
193
|
+
used.
|
194
|
+
|
195
|
+
Those variables are then also used in panel queries, to react on selecting or entering
|
196
|
+
those variables.
|
197
|
+
|
198
|
+
You may provide those variables during report generation to the reporter. Therefore
|
199
|
+
you have to specify them in the individual calls.
|
200
|
+
|
201
|
+
Let's say, you have a variable called `serverid` in the dashboard. You may now want
|
202
|
+
to set this variable for a panel image rendering. This cann be done with the following
|
203
|
+
calls:
|
204
|
+
|
205
|
+
````
|
206
|
+
grafana_panel_image:1[var-serverid=main-server]
|
207
|
+
grafana_panel_image:1[var-serverid=replica-server]
|
208
|
+
````
|
209
|
+
|
210
|
+
This will render two images: one for `main-server` and one for `replica-server`.
|
211
|
+
|
212
|
+
So, to forward grafana variables to the reporter calls, you simply have to use the
|
213
|
+
form `var-<<your-variable-name>>` and specify those in your reporter template.
|
214
|
+
|
215
|
+
### Webservice endpoints
|
172
216
|
|
173
217
|
Running the reporter as a webservice provides the following URLs
|
174
218
|
|
@@ -329,9 +373,3 @@ The code in this project is licensed under MIT license.
|
|
329
373
|
* [grafana](https://github.com/grafana/grafana)
|
330
374
|
|
331
375
|
Inspired by [Izak Marai's grafana reporter](https://github.com/IzakMarais/reporter)
|
332
|
-
|
333
|
-
## Donations
|
334
|
-
|
335
|
-
If you like this project and you would like to support my work, feel free to donate. :)
|
336
|
-
|
337
|
-
[](https://www.paypal.com/donate?hosted_button_id=35LH6JNLPHPHQ)
|
data/bin/ruby-grafana-reporter
CHANGED
data/lib/VERSION.rb
CHANGED
data/lib/grafana/grafana.rb
CHANGED
@@ -68,6 +68,23 @@ module Grafana
|
|
68
68
|
'NON-Admin'
|
69
69
|
end
|
70
70
|
|
71
|
+
# Returns the datasource, which has been queried by model entry in the panel model.
|
72
|
+
#
|
73
|
+
# @param model_entry [Object] model entry of the searched datasource (e.g. String or Hash)
|
74
|
+
# @return [Datasource] Datasource for the specified datasource model entry
|
75
|
+
def datasource_by_model_entry(model_entry)
|
76
|
+
datasource = nil
|
77
|
+
if model_entry.is_a?(String)
|
78
|
+
datasource = datasource_by_name(model_entry)
|
79
|
+
elsif model_entry.is_a?(Hash)
|
80
|
+
datasource = datasource_by_uid(model_entry['uid'])
|
81
|
+
end
|
82
|
+
|
83
|
+
raise DatasourceDoesNotExistError.new('model entry', model_entry) unless datasource
|
84
|
+
|
85
|
+
datasource
|
86
|
+
end
|
87
|
+
|
71
88
|
# Returns the datasource, which has been queried by the datasource name.
|
72
89
|
#
|
73
90
|
# @param datasource_name [String] name of the searched datasource
|
@@ -86,6 +103,8 @@ module Grafana
|
|
86
103
|
# @param datasource_uid [String] unique id of the searched datasource
|
87
104
|
# @return [Datasource] Datasource for the specified datasource unique id
|
88
105
|
def datasource_by_uid(datasource_uid)
|
106
|
+
raise DatasourceDoesNotExistError.new('uid', datasource_uid) unless datasource_uid
|
107
|
+
|
89
108
|
clean_nil_datasources
|
90
109
|
datasource = @datasources.select { |ds_name, ds| ds.uid == datasource_uid }.values.first
|
91
110
|
raise DatasourceDoesNotExistError.new('uid', datasource_uid) unless datasource
|
@@ -16,7 +16,7 @@ module Grafana
|
|
16
16
|
webrequest.relative_url = panel.render_url + url_params(query_description)
|
17
17
|
webrequest.options.merge!({ accept: 'image/png' })
|
18
18
|
|
19
|
-
result = webrequest.execute
|
19
|
+
result = webrequest.execute(query_description[:timeout])
|
20
20
|
|
21
21
|
raise ImageCouldNotBeRenderedError, panel if result.body.include?('<html')
|
22
22
|
|
@@ -26,7 +26,7 @@ module Grafana
|
|
26
26
|
private
|
27
27
|
|
28
28
|
def url_params(query_desc)
|
29
|
-
url_vars = query_desc[:variables].select { |k, _v| k =~ /^(?:timeout|height|width|theme|fullscreen|var-.+)$/ }
|
29
|
+
url_vars = query_desc[:variables].select { |k, _v| k =~ /^(?:timeout|scale|height|width|theme|fullscreen|var-.+)$/ }
|
30
30
|
url_vars = default_vars.merge(url_vars)
|
31
31
|
url_vars['from'] = Variable.new(query_desc[:from])
|
32
32
|
url_vars['to'] = Variable.new(query_desc[:to])
|
@@ -31,11 +31,36 @@ module Grafana
|
|
31
31
|
query = query.gsub(/\$(?:__)?interval(?=\W|$)/, "#{interval.is_a?(String) ? interval : "#{(interval / 1000).to_i}s"}")
|
32
32
|
query = query.gsub(/\$(?:__)?interval_ms(?=\W|$)/, "#{interval}")
|
33
33
|
|
34
|
-
url = "/api/datasources/proxy/#{id}/query?db=#{@model['database']}&q=#{ERB::Util.url_encode(query)}&epoch=ms"
|
35
|
-
|
36
34
|
webrequest = query_description[:prepared_request]
|
37
|
-
|
38
|
-
|
35
|
+
request = {}
|
36
|
+
|
37
|
+
ver = query_description[:grafana_version].split('.').map{|x| x.to_i}
|
38
|
+
if ver[0] >= 8
|
39
|
+
webrequest.relative_url = "/api/ds/query?ds_type=influxdb"
|
40
|
+
|
41
|
+
request = {
|
42
|
+
request: Net::HTTP::Post,
|
43
|
+
body: {
|
44
|
+
from: query_description[:from],
|
45
|
+
to: query_description[:to],
|
46
|
+
queries: [
|
47
|
+
{
|
48
|
+
datasource: {type: "influxdb"},
|
49
|
+
datasourceId: id,
|
50
|
+
intervalMs: interval,
|
51
|
+
query: query
|
52
|
+
}
|
53
|
+
]}.to_json
|
54
|
+
}
|
55
|
+
else
|
56
|
+
webrequest.relative_url = "/api/datasources/proxy/#{id}/query?db=#{@model['database']}&q=#{ERB::Util.url_encode(query)}&epoch=ms"
|
57
|
+
request = {
|
58
|
+
request: Net::HTTP::Get
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
webrequest.options.merge!(request)
|
63
|
+
|
39
64
|
|
40
65
|
result = webrequest.execute(query_description[:timeout])
|
41
66
|
preformat_response(result.body)
|
@@ -43,7 +68,7 @@ module Grafana
|
|
43
68
|
|
44
69
|
# @see AbstractDatasource#raw_query_from_panel_model
|
45
70
|
def raw_query_from_panel_model(panel_query_target)
|
46
|
-
return panel_query_target['query'] if panel_query_target['rawQuery']
|
71
|
+
return panel_query_target['query'] if panel_query_target['query'] or panel_query_target['rawQuery']
|
47
72
|
|
48
73
|
# build composed queries
|
49
74
|
build_select(panel_query_target['select']) + build_from(panel_query_target) + build_where(panel_query_target['tags']) + build_group_by(panel_query_target['groupBy'])
|
@@ -383,7 +383,7 @@ module GrafanaReporter
|
|
383
383
|
delta_match = date_spec.match(/^(?<op>(?:-|\+))(?<count>\d+)?(?<unit>[smhdwMy])/)
|
384
384
|
if delta_match
|
385
385
|
date = delta_date(date, "#{delta_match[:op]}#{delta_match[:count] || 1}".to_i, delta_match[:unit])
|
386
|
-
date_spec = date_spec.gsub(/^#{delta_match[:op]}#{delta_match[:count]}#{delta_match[:unit]}/, '')
|
386
|
+
date_spec = date_spec.gsub(/^#{delta_match[:op] == '+' ? '\+' : '-'}#{delta_match[:count]}#{delta_match[:unit]}/, '')
|
387
387
|
end
|
388
388
|
|
389
389
|
raise TimeRangeUnknownError, orig_date unless fit_match || delta_match
|
@@ -138,7 +138,7 @@ module GrafanaReporter
|
|
138
138
|
|
139
139
|
# automatically add extension, if a file with default template extension exists
|
140
140
|
@template = "#{@template}.#{self.class.default_template_extension}" if File.file?("#{@template}.#{self.class.default_template_extension}") && !File.file?(@template.to_s)
|
141
|
-
raise MissingTemplateError, @template.
|
141
|
+
raise MissingTemplateError, "#{@template}.#{self.class.default_template_extension}" unless File.file?(@template.to_s)
|
142
142
|
|
143
143
|
notify(:on_before_create)
|
144
144
|
@start_time = Time.new
|
@@ -34,8 +34,8 @@ module GrafanaReporter
|
|
34
34
|
action_wizard = false
|
35
35
|
|
36
36
|
parser = OptionParser.new do |opts|
|
37
|
-
opts.banner = if ENV['
|
38
|
-
"Usage: #{ENV['
|
37
|
+
opts.banner = if ENV['OCRAN_EXECUTABLE']
|
38
|
+
"Usage: #{ENV['OCRAN_EXECUTABLE'].gsub("#{Dir.pwd}/".gsub('/', '\\'), '')} [options]"
|
39
39
|
else
|
40
40
|
"Usage: #{Gem.ruby} #{$PROGRAM_NAME} [options]"
|
41
41
|
end
|
@@ -229,6 +229,12 @@ end}
|
|
229
229
|
escaped by using `_,`. Execution of related functions is applied in the following order
|
230
230
|
`format`, `replace_values`, `filter_columns`, `transpose`.
|
231
231
|
|
232
|
+
select_value:
|
233
|
+
call: select_value="<select_value>"
|
234
|
+
description: >-
|
235
|
+
Allows the selection of a specific value from the result set. Supported options are `min`, `max`, `avg`,
|
236
|
+
`sum`, `first`, `last`.
|
237
|
+
|
232
238
|
transpose:
|
233
239
|
call: transpose="true"
|
234
240
|
description: >-
|
@@ -391,6 +397,9 @@ end}
|
|
391
397
|
render-width:
|
392
398
|
description: can be used to override default `width` in which the panel shall be rendered
|
393
399
|
call: render-width="<width>"
|
400
|
+
render-scale:
|
401
|
+
description: can be used to override default scale in which the panel shall be rendered
|
402
|
+
call: render-scale="<scale>"
|
394
403
|
render-theme:
|
395
404
|
description: can be used to override default `theme` in which the panel shall be rendered (light by default)
|
396
405
|
call: render-theme="<theme>"
|
@@ -453,6 +462,7 @@ end}
|
|
453
462
|
from:
|
454
463
|
instance:
|
455
464
|
replace_values:
|
465
|
+
select_value:
|
456
466
|
timeout:
|
457
467
|
to:
|
458
468
|
from_timezone:
|
@@ -502,6 +512,7 @@ end}
|
|
502
512
|
from:
|
503
513
|
instance:
|
504
514
|
replace_values:
|
515
|
+
select_value:
|
505
516
|
timeout:
|
506
517
|
to:
|
507
518
|
from_timezone:
|
@@ -40,7 +40,7 @@ module GrafanaReporter
|
|
40
40
|
k =~ /^(?:timeout|from|to)$/ ||
|
41
41
|
k =~ /filter_columns|format|replace_values_.*|transpose|from_timezone|
|
42
42
|
to_timezone|result_type|query|table_formatter|include_headline|
|
43
|
-
column_divider|row_divider|instant|interval|verbose_log/x
|
43
|
+
column_divider|row_divider|instant|interval|verbose_log|select_value/x
|
44
44
|
end)
|
45
45
|
|
46
46
|
result
|
@@ -74,7 +74,6 @@ module GrafanaReporter
|
|
74
74
|
# @see ProcessorMixin#build_demo_entry
|
75
75
|
def build_demo_entry(panel)
|
76
76
|
return nil unless panel
|
77
|
-
return nil unless panel.model['type'].include?('table')
|
78
77
|
|
79
78
|
ref_id = nil
|
80
79
|
panel.model['targets'].each do |item|
|
@@ -85,8 +84,8 @@ module GrafanaReporter
|
|
85
84
|
end
|
86
85
|
return nil unless ref_id
|
87
86
|
|
88
|
-
"|===\ninclude::grafana_sql_table:#{panel.dashboard.grafana.
|
89
|
-
"[sql=\"#{panel.query(ref_id).gsub(/"/, '\"').gsub("\n", ' ').gsub(/\\/, '\\\\')}\",filter_columns=\"time\","\
|
87
|
+
"|===\ninclude::grafana_sql_table:#{panel.dashboard.grafana.datasource_by_model_entry(panel.model['datasource']).id}"\
|
88
|
+
"[sql=\"#{panel.query(ref_id).gsub(/"/, '\"').gsub("\r\n", ' ').gsub("\n", ' ').gsub(/\\/, '\\\\')}\",filter_columns=\"time\","\
|
90
89
|
"dashboard=\"#{panel.dashboard.id}\",from=\"now-1h\",to=\"now\"]\n|==="
|
91
90
|
end
|
92
91
|
end
|
@@ -79,7 +79,6 @@ module GrafanaReporter
|
|
79
79
|
# @see ProcessorMixin#build_demo_entry
|
80
80
|
def build_demo_entry(panel)
|
81
81
|
return nil unless panel
|
82
|
-
return nil unless panel.model['type'] == 'singlestat'
|
83
82
|
|
84
83
|
ref_id = nil
|
85
84
|
panel.model['targets'].each do |item|
|
@@ -90,8 +89,8 @@ module GrafanaReporter
|
|
90
89
|
end
|
91
90
|
return nil unless ref_id
|
92
91
|
|
93
|
-
"grafana_sql_value:#{panel.dashboard.grafana.
|
94
|
-
"[sql=\"#{panel.query(ref_id).gsub(/"/, '\"').gsub("\n", ' ').gsub(/\\/, '\\\\')}\",from=\"now-1h\","\
|
92
|
+
"grafana_sql_value:#{panel.dashboard.grafana.datasource_by_model_entry(panel.model['datasource']).id}"\
|
93
|
+
"[sql=\"#{panel.query(ref_id).gsub(/"/, '\"').gsub("\r\n", ' ').gsub("\n", ' ').gsub(/\\/, '\\\\')}\",from=\"now-1h\","\
|
95
94
|
'to="now"]'
|
96
95
|
end
|
97
96
|
end
|
@@ -32,7 +32,7 @@ module GrafanaReporter
|
|
32
32
|
demo_report ||= '<<your_report_name>>'
|
33
33
|
config_param = config_file == Configuration::DEFAULT_CONFIG_FILE_NAME ? '' : " -c #{config_file}"
|
34
34
|
program_call = "#{Gem.ruby} #{$PROGRAM_NAME}"
|
35
|
-
program_call = ENV['
|
35
|
+
program_call = ENV['OCRAN_EXECUTABLE'].gsub("#{Dir.pwd}/".gsub('/', '\\'), '') if ENV['OCRAN_EXECUTABLE']
|
36
36
|
|
37
37
|
puts
|
38
38
|
puts 'Now everything is setup properly. Create your reports as required in the templates '\
|
@@ -50,7 +50,7 @@ module GrafanaReporter
|
|
50
50
|
# Thrown if a non existing template has been specified.
|
51
51
|
class MissingTemplateError < ConfigurationError
|
52
52
|
def initialize(template)
|
53
|
-
super("
|
53
|
+
super("Accessing report template file '#{template}' is not possible. Check if file exists and is accessible.")
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
@@ -79,6 +79,15 @@ module GrafanaReporter
|
|
79
79
|
end
|
80
80
|
end
|
81
81
|
|
82
|
+
# Thrown, if the value configuration in {QueryValueQuery#select_value} is
|
83
|
+
# invalid.
|
84
|
+
class UnsupportedSelectValueStatementError < GrafanaReporterError
|
85
|
+
def initialize(statement)
|
86
|
+
super("Unsupported 'select_value' specified in template file: '#{statement}'. Supported values are 'min', 'max', "\
|
87
|
+
"'avg', 'sum', 'first', 'last'.")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
82
91
|
# Thrown, if a configured parameter is malformed.
|
83
92
|
class MalformedAttributeContentError < GrafanaReporterError
|
84
93
|
def initialize(message, attribute, content)
|
@@ -33,7 +33,30 @@ module GrafanaReporter
|
|
33
33
|
|
34
34
|
when /(?:panel_value|sql_value)/
|
35
35
|
tmp = @result[:content] || []
|
36
|
-
|
36
|
+
# use only first column of return values and replace null values with zero
|
37
|
+
tmp = tmp.map{ |item| item[0] || 0 }
|
38
|
+
|
39
|
+
# as default behaviour we fallback to the first_value, as this was the default in older releases
|
40
|
+
select_value = 'first'
|
41
|
+
select_value = @variables['select_value'].raw_value if @variables['select_value']
|
42
|
+
case select_value
|
43
|
+
when 'min'
|
44
|
+
result = tmp.min
|
45
|
+
when 'max'
|
46
|
+
result = tmp.max
|
47
|
+
when 'avg'
|
48
|
+
result = tmp.size > 0 ? tmp.sum / tmp.size : 0
|
49
|
+
when 'sum'
|
50
|
+
result = tmp.sum
|
51
|
+
when 'last'
|
52
|
+
result = tmp.last
|
53
|
+
when 'first'
|
54
|
+
result = tmp.first
|
55
|
+
else
|
56
|
+
raise UnsupportedSelectValueStatementError, @variables['select_value'].raw_value
|
57
|
+
end
|
58
|
+
|
59
|
+
@result = result
|
37
60
|
|
38
61
|
else
|
39
62
|
raise StandardError, "Unsupported 'result_type' received: '#{@variables['result_type'].raw_value}'"
|
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.7.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:
|
11
|
+
date: 2024-05-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: asciidoctor
|
@@ -206,7 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
206
206
|
- !ruby/object:Gem::Version
|
207
207
|
version: '0'
|
208
208
|
requirements: []
|
209
|
-
rubygems_version: 3.1.
|
209
|
+
rubygems_version: 3.1.6
|
210
210
|
signing_key:
|
211
211
|
specification_version: 4
|
212
212
|
summary: Reporter Service for Grafana
|