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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd6bdf1a9856b3c0a663060c4b2ecfc3a094ae230635807398a611d38d74ac90
4
- data.tar.gz: 95ac2750e962a67b0d3b71184b5409b88441840f4b08f31284b33f20b0d862a3
3
+ metadata.gz: de6063d128d57f930e83b17253e34e4e20624ef796e7eb86df2e7d54bee7825c
4
+ data.tar.gz: a6a5f8b3bfcdcfc557ede19e7d89a25de8dca277806da0f662c4165d0a05b03c
5
5
  SHA512:
6
- metadata.gz: 53698b98b33afef1706243cf5c322bd1a9968ec723e18d32ec5d550769117fa9c64c43c9d174752630ac7f8e8015d1394d4f2060fbe6be1b2ce73d7ad8390801
7
- data.tar.gz: 5f39c12e056e689ff442f00c175219e6a560e9e38362a5aec9f67dc738b172ebf0b9d288958e312020f8304603e54ff1c4e21ca44d77182342a3df9fe03825ba
6
+ metadata.gz: c9338b1dbd16a81af0c28feb9e8f4efd576ffb3f879aae7dd5d31b00468b32cc18ee49e7a73f87e952e2781845cd1d54124512aef6112194379d3840322fbff8
7
+ data.tar.gz: 01bed067acfc6442e62ba17dbda3491e6147778a274c65ccacc7f838e57beda53809e8a8b827fad09266b4b506c2e29d7e8d3b0670c506eb0063eddd40d1aa26
data/README.md CHANGED
@@ -30,7 +30,7 @@ professional reporting functionality. And this is, where the ruby grafana report
30
30
  steps in.
31
31
 
32
32
  The key functionality of the reporter is to capture data and images from grafana
33
- dashboards and to use it in your custom templates to finally create reports in PDF
33
+ dashboards and to use it in your custom templates to finally create reports in PDF
34
34
  (default), HTML, or any other format.
35
35
 
36
36
  By default (an extended version of) Asciidoctor is enabled as template language.
@@ -65,7 +65,7 @@ Database | Image rendering | Raw queries | Composed queries
65
65
  ------------------------- | :-------------: | :-----------: | :------------:
66
66
  all SQL based datasources | supported | supported | supported
67
67
  Graphite | supported | supported | supported
68
- InfluxDB | supported | supported | not (yet) supported
68
+ InfluxDB | supported | supported | supported
69
69
  Prometheus | supported | supported | n/a in grafana
70
70
  other datasources | supported | not supported | not supported
71
71
 
data/lib/VERSION.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Version information
4
- GRAFANA_REPORTER_VERSION = [0, 4, 5].freeze
4
+ GRAFANA_REPORTER_VERSION = [0, 5, 2].freeze
5
5
  # Release date
6
- GRAFANA_REPORTER_RELEASE_DATE = '2021-08-26'
6
+ GRAFANA_REPORTER_RELEASE_DATE = '2022-03-22'
@@ -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
@@ -128,6 +133,9 @@ module Grafana
128
133
  repeat_count += 1
129
134
 
130
135
  variables.each do |name, variable|
136
+ # do not replace with non grafana variables
137
+ next unless name =~ /^var-/
138
+
131
139
  # only set ticks if value is string
132
140
  var_name = name.gsub(/^var-/, '')
133
141
  next unless var_name =~ /^\w+$/
@@ -35,6 +35,11 @@ module Grafana
35
35
  @model['uid']
36
36
  end
37
37
 
38
+ # @return [String] dashboard title
39
+ def title
40
+ @model['title']
41
+ end
42
+
38
43
  # @return [Panel] panel for the specified ID
39
44
  def panel(id)
40
45
  panels = @panels.select { |item| item.field('id') == id.to_i }
@@ -53,7 +58,7 @@ module Grafana
53
58
  list = @model['templating']['list']
54
59
  return unless list.is_a? Array
55
60
 
56
- list.each { |item| @variables << Variable.new(item) }
61
+ list.each { |item| @variables << Variable.new(item, self) }
57
62
  end
58
63
 
59
64
  # read panels
@@ -53,7 +53,8 @@ module Grafana
53
53
  class ImageCouldNotBeRenderedError < GrafanaError
54
54
  def initialize(panel)
55
55
  super("The specified panel '#{panel.id}' from dashboard '#{panel.dashboard.id}' could not be "\
56
- 'rendered to an image.')
56
+ 'rendered to an image. Check if rendering is possible manually by selecting "Share" and then '\
57
+ '"Direct link rendered image" from a panel\'s options menu.')
57
58
  end
58
59
  end
59
60
 
@@ -70,4 +71,11 @@ module Grafana
70
71
  super("The datasource query provided, does not look like a grafana datasource target (received: #{query}).")
71
72
  end
72
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
73
81
  end
@@ -25,6 +25,30 @@ module Grafana
25
25
  initialize_datasources unless @base_uri.empty?
26
26
  end
27
27
 
28
+ # @return [Hash] Information about the current organization
29
+ def organization
30
+ return @organization if @organization
31
+
32
+ response = prepare_request({ relative_url: '/api/org/' }).execute
33
+ if response.is_a?(Net::HTTPOK)
34
+ @organization = JSON.parse(response.body)
35
+ end
36
+
37
+ @organization
38
+ end
39
+
40
+ # @return [String] grafana version
41
+ def version
42
+ return @version if @version
43
+
44
+ response = prepare_request({ relative_url: '/api/health' }).execute
45
+ if response.is_a?(Net::HTTPOK)
46
+ @version = JSON.parse(response.body)['version']
47
+ end
48
+
49
+ @version
50
+ end
51
+
28
52
  # Used to test a connection to the grafana instance.
29
53
  #
30
54
  # Running this function also determines, if the API configured here has Admin or NON-Admin privileges,
@@ -57,6 +81,17 @@ module Grafana
57
81
  @datasources[datasource_name]
58
82
  end
59
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
+
60
95
  # Returns the datasource, which has been queried by the datasource id.
61
96
  #
62
97
  # @param datasource_id [Integer] id of the searched datasource
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grafana
4
+ # Implements a datasource to return environment related information about the grafana instance in a tabular format.
5
+ class GrafanaEnvironmentDatasource < ::Grafana::AbstractDatasource
6
+ # +:raw_query+ needs to contain a Hash with the following structure:
7
+ #
8
+ # {
9
+ # grafana: {Grafana} object to query
10
+ # mode: 'general' (default) or 'dashboards' for receiving different environment information
11
+ # }
12
+ # @see AbstractDatasource#request
13
+ def request(query_description)
14
+ raise MissingSqlQueryError if query_description[:raw_query].nil?
15
+ raw_query = {mode: 'general'}.merge(query_description[:raw_query])
16
+
17
+ return dashboards_data(raw_query[:grafana]) if raw_query[:mode] == 'dashboards'
18
+
19
+ general_data(raw_query[:grafana])
20
+ end
21
+
22
+ # @see AbstractDatasource#default_variable_format
23
+ def default_variable_format
24
+ nil
25
+ end
26
+
27
+ # @see AbstractDatasource#name
28
+ def name
29
+ self.class.to_s
30
+ end
31
+
32
+ private
33
+
34
+ def general_data(grafana)
35
+ {
36
+ header: ['Version', 'Organization Name', 'Organization ID', 'Access permissions'],
37
+ content: [[grafana.version,
38
+ grafana.organization['name'],
39
+ grafana.organization['id'],
40
+ grafana.test_connection]]
41
+ }
42
+ end
43
+
44
+ def dashboards_data(grafana)
45
+ content = []
46
+ grafana.dashboard_ids.each do |id|
47
+ content << [id, grafana.dashboard(id).title, grafana.dashboard(id).panels.length]
48
+ end
49
+
50
+ {
51
+ header: ['Dashboard ID', 'Dashboard Name', '# Panels'],
52
+ content: content
53
+ }
54
+ end
55
+ end
56
+ end
@@ -47,6 +47,9 @@ module Grafana
47
47
  def preformat_response(response_body)
48
48
  json = JSON.parse(response_body)
49
49
 
50
+ raise UnsupportedQueryResponseReceivedError, response_body if json.first['target'].nil?
51
+ raise UnsupportedQueryResponseReceivedError, response_body if json.first['datapoints'].nil?
52
+
50
53
  header = ['time']
51
54
  content = {}
52
55
 
@@ -10,12 +10,16 @@ module Grafana
10
10
  # }
11
11
  # @see AbstractDatasource#request
12
12
  def request(query_description)
13
+ panel = query_description[:raw_query][:panel]
14
+
13
15
  webrequest = query_description[:prepared_request]
14
- webrequest.relative_url = query_description[:raw_query][:panel].render_url + url_params(query_description)
16
+ webrequest.relative_url = panel.render_url + url_params(query_description)
15
17
  webrequest.options.merge!({ accept: 'image/png' })
16
18
 
17
19
  result = webrequest.execute
18
20
 
21
+ raise ImageCouldNotBeRenderedError, panel if result.body.include?('<html')
22
+
19
23
  { header: ['image'], content: [result.body] }
20
24
  end
21
25
 
@@ -23,11 +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
- # TODO: influx datasource currently uses a fixed values of 1000 width for interval variables specified in a query - it should be possible to calculate this according to grafana
28
30
  # TODO: check where calculation and replacement of interval variable should take place
29
- query = query.gsub(/\$(?:__)?interval(?=\W|$)/, "#{((query_description[:to].to_i - query_description[:from].to_i) / 1000 / 1000).to_i}s")
30
- query = query.gsub(/\$(?:__)?interval_ms(?=\W|$)/, "#{((query_description[:to].to_i - query_description[:from].to_i) / 1000).to_i}")
31
+ query = query.gsub(/\$(?:__)?interval(?=\W|$)/, "#{interval.is_a?(String) ? interval : "#{(interval / 1000).to_i}s"}")
32
+ query = query.gsub(/\$(?:__)?interval_ms(?=\W|$)/, "#{interval}")
31
33
 
32
34
  url = "/api/datasources/proxy/#{id}/query?db=#{@model['database']}&q=#{ERB::Util.url_encode(query)}&epoch=ms"
33
35
 
@@ -128,7 +130,12 @@ module Grafana
128
130
  # @see AbstractDatasource#preformat_response
129
131
  def preformat_response(response_body)
130
132
  # TODO: how to handle multiple query results?
131
- json = JSON.parse(response_body)['results'].first['series']
133
+ json = JSON.parse(response_body)
134
+ raise UnsupportedQueryResponseReceivedError, response_body if json['results'].nil?
135
+ raise UnsupportedQueryResponseReceivedError, response_body if json['results'].first.nil?
136
+ raise UnsupportedQueryResponseReceivedError, response_body if json['results'].first['series'].nil?
137
+
138
+ json = json['results'].first['series']
132
139
  return {} if json.nil?
133
140
 
134
141
  header = ['time']
data/lib/grafana/panel.rb CHANGED
@@ -28,7 +28,11 @@ module Grafana
28
28
 
29
29
  # @return [Datasource] datasource object specified for the current panel
30
30
  def datasource
31
- dashboard.grafana.datasource_by_name(@model['datasource'])
31
+ if @model['datasource'].is_a?(Hash)
32
+ dashboard.grafana.datasource_by_uid(@model['datasource']['uid'])
33
+ else
34
+ dashboard.grafana.datasource_by_name(@model['datasource'])
35
+ end
32
36
  end
33
37
 
34
38
  # @return [String] query string for the requested query letter
@@ -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
@@ -45,16 +57,64 @@ module Grafana
45
57
 
46
58
  # @see AbstractDatasource#preformat_response
47
59
  def preformat_response(response_body)
48
- json = JSON.parse(response_body)['data']['result']
60
+ json = {}
61
+ begin
62
+ json = JSON.parse(response_body)
63
+ rescue
64
+ raise UnsupportedQueryResponseReceivedError, response_body
65
+ end
66
+
67
+ # handle response with error result
68
+ unless json['error'].nil?
69
+ return { header: ['error'], content: [[ json['error'] ]] }
70
+ end
71
+
72
+ # handle dataframes
73
+ if json['results']
74
+ data = json['results'].values.first
75
+ raise UnsupportedQueryResponseReceivedError, response_body if data.nil?
76
+ raise UnsupportedQueryResponseReceivedError, response_body if data['frames'].nil?
77
+ # TODO: check how multiple frames have to be handled
78
+
79
+ data = data['frames']
80
+ headers = []
81
+ data.first['schema']['fields'].each do |headline|
82
+ header = headline['config']['displayNameFromDS'].nil? ? headline['name'] : headline['config']['displayNameFromDS']
83
+ headers << header
84
+ end
85
+ content = data.first['data']['values'][0].zip(data.first['data']['values'][1])
86
+ return { header: headers, content: content }
87
+ end
88
+
89
+ # handle former result formats
90
+ raise UnsupportedQueryResponseReceivedError, response_body if json['data'].nil?
91
+ raise UnsupportedQueryResponseReceivedError, response_body if json['data']['resultType'].nil?
92
+ raise UnsupportedQueryResponseReceivedError, response_body if json['data']['result'].nil?
93
+
94
+ result_type = json['data']['resultType']
95
+ json = json['data']['result']
96
+
97
+ raise UnsupportedQueryResponseReceivedError, response_body if not result_type =~ /^(?:scalar|string|vector|matrix)$/
49
98
 
50
99
  headers = ['time']
51
100
  content = {}
52
101
 
102
+ # handle vector queries
103
+ if result_type == 'vector'
104
+ return {
105
+ header: (headers << 'value') + json.first['metric'].keys,
106
+ content: [ [json.first['value'][0], json.first['value'][1]] + json.first['metric'].values ]
107
+ }
108
+ end
109
+
110
+ # handle scalar queries
111
+ if result_type =~ /^(?:scalar|string)$/
112
+ return { header: headers << result_type, content: [[json[0], json[1]]] }
113
+ end
114
+
53
115
  # keep sorting, if json has only one target item, otherwise merge results and return
54
116
  # as a time sorted array
55
- # TODO properly set headlines
56
117
  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
118
  return { header: headers << json.first['metric']['mode'], content: json.first['values'] }
59
119
  end
60
120
 
@@ -49,18 +49,24 @@ module Grafana
49
49
  def preformat_response(response_body)
50
50
  results = {}
51
51
  results.default = []
52
+ results[:header] = []
53
+ results[:content] = []
52
54
 
53
55
  JSON.parse(response_body)['results'].each_value do |query_result|
54
56
  if query_result.key?('error')
55
57
  results[:header] = results[:header] + ['SQL Error']
56
58
  results[:content] = [[query_result['error']]]
57
59
 
58
- elsif query_result['tables']
59
- query_result['tables'].each do |table|
60
- results[:header] = results[:header] + table['columns'].map { |header| header['text'] }
61
- results[:content] = table['rows']
60
+ elsif query_result.key?('tables')
61
+ if query_result['tables']
62
+ query_result['tables'].each do |table|
63
+ results[:header] = results[:header] + table['columns'].map { |header| header['text'] }
64
+ results[:content] = table['rows']
65
+ end
62
66
  end
63
67
 
68
+ else
69
+ raise UnsupportedQueryResponseReceivedError, response_body
64
70
  end
65
71
  end
66
72
 
@@ -29,15 +29,14 @@ module Grafana
29
29
 
30
30
  # @param config_or_value [Hash, Object] configuration hash of a variable out of an {Dashboard} instance
31
31
  # or a value of any kind.
32
- def initialize(config_or_value)
32
+ # @param dashboard [Dashboard] parent dashboard, if applicable; especially needed for query variable
33
+ # evaluation.
34
+ def initialize(config_or_value, dashboard = nil)
33
35
  if config_or_value.is_a? Hash
36
+ @dashboard = dashboard
34
37
  @config = config_or_value
35
38
  @name = @config['name']
36
- # TODO: if a variable uses type 'query' which is never updated, the selected values are stored in 'options'
37
- unless @config['current'].nil?
38
- @raw_value = @config['current']['value']
39
- @text = @config['current']['text']
40
- end
39
+ init_values
41
40
  else
42
41
  @config = {}
43
42
  @raw_value = config_or_value
@@ -68,14 +67,19 @@ module Grafana
68
67
  if value == '$__all'
69
68
  if !@config['options'].empty?
70
69
  # this query contains predefined values, so capture them and format the values accordingly
71
- # this happens either if type='custom' or a query, which is never updated
72
- value = @config['options'].map { |item| item['value'] }
70
+ # this happens either for type='custom' or type 'query', if it is never updated
71
+ value = @config['options'].reject { |item| item['value'] == '$__all' }.map { |item| item['value'] }
73
72
 
74
73
  elsif @config['type'] == 'query' && !@config['query'].empty?
75
- # TODO: replace variables in query, execute it and evaluate the results as if they were normally selected
76
- # "multiFormat": contains variable replacement in "query", e.g. 'regex values' or 'glob'
77
- # "datasource": contains name of datasource
78
- return @config['query']
74
+ # variables in this configuration are not stored in grafana, i.e. if all is selected here,
75
+ # the values have to be fetched from the datasource
76
+ query = ::GrafanaReporter::QueryValueQuery.new(@dashboard)
77
+ query.datasource = @dashboard.grafana.datasource_by_name(@config['datasource'])
78
+ query.variables['result_type'] = Variable.new('object')
79
+ query.raw_query = @config['query']
80
+ result = query.execute
81
+
82
+ value = result[:content].map { |item| item[0].to_s }
79
83
 
80
84
  else
81
85
  # TODO: add support for variable type: 'datasource' and 'adhoc'
@@ -149,8 +153,10 @@ module Grafana
149
153
  value.gsub(%r{[" |=/\\]}, '\\\\\0')
150
154
 
151
155
  when /^date(?::(?<format>.*))?$/
152
- # TODO: grafana does not seem to allow multiselection of variables with date format - raise an error if this happens anyway
153
- get_date_formatted(value, Regexp.last_match(1))
156
+ if multi? && value.is_a?(Array)
157
+ raise GrafanaError, "Date format cannot be specified for a variable containing an array of values"
158
+ end
159
+ Variable.format_as_date(value, Regexp.last_match(1))
154
160
 
155
161
  when ''
156
162
  # default
@@ -168,8 +174,9 @@ module Grafana
168
174
  end
169
175
  end
170
176
 
171
- # @return [Boolean] true, if the value can contain multiple selections, i.e. can contain an Array
177
+ # @return [Boolean] true, if the value can contain multiple selections, i.e. can contain an Array or does contain all
172
178
  def multi?
179
+ return true if @raw_value == '$__all'
173
180
  return @config['multi'] unless @config['multi'].nil?
174
181
 
175
182
  @raw_value.is_a? Array
@@ -187,12 +194,13 @@ module Grafana
187
194
  @text = new_text
188
195
  end
189
196
 
190
- private
191
-
192
- # Realize time formatting according
197
+ # Applies the date format according
193
198
  # {https://grafana.com/docs/grafana/latest/variables/variable-types/global-variables/#__from-and-__to}
194
- # and {https://momentjs.com/docs/#/displaying/}.
195
- def get_date_formatted(value, format)
199
+ # and {https://momentjs.com/docs/#/displaying/} to a given value.
200
+ # @param value [String] time as milliseconds to be formatted
201
+ # @param format [String] format string in which the time value shall be returned
202
+ # @return [String] time converted to the specified time format
203
+ def self.format_as_date(value, format)
196
204
  return (Float(value) / 1000).to_i.to_s if format == 'seconds'
197
205
  return Time.at((Float(value) / 1000).to_i).utc.iso8601(3) if !format || (format == 'iso')
198
206
 
@@ -203,13 +211,12 @@ module Grafana
203
211
  tmp = work_string.scan(/^(?:M{1,4}|D{1,4}|d{1,4}|e|E|w{1,2}|W{1,2}|Y{4}|Y{2}|A|a|H{1,2}|
204
212
  h{1,2}|k{1,2}|m{1,2}|s{1,2}|S+|X)/x)
205
213
 
206
- # TODO: add test for sub! and switch to non-modifying frozen string action
207
214
  if tmp.empty?
208
215
  matches << work_string[0]
209
- work_string.sub!(/^#{work_string[0]}/, '')
216
+ work_string = work_string.sub(/^#{work_string[0]}/, '')
210
217
  else
211
218
  matches << tmp[0]
212
- work_string.sub!(/^#{tmp[0]}/, '')
219
+ work_string = work_string.sub(/^#{tmp[0]}/, '')
213
220
  end
214
221
  end
215
222
 
@@ -221,5 +228,21 @@ module Grafana
221
228
 
222
229
  Time.at((Float(value) / 1000).to_i).strftime(format_string)
223
230
  end
231
+
232
+ private
233
+
234
+ def init_values
235
+ case @config['type']
236
+ when 'constant'
237
+ self.raw_value = @config['query']
238
+
239
+ else
240
+ if !@config['current'].nil?
241
+ self.raw_value = @config['current']['value']
242
+ else
243
+ raise GrafanaError.new("Grafana variable with type '#{@config['type']}' and name '#{@config['name']}' could not be handled properly. Please raise a ticket.")
244
+ end
245
+ end
246
+ end
224
247
  end
225
248
  end
@@ -50,6 +50,7 @@ module Grafana
50
50
  @logger.debug("Requesting #{uri} with '#{@options[:body]}' and timeout '#{timeout}'")
51
51
  response = @http.request(request)
52
52
  @logger.debug("Received response #{response}")
53
+ @logger.debug("HTTP response body: #{response.body}") unless response.code =~ /^2.*/
53
54
 
54
55
  response
55
56
  end