ruby-grafana-reporter 0.1.7 → 0.4.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +86 -245
  3. data/bin/ruby-grafana-reporter +3 -2
  4. data/lib/VERSION.rb +6 -3
  5. data/lib/grafana/abstract_datasource.rb +116 -0
  6. data/lib/grafana/dashboard.rb +75 -66
  7. data/lib/grafana/errors.rb +81 -61
  8. data/lib/grafana/grafana.rb +130 -131
  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 +25 -0
  12. data/lib/grafana/graphite_datasource.rb +44 -0
  13. data/lib/grafana/image_rendering_datasource.rb +44 -0
  14. data/lib/grafana/panel.rb +47 -39
  15. data/lib/grafana/prometheus_datasource.rb +39 -0
  16. data/lib/grafana/sql_datasource.rb +65 -0
  17. data/lib/grafana/variable.rb +218 -259
  18. data/lib/grafana/webrequest.rb +71 -0
  19. data/lib/grafana_reporter/abstract_query.rb +401 -0
  20. data/lib/grafana_reporter/abstract_report.rb +163 -109
  21. data/lib/grafana_reporter/alerts_table_query.rb +44 -0
  22. data/lib/grafana_reporter/annotations_table_query.rb +43 -0
  23. data/lib/grafana_reporter/application/application.rb +162 -229
  24. data/lib/grafana_reporter/application/errors.rb +33 -30
  25. data/lib/grafana_reporter/application/webservice.rb +242 -0
  26. data/lib/grafana_reporter/asciidoctor/alerts_table_include_processor.rb +90 -0
  27. data/lib/grafana_reporter/asciidoctor/annotations_table_include_processor.rb +89 -0
  28. data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +76 -0
  29. data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +77 -0
  30. data/lib/grafana_reporter/asciidoctor/panel_property_inline_macro.rb +72 -0
  31. data/lib/grafana_reporter/asciidoctor/panel_query_table_include_processor.rb +98 -0
  32. data/lib/grafana_reporter/asciidoctor/panel_query_value_inline_macro.rb +93 -0
  33. data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +23 -0
  34. data/lib/grafana_reporter/asciidoctor/report.rb +172 -159
  35. data/lib/grafana_reporter/asciidoctor/show_environment_include_processor.rb +46 -0
  36. data/lib/grafana_reporter/asciidoctor/show_help_include_processor.rb +35 -0
  37. data/lib/grafana_reporter/asciidoctor/sql_table_include_processor.rb +92 -0
  38. data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +88 -0
  39. data/lib/grafana_reporter/asciidoctor/value_as_variable_include_processor.rb +90 -0
  40. data/lib/grafana_reporter/configuration.rb +310 -326
  41. data/lib/grafana_reporter/console_configuration_wizard.rb +319 -0
  42. data/lib/grafana_reporter/demo_report_wizard.rb +87 -0
  43. data/lib/grafana_reporter/errors.rb +81 -38
  44. data/lib/grafana_reporter/help.rb +447 -0
  45. data/lib/grafana_reporter/logger/two_way_logger.rb +58 -52
  46. data/lib/grafana_reporter/panel_image_query.rb +29 -0
  47. data/lib/grafana_reporter/panel_property_query.rb +22 -0
  48. data/lib/grafana_reporter/query_value_query.rb +79 -0
  49. data/lib/grafana_reporter/report_webhook.rb +35 -0
  50. data/lib/{ruby-grafana-reporter.rb → ruby_grafana_reporter.rb} +29 -27
  51. metadata +48 -60
  52. data/lib/grafana/abstract_panel_query.rb +0 -20
  53. data/lib/grafana/abstract_query.rb +0 -127
  54. data/lib/grafana/abstract_sql_query.rb +0 -42
  55. data/lib/grafana/panel_image_query.rb +0 -49
  56. data/lib/grafana_reporter/asciidoctor/alerts_table_query.rb +0 -99
  57. data/lib/grafana_reporter/asciidoctor/annotations_table_query.rb +0 -96
  58. data/lib/grafana_reporter/asciidoctor/errors.rb +0 -37
  59. data/lib/grafana_reporter/asciidoctor/extensions/alerts_table_include_processor.rb +0 -86
  60. data/lib/grafana_reporter/asciidoctor/extensions/annotations_table_include_processor.rb +0 -86
  61. data/lib/grafana_reporter/asciidoctor/extensions/panel_image_block_macro.rb +0 -67
  62. data/lib/grafana_reporter/asciidoctor/extensions/panel_image_inline_macro.rb +0 -65
  63. data/lib/grafana_reporter/asciidoctor/extensions/panel_property_inline_macro.rb +0 -58
  64. data/lib/grafana_reporter/asciidoctor/extensions/panel_query_table_include_processor.rb +0 -75
  65. data/lib/grafana_reporter/asciidoctor/extensions/panel_query_value_inline_macro.rb +0 -70
  66. data/lib/grafana_reporter/asciidoctor/extensions/processor_mixin.rb +0 -18
  67. data/lib/grafana_reporter/asciidoctor/extensions/show_environment_include_processor.rb +0 -41
  68. data/lib/grafana_reporter/asciidoctor/extensions/show_help_include_processor.rb +0 -202
  69. data/lib/grafana_reporter/asciidoctor/extensions/sql_table_include_processor.rb +0 -67
  70. data/lib/grafana_reporter/asciidoctor/extensions/sql_value_inline_macro.rb +0 -65
  71. data/lib/grafana_reporter/asciidoctor/extensions/value_as_variable_include_processor.rb +0 -57
  72. data/lib/grafana_reporter/asciidoctor/panel_first_value_query.rb +0 -32
  73. data/lib/grafana_reporter/asciidoctor/panel_image_query.rb +0 -23
  74. data/lib/grafana_reporter/asciidoctor/panel_property_query.rb +0 -43
  75. data/lib/grafana_reporter/asciidoctor/panel_table_query.rb +0 -36
  76. data/lib/grafana_reporter/asciidoctor/query_mixin.rb +0 -309
  77. data/lib/grafana_reporter/asciidoctor/sql_first_value_query.rb +0 -34
  78. data/lib/grafana_reporter/asciidoctor/sql_table_query.rb +0 -32
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'processor_mixin'
4
+
5
+ module GrafanaReporter
6
+ module Asciidoctor
7
+ # Implements the hook
8
+ # grafana_panel_image::<panel_id>[<options>]
9
+ #
10
+ # Stores the queried panel as a temporary image file and returns an asciidoctor link
11
+ # to be included in the report.
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
+ # +field+ - property to query for, e.g. +description+ or +title+ (*mandatory*)
24
+ #
25
+ # +instance+ - name of grafana instance, 'default' if not specified
26
+ #
27
+ # +dashboard+ - uid of grafana dashboard to use
28
+ #
29
+ # +from+ - 'from' time for the sql query
30
+ #
31
+ # +to+ - 'to' time for the sql query
32
+ class PanelImageBlockMacro < ::Asciidoctor::Extensions::BlockMacroProcessor
33
+ include ProcessorMixin
34
+ use_dsl
35
+
36
+ named :grafana_panel_image
37
+
38
+ # :nodoc:
39
+ def process(parent, target, attrs)
40
+ return if @report.cancel
41
+
42
+ @report.next_step
43
+ instance = attrs['instance'] || parent.document.attr('grafana_default_instance') || 'default'
44
+ dashboard = attrs['dashboard'] || parent.document.attr('grafana_default_dashboard')
45
+ @report.logger.debug("Processing PanelImageBlockMacro (instance: #{instance}, dashboard: #{dashboard},"\
46
+ " panel: #{target})")
47
+
48
+ begin
49
+ query = PanelImageQuery.new(@report.grafana(instance).dashboard(dashboard).panel(target))
50
+ query.merge_hash_variables(parent.document.attributes, attrs)
51
+ @report.logger.debug("from: #{query.from}, to: #{query.to}")
52
+
53
+ image = query.execute
54
+ image_path = @report.save_image_file(image)
55
+ rescue GrafanaReporterError => e
56
+ @report.logger.error(e.message)
57
+ return create_paragraph(parent, e.message, attrs)
58
+ rescue StandardError => e
59
+ @report.logger.fatal(e.message)
60
+ return create_paragraph(parent, e.message, attrs)
61
+ end
62
+
63
+ attrs['target'] = image_path
64
+ create_image_block(parent, attrs)
65
+ end
66
+
67
+ # @see ProcessorMixin#build_demo_entry
68
+ def build_demo_entry(panel)
69
+ return nil unless panel
70
+ return nil unless panel.model['type'] == 'graph'
71
+
72
+ "grafana_panel_image::#{panel.id}[dashboard=\"#{panel.dashboard.id}\",width=\"50%\"]"
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'processor_mixin'
4
+
5
+ module GrafanaReporter
6
+ module Asciidoctor
7
+ # Implements the hook
8
+ # grafana_panel_image:<panel_id>[<options>]
9
+ #
10
+ # Stores the queried panel as a temporary image file and returns an asciidoctor link
11
+ # to be included in the report.
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
+ # +field+ - property to query for, e.g. +description+ or +title+ (*mandatory*)
24
+ #
25
+ # +instance+ - name of grafana instance, 'default' if not specified
26
+ #
27
+ # +dashboard+ - uid of grafana dashboard to use
28
+ #
29
+ # +from+ - 'from' time for the sql query
30
+ #
31
+ # +to+ - 'to' time for the sql query
32
+ class PanelImageInlineMacro < ::Asciidoctor::Extensions::InlineMacroProcessor
33
+ include ProcessorMixin
34
+ use_dsl
35
+
36
+ named :grafana_panel_image
37
+
38
+ # :nodoc:
39
+ def process(parent, target, attrs)
40
+ return if @report.cancel
41
+
42
+ @report.next_step
43
+ instance = attrs['instance'] || parent.document.attr('grafana_default_instance') || 'default'
44
+ dashboard = attrs['dashboard'] || parent.document.attr('grafana_default_dashboard')
45
+ @report.logger.debug("Processing PanelImageInlineMacro (instance: #{instance}, dashboard: #{dashboard},"\
46
+ " panel: #{target})")
47
+ query = PanelImageQuery.new(@report.grafana(instance).dashboard(dashboard).panel(target))
48
+ # set alt text to a default, because otherwise asciidoctor fails
49
+ attrs['alt'] = '' unless attrs['alt']
50
+ query.merge_hash_variables(parent.document.attributes, attrs)
51
+ @report.logger.debug("from: #{query.from}, to: #{query.to}")
52
+
53
+ begin
54
+ image = query.execute
55
+ image_path = @report.save_image_file(image)
56
+ rescue GrafanaReporterError => e
57
+ @report.logger.error(e.message)
58
+ return create_inline(parent, :quoted, e.message)
59
+ rescue StandardError => e
60
+ @report.logger.fatal(e.message)
61
+ return create_inline(parent, :quoted, e.message)
62
+ end
63
+
64
+ create_inline(parent, :image, nil, { target: image_path, attributes: attrs })
65
+ end
66
+
67
+ # @see ProcessorMixin#build_demo_entry
68
+ def build_demo_entry(panel)
69
+ return nil unless panel
70
+ return nil unless panel.model['type'] == 'graph'
71
+
72
+ "see here: grafana_panel_image:#{panel.id}[dashboard=\"#{panel.dashboard.id}\","\
73
+ 'width="90%"] - a working inline image'
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'processor_mixin'
4
+
5
+ module GrafanaReporter
6
+ module Asciidoctor
7
+ # Implements the hook
8
+ # grafana_panel_property:<panel_id>[<options>]
9
+ #
10
+ # Returns the requested panel property.
11
+ #
12
+ # == Used document parameters
13
+ # +grafana_default_instance+ - name of grafana instance, 'default' if not specified
14
+ #
15
+ # +grafana_default_dashboard+ - uid of grafana default dashboard to use
16
+ #
17
+ # == Supported options
18
+ # +field+ - property to query for, e.g. +description+ or +title+ (*mandatory*)
19
+ #
20
+ # +instance+ - name of grafana instance, 'default' if not specified
21
+ #
22
+ # +dashboard+ - uid of grafana dashboard to use
23
+ class PanelPropertyInlineMacro < ::Asciidoctor::Extensions::InlineMacroProcessor
24
+ include ProcessorMixin
25
+ use_dsl
26
+
27
+ named :grafana_panel_property
28
+ name_positional_attributes :field
29
+
30
+ # :nodoc:
31
+ def process(parent, target, attrs)
32
+ return if @report.cancel
33
+
34
+ @report.next_step
35
+ instance = attrs['instance'] || parent.document.attr('grafana_default_instance') || 'default'
36
+ dashboard = attrs['dashboard'] || parent.document.attr('grafana_default_dashboard')
37
+ @report.logger.debug("Processing PanelPropertyInlineMacro (instance: #{instance}, dashboard: #{dashboard},"\
38
+ " panel: #{target}, property: #{attrs[:field]})")
39
+
40
+ begin
41
+ query = PanelPropertyQuery.new(@report.grafana(instance).dashboard(dashboard).panel(target))
42
+ query.raw_query = { property_name: attrs[:field] }
43
+ query.merge_hash_variables(parent.document.attributes, attrs)
44
+ @report.logger.debug("from: #{query.from}, to: #{query.to}")
45
+
46
+ description = query.execute
47
+ rescue GrafanaReporterError => e
48
+ @report.logger.error(e.message)
49
+ return create_inline(parent, :quoted, e.message)
50
+ rescue StandardError => e
51
+ @report.logger.fatal(e.message)
52
+ return create_inline(parent, :quoted, e.message)
53
+ end
54
+
55
+ # translate linebreaks to asciidoctor syntax
56
+ # and HTML encode to make sure, that HTML formattings are respected
57
+ create_inline(parent, :quoted, CGI.escapeHTML(description.gsub(%r{//[^\n]*(?:\n)?}, '').gsub(/\n/, " +\n")))
58
+ end
59
+
60
+ # @see ProcessorMixin#build_demo_entry
61
+ def build_demo_entry(panel)
62
+ return nil unless panel
63
+ return nil unless panel.model['title']
64
+ return nil if panel.model['title'].strip == ''
65
+ return nil if panel.model['title'].strip == 'Panel Title'
66
+
67
+ "this text includes the panel with title grafana_panel_property:#{panel.id}[\"title\","\
68
+ "dashboard=\"#{panel.dashboard.id}\"]"
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'processor_mixin'
4
+
5
+ module GrafanaReporter
6
+ module Asciidoctor
7
+ # Implements the hook
8
+ # include::grafana_panel_query_table:<panel_id>[<options>]
9
+ #
10
+ # Returns the results of the SQL query as a asciidoctor table.
11
+ #
12
+ # == Used document parameters
13
+ # +grafana_default_instance+ - name of grafana instance, 'default' if not specified
14
+ #
15
+ # +grafana_default_dashboard+ - uid of grafana default dashboard to use
16
+ #
17
+ # +from+ - 'from' time for the sql query
18
+ #
19
+ # +to+ - 'to' time for the sql query
20
+ #
21
+ # All other variables starting with +var-+ will be used to replace grafana templating strings
22
+ # in the given SQL query.
23
+ #
24
+ # == Supported options
25
+ # +query+ - query letter, which shall be used, e.g. +C+ (*mandatory*)
26
+ #
27
+ # +instance+ - name of grafana instance, 'default' if not specified
28
+ #
29
+ # +dashboard+ - uid of grafana dashboard to use
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 PanelQueryTableIncludeProcessor < ::Asciidoctor::Extensions::IncludeProcessor
41
+ include ProcessorMixin
42
+
43
+ # :nodoc:
44
+ def handles?(target)
45
+ target.start_with? 'grafana_panel_query_table:'
46
+ end
47
+
48
+ # :nodoc:
49
+ def process(doc, reader, target, attrs)
50
+ return if @report.cancel
51
+
52
+ @report.next_step
53
+ panel_id = target.split(':')[1]
54
+ instance = attrs['instance'] || doc.attr('grafana_default_instance') || 'default'
55
+ dashboard = attrs['dashboard'] || doc.attr('grafana_default_dashboard')
56
+ attrs['result_type'] = 'panel_table'
57
+ @report.logger.debug("Processing PanelQueryTableIncludeProcessor (instance: #{instance}, "\
58
+ "dashboard: #{dashboard}, panel: #{panel_id}, query: #{attrs['query']})")
59
+
60
+ begin
61
+ panel = @report.grafana(instance).dashboard(dashboard).panel(panel_id)
62
+ query = QueryValueQuery.new(panel)
63
+ query.set_defaults_from_dashboard(panel.dashboard)
64
+ query.merge_hash_variables(doc.attributes, attrs)
65
+ @report.logger.debug("from: #{query.from}, to: #{query.to}")
66
+
67
+ reader.unshift_lines query.execute
68
+ rescue GrafanaReporterError => e
69
+ @report.logger.error(e.message)
70
+ reader.unshift_line "|#{e.message}"
71
+ rescue StandardError => e
72
+ @report.logger.fatal(e.message)
73
+ reader.unshift_line "|#{e.message}"
74
+ end
75
+
76
+ reader
77
+ end
78
+
79
+ # @see ProcessorMixin#build_demo_entry
80
+ def build_demo_entry(panel)
81
+ return nil unless panel
82
+ return nil unless panel.model['type'].include?('table')
83
+
84
+ ref_id = nil
85
+ panel.model['targets'].each do |item|
86
+ if !item['hide'] && !panel.query(item['refId']).to_s.empty?
87
+ ref_id = item['refId']
88
+ break
89
+ end
90
+ end
91
+ return nil unless ref_id
92
+
93
+ "|===\ninclude::grafana_panel_query_table:#{panel.id}[query=\"#{ref_id}\",filter_columns=\"time\","\
94
+ "dashboard=\"#{panel.dashboard.id}\"]\n|==="
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'processor_mixin'
4
+
5
+ module GrafanaReporter
6
+ module Asciidoctor
7
+ # Implements the hook
8
+ # grafana_panel_query_value:<panel_id>[<options>]
9
+ #
10
+ # Returns the first value of the resulting SQL query.
11
+ #
12
+ # == Used document parameters
13
+ # +grafana_default_instance+ - name of grafana instance, 'default' if not specified
14
+ #
15
+ # +grafana_default_dashboard+ - uid of grafana default dashboard to use
16
+ #
17
+ # +from+ - 'from' time for the sql query
18
+ #
19
+ # +to+ - 'to' time for the sql query
20
+ #
21
+ # All other variables starting with +var-+ will be used to replace grafana templating strings
22
+ # in the given SQL query.
23
+ #
24
+ # == Supported options
25
+ # +query+ - query letter, which shall be used, e.g. +C+ (*mandatory*)
26
+ #
27
+ # +instance+ - name of grafana instance, 'default' if not specified
28
+ #
29
+ # +dashboard+ - uid of grafana dashboard to use
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 PanelQueryValueInlineMacro < ::Asciidoctor::Extensions::InlineMacroProcessor
41
+ include ProcessorMixin
42
+ use_dsl
43
+
44
+ named :grafana_panel_query_value
45
+
46
+ # :nodoc:
47
+ def process(parent, target, attrs)
48
+ return if @report.cancel
49
+
50
+ @report.next_step
51
+ instance = attrs['instance'] || parent.document.attr('grafana_default_instance') || 'default'
52
+ dashboard = attrs['dashboard'] || parent.document.attr('grafana_default_dashboard')
53
+ attrs['result_type'] = 'panel_value'
54
+ @report.logger.debug("Processing PanelQueryValueInlineMacro (instance: #{instance}, dashboard: #{dashboard},"\
55
+ " panel: #{target}, query: #{attrs['query']})")
56
+
57
+ begin
58
+ panel = @report.grafana(instance).dashboard(dashboard).panel(target)
59
+ query = QueryValueQuery.new(panel)
60
+ query.set_defaults_from_dashboard(panel.dashboard)
61
+ query.merge_hash_variables(parent.document.attributes, attrs)
62
+ @report.logger.debug("from: #{query.from}, to: #{query.to}")
63
+
64
+ create_inline(parent, :quoted, query.execute)
65
+ rescue GrafanaReporterError => e
66
+ @report.logger.error(e.message)
67
+ create_inline(parent, :quoted, e.message)
68
+ rescue StandardError => e
69
+ @report.logger.fatal(e.message)
70
+ create_inline(parent, :quoted, e.message)
71
+ end
72
+ end
73
+
74
+ # @see ProcessorMixin#build_demo_entry
75
+ def build_demo_entry(panel)
76
+ return nil unless panel
77
+ return nil unless panel.model['type'] == 'singlestat'
78
+
79
+ ref_id = nil
80
+ panel.model['targets'].each do |item|
81
+ if !item['hide'] && !panel.query(item['refId']).to_s.empty?
82
+ ref_id = item['refId']
83
+ break
84
+ end
85
+ end
86
+ return nil unless ref_id
87
+
88
+ "it's easily possible to include the query value: grafana_panel_query_value:#{panel.id}[query=\"#{ref_id}\""\
89
+ ",dashboard=\"#{panel.dashboard.id}\"] - just within this text."
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrafanaReporter
4
+ module Asciidoctor
5
+ # This module contains common methods for all asciidoctor extensions.
6
+ module ProcessorMixin
7
+ # Used when initializing a object instance, to set the report object, which is currently in progress.
8
+ # @param report [GrafanaReporter::Asciidoctor::Report] current report
9
+ # @return [::Asciidoctor::Extensions::Processor] self
10
+ def current_report(report)
11
+ @report = report
12
+ self
13
+ end
14
+
15
+ # This method is called if a demo report shall be built for the given {Panel}.
16
+ # @param panel [Panel] panel object, for which a demo entry shall be created.
17
+ # @return [String] String containing the entry, or nil if not possible for given panel
18
+ def build_demo_entry(panel)
19
+ raise NotImplementedError
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,159 +1,172 @@
1
- module GrafanaReporter
2
- module Asciidoctor
3
- # Implementation of a specific {AbstractReport}. It is used to
4
- # build reports specifically for asciidoctor results.
5
- class Report < GrafanaReporter::AbstractReport
6
- # (see AbstractReport#initialize)
7
- def initialize(config, template, destination_file_or_path = nil, custom_attributes = {})
8
- super
9
- @current_pos = 0
10
- @image_files = []
11
- @grafana_instances = {}
12
- end
13
-
14
- # Starts to create an asciidoctor report. It utilizes all {Extensions} to
15
- # realize the conversion.
16
- # @see AbstractReport#create_report
17
- # @return [void]
18
- def create_report
19
- @start_time = Time.now
20
- attrs = {'convert-backend' => 'pdf'}.merge(@config.default_document_attributes.merge(@custom_attributes))
21
- attrs['grafana-report-timestamp'] = @start_time.to_s
22
- logger.info('Report started at ' + @start_time.to_s)
23
- logger.debug('Document attributes: ' + attrs.to_s)
24
-
25
- initialize_step_counter
26
-
27
- # register necessary extensions for the current report
28
- ::Asciidoctor::LoggerManager.logger = logger
29
-
30
- registry = ::Asciidoctor::Extensions::Registry.new
31
- #TODO dynamically register macros, which is also needed when supporting custom macros
32
- registry.inline_macro Extensions::PanelImageInlineMacro.new.current_report(self)
33
- registry.inline_macro Extensions::PanelQueryValueInlineMacro.new.current_report(self)
34
- registry.inline_macro Extensions::PanelPropertyInlineMacro.new.current_report(self)
35
- registry.inline_macro Extensions::SqlValueInlineMacro.new.current_report(self)
36
- registry.block_macro Extensions::PanelImageBlockMacro.new.current_report(self)
37
- registry.include_processor Extensions::ValueAsVariableIncludeProcessor.new.current_report(self)
38
- registry.include_processor Extensions::PanelQueryTableIncludeProcessor.new.current_report(self)
39
- registry.include_processor Extensions::SqlTableIncludeProcessor.new.current_report(self)
40
- registry.include_processor Extensions::ShowEnvironmentIncludeProcessor.new.current_report(self)
41
- registry.include_processor Extensions::ShowHelpIncludeProcessor.new.current_report(self)
42
- registry.include_processor Extensions::AnnotationsTableIncludeProcessor.new.current_report(self)
43
- registry.include_processor Extensions::AlertsTableIncludeProcessor.new.current_report(self)
44
-
45
- ::Asciidoctor.convert_file(@template, extension_registry: registry, backend: attrs['convert-backend'], to_file: path, attributes: attrs, header_footer: true)
46
-
47
- @destination_file_or_path.close if @destination_file_or_path.is_a?(File)
48
-
49
- # store report including als images as ZIP file, if the result is not a PDF
50
- # TODO add tests for zipping results
51
- if attrs['convert-backend'] != 'pdf'
52
- dest_path = @destination_file_or_path
53
- dest_path = @destination_file_or_path.path if @destination_file_or_path.is_a?(File)
54
-
55
- # build zip file
56
- zip_file = Tempfile.new("gf_zip")
57
- Zip::File.open(zip_file.path, Zip::File::CREATE) do |zipfile|
58
- # add report file
59
- zipfile.get_output_stream(dest_path.gsub(@config.reports_folder, '') + ".#{attrs['convert-backend']}") { |f| f.puts File.read(dest_path) }
60
-
61
- # add image files
62
- @image_files.each do |file|
63
- zipfile.get_output_stream(file.path.gsub(@config.images_folder, '')) { |f| f.puts File.read(file.path) }
64
- end
65
- end
66
-
67
- # replace original file with zip file
68
- zip_file.rewind
69
- begin
70
- File.write(dest_path, zip_file.read)
71
- rescue => e
72
- logger.fatal("Could not overwrite report file '#{dest_path}' with ZIP file. (#{e.message}).")
73
- end
74
-
75
- # cleanup temporary zip file
76
- zip_file.close
77
- zip_file.unlink
78
- end
79
-
80
- clean_image_files
81
- @end_time = Time.now
82
- logger.info('Report finished after ' + (@end_time - @start_time).to_s + ' seconds.')
83
- @done = true
84
- rescue StandardError => e
85
- # catch all errors during execution
86
- died_with_error(e)
87
- raise e
88
- end
89
-
90
- # @see AbstractReport#progress
91
- # @return [Float] number between 0 and 1 reflecting the current progress.
92
- def progress
93
- return 0 if @total_steps.to_i.zero?
94
-
95
- @current_pos.to_f / @total_steps
96
- end
97
-
98
- # @param instance [String] requested grafana instance
99
- # @return [Grafana::Grafana] the requested grafana instance.
100
- def grafana(instance)
101
- unless @grafana_instances[instance]
102
- @grafana_instances[instance] = ::Grafana::Grafana.new(@config.grafana_host(instance), @config.grafana_api_key(instance), logger: @logger, datasources: @config.grafana_datasources(instance))
103
- end
104
- @grafana_instances[instance]
105
- end
106
-
107
- # Increments the progress.
108
- # @return [Integer] number of the current progress position.
109
- def next_step
110
- @current_pos += 1
111
- @current_pos
112
- end
113
-
114
- # Called to save a temporary image file. After the final generation of the
115
- # report, these temporary files will automatically be removed.
116
- # @param img_data [String] image file raw data, which shall be saved
117
- # @return [String] path to the temporary file.
118
- def save_image_file(img_data)
119
- file = Tempfile.new(['gf_image_', '.png'], @config.images_folder.to_s)
120
- file.write(img_data)
121
- path = file.path.gsub(/#{@config.images_folder}/, '')
122
-
123
- @image_files << file
124
- file.close
125
-
126
- path
127
- end
128
-
129
- # Called, if the report generation has died with an error.
130
- # @param e [StandardError] occured error
131
- # @return [void]
132
- def died_with_error(e)
133
- @error = [e.message] << [e.backtrace]
134
- @end_time = Time.now
135
- @done = true
136
- end
137
-
138
- private
139
-
140
- def clean_image_files
141
- @image_files.each(&:unlink)
142
- @image_files = []
143
- end
144
-
145
- def initialize_step_counter
146
- @total_steps = 0
147
- File.readlines(@template).each do |line|
148
- begin
149
- @total_steps += line.gsub(%r{//.*}, '').scan(/(?:grafana_panel_image|grafana_panel_query_value|grafana_panel_query_table|grafana_sql_value|grafana_sql_table|grafana_environment|grafana_help|grafana_panel_property|grafana_annotations|grafana_alerts|grafana_value_as_variable)/).length
150
- rescue => e
151
- logger.error("Could not process line '#{line}' (Error: #{e.message})")
152
- raise e
153
- end
154
- end
155
- logger.debug("Template #{@template} contains #{@total_steps.to_s} calls of grafana reporter functions.")
156
- end
157
- end
158
- end
159
- end
1
+ # frozen_string_literal: true
2
+
3
+ module GrafanaReporter
4
+ # This module contains all classes, which are necessary to use the reporter in conjunction with asciidoctor.
5
+ module Asciidoctor
6
+ # Implementation of a specific {AbstractReport}. It is used to
7
+ # build reports specifically for asciidoctor results.
8
+ class Report < ::GrafanaReporter::AbstractReport
9
+ # (see AbstractReport#initialize)
10
+ def initialize(config, template, destination_file_or_path = nil, custom_attributes = {})
11
+ super
12
+ @current_pos = 0
13
+ @image_files = []
14
+ @grafana_instances = {}
15
+ end
16
+
17
+ # Starts to create an asciidoctor report. It utilizes all {Extensions} to
18
+ # realize the conversion.
19
+ # @see AbstractReport#create_report
20
+ # @return [void]
21
+ def create_report
22
+ super
23
+ attrs = { 'convert-backend' => 'pdf' }.merge(@config.default_document_attributes.merge(@custom_attributes))
24
+ attrs['grafana-report-timestamp'] = @start_time.to_s
25
+ logger.debug("Document attributes: #{attrs}")
26
+
27
+ initialize_step_counter
28
+
29
+ # register necessary extensions for the current report
30
+ ::Asciidoctor::LoggerManager.logger = logger
31
+
32
+ registry = ::Asciidoctor::Extensions::Registry.new
33
+ registry.inline_macro PanelImageInlineMacro.new.current_report(self)
34
+ registry.inline_macro PanelQueryValueInlineMacro.new.current_report(self)
35
+ registry.inline_macro PanelPropertyInlineMacro.new.current_report(self)
36
+ registry.inline_macro SqlValueInlineMacro.new.current_report(self)
37
+ registry.block_macro PanelImageBlockMacro.new.current_report(self)
38
+ registry.include_processor ValueAsVariableIncludeProcessor.new.current_report(self)
39
+ registry.include_processor PanelQueryTableIncludeProcessor.new.current_report(self)
40
+ registry.include_processor SqlTableIncludeProcessor.new.current_report(self)
41
+ registry.include_processor ShowEnvironmentIncludeProcessor.new.current_report(self)
42
+ registry.include_processor ShowHelpIncludeProcessor.new.current_report(self)
43
+ registry.include_processor AnnotationsTableIncludeProcessor.new.current_report(self)
44
+ registry.include_processor AlertsTableIncludeProcessor.new.current_report(self)
45
+
46
+ ::Asciidoctor.convert_file(@template, extension_registry: registry, backend: attrs['convert-backend'],
47
+ to_file: path, attributes: attrs, header_footer: true)
48
+
49
+ @destination_file_or_path.close if @destination_file_or_path.is_a?(File)
50
+
51
+ # store report including als images as ZIP file, if the result is not a PDF
52
+ if attrs['convert-backend'] != 'pdf'
53
+ dest_path = if @destination_file_or_path.is_a?(File) || @destination_file_or_path.is_a?(Tempfile)
54
+ @destination_file_or_path.path
55
+ else
56
+ @destination_file_or_path
57
+ end
58
+
59
+ # build zip file
60
+ zip_file = Tempfile.new('gf_zip')
61
+ buffer = Zip::OutputStream.write_buffer do |zipfile|
62
+ # add report file
63
+ zipfile.put_next_entry("#{dest_path.gsub(@config.reports_folder, '')}.#{attrs['convert-backend']}")
64
+ zipfile.write File.read(dest_path)
65
+
66
+ # add image files
67
+ @image_files.each do |file|
68
+ zipfile.put_next_entry(file.path.gsub(@config.images_folder, ''))
69
+ zipfile.write File.read(file.path)
70
+ end
71
+ end
72
+ File.open(zip_file, 'wb') do |f|
73
+ f.write buffer.string
74
+ end
75
+
76
+ # replace original file with zip file
77
+ zip_file.rewind
78
+ begin
79
+ File.write(dest_path, zip_file.read)
80
+ rescue StandardError => e
81
+ logger.fatal("Could not overwrite report file '#{dest_path}' with ZIP file. (#{e.message}).")
82
+ end
83
+
84
+ # cleanup temporary zip file
85
+ zip_file.close
86
+ zip_file.unlink
87
+ end
88
+
89
+ clean_image_files
90
+ done!
91
+ rescue StandardError => e
92
+ # catch all errors during execution
93
+ died_with_error(e)
94
+ raise e
95
+ end
96
+
97
+ # @see AbstractReport#progress
98
+ # @return [Float] number between 0 and 1 reflecting the current progress.
99
+ def progress
100
+ return 0 if @total_steps.to_i.zero?
101
+
102
+ @current_pos.to_f / @total_steps
103
+ end
104
+
105
+ # @param instance [String] requested grafana instance
106
+ # @return [Grafana::Grafana] the requested grafana instance.
107
+ def grafana(instance)
108
+ unless @grafana_instances[instance]
109
+ @grafana_instances[instance] = ::Grafana::Grafana.new(@config.grafana_host(instance),
110
+ @config.grafana_api_key(instance),
111
+ logger: @logger)
112
+ end
113
+ @grafana_instances[instance]
114
+ end
115
+
116
+ # Increments the progress.
117
+ # @return [Integer] number of the current progress position.
118
+ def next_step
119
+ @current_pos += 1
120
+ @current_pos
121
+ end
122
+
123
+ # Called to save a temporary image file. After the final generation of the
124
+ # report, these temporary files will automatically be removed.
125
+ # @param img_data [String] image file raw data, which shall be saved
126
+ # @return [String] path to the temporary file.
127
+ def save_image_file(img_data)
128
+ file = Tempfile.new(['gf_image_', '.png'], @config.images_folder.to_s)
129
+ file.write(img_data)
130
+ path = file.path.gsub(/#{@config.images_folder}/, '')
131
+
132
+ @image_files << file
133
+ file.close
134
+
135
+ path
136
+ end
137
+
138
+ # Called, if the report generation has died with an error.
139
+ # @param error [StandardError] occured error
140
+ # @return [void]
141
+ def died_with_error(error)
142
+ @error = [error.message] << [error.backtrace]
143
+ done!
144
+ end
145
+
146
+ private
147
+
148
+ def clean_image_files
149
+ @image_files.each(&:unlink)
150
+ @image_files = []
151
+ end
152
+
153
+ def initialize_step_counter
154
+ @total_steps = 0
155
+ File.readlines(@template).each do |line|
156
+ begin
157
+ # TODO: move these calls to the specific processors to ensure all are counted properly
158
+ @total_steps += line.gsub(%r{//.*}, '').scan(/(?:grafana_panel_image|grafana_panel_query_value|
159
+ grafana_panel_query_table|grafana_sql_value|
160
+ grafana_sql_table|grafana_environment|grafana_help|
161
+ grafana_panel_property|grafana_annotations|grafana_alerts|
162
+ grafana_value_as_variable)/x).length
163
+ rescue StandardError => e
164
+ logger.error("Could not process line '#{line}' (Error: #{e.message})")
165
+ raise e
166
+ end
167
+ end
168
+ logger.debug("Template #{@template} contains #{@total_steps} calls of grafana reporter functions.")
169
+ end
170
+ end
171
+ end
172
+ end