Class: GrafanaReporter::AbstractQuery Abstract
- Inherits:
-
Object
- Object
- GrafanaReporter::AbstractQuery
- Defined in:
- lib/grafana_reporter/abstract_query.rb
Overview
Override #pre_process and #post_process in subclass.
Superclass containing everything for all queries towards grafana.
Direct Known Subclasses
AlertsTableQuery, AnnotationsTableQuery, PanelImageQuery, PanelPropertyQuery, QueryValueQuery
Instance Attribute Summary collapse
-
#dashboard ⇒ Object
readonly
Returns the value of attribute dashboard.
-
#datasource ⇒ Object
Returns the value of attribute datasource.
-
#panel ⇒ Object
readonly
Returns the value of attribute panel.
-
#raw_query ⇒ Object
Overwrite this function to extract a proper raw query value from this object.
-
#result ⇒ Object
readonly
Returns the value of attribute result.
-
#variables ⇒ Object
readonly
Returns the value of attribute variables.
Instance Method Summary collapse
-
#apply(result, actions, variables) ⇒ Object
Applies a given action string, separated by commas, in the given order to the results.
-
#execute ⇒ Hash
abstract
Runs the whole process to receive values properly from this query: - calls #pre_process - executes this query against the Grafana::AbstractDatasource implementation instance - calls #post_process.
-
#filter_columns(result, filter_columns_variable) ⇒ Hash
Filters columns out of the query result.
-
#format_columns(result, formats) ⇒ Hash
Uses the Kernel#format method to format values in the query results.
-
#format_table_output(result, opts) ⇒ String
Used to build a table output in a custom format.
-
#initialize(grafana_obj, opts = {}) ⇒ AbstractQuery
constructor
A new instance of AbstractQuery.
-
#post_process ⇒ Object
abstract
Use this function to format the raw result of the @result variable to conform to the expected return value.
-
#pre_process ⇒ Object
abstract
Overwrite this function to perform all necessary actions, before the query is actually executed.
-
#replace_values(result, configs) ⇒ Hash
Used to replace values in a query result according given configurations.
- #timeout ⇒ Object
-
#translate_date(orig_date, report_time, is_to_time, timezone = nil) ⇒ String
Used to translate the relative date strings used by grafana, e.g.
-
#transpose(result, transpose_variable) ⇒ Hash
Transposes the given result.
Constructor Details
#initialize(grafana_obj, opts = {}) ⇒ AbstractQuery
Returns a new instance of AbstractQuery.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/grafana_reporter/abstract_query.rb', line 27 def initialize(grafana_obj, opts = {}) if grafana_obj.is_a?(Grafana::Panel) @panel = grafana_obj @dashboard = @panel.dashboard @grafana = @dashboard.grafana elsif grafana_obj.is_a?(Grafana::Dashboard) @dashboard = grafana_obj @grafana = @dashboard.grafana elsif grafana_obj.is_a?(Grafana::Grafana) @grafana = grafana_obj elsif !grafana_obj # nil given else raise GrafanaReporterError, "Internal error in AbstractQuery: given object is of type #{grafana_obj.class.name}, which is not supported" end @logger = @grafana ? @grafana.logger : ::Logger.new($stderr, level: :info) @variables = {} @variables['from'] = Grafana::Variable.new(nil) @variables['to'] = Grafana::Variable.new(nil) assign_dashboard_defaults unless opts[:ignore_dashboard_defaults] opts[:variables].each { |k, v| assign_variable(k, v) } if opts[:variables].is_a?(Hash) @translate_times = true @translate_times = false if opts[:do_not_use_translated_times] end |
Instance Attribute Details
#dashboard ⇒ Object (readonly)
Returns the value of attribute dashboard.
13 14 15 |
# File 'lib/grafana_reporter/abstract_query.rb', line 13 def dashboard @dashboard end |
#datasource ⇒ Object
Returns the value of attribute datasource.
11 12 13 |
# File 'lib/grafana_reporter/abstract_query.rb', line 11 def datasource @datasource end |
#panel ⇒ Object (readonly)
Returns the value of attribute panel.
13 14 15 |
# File 'lib/grafana_reporter/abstract_query.rb', line 13 def panel @panel end |
#raw_query ⇒ Object
Overwrite this function to extract a proper raw query value from this object.
If the property @raw_query is not set manually by the calling object, this method may be overwritten to extract the raw query from this object instead.
111 112 113 |
# File 'lib/grafana_reporter/abstract_query.rb', line 111 def raw_query @raw_query end |
#result ⇒ Object (readonly)
Returns the value of attribute result.
13 14 15 |
# File 'lib/grafana_reporter/abstract_query.rb', line 13 def result @result end |
#variables ⇒ Object (readonly)
Returns the value of attribute variables.
13 14 15 |
# File 'lib/grafana_reporter/abstract_query.rb', line 13 def variables @variables end |
Instance Method Details
#apply(result, actions, variables) ⇒ Object
Applies a given action string, separated by commas, in the given order to the results.
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 |
# File 'lib/grafana_reporter/abstract_query.rb', line 398 def apply(result, actions, variables) actions.raw_value.split(',').each do |action| case action.strip when 'filter_columns' result = filter_columns(result, variables['filter_columns']) when 'format' result = format_columns(result, variables['format']) when 'replace_values' result = replace_values(result, variables.select { |k, _v| k =~ /^replace_values_\d+/ }) when 'transpose!' result = transpose(result, Variable.new('true')) when 'transpose' result = transpose(result, variables['transpose']) else @logger.warn("Unsupported action '#{action}' configured in 'after_fetch' or 'after_calculate'. Only" \ " the following options are supported: filter_columns, format, replace_values, transpose,"\ " transpose!") end end result end |
#execute ⇒ Hash
Runs the whole process to receive values properly from this query:
-
calls #pre_process
-
executes this query against the Grafana::AbstractDatasource implementation instance
-
calls #post_process
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/grafana_reporter/abstract_query.rb', line 66 def execute return @result unless @result.nil? from = @variables['from'].raw_value to = @variables['to'].raw_value if @translate_times from = translate_date(@variables['from'], @variables['grafana_report_timestamp'], false, @variables['from_timezone'] || @variables['grafana_default_from_timezone']) to = translate_date(@variables['to'], @variables['grafana_report_timestamp'], true, @variables['to_timezone'] || @variables['grafana_default_to_timezone']) end pre_process raise DatasourceNotSupportedError.new(@datasource, self) if @datasource.is_a?(Grafana::UnsupportedDatasource) begin @result = @datasource.request(from: from, to: to, raw_query: raw_query, variables: @variables, prepared_request: @grafana.prepare_request, timeout: timeout, grafana_version: @grafana.version) if @variables['verbose_log'] @logger.debug("Raw result: #{@result}") if @variables['verbose_log'].raw_value.downcase == "true" end rescue ::Grafana::GrafanaError # grafana errors will be directly passed through raise rescue GrafanaReporterError # grafana errors will be directly passed through raise rescue StandardError => e raise DatasourceRequestInternalError.new(@datasource, "#{e.}\n#{e.backtrace.join("\n")}") end raise DatasourceRequestInvalidReturnValueError.new(@datasource, @result) unless datasource_response_valid? post_process if @variables['verbose_log'] @logger.debug("Formatted result: #{@result}") if @variables['verbose_log'].raw_value.downcase == "true" end @result end |
#filter_columns(result, filter_columns_variable) ⇒ Hash
Filters columns out of the query result.
Multiple columns may be filtered. Therefore the column titles have to be named in the Grafana::Variable#raw_value and have to be separated by , (comma).
Commas can be used in a format string, but need to be escaped by using _,
.
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/grafana_reporter/abstract_query.rb', line 157 def filter_columns(result, filter_columns_variable) return result unless filter_columns_variable filter_columns = filter_columns_variable.raw_value filter_columns.split(/(?<!_),/).each do |filter_column| pos = result[:header].index(filter_column.gsub("_,", ",")) unless pos.nil? result[:header].delete_at(pos) result[:content].each { |row| row.delete_at(pos) } end end result end |
#format_columns(result, formats) ⇒ Hash
Uses the Kernel#format method to format values in the query results.
The formatting will be applied separately for every column. Therefore the column formats have to be named in the Grafana::Variable#raw_value and have to be separated by , (comma). If no value is specified for a column, no change will happen.
It is also possible to format milliseconds as dates by specifying date formats, e.g. date:iso
. It is possible to use any date format according https://grafana.com/docs/grafana/latest/variables/variable-types/global-variables/#from-and-to
Commas can be used in a format string, but need to be escaped by using _,
.
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/grafana_reporter/abstract_query.rb', line 187 def format_columns(result, formats) return result unless formats formats.text.split(/(?<!_),/).each_index do |i| format = formats.text.split(/(?<!_),/)[i].gsub("_,", ",") next if format.empty? result[:content].map do |row| next unless row.length > i begin if format =~ /^date:/ row[i] = ::Grafana::Variable.format_as_date(row[i], format.sub(/^date:/, '')) if row[i] else row[i] = format % row[i] if row[i] end rescue StandardError => e @logger.warn("Formatting of row #{i} with content '#{row[i]}' and format request '#{format}'"\ " was not possible. Row is left unchanged (message: #{e.})") end end end result end |
#format_table_output(result, opts) ⇒ String
Used to build a table output in a custom format.
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 |
# File 'lib/grafana_reporter/abstract_query.rb', line 321 def format_table_output(result, opts) opts = { include_headline: Grafana::Variable.new('false'), table_formatter: Grafana::Variable.new('csv'), row_divider: Grafana::Variable.new('| '), column_divider: Grafana::Variable.new(' | '), transpose: Grafana::Variable.new('false') }.merge(opts.delete_if {|_k, v| v.nil? }) if opts[:table_formatter].raw_value == 'adoc_deprecated' @logger.warn("You are using deprecated 'table_formatter' named 'adoc_deprecated', which will be "\ "removed in a future version. Start using 'adoc_plain' or register your own "\ "implementation of AbstractTableFormatStrategy.") return result[:content].map do |row| opts[:row_divider].raw_value + row.map do |item| item.to_s.gsub('|', '\\|') end.join(opts[:column_divider].raw_value) end.join("\n") end AbstractTableFormatStrategy.get(opts[:table_formatter].raw_value).format(result, opts[:include_headline].raw_value.downcase == 'true', opts[:transpose].raw_value.downcase == 'true') end |
#post_process ⇒ Object
Use this function to format the raw result of the @result variable to conform to the expected return value.
129 130 131 |
# File 'lib/grafana_reporter/abstract_query.rb', line 129 def post_process raise NotImplementedError end |
#pre_process ⇒ Object
Overwrite this function to perform all necessary actions, before the query is actually executed. Here you can e.g. set values of variables or similar.
Especially for direct queries, it is essential to set the @datasource variable at latest here in the subclass.
122 123 124 |
# File 'lib/grafana_reporter/abstract_query.rb', line 122 def pre_process raise NotImplementedError end |
#replace_values(result, configs) ⇒ Hash
Used to replace values in a query result according given configurations.
The given variables will be applied to an appropriate column, depending on the naming of the variable. The variable name ending specifies the column, e.g. a variable named replace_values_2
will be applied to the second column.
The Grafana::Variable#text needs to contain the replace specification. Multiple replacements can be specified by separating them with ,. If a literal comma is needed, it can be escaped with a backslash: \,.
The rule will be separated from the replacement text with a colon :
. If a literal colon is wanted, it can be escaped with a backslash: \:.
Examples:
-
Basic string replacement
MyTest:ThisValue
will replace all occurences of the text ‘MyTest’ with ‘ThisValue’.
-
Number comparison
<=10:OK
will replace all values smaller or equal to 10 with ‘OK’.
-
Regular expression
^[^ ]\\+ (\d+)$:\1 is the answer
will replace all values matching the pattern, e.g. ‘answerToAllQuestions 42’ to ‘42 is the answer’. Important to know: the regular expressions always have to start with ^ and end with $, i.e. the expression itself always has to match the whole content in one field.
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 |
# File 'lib/grafana_reporter/abstract_query.rb', line 241 def replace_values(result, configs) return result if configs.empty? configs.each do |key, formats| cols = key.split('_')[2..-1].map(&:to_i) formats.text.split(/(?<!\\),/).each_index do |j| format = formats.text.split(/(?<!\\),/)[j] arr = format.split(/(?<!\\):/) raise MalformedReplaceValuesStatementError, format if arr.length != 2 k = arr[0] v = arr[1] # allow keys and values to contain escaped colons or commas k = k.gsub(/\\([:,])/, '\1') v = v.gsub(/\\([:,])/, '\1') result[:content].map do |row| (row.length - 1).downto 0 do |i| if cols.include?(i + 1) || cols.empty? # handle regular expressions if k.start_with?('^') && k.end_with?('$') begin row[i] = row[i].to_s.gsub(/#{k}/, v) if row[i].to_s =~ /#{k}/ rescue StandardError => e @logger.error(e.) row[i] = e. end # handle value comparisons elsif (match = k.match(/^ *(?<operator>[<>]=?|<>|=) *(?<number>[+-]?\d+(?:\.\d+)?)$/)) skip = false begin val = Float(row[i]) rescue StandardError # value cannot be converted to number, simply ignore it as the comparison does not fit here skip = true end unless skip begin op = match[:operator].gsub(/^=$/, '==').gsub(/^<>$/, '!=') if val.public_send(op.to_sym, Float(match[:number])) row[i] = if v.include?('\\1') v.gsub(/\\1/, row[i].to_s) else v end end rescue StandardError => e @logger.error(e.) row[i] = e. end end # handle as normal comparison elsif row[i].to_s == k row[i] = v end end end end end end result end |
#timeout ⇒ Object
15 16 17 18 19 20 |
# File 'lib/grafana_reporter/abstract_query.rb', line 15 def timeout return @variables['timeout'].raw_value if @variables['timeout'] return @variables['grafana_default_timeout'].raw_value if @variables['grafana_default_timeout'] nil end |
#translate_date(orig_date, report_time, is_to_time, timezone = nil) ⇒ String
Used to translate the relative date strings used by grafana, e.g. now-5d/w
to the correct timestamp. Reason is that grafana does this in the frontend, which we have to emulate here for the reporter.
Additionally providing this function the report_time
assures that all queries rendered within one report will use exactly the same timestamp in those relative times, i.e. there shouldn’t appear any time differences, no matter how long the report is running.
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 |
# File 'lib/grafana_reporter/abstract_query.rb', line 356 def translate_date(orig_date, report_time, is_to_time, timezone = nil) @logger.warn("#translate_date has been called without 'report_time' - using current time as fallback.") unless report_time report_time ||= ::Grafana::Variable.new(Time.now.to_s) orig_date = orig_date.raw_value if orig_date.is_a?(Grafana::Variable) return (DateTime.parse(report_time.raw_value).to_time.to_i * 1000).to_s unless orig_date return orig_date if orig_date =~ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/ return orig_date if orig_date =~ /^\d+$/ # check if a relative date is mentioned date_spec = orig_date.clone date_spec = date_spec.gsub(/^now/, '') raise TimeRangeUnknownError, orig_date unless date_spec date = DateTime.parse(report_time.raw_value) # TODO: PRIO allow from_translated or similar in ADOC template date = date.new_offset(timezone.raw_value) if timezone until date_spec.empty? fit_match = date_spec.match(%r{^/(?<fit>[smhdwMy])}) if fit_match date = fit_date(date, fit_match[:fit], is_to_time) date_spec = date_spec.gsub(%r{^/#{fit_match[:fit]}}, '') end delta_match = date_spec.match(/^(?<op>(?:-|\+))(?<count>\d+)?(?<unit>[smhdwMy])/) if delta_match date = delta_date(date, "#{delta_match[:op]}#{delta_match[:count] || 1}".to_i, delta_match[:unit]) date_spec = date_spec.gsub(/^#{delta_match[:op] == '+' ? '\+' : '-'}#{delta_match[:count]}#{delta_match[:unit]}/, '') end raise TimeRangeUnknownError, orig_date unless fit_match || delta_match end # step back one second, if this is the 'to' time date = (date.to_time - 1).to_datetime if is_to_time (Time.at(date.to_time.to_i).to_i * 1000).to_s end |
#transpose(result, transpose_variable) ⇒ Hash
Transposes the given result.
NOTE: Only the :content
of the given result hash is transposed. The :header
is ignored.
140 141 142 143 144 145 146 |
# File 'lib/grafana_reporter/abstract_query.rb', line 140 def transpose(result, transpose_variable) return result unless transpose_variable return result unless transpose_variable.raw_value == 'true' result[:content] = result[:content].transpose result end |