mode-sdk 0.0.1 → 0.1.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/.travis.yml +3 -0
- data/README.md +7 -0
- data/bin/mode_import +38 -0
- data/lib/mode/sdk.rb +8 -0
- data/lib/mode/sdk/cli.rb +241 -0
- data/lib/mode/sdk/client.rb +9 -0
- data/lib/mode/sdk/client/request.rb +1 -2
- data/lib/mode/sdk/client/response.rb +1 -1
- data/lib/mode/sdk/configuration.rb +32 -4
- data/lib/mode/sdk/csv_file.rb +82 -0
- data/lib/mode/sdk/version.rb +1 -1
- data/mode-sdk.gemspec +2 -0
- data/spec/lib/mode/sdk/cli_spec.rb +233 -0
- data/spec/lib/mode/sdk/client/response_spec.rb +1 -1
- data/spec/lib/mode/sdk/client_spec.rb +20 -0
- data/spec/lib/mode/sdk/csv_file_spec.rb +47 -0
- data/spec/lib/mode/sdk_spec.rb +8 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/support/file_helper.rb +9 -0
- data/spec/support/files/population_growth.csv +10 -0
- data/spec/support/streams_helper.rb +13 -0
- metadata +21 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c793c84297832a164a7baa05f0a6d7c46545dbc0
|
4
|
+
data.tar.gz: f3b44b33ce27af676e1440ac4fb0ba13e43dabcf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a55c0fb2ed0f63a454ccb1acb6148f3ae66dd856cb33f4bafc0fd8a709245bc44fb7adcc8d00fbbd60b662392f0349d2190c79122bad9013bedb8df66eccd29
|
7
|
+
data.tar.gz: 03d4035e802ea964a4806ed3c15b0419ea7763db6bff24af4d721ffab7c1dd200787c6fa0421600b92d0155a5e12275f6b677f0ae204f96dd418d45020048498
|
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Mode Ruby SDK
|
2
2
|
|
3
|
+
[](https://travis-ci.org/mode/mode-ruby-sdk)
|
3
4
|
[](https://codeclimate.com/repos/53ea7f57e30ba007c500a24a/feed)
|
5
|
+
[](http://badge.fury.io/rb/mode-sdk)
|
4
6
|
|
5
7
|
This SDK provides a wrapper for the [Mode Analytics](https://modeanalytics.com)
|
6
8
|
API.
|
@@ -94,6 +96,11 @@ your import, including:
|
|
94
96
|
|
95
97
|
You can poll this response until your import has completed.
|
96
98
|
|
99
|
+
## Documentation
|
100
|
+
|
101
|
+
[http://rubydoc.info/github/mode/mode-ruby-sdk/master/frames]
|
102
|
+
(http://rubydoc.info/github/mode/mode-ruby-sdk/master/frames)
|
103
|
+
|
97
104
|
## Examples
|
98
105
|
|
99
106
|
A collection of examples can be found in the
|
data/bin/mode_import
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'mode/sdk'
|
3
|
+
require 'optparse'
|
4
|
+
require 'readline'
|
5
|
+
|
6
|
+
options = {}
|
7
|
+
|
8
|
+
OptionParser.new do |opts|
|
9
|
+
opts.banner = "Usage: #{File.basename(__FILE__)} [options] filename"
|
10
|
+
|
11
|
+
opts.on('-h', '--help', 'Display this help') do
|
12
|
+
$stdout.puts opts.to_s
|
13
|
+
exit
|
14
|
+
end
|
15
|
+
|
16
|
+
opts.on('-r', '--replace', 'Replace table if it already exists') do |value|
|
17
|
+
options[:replace] = value
|
18
|
+
end
|
19
|
+
|
20
|
+
opts.on('-u', '--upload-token=value', 'Upload token') do |value|
|
21
|
+
options[:upload_token] = value
|
22
|
+
end
|
23
|
+
|
24
|
+
opts.on('-t', '--table-name=name', 'Table name') do |value|
|
25
|
+
options[:table_name] = value
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on('-f', '--file=value', 'CSV file') do |value|
|
29
|
+
options[:file] = value
|
30
|
+
end
|
31
|
+
end.parse!
|
32
|
+
|
33
|
+
options[:token] = Mode::Sdk::Cli.env_get('MODE_TOKEN', 'Mode API token')
|
34
|
+
options[:secret] = Mode::Sdk::Cli.env_get('MODE_SECRET', 'Mode API secret')
|
35
|
+
|
36
|
+
options[:file] ||= Readline.readline('Path to CSV file: ', true).strip
|
37
|
+
|
38
|
+
Mode::Sdk::Cli.new(options).import!
|
data/lib/mode/sdk.rb
CHANGED
@@ -45,6 +45,12 @@ module Mode
|
|
45
45
|
account.fetch('username')
|
46
46
|
end
|
47
47
|
|
48
|
+
# @see Mode::Sdk::Client.authenticated?
|
49
|
+
#
|
50
|
+
def authenticated?
|
51
|
+
Mode::Sdk::Client.authenticated?
|
52
|
+
end
|
53
|
+
|
48
54
|
# Un-memoize everything
|
49
55
|
#
|
50
56
|
def reset
|
@@ -58,10 +64,12 @@ module Mode
|
|
58
64
|
end
|
59
65
|
end
|
60
66
|
|
67
|
+
require 'mode/sdk/cli'
|
61
68
|
require 'mode/sdk/client'
|
62
69
|
require 'mode/sdk/column'
|
63
70
|
require 'mode/sdk/column_set'
|
64
71
|
require 'mode/sdk/configuration'
|
72
|
+
require 'mode/sdk/csv_file'
|
65
73
|
require 'mode/sdk/hash_util'
|
66
74
|
require 'mode/sdk/table'
|
67
75
|
require 'mode/sdk/table_import'
|
data/lib/mode/sdk/cli.rb
ADDED
@@ -0,0 +1,241 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
module Mode
|
6
|
+
module Sdk
|
7
|
+
# Provides command line interface functionality for importing data
|
8
|
+
#
|
9
|
+
# @attr_reader [Hash] options hash of options
|
10
|
+
#
|
11
|
+
class Cli
|
12
|
+
attr_reader :options
|
13
|
+
|
14
|
+
# Construct a new Cli instance
|
15
|
+
#
|
16
|
+
# @param options [optional, Hash] hash of options
|
17
|
+
#
|
18
|
+
# @option options [true, false] :replace whether to allow the table to be
|
19
|
+
# replaced, if it exists
|
20
|
+
# @option options [String] :table_name the name of the table (defaults to
|
21
|
+
# normalized file name)
|
22
|
+
# @option options [String] :upload_token the token of a previously
|
23
|
+
# created Mode upload
|
24
|
+
# @option options [String] :token Mode API token
|
25
|
+
# @option options [String] :secret Mode API secret
|
26
|
+
#
|
27
|
+
# @return [Mode::Sdk::Cli] the instance
|
28
|
+
#
|
29
|
+
def initialize(options = {})
|
30
|
+
@options = options
|
31
|
+
end
|
32
|
+
|
33
|
+
# Authenticate and import data
|
34
|
+
#
|
35
|
+
def import!
|
36
|
+
authenticate!
|
37
|
+
validate_options!
|
38
|
+
|
39
|
+
import.poll do |repr|
|
40
|
+
handle_import_state(repr)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def path
|
47
|
+
options.fetch(:file)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Handle the response from import state polling
|
51
|
+
#
|
52
|
+
# @param repr [Hash] the API representation of the table import
|
53
|
+
#
|
54
|
+
def handle_import_state(repr)
|
55
|
+
state = repr.fetch('state')
|
56
|
+
|
57
|
+
case state
|
58
|
+
when 'new', 'enqueued', 'running'
|
59
|
+
log "Import #{state}..."
|
60
|
+
when 'succeeded'
|
61
|
+
table_url = repr['_embedded']['table']['_links']['web']['href']
|
62
|
+
log "Import #{state}"
|
63
|
+
log "Created table #{table_url}"
|
64
|
+
else
|
65
|
+
log "Import #{state}"
|
66
|
+
log repr, true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [Mode::Sdk::TableImport] table import instance
|
71
|
+
#
|
72
|
+
def import
|
73
|
+
return @import if defined?(@import)
|
74
|
+
|
75
|
+
import_path = upsert_table.body['_links']['self'].fetch('href')
|
76
|
+
|
77
|
+
@import = Mode::Sdk::TableImport.new(import_path)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Create or replace table in Mode warehouse
|
81
|
+
#
|
82
|
+
# @return [Mode::Sdk::Client::Response] the response from the create or
|
83
|
+
# replace request
|
84
|
+
#
|
85
|
+
def upsert_table
|
86
|
+
validate_upsert!
|
87
|
+
|
88
|
+
if table.exists?
|
89
|
+
log "Replacing #{table.full_name}..."
|
90
|
+
table.replace
|
91
|
+
else
|
92
|
+
log "Creating #{table.full_name}..."
|
93
|
+
table.create
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Ensure table options are compatible with table state
|
98
|
+
#
|
99
|
+
# @return [true]
|
100
|
+
#
|
101
|
+
def validate_upsert!
|
102
|
+
return true unless table.exists? && !replace_table?
|
103
|
+
|
104
|
+
error = "Table #{table.full_name} already exists."
|
105
|
+
error << 'Use --replace flag to allow replace.'
|
106
|
+
|
107
|
+
stop! error
|
108
|
+
end
|
109
|
+
|
110
|
+
# Check whether the given table may be replaced according to options
|
111
|
+
#
|
112
|
+
# @return [true, false]
|
113
|
+
#
|
114
|
+
def replace_table?
|
115
|
+
options.fetch(:replace, false)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Test Mode authentication with the provided credentials
|
119
|
+
#
|
120
|
+
# @return [true]
|
121
|
+
#
|
122
|
+
def authenticate!
|
123
|
+
@authenticated ||= true.tap do
|
124
|
+
configure!
|
125
|
+
|
126
|
+
unless Mode::Sdk.authenticated?
|
127
|
+
stop! 'Authentication failed, please try again'
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Ensure provided options are valid
|
133
|
+
#
|
134
|
+
# @return [true]
|
135
|
+
#
|
136
|
+
def validate_options!
|
137
|
+
stop! 'Please provide path to CSV file' unless path && path.size > 0
|
138
|
+
|
139
|
+
true
|
140
|
+
end
|
141
|
+
|
142
|
+
# Initialize table instance and assign upload token and column schema
|
143
|
+
#
|
144
|
+
# @return [Mode::Sdk::Table] the table
|
145
|
+
#
|
146
|
+
def table
|
147
|
+
@table ||= Mode::Sdk::Table.new(table_name).tap do |table|
|
148
|
+
table.upload_token = upload_token
|
149
|
+
table.columns = columns
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Build Mode API-compatible column definitions from CSV header
|
154
|
+
#
|
155
|
+
# @return [Array<Hash>] array of columns
|
156
|
+
#
|
157
|
+
# @todo Infer data types from sample values and allow overrides
|
158
|
+
#
|
159
|
+
def columns
|
160
|
+
csv.header.map do |name|
|
161
|
+
{ name: Mode::Sdk::Column.normalize_name(name),
|
162
|
+
type: 'string' }
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Name of the table to import
|
167
|
+
#
|
168
|
+
# @return [String] normalized table name
|
169
|
+
#
|
170
|
+
def table_name
|
171
|
+
@table_name ||= options[:table_name] || begin
|
172
|
+
Mode::Sdk::Table.normalize_name(csv.name)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# @return [Mode::Sdk::CsvFile] the csv file to import
|
177
|
+
#
|
178
|
+
def csv
|
179
|
+
@csv ||= Mode::Sdk::CsvFile.new(path)
|
180
|
+
end
|
181
|
+
|
182
|
+
# @return [String the token of the upload associated with this import
|
183
|
+
#
|
184
|
+
def upload_token
|
185
|
+
@upload_token ||= options[:upload_token] || begin
|
186
|
+
log "Uploading #{path} (#{csv.line_count} lines)..."
|
187
|
+
|
188
|
+
upload = Mode::Sdk::Upload.new(csv.content)
|
189
|
+
upload.create
|
190
|
+
upload.token
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Configure the Mode SDK with authentication credentials
|
195
|
+
#
|
196
|
+
def configure!
|
197
|
+
Mode::Sdk.configure do |config|
|
198
|
+
config.token = options.fetch(:token, nil)
|
199
|
+
config.secret = options.fetch(:secret, nil)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Log a message to stdout
|
204
|
+
#
|
205
|
+
# @param value [Object] the value to print
|
206
|
+
# @param pretty [optional, true, false] whether to pretty-print the value
|
207
|
+
#
|
208
|
+
def log(value, pretty = false)
|
209
|
+
$stdout.puts pretty ? PP.pp(value, '') : "=> #{value}"
|
210
|
+
end
|
211
|
+
|
212
|
+
# Print an error message and exit
|
213
|
+
#
|
214
|
+
# @param error [String] the error message
|
215
|
+
#
|
216
|
+
# @raise [SystemExit]
|
217
|
+
#
|
218
|
+
def stop!(error)
|
219
|
+
$stderr.puts "=> #{error}"
|
220
|
+
exit
|
221
|
+
end
|
222
|
+
|
223
|
+
class << self
|
224
|
+
# Get value from environment variable or stdin prompt
|
225
|
+
#
|
226
|
+
# @param key [String] the name of the environment variable
|
227
|
+
# @param prompt [optional, String] prompt to use if environment
|
228
|
+
# variable is missing
|
229
|
+
#
|
230
|
+
# @return [String] the value
|
231
|
+
#
|
232
|
+
def env_get(key, prompt = nil)
|
233
|
+
return ENV[key] if ENV[key]
|
234
|
+
|
235
|
+
$stdout.print "#{prompt}: " if prompt
|
236
|
+
$stdin.gets.chomp
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
data/lib/mode/sdk/client.rb
CHANGED
@@ -73,6 +73,15 @@ module Mode
|
|
73
73
|
get('/api/account', expect: [200]).body
|
74
74
|
end
|
75
75
|
|
76
|
+
# Test authentication credentials
|
77
|
+
#
|
78
|
+
# @return [true, false] whether the provided authentication credentials
|
79
|
+
# are valid
|
80
|
+
#
|
81
|
+
def authenticated?
|
82
|
+
get('/api/account', expect: [200, 401]).code == 200
|
83
|
+
end
|
84
|
+
|
76
85
|
private
|
77
86
|
|
78
87
|
def request(request, body, options = {})
|
@@ -44,7 +44,6 @@ module Mode
|
|
44
44
|
|
45
45
|
private
|
46
46
|
|
47
|
-
DEFAULT_HOST = 'https://modeanalytics.com'
|
48
47
|
DEFAULT_CONTENT_TYPE = 'application/json'
|
49
48
|
USER_AGENT = "mode-sdk/#{Mode::Sdk::VERSION}"
|
50
49
|
|
@@ -118,7 +117,7 @@ module Mode
|
|
118
117
|
# @return [String] the API host
|
119
118
|
#
|
120
119
|
def host
|
121
|
-
|
120
|
+
Mode::Sdk.config.host
|
122
121
|
end
|
123
122
|
|
124
123
|
private
|
@@ -10,12 +10,40 @@ module Mode
|
|
10
10
|
# [http://developer.modeanalytics.com/#page:authentication]
|
11
11
|
# (http://developer.modeanalytics.com/#page:authentication)
|
12
12
|
#
|
13
|
-
# @
|
14
|
-
# @
|
15
|
-
# @
|
13
|
+
# @attr_writer [String] token Mode API token
|
14
|
+
# @attr_writer [String] secret Mode API secret
|
15
|
+
# @attr_writer [String] host Mode API host
|
16
16
|
#
|
17
17
|
class Configuration
|
18
|
-
|
18
|
+
# The default host to use for Mode API requests
|
19
|
+
#
|
20
|
+
DEFAULT_HOST = 'https://modeanalytics.com'
|
21
|
+
|
22
|
+
attr_writer :token, :secret, :host
|
23
|
+
|
24
|
+
# Mode API token
|
25
|
+
#
|
26
|
+
# @return [String] the token
|
27
|
+
#
|
28
|
+
def token
|
29
|
+
@token || ENV['MODE_TOKEN']
|
30
|
+
end
|
31
|
+
|
32
|
+
# Mode API secret
|
33
|
+
#
|
34
|
+
# @return [String] the secret
|
35
|
+
#
|
36
|
+
def secret
|
37
|
+
@secret || ENV['MODE_SECRET']
|
38
|
+
end
|
39
|
+
|
40
|
+
# Mode API host
|
41
|
+
#
|
42
|
+
# @return [String] the host
|
43
|
+
#
|
44
|
+
def host
|
45
|
+
@host || ENV['MODE_HOST'] || DEFAULT_HOST
|
46
|
+
end
|
19
47
|
end
|
20
48
|
end
|
21
49
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
require 'csv'
|
4
|
+
|
5
|
+
module Mode
|
6
|
+
module Sdk
|
7
|
+
# Represents a local CSV file
|
8
|
+
#
|
9
|
+
# @attr_reader [String] original_path the path of the file
|
10
|
+
#
|
11
|
+
class CsvFile
|
12
|
+
attr_reader :original_path
|
13
|
+
|
14
|
+
# Construct a new CsvFile instance
|
15
|
+
#
|
16
|
+
# @param original_path [String] the original path of the file
|
17
|
+
#
|
18
|
+
# @return [Mode::Sdk::CsvFile] the instance
|
19
|
+
#
|
20
|
+
def initialize(original_path)
|
21
|
+
@original_path = original_path
|
22
|
+
end
|
23
|
+
|
24
|
+
# The expanded path of the file
|
25
|
+
#
|
26
|
+
# @return [String] the expanded path
|
27
|
+
#
|
28
|
+
def path
|
29
|
+
@path ||= File.expand_path(original_path)
|
30
|
+
end
|
31
|
+
|
32
|
+
# The base name of the file with CSV extension removed, if present
|
33
|
+
#
|
34
|
+
# @return [String] the filename
|
35
|
+
#
|
36
|
+
def name
|
37
|
+
File.basename(path, '.csv')
|
38
|
+
end
|
39
|
+
|
40
|
+
# The size of the file
|
41
|
+
#
|
42
|
+
# @return [Integer] the filesize
|
43
|
+
#
|
44
|
+
def size
|
45
|
+
@size ||= File.size(path)
|
46
|
+
end
|
47
|
+
|
48
|
+
# An array of all lines in the file
|
49
|
+
#
|
50
|
+
# @return [Array<String>] array of lines
|
51
|
+
#
|
52
|
+
def lines
|
53
|
+
@lines ||= File.open(path, 'r', &:read).split(/[\r\n]+/)
|
54
|
+
end
|
55
|
+
|
56
|
+
# The total number of lines in the file
|
57
|
+
#
|
58
|
+
# @return [Integer] number of lines
|
59
|
+
#
|
60
|
+
def line_count
|
61
|
+
@line_count ||= lines.size
|
62
|
+
end
|
63
|
+
|
64
|
+
# An array of parsed CSV header column names
|
65
|
+
#
|
66
|
+
# @return [Array<String>] array of column names
|
67
|
+
#
|
68
|
+
def header
|
69
|
+
@header ||= CSV.parse_line(lines[0])
|
70
|
+
end
|
71
|
+
|
72
|
+
# All lines of the file except the header with standardized LF line
|
73
|
+
# breaks
|
74
|
+
#
|
75
|
+
# @return [String] the file content
|
76
|
+
#
|
77
|
+
def content
|
78
|
+
@content ||= lines[1..-1].join("\n")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/mode/sdk/version.rb
CHANGED
data/mode-sdk.gemspec
CHANGED
@@ -20,6 +20,8 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.test_files = spec.files.grep(/^(test|spec|features)/)
|
21
21
|
spec.require_paths = ['lib']
|
22
22
|
|
23
|
+
spec.required_ruby_version = '~> 2.0'
|
24
|
+
|
23
25
|
spec.add_development_dependency 'bundler', '~> 1.3'
|
24
26
|
spec.add_development_dependency 'rake'
|
25
27
|
spec.add_development_dependency 'rspec'
|
@@ -0,0 +1,233 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Mode::Sdk::Cli do
|
6
|
+
describe '#import!' do
|
7
|
+
let :cli do
|
8
|
+
Mode::Sdk::Cli.new
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'produces success output' do
|
12
|
+
import = double(:import)
|
13
|
+
|
14
|
+
success_repr = {
|
15
|
+
'state' => 'succeeded',
|
16
|
+
'_embedded' => {
|
17
|
+
'table' => { '_links' => { 'web' => { 'href' => 'http://test' } } }
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
expect(import).to receive(:poll)
|
22
|
+
.and_yield('state' => 'enqueued')
|
23
|
+
.and_yield(success_repr)
|
24
|
+
|
25
|
+
expect(cli).to receive(:import).and_return(import)
|
26
|
+
expect(cli).to receive(:authenticate!)
|
27
|
+
expect(cli).to receive(:validate_options!)
|
28
|
+
|
29
|
+
lines = [
|
30
|
+
'=> Import enqueued...',
|
31
|
+
'=> Import succeeded',
|
32
|
+
'=> Created table http://test',
|
33
|
+
''
|
34
|
+
]
|
35
|
+
|
36
|
+
expect { cli.import! }.to output(lines.join("\n")).to_stdout
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'produces failure output' do
|
40
|
+
import = double(:import)
|
41
|
+
|
42
|
+
expect(import).to receive(:poll)
|
43
|
+
.and_yield('state' => 'enqueued')
|
44
|
+
.and_yield('state' => 'failed')
|
45
|
+
|
46
|
+
expect(cli).to receive(:import).and_return(import)
|
47
|
+
expect(cli).to receive(:authenticate!)
|
48
|
+
expect(cli).to receive(:validate_options!)
|
49
|
+
|
50
|
+
lines = [
|
51
|
+
'=> Import enqueued...',
|
52
|
+
'=> Import failed',
|
53
|
+
{ 'state' => 'failed' },
|
54
|
+
''
|
55
|
+
]
|
56
|
+
|
57
|
+
expect { cli.import! }.to output(lines.join("\n")).to_stdout
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#validate_options!' do
|
62
|
+
it 'returns true if path is present' do
|
63
|
+
cli = Mode::Sdk::Cli.new(file: 'filename.csv')
|
64
|
+
|
65
|
+
expect(cli.send(:validate_options!)).to eq(true)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'stops if path is blank' do
|
69
|
+
cli = Mode::Sdk::Cli.new(file: '')
|
70
|
+
|
71
|
+
expect {
|
72
|
+
silence_stderr do
|
73
|
+
cli.send(:validate_options!)
|
74
|
+
end
|
75
|
+
}.to raise_exception(SystemExit)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#table' do
|
80
|
+
let :cli do
|
81
|
+
Mode::Sdk::Cli.new(file: 'filename.csv')
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'builds table and assigns attributes' do
|
85
|
+
expect(cli).to receive(:upload_token).and_return('token')
|
86
|
+
expect(cli).to receive(:columns).and_return([{ foo: 'bar' }])
|
87
|
+
|
88
|
+
table = cli.send(:table)
|
89
|
+
|
90
|
+
expect(table).to be_an_instance_of(Mode::Sdk::Table)
|
91
|
+
expect(table.upload_token).to eq('token')
|
92
|
+
expect(table.columns).to eq([{ foo: 'bar' }])
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe '#upsert_table' do
|
97
|
+
describe 'if table exists' do
|
98
|
+
let :table do
|
99
|
+
double(:table, exists?: true, full_name: 'full.name')
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'exits without replace flag' do
|
103
|
+
cli = Mode::Sdk::Cli.new(replace: false)
|
104
|
+
expect(cli).to receive(:table).and_return(table).at_least(:once)
|
105
|
+
|
106
|
+
expect {
|
107
|
+
silence_stderr do
|
108
|
+
cli.send(:upsert_table)
|
109
|
+
end
|
110
|
+
}.to raise_error(SystemExit)
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'replaces with replace flag' do
|
114
|
+
cli = Mode::Sdk::Cli.new(replace: true)
|
115
|
+
expect(cli).to receive(:table).and_return(table).at_least(:once)
|
116
|
+
expect(table).to receive(:replace)
|
117
|
+
|
118
|
+
expect {
|
119
|
+
cli.send(:upsert_table)
|
120
|
+
}.to output("=> Replacing full.name...\n").to_stdout
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe 'if table does not exist' do
|
125
|
+
let :table do
|
126
|
+
double(:table, exists?: false, full_name: 'full.name')
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'creates table' do
|
130
|
+
cli = Mode::Sdk::Cli.new
|
131
|
+
expect(cli).to receive(:table).and_return(table).at_least(:once)
|
132
|
+
expect(table).to receive(:create)
|
133
|
+
|
134
|
+
expect {
|
135
|
+
cli.send(:upsert_table)
|
136
|
+
}.to output("=> Creating full.name...\n").to_stdout
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe '#authenticate!' do
|
142
|
+
it 'memoizes successful result' do
|
143
|
+
cli = Mode::Sdk::Cli.new
|
144
|
+
|
145
|
+
expect(Mode::Sdk).to receive(:authenticated?).and_return(true).once
|
146
|
+
|
147
|
+
3.times do
|
148
|
+
expect(cli.send(:authenticate!)).to eq(true)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'raises exception if unsuccessful' do
|
153
|
+
cli = Mode::Sdk::Cli.new
|
154
|
+
|
155
|
+
expect(Mode::Sdk).to receive(:authenticated?).and_return(false)
|
156
|
+
|
157
|
+
expect {
|
158
|
+
silence_stderr do
|
159
|
+
cli.send(:authenticate!)
|
160
|
+
end
|
161
|
+
}.to raise_error(SystemExit)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe '#columns' do
|
166
|
+
it 'forms columns from csv header' do
|
167
|
+
cli = Mode::Sdk::Cli.new
|
168
|
+
|
169
|
+
csv = double(:csv, header: %w(foo bar))
|
170
|
+
|
171
|
+
expect(cli).to receive(:csv).and_return(csv)
|
172
|
+
|
173
|
+
columns = [
|
174
|
+
{ name: 'foo', type: 'string' },
|
175
|
+
{ name: 'bar', type: 'string' }]
|
176
|
+
|
177
|
+
expect(cli.send(:columns)).to eq(columns)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe '.env_get' do
|
182
|
+
it 'returns value from environment without prompting' do
|
183
|
+
expect(ENV).to receive(:[]).with('FOO').and_return('bar').at_least(:once)
|
184
|
+
|
185
|
+
expect(Mode::Sdk::Cli.env_get('FOO')).to eq('bar')
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'prompts for missing environment variable' do
|
189
|
+
expect($stdin).to receive(:gets).and_return("input\n")
|
190
|
+
|
191
|
+
expect(Mode::Sdk::Cli.env_get('FOO')).to eq('input')
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
describe '#upload_token' do
|
196
|
+
it 'returns value from options if provided' do
|
197
|
+
cli = Mode::Sdk::Cli.new(upload_token: 'token')
|
198
|
+
|
199
|
+
expect_any_instance_of(Mode::Sdk::Upload).to receive(:token).never
|
200
|
+
|
201
|
+
expect(cli.send(:upload_token)).to eq('token')
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'creates upload if necessary' do
|
205
|
+
cli = Mode::Sdk::Cli.new(file: 'path')
|
206
|
+
|
207
|
+
expect_any_instance_of(Mode::Sdk::CsvFile).to receive(:content)
|
208
|
+
expect_any_instance_of(Mode::Sdk::CsvFile).to(
|
209
|
+
receive(:line_count).and_return(10))
|
210
|
+
|
211
|
+
expect_any_instance_of(Mode::Sdk::Upload).to receive(:create)
|
212
|
+
expect_any_instance_of(Mode::Sdk::Upload).to(
|
213
|
+
receive(:token).and_return('token'))
|
214
|
+
|
215
|
+
lines = ['=> Uploading path (10 lines)...', '']
|
216
|
+
|
217
|
+
expect { cli.send(:upload_token) }.to output(lines.join("\n")).to_stdout
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
describe '#import' do
|
222
|
+
it 'returns table import' do
|
223
|
+
cli = Mode::Sdk::Cli.new
|
224
|
+
|
225
|
+
body = { '_links' => { 'self' => { 'href' => 'path' } } }
|
226
|
+
response = double(:response, body: body)
|
227
|
+
expect(cli).to receive(:upsert_table).and_return(response)
|
228
|
+
|
229
|
+
import = cli.send(:import)
|
230
|
+
expect(import).to be_an_instance_of(Mode::Sdk::TableImport)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
@@ -45,7 +45,7 @@ describe Mode::Sdk::Client::Response do
|
|
45
45
|
end
|
46
46
|
|
47
47
|
it 'raises exception if response is unexpected' do
|
48
|
-
http_response = double(:http_response, code: '200')
|
48
|
+
http_response = double(:http_response, code: '200', body: '{}')
|
49
49
|
|
50
50
|
response = Mode::Sdk::Client::Response.new(http_response, [201])
|
51
51
|
|
@@ -68,4 +68,24 @@ describe Mode::Sdk::Client do
|
|
68
68
|
expect(response).to eq('username' => 'someone')
|
69
69
|
end
|
70
70
|
end
|
71
|
+
|
72
|
+
describe '.authenticated' do
|
73
|
+
it 'returns true if authenticated' do
|
74
|
+
mock_response = double(:response, code: 200)
|
75
|
+
|
76
|
+
expect_any_instance_of(Mode::Sdk::Client::Request).to(
|
77
|
+
receive(:response).and_return(mock_response))
|
78
|
+
|
79
|
+
expect(Mode::Sdk::Client.authenticated?).to eq(true)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'returns false if not authenticated' do
|
83
|
+
mock_response = double(:response, code: 401)
|
84
|
+
|
85
|
+
expect_any_instance_of(Mode::Sdk::Client::Request).to(
|
86
|
+
receive(:response).and_return(mock_response))
|
87
|
+
|
88
|
+
expect(Mode::Sdk::Client.authenticated?).to eq(false)
|
89
|
+
end
|
90
|
+
end
|
71
91
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Mode::Sdk::CsvFile do
|
6
|
+
let :file do
|
7
|
+
Mode::Sdk::CsvFile.new(test_file_path('population_growth.csv'))
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#name' do
|
11
|
+
it 'returns name without csv extension' do
|
12
|
+
expect(file.name).to eq('population_growth')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#size' do
|
17
|
+
it 'returns size' do
|
18
|
+
expect(file.size).to be_an_instance_of(Fixnum)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#lines' do
|
23
|
+
it 'parses csv' do
|
24
|
+
expect(file.lines).to be_an_instance_of(Array)
|
25
|
+
expect(file.lines.size).to eq(10)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#line_count' do
|
30
|
+
it 'returns line count' do
|
31
|
+
expect(file.line_count).to eq(10)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#header' do
|
36
|
+
it 'parses header' do
|
37
|
+
expect(file.header).to eq(%w(date value))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#content' do
|
42
|
+
it 'returns content' do
|
43
|
+
expect(file.content).to be_an_instance_of(String)
|
44
|
+
expect(file.content.scan(/\n/).count).to eq(8)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/spec/lib/mode/sdk_spec.rb
CHANGED
@@ -47,4 +47,12 @@ describe Mode::Sdk do
|
|
47
47
|
expect(Mode::Sdk.username).to eq('someone')
|
48
48
|
end
|
49
49
|
end
|
50
|
+
|
51
|
+
describe '.authenticated?' do
|
52
|
+
it 'returns value from client' do
|
53
|
+
expect(Mode::Sdk::Client).to receive(:authenticated?).and_return(true)
|
54
|
+
|
55
|
+
expect(Mode::Sdk.authenticated?).to eq(true)
|
56
|
+
end
|
57
|
+
end
|
50
58
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -8,7 +8,12 @@ Bundler.setup
|
|
8
8
|
|
9
9
|
require 'mode/sdk'
|
10
10
|
|
11
|
+
Dir['./spec/support/**/*.rb'].each { |file| require file }
|
12
|
+
|
11
13
|
RSpec.configure do |config|
|
14
|
+
config.include FileHelper
|
15
|
+
config.include StreamsHelper
|
16
|
+
|
12
17
|
config.before(:all) do
|
13
18
|
Mode::Sdk.configure do |mode_config|
|
14
19
|
mode_config.host = 'http://testhost'
|
@@ -0,0 +1,10 @@
|
|
1
|
+
date,value
|
2
|
+
2012-12-31,0.74379786478725
|
3
|
+
2011-12-31,0.72847474918508
|
4
|
+
2010-12-31,0.82931990108905
|
5
|
+
2009-12-31,0.87665129880291
|
6
|
+
2008-12-31,0.94586528728259
|
7
|
+
2007-12-31,0.95105524277243
|
8
|
+
2006-12-31,0.96425391713608
|
9
|
+
2005-12-31,0.92171316716121
|
10
|
+
2004-12-31,0.92548396894348
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mode-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Heather Rivers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-09-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -69,23 +69,28 @@ dependencies:
|
|
69
69
|
description: Mode Ruby SDK
|
70
70
|
email:
|
71
71
|
- heather@modeanalytics.com
|
72
|
-
executables:
|
72
|
+
executables:
|
73
|
+
- mode_import
|
73
74
|
extensions: []
|
74
75
|
extra_rdoc_files: []
|
75
76
|
files:
|
76
77
|
- .gitignore
|
78
|
+
- .travis.yml
|
77
79
|
- .yardopts
|
78
80
|
- Gemfile
|
79
81
|
- LICENSE.txt
|
80
82
|
- README.md
|
81
83
|
- Rakefile
|
84
|
+
- bin/mode_import
|
82
85
|
- lib/mode/sdk.rb
|
86
|
+
- lib/mode/sdk/cli.rb
|
83
87
|
- lib/mode/sdk/client.rb
|
84
88
|
- lib/mode/sdk/client/request.rb
|
85
89
|
- lib/mode/sdk/client/response.rb
|
86
90
|
- lib/mode/sdk/column.rb
|
87
91
|
- lib/mode/sdk/column_set.rb
|
88
92
|
- lib/mode/sdk/configuration.rb
|
93
|
+
- lib/mode/sdk/csv_file.rb
|
89
94
|
- lib/mode/sdk/hash_util.rb
|
90
95
|
- lib/mode/sdk/table.rb
|
91
96
|
- lib/mode/sdk/table_import.rb
|
@@ -93,11 +98,13 @@ files:
|
|
93
98
|
- lib/mode/sdk/version.rb
|
94
99
|
- lib/mode/sdk/warehouse_util.rb
|
95
100
|
- mode-sdk.gemspec
|
101
|
+
- spec/lib/mode/sdk/cli_spec.rb
|
96
102
|
- spec/lib/mode/sdk/client/request_spec.rb
|
97
103
|
- spec/lib/mode/sdk/client/response_spec.rb
|
98
104
|
- spec/lib/mode/sdk/client_spec.rb
|
99
105
|
- spec/lib/mode/sdk/column_set_spec.rb
|
100
106
|
- spec/lib/mode/sdk/column_spec.rb
|
107
|
+
- spec/lib/mode/sdk/csv_file_spec.rb
|
101
108
|
- spec/lib/mode/sdk/hash_util_spec.rb
|
102
109
|
- spec/lib/mode/sdk/table_import_spec.rb
|
103
110
|
- spec/lib/mode/sdk/table_spec.rb
|
@@ -105,6 +112,9 @@ files:
|
|
105
112
|
- spec/lib/mode/sdk/warehouse_util_spec.rb
|
106
113
|
- spec/lib/mode/sdk_spec.rb
|
107
114
|
- spec/spec_helper.rb
|
115
|
+
- spec/support/file_helper.rb
|
116
|
+
- spec/support/files/population_growth.csv
|
117
|
+
- spec/support/streams_helper.rb
|
108
118
|
homepage: https://github.com/mode/mode-ruby-sdk
|
109
119
|
licenses:
|
110
120
|
- MIT
|
@@ -115,9 +125,9 @@ require_paths:
|
|
115
125
|
- lib
|
116
126
|
required_ruby_version: !ruby/object:Gem::Requirement
|
117
127
|
requirements:
|
118
|
-
- -
|
128
|
+
- - ~>
|
119
129
|
- !ruby/object:Gem::Version
|
120
|
-
version: '0'
|
130
|
+
version: '2.0'
|
121
131
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
132
|
requirements:
|
123
133
|
- - '>='
|
@@ -125,16 +135,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
125
135
|
version: '0'
|
126
136
|
requirements: []
|
127
137
|
rubyforge_project:
|
128
|
-
rubygems_version: 2.0.
|
138
|
+
rubygems_version: 2.0.14
|
129
139
|
signing_key:
|
130
140
|
specification_version: 4
|
131
141
|
summary: Ruby SDK for interacting with the Mode Analytics API
|
132
142
|
test_files:
|
143
|
+
- spec/lib/mode/sdk/cli_spec.rb
|
133
144
|
- spec/lib/mode/sdk/client/request_spec.rb
|
134
145
|
- spec/lib/mode/sdk/client/response_spec.rb
|
135
146
|
- spec/lib/mode/sdk/client_spec.rb
|
136
147
|
- spec/lib/mode/sdk/column_set_spec.rb
|
137
148
|
- spec/lib/mode/sdk/column_spec.rb
|
149
|
+
- spec/lib/mode/sdk/csv_file_spec.rb
|
138
150
|
- spec/lib/mode/sdk/hash_util_spec.rb
|
139
151
|
- spec/lib/mode/sdk/table_import_spec.rb
|
140
152
|
- spec/lib/mode/sdk/table_spec.rb
|
@@ -142,4 +154,6 @@ test_files:
|
|
142
154
|
- spec/lib/mode/sdk/warehouse_util_spec.rb
|
143
155
|
- spec/lib/mode/sdk_spec.rb
|
144
156
|
- spec/spec_helper.rb
|
145
|
-
|
157
|
+
- spec/support/file_helper.rb
|
158
|
+
- spec/support/files/population_growth.csv
|
159
|
+
- spec/support/streams_helper.rb
|