ruby-grafana-reporter 0.4.1 → 0.4.5
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 +336 -185
- data/lib/VERSION.rb +2 -2
- data/lib/grafana/abstract_datasource.rb +30 -17
- data/lib/grafana/errors.rb +4 -4
- data/lib/grafana/grafana.rb +2 -0
- data/lib/grafana/grafana_property_datasource.rb +12 -0
- data/lib/grafana/graphite_datasource.rb +27 -5
- data/lib/grafana/influxdb_datasource.rb +156 -0
- data/lib/grafana/panel.rb +1 -1
- data/lib/grafana/prometheus_datasource.rb +37 -6
- data/lib/grafana/sql_datasource.rb +10 -11
- data/lib/grafana/variable.rb +29 -22
- data/lib/grafana_reporter/abstract_query.rb +150 -31
- data/lib/grafana_reporter/abstract_report.rb +37 -5
- data/lib/grafana_reporter/abstract_table_format_strategy.rb +34 -0
- data/lib/grafana_reporter/alerts_table_query.rb +5 -6
- data/lib/grafana_reporter/annotations_table_query.rb +5 -6
- data/lib/grafana_reporter/application/application.rb +7 -2
- data/lib/grafana_reporter/application/webservice.rb +35 -30
- data/lib/grafana_reporter/asciidoctor/adoc_plain_table_format_strategy.rb +25 -0
- data/lib/grafana_reporter/asciidoctor/alerts_table_include_processor.rb +7 -5
- data/lib/grafana_reporter/asciidoctor/annotations_table_include_processor.rb +7 -5
- data/lib/grafana_reporter/asciidoctor/help.rb +458 -0
- data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +5 -4
- data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +5 -4
- data/lib/grafana_reporter/asciidoctor/panel_property_inline_macro.rb +5 -4
- data/lib/grafana_reporter/asciidoctor/panel_query_table_include_processor.rb +6 -6
- data/lib/grafana_reporter/asciidoctor/panel_query_value_inline_macro.rb +4 -4
- data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +21 -35
- data/lib/grafana_reporter/asciidoctor/report.rb +16 -26
- data/lib/grafana_reporter/asciidoctor/show_help_include_processor.rb +1 -1
- data/lib/grafana_reporter/asciidoctor/sql_table_include_processor.rb +6 -4
- data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +5 -3
- data/lib/grafana_reporter/console_configuration_wizard.rb +2 -2
- data/lib/grafana_reporter/csv_table_format_strategy.rb +23 -0
- data/lib/grafana_reporter/demo_report_wizard.rb +5 -2
- data/lib/grafana_reporter/erb/demo_report_builder.rb +46 -0
- data/lib/grafana_reporter/erb/report.rb +14 -21
- data/lib/grafana_reporter/erb/report_jail.rb +21 -0
- data/lib/grafana_reporter/errors.rb +19 -3
- data/lib/grafana_reporter/panel_image_query.rb +2 -5
- data/lib/grafana_reporter/query_value_query.rb +1 -19
- data/lib/grafana_reporter/report_webhook.rb +12 -8
- data/lib/ruby_grafana_reporter.rb +10 -11
- metadata +9 -3
- data/lib/grafana_reporter/help.rb +0 -443
data/lib/VERSION.rb
CHANGED
@@ -8,19 +8,18 @@ module Grafana
|
|
8
8
|
|
9
9
|
@@subclasses = []
|
10
10
|
|
11
|
-
# Registers the subclass as datasource
|
12
|
-
# model.
|
11
|
+
# Registers the subclass as datasource.
|
13
12
|
# @param subclass [Class] class inheriting from this abstract class
|
14
13
|
def self.inherited(subclass)
|
15
14
|
@@subclasses << subclass
|
16
15
|
end
|
17
16
|
|
18
17
|
# Overwrite this method, to specify if the current datasource implementation handles the given model.
|
19
|
-
# This method is called by {
|
18
|
+
# This method is called by {build_instance} to determine, if the current datasource implementation
|
20
19
|
# can handle the given grafana model. By default this method returns false.
|
21
20
|
# @param model [Hash] grafana specification of the datasource to check
|
22
21
|
# @return [Boolean] True if fits, false otherwise
|
23
|
-
def self.handles?(
|
22
|
+
def self.handles?(model)
|
24
23
|
false
|
25
24
|
end
|
26
25
|
|
@@ -43,12 +42,12 @@ module Grafana
|
|
43
42
|
@model = model
|
44
43
|
end
|
45
44
|
|
46
|
-
# @return [String] category of the datasource, e.g.
|
45
|
+
# @return [String] category of the datasource, e.g. +tsdb+ or +sql+
|
47
46
|
def category
|
48
47
|
@model['meta']['category']
|
49
48
|
end
|
50
49
|
|
51
|
-
# @return [String] type of the datasource, e.g.
|
50
|
+
# @return [String] type of the datasource, e.g. +mysql+
|
52
51
|
def type
|
53
52
|
@model['type'] || @model['meta']['id']
|
54
53
|
end
|
@@ -78,12 +77,12 @@ module Grafana
|
|
78
77
|
# }
|
79
78
|
#
|
80
79
|
# @param query_description [Hash] query description, which will requested:
|
81
|
-
# @option [String] :from +from+ timestamp
|
82
|
-
# @option [String] :to +to+ timestamp
|
83
|
-
# @option [Integer] :timeout expected timeout for the request
|
84
|
-
# @option [WebRequest] :prepared_request prepared web request for relevant {Grafana} instance, if this is needed by datasource
|
85
|
-
# @option [String] :raw_query raw query, which shall be executed. May include variables, which will be replaced before execution
|
86
|
-
# @option [Hash<Variable>] :variables hash of variables, which can potentially be replaced in the given +:raw_query+
|
80
|
+
# @option query_description [String] :from +from+ timestamp
|
81
|
+
# @option query_description [String] :to +to+ timestamp
|
82
|
+
# @option query_description [Integer] :timeout expected timeout for the request
|
83
|
+
# @option query_description [WebRequest] :prepared_request prepared web request for relevant {Grafana} instance, if this is needed by datasource
|
84
|
+
# @option query_description [String] :raw_query raw query, which shall be executed. May include variables, which will be replaced before execution
|
85
|
+
# @option query_description [Hash<Variable>] :variables hash of variables, which can potentially be replaced in the given +:raw_query+
|
87
86
|
# @return [Hash] sql result formatted as stated above
|
88
87
|
def request(query_description)
|
89
88
|
raise NotImplementedError
|
@@ -101,6 +100,14 @@ module Grafana
|
|
101
100
|
raise NotImplementedError
|
102
101
|
end
|
103
102
|
|
103
|
+
# @abstract
|
104
|
+
#
|
105
|
+
# Overwrite in subclass, to specify the default variable format during replacement of variables.
|
106
|
+
# @return [String] default {Variable#value_formatted} format
|
107
|
+
def default_variable_format
|
108
|
+
raise NotImplementedError
|
109
|
+
end
|
110
|
+
|
104
111
|
private
|
105
112
|
|
106
113
|
# Replaces the grafana variables in the given string with their replacement value.
|
@@ -119,12 +126,18 @@ module Grafana
|
|
119
126
|
while repeat && (repeat_count < 3)
|
120
127
|
repeat = false
|
121
128
|
repeat_count += 1
|
122
|
-
|
129
|
+
|
130
|
+
variables.each do |name, variable|
|
123
131
|
# only set ticks if value is string
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
132
|
+
var_name = name.gsub(/^var-/, '')
|
133
|
+
next unless var_name =~ /^\w+$/
|
134
|
+
|
135
|
+
res = res.gsub(/(?:\$\{#{var_name}(?::(?<format>\w+))?\}|\$#{var_name}(?!\w))/) do
|
136
|
+
format = default_variable_format
|
137
|
+
if $LAST_MATCH_INFO
|
138
|
+
format = $LAST_MATCH_INFO[:format] if $LAST_MATCH_INFO[:format]
|
139
|
+
end
|
140
|
+
variable.value_formatted(format)
|
128
141
|
end
|
129
142
|
end
|
130
143
|
repeat = true if res.include?('$')
|
data/lib/grafana/errors.rb
CHANGED
@@ -37,9 +37,9 @@ module Grafana
|
|
37
37
|
|
38
38
|
# Raised if a given datasource does not exist in a specific {Grafana} instance.
|
39
39
|
class DatasourceDoesNotExistError < GrafanaError
|
40
|
-
# @param field [String] specifies, how the datasource has been searched, e.g.
|
40
|
+
# @param field [String] specifies, how the datasource has been searched, e.g. +id+ or +name+
|
41
41
|
# @param datasource_identifier [String] identifier of the datasource, which could not be found,
|
42
|
-
# e.g. the
|
42
|
+
# e.g. the specified id or name
|
43
43
|
def initialize(field, datasource_identifier)
|
44
44
|
super("Datasource with #{field} '#{datasource_identifier}' does not exist.")
|
45
45
|
end
|
@@ -49,10 +49,10 @@ module Grafana
|
|
49
49
|
#
|
50
50
|
# Most likely this happens, because the image renderer is not configures properly in grafana,
|
51
51
|
# or the panel rendering ran into a timeout.
|
52
|
+
# @param panel [Panel] panel object, which could not be rendered
|
52
53
|
class ImageCouldNotBeRenderedError < GrafanaError
|
53
|
-
# @param panel [Panel] panel object, which could not be rendered
|
54
54
|
def initialize(panel)
|
55
|
-
super("The specified panel '#{panel.id}' from dashboard '#{panel.dashboard.id} could not be "\
|
55
|
+
super("The specified panel '#{panel.id}' from dashboard '#{panel.dashboard.id}' could not be "\
|
56
56
|
'rendered to an image.')
|
57
57
|
end
|
58
58
|
end
|
data/lib/grafana/grafana.rb
CHANGED
@@ -50,6 +50,8 @@ module Grafana
|
|
50
50
|
# @return [Datasource] Datasource for the specified datasource name
|
51
51
|
def datasource_by_name(datasource_name)
|
52
52
|
datasource_name = 'default' if datasource_name.to_s.empty?
|
53
|
+
# TODO: PRIO add support for grafana builtin datasource types
|
54
|
+
return UnsupportedDatasource.new(nil) if datasource_name.to_s =~ /-- (?:Mixed|Dashboard|Grafana) --/
|
53
55
|
raise DatasourceDoesNotExistError.new('name', datasource_name) unless @datasources[datasource_name]
|
54
56
|
|
55
57
|
@datasources[datasource_name]
|
@@ -16,10 +16,22 @@ module Grafana
|
|
16
16
|
panel = query_description[:raw_query][:panel]
|
17
17
|
property_name = query_description[:raw_query][:property_name]
|
18
18
|
|
19
|
+
return "Panel property '#{property_name}' does not exist for panel '#{panel.id}'" unless panel.field(property_name)
|
20
|
+
|
19
21
|
{
|
20
22
|
header: [query_description[:raw_query][:property_name]],
|
21
23
|
content: [replace_variables(panel.field(property_name), query_description[:variables])]
|
22
24
|
}
|
23
25
|
end
|
26
|
+
|
27
|
+
# @see AbstractDatasource#default_variable_format
|
28
|
+
def default_variable_format
|
29
|
+
'glob'
|
30
|
+
end
|
31
|
+
|
32
|
+
# @see AbstractDatasource#name
|
33
|
+
def name
|
34
|
+
self.class.to_s
|
35
|
+
end
|
24
36
|
end
|
25
37
|
end
|
@@ -36,15 +36,37 @@ module Grafana
|
|
36
36
|
panel_query_target['target']
|
37
37
|
end
|
38
38
|
|
39
|
+
# @see AbstractDatasource#default_variable_format
|
40
|
+
def default_variable_format
|
41
|
+
'glob'
|
42
|
+
end
|
43
|
+
|
39
44
|
private
|
40
45
|
|
41
46
|
# @see AbstractDatasource#preformat_response
|
42
47
|
def preformat_response(response_body)
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
+
json = JSON.parse(response_body)
|
49
|
+
|
50
|
+
header = ['time']
|
51
|
+
content = {}
|
52
|
+
|
53
|
+
# keep sorting, if json has only one target item, otherwise merge results and return
|
54
|
+
# as a time sorted array
|
55
|
+
return { header: header << json.first['target'], content: json.first['datapoints'].map! { |item| [item[1], item[0]] } } if json.length == 1
|
56
|
+
|
57
|
+
# TODO: show warning if results may be sorted different
|
58
|
+
json.each_index do |i|
|
59
|
+
header << json[i]['target']
|
60
|
+
tmp = json[i]['datapoints'].map! { |item| [item[1], item[0]] }.to_h
|
61
|
+
tmp.each_key { |key| content[key] = Array.new(json.length) unless content[key] }
|
62
|
+
|
63
|
+
content.merge!(tmp) do |_key, old, new|
|
64
|
+
old[i] = new
|
65
|
+
old
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
{ header: header, content: content.to_a.map(&:flatten).sort { |a, b| a[0] <=> b[0] } }
|
48
70
|
end
|
49
71
|
end
|
50
72
|
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grafana
|
4
|
+
# Implements the interface to Prometheus datasources.
|
5
|
+
class InfluxDbDatasource < AbstractDatasource
|
6
|
+
# @see AbstractDatasource#handles?
|
7
|
+
def self.handles?(model)
|
8
|
+
tmp = new(model)
|
9
|
+
tmp.type == 'influxdb'
|
10
|
+
end
|
11
|
+
|
12
|
+
# +:database+ needs to contain the InfluxDb database name
|
13
|
+
# +:raw_query+ needs to contain a InfluxDb query as String
|
14
|
+
# @see AbstractDatasource#request
|
15
|
+
def request(query_description)
|
16
|
+
raise MissingSqlQueryError if query_description[:raw_query].nil?
|
17
|
+
|
18
|
+
# replace variables
|
19
|
+
query = replace_variables(query_description[:raw_query], query_description[:variables])
|
20
|
+
|
21
|
+
# Unfortunately the grafana internal variables are not replaced in the grafana backend, but in the
|
22
|
+
# frontend, i.e. we have to replace them here manually
|
23
|
+
# replace $timeFilter variable
|
24
|
+
query = query.gsub(/\$timeFilter(?=\W|$)/, "time >= #{query_description[:from]}ms and time <= #{query_description[:to]}ms")
|
25
|
+
|
26
|
+
# 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
|
+
# TODO: check where calculation and replacement of interval variable should take place
|
29
|
+
query = query.gsub(/\$(?:__)?interval(?=\W|$)/, "#{((query_description[:to].to_i - query_description[:from].to_i) / 1000 / 1000).to_i}s")
|
30
|
+
query = query.gsub(/\$(?:__)?interval_ms(?=\W|$)/, "#{((query_description[:to].to_i - query_description[:from].to_i) / 1000).to_i}")
|
31
|
+
|
32
|
+
url = "/api/datasources/proxy/#{id}/query?db=#{@model['database']}&q=#{ERB::Util.url_encode(query)}&epoch=ms"
|
33
|
+
|
34
|
+
webrequest = query_description[:prepared_request]
|
35
|
+
webrequest.relative_url = url
|
36
|
+
webrequest.options.merge!({ request: Net::HTTP::Get })
|
37
|
+
|
38
|
+
result = webrequest.execute(query_description[:timeout])
|
39
|
+
preformat_response(result.body)
|
40
|
+
end
|
41
|
+
|
42
|
+
# @see AbstractDatasource#raw_query_from_panel_model
|
43
|
+
def raw_query_from_panel_model(panel_query_target)
|
44
|
+
return panel_query_target['query'] if panel_query_target['rawQuery']
|
45
|
+
|
46
|
+
# build composed queries
|
47
|
+
build_select(panel_query_target['select']) + build_from(panel_query_target) + build_where(panel_query_target['tags']) + build_group_by(panel_query_target['groupBy'])
|
48
|
+
end
|
49
|
+
|
50
|
+
# @see AbstractDatasource#default_variable_format
|
51
|
+
def default_variable_format
|
52
|
+
'regex'
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def build_group_by(stmt)
|
58
|
+
groups = []
|
59
|
+
fill = ""
|
60
|
+
|
61
|
+
stmt.each do |group|
|
62
|
+
case group['type']
|
63
|
+
when 'tag'
|
64
|
+
groups << "\"#{group['params'].first}\""
|
65
|
+
|
66
|
+
when 'fill'
|
67
|
+
fill = " fill(#{group['params'].first})"
|
68
|
+
|
69
|
+
else
|
70
|
+
groups << "#{group['type']}(#{group['params'].join(', ')})"
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
" GROUP BY #{groups.join(', ')}#{fill}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def build_where(stmt)
|
79
|
+
custom_where = []
|
80
|
+
|
81
|
+
stmt.each do |where|
|
82
|
+
value = where['operator'] =~ /^[=!]~$/ ? where['value'] : "'#{where['value']}'"
|
83
|
+
custom_where << "\"#{where['key']}\" #{where['operator']} #{value}"
|
84
|
+
end
|
85
|
+
|
86
|
+
" WHERE #{"(#{custom_where.join(' AND ')}) AND " unless custom_where.empty?}$timeFilter"
|
87
|
+
end
|
88
|
+
|
89
|
+
def build_from(stmt)
|
90
|
+
" FROM \"#{"stmt['policy']." unless stmt['policy'] == 'default'}#{stmt['measurement']}\""
|
91
|
+
end
|
92
|
+
|
93
|
+
def build_select(stmt)
|
94
|
+
res = "SELECT"
|
95
|
+
parts = []
|
96
|
+
|
97
|
+
stmt.each do |value|
|
98
|
+
part = ""
|
99
|
+
|
100
|
+
value.each do |item|
|
101
|
+
case item['type']
|
102
|
+
when 'field'
|
103
|
+
# frame field parameter as string
|
104
|
+
part = "\"#{item['params'].first}\""
|
105
|
+
|
106
|
+
when 'alias'
|
107
|
+
# append AS with parameter as string
|
108
|
+
part = "#{part} AS \"#{item['params'].first}\""
|
109
|
+
|
110
|
+
|
111
|
+
when 'math'
|
112
|
+
# append parameter as raw value for calculation
|
113
|
+
part = "#{part} #{item['params'].first}"
|
114
|
+
|
115
|
+
|
116
|
+
else
|
117
|
+
# frame current part by brackets and call by item function including parameters
|
118
|
+
part = "#{item['type']}(#{part}#{", #{item['params'].join(', ')}" unless item['params'].empty?})"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
parts << part
|
123
|
+
end
|
124
|
+
|
125
|
+
"#{res} #{parts.join(', ')}"
|
126
|
+
end
|
127
|
+
|
128
|
+
# @see AbstractDatasource#preformat_response
|
129
|
+
def preformat_response(response_body)
|
130
|
+
# TODO: how to handle multiple query results?
|
131
|
+
json = JSON.parse(response_body)['results'].first['series']
|
132
|
+
return {} if json.nil?
|
133
|
+
|
134
|
+
header = ['time']
|
135
|
+
content = {}
|
136
|
+
|
137
|
+
# keep sorting, if json has only one target item, otherwise merge results and return
|
138
|
+
# as a time sorted array
|
139
|
+
return { header: header << "#{json.first['name']} #{json.first['columns'][1]} (#{json.first['tags']})", content: json.first['values'] } if json.length == 1
|
140
|
+
|
141
|
+
# TODO: show warning here, as results may be sorted different
|
142
|
+
json.each_index do |i|
|
143
|
+
header << "#{json[i]['name']} #{json[i]['columns'][1]} (#{json[i]['tags']})"
|
144
|
+
tmp = json[i]['values'].to_h
|
145
|
+
tmp.each_key { |key| content[key] = Array.new(json.length) unless content[key] }
|
146
|
+
|
147
|
+
content.merge!(tmp) do |_key, old, new|
|
148
|
+
old[i] = new
|
149
|
+
old
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
{ header: header, content: content.to_a.map(&:flatten).sort { |a, b| a[0] <=> b[0] } }
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
data/lib/grafana/panel.rb
CHANGED
@@ -14,7 +14,11 @@ module Grafana
|
|
14
14
|
def request(query_description)
|
15
15
|
raise MissingSqlQueryError if query_description[:raw_query].nil?
|
16
16
|
|
17
|
-
|
17
|
+
# TODO: properly allow endpoint to be set - also check raw_query method
|
18
|
+
end_point = @endpoint ? @endpoint : "query_range"
|
19
|
+
|
20
|
+
# TODO: set query option 'step' on request
|
21
|
+
url = "/api/datasources/proxy/#{id}/api/v1/#{end_point}?"\
|
18
22
|
"start=#{query_description[:from]}&end=#{query_description[:to]}"\
|
19
23
|
"&query=#{replace_variables(query_description[:raw_query], query_description[:variables])}"
|
20
24
|
|
@@ -28,18 +32,45 @@ module Grafana
|
|
28
32
|
|
29
33
|
# @see AbstractDatasource#raw_query_from_panel_model
|
30
34
|
def raw_query_from_panel_model(panel_query_target)
|
35
|
+
@endpoint = panel_query_target['format'] == 'time_series' && (panel_query_target['instant'] == false || !panel_query_target['instant']) ? 'query_range' : 'query'
|
31
36
|
panel_query_target['expr']
|
32
37
|
end
|
33
38
|
|
39
|
+
# @see AbstractDatasource#default_variable_format
|
40
|
+
def default_variable_format
|
41
|
+
'regex'
|
42
|
+
end
|
43
|
+
|
34
44
|
private
|
35
45
|
|
36
46
|
# @see AbstractDatasource#preformat_response
|
37
47
|
def preformat_response(response_body)
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
48
|
+
json = JSON.parse(response_body)['data']['result']
|
49
|
+
|
50
|
+
headers = ['time']
|
51
|
+
content = {}
|
52
|
+
|
53
|
+
# keep sorting, if json has only one target item, otherwise merge results and return
|
54
|
+
# as a time sorted array
|
55
|
+
# TODO properly set headlines
|
56
|
+
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
|
+
return { header: headers << json.first['metric']['mode'], content: json.first['values'] }
|
59
|
+
end
|
60
|
+
|
61
|
+
# TODO: show warning if results may be sorted different
|
62
|
+
json.each_index do |i|
|
63
|
+
headers += [json[i]['metric']['mode']]
|
64
|
+
tmp = json[i]['values'].to_h
|
65
|
+
tmp.each_key { |key| content[key] = Array.new(json.length) unless content[key] }
|
66
|
+
|
67
|
+
content.merge!(tmp) do |_key, old, new|
|
68
|
+
old[i] = new
|
69
|
+
old
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
{ header: headers, content: content.to_a.map(&:flatten).sort { |a, b| a[0] <=> b[0] } }
|
43
74
|
end
|
44
75
|
end
|
45
76
|
end
|
@@ -19,7 +19,7 @@ module Grafana
|
|
19
19
|
body: {
|
20
20
|
from: query_description[:from],
|
21
21
|
to: query_description[:to],
|
22
|
-
queries: [rawSql:
|
22
|
+
queries: [rawSql: sql, datasourceId: id, format: 'table']
|
23
23
|
}.to_json,
|
24
24
|
request: Net::HTTP::Post
|
25
25
|
}
|
@@ -32,11 +32,18 @@ module Grafana
|
|
32
32
|
preformat_response(result.body)
|
33
33
|
end
|
34
34
|
|
35
|
+
# Currently all composed SQL queries are saved in the dashboard as rawSql, so no conversion
|
36
|
+
# necessary here.
|
35
37
|
# @see AbstractDatasource#raw_query_from_panel_model
|
36
38
|
def raw_query_from_panel_model(panel_query_target)
|
37
39
|
panel_query_target['rawSql']
|
38
40
|
end
|
39
41
|
|
42
|
+
# @see AbstractDatasource#default_variable_format
|
43
|
+
def default_variable_format
|
44
|
+
'glob'
|
45
|
+
end
|
46
|
+
|
40
47
|
private
|
41
48
|
|
42
49
|
def preformat_response(response_body)
|
@@ -45,12 +52,12 @@ module Grafana
|
|
45
52
|
|
46
53
|
JSON.parse(response_body)['results'].each_value do |query_result|
|
47
54
|
if query_result.key?('error')
|
48
|
-
results[:header] = results[:header]
|
55
|
+
results[:header] = results[:header] + ['SQL Error']
|
49
56
|
results[:content] = [[query_result['error']]]
|
50
57
|
|
51
58
|
elsif query_result['tables']
|
52
59
|
query_result['tables'].each do |table|
|
53
|
-
results[:header] = results[:header]
|
60
|
+
results[:header] = results[:header] + table['columns'].map { |header| header['text'] }
|
54
61
|
results[:content] = table['rows']
|
55
62
|
end
|
56
63
|
|
@@ -59,13 +66,5 @@ module Grafana
|
|
59
66
|
|
60
67
|
results
|
61
68
|
end
|
62
|
-
|
63
|
-
def prepare_sql(sql)
|
64
|
-
# remove comments in query
|
65
|
-
sql.gsub!(/--[^\r\n]*(?:[\r\n]+|$)/, ' ')
|
66
|
-
sql.gsub!(/\r\n/, ' ')
|
67
|
-
sql.gsub!(/\n/, ' ')
|
68
|
-
sql
|
69
|
-
end
|
70
69
|
end
|
71
70
|
end
|