ruby-grafana-reporter 0.4.2 → 0.5.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.
- checksums.yaml +4 -4
- data/README.md +144 -11
- data/lib/VERSION.rb +2 -2
- data/lib/grafana/abstract_datasource.rb +9 -3
- data/lib/grafana/dashboard.rb +6 -1
- data/lib/grafana/errors.rb +4 -11
- data/lib/grafana/grafana.rb +25 -1
- data/lib/grafana/grafana_environment_datasource.rb +56 -0
- data/lib/grafana/grafana_property_datasource.rb +8 -1
- data/lib/grafana/image_rendering_datasource.rb +5 -1
- data/lib/grafana/influxdb_datasource.rb +87 -3
- data/lib/grafana/panel.rb +1 -1
- data/lib/grafana/prometheus_datasource.rb +11 -2
- data/lib/grafana/sql_datasource.rb +3 -9
- data/lib/grafana/variable.rb +67 -36
- data/lib/grafana/webrequest.rb +1 -0
- data/lib/grafana_reporter/abstract_query.rb +65 -39
- data/lib/grafana_reporter/abstract_report.rb +19 -4
- data/lib/grafana_reporter/abstract_table_format_strategy.rb +74 -0
- data/lib/grafana_reporter/alerts_table_query.rb +6 -1
- data/lib/grafana_reporter/annotations_table_query.rb +6 -1
- data/lib/grafana_reporter/application/application.rb +7 -2
- data/lib/grafana_reporter/application/webservice.rb +41 -32
- data/lib/grafana_reporter/asciidoctor/adoc_plain_table_format_strategy.rb +27 -0
- data/lib/grafana_reporter/asciidoctor/alerts_table_include_processor.rb +3 -2
- data/lib/grafana_reporter/asciidoctor/annotations_table_include_processor.rb +3 -2
- data/lib/grafana_reporter/asciidoctor/help.rb +470 -0
- data/lib/grafana_reporter/asciidoctor/panel_image_block_macro.rb +7 -5
- data/lib/grafana_reporter/asciidoctor/panel_image_inline_macro.rb +7 -5
- data/lib/grafana_reporter/asciidoctor/panel_property_inline_macro.rb +5 -1
- data/lib/grafana_reporter/asciidoctor/panel_query_table_include_processor.rb +6 -2
- 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_environment_include_processor.rb +37 -6
- data/lib/grafana_reporter/asciidoctor/show_help_include_processor.rb +1 -1
- data/lib/grafana_reporter/asciidoctor/sql_table_include_processor.rb +6 -2
- data/lib/grafana_reporter/asciidoctor/sql_value_inline_macro.rb +5 -1
- data/lib/grafana_reporter/asciidoctor/value_as_variable_include_processor.rb +0 -5
- data/lib/grafana_reporter/configuration.rb +27 -0
- data/lib/grafana_reporter/console_configuration_wizard.rb +5 -3
- data/lib/grafana_reporter/csv_table_format_strategy.rb +25 -0
- data/lib/grafana_reporter/demo_report_wizard.rb +3 -7
- 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/panel_image_query.rb +1 -1
- data/lib/grafana_reporter/query_value_query.rb +7 -1
- data/lib/grafana_reporter/report_webhook.rb +12 -8
- data/lib/grafana_reporter/reporter_environment_datasource.rb +24 -0
- metadata +9 -3
- data/lib/grafana_reporter/help.rb +0 -443
    
        data/lib/grafana/variable.rb
    CHANGED
    
    | @@ -29,14 +29,14 @@ module Grafana | |
| 29 29 |  | 
| 30 30 | 
             
                # @param config_or_value [Hash, Object] configuration hash of a variable out of an {Dashboard} instance
         | 
| 31 31 | 
             
                #  or a value of any kind.
         | 
| 32 | 
            -
                 | 
| 32 | 
            +
                # @param dashboard [Dashboard] parent dashboard, if applicable; especially needed for query variable
         | 
| 33 | 
            +
                #  evaluation.
         | 
| 34 | 
            +
                def initialize(config_or_value, dashboard = nil)
         | 
| 33 35 | 
             
                  if config_or_value.is_a? Hash
         | 
| 36 | 
            +
                    @dashboard = dashboard
         | 
| 34 37 | 
             
                    @config = config_or_value
         | 
| 35 38 | 
             
                    @name = @config['name']
         | 
| 36 | 
            -
                     | 
| 37 | 
            -
                      @raw_value = @config['current']['value']
         | 
| 38 | 
            -
                      @text = @config['current']['text']
         | 
| 39 | 
            -
                    end
         | 
| 39 | 
            +
                    init_values
         | 
| 40 40 | 
             
                  else
         | 
| 41 41 | 
             
                    @config = {}
         | 
| 42 42 | 
             
                    @raw_value = config_or_value
         | 
| @@ -62,93 +62,105 @@ module Grafana | |
| 62 62 | 
             
                def value_formatted(format = '')
         | 
| 63 63 | 
             
                  value = @raw_value
         | 
| 64 64 |  | 
| 65 | 
            -
                  #  | 
| 66 | 
            -
                  #  | 
| 67 | 
            -
                  if  | 
| 65 | 
            +
                  # if 'All' is selected for this template variable, capture all values properly
         | 
| 66 | 
            +
                  # (from grafana config or query) and format the results afterwards accordingly
         | 
| 67 | 
            +
                  if value == '$__all'
         | 
| 68 68 | 
             
                    if !@config['options'].empty?
         | 
| 69 | 
            -
                       | 
| 70 | 
            -
             | 
| 71 | 
            -
                       | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 69 | 
            +
                      # this query contains predefined values, so capture them and format the values accordingly
         | 
| 70 | 
            +
                      # this happens either for type='custom' or type 'query', if it is never updated
         | 
| 71 | 
            +
                      value = @config['options'].reject { |item| item['value'] == '$__all' }.map { |item| item['value'] }
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    elsif @config['type'] == 'query' && !@config['query'].empty?
         | 
| 74 | 
            +
                      # variables in this configuration are not stored in grafana, i.e. if all is selected here,
         | 
| 75 | 
            +
                      # the values have to be fetched from the datasource
         | 
| 76 | 
            +
                      query = ::GrafanaReporter::QueryValueQuery.new(@dashboard)
         | 
| 77 | 
            +
                      query.datasource = @dashboard.grafana.datasource_by_name(@config['datasource'])
         | 
| 78 | 
            +
                      query.variables['result_type'] = Variable.new('object')
         | 
| 79 | 
            +
                      query.raw_query = @config['query']
         | 
| 80 | 
            +
                      result = query.execute
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                      value = result[:content].map { |item| item[0].to_s }
         | 
| 83 | 
            +
             | 
| 74 84 | 
             
                    else
         | 
| 75 | 
            -
                      # TODO:  | 
| 85 | 
            +
                      # TODO: add support for variable type: 'datasource' and 'adhoc'
         | 
| 76 86 | 
             
                    end
         | 
| 77 87 | 
             
                  end
         | 
| 78 88 |  | 
| 79 89 | 
             
                  case format
         | 
| 80 90 | 
             
                  when 'csv'
         | 
| 81 | 
            -
                    return value.join(',').to_s if multi?
         | 
| 91 | 
            +
                    return value.join(',').to_s if multi? && value.is_a?(Array)
         | 
| 82 92 |  | 
| 83 93 | 
             
                    value.to_s
         | 
| 84 94 |  | 
| 85 95 | 
             
                  when 'distributed'
         | 
| 86 | 
            -
                    return value.join(",#{name}=") if multi?
         | 
| 96 | 
            +
                    return value.join(",#{name}=") if multi? && value.is_a?(Array)
         | 
| 87 97 |  | 
| 88 98 | 
             
                    value
         | 
| 89 99 | 
             
                  when 'doublequote'
         | 
| 90 | 
            -
                    if multi?
         | 
| 100 | 
            +
                    if multi? && value.is_a?(Array)
         | 
| 91 101 | 
             
                      value = value.map { |item| "\"#{item.gsub(/\\/, '\\\\').gsub(/"/, '\\"')}\"" }
         | 
| 92 102 | 
             
                      return value.join(',')
         | 
| 93 103 | 
             
                    end
         | 
| 94 104 | 
             
                    "\"#{value.gsub(/"/, '\\"')}\""
         | 
| 95 105 |  | 
| 96 106 | 
             
                  when 'json'
         | 
| 97 | 
            -
                    if multi?
         | 
| 107 | 
            +
                    if multi? && value.is_a?(Array)
         | 
| 98 108 | 
             
                      value = value.map { |item| "\"#{item.gsub(/["\\]/, '\\\\\0')}\"" }
         | 
| 99 109 | 
             
                      return "[#{value.join(',')}]"
         | 
| 100 110 | 
             
                    end
         | 
| 101 111 | 
             
                    "\"#{value.gsub(/"/, '\\"')}\""
         | 
| 102 112 |  | 
| 103 113 | 
             
                  when 'percentencode'
         | 
| 104 | 
            -
                    value = "{#{value.join(',')}}" if multi?
         | 
| 114 | 
            +
                    value = "{#{value.join(',')}}" if multi? && value.is_a?(Array)
         | 
| 105 115 | 
             
                    ERB::Util.url_encode(value)
         | 
| 106 116 |  | 
| 107 117 | 
             
                  when 'pipe'
         | 
| 108 | 
            -
                    return value.join('|') if multi?
         | 
| 118 | 
            +
                    return value.join('|') if multi? && value.is_a?(Array)
         | 
| 109 119 |  | 
| 110 120 | 
             
                    value
         | 
| 111 121 |  | 
| 112 122 | 
             
                  when 'raw'
         | 
| 113 | 
            -
                    return "{#{value.join(',')}}" if multi?
         | 
| 123 | 
            +
                    return "{#{value.join(',')}}" if multi? && value.is_a?(Array)
         | 
| 114 124 |  | 
| 115 125 | 
             
                    value
         | 
| 116 126 |  | 
| 117 127 | 
             
                  when 'regex'
         | 
| 118 | 
            -
                    if multi?
         | 
| 128 | 
            +
                    if multi? && value.is_a?(Array)
         | 
| 119 129 | 
             
                      value = value.map { |item| item.gsub(%r{[/$.|\\]}, '\\\\\0') }
         | 
| 120 130 | 
             
                      return "(#{value.join('|')})"
         | 
| 121 131 | 
             
                    end
         | 
| 122 132 | 
             
                    value.gsub(%r{[/$.|\\]}, '\\\\\0')
         | 
| 123 133 |  | 
| 124 134 | 
             
                  when 'singlequote'
         | 
| 125 | 
            -
                    if multi?
         | 
| 135 | 
            +
                    if multi? && value.is_a?(Array)
         | 
| 126 136 | 
             
                      value = value.map { |item| "'#{item.gsub(/'/, '\\\\\0')}'" }
         | 
| 127 137 | 
             
                      return value.join(',')
         | 
| 128 138 | 
             
                    end
         | 
| 129 139 | 
             
                    "'#{value.gsub(/'/, '\\\\\0')}'"
         | 
| 130 140 |  | 
| 131 141 | 
             
                  when 'sqlstring'
         | 
| 132 | 
            -
                    if multi?
         | 
| 142 | 
            +
                    if multi? && value.is_a?(Array)
         | 
| 133 143 | 
             
                      value = value.map { |item| "'#{item.gsub(/'/, "''")}'" }
         | 
| 134 144 | 
             
                      return value.join(',')
         | 
| 135 145 | 
             
                    end
         | 
| 136 146 | 
             
                    "'#{value.gsub(/'/, "''")}'"
         | 
| 137 147 |  | 
| 138 148 | 
             
                  when 'lucene'
         | 
| 139 | 
            -
                    if multi?
         | 
| 149 | 
            +
                    if multi? && value.is_a?(Array)
         | 
| 140 150 | 
             
                      value = value.map { |item| "\"#{item.gsub(%r{[" |=/\\]}, '\\\\\0')}\"" }
         | 
| 141 151 | 
             
                      return "(#{value.join(' OR ')})"
         | 
| 142 152 | 
             
                    end
         | 
| 143 153 | 
             
                    value.gsub(%r{[" |=/\\]}, '\\\\\0')
         | 
| 144 154 |  | 
| 145 155 | 
             
                  when /^date(?::(?<format>.*))?$/
         | 
| 146 | 
            -
                     | 
| 147 | 
            -
             | 
| 156 | 
            +
                    if multi? && value.is_a?(Array)
         | 
| 157 | 
            +
                      raise GrafanaError, "Date format cannot be specified for a variable containing an array of values"
         | 
| 158 | 
            +
                    end
         | 
| 159 | 
            +
                    Variable.format_as_date(value, Regexp.last_match(1))
         | 
| 148 160 |  | 
| 149 161 | 
             
                  when ''
         | 
| 150 162 | 
             
                    # default
         | 
| 151 | 
            -
                    if multi?
         | 
| 163 | 
            +
                    if multi? && value.is_a?(Array)
         | 
| 152 164 | 
             
                      value = value.map { |item| "'#{item.gsub(/'/, "''")}'" }
         | 
| 153 165 | 
             
                      return value.join(',')
         | 
| 154 166 | 
             
                    end
         | 
| @@ -162,8 +174,9 @@ module Grafana | |
| 162 174 | 
             
                  end
         | 
| 163 175 | 
             
                end
         | 
| 164 176 |  | 
| 165 | 
            -
                # @return [Boolean] true, if the value can contain multiple selections, i.e.  | 
| 177 | 
            +
                # @return [Boolean] true, if the value can contain multiple selections, i.e. can contain an Array or does contain all
         | 
| 166 178 | 
             
                def multi?
         | 
| 179 | 
            +
                  return true if @raw_value == '$__all'
         | 
| 167 180 | 
             
                  return @config['multi'] unless @config['multi'].nil?
         | 
| 168 181 |  | 
| 169 182 | 
             
                  @raw_value.is_a? Array
         | 
| @@ -181,12 +194,13 @@ module Grafana | |
| 181 194 | 
             
                  @text = new_text
         | 
| 182 195 | 
             
                end
         | 
| 183 196 |  | 
| 184 | 
            -
                 | 
| 185 | 
            -
             | 
| 186 | 
            -
                # Realize time formatting according
         | 
| 197 | 
            +
                # Applies the date format according
         | 
| 187 198 | 
             
                # {https://grafana.com/docs/grafana/latest/variables/variable-types/global-variables/#__from-and-__to}
         | 
| 188 | 
            -
                # and {https://momentjs.com/docs/#/displaying/}.
         | 
| 189 | 
            -
                 | 
| 199 | 
            +
                # and {https://momentjs.com/docs/#/displaying/} to a given value.
         | 
| 200 | 
            +
                # @param value [String] time as milliseconds to be formatted
         | 
| 201 | 
            +
                # @param format [String] format string in which the time value shall be returned
         | 
| 202 | 
            +
                # @return [String] time converted to the specified time format
         | 
| 203 | 
            +
                def self.format_as_date(value, format)
         | 
| 190 204 | 
             
                  return (Float(value) / 1000).to_i.to_s if format == 'seconds'
         | 
| 191 205 | 
             
                  return Time.at((Float(value) / 1000).to_i).utc.iso8601(3) if !format || (format == 'iso')
         | 
| 192 206 |  | 
| @@ -196,12 +210,13 @@ module Grafana | |
| 196 210 | 
             
                  until work_string.empty?
         | 
| 197 211 | 
             
                    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 212 | 
             
                                                h{1,2}|k{1,2}|m{1,2}|s{1,2}|S+|X)/x)
         | 
| 213 | 
            +
             | 
| 199 214 | 
             
                    if tmp.empty?
         | 
| 200 215 | 
             
                      matches << work_string[0]
         | 
| 201 | 
            -
                      work_string.sub | 
| 216 | 
            +
                      work_string = work_string.sub(/^#{work_string[0]}/, '')
         | 
| 202 217 | 
             
                    else
         | 
| 203 218 | 
             
                      matches << tmp[0]
         | 
| 204 | 
            -
                      work_string.sub | 
| 219 | 
            +
                      work_string = work_string.sub(/^#{tmp[0]}/, '')
         | 
| 205 220 | 
             
                    end
         | 
| 206 221 | 
             
                  end
         | 
| 207 222 |  | 
| @@ -213,5 +228,21 @@ module Grafana | |
| 213 228 |  | 
| 214 229 | 
             
                  Time.at((Float(value) / 1000).to_i).strftime(format_string)
         | 
| 215 230 | 
             
                end
         | 
| 231 | 
            +
             | 
| 232 | 
            +
                private
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                def init_values
         | 
| 235 | 
            +
                  case @config['type']
         | 
| 236 | 
            +
                  when 'constant'
         | 
| 237 | 
            +
                    self.raw_value = @config['query']
         | 
| 238 | 
            +
             | 
| 239 | 
            +
                  else
         | 
| 240 | 
            +
                    if !@config['current'].nil?
         | 
| 241 | 
            +
                      self.raw_value = @config['current']['value']
         | 
| 242 | 
            +
                    else
         | 
| 243 | 
            +
                      raise GrafanaError.new("Grafana variable with type '#{@config['type']}' and name '#{@config['name']}' could not be handled properly. Please raise a ticket.")
         | 
| 244 | 
            +
                    end
         | 
| 245 | 
            +
                  end
         | 
| 246 | 
            +
                end
         | 
| 216 247 | 
             
              end
         | 
| 217 248 | 
             
            end
         | 
    
        data/lib/grafana/webrequest.rb
    CHANGED
    
    | @@ -50,6 +50,7 @@ module Grafana | |
| 50 50 | 
             
                  @logger.debug("Requesting #{uri} with '#{@options[:body]}' and timeout '#{timeout}'")
         | 
| 51 51 | 
             
                  response = @http.request(request)
         | 
| 52 52 | 
             
                  @logger.debug("Received response #{response}")
         | 
| 53 | 
            +
                  @logger.debug("HTTP response body: #{response.body}") unless response.code =~ /^2.*/
         | 
| 53 54 |  | 
| 54 55 | 
             
                  response
         | 
| 55 56 | 
             
                end
         | 
| @@ -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 | 
             
              #
         | 
| @@ -10,7 +13,7 @@ module GrafanaReporter | |
| 10 13 | 
             
                attr_reader :variables, :result, :panel, :dashboard
         | 
| 11 14 |  | 
| 12 15 | 
             
                def timeout
         | 
| 13 | 
            -
                  # TODO: check where value priorities should be evaluated
         | 
| 16 | 
            +
                  # TODO: PRIO check where value priorities should be evaluated
         | 
| 14 17 | 
             
                  return @variables['timeout'].raw_value if @variables['timeout']
         | 
| 15 18 | 
             
                  return @variables['grafana_default_timeout'].raw_value if @variables['grafana_default_timeout']
         | 
| 16 19 |  | 
| @@ -41,6 +44,7 @@ module GrafanaReporter | |
| 41 44 | 
             
                  else
         | 
| 42 45 | 
             
                    raise GrafanaReporterError, "Internal error in AbstractQuery: given object is of type #{grafana_obj.class.name}, which is not supported"
         | 
| 43 46 | 
             
                  end
         | 
| 47 | 
            +
                  @logger = @grafana ? @grafana.logger : ::Logger.new($stderr, level: :info)
         | 
| 44 48 | 
             
                  @variables = {}
         | 
| 45 49 | 
             
                  @variables['from'] = Grafana::Variable.new(nil)
         | 
| 46 50 | 
             
                  @variables['to'] = Grafana::Variable.new(nil)
         | 
| @@ -76,7 +80,7 @@ module GrafanaReporter | |
| 76 80 | 
             
                  raise DatasourceNotSupportedError.new(@datasource, self) if @datasource.is_a?(Grafana::UnsupportedDatasource)
         | 
| 77 81 |  | 
| 78 82 | 
             
                  begin
         | 
| 79 | 
            -
                    @result = @datasource.request(from: from, to: to, raw_query: raw_query, variables:  | 
| 83 | 
            +
                    @result = @datasource.request(from: from, to: to, raw_query: raw_query, variables: @variables,
         | 
| 80 84 | 
             
                                                  prepared_request: @grafana.prepare_request, timeout: timeout)
         | 
| 81 85 | 
             
                  rescue ::Grafana::GrafanaError
         | 
| 82 86 | 
             
                    # grafana errors will be directly passed through
         | 
| @@ -89,6 +93,7 @@ module GrafanaReporter | |
| 89 93 | 
             
                  end
         | 
| 90 94 |  | 
| 91 95 | 
             
                  raise DatasourceRequestInvalidReturnValueError.new(@datasource, @result) unless datasource_response_valid?
         | 
| 96 | 
            +
             | 
| 92 97 | 
             
                  post_process
         | 
| 93 98 | 
             
                  @result
         | 
| 94 99 | 
             
                end
         | 
| @@ -139,6 +144,8 @@ module GrafanaReporter | |
| 139 144 | 
             
                #
         | 
| 140 145 | 
             
                # Multiple columns may be filtered. Therefore the column titles have to be named in the
         | 
| 141 146 | 
             
                # {Grafana::Variable#raw_value} and have to be separated by +,+ (comma).
         | 
| 147 | 
            +
                #
         | 
| 148 | 
            +
                # Commas can be used in a format string, but need to be escaped by using +_,+.
         | 
| 142 149 | 
             
                # @param result [Hash] preformatted sql hash, (see {Grafana::AbstractDatasource#request})
         | 
| 143 150 | 
             
                # @param filter_columns_variable [Grafana::Variable] column names, which shall be removed in the query result
         | 
| 144 151 | 
             
                # @return [Hash] filtered query result
         | 
| @@ -146,8 +153,8 @@ module GrafanaReporter | |
| 146 153 | 
             
                  return result unless filter_columns_variable
         | 
| 147 154 |  | 
| 148 155 | 
             
                  filter_columns = filter_columns_variable.raw_value
         | 
| 149 | 
            -
                  filter_columns.split( | 
| 150 | 
            -
                    pos = result[:header].index(filter_column)
         | 
| 156 | 
            +
                  filter_columns.split(/(?<!_),/).each do |filter_column|
         | 
| 157 | 
            +
                    pos = result[:header].index(filter_column.gsub("_,", ","))
         | 
| 151 158 |  | 
| 152 159 | 
             
                    unless pos.nil?
         | 
| 153 160 | 
             
                      result[:header].delete_at(pos)
         | 
| @@ -163,23 +170,33 @@ module GrafanaReporter | |
| 163 170 | 
             
                # The formatting will be applied separately for every column. Therefore the column formats have to be named
         | 
| 164 171 | 
             
                # in the {Grafana::Variable#raw_value} and have to be separated by +,+ (comma). If no value is specified for
         | 
| 165 172 | 
             
                # a column, no change will happen.
         | 
| 173 | 
            +
                #
         | 
| 174 | 
            +
                # It is also possible to format milliseconds as dates by specifying date formats, e.g. +date:iso+. It is
         | 
| 175 | 
            +
                # possible to use any date format according
         | 
| 176 | 
            +
                # {https://grafana.com/docs/grafana/latest/variables/variable-types/global-variables/#from-and-to}
         | 
| 177 | 
            +
                #
         | 
| 178 | 
            +
                # Commas can be used in a format string, but need to be escaped by using +_,+.
         | 
| 166 179 | 
             
                # @param result [Hash] preformatted sql hash, (see {Grafana::AbstractDatasource#request})
         | 
| 167 180 | 
             
                # @param formats [Grafana::Variable] formats, which shall be applied to the columns in the query result
         | 
| 168 181 | 
             
                # @return [Hash] formatted query result
         | 
| 169 182 | 
             
                def format_columns(result, formats)
         | 
| 170 183 | 
             
                  return result unless formats
         | 
| 171 184 |  | 
| 172 | 
            -
                  formats.text.split( | 
| 173 | 
            -
                    format = formats.text.split( | 
| 185 | 
            +
                  formats.text.split(/(?<!_),/).each_index do |i|
         | 
| 186 | 
            +
                    format = formats.text.split(/(?<!_),/)[i].gsub("_,", ",")
         | 
| 174 187 | 
             
                    next if format.empty?
         | 
| 175 188 |  | 
| 176 189 | 
             
                    result[:content].map do |row|
         | 
| 177 190 | 
             
                      next unless row.length > i
         | 
| 178 191 |  | 
| 179 192 | 
             
                      begin
         | 
| 180 | 
            -
                         | 
| 193 | 
            +
                        if format =~ /^date:/
         | 
| 194 | 
            +
                          row[i] = ::Grafana::Variable.format_as_date(row[i], format.sub(/^date:/, '')) if row[i]
         | 
| 195 | 
            +
                        else
         | 
| 196 | 
            +
                          row[i] = format % row[i] if row[i]
         | 
| 197 | 
            +
                        end
         | 
| 181 198 | 
             
                      rescue StandardError => e
         | 
| 182 | 
            -
                        @ | 
| 199 | 
            +
                        @logger.error(e.message)
         | 
| 183 200 | 
             
                        row[i] = e.message
         | 
| 184 201 | 
             
                      end
         | 
| 185 202 | 
             
                    end
         | 
| @@ -216,7 +233,6 @@ module GrafanaReporter | |
| 216 233 | 
             
                # @param result [Hash] preformatted query result (see {Grafana::AbstractDatasource#request}.
         | 
| 217 234 | 
             
                # @param configs [Array<Grafana::Variable>] one variable for replacing values in one column
         | 
| 218 235 | 
             
                # @return [Hash] query result with replaced values
         | 
| 219 | 
            -
                # TODO: make sure that caught errors are also visible in logger
         | 
| 220 236 | 
             
                def replace_values(result, configs)
         | 
| 221 237 | 
             
                  return result if configs.empty?
         | 
| 222 238 |  | 
| @@ -231,8 +247,11 @@ module GrafanaReporter | |
| 231 247 |  | 
| 232 248 | 
             
                      k = arr[0]
         | 
| 233 249 | 
             
                      v = arr[1]
         | 
| 234 | 
            -
             | 
| 235 | 
            -
                       | 
| 250 | 
            +
             | 
| 251 | 
            +
                      # allow keys and values to contain escaped colons or commas
         | 
| 252 | 
            +
                      k = k.gsub(/\\([:,])/, '\1')
         | 
| 253 | 
            +
                      v = v.gsub(/\\([:,])/, '\1')
         | 
| 254 | 
            +
             | 
| 236 255 | 
             
                      result[:content].map do |row|
         | 
| 237 256 | 
             
                        (row.length - 1).downto 0 do |i|
         | 
| 238 257 | 
             
                          if cols.include?(i + 1) || cols.empty?
         | 
| @@ -242,7 +261,7 @@ module GrafanaReporter | |
| 242 261 | 
             
                              begin
         | 
| 243 262 | 
             
                                row[i] = row[i].to_s.gsub(/#{k}/, v) if row[i].to_s =~ /#{k}/
         | 
| 244 263 | 
             
                              rescue StandardError => e
         | 
| 245 | 
            -
                                @ | 
| 264 | 
            +
                                @logger.error(e.message)
         | 
| 246 265 | 
             
                                row[i] = e.message
         | 
| 247 266 | 
             
                              end
         | 
| 248 267 |  | 
| @@ -267,6 +286,7 @@ module GrafanaReporter | |
| 267 286 | 
             
                                             end
         | 
| 268 287 | 
             
                                  end
         | 
| 269 288 | 
             
                                rescue StandardError => e
         | 
| 289 | 
            +
                                  @logger.error(e.message)
         | 
| 270 290 | 
             
                                  row[i] = e.message
         | 
| 271 291 | 
             
                                end
         | 
| 272 292 | 
             
                              end
         | 
| @@ -284,22 +304,34 @@ module GrafanaReporter | |
| 284 304 | 
             
                  result
         | 
| 285 305 | 
             
                end
         | 
| 286 306 |  | 
| 287 | 
            -
                # Used to build a output  | 
| 307 | 
            +
                # Used to build a table output in a custom format.
         | 
| 288 308 | 
             
                # @param result [Hash] preformatted sql hash, (see {Grafana::AbstractDatasource#request})
         | 
| 289 309 | 
             
                # @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 | 
            -
                # @ | 
| 310 | 
            +
                # @option opts [Grafana::Variable] :row_divider requested row divider for the result table, only to be used with table_formatter `adoc_deprecated`
         | 
| 311 | 
            +
                # @option opts [Grafana::Variable] :column_divider requested row divider for the result table, only to be used with table_formatter `adoc_deprecated`
         | 
| 312 | 
            +
                # @option opts [Grafana::Variable] :include_headline specifies if table should contain headline, defaults to false
         | 
| 313 | 
            +
                # @option opts [Grafana::Variable] :table_formatter specifies which formatter shall be used, defaults to 'csv'
         | 
| 314 | 
            +
                # @option opts [Grafana::Variable] :transposed specifies whether the result table is transposed
         | 
| 315 | 
            +
                # @return [String] table in custom output format
         | 
| 295 316 | 
             
                def format_table_output(result, opts)
         | 
| 296 | 
            -
                  opts = {  | 
| 297 | 
            -
             | 
| 298 | 
            -
             | 
| 299 | 
            -
             | 
| 300 | 
            -
             | 
| 301 | 
            -
             | 
| 317 | 
            +
                  opts = { include_headline: Grafana::Variable.new('false'),
         | 
| 318 | 
            +
                           table_formatter: Grafana::Variable.new('csv'),
         | 
| 319 | 
            +
                           row_divider: Grafana::Variable.new('| '),
         | 
| 320 | 
            +
                           column_divider: Grafana::Variable.new(' | '),
         | 
| 321 | 
            +
                           transpose: Grafana::Variable.new('false') }.merge(opts.delete_if {|_k, v| v.nil? })
         | 
| 322 | 
            +
             | 
| 323 | 
            +
                  if opts[:table_formatter].raw_value == 'adoc_deprecated'
         | 
| 324 | 
            +
                    @logger.warn("You are using deprecated 'table_formatter' named 'adoc_deprecated', which will be "\
         | 
| 325 | 
            +
                                 "removed in a future version. Start using 'adoc_plain' or register your own "\
         | 
| 326 | 
            +
                                 "implementation of AbstractTableFormatStrategy.")
         | 
| 327 | 
            +
                    return result[:content].map do |row|
         | 
| 328 | 
            +
                      opts[:row_divider].raw_value + row.map do |item|
         | 
| 329 | 
            +
                        item.to_s.gsub('|', '\\|')
         | 
| 330 | 
            +
                      end.join(opts[:column_divider].raw_value)
         | 
| 331 | 
            +
                    end.join("\n")
         | 
| 302 332 | 
             
                  end
         | 
| 333 | 
            +
             | 
| 334 | 
            +
                  AbstractTableFormatStrategy.get(opts[:table_formatter].raw_value).format(result, opts[:include_headline].raw_value.downcase == 'true', opts[:transpose].raw_value.downcase == 'true')
         | 
| 303 335 | 
             
                end
         | 
| 304 336 |  | 
| 305 337 | 
             
                # Used to translate the relative date strings used by grafana, e.g. +now-5d/w+ to the
         | 
| @@ -317,9 +349,10 @@ module GrafanaReporter | |
| 317 349 | 
             
                # @param timezone [Grafana::Variable] timezone to use, if not system timezone
         | 
| 318 350 | 
             
                # @return [String] translated date as timestamp string
         | 
| 319 351 | 
             
                def translate_date(orig_date, report_time, is_to_time, timezone = nil)
         | 
| 320 | 
            -
                  #  | 
| 352 | 
            +
                  @logger.warn("#translate_date has been called without 'report_time' - using current time as fallback.") unless report_time
         | 
| 321 353 | 
             
                  report_time ||= ::Grafana::Variable.new(Time.now.to_s)
         | 
| 322 354 | 
             
                  orig_date = orig_date.raw_value if orig_date.is_a?(Grafana::Variable)
         | 
| 355 | 
            +
             | 
| 323 356 | 
             
                  return (DateTime.parse(report_time.raw_value).to_time.to_i * 1000).to_s unless orig_date
         | 
| 324 357 | 
             
                  return orig_date if orig_date =~ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
         | 
| 325 358 | 
             
                  return orig_date if orig_date =~ /^\d+$/
         | 
| @@ -327,24 +360,24 @@ module GrafanaReporter | |
| 327 360 | 
             
                  # check if a relative date is mentioned
         | 
| 328 361 | 
             
                  date_spec = orig_date.clone
         | 
| 329 362 |  | 
| 330 | 
            -
                  date_spec. | 
| 363 | 
            +
                  date_spec = date_spec.gsub(/^now/, '')
         | 
| 331 364 | 
             
                  raise TimeRangeUnknownError, orig_date unless date_spec
         | 
| 332 365 |  | 
| 333 366 | 
             
                  date = DateTime.parse(report_time.raw_value)
         | 
| 334 | 
            -
                  # TODO: allow from_translated or similar in ADOC template
         | 
| 367 | 
            +
                  # TODO: PRIO allow from_translated or similar in ADOC template
         | 
| 335 368 | 
             
                  date = date.new_offset(timezone.raw_value) if timezone
         | 
| 336 369 |  | 
| 337 370 | 
             
                  until date_spec.empty?
         | 
| 338 371 | 
             
                    fit_match = date_spec.match(%r{^/(?<fit>[smhdwMy])})
         | 
| 339 372 | 
             
                    if fit_match
         | 
| 340 373 | 
             
                      date = fit_date(date, fit_match[:fit], is_to_time)
         | 
| 341 | 
            -
                      date_spec. | 
| 374 | 
            +
                      date_spec = date_spec.gsub(%r{^/#{fit_match[:fit]}}, '')
         | 
| 342 375 | 
             
                    end
         | 
| 343 376 |  | 
| 344 377 | 
             
                    delta_match = date_spec.match(/^(?<op>(?:-|\+))(?<count>\d+)?(?<unit>[smhdwMy])/)
         | 
| 345 378 | 
             
                    if delta_match
         | 
| 346 379 | 
             
                      date = delta_date(date, "#{delta_match[:op]}#{delta_match[:count] || 1}".to_i, delta_match[:unit])
         | 
| 347 | 
            -
                      date_spec. | 
| 380 | 
            +
                      date_spec = date_spec.gsub(/^#{delta_match[:op]}#{delta_match[:count]}#{delta_match[:unit]}/, '')
         | 
| 348 381 | 
             
                    end
         | 
| 349 382 |  | 
| 350 383 | 
             
                    raise TimeRangeUnknownError, orig_date unless fit_match || delta_match
         | 
| @@ -387,22 +420,15 @@ module GrafanaReporter | |
| 387 420 | 
             
                def datasource_response_valid?
         | 
| 388 421 | 
             
                  return false if @result.nil?
         | 
| 389 422 | 
             
                  return false unless @result.is_a?(Hash)
         | 
| 390 | 
            -
                   | 
| 391 | 
            -
                  return  | 
| 392 | 
            -
                  return false unless @result. | 
| 393 | 
            -
                  return false unless @result.has_key?(:content)
         | 
| 423 | 
            +
                  return false if @result.empty?
         | 
| 424 | 
            +
                  return false unless @result.key?(:header)
         | 
| 425 | 
            +
                  return false unless @result.key?(:content)
         | 
| 394 426 | 
             
                  return false unless @result[:header].is_a?(Array)
         | 
| 395 427 | 
             
                  return false unless @result[:content].is_a?(Array)
         | 
| 396 428 |  | 
| 397 429 | 
             
                  true
         | 
| 398 430 | 
             
                end
         | 
| 399 431 |  | 
| 400 | 
            -
                # @return [Hash<String, Variable>] all grafana variables stored in this query, i.e. the variable name
         | 
| 401 | 
            -
                #  is prefixed with +var-+
         | 
| 402 | 
            -
                def grafana_variables
         | 
| 403 | 
            -
                  @variables.select { |k, _v| k =~ /^var-.+/ }
         | 
| 404 | 
            -
                end
         | 
| 405 | 
            -
             | 
| 406 432 | 
             
                def delta_date(date, delta_count, time_letter)
         | 
| 407 433 | 
             
                  # substract specified time
         | 
| 408 434 | 
             
                  case time_letter
         | 
| @@ -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,16 @@ 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 | 
            -
                   | 
| 146 | 
            +
                  logger.info("You are running ruby-grafana-reporter version #{GRAFANA_REPORTER_VERSION.join('.')}.")
         | 
| 147 | 
            +
                  logger.info("A newer version is released. Check out https://github.com/divinity666/ruby-grafana-reporter/releases/latest") unless @config.latest_version_check_ok?
         | 
| 148 | 
            +
                  build
         | 
| 147 149 | 
             
                rescue MissingTemplateError => e
         | 
| 148 150 | 
             
                  @logger.error(e.message)
         | 
| 149 151 | 
             
                  @error = [e.message]
         | 
| @@ -186,6 +188,18 @@ module GrafanaReporter | |
| 186 188 | 
             
                  raise NotImplementedError
         | 
| 187 189 | 
             
                end
         | 
| 188 190 |  | 
| 191 | 
            +
                # @abstract
         | 
| 192 | 
            +
                # @return [String] specifying the default extension of a template file
         | 
| 193 | 
            +
                def self.default_template_extension
         | 
| 194 | 
            +
                  raise NotImplementedError
         | 
| 195 | 
            +
                end
         | 
| 196 | 
            +
             | 
| 197 | 
            +
                # @abstract
         | 
| 198 | 
            +
                # @return [String] specifying the default extension of a rendered result file
         | 
| 199 | 
            +
                def self.default_result_extension
         | 
| 200 | 
            +
                  raise NotImplementedError
         | 
| 201 | 
            +
                end
         | 
| 202 | 
            +
             | 
| 189 203 | 
             
                private
         | 
| 190 204 |  | 
| 191 205 | 
             
                # Called, if the report generation has died with an error.
         | 
| @@ -207,6 +221,7 @@ module GrafanaReporter | |
| 207 221 | 
             
                def done!
         | 
| 208 222 | 
             
                  return if @done
         | 
| 209 223 |  | 
| 224 | 
            +
                  @destination_file_or_path.close if @destination_file_or_path.is_a?(File)
         | 
| 210 225 | 
             
                  @done = true
         | 
| 211 226 | 
             
                  @end_time = Time.new
         | 
| 212 227 | 
             
                  @start_time ||= @end_time
         | 
| @@ -0,0 +1,74 @@ | |
| 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 | 
            +
                # Used to format a given content array to the desired output format. The default
         | 
| 27 | 
            +
                # implementation applies the {#format_rules} to create a custom string export. If
         | 
| 28 | 
            +
                # this is not sufficient for a desired table format, you may simply overwrite this
         | 
| 29 | 
            +
                # function to have full freedom about the desired output.
         | 
| 30 | 
            +
                # @param content [Hash] datasource table result
         | 
| 31 | 
            +
                # @param include_headline [Boolean] true, if headline should be included in result
         | 
| 32 | 
            +
                # @param transposed [Boolean] true, if result array is in transposed format
         | 
| 33 | 
            +
                # @return [String] formatted in table format
         | 
| 34 | 
            +
                def format(content, include_headline, transposed)
         | 
| 35 | 
            +
                  result = content[:content]
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  # add the headline at the correct position to the content array
         | 
| 38 | 
            +
                  if include_headline
         | 
| 39 | 
            +
                    if transposed
         | 
| 40 | 
            +
                      result.each_index do |i|
         | 
| 41 | 
            +
                        result[i] = [content[:header][i]] + result[i]
         | 
| 42 | 
            +
                      end
         | 
| 43 | 
            +
                    else
         | 
| 44 | 
            +
                      result = result.unshift(content[:header])
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  # translate the content to a table
         | 
| 49 | 
            +
                  result.map do |row|
         | 
| 50 | 
            +
                    format_rules[:row_start] + row.map do |item|
         | 
| 51 | 
            +
                      value = item.to_s
         | 
| 52 | 
            +
                      if format_rules[:replace_string_or_regex]
         | 
| 53 | 
            +
                        value = value.gsub(format_rules[:replace_string_or_regex], format_rules[:replacement])
         | 
| 54 | 
            +
                      end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                      format_rules[:cell_start] + value + format_rules[:cell_end]
         | 
| 57 | 
            +
                    end.join(format_rules[:between_cells])
         | 
| 58 | 
            +
                  end.join(format_rules[:row_end])
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                # Formatting rules, which are applied to build the table output format.
         | 
| 62 | 
            +
                def format_rules
         | 
| 63 | 
            +
                  {
         | 
| 64 | 
            +
                    row_start: '',
         | 
| 65 | 
            +
                    row_end: '',
         | 
| 66 | 
            +
                    cell_start: '',
         | 
| 67 | 
            +
                    between_cells: '',
         | 
| 68 | 
            +
                    cell_end: '',
         | 
| 69 | 
            +
                    replace_string_or_regex: nil,
         | 
| 70 | 
            +
                    replacement: ''
         | 
| 71 | 
            +
                  }
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
              end
         | 
| 74 | 
            +
            end
         | 
| @@ -33,7 +33,12 @@ module GrafanaReporter | |
| 33 33 | 
             
                  @result = replace_values(@result, @variables.select { |k, _v| k =~ /^replace_values_\d+/ })
         | 
| 34 34 | 
             
                  @result = filter_columns(@result, @variables['filter_columns'])
         | 
| 35 35 |  | 
| 36 | 
            -
                  @result = format_table_output(@result, | 
| 36 | 
            +
                  @result = format_table_output(@result,
         | 
| 37 | 
            +
                                                row_divider: @variables['row_divider'],
         | 
| 38 | 
            +
                                                column_divider: @variables['column_divider'],
         | 
| 39 | 
            +
                                                table_formatter: @variables['table_formatter'],
         | 
| 40 | 
            +
                                                include_headline: @variables['include_headline'],
         | 
| 41 | 
            +
                                                transpose: @variables['transpose'])
         | 
| 37 42 | 
             
                end
         | 
| 38 43 | 
             
              end
         | 
| 39 44 | 
             
            end
         | 
| @@ -32,7 +32,12 @@ module GrafanaReporter | |
| 32 32 | 
             
                  @result = replace_values(@result, @variables.select { |k, _v| k =~ /^replace_values_\d+/ })
         | 
| 33 33 | 
             
                  @result = filter_columns(@result, @variables['filter_columns'])
         | 
| 34 34 |  | 
| 35 | 
            -
                  @result = format_table_output(@result, | 
| 35 | 
            +
                  @result = format_table_output(@result,
         | 
| 36 | 
            +
                                                row_divider: @variables['row_divider'],
         | 
| 37 | 
            +
                                                column_divider: @variables['column_divider'],
         | 
| 38 | 
            +
                                                table_formatter: @variables['table_formatter'],
         | 
| 39 | 
            +
                                                include_headline: @variables['include_headline'],
         | 
| 40 | 
            +
                                                transpose: @variables['transpose'])
         | 
| 36 41 | 
             
                end
         | 
| 37 42 | 
             
              end
         | 
| 38 43 | 
             
            end
         | 
| @@ -13,7 +13,6 @@ module GrafanaReporter | |
| 13 13 | 
             
                # It can be run to test the grafana connection, render a single template
         | 
| 14 14 | 
             
                # or run as a service.
         | 
| 15 15 | 
             
                class Application
         | 
| 16 | 
            -
             | 
| 17 16 | 
             
                  # Contains the {Configuration} object of the application.
         | 
| 18 17 | 
             
                  attr_accessor :config
         | 
| 19 18 |  | 
| @@ -140,7 +139,13 @@ module GrafanaReporter | |
| 140 139 |  | 
| 141 140 | 
             
                    when Configuration::MODE_SINGLE_RENDER
         | 
| 142 141 | 
             
                      begin
         | 
| 143 | 
            -
                        config.report_class. | 
| 142 | 
            +
                        template_ext = config.report_class.default_template_extension
         | 
| 143 | 
            +
                        report_ext = config.report_class.default_result_extension
         | 
| 144 | 
            +
                        default_to_file = File.basename(config.template.to_s.gsub(/(?:\.#{template_ext})?$/, ".#{report_ext}"))
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                        to_file = config.to_file
         | 
| 147 | 
            +
                        to_file = "#{config.reports_folder}#{default_to_file}" if to_file == true
         | 
| 148 | 
            +
                        config.report_class.new(config).create_report(config.template, to_file)
         | 
| 144 149 | 
             
                      rescue StandardError => e
         | 
| 145 150 | 
             
                        puts "#{e.message}\n#{e.backtrace.join("\n")}"
         | 
| 146 151 | 
             
                      end
         |