keen-csv 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b9fe8e0dce221ce5b5e2f0b915e85bd2e8084b36
4
+ data.tar.gz: 1ab02862ac7b47d6fffb82c8b22633e09dc50fe5
5
+ SHA512:
6
+ metadata.gz: e2df446da5ab8e23eb305d2555487a37cd131dbef5650cff34d1ad1234fa91c501ef3505ea4502f220ca5ece89dde98ccdf561f925b21b9a55c7216bac441c45
7
+ data.tar.gz: 9b009e1685d3d5f4c99daedef05991d406cbd1e32aaf4ad1c7cb8e22558bde26354c31a645481c6344b032aa963f07c53ca87f67bf944505bd5f4905759bd845
@@ -0,0 +1,31 @@
1
+ require('keen')
2
+
3
+ # Monkeypatch the Keen gem to wrap :query and steal the :csv option
4
+ module Keen
5
+ class << self
6
+
7
+ def query_wrapper(*args)
8
+ # let's optimize for lowest-possible overhead in case 'csv' output isn't requested
9
+ if args[-1][:csv]
10
+ # CSV ouput was requested.
11
+ # First, remove the :csv option to keep from messing up the Keen gem
12
+ options = args[-1].delete(:csv)
13
+ # and perform the query
14
+ response = naked_query(*args)
15
+
16
+ # and now for the main event . . . generating the CSV output
17
+ keenCSV = Keen::CSV.new(response, options)
18
+ return keenCSV.csvString
19
+ else
20
+ # not CSV; just pass through to the naked query
21
+ return naked_query(*args)
22
+ end
23
+ end
24
+
25
+ # Wrap :query with :query_wrapper via aliases
26
+ alias :naked_query :query
27
+ alias :query :query_wrapper
28
+
29
+ end
30
+
31
+ end
data/lib/keen-csv.rb ADDED
@@ -0,0 +1,135 @@
1
+ require_relative('keen/query_wrapper')
2
+
3
+ class Keen::CSV
4
+ @@defaultOptions = {
5
+ delimiter: ',',
6
+ delimiterSub: '.',
7
+ nestedDelimiter: '.',
8
+ filteredColumns: nil,
9
+ }
10
+
11
+ def initialize(response, options = {})
12
+ @rawResponse = response
13
+ @options = options.is_a?(Hash) ? @@defaultOptions.merge(options) : @@defaultOptions
14
+ end
15
+
16
+ # ---------------------------------------------------------------------------
17
+ # generateResultColumns
18
+ # Transforms the Keen result columnar Map, keyed by header
19
+ # ---------------------------------------------------------------------------
20
+ def generateResultColumns
21
+ resultColumns = {
22
+ columns: {},
23
+ maxRowIndex: 0 # We're going to count the rows, for future use
24
+ }
25
+
26
+ # Using a lambda to add the right columns into resultColumns, and keep track
27
+ # of maxRowIndex
28
+ setColumnValue = lambda do |column, rowIndex, value|
29
+ unless self.columnIsFiltered?(column)
30
+ resultColumns[:columns][column] ||= []
31
+ resultColumns[:columns][column][rowIndex] = value
32
+ end
33
+
34
+ # Gotta keep track of how many rows we're working with.
35
+ resultColumns[:maxRowIndex] = rowIndex if rowIndex > resultColumns[:maxRowIndex]
36
+ end
37
+
38
+ # Exit early if this is a simple math operation
39
+ if @rawResponse.is_a? Numeric
40
+ resultColumns[:columns]['result'] = [@rawResponse]
41
+ resultColumns[:maxRowIndex] = 1
42
+ return resultColumns
43
+ end
44
+
45
+ rowIndex = 0
46
+ @rawResponse.each do |object|
47
+ if object["value"].is_a? Array
48
+ # This result is grouped! We're gonna have to create alot more columns and rows
49
+ object["value"].each do |group|
50
+
51
+ # iterate over each value grouping, and store the values
52
+ self.flatten(group).each do |column, value|
53
+ setColumnValue.call(column, rowIndex, value)
54
+ end
55
+ if object["timeframe"]
56
+ self.flatten({"timeframe" => object["timeframe"]}).each do |column, value|
57
+ setColumnValue.call(column, rowIndex, value)
58
+ end
59
+ end
60
+ rowIndex += 1
61
+
62
+ end
63
+ else
64
+ # Not grouped: This either an Extraction or a math operation on an interval.
65
+ self.flatten(object).each do |column, value|
66
+ setColumnValue.call(column, rowIndex, value)
67
+ end
68
+ rowIndex += 1
69
+
70
+ end
71
+ end
72
+
73
+ resultColumns
74
+ end
75
+
76
+ # ---------------------------------------------------------------------------
77
+ # columnIsFiltered?
78
+ # Takes a column header, and determines whether that column should be
79
+ # filtered out
80
+ # ---------------------------------------------------------------------------
81
+ def columnIsFiltered?(header)
82
+ return @options[:filteredColumns] &&
83
+ @options[:filteredColumns].is_a?(Array) &&
84
+ @options[:filteredColumns].include?(header)
85
+ end
86
+
87
+ # ---------------------------------------------------------------------------
88
+ # csvString
89
+ # Generates and returns a CSV for this Keen response
90
+ # ---------------------------------------------------------------------------
91
+ def csvString
92
+ resultColumns = self.generateResultColumns
93
+ headers = resultColumns[:columns].keys
94
+ # Start off instantiating the csv string with the header values
95
+ csvString = headers.map{|s| self.filterValue(s)}.join(@options[:delimiter])
96
+
97
+ # Now iterate over each row, sticking its value under each header
98
+ (0..resultColumns[:maxRowIndex]).each do |rowIndex|
99
+ csvString << "\r\n"
100
+ csvString << headers.map{ |header|
101
+ self.filterValue(resultColumns[:columns][header][rowIndex])
102
+ }.join(@options[:delimiter])
103
+ end
104
+ return csvString
105
+ end
106
+
107
+ # ---------------------------------------------------------------------------
108
+ # filterValue
109
+ # Takes a scalar value, and returns a CSV-compatible one
110
+ # ---------------------------------------------------------------------------
111
+ def filterValue(value)
112
+ if value == nil
113
+ return ''
114
+ else
115
+ return value.to_s.gsub(/#{Regexp.escape(@options[:delimiter])}/, @options[:delimiterSub])
116
+ end
117
+ end
118
+
119
+ # ---------------------------------------------------------------------------
120
+ # flatten
121
+ # Converts any nested dictionaries into a flattened/delimited one.
122
+ # ---------------------------------------------------------------------------
123
+ def flatten(object, flattened = {}, prefix = "")
124
+ object.each do |key, value|
125
+ if value.is_a?(Hash) || value.is_a?(Array)
126
+ # recurse!
127
+ flatten(value, flattened, prefix + key + @options[:nestedDelimiter])
128
+ else
129
+ flattened[prefix + key] = value
130
+ end
131
+ end
132
+
133
+ return flattened
134
+ end
135
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: keen-csv
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jevon Wild
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-10-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: keen
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Builds a CSV string from a Keen IO response
28
+ email: jevon@keenio
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/keen-csv.rb
34
+ - lib/keen/query_wrapper.rb
35
+ homepage: https://keen.io/
36
+ licenses:
37
+ - MIT
38
+ metadata: {}
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubyforge_project:
55
+ rubygems_version: 2.4.8
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: CSV output for Keen IO
59
+ test_files: []