etna 0.1.15 → 0.1.16

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fcca17011305af65656dd4e432613c75738d9a96d1afeb96ff442f1d4b5fc116
4
- data.tar.gz: a166eb3b9f5f1bcaa3d4a6f8fbae742370bfb8a561a4e766c134f51449fe8292
3
+ metadata.gz: 345d1790412dd359129f41586677ac2ac1a53c55897768d5d695e45ac828dcb6
4
+ data.tar.gz: ad2a6121a6659fa1352268b1545b434a9a7777531e6a8bdea8a74d6d779f54f8
5
5
  SHA512:
6
- metadata.gz: fd1bd5668794b0afb00a99fd39545c24de02677391737caf1c1165562933fe0214f4662b00bf3e7b90b9da05727e11cdc2226103dbdd1ceb6d91223a1e68b766
7
- data.tar.gz: 1c5dfa81e6151ec5b2f12d546753e970d081f7b462ff839b525bed7ead4d6079fa296acdb330115ecefc20571aea63767b4f76b739434869553f79429cedaf3c
6
+ metadata.gz: 3994385c74ff519195d80cae05e87dc5bd57755efd55ef20e26fdcb5b658c0ae45da22c62a7a87e77805e8d74eed7a7fe44440f4c5cd766dcf8d4b1f9ed46598
7
+ data.tar.gz: e0b39e27d1b5d2aab03aeb5d7118ca6c7b908e94778fe37aee9f83deee1fac3191a399bca95ca5c147f10261092974d2d40fd22ee44862e50506f763d6c982d8
@@ -5,6 +5,7 @@
5
5
 
6
6
  require_relative './sign_service'
7
7
  require 'singleton'
8
+ require 'rollbar'
8
9
 
9
10
  module Etna::Application
10
11
  def self.included(other)
@@ -31,6 +32,12 @@ module Etna::Application
31
32
 
32
33
  def configure(opts)
33
34
  @config = opts
35
+
36
+ if (rollbar_config = config(:rollbar)) && rollbar_config[:access_token]
37
+ Rollbar.configure do |config|
38
+ config.access_token = rollbar_config[:access_token]
39
+ end
40
+ end
34
41
  end
35
42
 
36
43
  def setup_logger
@@ -40,10 +47,9 @@ module Etna::Application
40
47
  # Number of old copies of the log to keep.
41
48
  config(:log_copies) || 5,
42
49
  # How large the log can get before overturning.
43
- config(:log_size) || 1048576
50
+ config(:log_size) || 1048576,
44
51
  )
45
52
  log_level = (config(:log_level) || 'warn').upcase.to_sym
46
-
47
53
  @logger.level = Logger.const_defined?(log_level) ? Logger.const_get(log_level) : Logger::WARN
48
54
  end
49
55
 
@@ -4,13 +4,15 @@ require 'singleton'
4
4
 
5
5
  module Etna
6
6
  class Client
7
- def initialize(host, token)
7
+ def initialize(host, token, routes_available: true)
8
8
  @host = host.sub(%r!/$!,'')
9
9
  @token = token
10
10
 
11
- set_routes
12
11
 
13
- define_route_helpers
12
+ if routes_available
13
+ set_routes
14
+ define_route_helpers
15
+ end
14
16
  end
15
17
 
16
18
  attr_reader :routes
@@ -19,6 +21,29 @@ module Etna
19
21
  Etna::Route.path(route[:route], params)
20
22
  end
21
23
 
24
+ def multipart_post(endpoint, content, &block)
25
+ uri = request_uri(endpoint)
26
+ multipart = Net::HTTP::Post::Multipart.new uri.path, content
27
+ multipart.add_field('Authorization', "Etna #{@token}")
28
+ request(uri, multipart, &block)
29
+ end
30
+
31
+ def post(endpoint, params={}, &block)
32
+ body_request(Net::HTTP::Post, endpoint, params, &block)
33
+ end
34
+
35
+ def get(endpoint, params={}, &block)
36
+ query_request(Net::HTTP::Get, endpoint, params, &block)
37
+ end
38
+
39
+ def options(endpoint, params={}, &block)
40
+ query_request(Net::HTTP::Options, endpoint, params, &block)
41
+ end
42
+
43
+ def delete(endpoint, params={}, &block)
44
+ body_request(Net::HTTP::Delete, endpoint, params, &block)
45
+ end
46
+
22
47
  private
23
48
 
24
49
  def set_routes
@@ -60,28 +85,6 @@ module Etna
60
85
  end
61
86
  end
62
87
 
63
- def multipart_post(endpoint, content, &block)
64
- uri = request_uri(endpoint)
65
- multipart = Net::HTTP::Post::Multipart.new uri.path, content
66
- multipart.add_field('Authorization', "Etna #{@token}")
67
- request(uri, multipart, &block)
68
- end
69
-
70
- def post(endpoint, params={}, &block)
71
- body_request(Net::HTTP::Post, endpoint, params, &block)
72
- end
73
-
74
- def get(endpoint, params={}, &block)
75
- query_request(Net::HTTP::Get, endpoint, params, &block)
76
- end
77
-
78
- def options(endpoint, params={}, &block)
79
- query_request(Net::HTTP::Options, endpoint, params, &block)
80
- end
81
-
82
- def delete(endpoint, params={}, &block)
83
- body_request(Net::HTTP::Delete, endpoint, params, &block)
84
- end
85
88
 
86
89
  def body_request(type, endpoint, params={}, &block)
87
90
  uri = request_uri(endpoint)
@@ -0,0 +1,2 @@
1
+ require_relative './magma/client'
2
+ require_relative './magma/models'
@@ -0,0 +1,65 @@
1
+ require 'net/http/persistent'
2
+ require 'net/http/post/multipart'
3
+ require 'singleton'
4
+ require_relative '../../client'
5
+ require_relative './models'
6
+
7
+ module Etna
8
+ module Clients
9
+ class Magma
10
+ def initialize(host:, token:)
11
+ raise 'Magma client configuration is missing host.' unless host
12
+ raise 'Magma client configuration is missing token.' unless token
13
+ @etna_client = ::Etna::Client.new(host, token, routes_available: false)
14
+ end
15
+
16
+ # This endpoint returns models and records by name:
17
+ # e.g. params:
18
+ # {
19
+ # model_name: "model_one", # or "all"
20
+ # record_names: [ "rn1", "rn2" ], # or "all",
21
+ # attribute_names: "all"
22
+ # }
23
+ def retrieve(retrieval_request)
24
+ json = nil
25
+ @etna_client.post('/retrieve', retrieval_request) do |res|
26
+ json = JSON.parse(res.body)
27
+ end
28
+
29
+ RetrievalResponse.new(json)
30
+ end
31
+
32
+ # This 'query' end point is used to fetch data by graph query
33
+ # See question.rb for more detail
34
+ def query(query_request)
35
+ json = nil
36
+ @etna_client.post('/query', query_request) do |res|
37
+ json = JSON.parse(res.body)
38
+ end
39
+
40
+ QueryResponse.new(json)
41
+ end
42
+
43
+ # Post revisions to Magma records
44
+ # { model_name: { record_name: { attribute1: 1, attribute2: 2 } } } }
45
+ # data can also be a File or IO stream
46
+ def update(update_request)
47
+ json = nil
48
+ @etna_client.multipart_post('/update', update_request.encode_multipart_content) do |res|
49
+ json = JSON.parse(res.body)
50
+ end
51
+
52
+ UpdateResponse.new(json)
53
+ end
54
+
55
+ def update_model(update_model_request)
56
+ json = nil
57
+ @etna_client.post('/update_model', update_model_request) do |res|
58
+ json = JSON.parse(res.body)
59
+ end
60
+
61
+ UpdateModelResponse.new(json)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,265 @@
1
+ require_relative '../../json_serializable_struct'
2
+ require_relative '../../multipart_serializable_nested_hash'
3
+ require_relative '../../directed_graph'
4
+
5
+ # TODO: In the near future, I'd like to transition to specifying apis via SWAGGER and generating model stubs from the
6
+ # common definitions. For nowe I've written them out by hand here.
7
+ module Etna
8
+ module Clients
9
+ class Magma
10
+ class RetrievalRequest < Struct.new(:model_name, :attribute_names, :record_names, :project_name, keyword_init: true)
11
+ include JsonSerializableStruct
12
+
13
+ def initialize(**params)
14
+ super({model_name: 'all', attribute_names: 'all', record_names: []}.update(params))
15
+ end
16
+ end
17
+
18
+ class QueryRequest < Struct.new(:query, :project_name, keyword_init: true)
19
+ include JsonSerializableStruct
20
+ end
21
+
22
+ class UpdateRequest < Struct.new(:revisions, :project_name, keyword_init: true)
23
+ include JsonSerializableStruct
24
+ include MultipartSerializableNestedHash
25
+
26
+ def initialize(**params)
27
+ super({revisions: {}}.update(params))
28
+ end
29
+
30
+ def update_revision(model_name, record_name, **attrs)
31
+ revision = revisions[model_name] ||= {}
32
+ record = revision[record_name] ||= {}
33
+ record.update(attrs)
34
+ end
35
+ end
36
+
37
+ class UpdateModelRequest < Struct.new(:project_name, :actions, keyword_init: true)
38
+ include JsonSerializableStruct
39
+
40
+ def initialize(**params)
41
+ super({actions: []}.update(params))
42
+ end
43
+
44
+ def add_action(action)
45
+ actions << action
46
+ end
47
+ end
48
+
49
+ class AddAttributeAction < Struct.new(:action_name, :model_name, :attribute_name, :type, :description, :display_name, :format_hint, :hidden, :index, :link_model_name, :read_only, :restricted, :unique, :validation, keyword_init: true)
50
+ include JsonSerializableStruct
51
+ def initialize(**args)
52
+ super({action_name: 'add_attribute'}.update(args))
53
+ end
54
+ end
55
+
56
+ class AttributeValidation < Struct.new(:type, :value, :begin, :end, keyword_init: true)
57
+ include JsonSerializableStruct
58
+ end
59
+
60
+ class AttributeValidationType < String
61
+ REGEXP = AttributeValidationType.new("Regexp")
62
+ ARRAY = AttributeValidationType.new("Array")
63
+ RANGE = AttributeValidationType.new("Range")
64
+ end
65
+
66
+ class RetrievalResponse
67
+ attr_reader :raw
68
+
69
+ def initialize(raw = {})
70
+ @raw = raw
71
+ end
72
+
73
+ def models
74
+ Models.new(raw['models'])
75
+ end
76
+ end
77
+
78
+ class UpdateModelResponse < RetrievalResponse
79
+ end
80
+
81
+ class QueryResponse
82
+ attr_reader :raw
83
+
84
+ def initialize(raw = {})
85
+ @raw = raw
86
+ end
87
+
88
+ def answer
89
+ raw['answer']
90
+ end
91
+
92
+ def format
93
+ raw['format']
94
+ end
95
+
96
+ def type
97
+ raw['type']
98
+ end
99
+ end
100
+
101
+ class UpdateResponse < RetrievalResponse
102
+ end
103
+
104
+ class Models
105
+ attr_reader :raw
106
+
107
+ def initialize(raw = {})
108
+ @raw = raw
109
+ end
110
+
111
+ def model_keys
112
+ raw.keys
113
+ end
114
+
115
+ def model(model_key)
116
+ Model.new(raw[model_key])
117
+ end
118
+
119
+ def to_directed_graph(include_casual_links=false)
120
+ graph = ::DirectedGraph.new
121
+
122
+ model_keys.each do |model_name|
123
+ graph.add_connection(model(model_name).template.parent, model_name)
124
+
125
+ if include_casual_links
126
+ attributes = model(model_name).template.attributes
127
+ attributes.attribute_keys.each do |attribute_name|
128
+ attribute = attributes.attribute(attribute_name)
129
+
130
+ linked_model_name = attribute.link_model_name
131
+ if linked_model_name
132
+ if attribute.attribute_type == AttributeType::PARENT
133
+ graph.add_connection(linked_model_name, model_name)
134
+ elsif attribute.attribute_type == AttributeType::COLLECTION
135
+ graph.add_connection(model_name, linked_model_name)
136
+ elsif attribute.attribute_type == AttributeType::CHILD
137
+ graph.add_connection(model_name, linked_model_name)
138
+ elsif attribute.attribute_type == AttributeType::LINK
139
+ graph.add_connection(model_name, linked_model_name)
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ graph
147
+ end
148
+ end
149
+
150
+ class Model
151
+ attr_reader :raw
152
+
153
+ def initialize(raw = {})
154
+ @raw = raw
155
+ end
156
+
157
+ def documents
158
+ Documents.new(raw['documents'])
159
+ end
160
+
161
+ def template
162
+ Template.new(raw['template'])
163
+ end
164
+ end
165
+
166
+ class Documents
167
+ attr_reader :raw
168
+
169
+ def initialize(raw = {})
170
+ @raw = raw
171
+ end
172
+
173
+ def document_keys
174
+ raw.keys
175
+ end
176
+
177
+ def document(document_key)
178
+ raw[document_key]
179
+ end
180
+ end
181
+
182
+ class Template
183
+ attr_reader :raw
184
+
185
+ def initialize(raw = {})
186
+ @raw = raw
187
+ end
188
+
189
+ def name
190
+ raw['name'] || ""
191
+ end
192
+
193
+ def identifier
194
+ raw['identifier'] || ""
195
+ end
196
+
197
+ def parent
198
+ raw['parent']
199
+ end
200
+
201
+ def attributes
202
+ Attributes.new(raw['attributes'])
203
+ end
204
+ end
205
+
206
+ class Attributes
207
+ attr_reader :raw
208
+
209
+ def initialize(raw = {})
210
+ @raw = raw
211
+ end
212
+
213
+ def attribute_keys
214
+ raw.keys
215
+ end
216
+
217
+ def attribute(attribute_key)
218
+ Attribute.new(raw[attribute_key])
219
+ end
220
+ end
221
+
222
+ class Attribute
223
+ attr_reader :raw
224
+
225
+ def initialize(raw = {})
226
+ @raw = raw
227
+ end
228
+
229
+ def name
230
+ @raw['name'] || ""
231
+ end
232
+
233
+ def attribute_name
234
+ @raw['attribute_name'] || ""
235
+ end
236
+
237
+ def attribute_type
238
+ @raw['attribute_type'] && AttributeType.new(@raw['attribute_type'])
239
+ end
240
+
241
+ def link_model_name
242
+ raw['link_model_name']
243
+ end
244
+ end
245
+
246
+ class AttributeType < String
247
+ STRING = AttributeType.new("string")
248
+ DATE_TIME = AttributeType.new("date_time")
249
+ BOOLEAN = AttributeType.new("boolean")
250
+ CHILD = AttributeType.new("child")
251
+ COLLECTION = AttributeType.new("collection")
252
+ FILE = AttributeType.new("file")
253
+ FLOAT = AttributeType.new("float")
254
+ IDENTIFIER = AttributeType.new("identifier")
255
+ IMAGE = AttributeType.new("image")
256
+ INTEGER = AttributeType.new("integer")
257
+ LINK = AttributeType.new("link")
258
+ MATCH = AttributeType.new("match")
259
+ MATRIX = AttributeType.new("matrix")
260
+ PARENT = AttributeType.new("parent")
261
+ TABLE = AttributeType.new("table")
262
+ end
263
+ end
264
+ end
265
+ end
@@ -0,0 +1,2 @@
1
+ require_relative './metis/client'
2
+ require_relative './metis/models'
@@ -0,0 +1,37 @@
1
+ require 'net/http/persistent'
2
+ require 'net/http/post/multipart'
3
+ require 'singleton'
4
+ require_relative '../../client'
5
+ require_relative './models'
6
+ require 'pry'
7
+ module Etna
8
+ module Clients
9
+ class Metis
10
+ def initialize(host:, token:)
11
+ raise 'Metis client configuration is missing host.' unless host
12
+ raise 'Metis client configuration is missing token.' unless token
13
+ @etna_client = ::Etna::Client.new(host, token)
14
+ end
15
+
16
+ def list_all_folders(list_all_folders_request)
17
+ FoldersResponse.new(
18
+ @etna_client.folder_list_all_folders(list_all_folders_request.to_h))
19
+ end
20
+
21
+ def list_folder(list_folder_request)
22
+ FoldersAndFilesResponse.new(
23
+ @etna_client.folder_list(list_folder_request.to_h))
24
+ end
25
+
26
+ def rename_folder(rename_folder_request)
27
+ FoldersResponse.new(
28
+ @etna_client.folder_rename(rename_folder_request.to_h))
29
+ end
30
+
31
+ def create_folder(create_folder_request)
32
+ FoldersResponse.new(
33
+ @etna_client.folder_create(create_folder_request.to_h))
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,142 @@
1
+ require_relative '../../json_serializable_struct'
2
+
3
+
4
+ module Etna
5
+ module Clients
6
+ class Metis
7
+ class ListFoldersRequest < Struct.new(:project_name, :bucket_name, :offset, :limit, keyword_init: true)
8
+ include JsonSerializableStruct
9
+
10
+ def initialize(**params)
11
+ super({}.update(params))
12
+ end
13
+
14
+ def to_h
15
+ # The :project_name comes in from Polyphemus as a symbol value,
16
+ # we need to make sure it's a string because it's going
17
+ # in the URL.
18
+ super().compact.transform_values(&:to_s)
19
+ end
20
+ end
21
+
22
+ class RenameFolderRequest < Struct.new(:project_name, :bucket_name, :folder_path, :new_bucket_name, :new_folder_path, keyword_init: true)
23
+ include JsonSerializableStruct
24
+
25
+ def initialize(**params)
26
+ super({}.update(params))
27
+ end
28
+
29
+ def to_h
30
+ # The :project_name comes in from Polyphemus as a symbol value,
31
+ # we need to make sure it's a string because it's going
32
+ # in the URL.
33
+ super().compact.transform_values(&:to_s)
34
+ end
35
+ end
36
+
37
+ class ListFolderRequest < Struct.new(:project_name, :bucket_name, :folder_path, keyword_init: true)
38
+ include JsonSerializableStruct
39
+
40
+ def initialize(**params)
41
+ super({}.update(params))
42
+ end
43
+
44
+ def to_h
45
+ # The :project_name comes in from Polyphemus as a symbol value,
46
+ # we need to make sure it's a string because it's going
47
+ # in the URL.
48
+ super().compact.transform_values(&:to_s)
49
+ end
50
+ end
51
+
52
+ class CreateFolderRequest < Struct.new(:project_name, :bucket_name, :folder_path, keyword_init: true)
53
+ include JsonSerializableStruct
54
+
55
+ def initialize(**params)
56
+ super({}.update(params))
57
+ end
58
+
59
+ def to_h
60
+ # The :project_name comes in from Polyphemus as a symbol value,
61
+ # we need to make sure it's a string because it's going
62
+ # in the URL.
63
+ super().compact.transform_values(&:to_s)
64
+ end
65
+ end
66
+
67
+ class FoldersResponse
68
+ attr_reader :raw
69
+
70
+ def initialize(raw = {})
71
+ @raw = raw
72
+ end
73
+
74
+ def folders
75
+ Folders.new(raw[:folders])
76
+ end
77
+ end
78
+
79
+ class FoldersAndFilesResponse < FoldersResponse
80
+ def files
81
+ Files.new(raw[:files])
82
+ end
83
+ end
84
+
85
+ class Files
86
+ attr_reader :raw
87
+
88
+ def initialize(raw = {})
89
+ @raw = raw
90
+ end
91
+
92
+ def all
93
+ raw.map { |file| File.new(file) }
94
+ end
95
+ end
96
+
97
+ class Folders
98
+ attr_reader :raw
99
+
100
+ def initialize(raw = {})
101
+ @raw = raw
102
+ end
103
+
104
+ def all
105
+ raw.map { |folder| Folder.new(folder) }
106
+ end
107
+ end
108
+
109
+ class File
110
+ attr_reader :raw
111
+
112
+ def initialize(raw = {})
113
+ @raw = raw
114
+ end
115
+
116
+ def file_path
117
+ raw[:file_path]
118
+ end
119
+
120
+ def file_name
121
+ raw[:file_name]
122
+ end
123
+ end
124
+
125
+ class Folder
126
+ attr_reader :raw
127
+
128
+ def initialize(raw = {})
129
+ @raw = raw
130
+ end
131
+
132
+ def folder_path
133
+ raw[:folder_path]
134
+ end
135
+
136
+ def bucket_name
137
+ raw[:bucket_name]
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -1,3 +1,5 @@
1
+ require 'rollbar'
2
+
1
3
  module Etna
2
4
  class Command
3
5
  class << self
@@ -15,12 +17,18 @@ module Etna
15
17
  # To be overridden during inheritance.
16
18
  def execute
17
19
  raise 'Command is not implemented'
20
+ rescue => e
21
+ Rollbar.error(e)
22
+ raise
18
23
  end
19
24
 
20
25
  # To be overridden during inheritance, to e.g. connect to a database.
21
26
  # Should be called with super by inheriting method.
22
27
  def setup(config)
23
28
  Etna::Application.find(self.class).configure(config)
29
+ rescue => e
30
+ Rollbar.error(e)
31
+ raise
24
32
  end
25
33
  end
26
34
  end
@@ -0,0 +1,56 @@
1
+ class DirectedGraph
2
+ def initialize
3
+ @children = {}
4
+ @parents = {}
5
+ end
6
+
7
+ attr_reader :children
8
+ attr_reader :parents
9
+
10
+ def add_connection(parent, child)
11
+ children = @children[parent] ||= {}
12
+ child_children = @children[child] ||= {}
13
+
14
+ children[child] = child_children
15
+
16
+ parents = @parents[child] ||= {}
17
+ parent_parents = @parents[parent] ||= {}
18
+ parents[parent] = parent_parents
19
+ end
20
+
21
+ def descendants(parent)
22
+ seen = Set.new
23
+
24
+ seen.add(parent)
25
+ queue = @children[parent].keys.dup
26
+ parent_queue = @parents[parent].keys.dup
27
+
28
+ # Because this is not an acyclical graph, the definition of descendants needs to be stronger;
29
+ # here we believe that any path that would move through --any-- parent to this child would not be considered
30
+ # descendant, so we first find all those parents and mark them as 'seen' so that they are not traveled.
31
+ while next_parent = parent_queue.pop
32
+ next if seen.include?(next_parent)
33
+ seen.add(next_parent)
34
+ parent_queue.push(*@parents[next_parent].keys)
35
+ end
36
+
37
+ queue = queue.nil? ? [] : queue.dup
38
+ paths = {}
39
+
40
+ while child = queue.pop
41
+ next if seen.include? child
42
+ seen.add(child)
43
+ path = (paths[child] ||= [parent])
44
+
45
+ @children[child].keys.each do |child_child|
46
+ queue.push child_child
47
+
48
+ unless paths.include? child_child
49
+ paths[child_child] = path + [child]
50
+ end
51
+ end
52
+ end
53
+
54
+ paths
55
+ end
56
+ end
@@ -0,0 +1,34 @@
1
+ module Etna
2
+ module JsonSerializableStruct
3
+ def self.included(cls)
4
+ cls.instance_eval do
5
+ def self.as_json(v)
6
+ if v.respond_to? :as_json
7
+ return v.as_json
8
+ end
9
+
10
+ if v.is_a? Hash
11
+ return v.map { |k, v| [k, as_json(v)] }.to_h
12
+ end
13
+
14
+ if v.class.include? Enumerable
15
+ return v.map { |v| as_json(v) }
16
+ end
17
+
18
+ v
19
+ end
20
+ end
21
+ end
22
+
23
+ def as_json
24
+ members.map do |k|
25
+ v = self.class.as_json(send(k))
26
+ [k, v]
27
+ end.to_h.delete_if { |k, v| v.nil? }
28
+ end
29
+
30
+ def to_json
31
+ as_json.to_json
32
+ end
33
+ end
34
+ end
@@ -1,10 +1,10 @@
1
1
  require 'logger'
2
+ require 'rollbar'
2
3
 
3
4
  module Etna
4
5
  class Logger < ::Logger
5
6
  def initialize(log_dev, age, size)
6
7
  super
7
-
8
8
  self.formatter = proc do |severity, datetime, progname, msg|
9
9
  format(severity, datetime, progname, msg)
10
10
  end
@@ -14,11 +14,28 @@ module Etna
14
14
  "#{severity}:#{datetime.iso8601} #{msg}\n"
15
15
  end
16
16
 
17
+ def warn(msg, &block)
18
+ super
19
+ Rollbar.warn(msg)
20
+ end
21
+
22
+ def error(msg, &block)
23
+ super
24
+ Rollbar.error(msg)
25
+ end
26
+
27
+ def fatal(msg, &block)
28
+ super
29
+ Rollbar.error(msg)
30
+ end
31
+
17
32
  def log_error(e)
18
33
  error(e.message)
19
34
  e.backtrace.each do |trace|
20
35
  error(trace)
21
36
  end
37
+
38
+ Rollbar.error(e)
22
39
  end
23
40
 
24
41
  def log_request(request)
@@ -0,0 +1,45 @@
1
+ module Etna
2
+ module MultipartSerializableNestedHash
3
+ def self.included(cls)
4
+ cls.instance_eval do
5
+ def self.encode_multipart_pairs(value, base_key, is_root, &block)
6
+ if value.is_a? Hash
7
+ value.each do |k, v|
8
+ encode_multipart_pairs(v, is_root ? k : "#{base_key}[#{k}]", false, &block)
9
+ end
10
+ elsif value.is_a? Array
11
+ value.each_with_index do |v, i|
12
+ # This is necessary to ensure that arrays of hashes that have hetergenous keys still get parsed correctly
13
+ # Since the only way to indicate a new entry in the array of hashes is by re-using a key that existed in
14
+ # the previous hash.
15
+ if v.is_a? Hash
16
+ encode_multipart_pairs(i, "#{base_key}[][_idx]", false, &block)
17
+ end
18
+
19
+ encode_multipart_pairs(v, "#{base_key}[]", false, &block)
20
+ end
21
+ else
22
+ raise "base_key cannot be empty for a scalar value!" if base_key.length == 0
23
+ yield [base_key, value.respond_to?(:read) ? UploadIO.new(value, 'application/octet-stream') : value.to_s]
24
+ end
25
+ end
26
+
27
+
28
+ def self.encode_multipart_content(value, base_key = '', is_root = true)
29
+ result = []
30
+ self.encode_multipart_pairs(value, base_key, is_root) { |pair| result << pair }
31
+ result
32
+ end
33
+ end
34
+ end
35
+
36
+ def encode_multipart_content(base_key = '')
37
+ value = self
38
+ if value.respond_to? :as_json
39
+ value = value.as_json
40
+ end
41
+
42
+ self.class.encode_multipart_content(value, base_key)
43
+ end
44
+ end
45
+ end
@@ -66,6 +66,9 @@ module Etna
66
66
  end
67
67
 
68
68
  [404, {}, ["There is no such path '#{request.path}'"]]
69
+ rescue => e
70
+ application.logger.log_error(e)
71
+ raise
69
72
  end
70
73
 
71
74
  def initialize
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: etna
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.15
4
+ version: 0.1.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Saurabh Asthana
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-18 00:00:00.000000000 Z
11
+ date: 2020-08-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rollbar
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  description: See summary
70
84
  email: Saurabh.Asthana@ucsf.edu
71
85
  executables: []
@@ -76,14 +90,23 @@ files:
76
90
  - lib/etna/application.rb
77
91
  - lib/etna/auth.rb
78
92
  - lib/etna/client.rb
93
+ - lib/etna/clients/magma.rb
94
+ - lib/etna/clients/magma/client.rb
95
+ - lib/etna/clients/magma/models.rb
96
+ - lib/etna/clients/metis.rb
97
+ - lib/etna/clients/metis/client.rb
98
+ - lib/etna/clients/metis/models.rb
79
99
  - lib/etna/command.rb
80
100
  - lib/etna/controller.rb
81
101
  - lib/etna/cross_origin.rb
82
102
  - lib/etna/describe_routes.rb
103
+ - lib/etna/directed_graph.rb
83
104
  - lib/etna/errors.rb
84
105
  - lib/etna/ext.rb
85
106
  - lib/etna/hmac.rb
107
+ - lib/etna/json_serializable_struct.rb
86
108
  - lib/etna/logger.rb
109
+ - lib/etna/multipart_serializable_nested_hash.rb
87
110
  - lib/etna/parse_body.rb
88
111
  - lib/etna/route.rb
89
112
  - lib/etna/server.rb