ruby-grafana-reporter 0.4.5 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/lib/VERSION.rb +2 -2
  4. data/lib/grafana/abstract_datasource.rb +8 -0
  5. data/lib/grafana/dashboard.rb +6 -1
  6. data/lib/grafana/errors.rb +9 -1
  7. data/lib/grafana/grafana.rb +35 -0
  8. data/lib/grafana/grafana_environment_datasource.rb +56 -0
  9. data/lib/grafana/graphite_datasource.rb +3 -0
  10. data/lib/grafana/image_rendering_datasource.rb +5 -1
  11. data/lib/grafana/influxdb_datasource.rb +11 -4
  12. data/lib/grafana/panel.rb +5 -1
  13. data/lib/grafana/prometheus_datasource.rb +71 -11
  14. data/lib/grafana/sql_datasource.rb +10 -4
  15. data/lib/grafana/variable.rb +46 -23
  16. data/lib/grafana/webrequest.rb +1 -0
  17. data/lib/grafana_reporter/abstract_query.rb +31 -24
  18. data/lib/grafana_reporter/abstract_report.rb +2 -0
  19. data/lib/grafana_reporter/abstract_table_format_strategy.rb +44 -4
  20. data/lib/grafana_reporter/alerts_table_query.rb +2 -1
  21. data/lib/grafana_reporter/annotations_table_query.rb +2 -1
  22. data/lib/grafana_reporter/application/webservice.rb +8 -4
  23. data/lib/grafana_reporter/asciidoctor/adoc_plain_table_format_strategy.rb +11 -9
  24. data/lib/grafana_reporter/asciidoctor/help.rb +53 -14
  25. data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +2 -4
  26. data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +2 -4
  27. data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +1 -1
  28. data/lib/grafana_reporter/asciidoctor/show_environment_include_processor.rb +37 -6
  29. data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +11 -2
  30. data/lib/grafana_reporter/asciidoctor/value_as_variable_include_processor.rb +0 -5
  31. data/lib/grafana_reporter/configuration.rb +53 -22
  32. data/lib/grafana_reporter/console_configuration_wizard.rb +3 -1
  33. data/lib/grafana_reporter/csv_table_format_strategy.rb +11 -9
  34. data/lib/grafana_reporter/demo_report_wizard.rb +3 -6
  35. data/lib/grafana_reporter/errors.rb +2 -2
  36. data/lib/grafana_reporter/panel_image_query.rb +0 -1
  37. data/lib/grafana_reporter/query_value_query.rb +7 -1
  38. data/lib/grafana_reporter/reporter_environment_datasource.rb +24 -0
  39. data/lib/ruby_grafana_reporter.rb +7 -7
  40. metadata +8 -7
@@ -13,7 +13,6 @@ module GrafanaReporter
13
13
  attr_reader :variables, :result, :panel, :dashboard
14
14
 
15
15
  def timeout
16
- # TODO: PRIO check where value priorities should be evaluated
17
16
  return @variables['timeout'].raw_value if @variables['timeout']
18
17
  return @variables['grafana_default_timeout'].raw_value if @variables['grafana_default_timeout']
19
18
 
@@ -44,6 +43,7 @@ module GrafanaReporter
44
43
  else
45
44
  raise GrafanaReporterError, "Internal error in AbstractQuery: given object is of type #{grafana_obj.class.name}, which is not supported"
46
45
  end
46
+ @logger = @grafana ? @grafana.logger : ::Logger.new($stderr, level: :info)
47
47
  @variables = {}
48
48
  @variables['from'] = Grafana::Variable.new(nil)
49
49
  @variables['to'] = Grafana::Variable.new(nil)
@@ -79,7 +79,7 @@ module GrafanaReporter
79
79
  raise DatasourceNotSupportedError.new(@datasource, self) if @datasource.is_a?(Grafana::UnsupportedDatasource)
80
80
 
81
81
  begin
82
- @result = @datasource.request(from: from, to: to, raw_query: raw_query, variables: grafana_variables,
82
+ @result = @datasource.request(from: from, to: to, raw_query: raw_query, variables: @variables,
83
83
  prepared_request: @grafana.prepare_request, timeout: timeout)
84
84
  rescue ::Grafana::GrafanaError
85
85
  # grafana errors will be directly passed through
@@ -143,6 +143,8 @@ module GrafanaReporter
143
143
  #
144
144
  # Multiple columns may be filtered. Therefore the column titles have to be named in the
145
145
  # {Grafana::Variable#raw_value} and have to be separated by +,+ (comma).
146
+ #
147
+ # Commas can be used in a format string, but need to be escaped by using +_,+.
146
148
  # @param result [Hash] preformatted sql hash, (see {Grafana::AbstractDatasource#request})
147
149
  # @param filter_columns_variable [Grafana::Variable] column names, which shall be removed in the query result
148
150
  # @return [Hash] filtered query result
@@ -150,8 +152,8 @@ module GrafanaReporter
150
152
  return result unless filter_columns_variable
151
153
 
152
154
  filter_columns = filter_columns_variable.raw_value
153
- filter_columns.split(',').each do |filter_column|
154
- pos = result[:header].index(filter_column)
155
+ filter_columns.split(/(?<!_),/).each do |filter_column|
156
+ pos = result[:header].index(filter_column.gsub("_,", ","))
155
157
 
156
158
  unless pos.nil?
157
159
  result[:header].delete_at(pos)
@@ -167,23 +169,33 @@ module GrafanaReporter
167
169
  # The formatting will be applied separately for every column. Therefore the column formats have to be named
168
170
  # in the {Grafana::Variable#raw_value} and have to be separated by +,+ (comma). If no value is specified for
169
171
  # a column, no change will happen.
172
+ #
173
+ # It is also possible to format milliseconds as dates by specifying date formats, e.g. +date:iso+. It is
174
+ # possible to use any date format according
175
+ # {https://grafana.com/docs/grafana/latest/variables/variable-types/global-variables/#from-and-to}
176
+ #
177
+ # Commas can be used in a format string, but need to be escaped by using +_,+.
170
178
  # @param result [Hash] preformatted sql hash, (see {Grafana::AbstractDatasource#request})
171
179
  # @param formats [Grafana::Variable] formats, which shall be applied to the columns in the query result
172
180
  # @return [Hash] formatted query result
173
181
  def format_columns(result, formats)
174
182
  return result unless formats
175
183
 
176
- formats.text.split(',').each_index do |i|
177
- format = formats.text.split(',')[i]
184
+ formats.text.split(/(?<!_),/).each_index do |i|
185
+ format = formats.text.split(/(?<!_),/)[i].gsub("_,", ",")
178
186
  next if format.empty?
179
187
 
180
188
  result[:content].map do |row|
181
189
  next unless row.length > i
182
190
 
183
191
  begin
184
- row[i] = format % row[i] if row[i]
192
+ if format =~ /^date:/
193
+ row[i] = ::Grafana::Variable.format_as_date(row[i], format.sub(/^date:/, '')) if row[i]
194
+ else
195
+ row[i] = format % row[i] if row[i]
196
+ end
185
197
  rescue StandardError => e
186
- @grafana.logger.error(e.message)
198
+ @logger.error(e.message)
187
199
  row[i] = e.message
188
200
  end
189
201
  end
@@ -248,7 +260,7 @@ module GrafanaReporter
248
260
  begin
249
261
  row[i] = row[i].to_s.gsub(/#{k}/, v) if row[i].to_s =~ /#{k}/
250
262
  rescue StandardError => e
251
- @grafana.logger.error(e.message)
263
+ @logger.error(e.message)
252
264
  row[i] = e.message
253
265
  end
254
266
 
@@ -273,7 +285,7 @@ module GrafanaReporter
273
285
  end
274
286
  end
275
287
  rescue StandardError => e
276
- @grafana.logger.error(e.message)
288
+ @logger.error(e.message)
277
289
  row[i] = e.message
278
290
  end
279
291
  end
@@ -298,17 +310,19 @@ module GrafanaReporter
298
310
  # @option opts [Grafana::Variable] :column_divider requested row divider for the result table, only to be used with table_formatter `adoc_deprecated`
299
311
  # @option opts [Grafana::Variable] :include_headline specifies if table should contain headline, defaults to false
300
312
  # @option opts [Grafana::Variable] :table_formatter specifies which formatter shall be used, defaults to 'csv'
313
+ # @option opts [Grafana::Variable] :transposed specifies whether the result table is transposed
301
314
  # @return [String] table in custom output format
302
315
  def format_table_output(result, opts)
303
316
  opts = { include_headline: Grafana::Variable.new('false'),
304
317
  table_formatter: Grafana::Variable.new('csv'),
305
318
  row_divider: Grafana::Variable.new('| '),
306
- column_divider: Grafana::Variable.new(' | ') }.merge(opts.delete_if {|_k, v| v.nil? })
319
+ column_divider: Grafana::Variable.new(' | '),
320
+ transpose: Grafana::Variable.new('false') }.merge(opts.delete_if {|_k, v| v.nil? })
307
321
 
308
322
  if opts[:table_formatter].raw_value == 'adoc_deprecated'
309
- @grafana.logger.warn("You are using deprecated 'table_formatter' named 'adoc_deprecated', which will be "\
310
- "removed in a future version. Start using 'adoc_plain' or register your own "\
311
- "implementation of AbstractTableFormatStrategy.")
323
+ @logger.warn("You are using deprecated 'table_formatter' named 'adoc_deprecated', which will be "\
324
+ "removed in a future version. Start using 'adoc_plain' or register your own "\
325
+ "implementation of AbstractTableFormatStrategy.")
312
326
  return result[:content].map do |row|
313
327
  opts[:row_divider].raw_value + row.map do |item|
314
328
  item.to_s.gsub('|', '\\|')
@@ -316,7 +330,7 @@ module GrafanaReporter
316
330
  end.join("\n")
317
331
  end
318
332
 
319
- AbstractTableFormatStrategy.get(opts[:table_formatter].raw_value).format(result, opts[:include_headline].raw_value.downcase == 'true')
333
+ AbstractTableFormatStrategy.get(opts[:table_formatter].raw_value).format(result, opts[:include_headline].raw_value.downcase == 'true', opts[:transpose].raw_value.downcase == 'true')
320
334
  end
321
335
 
322
336
  # Used to translate the relative date strings used by grafana, e.g. +now-5d/w+ to the
@@ -334,7 +348,7 @@ module GrafanaReporter
334
348
  # @param timezone [Grafana::Variable] timezone to use, if not system timezone
335
349
  # @return [String] translated date as timestamp string
336
350
  def translate_date(orig_date, report_time, is_to_time, timezone = nil)
337
- @grafana.logger.warn("#translate_date has been called without 'report_time' - using current time as fallback.") unless report_time
351
+ @logger.warn("#translate_date has been called without 'report_time' - using current time as fallback.") unless report_time
338
352
  report_time ||= ::Grafana::Variable.new(Time.now.to_s)
339
353
  orig_date = orig_date.raw_value if orig_date.is_a?(Grafana::Variable)
340
354
 
@@ -405,8 +419,7 @@ module GrafanaReporter
405
419
  def datasource_response_valid?
406
420
  return false if @result.nil?
407
421
  return false unless @result.is_a?(Hash)
408
- # TODO: compare how empty valid responses look like in grafana
409
- return true if @result.empty?
422
+ return false if @result.empty?
410
423
  return false unless @result.key?(:header)
411
424
  return false unless @result.key?(:content)
412
425
  return false unless @result[:header].is_a?(Array)
@@ -415,12 +428,6 @@ module GrafanaReporter
415
428
  true
416
429
  end
417
430
 
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
431
  def delta_date(date, delta_count, time_letter)
425
432
  # substract specified time
426
433
  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
- # @abstract
27
- # @param column [Array] datasource table result
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
- raise NotImplementedError
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
- accept_requests_loop
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('.') %>.</p>
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#format
14
- def format(result, include_headline)
15
- headline = '| ' + result[:header].map { |item| item.to_s.gsub(' | ', '\\|') }.join(' | ')
16
-
17
- content = result[:content].map do |row|
18
- '| ' + row.map { |item| item.to_s.gsub(' | ', '\\|') }.join(' | ')
19
- end.join("\n")
20
-
21
- "#{"#{headline}\n" if include_headline}#{content}"
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
@@ -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('|', '\|')}" }.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
@@ -109,12 +110,12 @@ end}
109
110
  res_item[:description] = item['description']
110
111
  res_item[:see] = item['see']
111
112
 
112
- opts = ((item['options'] ? item['options'].keys : [])
113
+ opts = ((item['options'] ? item['options'].keys : []) +
113
114
  (item['standard_options'] ? item['standard_options'].keys : [])).sort
114
115
  opts.each do |opt_key|
115
116
  res_item[:options][opt_key] = {}
116
117
 
117
- if item['standard_options'].key?(opt_key)
118
+ if std_opts.key?(opt_key)
118
119
  res_item[:options][opt_key][:call] = std_opts[opt_key]['call']
119
120
  res_item[:options][opt_key][:description] = "#{std_opts[opt_key]['description']} "\
120
121
  "#{item['standard_options'][opt_key]}".chop
@@ -196,8 +197,12 @@ end}
196
197
  format:
197
198
  call: format="<format_col1>,<format_col2>,..."
198
199
  description: >-
199
- Specify format in which the results shall be returned, e.g. `%.2f` for only two digit decimals of a
200
- float. Several columns are separated by `,`. Execution is applied in the following order `format`,
200
+ Specify format in which the results in a specific column shall be returned, e.g. `%.2f` for only
201
+ two digit decimals of a float. Several column formats are separated by `,`, i.e. `%.2f,%.3f` would
202
+ apply `%.2f` to the first column and `%.3f` to the second column. All other columns would not be
203
+ formatted. You may also format time in milliseconds to a time format by specifying e.g. `date:iso`.
204
+ Commas in format strings are supported, but have to be escaped by useing `_,`.
205
+ Execution of related functions is applied in the following order `format`,
201
206
  `replace_values`, `filter_columns`, `transpose`.
202
207
  see: 'https://ruby-doc.org/core/Kernel.html#method-i-sprintf'
203
208
 
@@ -207,21 +212,24 @@ end}
207
212
  Specify result values which shall be replaced, e.g. `2:OK` will replace query values `2` with value `OK`.
208
213
  Replacing several values is possible by separating by `,`. Matches with regular expressions are also
209
214
  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 is applied in the following order `format`,
215
+ Number replacements can also be performed, e.g. `<8.2` or `<>3`. Execution of related functions is
216
+ applied in the following order `format`,
211
217
  `replace_values`, `filter_columns`, `transpose`.
212
218
  see: https://ruby-doc.org/core/Regexp.html#class-Regexp-label-Character+Classes
213
219
 
214
220
  filter_columns:
215
221
  call: filter_columns="<column_name_1>,<column_name_2>,..."
216
222
  description: >-
217
- Removes specified columns from result. Execution is applied in the following order `format`, `replace_values`,
218
- `filter_columns`, `transpose`.
223
+ Removes specified columns from result. Commas in format strings are supported, but have to be
224
+ escaped by useing `_,`. Execution of related functions is applied in the following order
225
+ `format`, `replace_values`, `filter_columns`, `transpose`.
219
226
 
220
227
  transpose:
221
228
  call: transpose="true"
222
229
  description: >-
223
- Transposes the query result, i.e. columns become rows and rows become columnns. Execution is applied in the
224
- following order `format`, `replace_values`, `filter_columns`, `transpose`.
230
+ Transposes the query result, i.e. columns become rows and rows become columnns. Execution of related
231
+ functions is applied in the following order `format`, `replace_values`, `filter_columns`,
232
+ `transpose`.
225
233
 
226
234
  column_divider:
227
235
  call: column_divider="<divider>"
@@ -234,7 +242,7 @@ end}
234
242
  call: row_divider="<divider>"
235
243
  description: >-
236
244
  Replace the default row divider with another one, when used in conjunction with `table_formatter` set to
237
- `adoc_deprecated`. . Defaults to `| ` for being interpreted as a asciidoctor row. DEPRECATED: switch to
245
+ `adoc_deprecated`. Defaults to `| ` for being interpreted as a asciidoctor row. DEPRECATED: switch to
238
246
  `table_formatter` named `adoc_plain`, or implement a custom table formatter.
239
247
 
240
248
  table_formatter:
@@ -249,6 +257,20 @@ end}
249
257
  Set a timeout for the current query. If not overridden with `grafana_default_timeout` in the report template,
250
258
  this defaults to 60 seconds.
251
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
+
252
274
  # ----------------------------------
253
275
  # FUNCTION DOCUMENTATION STARTS HERE
254
276
  # ----------------------------------
@@ -258,8 +280,13 @@ end}
258
280
  call: 'include::grafana_help[]'
259
281
 
260
282
  grafana_environment:
261
- description: Shows all available variables in the rendering context which can be used in the asciidoctor template.
283
+ description: >-
284
+ Shows all available variables in the rendering context which can be used in the asciidoctor template.
285
+ If optional `instance` is specified, additional information about the configured grafana instance will be provided.
286
+ This is especially helpful for debugging.
262
287
  call: 'include::grafana_environment[]'
288
+ standard_options:
289
+ instance:
263
290
 
264
291
  grafana_alerts:
265
292
  description: >-
@@ -391,6 +418,8 @@ end}
391
418
  transpose:
392
419
  from_timezone:
393
420
  to_timezone:
421
+ instant:
422
+ interval:
394
423
 
395
424
  grafana_panel_query_value:
396
425
  call: 'grafana_panel_query_value:<panel_id>[query="<query_letter>",options]'
@@ -413,6 +442,8 @@ end}
413
442
  to:
414
443
  from_timezone:
415
444
  to_timezone:
445
+ instant:
446
+ interval:
416
447
 
417
448
  grafana_sql_table:
418
449
  call: 'include::grafana_sql_table:<datasource_id>[sql="<sql_query>",options]'
@@ -434,12 +465,18 @@ end}
434
465
  transpose:
435
466
  from_timezone:
436
467
  to_timezone:
468
+ instant:
469
+ interval:
437
470
 
438
471
  grafana_sql_value:
439
472
  call: 'grafana_sql_value:<datasource_id>[sql="<sql_query>",options]'
440
473
  description: >-
441
474
  Returns the value in the first column and the first row of the given query.
442
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 +\\]+.
443
480
  see: https://grafana.com/docs/grafana/latest/variables/syntax/
444
481
  standard_options:
445
482
  filter_columns:
@@ -451,6 +488,8 @@ end}
451
488
  to:
452
489
  from_timezone:
453
490
  to_timezone:
491
+ instant:
492
+ interval:
454
493
  YAML_HELP
455
494
  end
456
495
  end
@@ -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 an asciidoctor link
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 an asciidoctor link
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
@@ -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
@@ -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, _attrs)
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
- vars = ['== Accessible Variables',
31
- '|===']
60
+ result += ['|===', '',
61
+ '== Accessible Variables',
62
+ '|===']
32
63
  doc.attributes.sort.each do |k, v|
33
- vars << "| `+{#{k}}+` | #{v}"
64
+ result << "| `+{#{k}}+` | #{v}"
34
65
  end
35
- vars << '|==='
66
+ result << '|==='
36
67
 
37
- reader.unshift_lines vars
68
+ reader.unshift_lines result
38
69
  end
39
70
 
40
71
  # @see ProcessorMixin#build_demo_entry
@@ -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
@@ -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