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