ruby-grafana-reporter 0.5.0 → 0.5.3

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +0 -0
  3. data/lib/VERSION.rb +2 -2
  4. data/lib/grafana/abstract_datasource.rb +35 -4
  5. data/lib/grafana/dashboard.rb +0 -0
  6. data/lib/grafana/errors.rb +7 -0
  7. data/lib/grafana/grafana.rb +11 -0
  8. data/lib/grafana/grafana_environment_datasource.rb +0 -0
  9. data/lib/grafana/grafana_property_datasource.rb +0 -0
  10. data/lib/grafana/graphite_datasource.rb +10 -3
  11. data/lib/grafana/image_rendering_datasource.rb +0 -0
  12. data/lib/grafana/influxdb_datasource.rb +18 -5
  13. data/lib/grafana/panel.rb +28 -1
  14. data/lib/grafana/prometheus_datasource.rb +54 -13
  15. data/lib/grafana/sql_datasource.rb +16 -6
  16. data/lib/grafana/variable.rb +0 -0
  17. data/lib/grafana/webrequest.rb +0 -0
  18. data/lib/grafana_reporter/abstract_query.rb +1 -1
  19. data/lib/grafana_reporter/abstract_report.rb +0 -0
  20. data/lib/grafana_reporter/abstract_table_format_strategy.rb +0 -0
  21. data/lib/grafana_reporter/alerts_table_query.rb +0 -0
  22. data/lib/grafana_reporter/annotations_table_query.rb +0 -0
  23. data/lib/grafana_reporter/application/webservice.rb +0 -0
  24. data/lib/grafana_reporter/asciidoctor/adoc_plain_table_format_strategy.rb +0 -0
  25. data/lib/grafana_reporter/asciidoctor/help.rb +30 -3
  26. data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +0 -0
  27. data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +0 -0
  28. data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +1 -1
  29. data/lib/grafana_reporter/asciidoctor/show_environment_include_processor.rb +0 -0
  30. data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +11 -2
  31. data/lib/grafana_reporter/asciidoctor/value_as_variable_include_processor.rb +0 -0
  32. data/lib/grafana_reporter/configuration.rb +27 -23
  33. data/lib/grafana_reporter/console_configuration_wizard.rb +0 -0
  34. data/lib/grafana_reporter/csv_table_format_strategy.rb +0 -0
  35. data/lib/grafana_reporter/demo_report_wizard.rb +0 -0
  36. data/lib/grafana_reporter/errors.rb +2 -2
  37. data/lib/grafana_reporter/panel_image_query.rb +0 -0
  38. data/lib/grafana_reporter/query_value_query.rb +4 -1
  39. data/lib/grafana_reporter/reporter_environment_datasource.rb +0 -0
  40. data/lib/ruby_grafana_reporter.rb +0 -0
  41. metadata +6 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '06912db0a0635560dca75c00c01c047deac42741c7c6ba2ee717cbccdf26cb19'
4
- data.tar.gz: 8d09e509266737bf1b73c3f6ad5c03255de461c116a6e5ec7b45bfaef56520b7
3
+ metadata.gz: 8b50055253026b61f26a424e6016118db18c4b1998e1a26f8fa6f4c8c37f9d6d
4
+ data.tar.gz: 9ce58b615705b752f13260b4497b297aa1ad193f018a5699e0d4dca53fb1a743
5
5
  SHA512:
6
- metadata.gz: 66e81686e253f8e98101f97b0b08db97f955cd1c274f2ed683f597b675c228adf198cd2c66f9ce3df05506a760564643e43b94c40fa952b13748122ecc8d6890
7
- data.tar.gz: 916ec07231acf19c20b0f01d7bfe9a47d5825a8e0c3a6c67a9fa615e2b993343d1506fb129cc72b66e2be9b02102c15e338a6e8b5746286494de4164a9678c38
6
+ metadata.gz: 3d96e06e01a4f5d861603528f64f57cfdbca4fe32a9790f8bc82982ea8581d6d07243aa5bfccd1fac018f1da1f1ad7a514fc31a9ff1f660aa2abf99ebfd5a2ab
7
+ data.tar.gz: 17b0f9e00461cea9c9fab69572d16b12a69494b06e52500c4f0ba01ced604116c57419726982a87cdd08f462465c4e4e9fe4a891211ff185be2ca52c67c5841d
data/README.md CHANGED
File without changes
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, 5, 0].freeze
4
+ GRAFANA_REPORTER_VERSION = [0, 5, 3].freeze
5
5
  # Release date
6
- GRAFANA_REPORTER_RELEASE_DATE = '2021-11-05'
6
+ GRAFANA_REPORTER_RELEASE_DATE = '2022-03-24'
@@ -57,6 +57,11 @@ module Grafana
57
57
  @model['name']
58
58
  end
59
59
 
60
+ # @return [String] unique ID of the datasource
61
+ def uid
62
+ @model['uid']
63
+ end
64
+
60
65
  # @return [Integer] ID of the datasource
61
66
  def id
62
67
  @model['id'].to_i
@@ -108,15 +113,14 @@ module Grafana
108
113
  raise NotImplementedError
109
114
  end
110
115
 
111
- private
112
-
113
116
  # Replaces the grafana variables in the given string with their replacement value.
114
117
  #
115
118
  # @param string [String] string in which the variables shall be replaced
116
119
  # @param variables [Hash<String,Variable>] Hash containing the variables, which shall be replaced in the
117
120
  # given string
121
+ # @param overwrite_default_format [String] {Variable#value_formatted} value, if a custom default format should be used, otherwise {#default_variable_format} is used as default, which may be overwritten
118
122
  # @return [String] string in which all variables are properly replaced
119
- def replace_variables(string, variables = {})
123
+ def replace_variables(string, variables, overwrite_default_format = nil)
120
124
  res = string
121
125
  repeat = true
122
126
  repeat_count = 0
@@ -136,7 +140,8 @@ module Grafana
136
140
  next unless var_name =~ /^\w+$/
137
141
 
138
142
  res = res.gsub(/(?:\$\{#{var_name}(?::(?<format>\w+))?\}|\$#{var_name}(?!\w))/) do
139
- format = default_variable_format
143
+ format = overwrite_default_format
144
+ format = default_variable_format if overwrite_default_format.nil?
140
145
  if $LAST_MATCH_INFO
141
146
  format = $LAST_MATCH_INFO[:format] if $LAST_MATCH_INFO[:format]
142
147
  end
@@ -148,5 +153,31 @@ module Grafana
148
153
 
149
154
  res
150
155
  end
156
+
157
+ private
158
+
159
+ # Provides a general method to handle the given query response as general Grafana Dataframe format.
160
+ #
161
+ # This method throws {UnsupportedQueryResponseReceivedError} if the given query response is not a
162
+ # properly formattes dataframe
163
+ #
164
+ # @param response_body [String] raw response body
165
+ def preformat_dataframe_response(response_body)
166
+ json = JSON.parse(response_body)
167
+ data = json['results'].values.first
168
+
169
+ # TODO: check how multiple frames have to be handled
170
+ data = data['frames']
171
+ headers = []
172
+ data.first['schema']['fields'].each do |headline|
173
+ header = headline['config']['displayNameFromDS'].nil? ? headline['name'] : headline['config']['displayNameFromDS']
174
+ headers << header
175
+ end
176
+ content = data.first['data']['values'][0].zip(data.first['data']['values'][1])
177
+ return { header: headers, content: content }
178
+
179
+ rescue
180
+ raise UnsupportedQueryResponseReceivedError, response_body
181
+ end
151
182
  end
152
183
  end
File without changes
@@ -71,4 +71,11 @@ module Grafana
71
71
  super("The datasource query provided, does not look like a grafana datasource target (received: #{query}).")
72
72
  end
73
73
  end
74
+
75
+ # Raised if a datasource query returned with an unsupported result
76
+ class UnsupportedQueryResponseReceivedError < GrafanaError
77
+ def initialize(response)
78
+ super("The datasource request returned with an unsupported response format (received: #{response}).")
79
+ end
80
+ end
74
81
  end
@@ -81,6 +81,17 @@ module Grafana
81
81
  @datasources[datasource_name]
82
82
  end
83
83
 
84
+ # Returns the datasource, which has been queried by the datasource uid.
85
+ #
86
+ # @param datasource_uid [String] unique id of the searched datasource
87
+ # @return [Datasource] Datasource for the specified datasource unique id
88
+ def datasource_by_uid(datasource_uid)
89
+ datasource = @datasources.select { |_name, ds| ds.uid == datasource_uid }.values.first
90
+ raise DatasourceDoesNotExistError.new('uid', datasource_uid) unless datasource
91
+
92
+ datasource
93
+ end
94
+
84
95
  # Returns the datasource, which has been queried by the datasource id.
85
96
  #
86
97
  # @param datasource_id [Integer] id of the searched datasource
File without changes
File without changes
@@ -43,10 +43,14 @@ module Grafana
43
43
 
44
44
  private
45
45
 
46
- # @see AbstractDatasource#preformat_response
47
46
  def preformat_response(response_body)
48
- json = JSON.parse(response_body)
47
+ begin
48
+ return preformat_dataframe_response(response_body)
49
+ rescue
50
+ # TODO: show an info, that the response if not a dataframe
51
+ end
49
52
 
53
+ json = JSON.parse(response_body)
50
54
  header = ['time']
51
55
  content = {}
52
56
 
@@ -66,7 +70,10 @@ module Grafana
66
70
  end
67
71
  end
68
72
 
69
- { header: header, content: content.to_a.map(&:flatten).sort { |a, b| a[0] <=> b[0] } }
73
+ return { header: header, content: content.to_a.map(&:flatten).sort { |a, b| a[0] <=> b[0] } }
74
+
75
+ rescue
76
+ raise UnsupportedQueryResponseReceivedError, response_body
70
77
  end
71
78
  end
72
79
  end
File without changes
@@ -23,9 +23,13 @@ module Grafana
23
23
  # replace $timeFilter variable
24
24
  query = query.gsub(/\$timeFilter(?=\W|$)/, "time >= #{query_description[:from]}ms and time <= #{query_description[:to]}ms")
25
25
 
26
+ interval = query_description[:variables].delete('interval') || ((query_description[:to].to_i - query_description[:from].to_i) / 1000).to_i
27
+ interval = interval.raw_value if interval.is_a?(Variable)
28
+
26
29
  # replace grafana variables $__interval and $__interval_ms in query
27
- query = query.gsub(/\$(?:__)?interval(?=\W|$)/, "#{((query_description[:to].to_i - query_description[:from].to_i) / 1000 / 1000).to_i}s")
28
- query = query.gsub(/\$(?:__)?interval_ms(?=\W|$)/, "#{((query_description[:to].to_i - query_description[:from].to_i) / 1000).to_i}")
30
+ # TODO: check where calculation and replacement of interval variable should take place
31
+ query = query.gsub(/\$(?:__)?interval(?=\W|$)/, "#{interval.is_a?(String) ? interval : "#{(interval / 1000).to_i}s"}")
32
+ query = query.gsub(/\$(?:__)?interval_ms(?=\W|$)/, "#{interval}")
29
33
 
30
34
  url = "/api/datasources/proxy/#{id}/query?db=#{@model['database']}&q=#{ERB::Util.url_encode(query)}&epoch=ms"
31
35
 
@@ -123,10 +127,16 @@ module Grafana
123
127
  "#{res} #{parts.join(', ')}"
124
128
  end
125
129
 
126
- # @see AbstractDatasource#preformat_response
127
130
  def preformat_response(response_body)
131
+ begin
132
+ return preformat_dataframe_response(response_body)
133
+ rescue
134
+ # TODO: show an info, that the response if not a dataframe
135
+ end
136
+
128
137
  # TODO: how to handle multiple query results?
129
- json = JSON.parse(response_body)['results'].first['series']
138
+ json = JSON.parse(response_body)
139
+ json = json['results'].first['series']
130
140
  return {} if json.nil?
131
141
 
132
142
  header = ['time']
@@ -148,7 +158,10 @@ module Grafana
148
158
  end
149
159
  end
150
160
 
151
- { header: header, content: content.to_a.map(&:flatten).sort { |a, b| a[0] <=> b[0] } }
161
+ return { header: header, content: content.to_a.map(&:flatten).sort { |a, b| a[0] <=> b[0] } }
162
+
163
+ rescue
164
+ raise UnsupportedQueryResponseReceivedError, response_body
152
165
  end
153
166
  end
154
167
  end
data/lib/grafana/panel.rb CHANGED
@@ -12,6 +12,11 @@ module Grafana
12
12
  def initialize(model, dashboard)
13
13
  @model = model
14
14
  @dashboard = dashboard
15
+
16
+ @datasource_uid_or_name = @model['datasource']
17
+ if @model['datasource'].is_a?(Hash)
18
+ @datasource_uid_or_name = @model['datasource']['uid']
19
+ end
15
20
  end
16
21
 
17
22
  # @return [String] content of the requested field or +''+ if not found
@@ -26,9 +31,22 @@ module Grafana
26
31
  @model['id']
27
32
  end
28
33
 
34
+ # This method should always be called before the +datasource+ method of a
35
+ # panel is invoked, to ensure that the variable names in the datasource
36
+ # field are resolved.
37
+ #
38
+ # @param variables [Hash] variables hash, which should be use to resolve variable datasource
39
+ def resolve_variable_datasource(variables)
40
+ @datasource_uid_or_name = AbstractDatasource.new(nil).replace_variables(@datasource_uid_or_name, variables, 'raw')
41
+ end
42
+
29
43
  # @return [Datasource] datasource object specified for the current panel
30
44
  def datasource
31
- dashboard.grafana.datasource_by_name(@model['datasource'])
45
+ if datasource_kind_is_uid?
46
+ dashboard.grafana.datasource_by_uid(@datasource_uid_or_name)
47
+ else
48
+ dashboard.grafana.datasource_by_name(@datasource_uid_or_name)
49
+ end
32
50
  end
33
51
 
34
52
  # @return [String] query string for the requested query letter
@@ -43,5 +61,14 @@ module Grafana
43
61
  def render_url
44
62
  "/render/d-solo/#{@dashboard.id}?panelId=#{@model['id']}"
45
63
  end
64
+
65
+ private
66
+
67
+ def datasource_kind_is_uid?
68
+ if @model['datasource'].is_a?(Hash)
69
+ return true
70
+ end
71
+ false
72
+ end
46
73
  end
47
74
  end
@@ -14,13 +14,25 @@ module Grafana
14
14
  def request(query_description)
15
15
  raise MissingSqlQueryError if query_description[:raw_query].nil?
16
16
 
17
- # TODO: properly allow endpoint to be set - also check raw_query method
18
- end_point = @endpoint ? @endpoint : "query_range"
17
+ query_hash = query_description[:raw_query].is_a?(Hash) ? query_description[:raw_query] : {}
19
18
 
20
- # TODO: set query option 'step' on request
21
- url = "/api/datasources/proxy/#{id}/api/v1/#{end_point}?"\
22
- "start=#{query_description[:from]}&end=#{query_description[:to]}"\
23
- "&query=#{replace_variables(query_description[:raw_query], query_description[:variables])}"
19
+ # read instant value and convert instant value to boolean value
20
+ instant = query_description[:variables].delete('instant') || query_hash[:instant] || false
21
+ instant = instant.raw_value if instant.is_a?(Variable)
22
+ instant = instant.to_s.downcase == 'true'
23
+ interval = query_description[:variables].delete('interval') || query_hash[:interval] || 15
24
+ interval = interval.raw_value if interval.is_a?(Variable)
25
+ query = query_hash[:query] || query_description[:raw_query]
26
+
27
+ url = if instant
28
+ "/api/datasources/proxy/#{id}/api/v1/query?time=#{query_description[:to]}&query="\
29
+ "#{CGI.escape(replace_variables(query, query_description[:variables]))}"
30
+ else
31
+ "/api/datasources/proxy/#{id}/api/v1/query_range?start=#{query_description[:from]}"\
32
+ "&end=#{query_description[:to]}"\
33
+ "&query=#{CGI.escape(replace_variables(query, query_description[:variables]))}"\
34
+ "&step=#{interval}"
35
+ end
24
36
 
25
37
  webrequest = query_description[:prepared_request]
26
38
  webrequest.relative_url = url
@@ -32,8 +44,8 @@ module Grafana
32
44
 
33
45
  # @see AbstractDatasource#raw_query_from_panel_model
34
46
  def raw_query_from_panel_model(panel_query_target)
35
- @endpoint = panel_query_target['format'] == 'time_series' && (panel_query_target['instant'] == false || !panel_query_target['instant']) ? 'query_range' : 'query'
36
- panel_query_target['expr']
47
+ { query: panel_query_target['expr'], instant: panel_query_target['instant'],
48
+ interval: panel_query_target['step'] }
37
49
  end
38
50
 
39
51
  # @see AbstractDatasource#default_variable_format
@@ -43,18 +55,44 @@ module Grafana
43
55
 
44
56
  private
45
57
 
46
- # @see AbstractDatasource#preformat_response
47
58
  def preformat_response(response_body)
48
- json = JSON.parse(response_body)['data']['result']
59
+ # TODO: show raw response body to debug case https://github.com/divinity666/ruby-grafana-reporter/issues/24
60
+ begin
61
+ return preformat_dataframe_response(response_body)
62
+ rescue
63
+ # TODO: show an info, that the response if not a dataframe
64
+ end
65
+
66
+ json = JSON.parse(response_body)
67
+
68
+ # handle response with error result
69
+ unless json['error'].nil?
70
+ return { header: ['error'], content: [[ json['error'] ]] }
71
+ end
72
+
73
+ # handle former result formats
74
+ result_type = json['data']['resultType']
75
+ json = json['data']['result']
49
76
 
50
77
  headers = ['time']
51
78
  content = {}
52
79
 
80
+ # handle vector queries
81
+ if result_type == 'vector'
82
+ return {
83
+ header: (headers << 'value') + json.first['metric'].keys,
84
+ content: [ [json.first['value'][0], json.first['value'][1]] + json.first['metric'].values ]
85
+ }
86
+ end
87
+
88
+ # handle scalar queries
89
+ if result_type =~ /^(?:scalar|string)$/
90
+ return { header: headers << result_type, content: [[json[0], json[1]]] }
91
+ end
92
+
53
93
  # keep sorting, if json has only one target item, otherwise merge results and return
54
94
  # as a time sorted array
55
- # TODO properly set headlines
56
95
  if json.length == 1
57
- return { header: headers << json.first['metric'].to_s, content: [[json.first['value'][1], json.first['value'][0]]] } if json.first.has_key?('value') # this happens for the special case of calls to '/query' endpoint
58
96
  return { header: headers << json.first['metric']['mode'], content: json.first['values'] }
59
97
  end
60
98
 
@@ -70,7 +108,10 @@ module Grafana
70
108
  end
71
109
  end
72
110
 
73
- { header: headers, content: content.to_a.map(&:flatten).sort { |a, b| a[0] <=> b[0] } }
111
+ return { header: headers, content: content.to_a.map(&:flatten).sort { |a, b| a[0] <=> b[0] } }
112
+
113
+ rescue
114
+ raise UnsupportedQueryResponseReceivedError, response_body
74
115
  end
75
116
  end
76
117
  end
@@ -47,6 +47,12 @@ module Grafana
47
47
  private
48
48
 
49
49
  def preformat_response(response_body)
50
+ begin
51
+ return preformat_dataframe_response(response_body)
52
+ rescue
53
+ # TODO: show an info, that the response if not a dataframe
54
+ end
55
+
50
56
  results = {}
51
57
  results.default = []
52
58
  results[:header] = []
@@ -57,16 +63,20 @@ module Grafana
57
63
  results[:header] = results[:header] + ['SQL Error']
58
64
  results[:content] = [[query_result['error']]]
59
65
 
60
- elsif query_result['tables']
61
- query_result['tables'].each do |table|
62
- results[:header] = results[:header] + table['columns'].map { |header| header['text'] }
63
- results[:content] = table['rows']
66
+ elsif query_result.key?('tables')
67
+ if query_result['tables']
68
+ query_result['tables'].each do |table|
69
+ results[:header] = results[:header] + table['columns'].map { |header| header['text'] }
70
+ results[:content] = table['rows']
71
+ end
64
72
  end
65
-
66
73
  end
67
74
  end
68
75
 
69
- results
76
+ return results
77
+
78
+ rescue
79
+ raise UnsupportedQueryResponseReceivedError, response_body
70
80
  end
71
81
  end
72
82
  end
File without changes
File without changes
@@ -89,7 +89,7 @@ module GrafanaReporter
89
89
  # grafana errors will be directly passed through
90
90
  raise
91
91
  rescue StandardError => e
92
- raise DatasourceRequestInternalError.new(@datasource, e.message)
92
+ raise DatasourceRequestInternalError.new(@datasource, "#{e.message}\n#{e.backtrace.join("\n")}")
93
93
  end
94
94
 
95
95
  raise DatasourceRequestInvalidReturnValueError.new(@datasource, @result) unless datasource_response_valid?
File without changes
File without changes
File without changes
File without changes
@@ -22,12 +22,13 @@ module GrafanaReporter
22
22
  private
23
23
 
24
24
  def github_options
25
- { headline_separator: '#', code_begin: '`', code_end: '`', table_begin: "\n", head_postfix_col: '| -- ' }
25
+ { headline_separator: '#', code_begin: '`', code_end: '`', table_begin: "\n", head_postfix_col: '| -- ',
26
+ table_linebreak: "<br />"}
26
27
  end
27
28
 
28
29
  def asciidoctor_options
29
30
  { headline_separator: '=', code_begin: '`+', code_end: '+`', table_begin: "\n[%autowidth.stretch, "\
30
- "options=\"header\"]\n|===\n", table_end: "\n|===" }
31
+ "options=\"header\"]\n|===\n", table_end: "\n|===", table_linebreak: "\n\n" }
31
32
  end
32
33
 
33
34
  def help_text(opts)
@@ -82,7 +83,7 @@ Usage: #{opts[:code_begin]}#{v[:call]}#{opts[:code_end]}
82
83
  #{v[:description]}#{"\n\nSee also: #{v[:see]}" if v[:see]}#{unless v[:options].empty?
83
84
  %(
84
85
  #{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('|', '\|')}#{"\nSee also: #{opt_v[:see]}" if opt_v[:see]}" }.join("\n") }#{opts[:table_end]})
86
+ #{v[:options].sort.map { |_opt_k, opt_v| "| #{opts[:code_begin]}#{opt_v[:call]}#{opts[:code_end]} | #{opt_v[:description].gsub('|', '\|')}#{"#{opts[:table_linebreak]}See also: #{opt_v[:see]}" if opt_v[:see]}" }.join("\n") }#{opts[:table_end]})
86
87
  end}
87
88
  )
88
89
  end
@@ -256,6 +257,20 @@ end}
256
257
  Set a timeout for the current query. If not overridden with `grafana_default_timeout` in the report template,
257
258
  this defaults to 60 seconds.
258
259
 
260
+ interval:
261
+ call: interval="<intervaL>"
262
+ description: >-
263
+ Used to set the interval size for timescale datasources, whereas the value is used without further
264
+ conversion directly in the datasource specific interval parameter.
265
+ Prometheus default: 15 (passed as `step` parameter)
266
+ Influx default: similar to grafana default, i.e. `(to_time - from_time) / 1000`
267
+ (replaces `interval_ms` and `interval` variables in query)
268
+
269
+ instant:
270
+ call: instant="true"
271
+ description: >-
272
+ Optional parameter for Prometheus `instant` queries. Ignored for other datasources than Prometheus.
273
+
259
274
  # ----------------------------------
260
275
  # FUNCTION DOCUMENTATION STARTS HERE
261
276
  # ----------------------------------
@@ -403,6 +418,8 @@ end}
403
418
  transpose:
404
419
  from_timezone:
405
420
  to_timezone:
421
+ instant:
422
+ interval:
406
423
 
407
424
  grafana_panel_query_value:
408
425
  call: 'grafana_panel_query_value:<panel_id>[query="<query_letter>",options]'
@@ -425,6 +442,8 @@ end}
425
442
  to:
426
443
  from_timezone:
427
444
  to_timezone:
445
+ instant:
446
+ interval:
428
447
 
429
448
  grafana_sql_table:
430
449
  call: 'include::grafana_sql_table:<datasource_id>[sql="<sql_query>",options]'
@@ -446,12 +465,18 @@ end}
446
465
  transpose:
447
466
  from_timezone:
448
467
  to_timezone:
468
+ instant:
469
+ interval:
449
470
 
450
471
  grafana_sql_value:
451
472
  call: 'grafana_sql_value:<datasource_id>[sql="<sql_query>",options]'
452
473
  description: >-
453
474
  Returns the value in the first column and the first row of the given query.
454
475
  Grafana variables will be replaced in the SQL statement.
476
+
477
+ Please note that asciidoctor might fail, if you use square brackets in your
478
+ sql statement. To overcome this issue, you'll need to escape the closing
479
+ square brackets, i.e. +]+ needs to be replaced with +\\]+.
455
480
  see: https://grafana.com/docs/grafana/latest/variables/syntax/
456
481
  standard_options:
457
482
  filter_columns:
@@ -463,6 +488,8 @@ end}
463
488
  to:
464
489
  from_timezone:
465
490
  to_timezone:
491
+ instant:
492
+ interval:
466
493
  YAML_HELP
467
494
  end
468
495
  end
@@ -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/x
43
+ column_divider|row_divider|instant|interval/x
44
44
  end)
45
45
 
46
46
  result
@@ -44,15 +44,24 @@ module GrafanaReporter
44
44
  @report.next_step
45
45
  instance = attrs['instance'] || parent.document.attr('grafana_default_instance') || 'default'
46
46
  attrs['result_type'] = 'sql_value'
47
+ sql = attrs['sql']
47
48
  @report.logger.debug("Processing SqlValueInlineMacro (instance: #{instance}, datasource: #{target},"\
48
- " sql: #{attrs['sql']})")
49
+ " sql: #{sql})")
50
+
51
+ # translate sql statement to fix asciidoctor issue
52
+ # refer https://github.com/asciidoctor/asciidoctor/issues/4072#issuecomment-991305715
53
+ sql_translated = CGI::unescapeHTML(sql) if sql
54
+ if sql != sql_translated
55
+ @report.logger.debug("Translating SQL query to fix asciidoctor issue: #{sql_translated}")
56
+ sql = sql_translated
57
+ end
49
58
 
50
59
  begin
51
60
  # catch properly if datasource could not be identified
52
61
  query = QueryValueQuery.new(@report.grafana(instance),
53
62
  variables: build_attribute_hash(parent.document.attributes, attrs))
54
63
  query.datasource = @report.grafana(instance).datasource_by_id(target)
55
- query.raw_query = attrs['sql']
64
+ query.raw_query = sql
56
65
 
57
66
  create_inline(parent, :quoted, query.execute)
58
67
  rescue Grafana::GrafanaError => e
@@ -263,13 +263,13 @@ module GrafanaReporter
263
263
  cur_pos
264
264
  end
265
265
 
266
- def validate_schema(schema, subject)
266
+ def validate_schema(schema, subject, pattern = nil)
267
267
  return nil if subject.nil?
268
268
 
269
269
  schema.each do |key, config|
270
- type, min_occurence, next_level = config
270
+ type, min_occurence, pattern, next_level = config
271
271
 
272
- validate_schema(next_level, subject[key]) if next_level
272
+ validate_schema(next_level, subject[key], pattern) if next_level
273
273
 
274
274
  if key.nil?
275
275
  # apply to all on this level
@@ -289,9 +289,13 @@ module GrafanaReporter
289
289
  elsif subject.is_a?(Hash)
290
290
  if !subject.key?(key) && min_occurence.positive?
291
291
  raise ConfigurationDoesNotMatchSchemaError.new(key, 'occur', min_occurence, 0)
292
- end
293
- if !subject[key].is_a?(type) && subject.key?(key)
292
+ elsif !subject[key].is_a?(type) && subject.key?(key)
294
293
  raise ConfigurationDoesNotMatchSchemaError.new(key, 'be a', type, subject[key].class)
294
+ elsif pattern
295
+ # validate for regex
296
+ unless subject[key].to_s =~ pattern
297
+ raise ConfigurationDoesNotMatchSchemaError.new(key, 'match pattern', pattern.inspect, subject[key].to_s)
298
+ end
295
299
  end
296
300
 
297
301
  else
@@ -312,35 +316,35 @@ module GrafanaReporter
312
316
  {
313
317
  'grafana' =>
314
318
  [
315
- Hash, 1,
319
+ Hash, 1, nil,
316
320
  {
317
321
  nil =>
318
322
  [
319
- Hash, 1,
323
+ Hash, 1, nil,
320
324
  {
321
- 'host' => [String, 1],
322
- 'api_key' => [String, 0]
325
+ 'host' => [String, 1, %r{^http(s)?://.+}],
326
+ 'api_key' => [String, 0, %r{^(?:[\w]+[=]*)?$}]
323
327
  }
324
328
  ]
325
329
  }
326
330
  ],
327
- 'default-document-attributes' => [Hash, explicit ? 1 : 0],
328
- 'to_file' => [String, 0],
331
+ 'default-document-attributes' => [Hash, explicit ? 1 : 0, nil],
332
+ 'to_file' => [String, 0, nil],
329
333
  'grafana-reporter' =>
330
334
  [
331
- Hash, 1,
335
+ Hash, 1, nil,
332
336
  {
333
- 'check-for-updates' => [Integer, 0],
334
- 'debug-level' => [String, 0],
335
- 'run-mode' => [String, 0],
336
- 'test-instance' => [String, 0],
337
- 'templates-folder' => [String, explicit ? 1 : 0],
338
- 'report-class' => [String, 1],
339
- 'reports-folder' => [String, explicit ? 1 : 0],
340
- 'report-retention' => [Integer, explicit ? 1 : 0],
341
- 'ssl-cert' => [String, 0],
342
- 'webservice-port' => [Integer, explicit ? 1 : 0],
343
- 'callbacks' => [Hash, 0, { nil => [String, 1] }]
337
+ 'check-for-updates' => [Integer, 0, /^[0-9]*$/],
338
+ 'debug-level' => [String, 0, /^(?:DEBUG|INFO|WARN|ERROR|FATAL|UNKNOWN)?$/],
339
+ 'run-mode' => [String, 0, /^(?:test|single-render|webservice)?$/],
340
+ 'test-instance' => [String, 0, nil],
341
+ 'templates-folder' => [String, explicit ? 1 : 0, nil],
342
+ 'report-class' => [String, 1, nil],
343
+ 'reports-folder' => [String, explicit ? 1 : 0, nil],
344
+ 'report-retention' => [Integer, explicit ? 1 : 0, nil],
345
+ 'ssl-cert' => [String, 0, nil],
346
+ 'webservice-port' => [Integer, explicit ? 1 : 0, nil],
347
+ 'callbacks' => [Hash, 0, nil, { nil => [String, 1, nil] }]
344
348
  }
345
349
  ]
346
350
  }
File without changes
File without changes
File without changes
@@ -27,7 +27,7 @@ module GrafanaReporter
27
27
  # Raised if the return value of a datasource request does not match the expected return hash.
28
28
  class DatasourceRequestInvalidReturnValueError < GrafanaReporterError
29
29
  def initialize(datasource, message)
30
- super("The datasource request to '#{datasource.name}' (#{datasource.class})"\
30
+ super("The datasource request to '#{datasource.name}' (#{datasource.class}) "\
31
31
  "returned an invalid value: '#{message}'")
32
32
  end
33
33
  end
@@ -65,7 +65,7 @@ module GrafanaReporter
65
65
  # Details about how to fix that are provided in the message.
66
66
  class ConfigurationDoesNotMatchSchemaError < ConfigurationError
67
67
  def initialize(item, verb, expected, currently)
68
- super("Configuration file does not match schema definition. Expected '#{item}' to #{verb} '#{expected}',"\
68
+ super("Configuration file does not match schema definition. Expected '#{item}' to #{verb} '#{expected}', "\
69
69
  "but was '#{currently}'.")
70
70
  end
71
71
  end
File without changes
@@ -5,7 +5,10 @@ module GrafanaReporter
5
5
  class QueryValueQuery < AbstractQuery
6
6
  # @see Grafana::AbstractQuery#pre_process
7
7
  def pre_process
8
- @datasource = @panel.datasource if @panel
8
+ if @panel
9
+ @panel.resolve_variable_datasource(@variables)
10
+ @datasource = @panel.datasource
11
+ end
9
12
 
10
13
  @variables['result_type'] ||= Variable.new('')
11
14
  end
File without changes
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.5.0
4
+ version: 0.5.3
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-05 00:00:00.000000000 Z
11
+ date: 2022-03-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: asciidoctor
@@ -100,9 +100,9 @@ dependencies:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
102
  version: '3.9'
103
- description: Build reports based on grafana dashboards in asciidoctor syntax. Runs
104
- as webservice for easy integration with grafana, or as a standalone, command line
105
- utility.
103
+ description: Build reports based on grafana dashboards in asciidoctor or ERB syntax.
104
+ Runs as webservice for easy integration with grafana, or as a standalone, command
105
+ line utility.
106
106
  email: kohly@gmx.de
107
107
  executables:
108
108
  - ruby-grafana-reporter
@@ -193,8 +193,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
193
193
  - !ruby/object:Gem::Version
194
194
  version: '0'
195
195
  requirements: []
196
- rubyforge_project:
197
- rubygems_version: 2.7.6.2
196
+ rubygems_version: 3.2.5
198
197
  signing_key:
199
198
  specification_version: 4
200
199
  summary: Reporter Service for Grafana