ruby-grafana-reporter 0.3.0 → 0.4.4

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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +337 -170
  3. data/bin/ruby-grafana-reporter +5 -5
  4. data/lib/VERSION.rb +3 -2
  5. data/lib/grafana/abstract_datasource.rb +149 -0
  6. data/lib/grafana/dashboard.rb +1 -3
  7. data/lib/grafana/errors.rb +20 -5
  8. data/lib/grafana/grafana.rb +52 -57
  9. data/lib/grafana/grafana_alerts_datasource.rb +57 -0
  10. data/lib/grafana/grafana_annotations_datasource.rb +56 -0
  11. data/lib/grafana/grafana_property_datasource.rb +37 -0
  12. data/lib/grafana/graphite_datasource.rb +72 -0
  13. data/lib/grafana/image_rendering_datasource.rb +44 -0
  14. data/lib/grafana/influxdb_datasource.rb +70 -0
  15. data/lib/grafana/panel.rb +10 -4
  16. data/lib/grafana/prometheus_datasource.rb +67 -0
  17. data/lib/grafana/sql_datasource.rb +70 -0
  18. data/lib/grafana/unsupported_datasource.rb +7 -0
  19. data/lib/grafana/variable.rb +27 -21
  20. data/lib/grafana/webrequest.rb +71 -0
  21. data/lib/grafana_reporter/abstract_query.rb +478 -0
  22. data/lib/grafana_reporter/abstract_report.rb +152 -18
  23. data/lib/grafana_reporter/abstract_table_format_strategy.rb +34 -0
  24. data/lib/grafana_reporter/alerts_table_query.rb +43 -0
  25. data/lib/grafana_reporter/annotations_table_query.rb +42 -0
  26. data/lib/grafana_reporter/application/application.rb +28 -25
  27. data/lib/grafana_reporter/application/webservice.rb +80 -39
  28. data/lib/grafana_reporter/asciidoctor/adoc_plain_table_format_strategy.rb +25 -0
  29. data/lib/grafana_reporter/asciidoctor/alerts_table_include_processor.rb +92 -0
  30. data/lib/grafana_reporter/asciidoctor/annotations_table_include_processor.rb +91 -0
  31. data/lib/grafana_reporter/asciidoctor/help.rb +336 -313
  32. data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +78 -0
  33. data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +80 -0
  34. data/lib/grafana_reporter/asciidoctor/panel_property_inline_macro.rb +74 -0
  35. data/lib/grafana_reporter/asciidoctor/panel_query_table_include_processor.rb +99 -0
  36. data/lib/grafana_reporter/asciidoctor/panel_query_value_inline_macro.rb +93 -0
  37. data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +50 -0
  38. data/lib/grafana_reporter/asciidoctor/report.rb +41 -82
  39. data/lib/grafana_reporter/asciidoctor/show_environment_include_processor.rb +46 -0
  40. data/lib/grafana_reporter/asciidoctor/show_help_include_processor.rb +35 -0
  41. data/lib/grafana_reporter/asciidoctor/sql_table_include_processor.rb +94 -0
  42. data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +90 -0
  43. data/lib/grafana_reporter/asciidoctor/value_as_variable_include_processor.rb +90 -0
  44. data/lib/grafana_reporter/configuration.rb +26 -8
  45. data/lib/grafana_reporter/console_configuration_wizard.rb +109 -67
  46. data/lib/grafana_reporter/csv_table_format_strategy.rb +23 -0
  47. data/lib/grafana_reporter/demo_report_wizard.rb +104 -0
  48. data/lib/grafana_reporter/erb/demo_report_builder.rb +46 -0
  49. data/lib/grafana_reporter/erb/report.rb +36 -0
  50. data/lib/grafana_reporter/erb/report_jail.rb +21 -0
  51. data/lib/grafana_reporter/errors.rb +57 -0
  52. data/lib/grafana_reporter/logger/{two_way_logger.rb → two_way_delegate_logger.rb} +1 -1
  53. data/lib/grafana_reporter/panel_image_query.rb +25 -0
  54. data/lib/grafana_reporter/panel_property_query.rb +22 -0
  55. data/lib/grafana_reporter/query_value_query.rb +61 -0
  56. data/lib/grafana_reporter/report_webhook.rb +39 -0
  57. data/lib/ruby_grafana_extension.rb +8 -0
  58. data/lib/{ruby-grafana-reporter.rb → ruby_grafana_reporter.rb} +1 -3
  59. metadata +49 -38
  60. data/lib/grafana/abstract_panel_query.rb +0 -22
  61. data/lib/grafana/abstract_query.rb +0 -132
  62. data/lib/grafana/abstract_sql_query.rb +0 -51
  63. data/lib/grafana/panel_image_query.rb +0 -52
  64. data/lib/grafana_reporter/asciidoctor/alerts_table_query.rb +0 -101
  65. data/lib/grafana_reporter/asciidoctor/annotations_table_query.rb +0 -96
  66. data/lib/grafana_reporter/asciidoctor/errors.rb +0 -40
  67. data/lib/grafana_reporter/asciidoctor/extensions/alerts_table_include_processor.rb +0 -92
  68. data/lib/grafana_reporter/asciidoctor/extensions/annotations_table_include_processor.rb +0 -91
  69. data/lib/grafana_reporter/asciidoctor/extensions/panel_image_block_macro.rb +0 -69
  70. data/lib/grafana_reporter/asciidoctor/extensions/panel_image_inline_macro.rb +0 -68
  71. data/lib/grafana_reporter/asciidoctor/extensions/panel_property_inline_macro.rb +0 -61
  72. data/lib/grafana_reporter/asciidoctor/extensions/panel_query_table_include_processor.rb +0 -78
  73. data/lib/grafana_reporter/asciidoctor/extensions/panel_query_value_inline_macro.rb +0 -73
  74. data/lib/grafana_reporter/asciidoctor/extensions/processor_mixin.rb +0 -20
  75. data/lib/grafana_reporter/asciidoctor/extensions/show_environment_include_processor.rb +0 -43
  76. data/lib/grafana_reporter/asciidoctor/extensions/show_help_include_processor.rb +0 -30
  77. data/lib/grafana_reporter/asciidoctor/extensions/sql_table_include_processor.rb +0 -70
  78. data/lib/grafana_reporter/asciidoctor/extensions/sql_value_inline_macro.rb +0 -66
  79. data/lib/grafana_reporter/asciidoctor/extensions/value_as_variable_include_processor.rb +0 -88
  80. data/lib/grafana_reporter/asciidoctor/panel_first_value_query.rb +0 -36
  81. data/lib/grafana_reporter/asciidoctor/panel_image_query.rb +0 -28
  82. data/lib/grafana_reporter/asciidoctor/panel_property_query.rb +0 -44
  83. data/lib/grafana_reporter/asciidoctor/panel_table_query.rb +0 -40
  84. data/lib/grafana_reporter/asciidoctor/query_mixin.rb +0 -312
  85. data/lib/grafana_reporter/asciidoctor/sql_first_value_query.rb +0 -42
  86. data/lib/grafana_reporter/asciidoctor/sql_table_query.rb +0 -44
@@ -1,5 +1,5 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require_relative '../lib/ruby-grafana-reporter'
5
- GrafanaReporter::Application::Application.new.configure_and_run(ARGV) unless defined?(Ocra)
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/ruby_grafana_reporter'
5
+ GrafanaReporter::Application::Application.new.configure_and_run(ARGV) unless defined?(Ocra)
data/lib/VERSION.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Version information
4
- GRAFANA_REPORTER_VERSION = [0, 3, 0].freeze
5
- GRAFANA_REPORTER_RELEASE_DATE = '2021-03-02'
4
+ GRAFANA_REPORTER_VERSION = [0, 4, 4].freeze
5
+ # Release date
6
+ GRAFANA_REPORTER_RELEASE_DATE = '2021-07-12'
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grafana
4
+ # This abstract class defines the base functionalities for the common datasource implementations.
5
+ # Additionally it provides a factory method to build a real datasource from a given specification.
6
+ class AbstractDatasource
7
+ attr_reader :model
8
+
9
+ @@subclasses = []
10
+
11
+ # Registers the subclass as datasource.
12
+ # @param subclass [Class] class inheriting from this abstract class
13
+ def self.inherited(subclass)
14
+ @@subclasses << subclass
15
+ end
16
+
17
+ # Overwrite this method, to specify if the current datasource implementation handles the given model.
18
+ # This method is called by {build_instance} to determine, if the current datasource implementation
19
+ # can handle the given grafana model. By default this method returns false.
20
+ # @param model [Hash] grafana specification of the datasource to check
21
+ # @return [Boolean] True if fits, false otherwise
22
+ def self.handles?(model)
23
+ false
24
+ end
25
+
26
+ # Factory method to build a datasource from a given datasource Hash description.
27
+ # @param ds_model [Hash] grafana specification of a single datasource
28
+ # @return [AbstractDatasource] instance of a fitting datasource implementation
29
+ def self.build_instance(ds_model)
30
+ raise InvalidDatasourceQueryProvidedError, ds_model unless ds_model.is_a?(Hash)
31
+
32
+ raise InvalidDatasourceQueryProvidedError, ds_model unless ds_model['meta'].is_a?(Hash)
33
+
34
+ @@subclasses.each do |datasource_class|
35
+ return datasource_class.new(ds_model) if datasource_class.handles?(ds_model)
36
+ end
37
+
38
+ UnsupportedDatasource.new(ds_model)
39
+ end
40
+
41
+ def initialize(model)
42
+ @model = model
43
+ end
44
+
45
+ # @return [String] category of the datasource, e.g. +tsdb+ or +sql+
46
+ def category
47
+ @model['meta']['category']
48
+ end
49
+
50
+ # @return [String] type of the datasource, e.g. +mysql+
51
+ def type
52
+ @model['type'] || @model['meta']['id']
53
+ end
54
+
55
+ # @return [String] name of the datasource
56
+ def name
57
+ @model['name']
58
+ end
59
+
60
+ # @return [Integer] ID of the datasource
61
+ def id
62
+ @model['id'].to_i
63
+ end
64
+
65
+ # @abstract
66
+ #
67
+ # Executes a request for the current database with the given options.
68
+ #
69
+ # Used format of the response will always be the following:
70
+ #
71
+ # {
72
+ # :header => [column_title_1, column_title_2],
73
+ # :content => [
74
+ # [row_1_column_1, row_1_column_2],
75
+ # [row_2_column_1, row_2_column_2]
76
+ # ]
77
+ # }
78
+ #
79
+ # @param query_description [Hash] query description, which will requested:
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+
86
+ # @return [Hash] sql result formatted as stated above
87
+ def request(query_description)
88
+ raise NotImplementedError
89
+ end
90
+
91
+ # @abstract
92
+ #
93
+ # The different datasources supported by grafana use different ways to store the query in the
94
+ # panel's JSON model. This method extracts a query from that description, that can be used
95
+ # by the {AbstractDatasource} implementation of the datasource.
96
+ #
97
+ # @param panel_query_target [Hash] grafana panel target, which contains the query description
98
+ # @return [String] query string, which can be used as +raw_query+ in a {#request}
99
+ def raw_query_from_panel_model(panel_query_target)
100
+ raise NotImplementedError
101
+ end
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
+
111
+ private
112
+
113
+ # Replaces the grafana variables in the given string with their replacement value.
114
+ #
115
+ # @param string [String] string in which the variables shall be replaced
116
+ # @param variables [Hash<String,Variable>] Hash containing the variables, which shall be replaced in the
117
+ # given string
118
+ # @return [String] string in which all variables are properly replaced
119
+ def replace_variables(string, variables = {})
120
+ res = string
121
+ repeat = true
122
+ repeat_count = 0
123
+
124
+ # TODO: find a proper way to replace variables recursively instead of over and over again
125
+ # TODO: add tests for recursive replacement of variable
126
+ while repeat && (repeat_count < 3)
127
+ repeat = false
128
+ repeat_count += 1
129
+
130
+ variables.each do |name, variable|
131
+ # only set ticks if value is string
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)
141
+ end
142
+ end
143
+ repeat = true if res.include?('$')
144
+ end
145
+
146
+ res
147
+ end
148
+ end
149
+ end
@@ -53,9 +53,7 @@ module Grafana
53
53
  list = @model['templating']['list']
54
54
  return unless list.is_a? Array
55
55
 
56
- list.each do |item|
57
- @variables << Variable.new(item)
58
- end
56
+ list.each { |item| @variables << Variable.new(item) }
59
57
  end
60
58
 
61
59
  # read panels
@@ -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. 'id' or 'name'
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 specifiy id or name
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,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 in a {AbstractSqlQuery} object.
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
@@ -9,16 +9,17 @@
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+ and +:ssl_cert+.
18
+ # Currently supporting +:logger+.
17
19
  def initialize(base_uri, key = nil, opts = {})
18
20
  @base_uri = base_uri
19
21
  @key = key
20
22
  @dashboards = {}
21
- @ssl_cert = opts[:ssl_cert]
22
23
  @logger = opts[:logger] || ::Logger.new(nil)
23
24
 
24
25
  initialize_datasources unless @base_uri.empty?
@@ -31,33 +32,54 @@ module Grafana
31
32
  #
32
33
  # @return [String] +Admin+, +NON-Admin+ or +Failed+ is returned, depending on the test results
33
34
  def test_connection
34
- if execute_http_request('/api/datasources').is_a?(Net::HTTPOK)
35
+ if prepare_request({ relative_url: '/api/datasources' }).execute.is_a?(Net::HTTPOK)
35
36
  # we have admin rights
36
37
  @logger.warn('Reporter is running with Admin privileges on grafana. This is a potential security risk.')
37
38
  return 'Admin'
38
39
  end
39
40
  # check if we have lower rights
40
- return 'Failed' unless execute_http_request('/api/dashboards/home').is_a?(Net::HTTPOK)
41
+ return 'Failed' unless prepare_request({ relative_url: '/api/dashboards/home' }).execute.is_a?(Net::HTTPOK)
41
42
 
42
43
  @logger.info('Reporter is running with NON-Admin privileges on grafana.')
43
44
  'NON-Admin'
44
45
  end
45
46
 
46
- # Returns the ID of a datasource, which has been queried by the datasource name.
47
+ # Returns the datasource, which has been queried by the datasource name.
47
48
  #
48
- # @return [Integer] ID for the specified datasource name
49
- def datasource_id(datasource_name)
50
- datasource_name ||= 'default'
51
- return @datasources[datasource_name] if @datasources[datasource_name]
52
-
53
- raise DatasourceDoesNotExistError.new('name', datasource_name)
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]
54
58
  end
55
59
 
56
- # Returns if the given datasource ID exists for the grafana instance.
60
+ # Returns the datasource, which has been queried by the datasource id.
57
61
  #
58
- # @return [Boolean] true if exists, false otherwise
59
- def datasource_id_exists?(datasource_id)
60
- @datasources.value?(datasource_id)
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
61
83
  end
62
84
 
63
85
  # @param dashboard_uid [String] UID of the searched {Dashboard}
@@ -65,54 +87,27 @@ module Grafana
65
87
  def dashboard(dashboard_uid)
66
88
  return @dashboards[dashboard_uid] unless @dashboards[dashboard_uid].nil?
67
89
 
68
- response = execute_http_request("/api/dashboards/uid/#{dashboard_uid}")
69
- model = JSON.parse(response.body)['dashboard']
70
-
71
- 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)
72
92
 
73
93
  # cache dashboard for reuse
94
+ model = JSON.parse(response.body)['dashboard']
74
95
  @dashboards[dashboard_uid] = Dashboard.new(model, self)
75
96
 
76
97
  @dashboards[dashboard_uid]
77
98
  end
78
99
 
79
- # Runs a specific HTTP request against the current grafana instance.
100
+ # Prepares a {WebRequest} object for the current {Grafana} instance, which may be enriched
101
+ # with further properties and can then run {WebRequest#execute}.
80
102
  #
81
- # Default (can be overridden, by specifying the options Hash):
82
- # accept: 'application/json'
83
- # request: Net::HTTP::Get
84
- # content_type: 'application/json'
85
- #
86
- # @param relative_uri [String] relative URL with a leading slash, which shall be queried
87
- # @param options [Hash] options, which shall be merged to the request.
88
- # @param timeout [Integer] number of seconds to wait, before the http request is cancelled, defaults to 60 seconds
89
- def execute_http_request(relative_uri, options = {}, timeout = nil)
90
- uri = URI.parse(@base_uri + relative_uri)
91
- default_options = { accept: 'application/json', request: Net::HTTP::Get, content_type: 'application/json' }
92
- options = default_options.merge(options)
93
-
94
- http = Net::HTTP.new(uri.host, uri.port)
95
- if @base_uri =~ /^https/
96
- http.use_ssl = true
97
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
98
- if @ssl_cert && !File.exist?(@ssl_cert)
99
- @logger.warn('SSL certificate file does not exist.')
100
- elsif @ssl_cert
101
- http.cert_store = OpenSSL::X509::Store.new
102
- http.cert_store.set_default_paths
103
- http.cert_store.add_file(@ssl_cert)
104
- end
105
- end
106
- http.read_timeout = timeout.to_i if timeout
107
-
108
- request = options[:request].new(uri.request_uri)
109
- request['Accept'] = options[:accept]
110
- request['Content-Type'] = options[:content_type]
111
- request['Authorization'] = "Bearer #{@key}" unless @key.nil?
112
- request.body = options[:body]
113
-
114
- @logger.debug("Requesting #{relative_uri} with '#{options[:body]}' and timeout '#{http.read_timeout}'")
115
- 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))
116
111
  end
117
112
 
118
113
  private
@@ -120,12 +115,12 @@ module Grafana
120
115
  def initialize_datasources
121
116
  @datasources = {}
122
117
 
123
- settings = execute_http_request('/api/frontend/settings')
118
+ settings = prepare_request({ relative_url: '/api/frontend/settings' }).execute
124
119
  return unless settings.is_a?(Net::HTTPOK)
125
120
 
126
121
  json = JSON.parse(settings.body)
127
122
  json['datasources'].select { |_k, v| v['id'].to_i.positive? }.each do |ds_name, ds_value|
128
- @datasources[ds_name] = ds_value['id'].to_i
123
+ @datasources[ds_name] = AbstractDatasource.build_instance(ds_value)
129
124
  end
130
125
  @datasources['default'] = @datasources[json['defaultDatasource']]
131
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