ruby-grafana-reporter 0.2.1 → 0.4.2

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +126 -85
  3. data/bin/ruby-grafana-reporter +2 -2
  4. data/lib/VERSION.rb +3 -2
  5. data/lib/grafana/abstract_datasource.rb +146 -0
  6. data/lib/grafana/dashboard.rb +21 -23
  7. data/lib/grafana/errors.rb +18 -3
  8. data/lib/grafana/grafana.rb +64 -66
  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 +30 -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 +9 -3
  16. data/lib/grafana/prometheus_datasource.rb +67 -0
  17. data/lib/grafana/sql_datasource.rb +78 -0
  18. data/lib/grafana/unsupported_datasource.rb +7 -0
  19. data/lib/grafana/variable.rb +1 -1
  20. data/lib/grafana/webrequest.rb +71 -0
  21. data/lib/grafana_reporter/abstract_query.rb +460 -0
  22. data/lib/grafana_reporter/abstract_report.rb +139 -18
  23. data/lib/grafana_reporter/alerts_table_query.rb +39 -0
  24. data/lib/grafana_reporter/annotations_table_query.rb +38 -0
  25. data/lib/grafana_reporter/application/application.rb +34 -286
  26. data/lib/grafana_reporter/application/webservice.rb +50 -15
  27. data/lib/grafana_reporter/asciidoctor/alerts_table_include_processor.rb +91 -0
  28. data/lib/grafana_reporter/asciidoctor/annotations_table_include_processor.rb +90 -0
  29. data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +74 -0
  30. data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +76 -0
  31. data/lib/grafana_reporter/asciidoctor/panel_property_inline_macro.rb +70 -0
  32. data/lib/grafana_reporter/asciidoctor/panel_query_table_include_processor.rb +95 -0
  33. data/lib/grafana_reporter/asciidoctor/panel_query_value_inline_macro.rb +90 -0
  34. data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +49 -0
  35. data/lib/grafana_reporter/asciidoctor/report.rb +40 -81
  36. data/lib/grafana_reporter/asciidoctor/show_environment_include_processor.rb +46 -0
  37. data/lib/grafana_reporter/asciidoctor/show_help_include_processor.rb +35 -0
  38. data/lib/grafana_reporter/asciidoctor/sql_table_include_processor.rb +90 -0
  39. data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +86 -0
  40. data/lib/grafana_reporter/asciidoctor/value_as_variable_include_processor.rb +90 -0
  41. data/lib/grafana_reporter/configuration.rb +59 -52
  42. data/lib/grafana_reporter/console_configuration_wizard.rb +311 -0
  43. data/lib/grafana_reporter/demo_report_wizard.rb +105 -0
  44. data/lib/grafana_reporter/erb/report.rb +30 -0
  45. data/lib/grafana_reporter/erb/report_jail.rb +21 -0
  46. data/lib/grafana_reporter/errors.rb +55 -0
  47. data/lib/grafana_reporter/help.rb +443 -0
  48. data/lib/grafana_reporter/logger/{two_way_logger.rb → two_way_delegate_logger.rb} +1 -1
  49. data/lib/grafana_reporter/panel_image_query.rb +25 -0
  50. data/lib/grafana_reporter/panel_property_query.rb +22 -0
  51. data/lib/grafana_reporter/query_value_query.rb +61 -0
  52. data/lib/grafana_reporter/report_webhook.rb +35 -0
  53. data/lib/ruby_grafana_extension.rb +8 -0
  54. data/lib/{ruby-grafana-reporter.rb → ruby_grafana_reporter.rb} +1 -2
  55. metadata +47 -38
  56. data/lib/grafana/abstract_panel_query.rb +0 -22
  57. data/lib/grafana/abstract_query.rb +0 -132
  58. data/lib/grafana/abstract_sql_query.rb +0 -51
  59. data/lib/grafana/panel_image_query.rb +0 -52
  60. data/lib/grafana_reporter/asciidoctor/alerts_table_query.rb +0 -104
  61. data/lib/grafana_reporter/asciidoctor/annotations_table_query.rb +0 -99
  62. data/lib/grafana_reporter/asciidoctor/errors.rb +0 -40
  63. data/lib/grafana_reporter/asciidoctor/extensions/alerts_table_include_processor.rb +0 -92
  64. data/lib/grafana_reporter/asciidoctor/extensions/annotations_table_include_processor.rb +0 -91
  65. data/lib/grafana_reporter/asciidoctor/extensions/panel_image_block_macro.rb +0 -69
  66. data/lib/grafana_reporter/asciidoctor/extensions/panel_image_inline_macro.rb +0 -68
  67. data/lib/grafana_reporter/asciidoctor/extensions/panel_property_inline_macro.rb +0 -61
  68. data/lib/grafana_reporter/asciidoctor/extensions/panel_query_table_include_processor.rb +0 -78
  69. data/lib/grafana_reporter/asciidoctor/extensions/panel_query_value_inline_macro.rb +0 -73
  70. data/lib/grafana_reporter/asciidoctor/extensions/processor_mixin.rb +0 -20
  71. data/lib/grafana_reporter/asciidoctor/extensions/show_environment_include_processor.rb +0 -43
  72. data/lib/grafana_reporter/asciidoctor/extensions/show_help_include_processor.rb +0 -228
  73. data/lib/grafana_reporter/asciidoctor/extensions/sql_table_include_processor.rb +0 -70
  74. data/lib/grafana_reporter/asciidoctor/extensions/sql_value_inline_macro.rb +0 -66
  75. data/lib/grafana_reporter/asciidoctor/extensions/value_as_variable_include_processor.rb +0 -86
  76. data/lib/grafana_reporter/asciidoctor/panel_first_value_query.rb +0 -34
  77. data/lib/grafana_reporter/asciidoctor/panel_image_query.rb +0 -25
  78. data/lib/grafana_reporter/asciidoctor/panel_property_query.rb +0 -44
  79. data/lib/grafana_reporter/asciidoctor/panel_table_query.rb +0 -38
  80. data/lib/grafana_reporter/asciidoctor/query_mixin.rb +0 -294
  81. data/lib/grafana_reporter/asciidoctor/sql_first_value_query.rb +0 -32
  82. data/lib/grafana_reporter/asciidoctor/sql_table_query.rb +0 -34
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GrafanaReporter
4
- module Asciidoctor
5
- module Extensions
6
- # Implements the hook
7
- # include::grafana_sql_table:<datasource_id>[<options>]
8
- #
9
- # Returns the results of the SQL query as a asciidoctor table.
10
- #
11
- # == Used document parameters
12
- # +grafana_default_instance+ - name of grafana instance, 'default' if not specified
13
- #
14
- # +from+ - 'from' time for the sql query
15
- #
16
- # +to+ - 'to' time for the sql query
17
- #
18
- # All other variables starting with +var-+ will be used to replace grafana templating strings
19
- # in the given SQL query.
20
- #
21
- # == Supported options
22
- # +sql+ - sql statement (*mandatory*)
23
- #
24
- # +instance+ - name of grafana instance, 'default' if not specified
25
- #
26
- # +from+ - 'from' time for the sql query
27
- #
28
- # +to+ - 'to' time for the sql query
29
- #
30
- # +format+ - see {QueryMixin#format_columns}
31
- #
32
- # +replace_values+ - see {QueryMixin#replace_values}
33
- #
34
- # +filter_columns+ - see {QueryMixin#filter_columns}
35
- class SqlTableIncludeProcessor < ::Asciidoctor::Extensions::IncludeProcessor
36
- include ProcessorMixin
37
-
38
- # :nodoc:
39
- def handles?(target)
40
- target.start_with? 'grafana_sql_table:'
41
- end
42
-
43
- # :nodoc:
44
- def process(doc, reader, target, attrs)
45
- return if @report.cancel
46
-
47
- @report.next_step
48
- instance = attrs['instance'] || doc.attr('grafana_default_instance') || 'default'
49
- @report.logger.debug("Processing SqlTableIncludeProcessor (instance: #{instance},"\
50
- " datasource: #{target.split(':')[1]}, sql: #{attrs['sql']})")
51
- query = SqlTableQuery.new(attrs['sql'], target.split(':')[1])
52
- query.merge_hash_variables(doc.attributes, attrs)
53
- @report.logger.debug("from: #{query.from}, to: #{query.to}")
54
-
55
- begin
56
- reader.unshift_lines query.execute(@report.grafana(instance))
57
- rescue GrafanaReporterError => e
58
- @report.logger.error(e.message)
59
- reader.unshift_line "|#{e.message}"
60
- rescue StandardError => e
61
- @report.logger.fatal(e.message)
62
- reader.unshift_line "|#{e.message}"
63
- end
64
-
65
- reader
66
- end
67
- end
68
- end
69
- end
70
- end
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GrafanaReporter
4
- module Asciidoctor
5
- module Extensions
6
- # Implements the hook
7
- # grafana_sql_value:<datasource_id>[<options>]
8
- #
9
- # Returns the first value of the resulting SQL query.
10
- #
11
- # == Used document parameters
12
- # +grafana_default_instance+ - name of grafana instance, 'default' if not specified
13
- #
14
- # +from+ - 'from' time for the sql query
15
- #
16
- # +to+ - 'to' time for the sql query
17
- #
18
- # All other variables starting with +var-+ will be used to replace grafana templating strings
19
- # in the given SQL query.
20
- #
21
- # == Supported options
22
- # +sql+ - sql statement (*mandatory*)
23
- #
24
- # +instance+ - name of grafana instance, 'default' if not specified
25
- #
26
- # +from+ - 'from' time for the sql query
27
- #
28
- # +to+ - 'to' time for the sql query
29
- #
30
- # +format+ - see {QueryMixin#format_columns}
31
- #
32
- # +replace_values+ - see {QueryMixin#replace_values}
33
- #
34
- # +filter_columns+ - see {QueryMixin#filter_columns}
35
- class SqlValueInlineMacro < ::Asciidoctor::Extensions::InlineMacroProcessor
36
- include ProcessorMixin
37
- use_dsl
38
-
39
- named :grafana_sql_value
40
-
41
- # @see GrafanaReporter::Asciidoctor::SqlFirstValueQuery
42
- def process(parent, target, attrs)
43
- return if @report.cancel
44
-
45
- @report.next_step
46
- instance = attrs['instance'] || parent.document.attr('grafana_default_instance') || 'default'
47
- @report.logger.debug("Processing SqlValueInlineMacro (instance: #{instance}, datasource: #{target},"\
48
- " sql: #{attrs['sql']})")
49
- query = SqlFirstValueQuery.new(attrs['sql'], target)
50
- query.merge_hash_variables(parent.document.attributes, attrs)
51
- @report.logger.debug("from: #{query.from}, to: #{query.to}")
52
-
53
- begin
54
- create_inline(parent, :quoted, query.execute(@report.grafana(instance)))
55
- rescue GrafanaReporterError => e
56
- @report.logger.error(e.message)
57
- create_inline(parent, :quoted, e.message)
58
- rescue StandardError => e
59
- @report.logger.fatal(e.message)
60
- create_inline(parent, :quoted, e.message)
61
- end
62
- end
63
- end
64
- end
65
- end
66
- end
@@ -1,86 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'processor_mixin'
4
-
5
- module GrafanaReporter
6
- module Asciidoctor
7
- module Extensions
8
- # Implements the hook
9
- # include::grafana_value_as_variable[<options>]
10
- #
11
- # Returns an attribute definition in asciidoctor format. This is needed if you want to refer to values of
12
- # a grafana query within a variable in asciidoctor. As this works without this function for the
13
- # `IncludeProcessor`s values, it will not work for all the other processors.
14
- #
15
- # This method is just a proxy for all other hooks and will forward parameters accordingly.
16
- #
17
- # Example:
18
- #
19
- # include:grafana_value_as_variable[call="grafana_sql_value:1",variable_name="my_variable",sql="SELECT 'looks good'",<any_other_option>]
20
- #
21
- # This will call the {SqlValueInlineMacro} with `datasource_id` set to `1` and store the result in the
22
- # variable. The resulting asciidoctor variable definition will be created as:
23
- #
24
- # :my_variable: looks good
25
- #
26
- # and can be refered to in your document easily as
27
- #
28
- # {my_variable}
29
- #
30
- # == Supported options
31
- # +call+ - regular call to the reporter hook (*mandatory*)
32
- #
33
- # +variable_name+ - name of the variable, to which the result shall be assigned (*mandatory*)
34
- class ValueAsVariableIncludeProcessor < ::Asciidoctor::Extensions::IncludeProcessor
35
- include ProcessorMixin
36
-
37
- # :nodoc:
38
- def handles?(target)
39
- target.start_with? 'grafana_value_as_variable'
40
- end
41
-
42
- # :nodoc:
43
- def process(doc, reader, target, attrs)
44
- return if @report.cancel
45
-
46
- # do NOT increase step, as this is done by sub processor
47
- # @report.next_step
48
-
49
- call_attr = attrs.delete('call')
50
- call, target = call_attr.split(':') if call_attr
51
- attribute = attrs.delete('variable_name')
52
- @report.logger.debug("Processing ValueAsVariableIncludeProcessor (call: #{call}, target: #{target},"\
53
- " variable_name: #{attribute}, attrs: #{attrs})")
54
- if !call || !attribute
55
- @report.logger.error("Missing mandatory attribute 'call' or 'variable_name'.")
56
- return reader
57
- end
58
-
59
- # TODO: remove dirty hack to allow the document as parameter for other processors
60
- def doc.document
61
- self
62
- end
63
-
64
- # TODO: properly show error messages also in document
65
- ext = doc.extensions.find_inline_macro_extension(call) if doc.extensions.inline_macros?
66
- if !ext
67
- @report.logger.error("Could not find inline macro extension for '#{call}'.")
68
- else
69
- @report.logger.debug('ValueAsVariableIncludeProcessor: Calling sub-method.')
70
- item = ext.process_method.call(doc, target, attrs)
71
- if !item.text.to_s.empty?
72
- result = ":#{attribute}: #{item.text}"
73
- @report.logger.debug("ValueAsVariableIncludeProcessor: Adding '#{result}' to document.")
74
- reader.unshift_line(result)
75
- else
76
- @report.logger.debug("ValueAsVariableIncludeProcessor: Not adding variable '#{attribute}',"\
77
- ' as query result was empty.')
78
- end
79
- end
80
-
81
- reader
82
- end
83
- end
84
- end
85
- end
86
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'sql_first_value_query'
4
-
5
- module GrafanaReporter
6
- module Asciidoctor
7
- # (see SqlFirstValueQuery)
8
- #
9
- # The SQL query as well as the datasource configuration are thereby captured from a
10
- # {Grafana::Panel}.
11
- class PanelFirstValueQuery < SqlFirstValueQuery
12
- include QueryMixin
13
-
14
- # (see PanelTableQuery#initialize)
15
- def initialize(panel, query_letter)
16
- super(nil, nil)
17
- @panel = panel
18
- @query_letter = query_letter
19
- extract_dashboard_variables(@panel.dashboard)
20
- end
21
-
22
- # (see PanelTableQuery#pre_process)
23
- def pre_process(grafana)
24
- @sql = @panel.query(@query_letter)
25
- # resolve datasource name
26
- @datasource = @panel.field('datasource')
27
- @datasource_id = grafana.datasource_id(@datasource)
28
- super(grafana)
29
- @from = translate_date(@from, @variables['grafana-report-timestamp'], false)
30
- @to = translate_date(@to, @variables['grafana-report-timestamp'], true)
31
- end
32
- end
33
- end
34
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GrafanaReporter
4
- module Asciidoctor
5
- # This class is used to create an image out of a {Grafana::Panel}.
6
- class PanelImageQuery < Grafana::PanelImageQuery
7
- include QueryMixin
8
-
9
- # Sets the proper render variables.
10
- def pre_process(grafana)
11
- super
12
- @from = translate_date(@from, @variables['grafana-report-timestamp'], false)
13
- @to = translate_date(@to, @variables['grafana-report-timestamp'], true)
14
- # rename "render-" variables
15
- @variables = @variables.each_with_object({}) { |(k,v), h| h[k.gsub(/^render-/, '')] = v }
16
- end
17
-
18
- # Returns the body of the http query, which contains the raw image.
19
- def post_process
20
- super
21
- @result = @result.body
22
- end
23
- end
24
- end
25
- end
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GrafanaReporter
4
- module Asciidoctor
5
- # This class is used to query properties from a {Grafana::Panel}, such as +description+,
6
- # +title+ etc.
7
- class PanelPropertyQuery < ::Grafana::AbstractPanelQuery
8
- include QueryMixin
9
-
10
- # @param panel [Grafana::Panel] panel object, for which the property shall be retrieved
11
- # @param property [String] queried property, e.g. +title+
12
- def initialize(panel, property)
13
- super(panel)
14
- @property = property
15
- end
16
-
17
- # Overrides the default method, as the query does not have to run against a SQL table,
18
- # but rather against the panel model.
19
- # @param grafana [Grafana::Grafana] grafana instance against which the panel property is queried
20
- # @return [String] fetched property
21
- def execute(grafana)
22
- return @result unless @result.nil?
23
-
24
- pre_process(grafana)
25
- @result = panel.field(@property)
26
- post_process
27
- @result
28
-
29
- # TODO: handle text (markdown and similar) properly
30
- end
31
-
32
- # Prepare query. Mainly here nothing special has to take place.
33
- def pre_process(_grafana)
34
- @from = nil
35
- @to = nil
36
- end
37
-
38
- # Replaces variables in the property field, if any are available.
39
- def post_process
40
- @result = replace_variables(@result, grafana_variables)
41
- end
42
- end
43
- end
44
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'sql_table_query'
4
-
5
- module GrafanaReporter
6
- module Asciidoctor
7
- # (see SqlTableQuery)
8
- #
9
- # The SQL query as well as the datasource configuration are thereby captured from a
10
- # {Grafana::Panel}.
11
- class PanelTableQuery < SqlTableQuery
12
- include QueryMixin
13
-
14
- # @param panel [Grafana::Panel] panel which contains the query
15
- # @param query_letter [String] letter of the query within the panel, which shall be used, e.g. +C+
16
- def initialize(panel, query_letter)
17
- super(nil, nil)
18
- @panel = panel
19
- @query_letter = query_letter
20
- extract_dashboard_variables(@panel.dashboard)
21
- end
22
-
23
- # Retrieves the SQL query and the configured datasource from the panel.
24
- # @see Grafana::AbstractSqlQuery#pre_process
25
- # @param grafana [Grafana::Grafana] grafana instance against which the query shall be executed
26
- # @return [void]
27
- def pre_process(grafana)
28
- @sql = @panel.query(@query_letter)
29
- # resolve datasource name
30
- @datasource = @panel.field('datasource')
31
- @datasource_id = grafana.datasource_id(@datasource)
32
- super(grafana)
33
- @from = translate_date(@from, @variables['grafana-report-timestamp'], false)
34
- @to = translate_date(@to, @variables['grafana-report-timestamp'], true)
35
- end
36
- end
37
- end
38
- end
@@ -1,294 +0,0 @@
1
- module GrafanaReporter
2
- module Asciidoctor
3
- # This mixin contains several common methods, which can be used within the queries.
4
- module QueryMixin
5
- # Merges the given hashes to the current object by using the {Grafana::AbstractQuery#merge_variables} method.
6
- # It respects the priorities of the hashes and the object and allows only valid variables to be passed.
7
- # @param document_hash [Hash] variables from report template level
8
- # @param item_hash [Hash] variables from item configuration level, i.e. specific call, which may override document
9
- # @return [void]
10
- def merge_hash_variables(document_hash, item_hash)
11
- merge_variables(document_hash.select { |k, _v| k =~ /^var-/ || k == 'grafana-report-timestamp' }.each_with_object({}) { |(k,v), h| h[k] = ::Grafana::Variable.new(v) })
12
- merge_variables(item_hash.select { |k, _v| k =~ /^var-/ || k =~ /^render-/ || k =~ /filter_columns|format|replace_values_.*|transpose|column_divider|row_divider/ }.each_with_object({}) { |(k,v), h| h[k] = ::Grafana::Variable.new(v) })
13
- self.timeout = item_hash['timeout'] || document_hash['grafana-default-timeout'] || timeout
14
- self.from = item_hash['from'] || document_hash['from'] || from
15
- self.to = item_hash['to'] || document_hash['to'] || to
16
- end
17
-
18
- # Formats the SQL results returned from grafana to an easier to use format.
19
- #
20
- # The result is being formatted as stated below:
21
- #
22
- # {
23
- # :header => [column_title_1, column_title_2],
24
- # :content => [
25
- # [row_1_column_1, row_1_column_2],
26
- # [row_2_column_1, row_2_column_2]
27
- # ]
28
- # }
29
- # @param raw_result [Hash] query result hash from grafana
30
- # @return [Hash] sql result formatted as stated above
31
- def preformat_sql_result(raw_result)
32
- results = {}
33
- results.default = []
34
-
35
- JSON.parse(raw_result)['results'].each_value do |query_result|
36
- if query_result.key?('error')
37
- results[:header] = results[:header] << ['SQL Error']
38
- results[:content] = [[query_result['error']]]
39
-
40
- elsif query_result['tables']
41
- query_result['tables'].each do |table|
42
- results[:header] = results[:header] << table['columns'].map { |header| header['text'] }
43
- results[:content] = table['rows']
44
- end
45
-
46
- end
47
- end
48
-
49
- results
50
- end
51
-
52
- # Transposes the given result.
53
- #
54
- # NOTE: Only the +:content+ of the given result hash is transposed. The +:header+ is ignored.
55
- #
56
- # @param result [Hash] preformatted sql hash, (see {#preformat_sql_result})
57
- # @param transpose_variable [Grafana::Variable] true, if the result hash shall be transposed
58
- # @return [Hash] transposed query result
59
- def transpose(result, transpose_variable)
60
- return result unless transpose_variable
61
- return result unless transpose_variable.raw_value == 'true'
62
-
63
- result[:content] = result[:content].transpose
64
-
65
- result
66
- end
67
-
68
- # Filters columns out of the query result.
69
- #
70
- # Multiple columns may be filtered. Therefore the column titles have to be named in the
71
- # {Grafana::Variable#raw_value} and have to be separated by +,+ (comma).
72
- # @param result [Hash] preformatted sql hash, (see {#preformat_sql_result})
73
- # @param filter_columns_variable [Grafana::Variable] column names, which shall be removed in the query result
74
- # @return [Hash] filtered query result
75
- def filter_columns(result, filter_columns_variable)
76
- return result unless filter_columns_variable
77
-
78
- filter_columns = filter_columns_variable.raw_value
79
- filter_columns.split(',').each do |filter_column|
80
- pos = result[:header][0].index(filter_column)
81
-
82
- unless pos.nil?
83
- result[:header][0].delete_at(pos)
84
- result[:content].each { |row| row.delete_at(pos) }
85
- end
86
- end
87
-
88
- result
89
- end
90
-
91
- # Uses the {Kernel#format} method to format values in the query results.
92
- #
93
- # The formatting will be applied separately for every column. Therefore the column formats have to be named
94
- # in the {Grafana::Variable#raw_value} and have to be separated by +,+ (comma). If no value is specified for
95
- # a column, no change will happen.
96
- # @param result [Hash] preformatted sql hash, (see {#preformat_sql_result})
97
- # @param formats [Grafana::Variable] formats, which shall be applied to the columns in the query result
98
- # @return [Hash] formatted query result
99
- # TODO: make sure that caught errors are also visible in logger
100
- def format_columns(result, formats)
101
- return result unless formats
102
-
103
- formats.text.split(',').each_index do |i|
104
- format = formats.text.split(',')[i]
105
- next if format.empty?
106
-
107
- result[:content].map do |row|
108
- next unless row.length > i
109
-
110
- begin
111
- row[i] = format % row[i] if row[i]
112
- rescue StandardError => e
113
- row[i] = e.message
114
- end
115
- end
116
- end
117
- result
118
- end
119
-
120
- # Used to replace values in a query result according given configurations.
121
- #
122
- # The given variables will be applied to an appropriate column, depending
123
- # on the naming of the variable. The variable name ending specifies the column,
124
- # e.g. a variable named +replace_values_2+ will be applied to the second column.
125
- #
126
- # The {Grafana::Variable#text} needs to contain the replace specification.
127
- # Multiple replacements can be specified by separating them with +,+. If a
128
- # literal comma is needed, it can be escaped with a backslash: +\\,+.
129
- #
130
- # The rule will be separated from the replacement text with a colon +:+.
131
- # If a literal colon is wanted, it can be escaped with a backslash: +\\:+.
132
- #
133
- # Examples:
134
- # - Basic string replacement
135
- # MyTest:ThisValue
136
- # will replace all occurences of the text 'MyTest' with 'ThisValue'.
137
- # - Number comparison
138
- # <=10:OK
139
- # will replace all values smaller or equal to 10 with 'OK'.
140
- # - Regular expression
141
- # ^[^ ]\\+ (\d+)$:\1 is the answer
142
- # will replace all values matching the pattern, e.g. 'answerToAllQuestions 42' to
143
- # '42 is the answer'. Important to know: the regular expressions always have to start
144
- # with +^+ and end with +$+, i.e. the expression itself always has to match
145
- # the whole content in one field.
146
- # @param result [Hash] preformatted query result (see {#preformat_sql_result}.
147
- # @param configs [Array<Grafana::Variable>] one variable for replacing values in one column
148
- # @return [Hash] query result with replaced values
149
- # TODO: make sure that caught errors are also visible in logger
150
- def replace_values(result, configs)
151
- return result if configs.empty?
152
-
153
- configs.each do |key, formats|
154
- cols = key.split('_')[2..-1].map(&:to_i)
155
-
156
- formats.text.split(/(?<!\\),/).each_index do |j|
157
- format = formats.text.split(/(?<!\\),/)[j]
158
-
159
- arr = format.split(/(?<!\\):/)
160
- raise MalformedReplaceValuesStatementError, format if arr.length != 2
161
-
162
- k = arr[0]
163
- v = arr[1]
164
- k.gsub!(/\\([:,])/, '\1')
165
- v.gsub!(/\\([:,])/, '\1')
166
- result[:content].map do |row|
167
- (row.length - 1).downto 0 do |i|
168
- if cols.include?(i + 1) || cols.empty?
169
-
170
- # handle regular expressions
171
- if k.start_with?('^') && k.end_with?('$')
172
- begin
173
- row[i] = row[i].to_s.gsub(/#{k}/, v) if row[i].to_s =~ /#{k}/
174
- rescue StandardError => e
175
- row[i] = e.message
176
- end
177
-
178
- # handle value comparisons
179
- elsif (match = k.match(/^ *(?<operator>[<>]=?|<>|=) *(?<number>[+-]?\d+(?:\.\d+)?)$/))
180
- skip = false
181
- begin
182
- val = Float(row[i])
183
- rescue StandardError
184
- # value cannot be converted to number, simply ignore it as the comparison does not fit here
185
- skip = true
186
- end
187
-
188
- unless skip
189
- begin
190
- op = match[:operator].gsub(/^=$/, '==').gsub(/^<>$/, '!=')
191
- if val.public_send(op.to_sym, Float(match[:number]))
192
- row[i] = if v.include?('\\1')
193
- v.gsub(/\\1/, row[i].to_s)
194
- else
195
- v
196
- end
197
- end
198
- rescue StandardError => e
199
- row[i] = e.message
200
- end
201
- end
202
-
203
- # handle as normal comparison
204
- elsif row[i].to_s == k
205
- row[i] = v
206
- end
207
- end
208
- end
209
- end
210
- end
211
- end
212
-
213
- result
214
- end
215
-
216
- # Used to translate the relative date strings used by grafana, e.g. +now-5d/w+ to the
217
- # correct timestamp. Reason is that grafana does this in the frontend, which we have
218
- # to emulate here for the reporter.
219
- #
220
- # Additionally providing this function the +report_time+ assures that all queries
221
- # rendered within one report will use _exactly_ the same timestamp in those relative
222
- # times, i.e. there shouldn't appear any time differences, no matter how long the
223
- # report is running.
224
- # @param orig_date [String] time string provided by grafana, usually +from+ or +to+.
225
- # @param report_time [Grafana::Variable] report start time
226
- # @param is_to_time [Boolean] true, if the time should be calculated for +to+, false if it shall be
227
- # calculated for +from+
228
- # @return [String] translated date as timestamp string
229
- def translate_date(orig_date, report_time, is_to_time)
230
- report_time ||= Variable.new(Time.now.to_s)
231
- return (DateTime.parse(report_time.raw_value).to_time.to_i * 1000).to_s unless orig_date
232
- return orig_date if orig_date =~ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
233
- return orig_date if orig_date =~ /^\d+$/
234
-
235
- # replace grafana from and to values using now, now-2d etc.
236
- date_splitted = orig_date.match(%r{^(?<now>now)(?:-(?<sub_count>\d+)?(?<sub_unit>[smhdwMy]?))?(?:/(?<fit>[smhdwMy]))?$})
237
- raise TimeRangeUnknownError, orig_date unless date_splitted
238
-
239
- date = DateTime.parse(report_time.raw_value)
240
- # substract specified time
241
- count = 1
242
- count = date_splitted[:sub_count].to_i if date_splitted[:sub_count]
243
- case date_splitted[:sub_unit]
244
- when 's'
245
- date = (date.to_time - (count * 1)).to_datetime
246
- when 'm'
247
- date = (date.to_time - (count * 60)).to_datetime
248
- when 'h'
249
- date = (date.to_time - (count * 60 * 60)).to_datetime
250
- when 'd'
251
- date = date.prev_day(count)
252
- when 'w'
253
- date = date.prev_day(count * 7)
254
- when 'M'
255
- date = date.prev_month(count)
256
- when 'y'
257
- date = date.prev_year(count)
258
- end
259
-
260
- # fit to specified time frame
261
- case date_splitted[:fit]
262
- when 's'
263
- date = DateTime.new(date.year, date.month, date.day, date.hour, date.min, date.sec, date.zone)
264
- date = (date.to_time + 1).to_datetime if is_to_time
265
- when 'm'
266
- date = DateTime.new(date.year, date.month, date.day, date.hour, date.min, 0, date.zone)
267
- date = (date.to_time + 60).to_datetime if is_to_time
268
- when 'h'
269
- date = DateTime.new(date.year, date.month, date.day, date.hour, 0, 0, date.zone)
270
- date = (date.to_time + 60 * 60).to_datetime if is_to_time
271
- when 'd'
272
- date = DateTime.new(date.year, date.month, date.day, 0, 0, 0, date.zone)
273
- date = date.next_day(1) if is_to_time
274
- when 'w'
275
- date = DateTime.new(date.year, date.month, date.day, 0, 0, 0, date.zone)
276
- date = if date.wday.zero?
277
- date.prev_day(7)
278
- else
279
- date.prev_day(date.wday - 1)
280
- end
281
- date = date.next_day(7) if is_to_time
282
- when 'M'
283
- date = DateTime.new(date.year, date.month, 1, 0, 0, 0, date.zone)
284
- date = date.next_month if is_to_time
285
- when 'y'
286
- date = DateTime.new(date.year, 1, 1, 0, 0, 0, date.zone)
287
- date = date.next_year if is_to_time
288
- end
289
-
290
- (date.to_time.to_i * 1000).to_s
291
- end
292
- end
293
- end
294
- end