ruby-grafana-reporter 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de6063d128d57f930e83b17253e34e4e20624ef796e7eb86df2e7d54bee7825c
4
- data.tar.gz: a6a5f8b3bfcdcfc557ede19e7d89a25de8dca277806da0f662c4165d0a05b03c
3
+ metadata.gz: e198f995d87598512fbe2a4578edf8be48630bab415d14c5348cf23a57e47df7
4
+ data.tar.gz: c1f5a3c85ba1081fbfc0b526b1c4e15412d146a8f88af897a0620fd762687482
5
5
  SHA512:
6
- metadata.gz: c9338b1dbd16a81af0c28feb9e8f4efd576ffb3f879aae7dd5d31b00468b32cc18ee49e7a73f87e952e2781845cd1d54124512aef6112194379d3840322fbff8
7
- data.tar.gz: 01bed067acfc6442e62ba17dbda3491e6147778a274c65ccacc7f838e57beda53809e8a8b827fad09266b4b506c2e29d7e8d3b0670c506eb0063eddd40d1aa26
6
+ metadata.gz: 9e5af10cd799f63a8ac662db422cebe1a8f7d1484c5e29e54ec73280c966efc7a57059c5fbe2bef2717b27fe6c2fa1c22c819fce3c851d98d568fd556291d933
7
+ data.tar.gz: 97c1477d8e97a6afa096d7dc4557d4ae15e9aee87ca541af165f898f3cfd1a20d002e2b06c68c98dd4f0c436299129e766e0023f34b6f1a2639d4fcabcfde73d
data/README.md CHANGED
@@ -310,6 +310,7 @@ webservice or a rendering process starts.
310
310
  This is just a collection of things, I am heading for in future, without a schedule.
311
311
 
312
312
  * Support grafana internal datasources
313
+ * Support additional templating variable types
313
314
  * Solve code TODOs
314
315
  * Become [rubocop](https://rubocop.org/) ready
315
316
 
data/lib/VERSION.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Version information
4
- GRAFANA_REPORTER_VERSION = [0, 5, 2].freeze
4
+ GRAFANA_REPORTER_VERSION = [0, 6, 0].freeze
5
5
  # Release date
6
- GRAFANA_REPORTER_RELEASE_DATE = '2022-03-22'
6
+ GRAFANA_REPORTER_RELEASE_DATE = '2022-08-01'
@@ -82,6 +82,7 @@ module Grafana
82
82
  # }
83
83
  #
84
84
  # @param query_description [Hash] query description, which will requested:
85
+ # @option query_description [String] :grafana_version grafana version, for which the request is to be prepared
85
86
  # @option query_description [String] :from +from+ timestamp
86
87
  # @option query_description [String] :to +to+ timestamp
87
88
  # @option query_description [Integer] :timeout expected timeout for the request
@@ -113,15 +114,14 @@ module Grafana
113
114
  raise NotImplementedError
114
115
  end
115
116
 
116
- private
117
-
118
117
  # Replaces the grafana variables in the given string with their replacement value.
119
118
  #
120
119
  # @param string [String] string in which the variables shall be replaced
121
120
  # @param variables [Hash<String,Variable>] Hash containing the variables, which shall be replaced in the
122
121
  # given string
122
+ # @param overwrite_default_format [String] {Variable#value_formatted} value, if a custom default format should be used, otherwise {#default_variable_format} is used as default, which may be overwritten
123
123
  # @return [String] string in which all variables are properly replaced
124
- def replace_variables(string, variables = {})
124
+ def replace_variables(string, variables, overwrite_default_format = nil)
125
125
  res = string
126
126
  repeat = true
127
127
  repeat_count = 0
@@ -141,7 +141,8 @@ module Grafana
141
141
  next unless var_name =~ /^\w+$/
142
142
 
143
143
  res = res.gsub(/(?:\$\{#{var_name}(?::(?<format>\w+))?\}|\$#{var_name}(?!\w))/) do
144
- format = default_variable_format
144
+ format = overwrite_default_format
145
+ format = default_variable_format if overwrite_default_format.nil?
145
146
  if $LAST_MATCH_INFO
146
147
  format = $LAST_MATCH_INFO[:format] if $LAST_MATCH_INFO[:format]
147
148
  end
@@ -153,5 +154,31 @@ module Grafana
153
154
 
154
155
  res
155
156
  end
157
+
158
+ private
159
+
160
+ # Provides a general method to handle the given query response as general Grafana Dataframe format.
161
+ #
162
+ # This method throws {UnsupportedQueryResponseReceivedError} if the given query response is not a
163
+ # properly formattes dataframe
164
+ #
165
+ # @param response_body [String] raw response body
166
+ def preformat_dataframe_response(response_body)
167
+ json = JSON.parse(response_body)
168
+ data = json['results'].values.first
169
+
170
+ # TODO: check how multiple frames have to be handled
171
+ data = data['frames']
172
+ headers = []
173
+ data.first['schema']['fields'].each do |headline|
174
+ header = headline['config']['displayNameFromDS'].nil? ? headline['name'] : headline['config']['displayNameFromDS']
175
+ headers << header
176
+ end
177
+ content = data.first['data']['values'][0].zip(data.first['data']['values'][1])
178
+ return { header: headers, content: content }
179
+
180
+ rescue
181
+ raise UnsupportedQueryResponseReceivedError, response_body
182
+ end
156
183
  end
157
184
  end
@@ -58,7 +58,14 @@ module Grafana
58
58
  list = @model['templating']['list']
59
59
  return unless list.is_a? Array
60
60
 
61
- list.each { |item| @variables << Variable.new(item, self) }
61
+ list.each do |item|
62
+ begin
63
+ @variables << Variable.new(item, self)
64
+ rescue => e
65
+ # TODO: show this message as a warning - needs test cleanup
66
+ @grafana.logger.debug(e.message)
67
+ end
68
+ end
62
69
  end
63
70
 
64
71
  # read panels
@@ -86,7 +86,15 @@ module Grafana
86
86
  # @param datasource_uid [String] unique id of the searched datasource
87
87
  # @return [Datasource] Datasource for the specified datasource unique id
88
88
  def datasource_by_uid(datasource_uid)
89
- datasource = @datasources.select { |_name, ds| ds.uid == datasource_uid }.values.first
89
+ datasource = @datasources.select do |ds_name, ds|
90
+ if (ds.nil?)
91
+ # print debug info for https://github.com/divinity666/ruby-grafana-reporter/issues/29
92
+ @logger.warn("Datasource with name #{ds_name} is nil, which should never happen. Check logs for details.")
93
+ false
94
+ else
95
+ ds.uid == datasource_uid
96
+ end
97
+ end.values.first
90
98
  raise DatasourceDoesNotExistError.new('uid', datasource_uid) unless datasource
91
99
 
92
100
  datasource
@@ -156,6 +164,12 @@ module Grafana
156
164
  json = JSON.parse(settings.body)
157
165
  json['datasources'].select { |_k, v| v['id'].to_i.positive? }.each do |ds_name, ds_value|
158
166
  @datasources[ds_name] = AbstractDatasource.build_instance(ds_value)
167
+
168
+ # print debug info for https://github.com/divinity666/ruby-grafana-reporter/issues/29
169
+ if @datasources[ds_name].nil?
170
+ @logger.error("Datasource with name '#{ds_name}' and configuration: '#{ds_value}' could not be initialized.")
171
+ @datasources.delete(ds_name)
172
+ end
159
173
  end
160
174
  @datasources['default'] = @datasources[json['defaultDatasource']]
161
175
  end
File without changes
@@ -43,13 +43,14 @@ module Grafana
43
43
 
44
44
  private
45
45
 
46
- # @see AbstractDatasource#preformat_response
47
46
  def preformat_response(response_body)
48
- json = JSON.parse(response_body)
49
-
50
- raise UnsupportedQueryResponseReceivedError, response_body if json.first['target'].nil?
51
- raise UnsupportedQueryResponseReceivedError, response_body if json.first['datapoints'].nil?
47
+ begin
48
+ return preformat_dataframe_response(response_body)
49
+ rescue
50
+ # TODO: show an info, that the response if not a dataframe
51
+ end
52
52
 
53
+ json = JSON.parse(response_body)
53
54
  header = ['time']
54
55
  content = {}
55
56
 
@@ -69,7 +70,10 @@ module Grafana
69
70
  end
70
71
  end
71
72
 
72
- { header: header, content: content.to_a.map(&:flatten).sort { |a, b| a[0] <=> b[0] } }
73
+ return { header: header, content: content.to_a.map(&:flatten).sort { |a, b| a[0] <=> b[0] } }
74
+
75
+ rescue
76
+ raise UnsupportedQueryResponseReceivedError, response_body
73
77
  end
74
78
  end
75
79
  end
@@ -127,14 +127,15 @@ module Grafana
127
127
  "#{res} #{parts.join(', ')}"
128
128
  end
129
129
 
130
- # @see AbstractDatasource#preformat_response
131
130
  def preformat_response(response_body)
131
+ begin
132
+ return preformat_dataframe_response(response_body)
133
+ rescue
134
+ # TODO: show an info, that the response if not a dataframe
135
+ end
136
+
132
137
  # TODO: how to handle multiple query results?
133
138
  json = JSON.parse(response_body)
134
- raise UnsupportedQueryResponseReceivedError, response_body if json['results'].nil?
135
- raise UnsupportedQueryResponseReceivedError, response_body if json['results'].first.nil?
136
- raise UnsupportedQueryResponseReceivedError, response_body if json['results'].first['series'].nil?
137
-
138
139
  json = json['results'].first['series']
139
140
  return {} if json.nil?
140
141
 
@@ -157,7 +158,10 @@ module Grafana
157
158
  end
158
159
  end
159
160
 
160
- { header: header, content: content.to_a.map(&:flatten).sort { |a, b| a[0] <=> b[0] } }
161
+ return { header: header, content: content.to_a.map(&:flatten).sort { |a, b| a[0] <=> b[0] } }
162
+
163
+ rescue
164
+ raise UnsupportedQueryResponseReceivedError, response_body
161
165
  end
162
166
  end
163
167
  end
data/lib/grafana/panel.rb CHANGED
@@ -12,6 +12,11 @@ module Grafana
12
12
  def initialize(model, dashboard)
13
13
  @model = model
14
14
  @dashboard = dashboard
15
+
16
+ @datasource_uid_or_name = @model['datasource']
17
+ if @model['datasource'].is_a?(Hash)
18
+ @datasource_uid_or_name = @model['datasource']['uid']
19
+ end
15
20
  end
16
21
 
17
22
  # @return [String] content of the requested field or +''+ if not found
@@ -26,12 +31,21 @@ module Grafana
26
31
  @model['id']
27
32
  end
28
33
 
34
+ # This method should always be called before the +datasource+ method of a
35
+ # panel is invoked, to ensure that the variable names in the datasource
36
+ # field are resolved.
37
+ #
38
+ # @param variables [Hash] variables hash, which should be use to resolve variable datasource
39
+ def resolve_variable_datasource(variables)
40
+ @datasource_uid_or_name = AbstractDatasource.new(nil).replace_variables(@datasource_uid_or_name, variables, 'raw') if @datasource_uid_or_name.is_a?(String)
41
+ end
42
+
29
43
  # @return [Datasource] datasource object specified for the current panel
30
44
  def datasource
31
- if @model['datasource'].is_a?(Hash)
32
- dashboard.grafana.datasource_by_uid(@model['datasource']['uid'])
45
+ if datasource_kind_is_uid?
46
+ dashboard.grafana.datasource_by_uid(@datasource_uid_or_name)
33
47
  else
34
- dashboard.grafana.datasource_by_name(@model['datasource'])
48
+ dashboard.grafana.datasource_by_name(@datasource_uid_or_name)
35
49
  end
36
50
  end
37
51
 
@@ -47,5 +61,14 @@ module Grafana
47
61
  def render_url
48
62
  "/render/d-solo/#{@dashboard.id}?panelId=#{@model['id']}"
49
63
  end
64
+
65
+ private
66
+
67
+ def datasource_kind_is_uid?
68
+ if @model['datasource'].is_a?(Hash)
69
+ return true
70
+ end
71
+ false
72
+ end
50
73
  end
51
74
  end
@@ -24,21 +24,15 @@ module Grafana
24
24
  interval = interval.raw_value if interval.is_a?(Variable)
25
25
  query = query_hash[:query] || query_description[:raw_query]
26
26
 
27
- url = if instant
28
- "/api/datasources/proxy/#{id}/api/v1/query?time=#{query_description[:to]}&query="\
29
- "#{CGI.escape(replace_variables(query, query_description[:variables]))}"
27
+ ver = query_description[:grafana_version].split('.').map{|x| x.to_i}
28
+ request = nil
29
+ if (ver[0] == 7 and ver[1] < 5) or ver[0] < 7
30
+ request = prepare_get_request({query_description: query_description, instant: instant, interval: interval, query: query})
30
31
  else
31
- "/api/datasources/proxy/#{id}/api/v1/query_range?start=#{query_description[:from]}"\
32
- "&end=#{query_description[:to]}"\
33
- "&query=#{CGI.escape(replace_variables(query, query_description[:variables]))}"\
34
- "&step=#{interval}"
32
+ request = prepare_post_request({query_description: query_description, instant: instant, interval: interval, query: query})
35
33
  end
36
34
 
37
- webrequest = query_description[:prepared_request]
38
- webrequest.relative_url = url
39
- webrequest.options.merge!({ request: Net::HTTP::Get })
40
-
41
- result = webrequest.execute(query_description[:timeout])
35
+ result = request.execute(query_description[:timeout])
42
36
  preformat_response(result.body)
43
37
  end
44
38
 
@@ -54,48 +48,80 @@ module Grafana
54
48
  end
55
49
 
56
50
  private
51
+ def prepare_get_request(hash)
52
+ url = if hash[:instant]
53
+ "/api/datasources/proxy/#{id}/api/v1/query?time=#{hash[:query_description][:to]}&query="\
54
+ "#{CGI.escape(replace_variables(hash[:query], hash[:query_description][:variables]))}"
55
+ else
56
+ "/api/datasources/proxy/#{id}/api/v1/query_range?start=#{hash[:query_description][:from]}"\
57
+ "&end=#{hash[:query_description][:to]}"\
58
+ "&query=#{CGI.escape(replace_variables(hash[:query], hash[:query_description][:variables]))}"\
59
+ "&step=#{hash[:interval]}"
60
+ end
61
+
62
+ webrequest = hash[:query_description][:prepared_request]
63
+ webrequest.relative_url = url
64
+ webrequest.options.merge!({ request: Net::HTTP::Get })
65
+
66
+ webrequest
67
+ end
68
+
69
+ def prepare_post_request(hash)
70
+ webrequest = hash[:query_description][:prepared_request]
71
+ webrequest.relative_url = '/api/ds/query'
72
+
73
+ params = {
74
+ from: hash[:query_description][:from],
75
+ to: hash[:query_description][:to],
76
+ queries: [{
77
+ datasource: { type: type, uid: uid },
78
+ datasourceId: id,
79
+ exemplar: false,
80
+ expr: hash[:query],
81
+ format: 'time_series',
82
+ interval: '',
83
+ # intervalFactor: ### 2,
84
+ # intervalMs: ### 15000,
85
+ # legendFormat: '', ### {{job}}
86
+ # maxDataPoints: 999,
87
+ metric: '',
88
+ queryType: 'timeSeriesQuery',
89
+ refId: 'A',
90
+ # requestId: '14A',
91
+ # utcOffsetSec: 7200,
92
+ step: hash[:interval]
93
+ }],
94
+ range: {
95
+ #from: ### "2022-07-31T16:19:26.198Z",
96
+ #to: ### "2022-07-31T16:19:26.198Z",
97
+ raw: { from: hash[:query_description][:variables]['from'].raw_value, to: hash[:query_description][:variables]['to'].raw_value }
98
+ }
99
+ }
100
+
101
+ webrequest.options.merge!({ request: Net::HTTP::Post, body: params.to_json })
102
+
103
+ webrequest
104
+ end
57
105
 
58
- # @see AbstractDatasource#preformat_response
59
106
  def preformat_response(response_body)
60
- json = {}
107
+ # TODO: show raw response body to debug case https://github.com/divinity666/ruby-grafana-reporter/issues/24
61
108
  begin
62
- json = JSON.parse(response_body)
109
+ return preformat_dataframe_response(response_body)
63
110
  rescue
64
- raise UnsupportedQueryResponseReceivedError, response_body
111
+ # TODO: show an info, that the response is not a dataframe
65
112
  end
66
113
 
114
+ json = JSON.parse(response_body)
115
+
67
116
  # handle response with error result
68
117
  unless json['error'].nil?
69
118
  return { header: ['error'], content: [[ json['error'] ]] }
70
119
  end
71
120
 
72
- # handle dataframes
73
- if json['results']
74
- data = json['results'].values.first
75
- raise UnsupportedQueryResponseReceivedError, response_body if data.nil?
76
- raise UnsupportedQueryResponseReceivedError, response_body if data['frames'].nil?
77
- # TODO: check how multiple frames have to be handled
78
-
79
- data = data['frames']
80
- headers = []
81
- data.first['schema']['fields'].each do |headline|
82
- header = headline['config']['displayNameFromDS'].nil? ? headline['name'] : headline['config']['displayNameFromDS']
83
- headers << header
84
- end
85
- content = data.first['data']['values'][0].zip(data.first['data']['values'][1])
86
- return { header: headers, content: content }
87
- end
88
-
89
121
  # handle former result formats
90
- raise UnsupportedQueryResponseReceivedError, response_body if json['data'].nil?
91
- raise UnsupportedQueryResponseReceivedError, response_body if json['data']['resultType'].nil?
92
- raise UnsupportedQueryResponseReceivedError, response_body if json['data']['result'].nil?
93
-
94
122
  result_type = json['data']['resultType']
95
123
  json = json['data']['result']
96
124
 
97
- raise UnsupportedQueryResponseReceivedError, response_body if not result_type =~ /^(?:scalar|string|vector|matrix)$/
98
-
99
125
  headers = ['time']
100
126
  content = {}
101
127
 
@@ -130,7 +156,10 @@ module Grafana
130
156
  end
131
157
  end
132
158
 
133
- { header: headers, content: content.to_a.map(&:flatten).sort { |a, b| a[0] <=> b[0] } }
159
+ return { header: headers, content: content.to_a.map(&:flatten).sort { |a, b| a[0] <=> b[0] } }
160
+
161
+ rescue
162
+ raise UnsupportedQueryResponseReceivedError, response_body
134
163
  end
135
164
  end
136
165
  end
@@ -47,6 +47,12 @@ module Grafana
47
47
  private
48
48
 
49
49
  def preformat_response(response_body)
50
+ begin
51
+ return preformat_dataframe_response(response_body)
52
+ rescue
53
+ # TODO: show an info, that the response if not a dataframe
54
+ end
55
+
50
56
  results = {}
51
57
  results.default = []
52
58
  results[:header] = []
@@ -64,13 +70,13 @@ module Grafana
64
70
  results[:content] = table['rows']
65
71
  end
66
72
  end
67
-
68
- else
69
- raise UnsupportedQueryResponseReceivedError, response_body
70
73
  end
71
74
  end
72
75
 
73
- results
76
+ return results
77
+
78
+ rescue
79
+ raise UnsupportedQueryResponseReceivedError, response_body
74
80
  end
75
81
  end
76
82
  end
@@ -240,7 +240,7 @@ module Grafana
240
240
  if !@config['current'].nil?
241
241
  self.raw_value = @config['current']['value']
242
242
  else
243
- raise GrafanaError.new("Grafana variable with type '#{@config['type']}' and name '#{@config['name']}' could not be handled properly. Please raise a ticket.")
243
+ raise GrafanaError.new("Grafana variable with type '#{@config['type']}' and name '#{@config['name']}' cannot be handled properly by the reporter. Check your results and raise a ticket on github.")
244
244
  end
245
245
  end
246
246
  end
@@ -80,7 +80,8 @@ module GrafanaReporter
80
80
 
81
81
  begin
82
82
  @result = @datasource.request(from: from, to: to, raw_query: raw_query, variables: @variables,
83
- prepared_request: @grafana.prepare_request, timeout: timeout)
83
+ prepared_request: @grafana.prepare_request, timeout: timeout,
84
+ grafana_version: @grafana.version)
84
85
  rescue ::Grafana::GrafanaError
85
86
  # grafana errors will be directly passed through
86
87
  raise
@@ -88,7 +89,7 @@ module GrafanaReporter
88
89
  # grafana errors will be directly passed through
89
90
  raise
90
91
  rescue StandardError => e
91
- raise DatasourceRequestInternalError.new(@datasource, e.message)
92
+ raise DatasourceRequestInternalError.new(@datasource, "#{e.message}\n#{e.backtrace.join("\n")}")
92
93
  end
93
94
 
94
95
  raise DatasourceRequestInvalidReturnValueError.new(@datasource, @result) unless datasource_response_valid?
File without changes
@@ -5,7 +5,10 @@ module GrafanaReporter
5
5
  class QueryValueQuery < AbstractQuery
6
6
  # @see Grafana::AbstractQuery#pre_process
7
7
  def pre_process
8
- @datasource = @panel.datasource if @panel
8
+ if @panel
9
+ @panel.resolve_variable_datasource(@variables)
10
+ @datasource = @panel.datasource
11
+ end
9
12
 
10
13
  @variables['result_type'] ||= Variable.new('')
11
14
  end
@@ -18,17 +18,6 @@ require 'asciidoctor-pdf'
18
18
  require 'zip'
19
19
  require_relative 'VERSION'
20
20
 
21
- # TODO: add test for variable replacement for sql
22
- # TODO: check why value is All instead of $__all
23
- # TODO: check why single sql values are replaced including ticks, whereas grafana does not do so
24
-
25
- # TODO: add FAQ for fixing most common issues with the reporter
26
- # TODO: implement an easy function to document a whole dashboard at once with different presentations
27
- # TODO: add automated test against grafana playground before building a new release
28
- # TODO: allow registration of files to be defined in config file
29
- # TODO: append necessary variables on demo report creation for plain SQL queries, as they are lacking the grafana reference
30
- # TODO: make demo report more readable
31
-
32
21
  folders = [
33
22
  %w[grafana],
34
23
  %w[grafana_reporter logger],
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-grafana-reporter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christian Kohlmeyer
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-22 00:00:00.000000000 Z
11
+ date: 2022-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: asciidoctor
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.6'
33
+ version: '2.2'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '1.6'
40
+ version: '2.2'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rubyzip
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -178,7 +178,7 @@ licenses:
178
178
  metadata:
179
179
  source_code_uri: https://github.com/divinity666/ruby-grafana-reporter
180
180
  bug_tracker_uri: https://github.com/divinity666/ruby-grafana-reporter/issues
181
- post_install_message:
181
+ post_install_message:
182
182
  rdoc_options: []
183
183
  require_paths:
184
184
  - lib
@@ -186,7 +186,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
186
186
  requirements:
187
187
  - - ">="
188
188
  - !ruby/object:Gem::Version
189
- version: '2.5'
189
+ version: '2.7'
190
190
  required_rubygems_version: !ruby/object:Gem::Requirement
191
191
  requirements:
192
192
  - - ">="
@@ -194,7 +194,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
194
194
  version: '0'
195
195
  requirements: []
196
196
  rubygems_version: 3.2.5
197
- signing_key:
197
+ signing_key:
198
198
  specification_version: 4
199
199
  summary: Reporter Service for Grafana
200
200
  test_files: []