ruby-grafana-reporter 0.6.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e06395c66d098c01d5248872f7aac2bc8dd36c4f9c6f02774ffe022fd0cafc9f
4
- data.tar.gz: 6ad05dfac340497fb0be61b14b6420a348b05dbd71b14e17b55553573cfcf3b8
3
+ metadata.gz: 49639b5da410d46b0ad6cf16a187a3f30389fac610497de09d1e1466a66f22f5
4
+ data.tar.gz: 673791b6d38cd46e54292903f07e0fdd16070f5e0abf999565fecf04b930efbb
5
5
  SHA512:
6
- metadata.gz: 769a0c9258678b32100b21e38782ca668467ad2e016e63e3bc293d96311bb06d74b0b2a71f4033a576c71c94185e072ad37ac55d8d4cdc13a516a92f6c359895
7
- data.tar.gz: 53a645d2db1eac77ed0e6977932b2947eb41efccf33f49f77db6c7ea4254ebd2f0c299fb1211546356734b744ca6359f36f392ae3afcf4243c835da3c7671bfa
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
- * [Donations](#donations)
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
+ [![buymeacoffee](https://az743702.vo.msecnd.net/cdn/kofi3.png?v=0)](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
+ ![GettingStarted](./assets/GettingStartedAnimation.gif)
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, you need to simply add a link to your
140
- grafana dashboard:
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
- ### Webservice
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
- [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/donate?hosted_button_id=35LH6JNLPHPHQ)
@@ -2,4 +2,4 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require_relative '../lib/ruby_grafana_reporter'
5
- GrafanaReporter::Application::Application.new.configure_and_run(ARGV) unless defined?(Ocra)
5
+ GrafanaReporter::Application::Application.new.configure_and_run(ARGV) unless defined?(Ocran)
data/lib/VERSION.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Version information
4
- GRAFANA_REPORTER_VERSION = [0, 6, 3].freeze
4
+ GRAFANA_REPORTER_VERSION = [0, 7, 0].freeze
5
5
  # Release date
6
- GRAFANA_REPORTER_RELEASE_DATE = '2023-01-08'
6
+ GRAFANA_REPORTER_RELEASE_DATE = '2024-05-19'
@@ -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
- webrequest.relative_url = url
38
- webrequest.options.merge!({ request: Net::HTTP::Get })
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.to_s unless File.file?(@template.to_s)
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['OCRA_EXECUTABLE']
38
- "Usage: #{ENV['OCRA_EXECUTABLE'].gsub("#{Dir.pwd}/".gsub('/', '\\'), '')} [options]"
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:
@@ -80,7 +80,6 @@ module GrafanaReporter
80
80
  # @see ProcessorMixin#build_demo_entry
81
81
  def build_demo_entry(panel)
82
82
  return nil unless panel
83
- return nil unless panel.model['type'].include?('table')
84
83
 
85
84
  ref_id = nil
86
85
  panel.model['targets'].each do |item|
@@ -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'] == 'singlestat'
78
77
 
79
78
  ref_id = nil
80
79
  panel.model['targets'].each do |item|
@@ -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.datasource_by_name(panel.model['datasource']).id}"\
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.datasource_by_name(panel.model['datasource']).id}"\
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['OCRA_EXECUTABLE'].gsub("#{Dir.pwd}/".gsub('/', '\\'), '') if ENV['OCRA_EXECUTABLE']
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("Given report template '#{template}' is not a valid template.")
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
- @result = tmp.flatten.first
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}'"
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'rubygems'
4
+ require 'rubygems/ext'
3
5
  require 'net/http'
4
6
  require 'fileutils'
5
7
  require 'yaml'
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.6.3
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: 2023-01-08 00:00:00.000000000 Z
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.4
209
+ rubygems_version: 3.1.6
210
210
  signing_key:
211
211
  specification_version: 4
212
212
  summary: Reporter Service for Grafana