ruby-grafana-reporter 0.4.5 → 0.5.2
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.
- 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
|