ruby-grafana-reporter 0.2.0 → 0.4.1
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 +105 -86
- data/bin/ruby-grafana-reporter +5 -5
- data/lib/VERSION.rb +3 -2
- data/lib/grafana/abstract_datasource.rb +136 -0
- data/lib/grafana/dashboard.rb +21 -23
- data/lib/grafana/errors.rb +8 -1
- data/lib/grafana/grafana.rb +61 -65
- data/lib/grafana/grafana_alerts_datasource.rb +57 -0
- data/lib/grafana/grafana_annotations_datasource.rb +56 -0
- data/lib/grafana/grafana_property_datasource.rb +25 -0
- data/lib/grafana/graphite_datasource.rb +50 -0
- data/lib/grafana/image_rendering_datasource.rb +44 -0
- data/lib/grafana/panel.rb +9 -3
- data/lib/grafana/prometheus_datasource.rb +45 -0
- data/lib/grafana/sql_datasource.rb +71 -0
- data/lib/grafana/unsupported_datasource.rb +7 -0
- data/lib/grafana/variable.rb +3 -2
- data/lib/grafana/webrequest.rb +71 -0
- data/lib/grafana_reporter/abstract_query.rb +359 -0
- data/lib/grafana_reporter/abstract_report.rb +119 -17
- data/lib/grafana_reporter/alerts_table_query.rb +44 -0
- data/lib/grafana_reporter/annotations_table_query.rb +43 -0
- data/lib/grafana_reporter/application/application.rb +49 -297
- data/lib/grafana_reporter/application/webservice.rb +49 -14
- data/lib/grafana_reporter/asciidoctor/alerts_table_include_processor.rb +90 -0
- data/lib/grafana_reporter/asciidoctor/annotations_table_include_processor.rb +89 -0
- data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +77 -0
- data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +79 -0
- data/lib/grafana_reporter/asciidoctor/panel_property_inline_macro.rb +73 -0
- data/lib/grafana_reporter/asciidoctor/panel_query_table_include_processor.rb +99 -0
- data/lib/grafana_reporter/asciidoctor/panel_query_value_inline_macro.rb +93 -0
- data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +64 -0
- data/lib/grafana_reporter/asciidoctor/report.rb +47 -76
- data/lib/grafana_reporter/asciidoctor/show_environment_include_processor.rb +46 -0
- data/lib/grafana_reporter/asciidoctor/show_help_include_processor.rb +35 -0
- data/lib/grafana_reporter/asciidoctor/sql_table_include_processor.rb +92 -0
- data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +88 -0
- data/lib/grafana_reporter/asciidoctor/value_as_variable_include_processor.rb +90 -0
- data/lib/grafana_reporter/configuration.rb +108 -43
- data/lib/grafana_reporter/console_configuration_wizard.rb +311 -0
- data/lib/grafana_reporter/demo_report_wizard.rb +101 -0
- data/lib/grafana_reporter/erb/report.rb +43 -0
- data/lib/grafana_reporter/errors.rb +41 -0
- data/lib/grafana_reporter/help.rb +443 -0
- data/lib/grafana_reporter/logger/{two_way_logger.rb → two_way_delegate_logger.rb} +1 -1
- data/lib/grafana_reporter/panel_image_query.rb +29 -0
- data/lib/grafana_reporter/panel_property_query.rb +22 -0
- data/lib/grafana_reporter/query_value_query.rb +79 -0
- data/lib/grafana_reporter/report_webhook.rb +35 -0
- data/lib/ruby_grafana_extension.rb +8 -0
- data/lib/{ruby-grafana-reporter.rb → ruby_grafana_reporter.rb} +13 -0
- metadata +47 -43
- data/lib/grafana/abstract_panel_query.rb +0 -22
- data/lib/grafana/abstract_query.rb +0 -132
- data/lib/grafana/abstract_sql_query.rb +0 -51
- data/lib/grafana/panel_image_query.rb +0 -52
- data/lib/grafana_reporter/asciidoctor/alerts_table_query.rb +0 -104
- data/lib/grafana_reporter/asciidoctor/annotations_table_query.rb +0 -99
- data/lib/grafana_reporter/asciidoctor/errors.rb +0 -40
- data/lib/grafana_reporter/asciidoctor/extensions/alerts_table_include_processor.rb +0 -92
- data/lib/grafana_reporter/asciidoctor/extensions/annotations_table_include_processor.rb +0 -91
- data/lib/grafana_reporter/asciidoctor/extensions/panel_image_block_macro.rb +0 -69
- data/lib/grafana_reporter/asciidoctor/extensions/panel_image_inline_macro.rb +0 -68
- data/lib/grafana_reporter/asciidoctor/extensions/panel_property_inline_macro.rb +0 -61
- data/lib/grafana_reporter/asciidoctor/extensions/panel_query_table_include_processor.rb +0 -78
- data/lib/grafana_reporter/asciidoctor/extensions/panel_query_value_inline_macro.rb +0 -73
- data/lib/grafana_reporter/asciidoctor/extensions/processor_mixin.rb +0 -20
- data/lib/grafana_reporter/asciidoctor/extensions/show_environment_include_processor.rb +0 -43
- data/lib/grafana_reporter/asciidoctor/extensions/show_help_include_processor.rb +0 -202
- data/lib/grafana_reporter/asciidoctor/extensions/sql_table_include_processor.rb +0 -70
- data/lib/grafana_reporter/asciidoctor/extensions/sql_value_inline_macro.rb +0 -66
- data/lib/grafana_reporter/asciidoctor/extensions/value_as_variable_include_processor.rb +0 -61
- data/lib/grafana_reporter/asciidoctor/panel_first_value_query.rb +0 -34
- data/lib/grafana_reporter/asciidoctor/panel_image_query.rb +0 -25
- data/lib/grafana_reporter/asciidoctor/panel_property_query.rb +0 -44
- data/lib/grafana_reporter/asciidoctor/panel_table_query.rb +0 -38
- data/lib/grafana_reporter/asciidoctor/query_mixin.rb +0 -310
- data/lib/grafana_reporter/asciidoctor/sql_first_value_query.rb +0 -37
- data/lib/grafana_reporter/asciidoctor/sql_table_query.rb +0 -39
data/lib/grafana/errors.rb
CHANGED
@@ -57,10 +57,17 @@ module Grafana
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
-
# Raised if no SQL query is specified
|
60
|
+
# Raised if no SQL query is specified.
|
61
61
|
class MissingSqlQueryError < GrafanaError
|
62
62
|
def initialize
|
63
63
|
super('No SQL statement has been specified.')
|
64
64
|
end
|
65
65
|
end
|
66
|
+
|
67
|
+
# Raised if a datasource shall be queried, which is not (yet) supported by the reporter
|
68
|
+
class InvalidDatasourceQueryProvidedError < GrafanaError
|
69
|
+
def initialize(query)
|
70
|
+
super("The datasource query provided, does not look like a grafana datasource target (received: #{query}).")
|
71
|
+
end
|
72
|
+
end
|
66
73
|
end
|
data/lib/grafana/grafana.rb
CHANGED
@@ -9,20 +9,20 @@
|
|
9
9
|
module Grafana
|
10
10
|
# Main class for handling the interaction with one specific Grafana instance.
|
11
11
|
class Grafana
|
12
|
+
attr_reader :logger
|
13
|
+
|
12
14
|
# @param base_uri [String] full URI pointing to the specific grafana instance without
|
13
15
|
# trailing slash, e.g. +https://localhost:3000+.
|
14
16
|
# @param key [String] API key for the grafana instance, if required
|
15
17
|
# @param opts [Hash] additional options.
|
16
|
-
# Currently supporting +:logger
|
17
|
-
# +:datasources+ need to be an Hash with datasource name as key and datasource id as value.
|
18
|
-
# If not specified, the datasources will be queried from the grafana interface.
|
19
|
-
# Specifying +:datasources+> here can be used, so that the interface can be used without grafana Admin privileges.
|
18
|
+
# Currently supporting +:logger+.
|
20
19
|
def initialize(base_uri, key = nil, opts = {})
|
21
20
|
@base_uri = base_uri
|
22
21
|
@key = key
|
23
22
|
@dashboards = {}
|
24
23
|
@logger = opts[:logger] || ::Logger.new(nil)
|
25
|
-
|
24
|
+
|
25
|
+
initialize_datasources unless @base_uri.empty?
|
26
26
|
end
|
27
27
|
|
28
28
|
# Used to test a connection to the grafana instance.
|
@@ -32,34 +32,52 @@ module Grafana
|
|
32
32
|
#
|
33
33
|
# @return [String] +Admin+, +NON-Admin+ or +Failed+ is returned, depending on the test results
|
34
34
|
def test_connection
|
35
|
-
if
|
35
|
+
if prepare_request({ relative_url: '/api/datasources' }).execute.is_a?(Net::HTTPOK)
|
36
36
|
# we have admin rights
|
37
|
-
@logger.
|
37
|
+
@logger.warn('Reporter is running with Admin privileges on grafana. This is a potential security risk.')
|
38
38
|
return 'Admin'
|
39
39
|
end
|
40
40
|
# check if we have lower rights
|
41
|
-
return 'Failed' unless
|
41
|
+
return 'Failed' unless prepare_request({ relative_url: '/api/dashboards/home' }).execute.is_a?(Net::HTTPOK)
|
42
42
|
|
43
|
-
@logger.
|
44
|
-
'datasources are specified in CONFIG_FILE, otherwise operation will fail')
|
43
|
+
@logger.info('Reporter is running with NON-Admin privileges on grafana.')
|
45
44
|
'NON-Admin'
|
46
45
|
end
|
47
46
|
|
48
|
-
# Returns the
|
47
|
+
# Returns the datasource, which has been queried by the datasource name.
|
49
48
|
#
|
50
|
-
# @
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
# @param datasource_name [String] name of the searched datasource
|
50
|
+
# @return [Datasource] Datasource for the specified datasource name
|
51
|
+
def datasource_by_name(datasource_name)
|
52
|
+
datasource_name = 'default' if datasource_name.to_s.empty?
|
53
|
+
raise DatasourceDoesNotExistError.new('name', datasource_name) unless @datasources[datasource_name]
|
54
54
|
|
55
|
-
|
55
|
+
@datasources[datasource_name]
|
56
56
|
end
|
57
57
|
|
58
|
-
# Returns
|
58
|
+
# Returns the datasource, which has been queried by the datasource id.
|
59
59
|
#
|
60
|
-
# @
|
61
|
-
|
62
|
-
|
60
|
+
# @param datasource_id [Integer] id of the searched datasource
|
61
|
+
# @return [Datasource] Datasource for the specified datasource id
|
62
|
+
def datasource_by_id(datasource_id)
|
63
|
+
datasource = @datasources.select { |_name, ds| ds.id == datasource_id.to_i }.values.first
|
64
|
+
raise DatasourceDoesNotExistError.new('id', datasource_id) unless datasource
|
65
|
+
|
66
|
+
datasource
|
67
|
+
end
|
68
|
+
|
69
|
+
# @return [Array] Array of dashboard uids within the current grafana object
|
70
|
+
def dashboard_ids
|
71
|
+
response = prepare_request({ relative_url: '/api/search' }).execute
|
72
|
+
return [] unless response.is_a?(Net::HTTPOK)
|
73
|
+
|
74
|
+
dashboards = JSON.parse(response.body)
|
75
|
+
|
76
|
+
dashboards.each do |dashboard|
|
77
|
+
@dashboards[dashboard['uid']] = nil unless @dashboards[dashboard['uid']]
|
78
|
+
end
|
79
|
+
|
80
|
+
@dashboards.keys
|
63
81
|
end
|
64
82
|
|
65
83
|
# @param dashboard_uid [String] UID of the searched {Dashboard}
|
@@ -67,64 +85,42 @@ module Grafana
|
|
67
85
|
def dashboard(dashboard_uid)
|
68
86
|
return @dashboards[dashboard_uid] unless @dashboards[dashboard_uid].nil?
|
69
87
|
|
70
|
-
response =
|
71
|
-
|
72
|
-
|
73
|
-
raise DashboardDoesNotExistError, dashboard_uid if model.nil?
|
88
|
+
response = prepare_request({ relative_url: "/api/dashboards/uid/#{dashboard_uid}" }).execute
|
89
|
+
raise DashboardDoesNotExistError, dashboard_uid unless response.is_a?(Net::HTTPOK)
|
74
90
|
|
75
91
|
# cache dashboard for reuse
|
92
|
+
model = JSON.parse(response.body)['dashboard']
|
76
93
|
@dashboards[dashboard_uid] = Dashboard.new(model, self)
|
77
94
|
|
78
95
|
@dashboards[dashboard_uid]
|
79
96
|
end
|
80
97
|
|
81
|
-
#
|
98
|
+
# Prepares a {WebRequest} object for the current {Grafana} instance, which may be enriched
|
99
|
+
# with further properties and can then run {WebRequest#execute}.
|
82
100
|
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
def execute_http_request(relative_uri, options = {}, timeout = nil)
|
92
|
-
uri = URI.parse(@base_uri + relative_uri)
|
93
|
-
default_options = { accept: 'application/json', request: Net::HTTP::Get, content_type: 'application/json' }
|
94
|
-
options = default_options.merge(options)
|
95
|
-
|
96
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
97
|
-
if @base_uri =~ /^https/
|
98
|
-
http.use_ssl = true
|
99
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
100
|
-
end
|
101
|
-
http.read_timeout = timeout.to_i if timeout
|
102
|
-
|
103
|
-
request = options[:request].new(uri.request_uri)
|
104
|
-
request['Accept'] = options[:accept]
|
105
|
-
request['Content-Type'] = options[:content_type]
|
106
|
-
request['Authorization'] = "Bearer #{@key}" unless @key.nil?
|
107
|
-
request.body = options[:body]
|
108
|
-
|
109
|
-
@logger.debug("Requesting #{relative_uri} with '#{options[:body]}' and timeout '#{http.read_timeout}'")
|
110
|
-
http.request(request)
|
101
|
+
# @option options [Hash] :relative_url relative URL with a leading slash, which shall be queried
|
102
|
+
# @option options [Hash] :accept
|
103
|
+
# @option options [Hash] :body
|
104
|
+
# @option options [Hash] :content_type
|
105
|
+
# @return [WebRequest] webrequest prepared for execution
|
106
|
+
def prepare_request(options = {})
|
107
|
+
auth = @key ? { authorization: "Bearer #{@key}" } : {}
|
108
|
+
WebRequest.new(@base_uri, auth.merge({ logger: @logger }).merge(options))
|
111
109
|
end
|
112
110
|
|
113
111
|
private
|
114
112
|
|
115
|
-
def
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
@datasources = {}
|
125
|
-
end
|
113
|
+
def initialize_datasources
|
114
|
+
@datasources = {}
|
115
|
+
|
116
|
+
settings = prepare_request({ relative_url: '/api/frontend/settings' }).execute
|
117
|
+
return unless settings.is_a?(Net::HTTPOK)
|
118
|
+
|
119
|
+
json = JSON.parse(settings.body)
|
120
|
+
json['datasources'].select { |_k, v| v['id'].to_i.positive? }.each do |ds_name, ds_value|
|
121
|
+
@datasources[ds_name] = AbstractDatasource.build_instance(ds_value)
|
126
122
|
end
|
127
|
-
@datasources
|
123
|
+
@datasources['default'] = @datasources[json['defaultDatasource']]
|
128
124
|
end
|
129
125
|
end
|
130
126
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grafana
|
4
|
+
# Implements the datasource interface to grafana alerts.
|
5
|
+
class GrafanaAlertsDatasource < AbstractDatasource
|
6
|
+
# +:raw_query+ needs to contain a Hash with the following structure:
|
7
|
+
#
|
8
|
+
# {
|
9
|
+
# dashboardId: Dashboard ID as String or nil
|
10
|
+
# panelId: Panel ID as String or nil
|
11
|
+
# columns:
|
12
|
+
# limit:
|
13
|
+
# query:
|
14
|
+
# state:
|
15
|
+
# folderId:
|
16
|
+
# dashboardQuery:
|
17
|
+
# dashboardTag:
|
18
|
+
# }
|
19
|
+
# @see AbstractDatasource#request
|
20
|
+
def request(query_description)
|
21
|
+
webrequest = query_description[:prepared_request]
|
22
|
+
webrequest.relative_url = "/api/alerts#{url_parameters(query_description)}"
|
23
|
+
|
24
|
+
result = webrequest.execute(query_description[:timeout])
|
25
|
+
|
26
|
+
json = JSON.parse(result.body)
|
27
|
+
|
28
|
+
content = []
|
29
|
+
begin
|
30
|
+
json.each { |item| content << item.fetch_values(*query_description[:raw_query]['columns'].split(',')) }
|
31
|
+
rescue KeyError => e
|
32
|
+
raise MalformedAttributeContentError.new(e.message, 'columns', query_description[:raw_query]['columns'])
|
33
|
+
end
|
34
|
+
|
35
|
+
result = {}
|
36
|
+
result[:header] = [query_description[:raw_query]['columns'].split(',')]
|
37
|
+
result[:content] = content
|
38
|
+
|
39
|
+
result
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def url_parameters(query_desc)
|
45
|
+
url_vars = {}
|
46
|
+
url_vars.merge!(query_desc[:raw_query].select do |k, _v|
|
47
|
+
k =~ /^(?:limit|dashboardId|panelId|query|state|folderId|dashboardQuery|dashboardTag)/
|
48
|
+
end)
|
49
|
+
url_vars['from'] = query_desc[:from] if query_desc[:from]
|
50
|
+
url_vars['to'] = query_desc[:to] if query_desc[:to]
|
51
|
+
url_params = URI.encode_www_form(url_vars.map { |k, v| [k, v.to_s] })
|
52
|
+
return '' if url_params.empty?
|
53
|
+
|
54
|
+
"?#{url_params}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grafana
|
4
|
+
# Implements the datasource interface to grafana annotations.
|
5
|
+
class GrafanaAnnotationsDatasource < AbstractDatasource
|
6
|
+
# +:raw_query+ needs to contain a Hash with the following structure:
|
7
|
+
#
|
8
|
+
# {
|
9
|
+
# dashboardId: Dashboard ID as String or nil
|
10
|
+
# panelId: Panel ID as String or nil
|
11
|
+
# columns:
|
12
|
+
# limit:
|
13
|
+
# alertId:
|
14
|
+
# userId:
|
15
|
+
# type:
|
16
|
+
# tags:
|
17
|
+
# }
|
18
|
+
# @see AbstractDatasource#request
|
19
|
+
def request(query_description)
|
20
|
+
webrequest = query_description[:prepared_request]
|
21
|
+
webrequest.relative_url = "/api/annotations#{url_parameters(query_description)}"
|
22
|
+
|
23
|
+
result = webrequest.execute(query_description[:timeout])
|
24
|
+
|
25
|
+
json = JSON.parse(result.body)
|
26
|
+
|
27
|
+
content = []
|
28
|
+
begin
|
29
|
+
json.each { |item| content << item.fetch_values(*query_description[:raw_query]['columns'].split(',')) }
|
30
|
+
rescue KeyError => e
|
31
|
+
raise MalformedAttributeContentError.new(e.message, 'columns', query_description[:raw_query]['columns'])
|
32
|
+
end
|
33
|
+
|
34
|
+
result = {}
|
35
|
+
result[:header] = [query_description[:raw_query]['columns'].split(',')]
|
36
|
+
result[:content] = content
|
37
|
+
|
38
|
+
result
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def url_parameters(query_desc)
|
44
|
+
url_vars = {}
|
45
|
+
url_vars.merge!(query_desc[:raw_query].select do |k, _v|
|
46
|
+
k =~ /^(?:limit|alertId|dashboardId|panelId|userId|type|tags)/
|
47
|
+
end)
|
48
|
+
url_vars['from'] = query_desc[:from] if query_desc[:from]
|
49
|
+
url_vars['to'] = query_desc[:to] if query_desc[:to]
|
50
|
+
url_params = URI.encode_www_form(url_vars.map { |k, v| [k, v.to_s] })
|
51
|
+
return '' if url_params.empty?
|
52
|
+
|
53
|
+
"?#{url_params}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grafana
|
4
|
+
# Implements the datasource interface to grafana model properties.
|
5
|
+
class GrafanaPropertyDatasource < AbstractDatasource
|
6
|
+
# +:raw_query+ needs to contain a Hash with the following structure:
|
7
|
+
#
|
8
|
+
# {
|
9
|
+
# property_name: Name of the queried property as String
|
10
|
+
# panel: {Panel} object to query
|
11
|
+
# }
|
12
|
+
# @see AbstractDatasource#request
|
13
|
+
def request(query_description)
|
14
|
+
raise MissingSqlQueryError if query_description[:raw_query].nil?
|
15
|
+
|
16
|
+
panel = query_description[:raw_query][:panel]
|
17
|
+
property_name = query_description[:raw_query][:property_name]
|
18
|
+
|
19
|
+
{
|
20
|
+
header: [query_description[:raw_query][:property_name]],
|
21
|
+
content: [replace_variables(panel.field(property_name), query_description[:variables])]
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grafana
|
4
|
+
# Implements the interface to graphite datasources.
|
5
|
+
class GraphiteDatasource < AbstractDatasource
|
6
|
+
# @see AbstractDatasource#handles?
|
7
|
+
def self.handles?(model)
|
8
|
+
tmp = new(model)
|
9
|
+
tmp.type == 'graphite'
|
10
|
+
end
|
11
|
+
|
12
|
+
# +:raw_query+ needs to contain a Graphite query as String
|
13
|
+
# @see AbstractDatasource#request
|
14
|
+
def request(query_description)
|
15
|
+
raise MissingSqlQueryError if query_description[:raw_query].nil?
|
16
|
+
|
17
|
+
request = {
|
18
|
+
body: URI.encode_www_form('from': DateTime.strptime(query_description[:from], '%Q').strftime('%H:%M_%Y%m%d'),
|
19
|
+
'until': DateTime.strptime(query_description[:to], '%Q').strftime('%H:%M_%Y%m%d'),
|
20
|
+
'format': 'json',
|
21
|
+
'target': replace_variables(query_description[:raw_query], query_description[:variables])),
|
22
|
+
content_type: 'application/x-www-form-urlencoded',
|
23
|
+
request: Net::HTTP::Post
|
24
|
+
}
|
25
|
+
|
26
|
+
webrequest = query_description[:prepared_request]
|
27
|
+
webrequest.relative_url = "/api/datasources/proxy/#{id}/render"
|
28
|
+
webrequest.options.merge!(request)
|
29
|
+
|
30
|
+
result = webrequest.execute(query_description[:timeout])
|
31
|
+
preformat_response(result.body)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @see AbstractDatasource#raw_query_from_panel_model
|
35
|
+
def raw_query_from_panel_model(panel_query_target)
|
36
|
+
panel_query_target['target']
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# @see AbstractDatasource#preformat_response
|
42
|
+
def preformat_response(response_body)
|
43
|
+
# TODO: support multiple metrics as return types
|
44
|
+
{
|
45
|
+
header: %w[value time],
|
46
|
+
content: JSON.parse(response_body).first['datapoints']
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grafana
|
4
|
+
# Implements the interface to image rendering datasources.
|
5
|
+
class ImageRenderingDatasource < AbstractDatasource
|
6
|
+
# +:raw_query+ needs to contain a Hash with the following structure:
|
7
|
+
#
|
8
|
+
# {
|
9
|
+
# panel: {Panel} which shall be rendered
|
10
|
+
# }
|
11
|
+
# @see AbstractDatasource#request
|
12
|
+
def request(query_description)
|
13
|
+
webrequest = query_description[:prepared_request]
|
14
|
+
webrequest.relative_url = query_description[:raw_query][:panel].render_url + url_params(query_description)
|
15
|
+
webrequest.options.merge!({ accept: 'image/png' })
|
16
|
+
|
17
|
+
result = webrequest.execute
|
18
|
+
|
19
|
+
{ header: ['image'], content: [result.body] }
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def url_params(query_desc)
|
25
|
+
url_vars = query_desc[:variables].select { |k, _v| k =~ /^(?:timeout|height|width|theme|fullscreen|var-.+)$/ }
|
26
|
+
url_vars = default_vars.merge(url_vars)
|
27
|
+
url_vars['from'] = Variable.new(query_desc[:from])
|
28
|
+
url_vars['to'] = Variable.new(query_desc[:to])
|
29
|
+
result = URI.encode_www_form(url_vars.map { |k, v| [k, v.raw_value.to_s] })
|
30
|
+
|
31
|
+
return '' if result.empty?
|
32
|
+
|
33
|
+
"&#{result}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def default_vars
|
37
|
+
{
|
38
|
+
'fullscreen' => Variable.new(true),
|
39
|
+
'theme' => Variable.new('light'),
|
40
|
+
'timeout' => Variable.new(60)
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/grafana/panel.rb
CHANGED
@@ -5,6 +5,7 @@ module Grafana
|
|
5
5
|
class Panel
|
6
6
|
# @return [Dashboard] parent {Dashboard} object
|
7
7
|
attr_reader :dashboard
|
8
|
+
attr_reader :model
|
8
9
|
|
9
10
|
# @param model [Hash] converted JSON Hash of the panel
|
10
11
|
# @param dashboard [Dashboard] parent {Dashboard} object
|
@@ -25,12 +26,17 @@ module Grafana
|
|
25
26
|
@model['id']
|
26
27
|
end
|
27
28
|
|
28
|
-
# @return [
|
29
|
+
# @return [Datasource] datasource object specified for the current panel
|
30
|
+
def datasource
|
31
|
+
dashboard.grafana.datasource_by_name(@model['datasource'])
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [String] query string for the requested query letter
|
29
35
|
def query(query_letter)
|
30
36
|
query_item = @model['targets'].select { |item| item['refId'].to_s == query_letter.to_s }.first
|
31
|
-
raise QueryLetterDoesNotExistError.new(query_letter, self)
|
37
|
+
raise QueryLetterDoesNotExistError.new(query_letter, self) unless query_item
|
32
38
|
|
33
|
-
query_item
|
39
|
+
datasource.raw_query_from_panel_model(query_item)
|
34
40
|
end
|
35
41
|
|
36
42
|
# @return [String] relative rendering URL for the panel, to create an image out of it
|