steventen/sql_tracker

View on GitHub
lib/sql_tracker/report.rb

Summary

Maintainability
B
5 hrs
Test Coverage
module SqlTracker
  class Report
    attr_accessor :raw_data
    attr_accessor :terminal_width

    def initialize(data)
      self.raw_data = data
    end

    def valid?
      return false unless raw_data.key?('format_version')
      return false unless raw_data.key?('data')
      return false if raw_data['data'].nil? || raw_data['data'].empty?
      sample = raw_data['data'].values.first
      %w(sql count source).each do |key|
        return false unless sample.key?(key)
      end
      true
    end

    def version
      raw_data['format_version']
    end

    def data
      raw_data['data']
    end

    def print_text(options)
      self.terminal_width = options.fetch(:terminal_width)
      f = STDOUT
      f.puts '=================================='
      f.puts "Total Unique SQL Queries: #{data.keys.size}"
      f.puts '=================================='
      f.printf(
        "%-#{count_width}s | %-#{duration_width}s | %-#{sql_width}s | Source\n",
        'Count', 'Avg Time (ms)', 'SQL Query'
      )
      f.puts '-' * terminal_width

      sorted_data = sort_data(data.values, options[:sort_by])
      sorted_data.each do |row|
        chopped_sql = wrap_text(row['sql'], sql_width)
        source_list = wrap_list(row['source'].uniq, sql_width - 10)
        avg_duration = row['duration'].to_f / row['count']
        total_lines = if chopped_sql.length >= source_list.length
                        chopped_sql.length
                      else
                        source_list.length
                      end

        (0...total_lines).each do |line|
          count = line == 0 ? row['count'].to_s : ''
          duration = line == 0 ? avg_duration.round(2) : ''
          source = source_list.length > line ? source_list[line] : ''
          query = row['sql'].length > line ? chopped_sql[line] : ''
          f.printf(row_format, count, duration, query, source)
        end
        f.puts '-' * terminal_width
      end
    end

    def row_format
      "%-#{count_width}s | %-#{duration_width}s | %-#{sql_width}s | %-#{sql_width}s\n"
    end

    def sort_data(data, sort_by)
      data.sort_by do |d|
        if sort_by == 'duration'
          -d['duration'].to_f / d['count']
        else
          -d['count']
        end
      end
    end

    def +(other)
      unless self.class == other.class
        raise ArgumentError, "cannot combine #{other.class}"
      end
      unless version == other.version
        raise ArgumentError, "cannot combine v#{version} with v#{other.version}"
      end

      r1 = data
      r2 = other.data

      merged = (r1.keys + r2.keys).uniq.each_with_object({}) do |id, memo|
        if !r1.key?(id)
          memo[id] = r2[id]
        elsif r2.key?(id)
          memo[id] = r1[id]
          memo[id]['count'] += r2[id]['count']
          memo[id]['duration'] += r2[id]['duration']
          memo[id]['source'] += r2[id]['source']
        else
          memo[id] = r1[id]
        end
      end
      merged_data = { 'data' => merged, 'format_version' => version }

      self.class.new(merged_data)
    end

    private

    def wrap_text(text, width)
      return [text] if text.length <= width
      text.scan(/.{1,#{width}}/)
    end

    # an array of text
    def wrap_list(list, width)
      list.map do |text|
        wrap_text(text, width)
      end.flatten
    end

    def sql_width
      @sql_width ||= (terminal_width - count_width - duration_width) / 2
    end

    def count_width
      5
    end

    def duration_width
      15
    end
  end
end