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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/lib/VERSION.rb +2 -2
- data/lib/grafana/abstract_datasource.rb +8 -0
- data/lib/grafana/dashboard.rb +6 -1
- data/lib/grafana/errors.rb +9 -1
- data/lib/grafana/grafana.rb +35 -0
- data/lib/grafana/grafana_environment_datasource.rb +56 -0
- data/lib/grafana/graphite_datasource.rb +3 -0
- data/lib/grafana/image_rendering_datasource.rb +5 -1
- data/lib/grafana/influxdb_datasource.rb +11 -4
- data/lib/grafana/panel.rb +5 -1
- data/lib/grafana/prometheus_datasource.rb +71 -11
- data/lib/grafana/sql_datasource.rb +10 -4
- data/lib/grafana/variable.rb +46 -23
- data/lib/grafana/webrequest.rb +1 -0
- data/lib/grafana_reporter/abstract_query.rb +31 -24
- data/lib/grafana_reporter/abstract_report.rb +2 -0
- data/lib/grafana_reporter/abstract_table_format_strategy.rb +44 -4
- data/lib/grafana_reporter/alerts_table_query.rb +2 -1
- data/lib/grafana_reporter/annotations_table_query.rb +2 -1
- data/lib/grafana_reporter/application/webservice.rb +8 -4
- data/lib/grafana_reporter/asciidoctor/adoc_plain_table_format_strategy.rb +11 -9
- data/lib/grafana_reporter/asciidoctor/help.rb +53 -14
- data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +2 -4
- data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +2 -4
- data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +1 -1
- data/lib/grafana_reporter/asciidoctor/show_environment_include_processor.rb +37 -6
- data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +11 -2
- data/lib/grafana_reporter/asciidoctor/value_as_variable_include_processor.rb +0 -5
- data/lib/grafana_reporter/configuration.rb +53 -22
- data/lib/grafana_reporter/console_configuration_wizard.rb +3 -1
- data/lib/grafana_reporter/csv_table_format_strategy.rb +11 -9
- data/lib/grafana_reporter/demo_report_wizard.rb +3 -6
- data/lib/grafana_reporter/errors.rb +2 -2
- data/lib/grafana_reporter/panel_image_query.rb +0 -1
- data/lib/grafana_reporter/query_value_query.rb +7 -1
- data/lib/grafana_reporter/reporter_environment_datasource.rb +24 -0
- data/lib/ruby_grafana_reporter.rb +7 -7
- metadata +8 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de6063d128d57f930e83b17253e34e4e20624ef796e7eb86df2e7d54bee7825c
|
4
|
+
data.tar.gz: a6a5f8b3bfcdcfc557ede19e7d89a25de8dca277806da0f662c4165d0a05b03c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 |
|
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
@@ -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+$/
|
data/lib/grafana/dashboard.rb
CHANGED
@@ -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
|
data/lib/grafana/errors.rb
CHANGED
@@ -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
|
data/lib/grafana/grafana.rb
CHANGED
@@ -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 =
|
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|$)/, "#{(
|
30
|
-
query = query.gsub(/\$(?:__)?interval_ms(?=\W|$)/, "#{
|
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)
|
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
|
-
|
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
|
-
|
18
|
-
end_point = @endpoint ? @endpoint : "query_range"
|
17
|
+
query_hash = query_description[:raw_query].is_a?(Hash) ? query_description[:raw_query] : {}
|
19
18
|
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
36
|
-
|
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 =
|
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
|
59
|
-
query_result['tables']
|
60
|
-
|
61
|
-
|
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
|
|
data/lib/grafana/variable.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
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
|
-
#
|
76
|
-
#
|
77
|
-
|
78
|
-
|
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
|
-
|
153
|
-
|
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
|
-
|
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
|
-
|
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
|
216
|
+
work_string = work_string.sub(/^#{work_string[0]}/, '')
|
210
217
|
else
|
211
218
|
matches << tmp[0]
|
212
|
-
work_string.sub
|
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
|
data/lib/grafana/webrequest.rb
CHANGED
@@ -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
|