ruby-grafana-reporter 0.5.0 → 0.5.3
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 +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
|