hover-ruby-client 0.3.1

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.
@@ -0,0 +1,141 @@
1
+ require 'api-auth'
2
+ require 'uri'
3
+ require 'net/http'
4
+ require 'json'
5
+
6
+ module Hover
7
+ module Client
8
+ class HTTP
9
+ attr_accessor :prefix, :site
10
+
11
+ def initialize(site = 'http://localhost:3000', prefix = 'api/v1')
12
+ @prefix = prefix
13
+ @site = site
14
+ end
15
+
16
+ def authenticate(request)
17
+ end
18
+
19
+ def make_uri(path, parameters = {})
20
+ parts = [site, prefix, path]
21
+ uri = URI.parse(parts.map(&:presence).compact.join('/'))
22
+ uri = URI.parse("#{uri.to_s}?#{URI.encode_www_form(parameters)}") unless parameters.empty?
23
+
24
+ uri
25
+ end
26
+
27
+ def make_request(request, uri)
28
+ authenticate(request)
29
+
30
+ http = Net::HTTP.new(uri.host, uri.port)
31
+ http.use_ssl = true if 'https' == uri.scheme
32
+
33
+ response = http.request(request)
34
+
35
+ raise_errors_for_cloudfront_response(response)
36
+
37
+ response
38
+ end
39
+
40
+ def get(path, parameters = {})
41
+ uri = make_uri(path, parameters)
42
+ request = Net::HTTP::Get.new(uri)
43
+ response = make_request(request, uri)
44
+
45
+ response
46
+ end
47
+
48
+ def get_redirect_location(path, parameters = {}, &block)
49
+ uri = make_uri(path, parameters)
50
+ request = Net::HTTP::Get.new(uri)
51
+ redirect_response = make_request(request, uri)
52
+
53
+ if redirect_response.is_a?(Net::HTTPRedirection) && redirect_response['location']
54
+ redirect_response['location']
55
+ else
56
+ nil
57
+ end
58
+ end
59
+
60
+ def post(path, parameters = {})
61
+ uri = make_uri(path)
62
+ request = Net::HTTP::Post.new(uri)
63
+ make_form_request(request, uri, parameters)
64
+ end
65
+
66
+ def make_form_request(request, uri, parameters)
67
+ request.set_form_data(parameters)
68
+ response = make_request(request, uri)
69
+ response
70
+ end
71
+
72
+ def put(path, parameters = {})
73
+ uri = make_uri(path)
74
+ request = Net::HTTP::Put.new(uri)
75
+ make_form_request(request, uri, parameters)
76
+ end
77
+
78
+ def patch(path, parameters = {})
79
+ uri = make_uri(path)
80
+ request = Net::HTTP::Patch.new(uri)
81
+ make_form_request(request, uri, parameters)
82
+ end
83
+
84
+ def delete(path, parameters = {})
85
+ uri = make_uri(path)
86
+ request = Net::HTTP::Delete.new(uri)
87
+ make_form_request(request, uri, parameters)
88
+ end
89
+
90
+ def parse_response(response)
91
+ raise_errors(response)
92
+
93
+ case response.code.to_i
94
+ when 204 # no content
95
+ {}
96
+ else
97
+ ::JSON.parse(response.body)
98
+ end
99
+ end
100
+
101
+ def raise_errors_for_cloudfront_response(response)
102
+ #
103
+ # If the server can't be reached cloudfront will respond with an
104
+ # error message of it's own. We need to catch those so that we can retry
105
+ # the requests.
106
+ #
107
+
108
+ return unless (response.header['Server'] =~ /Cloudfront/i)
109
+
110
+ raise ::Hover::Client::Unavailable
111
+ end
112
+
113
+ def raise_errors(response)
114
+ message = "(#{response.code}): - #{response.body}"
115
+
116
+ case response.code.to_i
117
+ when 400
118
+ raise BadRequest, message
119
+ when 401
120
+ raise Unauthorized, message
121
+ when 403
122
+ raise General, message
123
+ when 404
124
+ raise NotFound, message
125
+ when 500
126
+ raise InternalError, "Internal error: #{message}"
127
+ when 502..503
128
+ raise Unavailable, message
129
+ end
130
+ end
131
+ end
132
+
133
+ class BadRequest < StandardError; end
134
+ class Unauthorized < StandardError; end
135
+ class General < StandardError; end
136
+ class Unavailable < StandardError; end
137
+ class InternalError < StandardError; end
138
+ class NotFound < StandardError; end
139
+ class AloomaReportingError < StandardError; end
140
+ end
141
+ end
@@ -0,0 +1,17 @@
1
+ require 'hover/client/hover'
2
+
3
+ module Hover
4
+ module Client
5
+ class Manowar < ::Hover::Client::Hover
6
+
7
+ def submit_order(parameters = {})
8
+ json_post('orders.json', parameters)
9
+ end
10
+
11
+ def order_results(order_id)
12
+ json_get("orders/#{order_id}/results.json")
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,120 @@
1
+ require 'open-uri'
2
+ require 'tempfile'
3
+ require 'zip'
4
+
5
+ require 'hover/client/hover'
6
+
7
+ module Hover
8
+ module Client
9
+ class Midas < ::Hover::Client::Hover
10
+
11
+ #
12
+ # Users
13
+ #
14
+
15
+ def me
16
+ json_get('me.json')
17
+ end
18
+
19
+ #
20
+ # Orders
21
+ #
22
+
23
+ def orders(parameters = {})
24
+ json_get('orders.json', parameters)
25
+ end
26
+
27
+ def order(id, parameters = {})
28
+ json_get("orders/#{id}.json", parameters)
29
+ end
30
+
31
+ def order_complete_work(order_id)
32
+ put("orders/#{order_id}/work_complete.json")
33
+ end
34
+
35
+ #def update_order(id, order_attributes)
36
+ # put("orders/#{id}.json", order_parameters(order_attributes))
37
+ #end
38
+
39
+ def create_order(params = {})
40
+ json_post('orders.json', params)
41
+ end
42
+
43
+ def complete_order_upload(order_id)
44
+ patch("orders/#{order_id}/archive_uploading_complete.json")
45
+ end
46
+
47
+ #
48
+ # Images
49
+ #
50
+
51
+ def images(parameters = {})
52
+ json_get("images.json", parameters)
53
+ end
54
+
55
+ def image(id)
56
+ json_get("images/#{id}.json")
57
+ end
58
+
59
+ def download_image(image, parameters = {}, &block)
60
+ return unless url = get_redirect_location("images/#{image["id"]}.jpg", parameters)
61
+
62
+ Tempfile.open(["image-download", ".jpg"]) do |output|
63
+ output.write(Kernel.open(url).read)
64
+ output.rewind
65
+
66
+ yield output
67
+ end
68
+ end
69
+
70
+ def upload_image(image, io)
71
+ raise "Already Uploaded" unless upload_url = image["image"].try(:[], "upload_url")
72
+
73
+ file = (io.is_a?(String) ? File.open(io) : io)
74
+
75
+ uri = URI.parse(upload_url)
76
+
77
+ request = Net::HTTP::Put.new(uri.request_uri)
78
+ request.body_stream = file
79
+ request.content_length = file.size
80
+ request['Content-Type'] = ''
81
+
82
+ http = Net::HTTP.new(uri.host, uri.port)
83
+ http.use_ssl = true
84
+ response = http.request(request)
85
+ end
86
+
87
+ def image_upload_urls(order_id)
88
+ json_get("images/upload_urls.json", order_id: order_id)
89
+ end
90
+
91
+ def upload_images_for_order(order_id, input_path)
92
+ image_array = image_upload_urls(order_id).map { |upload_url| {"image" => {"upload_url" => upload_url}} }
93
+ file_paths = Dir.glob(File.join(input_path, "*.jp*"))
94
+
95
+ if file_paths.size > image_array.size
96
+ raise("Trying to upload #{file_paths.size} files. #{image_array.size} is the maximum.")
97
+ end
98
+
99
+ file_paths.each do |file_path|
100
+ image = image_array.pop
101
+ upload_image(image, file_path)
102
+ end
103
+ end
104
+
105
+ def upload_images_from_prometheus_export(order_id, export_zip_path)
106
+ Zip::File.open(export_zip_path) do |zip|
107
+ Dir.mktmpdir do |directory_path|
108
+ zip.entries.select { |entry| entry.name =~ /original_image\.jp(g|eg)$/ }.each do |entry|
109
+ basename = File.basename(entry.name)
110
+ next if basename =~ /^\./
111
+ entry.extract(File.join(directory_path, basename)) { true }
112
+ end
113
+
114
+ upload_images_for_order(order_id, directory_path)
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,89 @@
1
+ require 'hover/client/hover'
2
+
3
+ module Hover
4
+ module Client
5
+ class Static < ::Hover::Client::Hover
6
+
7
+ attr_accessor :application, :environment
8
+
9
+ def initialize(application, environment, client_id, client_secret, site = 'http://localhost:3000', prefix = 'api/v1')
10
+ super(client_id, client_secret, site, prefix)
11
+
12
+ self.application = application
13
+ self.environment = environment
14
+ end
15
+
16
+ def create_metric(name:, value:, happened_at: Time.now.utc, tags: {}, remote_record_id: nil)
17
+ return unless alooma_url
18
+
19
+ parameters = {
20
+ "metric[name]" => name,
21
+ 'metric[value]' => value,
22
+ 'metric[happened_at]' => happened_at.to_s,
23
+ 'metric[environment]' => environment,
24
+ 'metric[application]' => application
25
+ }.merge(self.class.tags_to_params(tags))
26
+ parameters['metric[remote_record_id]'] = remote_record_id if remote_record_id
27
+ add_unique_identifier(parameters)
28
+
29
+ post_to_alooma(parameters, alooma_url)
30
+ end
31
+
32
+ def alooma_url
33
+ @alooma_url ||= if Rails.env.staging?
34
+ "https://inputs.alooma.com/rest/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnROYW1lIjoiaG92ZXItc2YtMSIsImlucHV0TGFiZWwiOiJzdGF0aWNfc3RhZ2luZyIsImlucHV0VHlwZSI6IlJFU1RBUEkifQ.Iba3VM9GaOjzJEmUFPuHDA6oaQT7TBe9A0iMxfYW0x0"
35
+ elsif Rails.env.production?
36
+ "https://inputs.alooma.com/rest/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnROYW1lIjoiaG92ZXItc2YtMSIsImlucHV0TGFiZWwiOiJzdGF0aWNfcHJvZHVjdGlvbiIsImlucHV0VHlwZSI6IlJFU1RBUEkifQ.jiuWVLDPDvZSv4bmiy54GVEpOre19bDqr9ZC7Y-HKwo"
37
+ end
38
+ end
39
+
40
+ def post_to_alooma(parameters, url)
41
+ uri = URI.parse(url)
42
+ request = Net::HTTP::Post.new(uri)
43
+ request.set_form_data(parameters)
44
+ http = Net::HTTP.new(uri.host, uri.port)
45
+ http.use_ssl = true if 'https' == uri.scheme
46
+ response = http.request(request)
47
+
48
+ unless (200 .. 299).include?(response.code.to_i)
49
+ raise AloomaReportingError.new("Error reporting to alooma. #{response.inspect}")
50
+ end
51
+
52
+ response
53
+ end
54
+
55
+ def add_unique_identifier(parameters)
56
+ sorted_array = parameters.to_a.sort_by(&:first)
57
+ metric_identifier = Digest::SHA256.hexdigest(sorted_array.to_json)
58
+
59
+ parameters['metric[identifier]'] = metric_identifier
60
+ end
61
+
62
+ #
63
+ # Class Methods
64
+ #
65
+
66
+ def self.tags_to_params(tags, parent_keys = ['metric', 'tags'])
67
+ params = {}
68
+
69
+ tags.each do |key, value|
70
+ if value.is_a?(Hash)
71
+ params.merge!(tags_to_params(value, (parent_keys + [key])))
72
+ elsif value.is_a?(Array)
73
+ params["#{param_name(*parent_keys, key)}[]"] = value
74
+ else
75
+ params[param_name(*parent_keys, key)] = value
76
+ end
77
+ end
78
+
79
+ params
80
+ end
81
+
82
+ def self.param_name(*args)
83
+ first = args.shift
84
+ "#{first}" + args.map{ |arg| "[#{arg}]" }.join
85
+ end
86
+
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,25 @@
1
+ require 'yajl'
2
+
3
+ module Hover
4
+ module Decoder
5
+ class JSONStream
6
+
7
+ def initialize(s3_object, block = ->(object) {})
8
+ @parser = Yajl::Parser.new
9
+ @s3_object = s3_object
10
+
11
+ @parser.on_parse_complete = block
12
+ end
13
+
14
+ def start
15
+ bucket_name = @s3_object.bucket.name
16
+ key = @s3_object.key
17
+
18
+ @s3_object.client.get_object(bucket: bucket_name, key: key) do |chunk|
19
+ @parser << chunk
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ module Hover
2
+ module Encoder
3
+ class JSONStream
4
+ attr_accessor :file
5
+
6
+ def initialize
7
+ end
8
+
9
+ def open(path)
10
+ self.file = File.open(path, 'w')
11
+ end
12
+
13
+ def close
14
+ self.file.close
15
+ self.file = nil
16
+ end
17
+
18
+ def append(attributes, klass)
19
+ self.file.puts(JSON.dump(attributes.merge('class' => klass.name)))
20
+ end
21
+
22
+ def append_each(attributes_sets, klass)
23
+ attributes_sets.each do |attributes|
24
+ append(attributes, klass)
25
+ end
26
+ end
27
+
28
+ def append_records(scope)
29
+ scope.find_each do |record|
30
+ append(record.attributes.as_json, record.class)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,134 @@
1
+ module Hover
2
+ module Importer
3
+ class ActiveRecord
4
+ attr_accessor :remote_local_map, :update_finder, :column_changes
5
+
6
+ def initialize(remote_local_map, update_finder = nil, column_changes = {})
7
+ self.remote_local_map = Hash.new { |hash, key| hash[key] = {} }
8
+ self.remote_local_map.merge!(remote_local_map)
9
+ self.update_finder = update_finder
10
+ self.column_changes = column_changes
11
+ end
12
+
13
+ def change_columns(attributes)
14
+ attributes.inject({}) do |hash, key_value|
15
+ hash[(column_changes[key_value[0]] || key_value[0])] = key_value[1]
16
+ hash
17
+ end
18
+ end
19
+
20
+ def update(table_name, records_attributes)
21
+ klass = table_name.singularize.camelize.constantize
22
+ records_attributes.collect { |attributes| update_record(klass, attributes) }
23
+ end
24
+
25
+ def update_record(klass, attributes)
26
+ return unless record = find_record_to_update(klass, attributes)
27
+
28
+ attributes = change_columns(attributes.dup)
29
+ s3_file_attributes = s3_file_keys(klass, attributes)
30
+
31
+ attributes.delete(klass.primary_key)
32
+ attributes.reject! { |key, value| !klass.column_names.include?(key) }
33
+ attributes.merge!(s3_file_attributes) if s3_file_attributes.is_a?(Hash)
34
+
35
+ record.assign_attributes(attributes)
36
+ record.save!(validate: false)
37
+
38
+ [klass, record.id]
39
+ end
40
+
41
+ def s3_file_keys(klass, attributes)
42
+ klass.try(:s3_file_versions).try(:keys).try(:inject, {}) do |output, attribute|
43
+ if value = attributes.delete(attribute.to_s)
44
+ output["#{attribute}_s3_keys"] = value
45
+ end
46
+
47
+ output
48
+ end
49
+ end
50
+
51
+ def find_record_to_update(klass, attributes)
52
+ self.update_finder.call(self, klass, attributes)
53
+ end
54
+
55
+ def import_results(results)
56
+ class_id_sets = []
57
+
58
+ results.each do |table_name, records_attributes|
59
+ if table_class_id_sets = import_table(table_name, records_attributes)
60
+ class_id_sets.concat(table_class_id_sets)
61
+ end
62
+ end
63
+
64
+ class_id_sets
65
+ end
66
+
67
+ def import_table(table_name, records_attributes)
68
+ klass = table_name.singularize.camelize.constantize
69
+
70
+ records_attributes.collect { |attributes|
71
+ import_record(klass, attributes) unless attributes.nil?
72
+ }
73
+ end
74
+
75
+ def import_record(klass, attributes)
76
+ attributes = change_columns(attributes.dup)
77
+ external_primary_key = attributes.delete(klass.primary_key)
78
+ s3_file_attributes = s3_file_keys(klass, attributes)
79
+
80
+ attributes.reject! { |key, value| !klass.column_names.include?(key) }
81
+ attributes.merge!(s3_file_attributes) if s3_file_attributes.is_a?(Hash)
82
+
83
+ record = klass.new(attributes)
84
+ return unless last_insert_id = insert_record(record)
85
+
86
+ remote_local_map[klass.table_name][external_primary_key] = last_insert_id
87
+
88
+ [klass, last_insert_id]
89
+ end
90
+
91
+ # see https://github.com/rails/rails/blob/v5.2.0/activerecord/lib/active_record/persistence.rb#L729-L741
92
+ def insert_record(record)
93
+ attribute_names = record.class.column_names
94
+ attributes_values = record.send(:attributes_with_values_for_create, attribute_names)
95
+
96
+ primary_key = record.class._insert_record(attributes_values)
97
+
98
+ record[record.class.primary_key] = primary_key
99
+ end
100
+
101
+ def new_foreign_keys(record)
102
+ record.class.reflect_on_all_associations.inject({}) do |hash, association|
103
+ method = "new_foreign_keys_for_#{association.macro}"
104
+ method += "_polymorphic" if association.options[:polymorphic]
105
+
106
+ if respond_to?(method) && before_after = send(method, record, association)
107
+ hash.store(*before_after)
108
+ end
109
+
110
+ hash
111
+ end
112
+ end
113
+
114
+ def new_foreign_keys_for_belongs_to(record, association)
115
+ return unless id = self.remote_local_map[association.klass.table_name].try(:[], record[association.foreign_key])
116
+ [association.foreign_key, id]
117
+ end
118
+
119
+ def new_foreign_keys_for_belongs_to_polymorphic(record, association)
120
+ return unless id = self.remote_local_map[record[association.foreign_type].tableize].try(:[], record[association.foreign_key])
121
+ [association.foreign_key, id]
122
+ end
123
+
124
+ def update_foreign_keys(record)
125
+ foreign_key_attributes = new_foreign_keys(record)
126
+ return if foreign_key_attributes.empty?
127
+
128
+ record.class.unscoped.where(id: record.id).update_all(foreign_key_attributes)
129
+
130
+ [record.reload.class, record.id]
131
+ end
132
+ end
133
+ end
134
+ end