ruby-grafana-reporter 0.2.2 → 0.4.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 +126 -88
- data/bin/ruby-grafana-reporter +2 -2
- data/lib/VERSION.rb +3 -2
- data/lib/grafana/abstract_datasource.rb +146 -0
- data/lib/grafana/dashboard.rb +1 -3
- data/lib/grafana/errors.rb +18 -3
- data/lib/grafana/grafana.rb +64 -66
- 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 +30 -0
- data/lib/grafana/graphite_datasource.rb +72 -0
- data/lib/grafana/image_rendering_datasource.rb +44 -0
- data/lib/grafana/influxdb_datasource.rb +70 -0
- data/lib/grafana/panel.rb +9 -3
- data/lib/grafana/prometheus_datasource.rb +67 -0
- data/lib/grafana/sql_datasource.rb +78 -0
- data/lib/grafana/unsupported_datasource.rb +7 -0
- data/lib/grafana/variable.rb +1 -1
- data/lib/grafana/webrequest.rb +71 -0
- data/lib/grafana_reporter/abstract_query.rb +460 -0
- data/lib/grafana_reporter/abstract_report.rb +139 -18
- data/lib/grafana_reporter/alerts_table_query.rb +39 -0
- data/lib/grafana_reporter/annotations_table_query.rb +38 -0
- data/lib/grafana_reporter/application/application.rb +34 -286
- data/lib/grafana_reporter/application/webservice.rb +50 -15
- data/lib/grafana_reporter/asciidoctor/alerts_table_include_processor.rb +91 -0
- data/lib/grafana_reporter/asciidoctor/annotations_table_include_processor.rb +90 -0
- data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +74 -0
- data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +76 -0
- data/lib/grafana_reporter/asciidoctor/panel_property_inline_macro.rb +70 -0
- data/lib/grafana_reporter/asciidoctor/panel_query_table_include_processor.rb +95 -0
- data/lib/grafana_reporter/asciidoctor/panel_query_value_inline_macro.rb +90 -0
- data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +49 -0
- data/lib/grafana_reporter/asciidoctor/report.rb +32 -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 +90 -0
- data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +86 -0
- data/lib/grafana_reporter/asciidoctor/value_as_variable_include_processor.rb +90 -0
- data/lib/grafana_reporter/configuration.rb +59 -52
- data/lib/grafana_reporter/console_configuration_wizard.rb +311 -0
- data/lib/grafana_reporter/demo_report_wizard.rb +105 -0
- data/lib/grafana_reporter/erb/report.rb +30 -0
- data/lib/grafana_reporter/erb/report_jail.rb +21 -0
- data/lib/grafana_reporter/errors.rb +55 -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 +25 -0
- data/lib/grafana_reporter/panel_property_query.rb +22 -0
- data/lib/grafana_reporter/query_value_query.rb +61 -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} +1 -0
- metadata +47 -39
- 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 -30
- 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 -86
- data/lib/grafana_reporter/asciidoctor/help.rb +0 -435
- data/lib/grafana_reporter/asciidoctor/panel_first_value_query.rb +0 -34
- data/lib/grafana_reporter/asciidoctor/panel_image_query.rb +0 -26
- 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 -301
- data/lib/grafana_reporter/asciidoctor/sql_first_value_query.rb +0 -42
- data/lib/grafana_reporter/asciidoctor/sql_table_query.rb +0 -44
data/lib/grafana/dashboard.rb
CHANGED
data/lib/grafana/errors.rb
CHANGED
@@ -49,18 +49,33 @@ 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
|
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
|
73
|
+
|
74
|
+
# Raised if a datasource implementation cannot handle a query, which is composed
|
75
|
+
# in the grafana visual editor.
|
76
|
+
class ComposedQueryNotSupportedError < GrafanaError
|
77
|
+
def initialize(class_obj)
|
78
|
+
super("Composed queries are not yet supported for datasource '#{class_obj}'.")
|
79
|
+
end
|
80
|
+
end
|
66
81
|
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,54 @@ 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.info('Reporter is running with NON-Admin privileges on grafana.
|
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
|
-
|
54
|
-
|
55
|
-
|
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
|
+
# TODO: add support for grafana builtin datasource types
|
54
|
+
return UnsupportedDatasource.new(nil) if datasource_name.to_s =~ /-- (?:Mixed|Dashboard|Grafana) --/
|
55
|
+
raise DatasourceDoesNotExistError.new('name', datasource_name) unless @datasources[datasource_name]
|
56
|
+
|
57
|
+
@datasources[datasource_name]
|
56
58
|
end
|
57
59
|
|
58
|
-
# Returns
|
60
|
+
# Returns the datasource, which has been queried by the datasource id.
|
59
61
|
#
|
60
|
-
# @
|
61
|
-
|
62
|
-
|
62
|
+
# @param datasource_id [Integer] id of the searched datasource
|
63
|
+
# @return [Datasource] Datasource for the specified datasource id
|
64
|
+
def datasource_by_id(datasource_id)
|
65
|
+
datasource = @datasources.select { |_name, ds| ds.id == datasource_id.to_i }.values.first
|
66
|
+
raise DatasourceDoesNotExistError.new('id', datasource_id) unless datasource
|
67
|
+
|
68
|
+
datasource
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [Array] Array of dashboard uids within the current grafana object
|
72
|
+
def dashboard_ids
|
73
|
+
response = prepare_request({ relative_url: '/api/search' }).execute
|
74
|
+
return [] unless response.is_a?(Net::HTTPOK)
|
75
|
+
|
76
|
+
dashboards = JSON.parse(response.body)
|
77
|
+
|
78
|
+
dashboards.each do |dashboard|
|
79
|
+
@dashboards[dashboard['uid']] = nil unless @dashboards[dashboard['uid']]
|
80
|
+
end
|
81
|
+
|
82
|
+
@dashboards.keys
|
63
83
|
end
|
64
84
|
|
65
85
|
# @param dashboard_uid [String] UID of the searched {Dashboard}
|
@@ -67,64 +87,42 @@ module Grafana
|
|
67
87
|
def dashboard(dashboard_uid)
|
68
88
|
return @dashboards[dashboard_uid] unless @dashboards[dashboard_uid].nil?
|
69
89
|
|
70
|
-
response =
|
71
|
-
|
72
|
-
|
73
|
-
raise DashboardDoesNotExistError, dashboard_uid if model.nil?
|
90
|
+
response = prepare_request({ relative_url: "/api/dashboards/uid/#{dashboard_uid}" }).execute
|
91
|
+
raise DashboardDoesNotExistError, dashboard_uid unless response.is_a?(Net::HTTPOK)
|
74
92
|
|
75
93
|
# cache dashboard for reuse
|
94
|
+
model = JSON.parse(response.body)['dashboard']
|
76
95
|
@dashboards[dashboard_uid] = Dashboard.new(model, self)
|
77
96
|
|
78
97
|
@dashboards[dashboard_uid]
|
79
98
|
end
|
80
99
|
|
81
|
-
#
|
100
|
+
# Prepares a {WebRequest} object for the current {Grafana} instance, which may be enriched
|
101
|
+
# with further properties and can then run {WebRequest#execute}.
|
82
102
|
#
|
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)
|
103
|
+
# @option options [Hash] :relative_url relative URL with a leading slash, which shall be queried
|
104
|
+
# @option options [Hash] :accept
|
105
|
+
# @option options [Hash] :body
|
106
|
+
# @option options [Hash] :content_type
|
107
|
+
# @return [WebRequest] webrequest prepared for execution
|
108
|
+
def prepare_request(options = {})
|
109
|
+
auth = @key ? { authorization: "Bearer #{@key}" } : {}
|
110
|
+
WebRequest.new(@base_uri, auth.merge({ logger: @logger }).merge(options))
|
111
111
|
end
|
112
112
|
|
113
113
|
private
|
114
114
|
|
115
|
-
def
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
@datasources = {}
|
125
|
-
end
|
115
|
+
def initialize_datasources
|
116
|
+
@datasources = {}
|
117
|
+
|
118
|
+
settings = prepare_request({ relative_url: '/api/frontend/settings' }).execute
|
119
|
+
return unless settings.is_a?(Net::HTTPOK)
|
120
|
+
|
121
|
+
json = JSON.parse(settings.body)
|
122
|
+
json['datasources'].select { |_k, v| v['id'].to_i.positive? }.each do |ds_name, ds_value|
|
123
|
+
@datasources[ds_name] = AbstractDatasource.build_instance(ds_value)
|
126
124
|
end
|
127
|
-
@datasources
|
125
|
+
@datasources['default'] = @datasources[json['defaultDatasource']]
|
128
126
|
end
|
129
127
|
end
|
130
128
|
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,30 @@
|
|
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
|
+
|
25
|
+
# @see AbstractDatasource#default_variable_format
|
26
|
+
def default_variable_format
|
27
|
+
'glob'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,72 @@
|
|
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
|
+
# @see AbstractDatasource#default_variable_format
|
40
|
+
def default_variable_format
|
41
|
+
'glob'
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# @see AbstractDatasource#preformat_response
|
47
|
+
def preformat_response(response_body)
|
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] } }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|