ruby-grafana-reporter 0.4.1 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +336 -185
- data/lib/VERSION.rb +2 -2
- data/lib/grafana/abstract_datasource.rb +30 -17
- data/lib/grafana/errors.rb +4 -4
- data/lib/grafana/grafana.rb +2 -0
- data/lib/grafana/grafana_property_datasource.rb +12 -0
- data/lib/grafana/graphite_datasource.rb +27 -5
- data/lib/grafana/influxdb_datasource.rb +156 -0
- data/lib/grafana/panel.rb +1 -1
- data/lib/grafana/prometheus_datasource.rb +37 -6
- data/lib/grafana/sql_datasource.rb +10 -11
- data/lib/grafana/variable.rb +29 -22
- data/lib/grafana_reporter/abstract_query.rb +150 -31
- data/lib/grafana_reporter/abstract_report.rb +37 -5
- data/lib/grafana_reporter/abstract_table_format_strategy.rb +34 -0
- data/lib/grafana_reporter/alerts_table_query.rb +5 -6
- data/lib/grafana_reporter/annotations_table_query.rb +5 -6
- data/lib/grafana_reporter/application/application.rb +7 -2
- data/lib/grafana_reporter/application/webservice.rb +35 -30
- data/lib/grafana_reporter/asciidoctor/adoc_plain_table_format_strategy.rb +25 -0
- data/lib/grafana_reporter/asciidoctor/alerts_table_include_processor.rb +7 -5
- data/lib/grafana_reporter/asciidoctor/annotations_table_include_processor.rb +7 -5
- data/lib/grafana_reporter/asciidoctor/help.rb +458 -0
- data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +5 -4
- data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +5 -4
- data/lib/grafana_reporter/asciidoctor/panel_property_inline_macro.rb +5 -4
- data/lib/grafana_reporter/asciidoctor/panel_query_table_include_processor.rb +6 -6
- data/lib/grafana_reporter/asciidoctor/panel_query_value_inline_macro.rb +4 -4
- data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +21 -35
- data/lib/grafana_reporter/asciidoctor/report.rb +16 -26
- data/lib/grafana_reporter/asciidoctor/show_help_include_processor.rb +1 -1
- data/lib/grafana_reporter/asciidoctor/sql_table_include_processor.rb +6 -4
- data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +5 -3
- data/lib/grafana_reporter/console_configuration_wizard.rb +2 -2
- data/lib/grafana_reporter/csv_table_format_strategy.rb +23 -0
- data/lib/grafana_reporter/demo_report_wizard.rb +5 -2
- data/lib/grafana_reporter/erb/demo_report_builder.rb +46 -0
- data/lib/grafana_reporter/erb/report.rb +14 -21
- data/lib/grafana_reporter/erb/report_jail.rb +21 -0
- data/lib/grafana_reporter/errors.rb +19 -3
- data/lib/grafana_reporter/panel_image_query.rb +2 -5
- data/lib/grafana_reporter/query_value_query.rb +1 -19
- data/lib/grafana_reporter/report_webhook.rb +12 -8
- data/lib/ruby_grafana_reporter.rb +10 -11
- metadata +9 -3
- data/lib/grafana_reporter/help.rb +0 -443
data/lib/grafana/variable.rb
CHANGED
@@ -33,6 +33,7 @@ module Grafana
|
|
33
33
|
if config_or_value.is_a? Hash
|
34
34
|
@config = config_or_value
|
35
35
|
@name = @config['name']
|
36
|
+
# TODO: if a variable uses type 'query' which is never updated, the selected values are stored in 'options'
|
36
37
|
unless @config['current'].nil?
|
37
38
|
@raw_value = @config['current']['value']
|
38
39
|
@text = @config['current']['text']
|
@@ -62,93 +63,98 @@ module Grafana
|
|
62
63
|
def value_formatted(format = '')
|
63
64
|
value = @raw_value
|
64
65
|
|
65
|
-
#
|
66
|
-
#
|
67
|
-
if
|
66
|
+
# if 'All' is selected for this template variable, capture all values properly
|
67
|
+
# (from grafana config or query) and format the results afterwards accordingly
|
68
|
+
if value == '$__all'
|
68
69
|
if !@config['options'].empty?
|
70
|
+
# this query contains predefined values, so capture them and format the values accordingly
|
71
|
+
# this happens either if type='custom' or a query, which is never updated
|
69
72
|
value = @config['options'].map { |item| item['value'] }
|
70
|
-
|
71
|
-
|
73
|
+
|
74
|
+
elsif @config['type'] == 'query' && !@config['query'].empty?
|
75
|
+
# TODO: replace variables in query, execute it and evaluate the results as if they were normally selected
|
76
|
+
# "multiFormat": contains variable replacement in "query", e.g. 'regex values' or 'glob'
|
77
|
+
# "datasource": contains name of datasource
|
72
78
|
return @config['query']
|
73
|
-
|
79
|
+
|
74
80
|
else
|
75
|
-
# TODO:
|
81
|
+
# TODO: add support for variable type: 'datasource' and 'adhoc'
|
76
82
|
end
|
77
83
|
end
|
78
84
|
|
79
85
|
case format
|
80
86
|
when 'csv'
|
81
|
-
return value.join(',').to_s if multi?
|
87
|
+
return value.join(',').to_s if multi? && value.is_a?(Array)
|
82
88
|
|
83
89
|
value.to_s
|
84
90
|
|
85
91
|
when 'distributed'
|
86
|
-
return value.join(",#{name}=") if multi?
|
92
|
+
return value.join(",#{name}=") if multi? && value.is_a?(Array)
|
87
93
|
|
88
94
|
value
|
89
95
|
when 'doublequote'
|
90
|
-
if multi?
|
96
|
+
if multi? && value.is_a?(Array)
|
91
97
|
value = value.map { |item| "\"#{item.gsub(/\\/, '\\\\').gsub(/"/, '\\"')}\"" }
|
92
98
|
return value.join(',')
|
93
99
|
end
|
94
100
|
"\"#{value.gsub(/"/, '\\"')}\""
|
95
101
|
|
96
102
|
when 'json'
|
97
|
-
if multi?
|
103
|
+
if multi? && value.is_a?(Array)
|
98
104
|
value = value.map { |item| "\"#{item.gsub(/["\\]/, '\\\\\0')}\"" }
|
99
105
|
return "[#{value.join(',')}]"
|
100
106
|
end
|
101
107
|
"\"#{value.gsub(/"/, '\\"')}\""
|
102
108
|
|
103
109
|
when 'percentencode'
|
104
|
-
value = "{#{value.join(',')}}" if multi?
|
110
|
+
value = "{#{value.join(',')}}" if multi? && value.is_a?(Array)
|
105
111
|
ERB::Util.url_encode(value)
|
106
112
|
|
107
113
|
when 'pipe'
|
108
|
-
return value.join('|') if multi?
|
114
|
+
return value.join('|') if multi? && value.is_a?(Array)
|
109
115
|
|
110
116
|
value
|
111
117
|
|
112
118
|
when 'raw'
|
113
|
-
return "{#{value.join(',')}}" if multi?
|
119
|
+
return "{#{value.join(',')}}" if multi? && value.is_a?(Array)
|
114
120
|
|
115
121
|
value
|
116
122
|
|
117
123
|
when 'regex'
|
118
|
-
if multi?
|
124
|
+
if multi? && value.is_a?(Array)
|
119
125
|
value = value.map { |item| item.gsub(%r{[/$.|\\]}, '\\\\\0') }
|
120
126
|
return "(#{value.join('|')})"
|
121
127
|
end
|
122
128
|
value.gsub(%r{[/$.|\\]}, '\\\\\0')
|
123
129
|
|
124
130
|
when 'singlequote'
|
125
|
-
if multi?
|
131
|
+
if multi? && value.is_a?(Array)
|
126
132
|
value = value.map { |item| "'#{item.gsub(/'/, '\\\\\0')}'" }
|
127
133
|
return value.join(',')
|
128
134
|
end
|
129
135
|
"'#{value.gsub(/'/, '\\\\\0')}'"
|
130
136
|
|
131
137
|
when 'sqlstring'
|
132
|
-
if multi?
|
138
|
+
if multi? && value.is_a?(Array)
|
133
139
|
value = value.map { |item| "'#{item.gsub(/'/, "''")}'" }
|
134
140
|
return value.join(',')
|
135
141
|
end
|
136
142
|
"'#{value.gsub(/'/, "''")}'"
|
137
143
|
|
138
144
|
when 'lucene'
|
139
|
-
if multi?
|
145
|
+
if multi? && value.is_a?(Array)
|
140
146
|
value = value.map { |item| "\"#{item.gsub(%r{[" |=/\\]}, '\\\\\0')}\"" }
|
141
147
|
return "(#{value.join(' OR ')})"
|
142
148
|
end
|
143
149
|
value.gsub(%r{[" |=/\\]}, '\\\\\0')
|
144
150
|
|
145
151
|
when /^date(?::(?<format>.*))?$/
|
146
|
-
# TODO:
|
152
|
+
# TODO: grafana does not seem to allow multiselection of variables with date format - raise an error if this happens anyway
|
147
153
|
get_date_formatted(value, Regexp.last_match(1))
|
148
154
|
|
149
155
|
when ''
|
150
156
|
# default
|
151
|
-
if multi?
|
157
|
+
if multi? && value.is_a?(Array)
|
152
158
|
value = value.map { |item| "'#{item.gsub(/'/, "''")}'" }
|
153
159
|
return value.join(',')
|
154
160
|
end
|
@@ -156,14 +162,13 @@ module Grafana
|
|
156
162
|
|
157
163
|
else
|
158
164
|
# glob and all unknown
|
159
|
-
# TODO add check for array value properly for all cases
|
160
165
|
return "{#{value.join(',')}}" if multi? && value.is_a?(Array)
|
161
166
|
|
162
167
|
value
|
163
168
|
end
|
164
169
|
end
|
165
170
|
|
166
|
-
# @return [Boolean] true, if the value can contain multiple selections, i.e.
|
171
|
+
# @return [Boolean] true, if the value can contain multiple selections, i.e. can contain an Array
|
167
172
|
def multi?
|
168
173
|
return @config['multi'] unless @config['multi'].nil?
|
169
174
|
|
@@ -197,6 +202,8 @@ module Grafana
|
|
197
202
|
until work_string.empty?
|
198
203
|
tmp = work_string.scan(/^(?:M{1,4}|D{1,4}|d{1,4}|e|E|w{1,2}|W{1,2}|Y{4}|Y{2}|A|a|H{1,2}|
|
199
204
|
h{1,2}|k{1,2}|m{1,2}|s{1,2}|S+|X)/x)
|
205
|
+
|
206
|
+
# TODO: add test for sub! and switch to non-modifying frozen string action
|
200
207
|
if tmp.empty?
|
201
208
|
matches << work_string[0]
|
202
209
|
work_string.sub!(/^#{work_string[0]}/, '')
|
@@ -1,23 +1,58 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'abstract_table_format_strategy'
|
4
|
+
require_relative 'csv_table_format_strategy'
|
5
|
+
|
3
6
|
module GrafanaReporter
|
4
7
|
# @abstract Override {#pre_process} and {#post_process} in subclass.
|
5
8
|
#
|
6
9
|
# Superclass containing everything for all queries towards grafana.
|
7
10
|
class AbstractQuery
|
8
|
-
attr_accessor :datasource
|
11
|
+
attr_accessor :datasource
|
9
12
|
attr_writer :raw_query
|
10
|
-
attr_reader :variables, :result, :panel
|
13
|
+
attr_reader :variables, :result, :panel, :dashboard
|
14
|
+
|
15
|
+
def timeout
|
16
|
+
# TODO: PRIO check where value priorities should be evaluated
|
17
|
+
return @variables['timeout'].raw_value if @variables['timeout']
|
18
|
+
return @variables['grafana_default_timeout'].raw_value if @variables['grafana_default_timeout']
|
19
|
+
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param grafana_obj [Object] {Grafana::Grafana}, {Grafana::Dashboard} or {Grafana::Panel} object for which the query is executed
|
24
|
+
# @param opts [Hash] hash options, which may consist of:
|
25
|
+
# @option opts [Hash] :variables hash of variables, which shall be used to replace variable references in the query
|
26
|
+
# @option opts [Boolean] :ignore_dashboard_defaults True if {#assign_dashboard_defaults} should not be called
|
27
|
+
# @option opts [Boolean] :do_not_use_translated_times True if given from and to times should used as is, without being resolved to reporter times - using this parameter can lead to inconsistent report contents
|
28
|
+
def initialize(grafana_obj, opts = {})
|
29
|
+
if grafana_obj.is_a?(Grafana::Panel)
|
30
|
+
@panel = grafana_obj
|
31
|
+
@dashboard = @panel.dashboard
|
32
|
+
@grafana = @dashboard.grafana
|
33
|
+
|
34
|
+
elsif grafana_obj.is_a?(Grafana::Dashboard)
|
35
|
+
@dashboard = grafana_obj
|
36
|
+
@grafana = @dashboard.grafana
|
37
|
+
|
38
|
+
elsif grafana_obj.is_a?(Grafana::Grafana)
|
39
|
+
@grafana = grafana_obj
|
40
|
+
|
41
|
+
elsif !grafana_obj
|
42
|
+
# nil given
|
11
43
|
|
12
|
-
# @param grafana_or_panel [Object] {Grafana::Grafana} or {Grafana::Panel} object for which the query is executed
|
13
|
-
def initialize(grafana_or_panel)
|
14
|
-
if grafana_or_panel.is_a?(Grafana::Panel)
|
15
|
-
@panel = grafana_or_panel
|
16
|
-
@grafana = @panel.dashboard.grafana
|
17
44
|
else
|
18
|
-
|
45
|
+
raise GrafanaReporterError, "Internal error in AbstractQuery: given object is of type #{grafana_obj.class.name}, which is not supported"
|
19
46
|
end
|
20
47
|
@variables = {}
|
48
|
+
@variables['from'] = Grafana::Variable.new(nil)
|
49
|
+
@variables['to'] = Grafana::Variable.new(nil)
|
50
|
+
|
51
|
+
assign_dashboard_defaults unless opts[:ignore_dashboard_defaults]
|
52
|
+
opts[:variables].each { |k, v| assign_variable(k, v) } if opts[:variables].is_a?(Hash)
|
53
|
+
|
54
|
+
@translate_times = true
|
55
|
+
@translate_times = false if opts[:do_not_use_translated_times]
|
21
56
|
end
|
22
57
|
|
23
58
|
# @abstract
|
@@ -31,11 +66,33 @@ module GrafanaReporter
|
|
31
66
|
def execute
|
32
67
|
return @result unless @result.nil?
|
33
68
|
|
69
|
+
from = @variables['from'].raw_value
|
70
|
+
to = @variables['to'].raw_value
|
71
|
+
if @translate_times
|
72
|
+
from = translate_date(@variables['from'], @variables['grafana_report_timestamp'], false, @variables['from_timezone'] ||
|
73
|
+
@variables['grafana_default_from_timezone'])
|
74
|
+
to = translate_date(@variables['to'], @variables['grafana_report_timestamp'], true, @variables['to_timezone'] ||
|
75
|
+
@variables['grafana_default_to_timezone'])
|
76
|
+
end
|
77
|
+
|
34
78
|
pre_process
|
35
79
|
raise DatasourceNotSupportedError.new(@datasource, self) if @datasource.is_a?(Grafana::UnsupportedDatasource)
|
36
80
|
|
37
|
-
|
38
|
-
|
81
|
+
begin
|
82
|
+
@result = @datasource.request(from: from, to: to, raw_query: raw_query, variables: grafana_variables,
|
83
|
+
prepared_request: @grafana.prepare_request, timeout: timeout)
|
84
|
+
rescue ::Grafana::GrafanaError
|
85
|
+
# grafana errors will be directly passed through
|
86
|
+
raise
|
87
|
+
rescue GrafanaReporterError
|
88
|
+
# grafana errors will be directly passed through
|
89
|
+
raise
|
90
|
+
rescue StandardError => e
|
91
|
+
raise DatasourceRequestInternalError.new(@datasource, e.message)
|
92
|
+
end
|
93
|
+
|
94
|
+
raise DatasourceRequestInvalidReturnValueError.new(@datasource, @result) unless datasource_response_valid?
|
95
|
+
|
39
96
|
post_process
|
40
97
|
@result
|
41
98
|
end
|
@@ -66,17 +123,6 @@ module GrafanaReporter
|
|
66
123
|
raise NotImplementedError
|
67
124
|
end
|
68
125
|
|
69
|
-
# Used to specify variables to be used for this query. This method ensures, that only the values of the
|
70
|
-
# {Grafana::Variable} stored in the +variables+ Array are overwritten.
|
71
|
-
# @param name [String] name of the variable to set
|
72
|
-
# @param variable [Grafana::Variable] variable from which the {Grafana::Variable#raw_value} will be assigned to the query variables
|
73
|
-
def assign_variable(name, variable)
|
74
|
-
raise GrafanaReporterError, "Provided variable is not of type Grafana::Variable (name: '#{name}', value: '#{value}')" unless variable.is_a?(Grafana::Variable)
|
75
|
-
|
76
|
-
@variables[name] ||= variable
|
77
|
-
@variables[name].raw_value = variable.raw_value
|
78
|
-
end
|
79
|
-
|
80
126
|
# Transposes the given result.
|
81
127
|
#
|
82
128
|
# NOTE: Only the +:content+ of the given result hash is transposed. The +:header+ is ignored.
|
@@ -105,10 +151,10 @@ module GrafanaReporter
|
|
105
151
|
|
106
152
|
filter_columns = filter_columns_variable.raw_value
|
107
153
|
filter_columns.split(',').each do |filter_column|
|
108
|
-
pos = result[:header]
|
154
|
+
pos = result[:header].index(filter_column)
|
109
155
|
|
110
156
|
unless pos.nil?
|
111
|
-
result[:header]
|
157
|
+
result[:header].delete_at(pos)
|
112
158
|
result[:content].each { |row| row.delete_at(pos) }
|
113
159
|
end
|
114
160
|
end
|
@@ -174,7 +220,6 @@ module GrafanaReporter
|
|
174
220
|
# @param result [Hash] preformatted query result (see {Grafana::AbstractDatasource#request}.
|
175
221
|
# @param configs [Array<Grafana::Variable>] one variable for replacing values in one column
|
176
222
|
# @return [Hash] query result with replaced values
|
177
|
-
# TODO: make sure that caught errors are also visible in logger
|
178
223
|
def replace_values(result, configs)
|
179
224
|
return result if configs.empty?
|
180
225
|
|
@@ -189,8 +234,11 @@ module GrafanaReporter
|
|
189
234
|
|
190
235
|
k = arr[0]
|
191
236
|
v = arr[1]
|
192
|
-
|
193
|
-
|
237
|
+
|
238
|
+
# allow keys and values to contain escaped colons or commas
|
239
|
+
k = k.gsub(/\\([:,])/, '\1')
|
240
|
+
v = v.gsub(/\\([:,])/, '\1')
|
241
|
+
|
194
242
|
result[:content].map do |row|
|
195
243
|
(row.length - 1).downto 0 do |i|
|
196
244
|
if cols.include?(i + 1) || cols.empty?
|
@@ -200,6 +248,7 @@ module GrafanaReporter
|
|
200
248
|
begin
|
201
249
|
row[i] = row[i].to_s.gsub(/#{k}/, v) if row[i].to_s =~ /#{k}/
|
202
250
|
rescue StandardError => e
|
251
|
+
@grafana.logger.error(e.message)
|
203
252
|
row[i] = e.message
|
204
253
|
end
|
205
254
|
|
@@ -224,6 +273,7 @@ module GrafanaReporter
|
|
224
273
|
end
|
225
274
|
end
|
226
275
|
rescue StandardError => e
|
276
|
+
@grafana.logger.error(e.message)
|
227
277
|
row[i] = e.message
|
228
278
|
end
|
229
279
|
end
|
@@ -241,6 +291,34 @@ module GrafanaReporter
|
|
241
291
|
result
|
242
292
|
end
|
243
293
|
|
294
|
+
# Used to build a table output in a custom format.
|
295
|
+
# @param result [Hash] preformatted sql hash, (see {Grafana::AbstractDatasource#request})
|
296
|
+
# @param opts [Hash] options for the formatting:
|
297
|
+
# @option opts [Grafana::Variable] :row_divider requested row divider for the result table, only to be used with table_formatter `adoc_deprecated`
|
298
|
+
# @option opts [Grafana::Variable] :column_divider requested row divider for the result table, only to be used with table_formatter `adoc_deprecated`
|
299
|
+
# @option opts [Grafana::Variable] :include_headline specifies if table should contain headline, defaults to false
|
300
|
+
# @option opts [Grafana::Variable] :table_formatter specifies which formatter shall be used, defaults to 'csv'
|
301
|
+
# @return [String] table in custom output format
|
302
|
+
def format_table_output(result, opts)
|
303
|
+
opts = { include_headline: Grafana::Variable.new('false'),
|
304
|
+
table_formatter: Grafana::Variable.new('csv'),
|
305
|
+
row_divider: Grafana::Variable.new('| '),
|
306
|
+
column_divider: Grafana::Variable.new(' | ') }.merge(opts.delete_if {|_k, v| v.nil? })
|
307
|
+
|
308
|
+
if opts[:table_formatter].raw_value == 'adoc_deprecated'
|
309
|
+
@grafana.logger.warn("You are using deprecated 'table_formatter' named 'adoc_deprecated', which will be "\
|
310
|
+
"removed in a future version. Start using 'adoc_plain' or register your own "\
|
311
|
+
"implementation of AbstractTableFormatStrategy.")
|
312
|
+
return result[:content].map do |row|
|
313
|
+
opts[:row_divider].raw_value + row.map do |item|
|
314
|
+
item.to_s.gsub('|', '\\|')
|
315
|
+
end.join(opts[:column_divider].raw_value)
|
316
|
+
end.join("\n")
|
317
|
+
end
|
318
|
+
|
319
|
+
AbstractTableFormatStrategy.get(opts[:table_formatter].raw_value).format(result, opts[:include_headline].raw_value.downcase == 'true')
|
320
|
+
end
|
321
|
+
|
244
322
|
# Used to translate the relative date strings used by grafana, e.g. +now-5d/w+ to the
|
245
323
|
# correct timestamp. Reason is that grafana does this in the frontend, which we have
|
246
324
|
# to emulate here for the reporter.
|
@@ -256,8 +334,10 @@ module GrafanaReporter
|
|
256
334
|
# @param timezone [Grafana::Variable] timezone to use, if not system timezone
|
257
335
|
# @return [String] translated date as timestamp string
|
258
336
|
def translate_date(orig_date, report_time, is_to_time, timezone = nil)
|
259
|
-
#
|
337
|
+
@grafana.logger.warn("#translate_date has been called without 'report_time' - using current time as fallback.") unless report_time
|
260
338
|
report_time ||= ::Grafana::Variable.new(Time.now.to_s)
|
339
|
+
orig_date = orig_date.raw_value if orig_date.is_a?(Grafana::Variable)
|
340
|
+
|
261
341
|
return (DateTime.parse(report_time.raw_value).to_time.to_i * 1000).to_s unless orig_date
|
262
342
|
return orig_date if orig_date =~ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
|
263
343
|
return orig_date if orig_date =~ /^\d+$/
|
@@ -265,24 +345,24 @@ module GrafanaReporter
|
|
265
345
|
# check if a relative date is mentioned
|
266
346
|
date_spec = orig_date.clone
|
267
347
|
|
268
|
-
date_spec.
|
348
|
+
date_spec = date_spec.gsub(/^now/, '')
|
269
349
|
raise TimeRangeUnknownError, orig_date unless date_spec
|
270
350
|
|
271
351
|
date = DateTime.parse(report_time.raw_value)
|
272
|
-
# TODO: allow from_translated or similar in ADOC template
|
352
|
+
# TODO: PRIO allow from_translated or similar in ADOC template
|
273
353
|
date = date.new_offset(timezone.raw_value) if timezone
|
274
354
|
|
275
355
|
until date_spec.empty?
|
276
356
|
fit_match = date_spec.match(%r{^/(?<fit>[smhdwMy])})
|
277
357
|
if fit_match
|
278
358
|
date = fit_date(date, fit_match[:fit], is_to_time)
|
279
|
-
date_spec.
|
359
|
+
date_spec = date_spec.gsub(%r{^/#{fit_match[:fit]}}, '')
|
280
360
|
end
|
281
361
|
|
282
362
|
delta_match = date_spec.match(/^(?<op>(?:-|\+))(?<count>\d+)?(?<unit>[smhdwMy])/)
|
283
363
|
if delta_match
|
284
364
|
date = delta_date(date, "#{delta_match[:op]}#{delta_match[:count] || 1}".to_i, delta_match[:unit])
|
285
|
-
date_spec.
|
365
|
+
date_spec = date_spec.gsub(/^#{delta_match[:op]}#{delta_match[:count]}#{delta_match[:unit]}/, '')
|
286
366
|
end
|
287
367
|
|
288
368
|
raise TimeRangeUnknownError, orig_date unless fit_match || delta_match
|
@@ -296,6 +376,45 @@ module GrafanaReporter
|
|
296
376
|
|
297
377
|
private
|
298
378
|
|
379
|
+
# Used to specify variables to be used for this query. This method ensures, that only the values of the
|
380
|
+
# {Grafana::Variable} stored in the +variables+ Array are overwritten.
|
381
|
+
# @param name [String] name of the variable to set
|
382
|
+
# @param variable [Grafana::Variable] variable from which the {Grafana::Variable#raw_value} will be assigned to the query variables
|
383
|
+
def assign_variable(name, variable)
|
384
|
+
variable = Grafana::Variable.new(variable) unless variable.is_a?(Grafana::Variable)
|
385
|
+
|
386
|
+
@variables[name] ||= variable
|
387
|
+
@variables[name].raw_value = variable.raw_value
|
388
|
+
end
|
389
|
+
|
390
|
+
# Sets default configurations from the given {Grafana::Dashboard} and store them as settings in the
|
391
|
+
# {AbstractQuery}.
|
392
|
+
#
|
393
|
+
# Following data is extracted:
|
394
|
+
# - +from+, by {Grafana::Dashboard#from_time}
|
395
|
+
# - +to+, by {Grafana::Dashboard#to_time}
|
396
|
+
# - and all variables as {Grafana::Variable}, prefixed with +var-+, as grafana also does it
|
397
|
+
def assign_dashboard_defaults
|
398
|
+
return unless @dashboard
|
399
|
+
|
400
|
+
assign_variable('from', @dashboard.from_time)
|
401
|
+
assign_variable('to', @dashboard.to_time)
|
402
|
+
@dashboard.variables.each { |item| assign_variable("var-#{item.name}", item) }
|
403
|
+
end
|
404
|
+
|
405
|
+
def datasource_response_valid?
|
406
|
+
return false if @result.nil?
|
407
|
+
return false unless @result.is_a?(Hash)
|
408
|
+
# TODO: compare how empty valid responses look like in grafana
|
409
|
+
return true if @result.empty?
|
410
|
+
return false unless @result.key?(:header)
|
411
|
+
return false unless @result.key?(:content)
|
412
|
+
return false unless @result[:header].is_a?(Array)
|
413
|
+
return false unless @result[:content].is_a?(Array)
|
414
|
+
|
415
|
+
true
|
416
|
+
end
|
417
|
+
|
299
418
|
# @return [Hash<String, Variable>] all grafana variables stored in this query, i.e. the variable name
|
300
419
|
# is prefixed with +var-+
|
301
420
|
def grafana_variables
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module GrafanaReporter
|
4
|
-
# @abstract Override {#
|
4
|
+
# @abstract Override {#build} and {#progress}.
|
5
5
|
#
|
6
6
|
# This class is used to build a report on basis of a given configuration and
|
7
7
|
# template.
|
@@ -124,8 +124,9 @@ module GrafanaReporter
|
|
124
124
|
logger.internal_messages
|
125
125
|
end
|
126
126
|
|
127
|
-
# Is being called to start the report generation.
|
128
|
-
#
|
127
|
+
# Is being called to start the report generation. To execute the specific report generation, this function
|
128
|
+
# calls the abstract {#build} method with the given parameters.
|
129
|
+
# @param template [String] path to the template to be used, trailing extension may be omitted, whereas {#default_template_extension} will be appended
|
129
130
|
# @param destination_file_or_path [String or File] path to the destination report or file object to use
|
130
131
|
# @param custom_attributes [Hash] custom attributes, which shall be merged with priority over the configuration
|
131
132
|
# @return [void]
|
@@ -135,13 +136,31 @@ module GrafanaReporter
|
|
135
136
|
@destination_file_or_path = destination_file_or_path
|
136
137
|
@custom_attributes = custom_attributes
|
137
138
|
|
138
|
-
# automatically add extension, if a file with
|
139
|
-
@template = "#{@template}.
|
139
|
+
# automatically add extension, if a file with default template extension exists
|
140
|
+
@template = "#{@template}.#{self.class.default_template_extension}" if File.file?("#{@template}.#{self.class.default_template_extension}") && !File.file?(@template.to_s)
|
140
141
|
raise MissingTemplateError, @template.to_s unless File.file?(@template.to_s)
|
141
142
|
|
142
143
|
notify(:on_before_create)
|
143
144
|
@start_time = Time.new
|
144
145
|
logger.info("Report started at #{@start_time}")
|
146
|
+
build
|
147
|
+
rescue MissingTemplateError => e
|
148
|
+
@logger.error(e.message)
|
149
|
+
@error = [e.message]
|
150
|
+
done!
|
151
|
+
raise e
|
152
|
+
rescue StandardError => e
|
153
|
+
# catch all errors during execution
|
154
|
+
died_with_error(e)
|
155
|
+
raise e
|
156
|
+
ensure
|
157
|
+
done!
|
158
|
+
end
|
159
|
+
|
160
|
+
# @abstract
|
161
|
+
# Needs to be overridden by the report implementation.
|
162
|
+
def build(template, destination_file_or_path, custom_attributes)
|
163
|
+
raise NotImplementedError
|
145
164
|
end
|
146
165
|
|
147
166
|
# Used to calculate the progress of a report. By default expects +@total_steps+ to contain the total
|
@@ -167,6 +186,18 @@ module GrafanaReporter
|
|
167
186
|
raise NotImplementedError
|
168
187
|
end
|
169
188
|
|
189
|
+
# @abstract
|
190
|
+
# @return [String] specifying the default extension of a template file
|
191
|
+
def self.default_template_extension
|
192
|
+
raise NotImplementedError
|
193
|
+
end
|
194
|
+
|
195
|
+
# @abstract
|
196
|
+
# @return [String] specifying the default extension of a rendered result file
|
197
|
+
def self.default_result_extension
|
198
|
+
raise NotImplementedError
|
199
|
+
end
|
200
|
+
|
170
201
|
private
|
171
202
|
|
172
203
|
# Called, if the report generation has died with an error.
|
@@ -188,6 +219,7 @@ module GrafanaReporter
|
|
188
219
|
def done!
|
189
220
|
return if @done
|
190
221
|
|
222
|
+
@destination_file_or_path.close if @destination_file_or_path.is_a?(File)
|
191
223
|
@done = true
|
192
224
|
@end_time = Time.new
|
193
225
|
@start_time ||= @end_time
|