ruby-grafana-reporter 0.4.3 → 0.4.4
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 +337 -203
- data/lib/VERSION.rb +2 -2
- data/lib/grafana/abstract_datasource.rb +6 -3
- data/lib/grafana/errors.rb +2 -2
- data/lib/grafana/grafana_property_datasource.rb +8 -1
- data/lib/grafana/panel.rb +1 -1
- data/lib/grafana/sql_datasource.rb +1 -9
- data/lib/grafana/variable.rb +26 -20
- data/lib/grafana_reporter/abstract_query.rb +37 -19
- data/lib/grafana_reporter/abstract_report.rb +17 -4
- data/lib/grafana_reporter/abstract_table_format_strategy.rb +34 -0
- data/lib/grafana_reporter/alerts_table_query.rb +5 -1
- data/lib/grafana_reporter/annotations_table_query.rb +5 -1
- data/lib/grafana_reporter/application/application.rb +7 -2
- data/lib/grafana_reporter/application/webservice.rb +34 -29
- data/lib/grafana_reporter/asciidoctor/adoc_plain_table_format_strategy.rb +25 -0
- data/lib/grafana_reporter/asciidoctor/alerts_table_include_processor.rb +2 -1
- data/lib/grafana_reporter/asciidoctor/annotations_table_include_processor.rb +2 -1
- data/lib/grafana_reporter/asciidoctor/help.rb +458 -0
- data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +5 -1
- data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +5 -1
- data/lib/grafana_reporter/asciidoctor/panel_property_inline_macro.rb +5 -1
- data/lib/grafana_reporter/asciidoctor/panel_query_table_include_processor.rb +5 -1
- data/lib/grafana_reporter/asciidoctor/panel_query_value_inline_macro.rb +3 -0
- data/lib/grafana_reporter/asciidoctor/processor_mixin.rb +3 -2
- data/lib/grafana_reporter/asciidoctor/report.rb +15 -13
- data/lib/grafana_reporter/asciidoctor/show_help_include_processor.rb +1 -1
- data/lib/grafana_reporter/asciidoctor/sql_table_include_processor.rb +5 -1
- data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +5 -1
- data/lib/grafana_reporter/console_configuration_wizard.rb +2 -2
- data/lib/grafana_reporter/csv_table_format_strategy.rb +23 -0
- data/lib/grafana_reporter/demo_report_wizard.rb +0 -1
- data/lib/grafana_reporter/erb/demo_report_builder.rb +46 -0
- data/lib/grafana_reporter/erb/report.rb +13 -7
- data/lib/grafana_reporter/errors.rb +9 -7
- data/lib/grafana_reporter/query_value_query.rb +1 -1
- data/lib/grafana_reporter/report_webhook.rb +12 -8
- metadata +7 -3
- data/lib/grafana_reporter/help.rb +0 -443
data/lib/VERSION.rb
CHANGED
@@ -42,12 +42,12 @@ module Grafana
|
|
42
42
|
@model = model
|
43
43
|
end
|
44
44
|
|
45
|
-
# @return [String] category of the datasource, e.g.
|
45
|
+
# @return [String] category of the datasource, e.g. +tsdb+ or +sql+
|
46
46
|
def category
|
47
47
|
@model['meta']['category']
|
48
48
|
end
|
49
49
|
|
50
|
-
# @return [String] type of the datasource, e.g.
|
50
|
+
# @return [String] type of the datasource, e.g. +mysql+
|
51
51
|
def type
|
52
52
|
@model['type'] || @model['meta']['id']
|
53
53
|
end
|
@@ -126,10 +126,13 @@ module Grafana
|
|
126
126
|
while repeat && (repeat_count < 3)
|
127
127
|
repeat = false
|
128
128
|
repeat_count += 1
|
129
|
+
|
129
130
|
variables.each do |name, variable|
|
130
131
|
# only set ticks if value is string
|
131
132
|
var_name = name.gsub(/^var-/, '')
|
132
|
-
|
133
|
+
next unless var_name =~ /^\w+$/
|
134
|
+
|
135
|
+
res = res.gsub(/(?:\$\{#{var_name}(?::(?<format>\w+))?\}|\$#{var_name}(?!\w))/) do
|
133
136
|
format = default_variable_format
|
134
137
|
if $LAST_MATCH_INFO
|
135
138
|
format = $LAST_MATCH_INFO[:format] if $LAST_MATCH_INFO[:format]
|
data/lib/grafana/errors.rb
CHANGED
@@ -37,9 +37,9 @@ module Grafana
|
|
37
37
|
|
38
38
|
# Raised if a given datasource does not exist in a specific {Grafana} instance.
|
39
39
|
class DatasourceDoesNotExistError < GrafanaError
|
40
|
-
# @param field [String] specifies, how the datasource has been searched, e.g.
|
40
|
+
# @param field [String] specifies, how the datasource has been searched, e.g. +id+ or +name+
|
41
41
|
# @param datasource_identifier [String] identifier of the datasource, which could not be found,
|
42
|
-
# e.g. the
|
42
|
+
# e.g. the specified id or name
|
43
43
|
def initialize(field, datasource_identifier)
|
44
44
|
super("Datasource with #{field} '#{datasource_identifier}' does not exist.")
|
45
45
|
end
|
@@ -16,6 +16,8 @@ module Grafana
|
|
16
16
|
panel = query_description[:raw_query][:panel]
|
17
17
|
property_name = query_description[:raw_query][:property_name]
|
18
18
|
|
19
|
+
return "Panel property '#{property_name}' does not exist for panel '#{panel.id}'" unless panel.field(property_name)
|
20
|
+
|
19
21
|
{
|
20
22
|
header: [query_description[:raw_query][:property_name]],
|
21
23
|
content: [replace_variables(panel.field(property_name), query_description[:variables])]
|
@@ -24,7 +26,12 @@ module Grafana
|
|
24
26
|
|
25
27
|
# @see AbstractDatasource#default_variable_format
|
26
28
|
def default_variable_format
|
27
|
-
|
29
|
+
'glob'
|
30
|
+
end
|
31
|
+
|
32
|
+
# @see AbstractDatasource#name
|
33
|
+
def name
|
34
|
+
self.class.to_s
|
28
35
|
end
|
29
36
|
end
|
30
37
|
end
|
data/lib/grafana/panel.rb
CHANGED
@@ -19,7 +19,7 @@ module Grafana
|
|
19
19
|
body: {
|
20
20
|
from: query_description[:from],
|
21
21
|
to: query_description[:to],
|
22
|
-
queries: [rawSql:
|
22
|
+
queries: [rawSql: sql, datasourceId: id, format: 'table']
|
23
23
|
}.to_json,
|
24
24
|
request: Net::HTTP::Post
|
25
25
|
}
|
@@ -66,13 +66,5 @@ module Grafana
|
|
66
66
|
|
67
67
|
results
|
68
68
|
end
|
69
|
-
|
70
|
-
def prepare_sql(sql)
|
71
|
-
# remove comments in query
|
72
|
-
sql.gsub!(/--[^\r\n]*(?:[\r\n]+|$)/, ' ')
|
73
|
-
sql.gsub!(/\r\n/, ' ')
|
74
|
-
sql.gsub!(/\n/, ' ')
|
75
|
-
sql
|
76
|
-
end
|
77
69
|
end
|
78
70
|
end
|
data/lib/grafana/variable.rb
CHANGED
@@ -63,92 +63,96 @@ module Grafana
|
|
63
63
|
value = @raw_value
|
64
64
|
|
65
65
|
# handle value 'All' properly
|
66
|
-
|
67
|
-
if (value == 'All') || (@text == 'All')
|
66
|
+
if value == '$__all'
|
68
67
|
if !@config['options'].empty?
|
68
|
+
# this query contains predefined values, so capture them and format the values accordingly
|
69
|
+
# this happens either if type='custom' or a query, which is never updated
|
69
70
|
value = @config['options'].map { |item| item['value'] }
|
70
|
-
|
71
|
-
|
71
|
+
|
72
|
+
elsif @config['type'] == 'query' && !@config['query'].empty?
|
73
|
+
# TODO: replace variables in query, execute it and evaluate the results as if they were normally selected
|
74
|
+
# "multiFormat": contains variable replacement in "query", e.g. 'regex values' or 'glob'
|
75
|
+
# "datasource": contains name of datasource
|
72
76
|
return @config['query']
|
73
|
-
|
77
|
+
|
74
78
|
else
|
75
|
-
# TODO:
|
79
|
+
# TODO: add support for variable type: 'datasource' and 'adhoc'
|
76
80
|
end
|
77
81
|
end
|
78
82
|
|
79
83
|
case format
|
80
84
|
when 'csv'
|
81
|
-
return value.join(',').to_s if multi?
|
85
|
+
return value.join(',').to_s if multi? && value.is_a?(Array)
|
82
86
|
|
83
87
|
value.to_s
|
84
88
|
|
85
89
|
when 'distributed'
|
86
|
-
return value.join(",#{name}=") if multi?
|
90
|
+
return value.join(",#{name}=") if multi? && value.is_a?(Array)
|
87
91
|
|
88
92
|
value
|
89
93
|
when 'doublequote'
|
90
|
-
if multi?
|
94
|
+
if multi? && value.is_a?(Array)
|
91
95
|
value = value.map { |item| "\"#{item.gsub(/\\/, '\\\\').gsub(/"/, '\\"')}\"" }
|
92
96
|
return value.join(',')
|
93
97
|
end
|
94
98
|
"\"#{value.gsub(/"/, '\\"')}\""
|
95
99
|
|
96
100
|
when 'json'
|
97
|
-
if multi?
|
101
|
+
if multi? && value.is_a?(Array)
|
98
102
|
value = value.map { |item| "\"#{item.gsub(/["\\]/, '\\\\\0')}\"" }
|
99
103
|
return "[#{value.join(',')}]"
|
100
104
|
end
|
101
105
|
"\"#{value.gsub(/"/, '\\"')}\""
|
102
106
|
|
103
107
|
when 'percentencode'
|
104
|
-
value = "{#{value.join(',')}}" if multi?
|
108
|
+
value = "{#{value.join(',')}}" if multi? && value.is_a?(Array)
|
105
109
|
ERB::Util.url_encode(value)
|
106
110
|
|
107
111
|
when 'pipe'
|
108
|
-
return value.join('|') if multi?
|
112
|
+
return value.join('|') if multi? && value.is_a?(Array)
|
109
113
|
|
110
114
|
value
|
111
115
|
|
112
116
|
when 'raw'
|
113
|
-
return "{#{value.join(',')}}" if multi?
|
117
|
+
return "{#{value.join(',')}}" if multi? && value.is_a?(Array)
|
114
118
|
|
115
119
|
value
|
116
120
|
|
117
121
|
when 'regex'
|
118
|
-
if multi?
|
122
|
+
if multi? && value.is_a?(Array)
|
119
123
|
value = value.map { |item| item.gsub(%r{[/$.|\\]}, '\\\\\0') }
|
120
124
|
return "(#{value.join('|')})"
|
121
125
|
end
|
122
126
|
value.gsub(%r{[/$.|\\]}, '\\\\\0')
|
123
127
|
|
124
128
|
when 'singlequote'
|
125
|
-
if multi?
|
129
|
+
if multi? && value.is_a?(Array)
|
126
130
|
value = value.map { |item| "'#{item.gsub(/'/, '\\\\\0')}'" }
|
127
131
|
return value.join(',')
|
128
132
|
end
|
129
133
|
"'#{value.gsub(/'/, '\\\\\0')}'"
|
130
134
|
|
131
135
|
when 'sqlstring'
|
132
|
-
if multi?
|
136
|
+
if multi? && value.is_a?(Array)
|
133
137
|
value = value.map { |item| "'#{item.gsub(/'/, "''")}'" }
|
134
138
|
return value.join(',')
|
135
139
|
end
|
136
140
|
"'#{value.gsub(/'/, "''")}'"
|
137
141
|
|
138
142
|
when 'lucene'
|
139
|
-
if multi?
|
143
|
+
if multi? && value.is_a?(Array)
|
140
144
|
value = value.map { |item| "\"#{item.gsub(%r{[" |=/\\]}, '\\\\\0')}\"" }
|
141
145
|
return "(#{value.join(' OR ')})"
|
142
146
|
end
|
143
147
|
value.gsub(%r{[" |=/\\]}, '\\\\\0')
|
144
148
|
|
145
149
|
when /^date(?::(?<format>.*))?$/
|
146
|
-
# TODO:
|
150
|
+
# TODO: grafana does not seem to allow multivariables with date format - so properly handle here as well
|
147
151
|
get_date_formatted(value, Regexp.last_match(1))
|
148
152
|
|
149
153
|
when ''
|
150
154
|
# default
|
151
|
-
if multi?
|
155
|
+
if multi? && value.is_a?(Array)
|
152
156
|
value = value.map { |item| "'#{item.gsub(/'/, "''")}'" }
|
153
157
|
return value.join(',')
|
154
158
|
end
|
@@ -162,7 +166,7 @@ module Grafana
|
|
162
166
|
end
|
163
167
|
end
|
164
168
|
|
165
|
-
# @return [Boolean] true, if the value can contain multiple selections, i.e.
|
169
|
+
# @return [Boolean] true, if the value can contain multiple selections, i.e. can contain an Array
|
166
170
|
def multi?
|
167
171
|
return @config['multi'] unless @config['multi'].nil?
|
168
172
|
|
@@ -196,6 +200,8 @@ module Grafana
|
|
196
200
|
until work_string.empty?
|
197
201
|
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}|
|
198
202
|
h{1,2}|k{1,2}|m{1,2}|s{1,2}|S+|X)/x)
|
203
|
+
|
204
|
+
# TODO: add test for sub! and switch to non-modifying frozen string action
|
199
205
|
if tmp.empty?
|
200
206
|
matches << work_string[0]
|
201
207
|
work_string.sub!(/^#{work_string[0]}/, '')
|
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'abstract_table_format_strategy'
|
4
|
+
require_relative 'csv_table_format_strategy'
|
5
|
+
|
3
6
|
module GrafanaReporter
|
4
7
|
# @abstract Override {#pre_process} and {#post_process} in subclass.
|
5
8
|
#
|
@@ -89,6 +92,7 @@ module GrafanaReporter
|
|
89
92
|
end
|
90
93
|
|
91
94
|
raise DatasourceRequestInvalidReturnValueError.new(@datasource, @result) unless datasource_response_valid?
|
95
|
+
|
92
96
|
post_process
|
93
97
|
@result
|
94
98
|
end
|
@@ -216,7 +220,6 @@ module GrafanaReporter
|
|
216
220
|
# @param result [Hash] preformatted query result (see {Grafana::AbstractDatasource#request}.
|
217
221
|
# @param configs [Array<Grafana::Variable>] one variable for replacing values in one column
|
218
222
|
# @return [Hash] query result with replaced values
|
219
|
-
# TODO: make sure that caught errors are also visible in logger
|
220
223
|
def replace_values(result, configs)
|
221
224
|
return result if configs.empty?
|
222
225
|
|
@@ -231,8 +234,11 @@ module GrafanaReporter
|
|
231
234
|
|
232
235
|
k = arr[0]
|
233
236
|
v = arr[1]
|
234
|
-
|
235
|
-
|
237
|
+
|
238
|
+
# allow keys and values to contain escaped colons or commas
|
239
|
+
k = k.gsub(/\\([:,])/, '\1')
|
240
|
+
v = v.gsub(/\\([:,])/, '\1')
|
241
|
+
|
236
242
|
result[:content].map do |row|
|
237
243
|
(row.length - 1).downto 0 do |i|
|
238
244
|
if cols.include?(i + 1) || cols.empty?
|
@@ -267,6 +273,7 @@ module GrafanaReporter
|
|
267
273
|
end
|
268
274
|
end
|
269
275
|
rescue StandardError => e
|
276
|
+
@grafana.logger.error(e.message)
|
270
277
|
row[i] = e.message
|
271
278
|
end
|
272
279
|
end
|
@@ -284,22 +291,32 @@ module GrafanaReporter
|
|
284
291
|
result
|
285
292
|
end
|
286
293
|
|
287
|
-
# Used to build a output
|
294
|
+
# Used to build a table output in a custom format.
|
288
295
|
# @param result [Hash] preformatted sql hash, (see {Grafana::AbstractDatasource#request})
|
289
296
|
# @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 [
|
293
|
-
# @option opts [
|
294
|
-
# @return [String]
|
297
|
+
# @option opts [Grafana::Variable] :row_divider requested row divider for the result table, only to be used with table_formatter `adoc_deprecated`
|
298
|
+
# @option opts [Grafana::Variable] :column_divider requested row divider for the result table, only to be used with table_formatter `adoc_deprecated`
|
299
|
+
# @option opts [Grafana::Variable] :include_headline specifies if table should contain headline, defaults to false
|
300
|
+
# @option opts [Grafana::Variable] :table_formatter specifies which formatter shall be used, defaults to 'csv'
|
301
|
+
# @return [String] table in custom output format
|
295
302
|
def format_table_output(result, opts)
|
296
|
-
opts = {
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
303
|
+
opts = { include_headline: Grafana::Variable.new('false'),
|
304
|
+
table_formatter: Grafana::Variable.new('csv'),
|
305
|
+
row_divider: Grafana::Variable.new('| '),
|
306
|
+
column_divider: Grafana::Variable.new(' | ') }.merge(opts.delete_if {|_k, v| v.nil? })
|
307
|
+
|
308
|
+
if opts[:table_formatter].raw_value == 'adoc_deprecated'
|
309
|
+
@grafana.logger.warn("You are using deprecated 'table_formatter' named 'adoc_deprecated', which will be "\
|
310
|
+
"removed in a future version. Start using 'adoc' or register your own implementation "\
|
311
|
+
"of AbstractTableFormatStrategy.")
|
312
|
+
return result[:content].map do |row|
|
313
|
+
opts[:row_divider].raw_value + row.map do |item|
|
314
|
+
item.to_s.gsub('|', '\\|')
|
315
|
+
end.join(opts[:column_divider].raw_value)
|
316
|
+
end
|
302
317
|
end
|
318
|
+
|
319
|
+
AbstractTableFormatStrategy.get(opts[:table_formatter].raw_value).format(result, opts[:include_headline].raw_value.downcase == 'true')
|
303
320
|
end
|
304
321
|
|
305
322
|
# Used to translate the relative date strings used by grafana, e.g. +now-5d/w+ to the
|
@@ -317,9 +334,10 @@ module GrafanaReporter
|
|
317
334
|
# @param timezone [Grafana::Variable] timezone to use, if not system timezone
|
318
335
|
# @return [String] translated date as timestamp string
|
319
336
|
def translate_date(orig_date, report_time, is_to_time, timezone = nil)
|
320
|
-
#
|
337
|
+
@grafana.logger.warn("#translate_date has been called without 'report_time' - using current time as fallback.") unless report_time
|
321
338
|
report_time ||= ::Grafana::Variable.new(Time.now.to_s)
|
322
339
|
orig_date = orig_date.raw_value if orig_date.is_a?(Grafana::Variable)
|
340
|
+
|
323
341
|
return (DateTime.parse(report_time.raw_value).to_time.to_i * 1000).to_s unless orig_date
|
324
342
|
return orig_date if orig_date =~ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
|
325
343
|
return orig_date if orig_date =~ /^\d+$/
|
@@ -387,10 +405,10 @@ module GrafanaReporter
|
|
387
405
|
def datasource_response_valid?
|
388
406
|
return false if @result.nil?
|
389
407
|
return false unless @result.is_a?(Hash)
|
390
|
-
# TODO:
|
408
|
+
# TODO: compare how empty valid responses look like in grafana
|
391
409
|
return true if @result.empty?
|
392
|
-
return false unless @result.
|
393
|
-
return false unless @result.
|
410
|
+
return false unless @result.key?(:header)
|
411
|
+
return false unless @result.key?(:content)
|
394
412
|
return false unless @result[:header].is_a?(Array)
|
395
413
|
return false unless @result[:content].is_a?(Array)
|
396
414
|
|
@@ -126,7 +126,7 @@ module GrafanaReporter
|
|
126
126
|
|
127
127
|
# Is being called to start the report generation. To execute the specific report generation, this function
|
128
128
|
# calls the abstract {#build} method with the given parameters.
|
129
|
-
# @param template [String] path to the template to be used, trailing
|
129
|
+
# @param template [String] path to the template to be used, trailing extension may be omitted, whereas {#default_template_extension} will be appended
|
130
130
|
# @param destination_file_or_path [String or File] path to the destination report or file object to use
|
131
131
|
# @param custom_attributes [Hash] custom attributes, which shall be merged with priority over the configuration
|
132
132
|
# @return [void]
|
@@ -136,14 +136,14 @@ module GrafanaReporter
|
|
136
136
|
@destination_file_or_path = destination_file_or_path
|
137
137
|
@custom_attributes = custom_attributes
|
138
138
|
|
139
|
-
# automatically add extension, if a file with
|
140
|
-
@template = "#{@template}.
|
139
|
+
# automatically add extension, if a file with default template extension exists
|
140
|
+
@template = "#{@template}.#{self.class.default_template_extension}" if File.file?("#{@template}.#{self.class.default_template_extension}") && !File.file?(@template.to_s)
|
141
141
|
raise MissingTemplateError, @template.to_s unless File.file?(@template.to_s)
|
142
142
|
|
143
143
|
notify(:on_before_create)
|
144
144
|
@start_time = Time.new
|
145
145
|
logger.info("Report started at #{@start_time}")
|
146
|
-
build
|
146
|
+
build
|
147
147
|
rescue MissingTemplateError => e
|
148
148
|
@logger.error(e.message)
|
149
149
|
@error = [e.message]
|
@@ -186,6 +186,18 @@ module GrafanaReporter
|
|
186
186
|
raise NotImplementedError
|
187
187
|
end
|
188
188
|
|
189
|
+
# @abstract
|
190
|
+
# @return [String] specifying the default extension of a template file
|
191
|
+
def self.default_template_extension
|
192
|
+
raise NotImplementedError
|
193
|
+
end
|
194
|
+
|
195
|
+
# @abstract
|
196
|
+
# @return [String] specifying the default extension of a rendered result file
|
197
|
+
def self.default_result_extension
|
198
|
+
raise NotImplementedError
|
199
|
+
end
|
200
|
+
|
189
201
|
private
|
190
202
|
|
191
203
|
# Called, if the report generation has died with an error.
|
@@ -207,6 +219,7 @@ module GrafanaReporter
|
|
207
219
|
def done!
|
208
220
|
return if @done
|
209
221
|
|
222
|
+
@destination_file_or_path.close if @destination_file_or_path.is_a?(File)
|
210
223
|
@done = true
|
211
224
|
@end_time = Time.new
|
212
225
|
@start_time ||= @end_time
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GrafanaReporter
|
4
|
+
# The abstract base class, which is to be implemented for different table
|
5
|
+
# output formats. By implementing this class, you e.g. can decide if a table
|
6
|
+
# will be formatted as CSV, JSON or any other format.
|
7
|
+
class AbstractTableFormatStrategy
|
8
|
+
@@subclasses = []
|
9
|
+
|
10
|
+
def self.inherited(obj)
|
11
|
+
@@subclasses << obj
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param abbreviation [String] name of the requested table format strategy
|
15
|
+
# @return [AbstractTableFormatStrategy] fitting strategy instance for the given name
|
16
|
+
def self.get(abbreviation)
|
17
|
+
@@subclasses.select { |item| item.abbreviation == abbreviation }.first.new
|
18
|
+
end
|
19
|
+
|
20
|
+
# @abstract
|
21
|
+
# @return [String] short name of the current stategy, under which it shall be accessible
|
22
|
+
def self.abbreviation
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
|
26
|
+
# @abstract
|
27
|
+
# @param column [Array] datasource table result
|
28
|
+
# @param include_headline [Boolean] true, if headline should be included in result
|
29
|
+
# @return [String] formatted in table format
|
30
|
+
def format(content, include_headline)
|
31
|
+
raise NotImplementedError
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|