ruby-grafana-reporter 0.1.6
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 +7 -0
- data/LICENSE +20 -0
- data/README.md +248 -0
- data/lib/VERSION.rb +3 -0
- data/lib/grafana/abstract_panel_query.rb +20 -0
- data/lib/grafana/abstract_query.rb +127 -0
- data/lib/grafana/abstract_sql_query.rb +42 -0
- data/lib/grafana/dashboard.rb +66 -0
- data/lib/grafana/errors.rb +61 -0
- data/lib/grafana/grafana.rb +131 -0
- data/lib/grafana/panel.rb +39 -0
- data/lib/grafana/panel_image_query.rb +49 -0
- data/lib/grafana/variable.rb +259 -0
- data/lib/grafana_reporter/abstract_report.rb +109 -0
- data/lib/grafana_reporter/application/application.rb +229 -0
- data/lib/grafana_reporter/application/errors.rb +30 -0
- data/lib/grafana_reporter/asciidoctor/alerts_table_query.rb +99 -0
- data/lib/grafana_reporter/asciidoctor/annotations_table_query.rb +96 -0
- data/lib/grafana_reporter/asciidoctor/errors.rb +37 -0
- data/lib/grafana_reporter/asciidoctor/extensions/alerts_table_include_processor.rb +86 -0
- data/lib/grafana_reporter/asciidoctor/extensions/annotations_table_include_processor.rb +86 -0
- data/lib/grafana_reporter/asciidoctor/extensions/panel_image_block_macro.rb +67 -0
- data/lib/grafana_reporter/asciidoctor/extensions/panel_image_inline_macro.rb +65 -0
- data/lib/grafana_reporter/asciidoctor/extensions/panel_property_inline_macro.rb +58 -0
- data/lib/grafana_reporter/asciidoctor/extensions/panel_query_table_include_processor.rb +75 -0
- data/lib/grafana_reporter/asciidoctor/extensions/panel_query_value_inline_macro.rb +70 -0
- data/lib/grafana_reporter/asciidoctor/extensions/processor_mixin.rb +18 -0
- data/lib/grafana_reporter/asciidoctor/extensions/show_environment_include_processor.rb +41 -0
- data/lib/grafana_reporter/asciidoctor/extensions/show_help_include_processor.rb +202 -0
- data/lib/grafana_reporter/asciidoctor/extensions/sql_table_include_processor.rb +67 -0
- data/lib/grafana_reporter/asciidoctor/extensions/sql_value_inline_macro.rb +65 -0
- data/lib/grafana_reporter/asciidoctor/extensions/value_as_variable_include_processor.rb +57 -0
- data/lib/grafana_reporter/asciidoctor/panel_first_value_query.rb +32 -0
- data/lib/grafana_reporter/asciidoctor/panel_image_query.rb +23 -0
- data/lib/grafana_reporter/asciidoctor/panel_property_query.rb +43 -0
- data/lib/grafana_reporter/asciidoctor/panel_table_query.rb +36 -0
- data/lib/grafana_reporter/asciidoctor/query_mixin.rb +309 -0
- data/lib/grafana_reporter/asciidoctor/report.rb +159 -0
- data/lib/grafana_reporter/asciidoctor/sql_first_value_query.rb +34 -0
- data/lib/grafana_reporter/asciidoctor/sql_table_query.rb +32 -0
- data/lib/grafana_reporter/configuration.rb +326 -0
- data/lib/grafana_reporter/errors.rb +38 -0
- data/lib/grafana_reporter/logger/two_way_logger.rb +52 -0
- data/lib/ruby-grafana-reporter.rb +27 -0
- metadata +88 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
module Grafana
|
2
|
+
# Representation of one specific dashboard in a {Grafana} instance.
|
3
|
+
class Dashboard
|
4
|
+
# @return [Grafana] parent {Grafana} object
|
5
|
+
attr_reader :grafana
|
6
|
+
attr_reader :panels, :variables
|
7
|
+
|
8
|
+
# @param model [Hash] converted JSON Hash of the grafana dashboard
|
9
|
+
# @param grafana [Grafana] parent {Grafana} object
|
10
|
+
def initialize(model, grafana)
|
11
|
+
@grafana = grafana
|
12
|
+
@model = model
|
13
|
+
|
14
|
+
# read panels
|
15
|
+
@panels = []
|
16
|
+
if @model.key?('panels')
|
17
|
+
@model['panels'].each do |panel|
|
18
|
+
if panel.key?('panels')
|
19
|
+
panel['panels'].each do |subpanel|
|
20
|
+
@panels << Panel.new(subpanel, self)
|
21
|
+
end
|
22
|
+
else
|
23
|
+
@panels << Panel.new(panel, self)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# store variables in array as objects of type Variable
|
29
|
+
@variables = []
|
30
|
+
return unless @model.key?('templating')
|
31
|
+
|
32
|
+
list = @model['templating']['list']
|
33
|
+
return unless list.is_a? Array
|
34
|
+
|
35
|
+
list.each do |item|
|
36
|
+
@variables << Variable.new(item)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [String] +from+ time configured in the dashboard.
|
41
|
+
def from_time
|
42
|
+
return @model['time']['from'] if @model['time']
|
43
|
+
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [String] +to+ time configured in the dashboard.
|
48
|
+
def to_time
|
49
|
+
@model['time']['to'] if @model['time']
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [String] dashboard UID
|
54
|
+
def id
|
55
|
+
@model['uid']
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [Panel] panel for the specified ID
|
59
|
+
def panel(id)
|
60
|
+
panels = @panels.select { |item| item.field('id') == id.to_i }
|
61
|
+
raise PanelDoesNotExistError.new(id, self) if panels.empty?
|
62
|
+
|
63
|
+
panels.first
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Grafana
|
2
|
+
# A top level alarm for all other errors in current module.
|
3
|
+
class GrafanaError < StandardError
|
4
|
+
def initialize(message)
|
5
|
+
super("GrafanaError: #{message} (#{self.class})")
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
# Raised if a given dashboard does not exist in a specific {Grafana} instance.
|
10
|
+
class DashboardDoesNotExistError < GrafanaError
|
11
|
+
# @param dashboard_uid [String] dashboard uid, which could not be found
|
12
|
+
def initialize(dashboard_uid)
|
13
|
+
super("The specified dashboard '#{dashboard_uid}' does not exist.")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Raised if a given panel does not exist on a specific {Dashboard} in the current {Grafana} instance.
|
18
|
+
class PanelDoesNotExistError < GrafanaError
|
19
|
+
# @param panel_id [String] panel id, which could not be found on the dashboard
|
20
|
+
# @param dashboard [Dashboard] dashboard object on which the panel could not be found
|
21
|
+
def initialize(panel_id, dashboard)
|
22
|
+
super("The specified panel id '#{panel_id}' does not exist on the dashboard '#{dashboard.id}'.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Raised if a given query letter does not exist on a specific {Panel}.
|
27
|
+
class QueryLetterDoesNotExistError < GrafanaError
|
28
|
+
# @param query_letter [String] query letter name, which could not be found on the panel
|
29
|
+
# @param panel [Panel] panel object on which the query could not be found
|
30
|
+
def initialize(query_letter, panel)
|
31
|
+
super("The specified query '#{query_letter}' does not exist in the panel '#{panel.id}' in dashboard '#{panel.dashboard}'.")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Raised if a given datasource does not exist in a specific {Grafana} instance.
|
36
|
+
class DatasourceDoesNotExistError < GrafanaError
|
37
|
+
# @param field [String] specifies, how the datasource has been searched, e.g. 'id' or 'name'
|
38
|
+
# @param datasource_identifier [String] identifier of the datasource, which could not be found, e.g. the specifiy id or name
|
39
|
+
def initialize(field, datasource_identifier)
|
40
|
+
super("Datasource with #{field} '#{datasource_identifier}' does not exist.")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Raised if a {Panel} could not be rendered as an image.
|
45
|
+
#
|
46
|
+
# Most likely this happens, because the image renderer is not configures properly in grafana,
|
47
|
+
# or the panel rendering ran into a timeout.
|
48
|
+
class ImageCouldNotBeRenderedError < GrafanaError
|
49
|
+
# @param panel [Panel] panel object, which could not be rendered
|
50
|
+
def initialize(panel)
|
51
|
+
super("The specified panel '#{panel.id}' from dashboard '#{panel.dashboard.id} could not be rendered to an image.")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Raised if no SQL query is specified in a {AbstractSqlQuery} object.
|
56
|
+
class MissingSqlQueryError < GrafanaError
|
57
|
+
def initialize
|
58
|
+
super('No SQL statement has been specified.')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# Contains all objects for creating structured objects for interfacing grafana.
|
2
|
+
#
|
3
|
+
# The intention is, that these represent the business logic contained within grafana in an appropriate object model for the reporter to work with.
|
4
|
+
#
|
5
|
+
# For details, see also {https://grafana.com/docs/grafana/latest/http_api Grafana API}.
|
6
|
+
module Grafana
|
7
|
+
# Main class for handling the interaction with one specific Grafana instance.
|
8
|
+
class Grafana
|
9
|
+
# @param base_uri [String] full URI pointing to the specific grafana instance without trailing slash, e.g. +https://localhost:3000+.
|
10
|
+
# @param key [String] API key for the grafana instance, if required
|
11
|
+
# @param opts [Hash] additional options.
|
12
|
+
# Currently supporting +:logger+ and +:datasources+.
|
13
|
+
# +:datasources+ need to be an Hash with datasource name as key and datasource id as value.
|
14
|
+
# If not specified, the datasources will be queried from the grafana interface.
|
15
|
+
# Specifying +:datasources+> here can be used, so that the interface can be used without grafana Admin privileges.
|
16
|
+
def initialize(base_uri, key = nil, opts = {})
|
17
|
+
@base_uri = base_uri
|
18
|
+
@key = key
|
19
|
+
@dashboards = {}
|
20
|
+
@logger = opts[:logger] || ::Logger.new(nil)
|
21
|
+
@datasources = opts[:datasources]
|
22
|
+
end
|
23
|
+
|
24
|
+
# Used to test a connection to the grafana instance.
|
25
|
+
#
|
26
|
+
# Running this function also determines, if the API configured here has Admin or NON-Admin privileges,
|
27
|
+
# or even fails on connecting to grafana.
|
28
|
+
#
|
29
|
+
# @return [String] +Admin+, +NON-Admin+ or +Failed+ is returned, depending on the test results
|
30
|
+
def test_connection
|
31
|
+
res = execute_http_request('/api/datasources')
|
32
|
+
if res.is_a?(Net::HTTPOK)
|
33
|
+
# we have admin rights
|
34
|
+
@logger.info('Reporter is running with Admin privileges on grafana.')
|
35
|
+
return 'Admin'
|
36
|
+
else
|
37
|
+
# check if we have lower rights
|
38
|
+
res = execute_http_request('/api/dashboards/home')
|
39
|
+
if res.is_a?(Net::HTTPOK)
|
40
|
+
@logger.warn('Reporter is running with NON-Admin privileges on grafana. Make sure that necessary datasources are specified in CONFIG_FILE, otherwise operation will fail')
|
41
|
+
return 'NON-Admin'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
'Failed'
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the ID of a datasource, which has been queried by the datasource name.
|
49
|
+
#
|
50
|
+
# @return [Integer] ID for the specified datasource name
|
51
|
+
def datasource_id(datasource_name)
|
52
|
+
id = datasources[datasource_name]
|
53
|
+
raise DatasourceDoesNotExistError.new('name', datasource_name) unless id
|
54
|
+
|
55
|
+
id
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns if the given datasource ID exists for the grafana instance.
|
59
|
+
#
|
60
|
+
# @return [Boolean] true if exists, false otherwise
|
61
|
+
def datasource_id_exists?(datasource_id)
|
62
|
+
datasources.value?(datasource_id)
|
63
|
+
end
|
64
|
+
|
65
|
+
# @param dashboard_uid [String] UID of the searched {Dashboard}
|
66
|
+
# @return [Dashboard] dashboard object, if it has been found
|
67
|
+
def dashboard(dashboard_uid)
|
68
|
+
return @dashboards[dashboard_uid] unless @dashboards[dashboard_uid].nil?
|
69
|
+
|
70
|
+
response = execute_http_request('/api/dashboards/uid/' + dashboard_uid)
|
71
|
+
model = JSON.parse(response.body)['dashboard']
|
72
|
+
|
73
|
+
raise DashboardDoesNotExistError, dashboard_uid if model.nil?
|
74
|
+
|
75
|
+
# cache dashboard for reuse
|
76
|
+
@dashboards[dashboard_uid] = Dashboard.new(model, self)
|
77
|
+
|
78
|
+
@dashboards[dashboard_uid]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Runs a specific HTTP request against the current grafana instance.
|
82
|
+
#
|
83
|
+
# Default (can be overridden, by specifying the options Hash):
|
84
|
+
# accept: 'application/json'
|
85
|
+
# request: Net::HTTP::Get
|
86
|
+
# content_type: 'application/json'
|
87
|
+
#
|
88
|
+
# @param relative_uri [String] relative URL with a leading slash, which shall be queried
|
89
|
+
# @param options [Hash] options, which shall be merged to the request.
|
90
|
+
# @param timeout [Integer] number of seconds to wait, before the http request is cancelled, defaults to 60 seconds
|
91
|
+
def execute_http_request(relative_uri, options = {}, timeout = nil)
|
92
|
+
uri = URI.parse(@base_uri + relative_uri)
|
93
|
+
default_options = { accept: 'application/json', request: Net::HTTP::Get, content_type: 'application/json' }
|
94
|
+
options = default_options.merge(options)
|
95
|
+
|
96
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
97
|
+
if @base_uri =~ /^https/
|
98
|
+
http.use_ssl = true
|
99
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
100
|
+
end
|
101
|
+
http.read_timeout = timeout.to_i if timeout
|
102
|
+
|
103
|
+
request = options[:request].new(uri.request_uri)
|
104
|
+
request['Accept'] = options[:accept]
|
105
|
+
request['Content-Type'] = options[:content_type]
|
106
|
+
request['Authorization'] = 'Bearer ' + @key unless @key.nil?
|
107
|
+
request.body = options[:body]
|
108
|
+
|
109
|
+
@logger.debug("Requesting #{relative_uri} with '#{options[:body]}' and timeout '#{http.read_timeout}'")
|
110
|
+
resp = http.request(request)
|
111
|
+
resp
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def datasources
|
117
|
+
if @datasources.nil?
|
118
|
+
# load datasources from grafana directly, if allowed
|
119
|
+
response = execute_http_request('/api/datasources')
|
120
|
+
if response['message'].nil?
|
121
|
+
json = JSON.parse(response.body)
|
122
|
+
# only store needed values
|
123
|
+
@datasources = json.map { |item| [item['name'], item['id']] }.to_h
|
124
|
+
else
|
125
|
+
@datasources = {}
|
126
|
+
end
|
127
|
+
end
|
128
|
+
@datasources
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Grafana
|
2
|
+
# Representation of one specific panel in a {Dashboard} instance.
|
3
|
+
class Panel
|
4
|
+
# @return [Dashboard] parent {Dashboard} object
|
5
|
+
attr_reader :dashboard
|
6
|
+
|
7
|
+
# @param model [Hash] converted JSON Hash of the panel
|
8
|
+
# @param dashboard [Dashboard] parent {Dashboard} object
|
9
|
+
def initialize(model, dashboard)
|
10
|
+
@model = model
|
11
|
+
@dashboard = dashboard
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [String] content of the requested field or +''+ if not found
|
15
|
+
def field(field)
|
16
|
+
return @model[field] if @model.key?(field)
|
17
|
+
|
18
|
+
''
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [String] panel ID
|
22
|
+
def id
|
23
|
+
@model['id']
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [String] SQL query string for the requested query letter
|
27
|
+
def query(query_letter)
|
28
|
+
query_item = @model['targets'].select { |item| item['refId'].to_s == query_letter.to_s }.first
|
29
|
+
raise QueryLetterDoesNotExistError.new(query_letter, self) if query_item.nil?
|
30
|
+
|
31
|
+
query_item['rawSql']
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [String] relative rendering URL for the panel, to create an image out of it
|
35
|
+
def render_url
|
36
|
+
"/render/d-solo/#{@dashboard.id}?panelId=#{@model['id']}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Grafana
|
2
|
+
# Query, which allows to render a {Panel} as a PNG image.
|
3
|
+
class PanelImageQuery < AbstractPanelQuery
|
4
|
+
# Returns the URL for rendering the panel. Uses {Panel#render_url} and sets additional url parameters according {https://grafana.com/docs/grafana/latest/reference/share_panel Grafana Share Panel}.
|
5
|
+
#
|
6
|
+
# @see AbstractQuery#url
|
7
|
+
# @return [String] string for rendering the panel
|
8
|
+
def url
|
9
|
+
@panel.render_url + url_parameters
|
10
|
+
end
|
11
|
+
|
12
|
+
# Changes the result of the request to be of type +image/png+.
|
13
|
+
#
|
14
|
+
# @see AbstractQuery#request
|
15
|
+
def request
|
16
|
+
{ accept: 'image/png' }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Adds default variables for querying the image.
|
20
|
+
#
|
21
|
+
# @see AbstractQuery#pre_process
|
22
|
+
def pre_process(_grafana)
|
23
|
+
@variables['fullscreen'] = Variable.new(true)
|
24
|
+
@variables['theme'] = Variable.new('light')
|
25
|
+
@variables['timeout'] = Variable.new(timeout) if timeout
|
26
|
+
@variables['timeout'] ||= Variable.new(60)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Checks if the rendering has been performed properly.
|
30
|
+
# If so, the resulting image is stored in the @result variable, otherwise an error is raised.
|
31
|
+
#
|
32
|
+
# @see AbstractQuery#post_process
|
33
|
+
def post_process
|
34
|
+
raise ImageCouldNotBeRenderedError, @panel if @result.body.include?('<html')
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def url_parameters
|
40
|
+
url_vars = variables.select { |k, _v| k =~ /^(?:timeout|height|width|theme|fullscreen)/ || k =~ /^var-.+/ }
|
41
|
+
url_vars['from'] = Variable.new(@from) if @from
|
42
|
+
url_vars['to'] = Variable.new(@to) if @to
|
43
|
+
url_params = URI.encode_www_form(url_vars.map { |k, v| [k, v.raw_value.to_s] })
|
44
|
+
return '' if url_params.empty?
|
45
|
+
|
46
|
+
'&' + url_params
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,259 @@
|
|
1
|
+
module Grafana
|
2
|
+
# This class contains a representation of
|
3
|
+
# {https://grafana.com/docs/grafana/latest/variables/templates-and-variables grafana variables},
|
4
|
+
# aka grafana templates.
|
5
|
+
#
|
6
|
+
# The main need therefore rises in order to replace variables properly in different
|
7
|
+
# texts, e.g. SQL statements or results.
|
8
|
+
class Variable
|
9
|
+
attr_reader :name, :text, :raw_value
|
10
|
+
|
11
|
+
# @param config_or_value [Hash, Object] configuration hash of a variable out of an {Dashboard} instance or a value of any kind.
|
12
|
+
def initialize(config_or_value)
|
13
|
+
if config_or_value.is_a? Hash
|
14
|
+
@config = config_or_value
|
15
|
+
@name = @config['name']
|
16
|
+
unless @config['current'].nil?
|
17
|
+
@raw_value = @config['current']['value']
|
18
|
+
@text = @config['current']['text']
|
19
|
+
end
|
20
|
+
else
|
21
|
+
@config = {}
|
22
|
+
@raw_value = config_or_value
|
23
|
+
@text = config_or_value.to_s
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the stored value formatted according the given format.
|
28
|
+
#
|
29
|
+
# Supported formats are: +csv+, +distributed+, +doublequote+, +json+, +percentencode+, +pipe+, +raw+, +regex+, +singlequote+, +sqlstring+, +lucene+, +date+ or +glob+ (default)
|
30
|
+
#
|
31
|
+
# For details see {https://grafana.com/docs/grafana/latest/variables/advanced-variable-format-options Grafana Advanced variable format options}.
|
32
|
+
#
|
33
|
+
# For details of +date+ format, see {https://grafana.com/docs/grafana/latest/variables/variable-types/global-variables/#__from-and-__to}. Please note that input for +date+ format is unixtime in milliseconds.
|
34
|
+
#
|
35
|
+
# @param format [String] desired format
|
36
|
+
# @return [String] value of stored variable according the specified format
|
37
|
+
def value_formatted(format = '')
|
38
|
+
value = @raw_value
|
39
|
+
|
40
|
+
# handle value 'All' properly
|
41
|
+
# TODO fix check for selection of All properly
|
42
|
+
if value == 'All' or @text == 'All'
|
43
|
+
if !@config['options'].empty?
|
44
|
+
value = @config['options'].map { |item| item['value'] }
|
45
|
+
elsif !@config['query'].empty?
|
46
|
+
# TODO: replace variables in this query, too
|
47
|
+
return @config['query']
|
48
|
+
# TODO handle 'All' value properly for query attributes
|
49
|
+
else
|
50
|
+
# TODO how to handle All selection properly at this point?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
case format
|
55
|
+
when 'csv'
|
56
|
+
return value.join(',').to_s if multi?
|
57
|
+
|
58
|
+
value.to_s
|
59
|
+
|
60
|
+
when 'distributed'
|
61
|
+
return value.join(",#{name}=") if multi?
|
62
|
+
|
63
|
+
value
|
64
|
+
when 'doublequote'
|
65
|
+
if multi?
|
66
|
+
value = value.map { |item| "\"#{item.gsub(/[\\]/, '\\\\').gsub(/"/, '\\"')}\"" }
|
67
|
+
return value.join(',')
|
68
|
+
end
|
69
|
+
"\"#{value.gsub(/"/, '\\"')}\""
|
70
|
+
|
71
|
+
when 'json'
|
72
|
+
if multi?
|
73
|
+
value = value.map { |item| "\"#{item.gsub(/["\\]/, '\\\\' + '\0')}\"" }
|
74
|
+
return "[#{value.join(',')}]"
|
75
|
+
end
|
76
|
+
"\"#{value.gsub(/"/, '\\"')}\""
|
77
|
+
|
78
|
+
when 'percentencode'
|
79
|
+
value = "{#{value.join(',')}}" if multi?
|
80
|
+
ERB::Util.url_encode(value)
|
81
|
+
|
82
|
+
when 'pipe'
|
83
|
+
return value.join('|') if multi?
|
84
|
+
|
85
|
+
value
|
86
|
+
|
87
|
+
when 'raw'
|
88
|
+
return "{#{value.join(',')}}" if multi?
|
89
|
+
|
90
|
+
value
|
91
|
+
|
92
|
+
when 'regex'
|
93
|
+
if multi?
|
94
|
+
value = value.map { |item| item.gsub(%r{[/$\.\|\\]}, '\\\\' + '\0') }
|
95
|
+
return "(#{value.join('|')})"
|
96
|
+
end
|
97
|
+
value.gsub(%r{[/$\.\|\\]}, '\\\\' + '\0')
|
98
|
+
|
99
|
+
when 'singlequote'
|
100
|
+
if multi?
|
101
|
+
value = value.map { |item| "'#{item.gsub(/[']/, '\\\\' + '\0')}'" }
|
102
|
+
return value.join(',')
|
103
|
+
end
|
104
|
+
"'#{value.gsub(/[']/, '\\\\' + '\0')}'"
|
105
|
+
|
106
|
+
when 'sqlstring'
|
107
|
+
if multi?
|
108
|
+
value = value.map { |item| "'#{item.gsub(/'/, "''")}'" }
|
109
|
+
return value.join(',')
|
110
|
+
end
|
111
|
+
"'#{value.gsub(/'/, "''")}'"
|
112
|
+
|
113
|
+
when 'lucene'
|
114
|
+
if multi?
|
115
|
+
value = value.map { |item| "\"#{item.gsub(%r{[" |=/\\]}, '\\\\' + '\0')}\"" }
|
116
|
+
return "(#{value.join(' OR ')})"
|
117
|
+
end
|
118
|
+
value.gsub(%r{[" |=/\\]}, '\\\\' + '\0')
|
119
|
+
|
120
|
+
when /^date(?:[:](?<format>.*))?$/
|
121
|
+
#TODO validate how grafana handles multivariables with date format
|
122
|
+
get_date_formatted(value, $1)
|
123
|
+
|
124
|
+
when ''
|
125
|
+
# default
|
126
|
+
if multi?
|
127
|
+
value = value.map { |item| "'#{item.gsub(/'/, "''")}'" }
|
128
|
+
return value.join(',')
|
129
|
+
end
|
130
|
+
value.gsub(/'/, "''")
|
131
|
+
|
132
|
+
else
|
133
|
+
# glob and all unknown
|
134
|
+
#TODO add check for array value properly for all cases
|
135
|
+
return "{#{value.join(',')}}" if multi? and value.is_a?(Array)
|
136
|
+
|
137
|
+
value
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# @return [Boolean] true, if the value can contain multiple selections, i.e. is an Array
|
142
|
+
def multi?
|
143
|
+
return @config['multi'] unless @config['multi'].nil?
|
144
|
+
|
145
|
+
@raw_value.is_a? Array
|
146
|
+
end
|
147
|
+
|
148
|
+
# @return [Object] raw value of the variable
|
149
|
+
def raw_value=(new_val)
|
150
|
+
@raw_value = new_val
|
151
|
+
@raw_value = @raw_value.to_s unless @raw_value.is_a?(Array)
|
152
|
+
new_text = @raw_value
|
153
|
+
if @config['options']
|
154
|
+
val = @config['options'].select { |item| item['value'] == @raw_value }
|
155
|
+
new_text = val.first['text'] unless val.empty?
|
156
|
+
end
|
157
|
+
@text = new_text
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
# Realize time formatting according
|
163
|
+
# {https://grafana.com/docs/grafana/latest/variables/variable-types/global-variables/#__from-and-__to}
|
164
|
+
# and {https://momentjs.com/docs/#/displaying/}.
|
165
|
+
def get_date_formatted(value, format)
|
166
|
+
return (Float(value) / 1000).to_i.to_s if format == 'seconds'
|
167
|
+
return Time.at((Float(value) / 1000).to_i).utc.iso8601(3) if !format or format == 'iso'
|
168
|
+
|
169
|
+
# build array of known matches
|
170
|
+
matches = []
|
171
|
+
work_string = format
|
172
|
+
while work_string.length > 0
|
173
|
+
tmp = work_string.scan(/^(?:M{1,4}|D{1,4}|d{1,4}|e|E|w{1,2}|W{1,2}|Y{4}|Y{2}|A|a|H{1,2}|h{1,2}|k{1,2}|m{1,2}|s{1,2}|S+|X)/)
|
174
|
+
unless tmp.empty?
|
175
|
+
matches << tmp[0]
|
176
|
+
work_string.delete_prefix!(tmp[0])
|
177
|
+
else
|
178
|
+
matches << work_string[0]
|
179
|
+
work_string.delete_prefix!(work_string[0])
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
#TODO move case when to hash
|
184
|
+
format_string = ""
|
185
|
+
matches.each do |match|
|
186
|
+
format_string += case match
|
187
|
+
when 'M'
|
188
|
+
'%-m'
|
189
|
+
when 'MM'
|
190
|
+
'%m'
|
191
|
+
when 'MMM'
|
192
|
+
'%b'
|
193
|
+
when 'MMMM'
|
194
|
+
'%B'
|
195
|
+
when 'D'
|
196
|
+
'%-d'
|
197
|
+
when 'DD'
|
198
|
+
'%d'
|
199
|
+
when 'DDD'
|
200
|
+
'%-j'
|
201
|
+
when 'DDDD'
|
202
|
+
'%j'
|
203
|
+
when 'YY'
|
204
|
+
'%y'
|
205
|
+
when 'YYYY'
|
206
|
+
'%Y'
|
207
|
+
when 'd'
|
208
|
+
'%w'
|
209
|
+
when 'ddd'
|
210
|
+
'%a'
|
211
|
+
when 'dddd'
|
212
|
+
'%A'
|
213
|
+
when 'e'
|
214
|
+
'%w'
|
215
|
+
when 'E'
|
216
|
+
'%u'
|
217
|
+
when 'w'
|
218
|
+
'%-U'
|
219
|
+
when 'ww'
|
220
|
+
'%U'
|
221
|
+
when 'W'
|
222
|
+
'%-V'
|
223
|
+
when 'WW'
|
224
|
+
'%V'
|
225
|
+
when 'YY'
|
226
|
+
'%y'
|
227
|
+
when 'YYYY'
|
228
|
+
'%Y'
|
229
|
+
when 'A'
|
230
|
+
'%p'
|
231
|
+
when 'a'
|
232
|
+
'%P'
|
233
|
+
when 'H'
|
234
|
+
'%-H'
|
235
|
+
when 'HH'
|
236
|
+
'%H'
|
237
|
+
when 'h'
|
238
|
+
'%-I'
|
239
|
+
when 'hh'
|
240
|
+
'%I'
|
241
|
+
when 'm'
|
242
|
+
'%-M'
|
243
|
+
when 'mm'
|
244
|
+
'%M'
|
245
|
+
when 's'
|
246
|
+
'%-S'
|
247
|
+
when 'ss'
|
248
|
+
'%S'
|
249
|
+
when 'X'
|
250
|
+
'%s'
|
251
|
+
else
|
252
|
+
match
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
Time.at((Float(value) / 1000).to_i).strftime(format_string)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|