ruby-grafana-reporter 0.1.7 → 0.2.0

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +166 -339
  3. data/bin/ruby-grafana-reporter +5 -4
  4. data/lib/VERSION.rb +5 -3
  5. data/lib/grafana/abstract_panel_query.rb +22 -20
  6. data/lib/grafana/abstract_query.rb +132 -127
  7. data/lib/grafana/abstract_sql_query.rb +51 -42
  8. data/lib/grafana/dashboard.rb +77 -66
  9. data/lib/grafana/errors.rb +66 -61
  10. data/lib/grafana/grafana.rb +130 -131
  11. data/lib/grafana/panel.rb +41 -39
  12. data/lib/grafana/panel_image_query.rb +52 -49
  13. data/lib/grafana/variable.rb +217 -259
  14. data/lib/grafana_reporter/abstract_report.rb +112 -109
  15. data/lib/grafana_reporter/application/application.rb +404 -229
  16. data/lib/grafana_reporter/application/errors.rb +33 -30
  17. data/lib/grafana_reporter/application/webservice.rb +231 -0
  18. data/lib/grafana_reporter/asciidoctor/alerts_table_query.rb +104 -99
  19. data/lib/grafana_reporter/asciidoctor/annotations_table_query.rb +99 -96
  20. data/lib/grafana_reporter/asciidoctor/errors.rb +40 -37
  21. data/lib/grafana_reporter/asciidoctor/extensions/alerts_table_include_processor.rb +92 -86
  22. data/lib/grafana_reporter/asciidoctor/extensions/annotations_table_include_processor.rb +91 -86
  23. data/lib/grafana_reporter/asciidoctor/extensions/panel_image_block_macro.rb +69 -67
  24. data/lib/grafana_reporter/asciidoctor/extensions/panel_image_inline_macro.rb +68 -65
  25. data/lib/grafana_reporter/asciidoctor/extensions/panel_property_inline_macro.rb +61 -58
  26. data/lib/grafana_reporter/asciidoctor/extensions/panel_query_table_include_processor.rb +78 -75
  27. data/lib/grafana_reporter/asciidoctor/extensions/panel_query_value_inline_macro.rb +73 -70
  28. data/lib/grafana_reporter/asciidoctor/extensions/processor_mixin.rb +20 -18
  29. data/lib/grafana_reporter/asciidoctor/extensions/show_environment_include_processor.rb +43 -41
  30. data/lib/grafana_reporter/asciidoctor/extensions/sql_table_include_processor.rb +70 -67
  31. data/lib/grafana_reporter/asciidoctor/extensions/sql_value_inline_macro.rb +66 -65
  32. data/lib/grafana_reporter/asciidoctor/extensions/value_as_variable_include_processor.rb +61 -57
  33. data/lib/grafana_reporter/asciidoctor/panel_first_value_query.rb +34 -32
  34. data/lib/grafana_reporter/asciidoctor/panel_image_query.rb +25 -23
  35. data/lib/grafana_reporter/asciidoctor/panel_property_query.rb +44 -43
  36. data/lib/grafana_reporter/asciidoctor/panel_table_query.rb +38 -36
  37. data/lib/grafana_reporter/asciidoctor/query_mixin.rb +310 -309
  38. data/lib/grafana_reporter/asciidoctor/report.rb +177 -159
  39. data/lib/grafana_reporter/asciidoctor/sql_first_value_query.rb +37 -34
  40. data/lib/grafana_reporter/asciidoctor/sql_table_query.rb +39 -32
  41. data/lib/grafana_reporter/configuration.rb +257 -326
  42. data/lib/grafana_reporter/errors.rb +48 -38
  43. data/lib/grafana_reporter/logger/two_way_logger.rb +58 -52
  44. data/lib/ruby-grafana-reporter.rb +29 -27
  45. metadata +10 -23
@@ -1,96 +1,99 @@
1
- module GrafanaReporter
2
- module Asciidoctor
3
- # This class is used to query annotations from grafana.
4
- class AnnotationsTableQuery < Grafana::AbstractQuery
5
- include QueryMixin
6
-
7
- # @option opts [Grafana::Dashboard] :dashboard dashboard, if annotations shall be filtered for a dashboard
8
- # @option opts [Grafana::Panel] :panel panel, if annotations shall be filtered for a panel
9
- def initialize(opts = {})
10
- super()
11
-
12
- @dashboard = opts[:dashboard]
13
- @panel = opts[:panel]
14
- @dashboard = @panel.dashboard if @panel
15
-
16
- extract_dashboard_variables(@dashboard) if @dashboard
17
- end
18
-
19
- # @return [String] URL for querying annotations
20
- def url
21
- '/api/annotations' + url_parameters
22
- end
23
-
24
- # @return [Hash] empty hash object
25
- def request
26
- {}
27
- end
28
-
29
- # Check if mandatory {Grafana::Variable} +columns+ is specified in variables.
30
- #
31
- # The value of the +columns+ variable has to be a comma separated list of column titles, which
32
- # need to be included in the following list:
33
- # - limit
34
- # - alertId
35
- # - userId
36
- # - type
37
- # - tags
38
- # - dashboardId
39
- # - panelId
40
- # @return [void]
41
- def pre_process(_grafana)
42
- raise MissingMandatoryAttributeError, 'columns' unless @variables['columns']
43
-
44
- @from = translate_date(@from, @variables['grafana-report-timestamp'], false)
45
- @to = translate_date(@to, @variables['grafana-report-timestamp'], true)
46
- end
47
-
48
- # Filters the query result for the given columns and sets the result
49
- # in the preformatted SQL result style.
50
- #
51
- # Additionally it applies {QueryMixin#format_columns}, {QueryMixin#replace_values} and
52
- # {QueryMixin#filter_columns}.
53
- # @return [void]
54
- def post_process
55
- # extract data from returned json
56
- result = JSON.parse(@result.body)
57
- content = []
58
- begin
59
- result.each { |item| content << item.fetch_values(*@variables['columns'].raw_value.split(',')) }
60
- rescue KeyError => e
61
- raise MalformedAttributeContentError.new(e.message, 'columns', @variables['columns'])
62
- end
63
-
64
- result = {}
65
- result[:header] = [@variables['columns'].raw_value.split(',')]
66
- result[:content] = content
67
-
68
- result = format_columns(result, @variables['format'])
69
- result = replace_values(result, @variables.select { |k, _v| k =~ /^replace_values_\d+/ })
70
- result = filter_columns(result, @variables['filter_columns'])
71
- if @variables['filter_column']
72
- @report.logger.warn("DEPRECATED: Call of no longer supported function 'filter_column' has been found. Rename to 'filter_columns'")
73
- result = filter_columns(result, @variables['filter_column'])
74
- end
75
-
76
- @result = result[:content].map { |row| '| ' + row.map { |item| item.to_s.gsub('|', '\\|') }.join(' | ') }
77
- end
78
-
79
- private
80
-
81
- def url_parameters
82
- url_vars = {}
83
- url_vars['dashboardId'] = ::Grafana::Variable.new(@dashboard.id) if @dashboard
84
- url_vars['panelId'] = ::Grafana::Variable.new(@panel.id) if @panel
85
-
86
- url_vars.merge!(variables.select { |k, _v| k =~ /^(?:limit|alertId|dashboardId|panelId|userId|type|tags)/ })
87
- url_vars['from'] = ::Grafana::Variable.new(@from) if @from
88
- url_vars['to'] = ::Grafana::Variable.new(@to) if @to
89
- url_params = URI.encode_www_form(url_vars.map { |k, v| [k, v.raw_value.to_s] })
90
- return '' if url_params.empty?
91
-
92
- '?' + url_params
93
- end
94
- end
95
- end
96
- end
1
+ # frozen_string_literal: true
2
+
3
+ module GrafanaReporter
4
+ module Asciidoctor
5
+ # This class is used to query annotations from grafana.
6
+ class AnnotationsTableQuery < Grafana::AbstractQuery
7
+ include QueryMixin
8
+
9
+ # @option opts [Grafana::Dashboard] :dashboard dashboard, if annotations shall be filtered for a dashboard
10
+ # @option opts [Grafana::Panel] :panel panel, if annotations shall be filtered for a panel
11
+ def initialize(opts = {})
12
+ super()
13
+
14
+ @dashboard = opts[:dashboard]
15
+ @panel = opts[:panel]
16
+ @dashboard = @panel.dashboard if @panel
17
+
18
+ extract_dashboard_variables(@dashboard) if @dashboard
19
+ end
20
+
21
+ # @return [String] URL for querying annotations
22
+ def url
23
+ "/api/annotations#{url_parameters}"
24
+ end
25
+
26
+ # @return [Hash] empty hash object
27
+ def request
28
+ {}
29
+ end
30
+
31
+ # Check if mandatory {Grafana::Variable} +columns+ is specified in variables.
32
+ #
33
+ # The value of the +columns+ variable has to be a comma separated list of column titles, which
34
+ # need to be included in the following list:
35
+ # - limit
36
+ # - alertId
37
+ # - userId
38
+ # - type
39
+ # - tags
40
+ # - dashboardId
41
+ # - panelId
42
+ # @return [void]
43
+ def pre_process(_grafana)
44
+ raise MissingMandatoryAttributeError, 'columns' unless @variables['columns']
45
+
46
+ @from = translate_date(@from, @variables['grafana-report-timestamp'], false)
47
+ @to = translate_date(@to, @variables['grafana-report-timestamp'], true)
48
+ end
49
+
50
+ # Filters the query result for the given columns and sets the result
51
+ # in the preformatted SQL result style.
52
+ #
53
+ # Additionally it applies {QueryMixin#format_columns}, {QueryMixin#replace_values} and
54
+ # {QueryMixin#filter_columns}.
55
+ # @return [void]
56
+ def post_process
57
+ # extract data from returned json
58
+ result = JSON.parse(@result.body)
59
+ content = []
60
+ begin
61
+ result.each { |item| content << item.fetch_values(*@variables['columns'].raw_value.split(',')) }
62
+ rescue KeyError => e
63
+ raise MalformedAttributeContentError.new(e.message, 'columns', @variables['columns'])
64
+ end
65
+
66
+ result = {}
67
+ result[:header] = [@variables['columns'].raw_value.split(',')]
68
+ result[:content] = content
69
+
70
+ result = format_columns(result, @variables['format'])
71
+ result = replace_values(result, @variables.select { |k, _v| k =~ /^replace_values_\d+/ })
72
+ result = filter_columns(result, @variables['filter_columns'])
73
+ if @variables['filter_column']
74
+ @report.logger.warn("DEPRECATED: Call of no longer supported function 'filter_column' has been found."\
75
+ " Rename to 'filter_columns'")
76
+ result = filter_columns(result, @variables['filter_column'])
77
+ end
78
+
79
+ @result = result[:content].map { |row| "| #{row.map { |item| item.to_s.gsub('|', '\\|') }.join(' | ')}" }
80
+ end
81
+
82
+ private
83
+
84
+ def url_parameters
85
+ url_vars = {}
86
+ url_vars['dashboardId'] = ::Grafana::Variable.new(@dashboard.id) if @dashboard
87
+ url_vars['panelId'] = ::Grafana::Variable.new(@panel.id) if @panel
88
+
89
+ url_vars.merge!(variables.select { |k, _v| k =~ /^(?:limit|alertId|dashboardId|panelId|userId|type|tags)/ })
90
+ url_vars['from'] = ::Grafana::Variable.new(@from) if @from
91
+ url_vars['to'] = ::Grafana::Variable.new(@to) if @to
92
+ url_params = URI.encode_www_form(url_vars.map { |k, v| [k, v.raw_value.to_s] })
93
+ return '' if url_params.empty?
94
+
95
+ "?#{url_params}"
96
+ end
97
+ end
98
+ end
99
+ end
@@ -1,37 +1,40 @@
1
- module GrafanaReporter
2
- # This module contains all classes, which are necessary to use the grafana
3
- # reporter to be used in conjunction with asciidoctor.
4
- module Asciidoctor
5
- # Thrown, if the value configuration in {QueryMixin#replace_values} is
6
- # invalid.
7
- class MalformedReplaceValuesStatementError < GrafanaReporterError
8
- def initialize(statement)
9
- super("The specified replace_values statement '#{statement}' is invalid. Make sure it contains exactly one not escaped ':' symbol.")
10
- end
11
- end
12
-
13
- # Thrown, if a configured parameter is malformed.
14
- class MalformedAttributeContentError < GrafanaReporterError
15
- def initialize(message, attribute, content)
16
- super("The content '#{content}' in attribute '#{attribute}' is malformed: #{message}")
17
- end
18
- end
19
-
20
- # Thrown, if a configured time range is not supported by the reporter.
21
- #
22
- # If this happens, most likely the reporter has to implement the new
23
- # time range definition.
24
- class TimeRangeUnknownError < GrafanaReporterError
25
- def initialize(time_range)
26
- super("The specified time range '#{time_range}' is unknown.")
27
- end
28
- end
29
-
30
- # Thrown, if a mandatory attribute is not set.
31
- class MissingMandatoryAttributeError < GrafanaReporterError
32
- def initialize(attribute)
33
- super("Missing mandatory attribute '#{attribute}'.")
34
- end
35
- end
36
- end
37
- end
1
+ # frozen_string_literal: true
2
+
3
+ module GrafanaReporter
4
+ # This module contains all classes, which are necessary to use the grafana
5
+ # reporter to be used in conjunction with asciidoctor.
6
+ module Asciidoctor
7
+ # Thrown, if the value configuration in {QueryMixin#replace_values} is
8
+ # invalid.
9
+ class MalformedReplaceValuesStatementError < GrafanaReporterError
10
+ def initialize(statement)
11
+ super("The specified replace_values statement '#{statement}' is invalid. Make sure it contains"\
12
+ " exactly one not escaped ':' symbol.")
13
+ end
14
+ end
15
+
16
+ # Thrown, if a configured parameter is malformed.
17
+ class MalformedAttributeContentError < GrafanaReporterError
18
+ def initialize(message, attribute, content)
19
+ super("The content '#{content}' in attribute '#{attribute}' is malformed: #{message}")
20
+ end
21
+ end
22
+
23
+ # Thrown, if a configured time range is not supported by the reporter.
24
+ #
25
+ # If this happens, most likely the reporter has to implement the new
26
+ # time range definition.
27
+ class TimeRangeUnknownError < GrafanaReporterError
28
+ def initialize(time_range)
29
+ super("The specified time range '#{time_range}' is unknown.")
30
+ end
31
+ end
32
+
33
+ # Thrown, if a mandatory attribute is not set.
34
+ class MissingMandatoryAttributeError < GrafanaReporterError
35
+ def initialize(attribute)
36
+ super("Missing mandatory attribute '#{attribute}'.")
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,86 +1,92 @@
1
- require_relative 'processor_mixin'
2
-
3
- module GrafanaReporter
4
- module Asciidoctor
5
- module Extensions
6
- # Implements the hook
7
- # include::grafana_alerts[<options>]
8
- #
9
- # Returns the results of alerts query as a asciidoctor table.
10
- #
11
- # == Used document parameters
12
- # +grafana_default_instance+ - name of grafana instance, 'default' if not specified
13
- #
14
- # +grafana_default_dashboard+ - uid of grafana default dashboard to use
15
- #
16
- # +from+ - 'from' time for the sql query
17
- #
18
- # +to+ - 'to' time for the sql query
19
- #
20
- # == Supported options
21
- # +columns+ - see {AlertsTableQuery#pre_process} (*mandatory*)
22
- #
23
- # +instance+ - name of grafana instance, 'default' if not specified
24
- #
25
- # +dashboard+ - uid of grafana dashboard to query for, empty string if no filter is wanted
26
- #
27
- # +panel+ - id of the panel to query for
28
- #
29
- # +from+ - 'from' time for the sql query
30
- #
31
- # +to+ - 'to' time for the sql query
32
- #
33
- # +format+ - see {QueryMixin#format_columns}
34
- #
35
- # +replace_values+ - see {QueryMixin#replace_values}
36
- #
37
- # +filter_columns+ - see {QueryMixin#filter_columns}
38
- class AlertsTableIncludeProcessor < ::Asciidoctor::Extensions::IncludeProcessor
39
- include ProcessorMixin
40
-
41
- # :nodoc:
42
- def handles?(target)
43
- target.start_with? 'grafana_alerts'
44
- end
45
-
46
- # :nodoc:
47
- def process(doc, reader, _target, attrs)
48
- return if @report.cancel
49
-
50
- @report.next_step
51
- instance = attrs['instance'] || doc.attr('grafana_default_instance') || 'default'
52
- dashboard_id = attrs['dashboard'] || doc.attr('grafana_default_dashboard')
53
- panel_id = attrs['panel']
54
- @report.logger.debug("Processing AlertsTableIncludeProcessor (instance: #{instance}, dashboard: #{dashboard_id}, panel: #{panel_id})")
55
-
56
- query = if dashboard_id.to_s.empty?
57
- # no dashboard shall be used, so also the panel will be omitted
58
- AlertsTableQuery.new
59
- elsif panel_id.to_s.empty?
60
- # a dashboard is given, but no panel, so set filter for dashboard only
61
- AlertsTableQuery.new(dashboard: @report.grafana(instance).dashboard(dashboard_id))
62
- else
63
- # dashboard and panel is given, so set filter for panel
64
- AlertsTableQuery.new(panel: @report.grafana(instance).dashboard(dashboard_id).panel(panel_id))
65
- end
66
-
67
- query.merge_hash_variables(doc.attributes, attrs)
68
- query.merge_variables(attrs.select { |k, _v| k =~ /(?:columns|limit|folderId|dashboardId|panelId|dahboardTag|dashboardQuery|state|query)/ }.transform_values { |item| ::Grafana::Variable.new(item) })
69
- @report.logger.debug("from: #{query.from}, to: #{query.to}")
70
-
71
- begin
72
- reader.unshift_lines query.execute(@report.grafana(instance))
73
- rescue GrafanaReporterError => e
74
- @report.logger.error(e.message)
75
- reader.unshift_line '|' + e.message
76
- rescue StandardError => e
77
- @report.logger.fatal(e.message)
78
- reader.unshift_line '|' + e.message
79
- end
80
-
81
- reader
82
- end
83
- end
84
- end
85
- end
86
- end
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_alerts[<options>]
10
+ #
11
+ # Returns the results of alerts query as a asciidoctor table.
12
+ #
13
+ # == Used document parameters
14
+ # +grafana_default_instance+ - name of grafana instance, 'default' if not specified
15
+ #
16
+ # +grafana_default_dashboard+ - uid of grafana default dashboard to use
17
+ #
18
+ # +from+ - 'from' time for the sql query
19
+ #
20
+ # +to+ - 'to' time for the sql query
21
+ #
22
+ # == Supported options
23
+ # +columns+ - see {AlertsTableQuery#pre_process} (*mandatory*)
24
+ #
25
+ # +instance+ - name of grafana instance, 'default' if not specified
26
+ #
27
+ # +dashboard+ - uid of grafana dashboard to query for, empty string if no filter is wanted
28
+ #
29
+ # +panel+ - id of the panel to query for
30
+ #
31
+ # +from+ - 'from' time for the sql query
32
+ #
33
+ # +to+ - 'to' time for the sql query
34
+ #
35
+ # +format+ - see {QueryMixin#format_columns}
36
+ #
37
+ # +replace_values+ - see {QueryMixin#replace_values}
38
+ #
39
+ # +filter_columns+ - see {QueryMixin#filter_columns}
40
+ class AlertsTableIncludeProcessor < ::Asciidoctor::Extensions::IncludeProcessor
41
+ include ProcessorMixin
42
+
43
+ # :nodoc:
44
+ def handles?(target)
45
+ target.start_with? 'grafana_alerts'
46
+ end
47
+
48
+ # :nodoc:
49
+ def process(doc, reader, _target, attrs)
50
+ return if @report.cancel
51
+
52
+ @report.next_step
53
+ instance = attrs['instance'] || doc.attr('grafana_default_instance') || 'default'
54
+ dashboard_id = attrs['dashboard'] || doc.attr('grafana_default_dashboard')
55
+ panel_id = attrs['panel']
56
+ @report.logger.debug("Processing AlertsTableIncludeProcessor (instance: #{instance},"\
57
+ " dashboard: #{dashboard_id}, panel: #{panel_id})")
58
+
59
+ query = if dashboard_id.to_s.empty?
60
+ # no dashboard shall be used, so also the panel will be omitted
61
+ AlertsTableQuery.new
62
+ elsif panel_id.to_s.empty?
63
+ # a dashboard is given, but no panel, so set filter for dashboard only
64
+ AlertsTableQuery.new(dashboard: @report.grafana(instance).dashboard(dashboard_id))
65
+ else
66
+ # dashboard and panel is given, so set filter for panel
67
+ AlertsTableQuery.new(panel: @report.grafana(instance).dashboard(dashboard_id).panel(panel_id))
68
+ end
69
+
70
+ query.merge_hash_variables(doc.attributes, attrs)
71
+ selected_attrs = attrs.select do |k, _v|
72
+ k =~ /(?:columns|limit|folderId|dashboardId|panelId|dahboardTag|dashboardQuery|state|query)/x
73
+ end
74
+ query.merge_variables(selected_attrs.transform_values { |item| ::Grafana::Variable.new(item) })
75
+ @report.logger.debug("from: #{query.from}, to: #{query.to}")
76
+
77
+ begin
78
+ reader.unshift_lines query.execute(@report.grafana(instance))
79
+ rescue GrafanaReporterError => e
80
+ @report.logger.error(e.message)
81
+ reader.unshift_line "|#{e.message}"
82
+ rescue StandardError => e
83
+ @report.logger.fatal(e.message)
84
+ reader.unshift_line "|#{e.message}"
85
+ end
86
+
87
+ reader
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end