gooddata 0.6.19 → 0.6.20

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -0
  3. data/Rakefile +17 -3
  4. data/gooddata.gemspec +8 -7
  5. data/lib/gooddata/bricks/middleware/base_middleware.rb +1 -1
  6. data/lib/gooddata/cli/commands/run_ruby_cmd.rb +2 -2
  7. data/lib/gooddata/cli/shared.rb +2 -1
  8. data/lib/gooddata/commands/auth.rb +58 -5
  9. data/lib/gooddata/commands/runners.rb +2 -6
  10. data/lib/gooddata/extensions/big_decimal.rb +4 -0
  11. data/lib/gooddata/extensions/false.rb +11 -0
  12. data/lib/gooddata/extensions/hash.rb +6 -17
  13. data/lib/gooddata/extensions/nil.rb +11 -0
  14. data/lib/gooddata/extensions/numeric.rb +11 -0
  15. data/lib/gooddata/extensions/object.rb +11 -0
  16. data/lib/gooddata/extensions/symbol.rb +11 -0
  17. data/lib/gooddata/extensions/true.rb +11 -0
  18. data/lib/gooddata/helpers/auth_helpers.rb +32 -2
  19. data/lib/gooddata/helpers/data_helper.rb +1 -1
  20. data/lib/gooddata/helpers/global_helpers.rb +98 -31
  21. data/lib/gooddata/mixins/md_finders.rb +15 -15
  22. data/lib/gooddata/mixins/md_object_query.rb +12 -2
  23. data/lib/gooddata/models/blueprint/blueprint_field.rb +2 -2
  24. data/lib/gooddata/models/blueprint/dataset_blueprint.rb +2 -2
  25. data/lib/gooddata/models/blueprint/project_blueprint.rb +3 -3
  26. data/lib/gooddata/models/blueprint/schema_blueprint.rb +1 -1
  27. data/lib/gooddata/models/datawarehouse.rb +1 -0
  28. data/lib/gooddata/models/domain.rb +13 -16
  29. data/lib/gooddata/models/from_wire.rb +0 -2
  30. data/lib/gooddata/models/membership.rb +1 -1
  31. data/lib/gooddata/models/metadata/attribute.rb +1 -1
  32. data/lib/gooddata/models/metadata/dashboard.rb +1 -1
  33. data/lib/gooddata/models/metadata/dataset.rb +1 -1
  34. data/lib/gooddata/models/metadata/dimension.rb +1 -1
  35. data/lib/gooddata/models/metadata/fact.rb +1 -1
  36. data/lib/gooddata/models/metadata/label.rb +16 -17
  37. data/lib/gooddata/models/metadata/metric.rb +1 -1
  38. data/lib/gooddata/models/metadata/report.rb +1 -1
  39. data/lib/gooddata/models/metadata/report_definition.rb +7 -7
  40. data/lib/gooddata/models/metadata/variable.rb +1 -1
  41. data/lib/gooddata/models/model.rb +2 -2
  42. data/lib/gooddata/models/profile.rb +2 -2
  43. data/lib/gooddata/models/project.rb +21 -23
  44. data/lib/gooddata/models/project_role.rb +3 -3
  45. data/lib/gooddata/models/schedule.rb +18 -4
  46. data/lib/gooddata/models/user_filters/mandatory_user_filter.rb +12 -15
  47. data/lib/gooddata/models/user_filters/user_filter.rb +8 -8
  48. data/lib/gooddata/models/user_filters/user_filter_builder.rb +16 -13
  49. data/lib/gooddata/models/user_filters/variable_user_filter.rb +1 -1
  50. data/lib/gooddata/rest/client.rb +4 -2
  51. data/lib/gooddata/rest/connection.rb +37 -30
  52. data/lib/gooddata/rest/connections/rest_client_connection.rb +1 -1
  53. data/lib/gooddata/version.rb +1 -1
  54. data/spec/environment/develop.rb +4 -4
  55. data/spec/environment/hotfix.rb +1 -1
  56. data/spec/environment/release.rb +1 -1
  57. data/spec/integration/full_project_spec.rb +3 -3
  58. data/spec/integration/over_to_user_filters_spec.rb +1 -0
  59. data/spec/integration/project_spec.rb +1 -1
  60. data/spec/integration/user_filters_spec.rb +0 -1
  61. data/spec/unit/commands/command_auth_spec.rb +10 -0
  62. data/spec/unit/extensions/hash_spec.rb +1 -1
  63. data/spec/unit/helpers_spec.rb +0 -8
  64. data/spec/unit/models/domain_spec.rb +1 -9
  65. data/spec/unit/models/from_wire_spec.rb +1 -19
  66. data/spec/unit/models/membership_spec.rb +1 -1
  67. data/spec/unit/models/metadata_spec.rb +1 -1
  68. data/spec/unit/models/profile_spec.rb +23 -47
  69. data/spec/unit/models/schedule_spec.rb +47 -3
  70. metadata +174 -50
  71. data/lib/gooddata/models/from_wire_parse.rb +0 -125
@@ -11,7 +11,7 @@ module GoodData
11
11
 
12
12
  def initialize(opts = {})
13
13
  opts = opts.is_a?(String) ? { type: :staging, path: opts } : opts
14
- opts = opts.symbolize_keys
14
+ opts = GoodData::Helpers.symbolize_keys(opts)
15
15
  @source = opts[:type]
16
16
  @options = opts
17
17
  @realized = false
@@ -1,12 +1,16 @@
1
1
  # encoding: UTF-8
2
2
 
3
- require 'active_support/all'
4
3
  require 'pathname'
4
+ require 'hashie'
5
5
 
6
6
  require_relative 'global_helpers_params'
7
7
 
8
8
  module GoodData
9
9
  module Helpers
10
+ class DeepMergeableHash < Hash
11
+ include Hashie::Extensions::DeepMerge
12
+ end
13
+
10
14
  class << self
11
15
  def error(msg)
12
16
  STDERR.puts(msg)
@@ -51,9 +55,11 @@ module GoodData
51
55
  end
52
56
  end
53
57
 
54
- # TODO: Implement without using ActiveSupport
55
- def humanize(str)
56
- ActiveSupport::Inflector.humanize(str)
58
+ def titleize(str)
59
+ titleized = str.gsub(/[\.|_](.)/) { |x| x.upcase }
60
+ titleized = titleized.gsub('_', ' ')
61
+ titleized[0] = titleized[0].upcase
62
+ titleized
57
63
  end
58
64
 
59
65
  def join(master, slave, on, on2, options = {})
@@ -91,41 +97,102 @@ module GoodData
91
97
  RUBY_PLATFORM =~ /-darwin\d/
92
98
  end
93
99
 
94
- # TODO: Implement without using ActiveSupport
95
- def sanitize_string(str, filter = /[^a-z_]/, replacement = '')
96
- str = ActiveSupport::Inflector.transliterate(str).downcase
97
- str.gsub(filter, replacement)
98
- end
99
-
100
100
  def underline(x)
101
101
  '=' * x.size
102
102
  end
103
103
 
104
- # Recurscively changes the string keys of a hash to symbols.
105
- #
106
- # @param h [Hash] Data structure to change
107
- # @return [Hash] Hash with symbolized keys
108
- def symbolize_keys_deep!(h)
109
- if Hash == h
110
- Hash[
111
- h.map do |k, v|
112
- [k.respond_to?(:to_sym) ? k.to_sym : k, symbolize_keys_deep!(v)]
113
- end
114
- ]
115
- else
116
- h
104
+ def transform_keys!(an_object)
105
+ return enum_for(:transform_keys!) unless block_given?
106
+ an_object.keys.each do |key|
107
+ an_object[yield(key)] = delete(key)
117
108
  end
109
+ an_object
118
110
  end
119
111
 
120
- def stringify_keys_deep!(h)
121
- if Hash == h.class
122
- Hash[
123
- h.map do |k, v|
124
- [k.respond_to?(:to_s) ? k.to_s : k, stringify_keys_deep!(v)]
125
- end
126
- ]
112
+ def symbolize_keys!(an_object)
113
+ transform_keys!(an_object) do |key|
114
+ begin
115
+ key.to_sym
116
+ rescue
117
+ key
118
+ end
119
+ end
120
+ end
121
+
122
+ def symbolize_keys(an_object)
123
+ transform_keys(an_object) do |key|
124
+ begin
125
+ key.to_sym
126
+ rescue
127
+ key
128
+ end
129
+ end
130
+ end
131
+
132
+ def transform_keys(an_object)
133
+ return enum_for(:transform_keys) unless block_given?
134
+ result = an_object.class.new
135
+ an_object.each_key do |key|
136
+ result[yield(key)] = an_object[key]
137
+ end
138
+ result
139
+ end
140
+
141
+ def deep_symbolize_keys(an_object)
142
+ deep_transform_keys(an_object) do |key|
143
+ begin
144
+ key.to_sym
145
+ rescue
146
+ key
147
+ end
148
+ end
149
+ end
150
+
151
+ def stringify_keys(an_object)
152
+ transform_keys(an_object) { |key| key.to_s }
153
+ end
154
+
155
+ def deep_stringify_keys(an_object)
156
+ deep_transform_keys(an_object) { |key| key.to_s }
157
+ end
158
+
159
+ def deep_transform_keys(an_object, &block)
160
+ _deep_transform_keys_in_object(an_object, &block)
161
+ end
162
+
163
+ def _deep_transform_keys_in_object(object, &block)
164
+ case object
165
+ when Hash
166
+ object.each_with_object({}) do |(key, value), result|
167
+ result[yield(key)] = _deep_transform_keys_in_object(value, &block)
168
+ end
169
+ when Array
170
+ object.map { |e| _deep_transform_keys_in_object(e, &block) }
127
171
  else
128
- h
172
+ object
173
+ end
174
+ end
175
+
176
+ def deep_dup(an_object)
177
+ case an_object
178
+ when Array
179
+ an_object.map { |it| GoodData::Helpers.deep_dup(it) }
180
+ when Hash
181
+ an_object.each_with_object(an_object.dup) do |(key, value), hash|
182
+ hash[GoodData::Helpers.deep_dup(key)] = GoodData::Helpers.deep_dup(value)
183
+ end
184
+ when Object
185
+ an_object.duplicable? ? an_object.dup : an_object
186
+ end
187
+ end
188
+
189
+ def undot(params)
190
+ # for each key-value config given
191
+ params.map do |k, v|
192
+ # dot notation to hash
193
+ k.split('__').reverse.reduce(v) do |memo, obj|
194
+ GoodData::Helper.DeepMergeableHash[{ obj => memo }]
195
+ end
129
196
  end
130
197
  end
131
198
  end
@@ -11,11 +11,11 @@ module GoodData
11
11
  def find_first_by_identifier(identifier, options = { :client => GoodData.connection, :project => GoodData.project })
12
12
  all = self[:all, options.merge(full: false)]
13
13
  item = if identifier.is_a?(Regexp)
14
- all.find { |r| r['identifier'] =~ identifier }
14
+ all.find { |r| r.identifier =~ identifier }
15
15
  else
16
- all.find { |r| r['identifier'] == identifier }
16
+ all.find { |r| r.identifier == identifier }
17
17
  end
18
- self[item['link'], options] unless item.nil?
18
+ self[item.uri, options] unless item.nil?
19
19
  end
20
20
 
21
21
  # Finds a specific type of the object by identifier. Returns all matches. Returns full object.
@@ -24,13 +24,13 @@ module GoodData
24
24
  # @param title [Regexp] regular expression that has to match
25
25
  # @return [Array<GoodData::MdObject>] Array of MdObject
26
26
  def find_by_identifier(identifier, options = { :client => GoodData.connection, :project => GoodData.project })
27
- all = self[:all, options.merge(full: false)]
27
+ all = self[:all, options]
28
28
  items = if identifier.is_a?(Regexp)
29
- all.select { |r| r['title'] =~ identifier }
29
+ all.select { |r| r.title =~ identifier }
30
30
  else
31
- all.select { |r| r['title'] == identifier }
31
+ all.select { |r| r.title == identifier }
32
32
  end
33
- items.pmap { |item| self[item['link'], options] unless item.nil? }
33
+ items.pmap { |item| self[item.uri, options] unless item.nil? }
34
34
  end
35
35
 
36
36
  def find_by_tag(tag, opts = { :client => GoodData.connection, :project => GoodData.project })
@@ -51,13 +51,13 @@ module GoodData
51
51
  # @param title [Regexp] regular expression that has to match
52
52
  # @return [Array<GoodData::MdObject>] Array of MdObject
53
53
  def find_first_by_title(title, options = { :client => GoodData.connection, :project => GoodData.project })
54
- all = self[:all, options.merge(full: false)]
54
+ all = self[:all, options]
55
55
  item = if title.is_a?(Regexp)
56
- all.find { |r| r['title'] =~ title }
56
+ all.find { |r| r.title =~ title }
57
57
  else
58
- all.find { |r| r['title'] == title }
58
+ all.find { |r| r.title == title }
59
59
  end
60
- self[item['link'], options] unless item.nil?
60
+ self[item.uri, options] unless item.nil?
61
61
  end
62
62
 
63
63
  # Finds a specific type of the object by title. Returns all matches. Returns full object.
@@ -66,13 +66,13 @@ module GoodData
66
66
  # @param title [Regexp] regular expression that has to match
67
67
  # @return [Array<GoodData::MdObject>] Array of MdObject
68
68
  def find_by_title(title, options = { :client => GoodData.connection, :project => GoodData.project })
69
- all = self[:all, options.merge(full: false)]
69
+ all = self[:all, options]
70
70
  items = if title.is_a?(Regexp)
71
- all.select { |r| r['title'] =~ title }
71
+ all.select { |r| r.title =~ title }
72
72
  else
73
- all.select { |r| r['title'] == title }
73
+ all.select { |r| r.title == title }
74
74
  end
75
- items.pmap { |item| self[item['link'], options] unless item.nil? }
75
+ items.pmap { |item| self[item.uri, options] unless item.nil? }
76
76
  end
77
77
  end
78
78
  end
@@ -33,8 +33,18 @@ module GoodData
33
33
  project = GoodData::Project[p, options]
34
34
  fail ArgumentError, 'Wrong :project specified' if project.nil?
35
35
 
36
- query_result = client.get(project.md['query'] + "/#{query_obj_type}/")['query']['entries']
37
- options[:full] == false ? query_result : query_result.pmap { |item| klass[item['link'], options] }
36
+ offset = 0
37
+ page_limit = 50
38
+ Enumerator.new do |y|
39
+ loop do
40
+ result = client.get(project.md['objects'] + '/query', params: { category: query_obj_type, limit: page_limit, offset: offset })
41
+ result['objects']['items'].each do |item|
42
+ y << (klass ? client.create(klass, item, project: project) : item)
43
+ end
44
+ break if result['objects']['paging']['count'] < page_limit
45
+ offset += page_limit
46
+ end
47
+ end
38
48
  end
39
49
 
40
50
  def dependency(uri, key = nil, opts = { :client => GoodData.connection })
@@ -10,7 +10,7 @@ module GoodData
10
10
  end
11
11
 
12
12
  def initialize(data, dataset)
13
- @data = data.symbolize_keys
13
+ @data = GoodData::Helpers.symbolize_keys(data)
14
14
  @data[:type] = @data[:type].to_sym
15
15
  @dataset_blueprint = dataset
16
16
  end
@@ -39,7 +39,7 @@ module GoodData
39
39
  end
40
40
 
41
41
  def title
42
- @data[:title] || @data[:id].titleize
42
+ @data[:title] || GoodData::Helpers.titleize(@data[:id])
43
43
  end
44
44
 
45
45
  # Validates the fields in the field
@@ -243,7 +243,7 @@ module GoodData
243
243
  #
244
244
  # @return [GoodData::Model::DatasetBlueprint] matching fields
245
245
  def dup
246
- DatasetBlueprint.new(data.deep_dup, project_blueprint)
246
+ DatasetBlueprint.new(GoodData::Helpers.deep_dup(data), project_blueprint)
247
247
  end
248
248
 
249
249
  # Returns facts of a dataset
@@ -367,7 +367,7 @@ module GoodData
367
367
  identifiers = facts.map { |f| identifier_for(f) }
368
368
  identifiers.zip(facts).map do |id, fact|
369
369
  Metric.xcreate(
370
- :title => fact[:name].titleize,
370
+ :title => GoodData::Helpers.titleize(fact[:name]),
371
371
  :expression => "SELECT SUM(![#{id}])")
372
372
  end
373
373
  end
@@ -51,7 +51,7 @@ module GoodData
51
51
  def self.remove_dataset(project, dataset_id)
52
52
  dataset = dataset_id.is_a?(String) ? find_dataset(project, dataset_id) : dataset_name
53
53
  index = project[:datasets].index(dataset)
54
- dupped_project = project.deep_dup
54
+ dupped_project = GoodData::Helpers.deep_dup(project)
55
55
  dupped_project[:datasets].delete_at(index)
56
56
  dupped_project
57
57
  end
@@ -338,7 +338,7 @@ module GoodData
338
338
  # @param a_blueprint [GoodData::Model::DatasetBlueprint] Dataset blueprint to be merged
339
339
  # @return [GoodData::Model::DatasetBlueprint]
340
340
  def dup
341
- ProjectBlueprint.new(data.deep_dup)
341
+ ProjectBlueprint.new(GoodData::Helpers.deep_dup(data))
342
342
  end
343
343
 
344
344
  # Returns list of facts from all the datasets in a blueprint
@@ -396,7 +396,7 @@ module GoodData
396
396
  else
397
397
  init_data
398
398
  end
399
- @data = some_data.deep_dup.symbolize_keys
399
+ @data = GoodData::Helpers.symbolize_keys(GoodData::Helpers.deep_dup(some_data))
400
400
  (@data[:datasets] || []).each do |d|
401
401
  d[:type] = d[:type].to_sym
402
402
  d[:columns].each do |c|
@@ -122,7 +122,7 @@ module GoodData
122
122
  #
123
123
  # @return [String]
124
124
  def title
125
- data[:title] || data[:id].titleize
125
+ data[:title] || GoodData::Helpers.titleize(data[:id])
126
126
  end
127
127
 
128
128
  # Validates the blueprint and returns true if model is valid. False otherwise.
@@ -17,6 +17,7 @@ module GoodData
17
17
  c = client(opts)
18
18
  fail ArgumentError, 'No :client specified' if c.nil?
19
19
 
20
+ opts = { :auth_token => Helpers::AuthHelper.read_token }.merge(opts)
20
21
  auth_token = opts[:auth_token] || opts[:token]
21
22
  fail ArgumentError, 'You have to provide your token for creating projects as :auth_token parameter' if auth_token.nil? || auth_token.empty?
22
23
 
@@ -183,26 +183,23 @@ module GoodData
183
183
  # @option opts [Number] :limit From address
184
184
  # TODO: Review opts[:limit] functionality
185
185
  def users(domain, id = :all, opts = {})
186
- c = client(opts)
187
- domain = c.domain(domain)
186
+ client = client(opts)
187
+ domain = client.domain(domain)
188
188
  if id == :all
189
189
  GoodData.logger.warn("Retrieving all users from domain #{domain.name}")
190
- result = []
191
- page_limit = opts[:page_limit] || 1000
192
- limit = opts[:limit] || Float::INFINITY
193
- offset = opts[:offset] || 0
194
- uri = "#{domain.uri}/users?offset=#{offset}&limit=#{page_limit}"
195
- loop do
196
- tmp = client(opts).get(uri)
197
- tmp['accountSettings']['items'].each do |account|
198
- result << client(opts).create(GoodData::Profile, account)
190
+ Enumerator.new do |y|
191
+ page_limit = opts[:page_limit] || 1000
192
+ offset = opts[:offset] || 0
193
+ loop do
194
+ tmp = client(opts).get("#{domain.uri}/users", params: { offset: offset, limit: page_limit })
195
+ tmp['accountSettings']['items'].each do |user_data|
196
+ user = client.create(GoodData::Profile, user_data)
197
+ y << user
198
+ end
199
+ break if tmp['accountSettings']['items'].count < page_limit
200
+ offset += page_limit
199
201
  end
200
- break if result.length >= limit
201
-
202
- uri = tmp['accountSettings']['paging']['next']
203
- break unless uri
204
202
  end
205
- result
206
203
  else
207
204
  find_user_by_login(domain, id)
208
205
  end
@@ -1,7 +1,5 @@
1
1
  # encoding: UTF-8
2
2
 
3
- require_relative 'from_wire_parse'
4
-
5
3
  module GoodData
6
4
  module Model
7
5
  module FromWire
@@ -381,7 +381,7 @@ module GoodData
381
381
  end
382
382
 
383
383
  def to_hash
384
- tmp = content.merge(meta).merge('uri' => uri).symbolize_keys
384
+ tmp = GoodData::Helpers.symbolize_keys(content.merge(meta).merge('uri' => uri))
385
385
  [
386
386
  [:userRoles, :role],
387
387
  [:companyName, :company_name],
@@ -21,7 +21,7 @@ module GoodData
21
21
  # @option options [Boolean] :full if passed true the subclass can decide to pull in full objects. This is desirable from the usability POV but unfortunately has negative impact on performance so it is not the default
22
22
  # @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
23
23
  def all(options = { :client => GoodData.connection, :project => GoodData.project })
24
- query('attributes', Attribute, options)
24
+ query('attribute', Attribute, options)
25
25
  end
26
26
 
27
27
  # Finds the value of an atribute and gives you the textual form for the label that is acquired by calling primary_label method
@@ -20,7 +20,7 @@ module GoodData
20
20
  # @option options [Boolean] :full if passed true the subclass can decide to pull in full objects. This is desirable from the usability POV but unfortunately has negative impact on performance so it is not the default
21
21
  # @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
22
22
  def all(options = { :client => GoodData.connection, :project => GoodData.project })
23
- query('projectdashboards', Dashboard, options)
23
+ query('projectDashboard', Dashboard, options)
24
24
  end
25
25
 
26
26
  def create_report_tab(tab)
@@ -13,7 +13,7 @@ module GoodData
13
13
  # @option options [Boolean] :full if passed true the subclass can decide to pull in full objects. This is desirable from the usability POV but unfortunately has negative impact on performance so it is not the default
14
14
  # @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
15
15
  def all(options = { :client => GoodData.connection, :project => GoodData.project })
16
- query('datasets', Dataset, options)
16
+ query('dataSet', Dataset, options)
17
17
  end
18
18
  end
19
19
 
@@ -17,7 +17,7 @@ module GoodData
17
17
  # @option options [Boolean] :full if passed true the subclass can decide to pull in full objects. This is desirable from the usability POV but unfortunately has negative impact on performance so it is not the default
18
18
  # @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
19
19
  def all(options = { :client => GoodData.connection, :project => GoodData.project })
20
- query('dimensions', Dimension, options)
20
+ query('dimension', Dimension, options)
21
21
  end
22
22
 
23
23
  # Returns a Project object identified by given string
@@ -22,7 +22,7 @@ module GoodData
22
22
  # @option options [Boolean] :full if passed true the subclass can decide to pull in full objects. This is desirable from the usability POV but unfortunately has negative impact on performance so it is not the default
23
23
  # @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
24
24
  def all(options = { :client => GoodData.connection, :project => GoodData.project })
25
- query('facts', Fact, options)
25
+ query('fact', Fact, options)
26
26
  end
27
27
  end
28
28