ruby-grafana-reporter 0.4.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.
- checksums.yaml +4 -4
- data/README.md +203 -185
- data/lib/VERSION.rb +2 -2
- data/lib/grafana/abstract_datasource.rb +25 -15
- data/lib/grafana/errors.rb +10 -2
- data/lib/grafana/grafana.rb +2 -0
- data/lib/grafana/grafana_property_datasource.rb +5 -0
- data/lib/grafana/graphite_datasource.rb +27 -5
- data/lib/grafana/influxdb_datasource.rb +70 -0
- data/lib/grafana/prometheus_datasource.rb +27 -5
- data/lib/grafana/sql_datasource.rb +9 -2
- data/lib/grafana/variable.rb +0 -1
- data/lib/grafana_reporter/abstract_query.rb +124 -23
- data/lib/grafana_reporter/abstract_report.rb +21 -2
- data/lib/grafana_reporter/alerts_table_query.rb +1 -6
- data/lib/grafana_reporter/annotations_table_query.rb +1 -6
- data/lib/grafana_reporter/application/webservice.rb +1 -1
- data/lib/grafana_reporter/asciidoctor/alerts_table_include_processor.rb +5 -4
- data/lib/grafana_reporter/asciidoctor/annotations_table_include_processor.rb +5 -4
- data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +1 -4
- data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +1 -4
- data/lib/grafana_reporter/asciidoctor/panel_property_inline_macro.rb +1 -4
- data/lib/grafana_reporter/asciidoctor/panel_query_table_include_processor.rb +1 -5
- data/lib/grafana_reporter/asciidoctor/panel_query_value_inline_macro.rb +1 -4
- data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +20 -35
- data/lib/grafana_reporter/asciidoctor/report.rb +2 -14
- data/lib/grafana_reporter/asciidoctor/sql_table_include_processor.rb +1 -3
- data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +1 -3
- data/lib/grafana_reporter/demo_report_wizard.rb +5 -1
- data/lib/grafana_reporter/erb/report.rb +3 -16
- data/lib/grafana_reporter/erb/report_jail.rb +21 -0
- data/lib/grafana_reporter/errors.rb +14 -0
- data/lib/grafana_reporter/help.rb +2 -2
- data/lib/grafana_reporter/panel_image_query.rb +1 -5
- data/lib/grafana_reporter/query_value_query.rb +1 -19
- data/lib/ruby_grafana_reporter.rb +0 -12
- metadata +4 -2
data/lib/grafana/errors.rb
CHANGED
@@ -49,10 +49,10 @@ module Grafana
|
|
49
49
|
#
|
50
50
|
# Most likely this happens, because the image renderer is not configures properly in grafana,
|
51
51
|
# or the panel rendering ran into a timeout.
|
52
|
+
# @param panel [Panel] panel object, which could not be rendered
|
52
53
|
class ImageCouldNotBeRenderedError < GrafanaError
|
53
|
-
# @param panel [Panel] panel object, which could not be rendered
|
54
54
|
def initialize(panel)
|
55
|
-
super("The specified panel '#{panel.id}' from dashboard '#{panel.dashboard.id} could not be "\
|
55
|
+
super("The specified panel '#{panel.id}' from dashboard '#{panel.dashboard.id}' could not be "\
|
56
56
|
'rendered to an image.')
|
57
57
|
end
|
58
58
|
end
|
@@ -70,4 +70,12 @@ module Grafana
|
|
70
70
|
super("The datasource query provided, does not look like a grafana datasource target (received: #{query}).")
|
71
71
|
end
|
72
72
|
end
|
73
|
+
|
74
|
+
# Raised if a datasource implementation cannot handle a query, which is composed
|
75
|
+
# in the grafana visual editor.
|
76
|
+
class ComposedQueryNotSupportedError < GrafanaError
|
77
|
+
def initialize(class_obj)
|
78
|
+
super("Composed queries are not yet supported for datasource '#{class_obj}'.")
|
79
|
+
end
|
80
|
+
end
|
73
81
|
end
|
data/lib/grafana/grafana.rb
CHANGED
@@ -50,6 +50,8 @@ module Grafana
|
|
50
50
|
# @return [Datasource] Datasource for the specified datasource name
|
51
51
|
def datasource_by_name(datasource_name)
|
52
52
|
datasource_name = 'default' if datasource_name.to_s.empty?
|
53
|
+
# TODO: add support for grafana builtin datasource types
|
54
|
+
return UnsupportedDatasource.new(nil) if datasource_name.to_s =~ /-- (?:Mixed|Dashboard|Grafana) --/
|
53
55
|
raise DatasourceDoesNotExistError.new('name', datasource_name) unless @datasources[datasource_name]
|
54
56
|
|
55
57
|
@datasources[datasource_name]
|
@@ -36,15 +36,37 @@ module Grafana
|
|
36
36
|
panel_query_target['target']
|
37
37
|
end
|
38
38
|
|
39
|
+
# @see AbstractDatasource#default_variable_format
|
40
|
+
def default_variable_format
|
41
|
+
'glob'
|
42
|
+
end
|
43
|
+
|
39
44
|
private
|
40
45
|
|
41
46
|
# @see AbstractDatasource#preformat_response
|
42
47
|
def preformat_response(response_body)
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
+
json = JSON.parse(response_body)
|
49
|
+
|
50
|
+
header = ['time']
|
51
|
+
content = {}
|
52
|
+
|
53
|
+
# keep sorting, if json has only one target item, otherwise merge results and return
|
54
|
+
# as a time sorted array
|
55
|
+
return { header: header << json.first['target'], content: json.first['datapoints'].map! { |item| [item[1], item[0]] } } if json.length == 1
|
56
|
+
|
57
|
+
# TODO: show warning if results may be sorted different
|
58
|
+
json.each_index do |i|
|
59
|
+
header << json[i]['target']
|
60
|
+
tmp = json[i]['datapoints'].map! { |item| [item[1], item[0]] }.to_h
|
61
|
+
tmp.each_key { |key| content[key] = Array.new(json.length) unless content[key] }
|
62
|
+
|
63
|
+
content.merge!(tmp) do |_key, old, new|
|
64
|
+
old[i] = new
|
65
|
+
old
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
{ header: header, content: content.to_a.map(&:flatten).sort { |a, b| a[0] <=> b[0] } }
|
48
70
|
end
|
49
71
|
end
|
50
72
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grafana
|
4
|
+
# Implements the interface to Prometheus datasources.
|
5
|
+
class InfluxDbDatasource < AbstractDatasource
|
6
|
+
# @see AbstractDatasource#handles?
|
7
|
+
def self.handles?(model)
|
8
|
+
tmp = new(model)
|
9
|
+
tmp.type == 'influxdb'
|
10
|
+
end
|
11
|
+
|
12
|
+
# +:database+ needs to contain the InfluxDb database name
|
13
|
+
# +:raw_query+ needs to contain a InfluxDb query as String
|
14
|
+
# @see AbstractDatasource#request
|
15
|
+
def request(query_description)
|
16
|
+
raise MissingSqlQueryError if query_description[:raw_query].nil?
|
17
|
+
|
18
|
+
url = "/api/datasources/proxy/#{id}/query?db=#{@model['database']}&q=#{URI.encode(query_description[:raw_query])}&epoch=ms"
|
19
|
+
|
20
|
+
webrequest = query_description[:prepared_request]
|
21
|
+
webrequest.relative_url = url
|
22
|
+
webrequest.options.merge!({ request: Net::HTTP::Get })
|
23
|
+
|
24
|
+
result = webrequest.execute(query_description[:timeout])
|
25
|
+
preformat_response(result.body)
|
26
|
+
end
|
27
|
+
|
28
|
+
# @see AbstractDatasource#raw_query_from_panel_model
|
29
|
+
def raw_query_from_panel_model(panel_query_target)
|
30
|
+
return panel_query_target['query'] if panel_query_target['rawQuery']
|
31
|
+
|
32
|
+
# TODO: support composed queries
|
33
|
+
raise ComposedQueryNotSupportedError, self
|
34
|
+
end
|
35
|
+
|
36
|
+
# @see AbstractDatasource#default_variable_format
|
37
|
+
def default_variable_format
|
38
|
+
'regex'
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# @see AbstractDatasource#preformat_response
|
44
|
+
def preformat_response(response_body)
|
45
|
+
# TODO: how to handle multiple query results?
|
46
|
+
json = JSON.parse(response_body)['results'].first['series']
|
47
|
+
|
48
|
+
header = ['time']
|
49
|
+
content = {}
|
50
|
+
|
51
|
+
# keep sorting, if json has only one target item, otherwise merge results and return
|
52
|
+
# as a time sorted array
|
53
|
+
return { header: header << "#{json.first['name']} #{json.first['columns'][1]} (#{json.first['tags']})", content: json.first['values'] } if json.length == 1
|
54
|
+
|
55
|
+
# TODO: show warning here, as results may be sorted different
|
56
|
+
json.each_index do |i|
|
57
|
+
header << "#{json[i]['name']} #{json[i]['columns'][1]} (#{json[i]['tags']})"
|
58
|
+
tmp = json[i]['values'].to_h
|
59
|
+
tmp.each_key { |key| content[key] = Array.new(json.length) unless content[key] }
|
60
|
+
|
61
|
+
content.merge!(tmp) do |_key, old, new|
|
62
|
+
old[i] = new
|
63
|
+
old
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
{ header: header, content: content.to_a.map(&:flatten).sort { |a, b| a[0] <=> b[0] } }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -31,15 +31,37 @@ module Grafana
|
|
31
31
|
panel_query_target['expr']
|
32
32
|
end
|
33
33
|
|
34
|
+
# @see AbstractDatasource#default_variable_format
|
35
|
+
def default_variable_format
|
36
|
+
'regex'
|
37
|
+
end
|
38
|
+
|
34
39
|
private
|
35
40
|
|
36
41
|
# @see AbstractDatasource#preformat_response
|
37
42
|
def preformat_response(response_body)
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
+
json = JSON.parse(response_body)['data']['result']
|
44
|
+
|
45
|
+
headers = ['time']
|
46
|
+
content = {}
|
47
|
+
|
48
|
+
# keep sorting, if json has only one target item, otherwise merge results and return
|
49
|
+
# as a time sorted array
|
50
|
+
return { header: headers << json.first['metric']['mode'], content: json.first['values'] } if json.length == 1
|
51
|
+
|
52
|
+
# TODO: show warning if results may be sorted different
|
53
|
+
json.each_index do |i|
|
54
|
+
headers += [json[i]['metric']['mode']]
|
55
|
+
tmp = json[i]['values'].to_h
|
56
|
+
tmp.each_key { |key| content[key] = Array.new(json.length) unless content[key] }
|
57
|
+
|
58
|
+
content.merge!(tmp) do |_key, old, new|
|
59
|
+
old[i] = new
|
60
|
+
old
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
{ header: headers, content: content.to_a.map(&:flatten).sort { |a, b| a[0] <=> b[0] } }
|
43
65
|
end
|
44
66
|
end
|
45
67
|
end
|
@@ -32,11 +32,18 @@ module Grafana
|
|
32
32
|
preformat_response(result.body)
|
33
33
|
end
|
34
34
|
|
35
|
+
# Currently all composed SQL queries are saved in the dashboard as rawSql, so no conversion
|
36
|
+
# necessary here.
|
35
37
|
# @see AbstractDatasource#raw_query_from_panel_model
|
36
38
|
def raw_query_from_panel_model(panel_query_target)
|
37
39
|
panel_query_target['rawSql']
|
38
40
|
end
|
39
41
|
|
42
|
+
# @see AbstractDatasource#default_variable_format
|
43
|
+
def default_variable_format
|
44
|
+
'glob'
|
45
|
+
end
|
46
|
+
|
40
47
|
private
|
41
48
|
|
42
49
|
def preformat_response(response_body)
|
@@ -45,12 +52,12 @@ module Grafana
|
|
45
52
|
|
46
53
|
JSON.parse(response_body)['results'].each_value do |query_result|
|
47
54
|
if query_result.key?('error')
|
48
|
-
results[:header] = results[:header]
|
55
|
+
results[:header] = results[:header] + ['SQL Error']
|
49
56
|
results[:content] = [[query_result['error']]]
|
50
57
|
|
51
58
|
elsif query_result['tables']
|
52
59
|
query_result['tables'].each do |table|
|
53
|
-
results[:header] = results[:header]
|
60
|
+
results[:header] = results[:header] + table['columns'].map { |header| header['text'] }
|
54
61
|
results[:content] = table['rows']
|
55
62
|
end
|
56
63
|
|
data/lib/grafana/variable.rb
CHANGED
@@ -5,19 +5,51 @@ module GrafanaReporter
|
|
5
5
|
#
|
6
6
|
# Superclass containing everything for all queries towards grafana.
|
7
7
|
class AbstractQuery
|
8
|
-
attr_accessor :datasource
|
8
|
+
attr_accessor :datasource
|
9
9
|
attr_writer :raw_query
|
10
|
-
attr_reader :variables, :result, :panel
|
10
|
+
attr_reader :variables, :result, :panel, :dashboard
|
11
|
+
|
12
|
+
def timeout
|
13
|
+
# TODO: check where value priorities should be evaluated
|
14
|
+
return @variables['timeout'].raw_value if @variables['timeout']
|
15
|
+
return @variables['grafana_default_timeout'].raw_value if @variables['grafana_default_timeout']
|
16
|
+
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param grafana_obj [Object] {Grafana::Grafana}, {Grafana::Dashboard} or {Grafana::Panel} object for which the query is executed
|
21
|
+
# @param opts [Hash] hash options, which may consist of:
|
22
|
+
# @option opts [Hash] :variables hash of variables, which shall be used to replace variable references in the query
|
23
|
+
# @option opts [Boolean] :ignore_dashboard_defaults True if {#assign_dashboard_defaults} should not be called
|
24
|
+
# @option opts [Boolean] :do_not_use_translated_times True if given from and to times should used as is, without being resolved to reporter times - using this parameter can lead to inconsistent report contents
|
25
|
+
def initialize(grafana_obj, opts = {})
|
26
|
+
if grafana_obj.is_a?(Grafana::Panel)
|
27
|
+
@panel = grafana_obj
|
28
|
+
@dashboard = @panel.dashboard
|
29
|
+
@grafana = @dashboard.grafana
|
30
|
+
|
31
|
+
elsif grafana_obj.is_a?(Grafana::Dashboard)
|
32
|
+
@dashboard = grafana_obj
|
33
|
+
@grafana = @dashboard.grafana
|
34
|
+
|
35
|
+
elsif grafana_obj.is_a?(Grafana::Grafana)
|
36
|
+
@grafana = grafana_obj
|
37
|
+
|
38
|
+
elsif !grafana_obj
|
39
|
+
# nil given
|
11
40
|
|
12
|
-
# @param grafana_or_panel [Object] {Grafana::Grafana} or {Grafana::Panel} object for which the query is executed
|
13
|
-
def initialize(grafana_or_panel)
|
14
|
-
if grafana_or_panel.is_a?(Grafana::Panel)
|
15
|
-
@panel = grafana_or_panel
|
16
|
-
@grafana = @panel.dashboard.grafana
|
17
41
|
else
|
18
|
-
|
42
|
+
raise GrafanaReporterError, "Internal error in AbstractQuery: given object is of type #{grafana_obj.class.name}, which is not supported"
|
19
43
|
end
|
20
44
|
@variables = {}
|
45
|
+
@variables['from'] = Grafana::Variable.new(nil)
|
46
|
+
@variables['to'] = Grafana::Variable.new(nil)
|
47
|
+
|
48
|
+
assign_dashboard_defaults unless opts[:ignore_dashboard_defaults]
|
49
|
+
opts[:variables].each { |k, v| assign_variable(k, v) } if opts[:variables].is_a?(Hash)
|
50
|
+
|
51
|
+
@translate_times = true
|
52
|
+
@translate_times = false if opts[:do_not_use_translated_times]
|
21
53
|
end
|
22
54
|
|
23
55
|
# @abstract
|
@@ -31,11 +63,32 @@ module GrafanaReporter
|
|
31
63
|
def execute
|
32
64
|
return @result unless @result.nil?
|
33
65
|
|
66
|
+
from = @variables['from'].raw_value
|
67
|
+
to = @variables['to'].raw_value
|
68
|
+
if @translate_times
|
69
|
+
from = translate_date(@variables['from'], @variables['grafana_report_timestamp'], false, @variables['from_timezone'] ||
|
70
|
+
@variables['grafana_default_from_timezone'])
|
71
|
+
to = translate_date(@variables['to'], @variables['grafana_report_timestamp'], true, @variables['to_timezone'] ||
|
72
|
+
@variables['grafana_default_to_timezone'])
|
73
|
+
end
|
74
|
+
|
34
75
|
pre_process
|
35
76
|
raise DatasourceNotSupportedError.new(@datasource, self) if @datasource.is_a?(Grafana::UnsupportedDatasource)
|
36
77
|
|
37
|
-
|
38
|
-
|
78
|
+
begin
|
79
|
+
@result = @datasource.request(from: from, to: to, raw_query: raw_query, variables: grafana_variables,
|
80
|
+
prepared_request: @grafana.prepare_request, timeout: timeout)
|
81
|
+
rescue ::Grafana::GrafanaError
|
82
|
+
# grafana errors will be directly passed through
|
83
|
+
raise
|
84
|
+
rescue GrafanaReporterError
|
85
|
+
# grafana errors will be directly passed through
|
86
|
+
raise
|
87
|
+
rescue StandardError => e
|
88
|
+
raise DatasourceRequestInternalError.new(@datasource, e.message)
|
89
|
+
end
|
90
|
+
|
91
|
+
raise DatasourceRequestInvalidReturnValueError.new(@datasource, @result) unless datasource_response_valid?
|
39
92
|
post_process
|
40
93
|
@result
|
41
94
|
end
|
@@ -66,17 +119,6 @@ module GrafanaReporter
|
|
66
119
|
raise NotImplementedError
|
67
120
|
end
|
68
121
|
|
69
|
-
# Used to specify variables to be used for this query. This method ensures, that only the values of the
|
70
|
-
# {Grafana::Variable} stored in the +variables+ Array are overwritten.
|
71
|
-
# @param name [String] name of the variable to set
|
72
|
-
# @param variable [Grafana::Variable] variable from which the {Grafana::Variable#raw_value} will be assigned to the query variables
|
73
|
-
def assign_variable(name, variable)
|
74
|
-
raise GrafanaReporterError, "Provided variable is not of type Grafana::Variable (name: '#{name}', value: '#{value}')" unless variable.is_a?(Grafana::Variable)
|
75
|
-
|
76
|
-
@variables[name] ||= variable
|
77
|
-
@variables[name].raw_value = variable.raw_value
|
78
|
-
end
|
79
|
-
|
80
122
|
# Transposes the given result.
|
81
123
|
#
|
82
124
|
# NOTE: Only the +:content+ of the given result hash is transposed. The +:header+ is ignored.
|
@@ -105,10 +147,10 @@ module GrafanaReporter
|
|
105
147
|
|
106
148
|
filter_columns = filter_columns_variable.raw_value
|
107
149
|
filter_columns.split(',').each do |filter_column|
|
108
|
-
pos = result[:header]
|
150
|
+
pos = result[:header].index(filter_column)
|
109
151
|
|
110
152
|
unless pos.nil?
|
111
|
-
result[:header]
|
153
|
+
result[:header].delete_at(pos)
|
112
154
|
result[:content].each { |row| row.delete_at(pos) }
|
113
155
|
end
|
114
156
|
end
|
@@ -200,6 +242,7 @@ module GrafanaReporter
|
|
200
242
|
begin
|
201
243
|
row[i] = row[i].to_s.gsub(/#{k}/, v) if row[i].to_s =~ /#{k}/
|
202
244
|
rescue StandardError => e
|
245
|
+
@grafana.logger.error(e.message)
|
203
246
|
row[i] = e.message
|
204
247
|
end
|
205
248
|
|
@@ -241,6 +284,24 @@ module GrafanaReporter
|
|
241
284
|
result
|
242
285
|
end
|
243
286
|
|
287
|
+
# Used to build a output format matching the requested report format.
|
288
|
+
# @param result [Hash] preformatted sql hash, (see {Grafana::AbstractDatasource#request})
|
289
|
+
# @param opts [Hash] options for the formatting:
|
290
|
+
# @option opts [Grafana::Variable] :row_divider requested row divider for the result table
|
291
|
+
# @option opts [Grafana::Variable] :column_divider requested row divider for the result table
|
292
|
+
# @option opts [Regex or String] :escape_regex regular expression which specifies a part of a cell content, which has to be escaped
|
293
|
+
# @option opts [String] :escape_replacement specifies how the found :escape_regex shall be replaced
|
294
|
+
# @return [String] formatted table result in requested output format
|
295
|
+
def format_table_output(result, opts)
|
296
|
+
opts = { escape_regex: '|', escape_replacement: '\\|', row_divider: Grafana::Variable.new('| '), column_divider: Grafana::Variable.new(' | ') }.merge(opts.delete_if {|_k, v| v.nil? })
|
297
|
+
|
298
|
+
result[:content].map do |row|
|
299
|
+
opts[:row_divider].raw_value + row.map do |item|
|
300
|
+
item.to_s.gsub(opts[:escape_regex], opts[:escape_replacement])
|
301
|
+
end.join(opts[:column_divider].raw_value)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
244
305
|
# Used to translate the relative date strings used by grafana, e.g. +now-5d/w+ to the
|
245
306
|
# correct timestamp. Reason is that grafana does this in the frontend, which we have
|
246
307
|
# to emulate here for the reporter.
|
@@ -258,6 +319,7 @@ module GrafanaReporter
|
|
258
319
|
def translate_date(orig_date, report_time, is_to_time, timezone = nil)
|
259
320
|
# TODO: add test case for creation of variable, if not given, maybe also print a warning
|
260
321
|
report_time ||= ::Grafana::Variable.new(Time.now.to_s)
|
322
|
+
orig_date = orig_date.raw_value if orig_date.is_a?(Grafana::Variable)
|
261
323
|
return (DateTime.parse(report_time.raw_value).to_time.to_i * 1000).to_s unless orig_date
|
262
324
|
return orig_date if orig_date =~ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
|
263
325
|
return orig_date if orig_date =~ /^\d+$/
|
@@ -296,6 +358,45 @@ module GrafanaReporter
|
|
296
358
|
|
297
359
|
private
|
298
360
|
|
361
|
+
# Used to specify variables to be used for this query. This method ensures, that only the values of the
|
362
|
+
# {Grafana::Variable} stored in the +variables+ Array are overwritten.
|
363
|
+
# @param name [String] name of the variable to set
|
364
|
+
# @param variable [Grafana::Variable] variable from which the {Grafana::Variable#raw_value} will be assigned to the query variables
|
365
|
+
def assign_variable(name, variable)
|
366
|
+
variable = Grafana::Variable.new(variable) unless variable.is_a?(Grafana::Variable)
|
367
|
+
|
368
|
+
@variables[name] ||= variable
|
369
|
+
@variables[name].raw_value = variable.raw_value
|
370
|
+
end
|
371
|
+
|
372
|
+
# Sets default configurations from the given {Grafana::Dashboard} and store them as settings in the
|
373
|
+
# {AbstractQuery}.
|
374
|
+
#
|
375
|
+
# Following data is extracted:
|
376
|
+
# - +from+, by {Grafana::Dashboard#from_time}
|
377
|
+
# - +to+, by {Grafana::Dashboard#to_time}
|
378
|
+
# - and all variables as {Grafana::Variable}, prefixed with +var-+, as grafana also does it
|
379
|
+
def assign_dashboard_defaults
|
380
|
+
return unless @dashboard
|
381
|
+
|
382
|
+
assign_variable('from', @dashboard.from_time)
|
383
|
+
assign_variable('to', @dashboard.to_time)
|
384
|
+
@dashboard.variables.each { |item| assign_variable("var-#{item.name}", item) }
|
385
|
+
end
|
386
|
+
|
387
|
+
def datasource_response_valid?
|
388
|
+
return false if @result.nil?
|
389
|
+
return false unless @result.is_a?(Hash)
|
390
|
+
# TODO: check if it should be ok if a datasource request returns an empty hash only
|
391
|
+
return true if @result.empty?
|
392
|
+
return false unless @result.has_key?(:header)
|
393
|
+
return false unless @result.has_key?(:content)
|
394
|
+
return false unless @result[:header].is_a?(Array)
|
395
|
+
return false unless @result[:content].is_a?(Array)
|
396
|
+
|
397
|
+
true
|
398
|
+
end
|
399
|
+
|
299
400
|
# @return [Hash<String, Variable>] all grafana variables stored in this query, i.e. the variable name
|
300
401
|
# is prefixed with +var-+
|
301
402
|
def grafana_variables
|