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
@@ -1,30 +1,33 @@
1
- module GrafanaReporter
2
- module Application
3
- # General grafana application error, from which the specific errors
4
- # inherit.
5
- class ApplicationError < GrafanaReporterError
6
- end
7
-
8
- # Thrown if a non existing template has been specified.
9
- class MissingTemplateError < ApplicationError
10
- def initialize(template)
11
- super("Given report template '#{template}' is not a valid template.")
12
- end
13
- end
14
-
15
- # Thrown, if a webservice request has been requested, which could not be
16
- # handled.
17
- class WebserviceUnknownPathError < ApplicationError
18
- def initialize(request)
19
- super("Request '#{request}' calls an unknown path for this webservice.")
20
- end
21
- end
22
-
23
- # Thrown, if an internal error appeared during creation of the report.
24
- class WebserviceGeneralRenderingError < ApplicationError
25
- def initialize(error)
26
- super("Could not render report because of internal error: #{error}")
27
- end
28
- end
29
- end
30
- end
1
+ # frozen_string_literal: true
2
+
3
+ module GrafanaReporter
4
+ module Application
5
+ # General grafana application error, from which the specific errors
6
+ # inherit.
7
+ class ApplicationError < GrafanaReporterError
8
+ end
9
+
10
+ # Thrown, if the '-s' parameter is not configured with exactly one variable
11
+ # name and one value.
12
+ class ParameterValueError < ApplicationError
13
+ def initialize(length)
14
+ super("Parameter '-s' needs exactly two values separated by comma, received #{length}.")
15
+ end
16
+ end
17
+
18
+ # Thrown, if a webservice request has been requested, which could not be
19
+ # handled.
20
+ class WebserviceUnknownPathError < ApplicationError
21
+ def initialize(request)
22
+ super("Request '#{request}' calls an unknown path for this webservice.")
23
+ end
24
+ end
25
+
26
+ # Thrown, if an internal error appeared during creation of the report.
27
+ class WebserviceGeneralRenderingError < ApplicationError
28
+ def initialize(error)
29
+ super("Could not render report because of internal error: #{error}")
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrafanaReporter
4
+ module Application
5
+ # This class provides the webservice for the reporter application. It does not
6
+ # make use of `webrick` or similar, so that it can be used without futher dependencies
7
+ # in conjunction with the standard asciidoctor docker container.
8
+ class Webservice
9
+ def initialize
10
+ @reports = []
11
+ @running = false
12
+ end
13
+
14
+ # Runs the webservice with the given {Configuration} object.
15
+ def run(config)
16
+ @config = config
17
+ @logger = config.logger
18
+
19
+ # start webserver
20
+ @server = TCPServer.new(@config.webserver_port)
21
+ @logger.info("Server listening on port #{@config.webserver_port}...")
22
+ @running = true
23
+
24
+ @progress_reporter = Thread.new {}
25
+
26
+ accept_requests_loop
27
+ @running = false
28
+ end
29
+
30
+ # @return True, if webservice is up and running, false otherwise
31
+ def running?
32
+ @running
33
+ end
34
+
35
+ private
36
+
37
+ def accept_requests_loop
38
+ loop do
39
+ # step 1) accept incoming connection
40
+ socket = @server.accept
41
+
42
+ # step 2) print the request headers (separated by a blank line e.g. \r\n)
43
+ request = ''
44
+ line = ''
45
+ begin
46
+ until line == "\r\n"
47
+ line = socket.readline
48
+ request += line
49
+ end
50
+ rescue EOFError => e
51
+ @logger.debug("Webserver EOFError: #{e.message}")
52
+ end
53
+
54
+ begin
55
+ response = handle_request(request)
56
+ socket.write response
57
+ rescue WebserviceUnknownPathError => e
58
+ @logger.debug(e.message)
59
+ socket.write http_response(404, '', e.message)
60
+ rescue MissingTemplateError => e
61
+ @logger.error(e.message)
62
+ socket.write http_response(400, 'Bad Request', e.message)
63
+ rescue WebserviceGeneralRenderingError => e
64
+ @logger.fatal(e.message)
65
+ socket.write http_response(400, 'Bad Request', e.message)
66
+ rescue StandardError => e
67
+ @logger.fatal(e.message)
68
+ socket.write http_response(400, 'Bad Request', e.message)
69
+ ensure
70
+ socket.close
71
+ end
72
+
73
+ log_report_progress
74
+ clean_outdated_temporary_reports
75
+ end
76
+ end
77
+
78
+ def log_report_progress
79
+ return if @progress_reporter.alive?
80
+
81
+ @progress_reporter = Thread.new do
82
+ running_reports = @reports.reject(&:done)
83
+ until running_reports.empty?
84
+ unless running_reports.empty?
85
+ @logger.info("#{running_reports.length} report(s) in progress: "\
86
+ "#{running_reports.map do |report|
87
+ "#{(report.progress * 100).to_i}% (running #{report.execution_time.to_i} secs)"
88
+ end.join(', ')}")
89
+ end
90
+ sleep 5
91
+ running_reports = @reports.reject(&:done)
92
+ end
93
+ # puts "no more running reports - stopping to report progress"
94
+ end
95
+ end
96
+
97
+ def clean_outdated_temporary_reports
98
+ clean_time = Time.now - 60 * 60 * @config.report_retention
99
+ @reports.select { |report| report.done && clean_time > report.end_time }.each do |report|
100
+ @reports.delete(report).delete_file
101
+ end
102
+ end
103
+
104
+ def handle_request(request)
105
+ raise WebserviceUnknownPathError, request.split("\r\n")[0] if request.nil?
106
+ raise WebserviceUnknownPathError, request.split("\r\n")[0] if request.split("\r\n")[0].nil?
107
+
108
+ query_string = request.split("\r\n")[0].gsub(%r{(?:[^?]+\?)(.*)(?: HTTP/.*)$}, '\1')
109
+ query_parameters = CGI.parse(query_string)
110
+
111
+ @logger.debug("Received request: #{request.split("\r\n")[0]}")
112
+ @logger.debug("query_parameters: #{query_parameters}")
113
+
114
+ # read URL parameters
115
+ attrs = {}
116
+ query_parameters.each do |k, v|
117
+ attrs[k] = v.length == 1 ? v[0] : v
118
+ end
119
+
120
+ case request.split("\r\n")[0]
121
+ when %r{^GET /render[? ]}
122
+ return render_report(attrs)
123
+
124
+ when %r{^GET /overview[? ]}
125
+ # show overview for current reports
126
+ return get_reports_status_as_html(@reports)
127
+
128
+ when %r{^GET /view_report[? ]}
129
+ return view_report(attrs)
130
+
131
+ when %r{^GET /cancel_report[? ]}
132
+ return cancel_report(attrs)
133
+
134
+ when %r{^GET /view_log[? ]}
135
+ return view_log(attrs)
136
+ end
137
+
138
+ raise WebserviceUnknownPathError, request.split("\r\n")[0]
139
+ end
140
+
141
+ def view_log(attrs)
142
+ # view report if already available, or show status view
143
+ report = @reports.select { |r| r.object_id.to_s == attrs['report_id'].to_s }.first
144
+ raise WebserviceGeneralRenderingError, 'view_log has been called without valid id' if report.nil?
145
+
146
+ content = report.full_log
147
+
148
+ http_response(200, 'OK', content, "Content-Type": 'text/plain')
149
+ end
150
+
151
+ def cancel_report(attrs)
152
+ # view report if already available, or show status view
153
+ report = @reports.select { |r| r.object_id.to_s == attrs['report_id'].to_s }.first
154
+ raise WebserviceGeneralRenderingError, 'cancel_report has been called without valid id' if report.nil?
155
+
156
+ report.cancel! unless report.done
157
+
158
+ # redirect to view_report page
159
+ http_response(302, 'Found', nil, Location: "/view_report?report_id=#{report.object_id}")
160
+ end
161
+
162
+ def view_report(attrs)
163
+ # view report if already available, or show status view
164
+ report = @reports.select { |r| r.object_id.to_s == attrs['report_id'].to_s }.first
165
+ raise WebserviceGeneralRenderingError, 'view_report has been called without valid id' if report.nil?
166
+
167
+ # show report status
168
+ return get_reports_status_as_html([report]) if !report.done || !report.error.empty?
169
+
170
+ # provide report
171
+ @logger.debug("Returning PDF report at #{report.path}")
172
+ content = File.read(report.path, mode: 'rb')
173
+ return http_response(200, 'OK', content, "Content-Type": 'application/pdf') if content.start_with?('%PDF')
174
+
175
+ http_response(200, 'OK', content, "Content-Type": 'application/octet-stream',
176
+ "Content-Disposition": 'attachment; '\
177
+ "filename=report_#{attrs['report_id']}.zip")
178
+ end
179
+
180
+ def render_report(attrs)
181
+ # build report
182
+ template_file = "#{@config.templates_folder}#{attrs['var-template']}.adoc"
183
+
184
+ file = Tempfile.new('gf_pdf_', @config.reports_folder)
185
+ begin
186
+ FileUtils.chmod('+r', file.path)
187
+ rescue StandardError => e
188
+ @logger.debug("File permissions could not be set for #{file.path}: #{e.message}")
189
+ end
190
+
191
+ report = @config.report_class.new(@config, template_file, file, attrs)
192
+ Thread.new do
193
+ report.create_report
194
+ end
195
+ @reports << report
196
+
197
+ http_response(302, 'Found', nil, Location: "/view_report?report_id=#{report.object_id}")
198
+ end
199
+
200
+ def get_reports_status_as_html(reports)
201
+ i = reports.length
202
+
203
+ content = '<html>'\
204
+ '<head></head>'\
205
+ '<body>'\
206
+ '<table>'\
207
+ '<thead>'\
208
+ '<th>#</th>'\
209
+ '<th>Start Time</th>'\
210
+ '<th>End Time</th>'\
211
+ '<th>Template</th>'\
212
+ '<th>Execution time</th>'\
213
+ '<th>Status</th>'\
214
+ '<th>Error</th>'\
215
+ '<th>Action</th>'\
216
+ '</thead>' +
217
+ reports.reverse.map do |report|
218
+ '<tr>'\
219
+ "<td>#{i -= 1}</td>"\
220
+ "<td>#{report.start_time}</td>"\
221
+ "<td>#{report.end_time}</td>"\
222
+ "<td>#{report.template}</td>"\
223
+ "<td>#{report.execution_time.to_i} secs</td>"\
224
+ "<td>#{report.status} (#{(report.progress * 100).to_i}%)</td>"\
225
+ "<td>#{report.error.join('<br>')}</td>"\
226
+ "<td>#{!report.done && !report.cancel ? "<a href=\"/cancel_report?report_id=#{report.object_id}\">Cancel</a>&nbsp;" : ''}"\
227
+ "#{(report.status == 'finished') || (report.status == 'cancelled') ? "<a href=\"/view_report?report_id=#{report.object_id}\">View</a>&nbsp;" : '&nbsp;'}"\
228
+ "<a href=\"/view_log?report_id=#{report.object_id}\">Log</a></td>"\
229
+ '</tr>'
230
+ end.join('') +
231
+ '</table></body></html>'
232
+
233
+ http_response(200, 'OK', content, "Content-Type": 'text/html')
234
+ end
235
+
236
+ def http_response(code, text, body, opts = {})
237
+ "HTTP/1.1 #{code} #{text}\r\n#{opts.map { |k, v| "#{k}: #{v}" }.join("\r\n")}"\
238
+ "#{body ? "\r\nContent-Length: #{body.to_s.bytesize}" : ''}\r\n\r\n#{body}"
239
+ end
240
+ end
241
+ end
242
+ end
@@ -0,0 +1,90 @@
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_alerts[<options>]
9
+ #
10
+ # Returns the results of alerts 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
+ # == Supported options
22
+ # +columns+ - see {AlertsTableQuery#pre_process} (*mandatory*)
23
+ #
24
+ # +instance+ - name of grafana instance, 'default' if not specified
25
+ #
26
+ # +dashboard+ - uid of grafana dashboard to query for, empty string if no filter is wanted
27
+ #
28
+ # +panel+ - id of the panel to query for
29
+ #
30
+ # +from+ - 'from' time for the sql query
31
+ #
32
+ # +to+ - 'to' time for the sql query
33
+ #
34
+ # +format+ - see {QueryMixin#format_columns}
35
+ #
36
+ # +replace_values+ - see {QueryMixin#replace_values}
37
+ #
38
+ # +filter_columns+ - see {QueryMixin#filter_columns}
39
+ class AlertsTableIncludeProcessor < ::Asciidoctor::Extensions::IncludeProcessor
40
+ include ProcessorMixin
41
+
42
+ # :nodoc:
43
+ def handles?(target)
44
+ target.start_with? 'grafana_alerts'
45
+ end
46
+
47
+ # :nodoc:
48
+ def process(doc, reader, _target, attrs)
49
+ return if @report.cancel
50
+
51
+ @report.next_step
52
+ instance = attrs['instance'] || doc.attr('grafana_default_instance') || 'default'
53
+ dashboard_id = attrs['dashboard'] || doc.attr('grafana_default_dashboard')
54
+ panel_id = attrs['panel']
55
+ @report.logger.debug("Processing AlertsTableIncludeProcessor (instance: #{instance},"\
56
+ " dashboard: #{dashboard_id}, panel: #{panel_id})")
57
+
58
+ query = AlertsTableQuery.new(@report.grafana(instance))
59
+ query.set_defaults_from_dashboard(@report.grafana(instance).dashboard(dashboard_id)) if dashboard_id
60
+ defaults = {}
61
+ defaults['dashboardId'] = dashboard_id if dashboard_id
62
+ defaults['panelId'] = panel_id if panel_id
63
+
64
+ query.merge_hash_variables(doc.attributes, attrs)
65
+ selected_attrs = attrs.select do |k, _v|
66
+ k =~ /(?:columns|limit|folderId|dashboardId|panelId|dahboardTag|dashboardQuery|state|query)/x
67
+ end
68
+ query.raw_query = defaults.merge(selected_attrs.each_with_object({}) { |(k, v), h| h[k] = v })
69
+ @report.logger.debug("from: #{query.from}, to: #{query.to}")
70
+
71
+ begin
72
+ reader.unshift_lines query.execute
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
+
84
+ # @see ProcessorMixin#build_demo_entry
85
+ def build_demo_entry(_panel)
86
+ "|===\ninclude::grafana_alerts[columns=\"panelId,name,state\"]\n|==="
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,89 @@
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_annotations[<options>]
9
+ #
10
+ # Returns the results of alerts 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
+ # == Supported options
22
+ # +columns+ - see {AnnotationsTableQuery#pre_process} (*mandatory*)
23
+ #
24
+ # +instance+ - name of grafana instance, 'default' if not specified
25
+ #
26
+ # +dashboard+ - uid of grafana dashboard to query for, empty string if no filter is wanted
27
+ #
28
+ # +panel+ - id of the panel to query for
29
+ #
30
+ # +from+ - 'from' time for the sql query
31
+ #
32
+ # +to+ - 'to' time for the sql query
33
+ #
34
+ # +format+ - see {QueryMixin#format_columns}
35
+ #
36
+ # +replace_values+ - see {QueryMixin#replace_values}
37
+ #
38
+ # +filter_columns+ - see {QueryMixin#filter_columns}
39
+ class AnnotationsTableIncludeProcessor < ::Asciidoctor::Extensions::IncludeProcessor
40
+ include ProcessorMixin
41
+
42
+ # :nodoc:
43
+ def handles?(target)
44
+ target.start_with? 'grafana_annotations'
45
+ end
46
+
47
+ # :nodoc:
48
+ def process(doc, reader, _target, attrs)
49
+ return if @report.cancel
50
+
51
+ @report.next_step
52
+ instance = attrs['instance'] || doc.attr('grafana_default_instance') || 'default'
53
+ dashboard_id = attrs['dashboard'] || doc.attr('grafana_default_dashboard')
54
+ panel_id = attrs['panel']
55
+ @report.logger.debug("Processing AnnotationsTableIncludeProcessor (instance: #{instance})")
56
+
57
+ query = AnnotationsTableQuery.new(@report.grafana(instance))
58
+ query.set_defaults_from_dashboard(@report.grafana(instance).dashboard(dashboard_id)) if dashboard_id
59
+ defaults = {}
60
+ defaults['dashboardId'] = dashboard_id if dashboard_id
61
+ defaults['panelId'] = panel_id if panel_id
62
+
63
+ query.merge_hash_variables(doc.attributes, attrs)
64
+ selected_attrs = attrs.select do |k, _v|
65
+ k =~ /(?:columns|limit|alertId|dashboardId|panelId|userId|type|tags)/
66
+ end
67
+ query.raw_query = defaults.merge(selected_attrs.each_with_object({}) { |(k, v), h| h[k] = v })
68
+ @report.logger.debug("from: #{query.from}, to: #{query.to}")
69
+
70
+ begin
71
+ reader.unshift_lines query.execute
72
+ rescue GrafanaReporterError => e
73
+ @report.logger.error(e.message)
74
+ reader.unshift_line "|#{e.message}"
75
+ rescue StandardError => e
76
+ @report.logger.fatal(e.message)
77
+ reader.unshift_line "|#{e.message}"
78
+ end
79
+
80
+ reader
81
+ end
82
+
83
+ # @see ProcessorMixin#build_demo_entry
84
+ def build_demo_entry(_panel)
85
+ "|===\ninclude::grafana_annotations[columns=\"time,panelId,newState,prevState,text\"]\n|==="
86
+ end
87
+ end
88
+ end
89
+ end