gooddata 0.6.11 → 0.6.12

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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -0
  3. data/.travis.yml +5 -0
  4. data/CHANGELOG.md +34 -1
  5. data/CLI.md +1 -1
  6. data/authors.sh +4 -0
  7. data/lib/gooddata.rb +1 -1
  8. data/lib/gooddata/cli/commands/api_cmd.rb +0 -2
  9. data/lib/gooddata/cli/commands/auth_cmd.rb +0 -3
  10. data/lib/gooddata/cli/commands/console_cmd.rb +1 -2
  11. data/lib/gooddata/cli/commands/domain_cmd.rb +0 -2
  12. data/lib/gooddata/cli/commands/process_cmd.rb +0 -2
  13. data/lib/gooddata/cli/commands/project_cmd.rb +0 -2
  14. data/lib/gooddata/cli/commands/projects_cmd.rb +0 -2
  15. data/lib/gooddata/cli/commands/run_ruby_cmd.rb +2 -3
  16. data/lib/gooddata/cli/commands/scaffold_cmd.rb +0 -3
  17. data/lib/gooddata/cli/commands/user_cmd.rb +0 -2
  18. data/lib/gooddata/cli/shared.rb +1 -2
  19. data/lib/gooddata/commands/datawarehouse.rb +24 -0
  20. data/lib/gooddata/commands/process.rb +0 -1
  21. data/lib/gooddata/commands/project.rb +1 -1
  22. data/lib/gooddata/commands/scaffold.rb +0 -1
  23. data/lib/gooddata/core/connection.rb +376 -0
  24. data/lib/gooddata/core/logging.rb +13 -0
  25. data/lib/gooddata/core/rest.rb +40 -16
  26. data/lib/gooddata/exceptions/user_in_different_domain.rb +11 -0
  27. data/lib/gooddata/extensions/enumerable.rb +8 -0
  28. data/lib/gooddata/goodzilla/goodzilla.rb +24 -0
  29. data/lib/gooddata/helpers/global_helpers.rb +126 -12
  30. data/lib/gooddata/mixins/author.rb +11 -5
  31. data/lib/gooddata/mixins/is_dimension.rb +13 -0
  32. data/lib/gooddata/mixins/md_object_indexer.rb +17 -1
  33. data/lib/gooddata/mixins/md_object_query.rb +10 -2
  34. data/lib/gooddata/mixins/md_relations.rb +2 -2
  35. data/lib/gooddata/mixins/rest_resource.rb +1 -0
  36. data/lib/gooddata/models/data_result.rb +0 -1
  37. data/lib/gooddata/models/datawarehouse.rb +90 -0
  38. data/lib/gooddata/models/domain.rb +202 -76
  39. data/lib/gooddata/models/execution.rb +11 -0
  40. data/lib/gooddata/models/from_wire.rb +4 -4
  41. data/lib/gooddata/models/invitation.rb +0 -5
  42. data/lib/gooddata/models/membership.rb +121 -91
  43. data/lib/gooddata/models/metadata.rb +1 -2
  44. data/lib/gooddata/models/metadata/attribute.rb +7 -0
  45. data/lib/gooddata/models/metadata/dashboard.rb +1 -1
  46. data/lib/gooddata/models/metadata/dimension.rb +52 -0
  47. data/lib/gooddata/models/metadata/fact.rb +1 -1
  48. data/lib/gooddata/models/metadata/label.rb +21 -7
  49. data/lib/gooddata/models/metadata/metric.rb +1 -23
  50. data/lib/gooddata/models/metadata/report.rb +2 -2
  51. data/lib/gooddata/models/metadata/report_definition.rb +22 -2
  52. data/lib/gooddata/models/metadata/variable.rb +81 -0
  53. data/lib/gooddata/models/model.rb +2 -1
  54. data/lib/gooddata/models/process.rb +3 -4
  55. data/lib/gooddata/models/profile.rb +50 -82
  56. data/lib/gooddata/models/project.rb +170 -213
  57. data/lib/gooddata/models/project_blueprint.rb +14 -5
  58. data/lib/gooddata/models/project_creator.rb +2 -2
  59. data/lib/gooddata/models/schedule.rb +10 -8
  60. data/lib/gooddata/models/to_wire.rb +2 -2
  61. data/lib/gooddata/models/user_filters/mandatory_user_filter.rb +67 -0
  62. data/lib/gooddata/models/user_filters/user_filter.rb +96 -0
  63. data/lib/gooddata/models/user_filters/user_filter_builder.rb +409 -0
  64. data/lib/gooddata/{rest/connections/connections.rb → models/user_filters/user_filters.rb} +1 -0
  65. data/lib/gooddata/models/user_filters/variable_user_filter.rb +14 -0
  66. data/lib/gooddata/rest/client.rb +32 -21
  67. data/lib/gooddata/rest/connection.rb +283 -11
  68. data/lib/gooddata/rest/connections/rest_client_connection.rb +47 -109
  69. data/lib/gooddata/version.rb +1 -1
  70. data/spec/data/column_based_permissions.csv +7 -0
  71. data/spec/data/column_based_permissions2.csv +6 -0
  72. data/spec/data/hello_world_process/hello_world.rb +3 -1
  73. data/spec/data/line_based_permissions.csv +3 -0
  74. data/spec/data/m_n_model/blueprint.json +76 -0
  75. data/spec/data/{model_view.json → wire_models/model_view.json} +0 -0
  76. data/spec/data/wire_models/nu_model.json +3046 -0
  77. data/spec/helpers/process_helper.rb +2 -2
  78. data/spec/helpers/project_helper.rb +29 -0
  79. data/spec/helpers/schedule_helper.rb +1 -1
  80. data/spec/integration/command_datawarehouse_spec.rb +32 -0
  81. data/spec/integration/create_project_spec.rb +0 -1
  82. data/spec/integration/full_process_schedule_spec.rb +13 -5
  83. data/spec/integration/full_project_spec.rb +2 -1
  84. data/spec/integration/over_to_user_filters_spec.rb +92 -0
  85. data/spec/integration/project_spec.rb +233 -0
  86. data/spec/integration/rest_spec.rb +209 -0
  87. data/spec/integration/user_filters_spec.rb +193 -0
  88. data/spec/integration/variables_spec.rb +196 -0
  89. data/spec/unit/commands/command_auth_spec.rb +0 -7
  90. data/spec/unit/commands/command_process_spec.rb +10 -13
  91. data/spec/unit/core/connection_spec.rb +0 -19
  92. data/spec/unit/helpers/global_helpers_spec.rb +57 -0
  93. data/spec/unit/models/domain_spec.rb +80 -40
  94. data/spec/unit/models/from_wire_spec.rb +8 -1
  95. data/spec/unit/models/params_spec.rb +6 -6
  96. data/spec/unit/models/profile_spec.rb +23 -22
  97. data/spec/unit/models/project_blueprint_spec.rb +1 -6
  98. data/spec/unit/models/project_spec.rb +331 -286
  99. data/spec/unit/models/schedule_spec.rb +39 -14
  100. data/spec/unit/models/user_filters_spec.rb +89 -0
  101. data/spec/unit/models/variable_spec.rb +259 -0
  102. metadata +31 -7
  103. data/lib/gooddata/rest/connections/dummy_connection.rb +0 -52
  104. data/spec/unit/core/rest_spec.rb +0 -106
@@ -5,6 +5,7 @@ require_relative 'nil_logger'
5
5
  module GoodData
6
6
  class << self
7
7
  attr_writer :logger
8
+ attr_writer :stats
8
9
 
9
10
  # Turn logging on
10
11
  #
@@ -43,5 +44,17 @@ module GoodData
43
44
  def logger
44
45
  @logger ||= NilLogger.new
45
46
  end
47
+
48
+ def stats_on
49
+ @stats = true
50
+ end
51
+
52
+ def stats_on? # rubocop:disable Style/TrivialAccessors
53
+ @stats
54
+ end
55
+
56
+ def stats_off
57
+ @stats = false
58
+ end
46
59
  end
47
60
  end
@@ -27,7 +27,7 @@ module GoodData
27
27
  #
28
28
  # ### Examples
29
29
  #
30
- # GoodData.post '/gdc/projects', { ... }
30
+ # client.post '/gdc/projects', { ... }
31
31
  #
32
32
  def post(path, data = {}, options = {})
33
33
  connection.post path, data, options
@@ -44,7 +44,7 @@ module GoodData
44
44
  #
45
45
  # ### Examples
46
46
  #
47
- # GoodData.put '/gdc/projects', { ... }
47
+ # client.put '/gdc/projects', { ... }
48
48
  #
49
49
  def put(path, data, options = {})
50
50
  connection.put path, data, options
@@ -64,8 +64,12 @@ module GoodData
64
64
  connection.delete path, options
65
65
  end
66
66
 
67
- def upload_to_user_webdav(file, options = {})
68
- u = URI(GoodData.project.links['uploads'])
67
+ # Upload to user directory
68
+ # @return [String]
69
+ def upload_to_user_webdav(file, options = { :project => GoodData.project })
70
+ options = merge_options(options)
71
+ project = options[:project]
72
+ u = URI(project.links['uploads'])
69
73
  url = URI.join(u.to_s.chomp(u.path.to_s), '/uploads/')
70
74
 
71
75
  connection.upload(file, options.merge(
@@ -73,28 +77,40 @@ module GoodData
73
77
  ))
74
78
  end
75
79
 
76
- def get_project_webdav_path(_file, _options = {})
77
- u = URI(GoodData.project.links['uploads'])
78
- URI.join(u.to_s.chomp(u.path.to_s), '/project-uploads/', "#{GoodData.project.pid}/")
80
+ # Get WebDav directory for project data
81
+ # @return [String]
82
+ def get_project_webdav_path(file, options = { :project => GoodData.project })
83
+ options = merge_options(options)
84
+ project = options[:project]
85
+ project.get_project_webdav_path(file)
79
86
  end
80
87
 
81
- def upload_to_project_webdav(file, options = {})
88
+ # Upload to project directory
89
+ def upload_to_project_webdav(file, options = { :project => GoodData.project })
90
+ options = merge_options(options)
82
91
  webdav_filename = File.basename(file)
83
92
  url = get_project_webdav_path(webdav_filename, options)
84
93
  connection.upload(file, options.merge(:staging_url => url))
85
94
  end
86
95
 
87
- def download_from_project_webdav(file, where, options = {})
96
+ # Download from project directory
97
+ def download_from_project_webdav(file, where, options = { :project => GoodData.project })
98
+ options = merge_options(options)
88
99
  url = get_project_webdav_path(file, options)
89
100
  connection.download(file, where, options.merge(:staging_url => url))
90
101
  end
91
102
 
92
- def get_user_webdav_path(_file, _options = {})
93
- u = URI(GoodData.project.links['uploads'])
94
- URI.join(u.to_s.chomp(u.path.to_s), '/uploads/')
103
+ # Get WebDav directory for user data
104
+ # @return [String]
105
+ def get_user_webdav_path(file, options = { :project => GoodData.project })
106
+ options = merge_options(options)
107
+ project = options[:project]
108
+ project.get_user_webdav_path(file)
95
109
  end
96
110
 
97
- def download_from_user_webdav(file, where, options = {})
111
+ # Download from user directory
112
+ def download_from_user_webdav(file, where, options = { :project => GoodData.project })
113
+ options = merge_options(options)
98
114
  url = get_user_webdav_path(file, options)
99
115
  connection.download(file, where, options.merge(:staging_url => url))
100
116
  end
@@ -114,7 +130,7 @@ module GoodData
114
130
  response = GoodData.get(link, :process => false)
115
131
  while response.code == code
116
132
  sleep sleep_interval
117
- GoodData::Rest::Client.retryable(:tries => 3, :on => RestClient::InternalServerError) do
133
+ GoodData::Rest::Client.retryable(:tries => 3, :refresh_token => proc { connection.refresh_token }) do
118
134
  sleep sleep_interval
119
135
  response = GoodData.get(link, :process => false)
120
136
  end
@@ -129,7 +145,7 @@ module GoodData
129
145
  # Generalizaton of poller. Since we have quite a variation of how async proceses are handled
130
146
  # this is a helper that should help you with resources where the information about "Are we done"
131
147
  # is inside the response. It expects the URI as an input where it can poll and a block that should
132
- # return either true -> 'meaning we are done' or false -> meaning sleep and repeat. It returns the
148
+ # return either false -> 'meaning we are done' or true -> meaning sleep and repeat. It returns the
133
149
  # value of last poll. In majority of cases these are the data that you need
134
150
  #
135
151
  # @param link [String] Link for polling
@@ -143,12 +159,20 @@ module GoodData
143
159
  response = get(link)
144
160
  while bl.call(response)
145
161
  sleep sleep_interval
146
- GoodData::Rest::Client.retryable(:tries => 3, :on => RestClient::InternalServerError) do
162
+ GoodData::Rest::Client.retryable(:tries => 3, :refresh_token => proc { client.connection.refresh_token }) do
147
163
  sleep sleep_interval
148
164
  response = get(link)
149
165
  end
150
166
  end
151
167
  response
152
168
  end
169
+
170
+ private
171
+
172
+ def merge_options(opts)
173
+ {
174
+ :project => GoodData.project
175
+ }.merge(opts)
176
+ end
153
177
  end
154
178
  end
@@ -0,0 +1,11 @@
1
+ # encoding: UTF-8
2
+
3
+ module GoodData
4
+ class UserInDifferentDomainError < RuntimeError
5
+ DEFAULT_MSG = 'User is already in different domain'
6
+
7
+ def initialize(msg = DEFAULT_MSG)
8
+ super(msg)
9
+ end
10
+ end
11
+ end
@@ -24,4 +24,12 @@ module Enumerable
24
24
  intermediate = pmap(&block)
25
25
  zip(intermediate).select { |x| x[1] }.map(&:first)
26
26
  end
27
+
28
+ def rjust(n, x)
29
+ Array.new([0, n - length].max, x) + self
30
+ end
31
+
32
+ def ljust(n, x)
33
+ dup.fill(x, length...n)
34
+ end
27
35
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module GoodData
4
4
  module SmallGoodZilla
5
+ PARSE_MAQL_OBJECT_REGEXP = /\[([^\]]+)\]/
6
+
5
7
  # Get IDs from MAQL string
6
8
  # @param a_maql_string Input MAQL string
7
9
  # @return [Array<String>] List of IDS
@@ -30,6 +32,28 @@ module GoodData
30
32
  a_maql_string.scan(/\?"([^\"]+)\"/).flatten
31
33
  end
32
34
 
35
+ # Pretty prints the MAQL expression. This basically means it finds out names of objects and elements and print their values instead of URIs
36
+ # @param expression [String] Expression to be beautified
37
+ # @return [String] Pretty printed MAQL expression
38
+ def self.pretty_print(expression, opts = { client: GoodData.connection, project: GoodData.project })
39
+ temp = expression.dup
40
+ pairs = expression.scan(PARSE_MAQL_OBJECT_REGEXP).pmap do |uri|
41
+ uri = uri.first
42
+ if uri =~ /elements/
43
+ [uri, Attribute.find_element_value(uri, opts)]
44
+ else
45
+ [uri, GoodData::MdObject[uri, opts].title]
46
+ end
47
+ end
48
+
49
+ pairs.each do |el|
50
+ uri = el[0]
51
+ obj = el[1]
52
+ temp.sub!(uri, obj)
53
+ end
54
+ temp
55
+ end
56
+
33
57
  def self.interpolate(values, dictionaries)
34
58
  {
35
59
  :facts => interpolate_values(values[:facts], dictionaries[:facts]),
@@ -6,16 +6,91 @@ require 'pathname'
6
6
  module GoodData
7
7
  module Helpers
8
8
  class << self
9
- def home_directory
10
- running_on_windows? ? ENV['USERPROFILE'] : ENV['HOME']
11
- end
9
+ # A helper which allows you to diff two lists of objects. The objects
10
+ # can be arbitrary objects as long as they respond to to_hash because
11
+ # the diff is eventually done on hashes. It allows you to specify
12
+ # several options to allow you to limit on what the sameness test is done
13
+ #
14
+ # @param [Array<Object>] old_list List of objects that serves as a base for comparison
15
+ # @param [Array<Object>] new_list List of objects that is compared agianst the old_list
16
+ # @return [Hash] A structure that contains the result of the comparison. There are
17
+ # four keys.
18
+ # :added contains the list that are in new_list but were not in the old_list
19
+ # :added contains the list that are in old_list but were not in the new_list
20
+ # :same contains objects that are in both lists and they are the same
21
+ # :changed contains list of objects that changed along ith original, the new one
22
+ # and the list of changes
23
+ def diff(old_list, new_list, options = {})
24
+ old_list = old_list.map(&:to_hash)
25
+ new_list = new_list.map(&:to_hash)
12
26
 
13
- def running_on_windows?
14
- RUBY_PLATFORM =~ /mswin32|mingw32/
27
+ fields = options[:fields]
28
+ lookup_key = options[:key]
29
+
30
+ old_lookup = Hash[old_list.map { |v| [v[lookup_key], v] }]
31
+
32
+ res = {
33
+ :added => [],
34
+ :removed => [],
35
+ :changed => [],
36
+ :same => []
37
+ }
38
+
39
+ new_list.each do |new_obj|
40
+ old_obj = old_lookup[new_obj[lookup_key]]
41
+ if old_obj.nil?
42
+ res[:added] << new_obj
43
+ next
44
+ end
45
+
46
+ if fields
47
+ sliced_old_obj = old_obj.slice(*fields)
48
+ sliced_new_obj = new_obj.slice(*fields)
49
+ else
50
+ sliced_old_obj = old_obj
51
+ sliced_new_obj = new_obj
52
+ end
53
+ if sliced_old_obj != sliced_new_obj
54
+ difference = sliced_new_obj.to_a - sliced_old_obj.to_a
55
+ differences = Hash[*difference.mapcat { |x| x }]
56
+ res[:changed] << {
57
+ old_obj: old_obj,
58
+ new_obj: new_obj,
59
+ diff: differences
60
+ }
61
+ else
62
+ res[:same] << old_obj
63
+ end
64
+ end
65
+
66
+ new_lookup = Hash[new_list.map { |v| [v[lookup_key], v] }]
67
+ old_list.each do |old_obj|
68
+ new_obj = new_lookup[old_obj[lookup_key]]
69
+ if new_obj.nil?
70
+ res[:removed] << old_obj
71
+ next
72
+ end
73
+ end
74
+
75
+ res
15
76
  end
16
77
 
17
- def running_on_a_mac?
18
- RUBY_PLATFORM =~ /-darwin\d/
78
+ def create_lookup(collection, on)
79
+ lookup = {}
80
+ if on.is_a?(Array)
81
+ collection.each do |e|
82
+ key = e.values_at(*on)
83
+ lookup[key] = [] unless lookup.key?(key)
84
+ lookup[key] << e
85
+ end
86
+ else
87
+ collection.each do |e|
88
+ key = e[on]
89
+ lookup[key] = [] unless lookup.key?(key)
90
+ lookup[key] << e
91
+ end
92
+ end
93
+ lookup
19
94
  end
20
95
 
21
96
  ENCODED_PARAMS_KEY = :gd_encoded_params
@@ -94,6 +169,10 @@ module GoodData
94
169
  end
95
170
  end
96
171
 
172
+ def home_directory
173
+ running_on_windows? ? ENV['USERPROFILE'] : ENV['HOME']
174
+ end
175
+
97
176
  def hash_dfs(thing, &block)
98
177
  if !thing.is_a?(Hash) && !thing.is_a?(Array) # rubocop:disable Style/GuardClause
99
178
  elsif thing.is_a?(Array)
@@ -109,14 +188,49 @@ module GoodData
109
188
  end
110
189
 
111
190
  # TODO: Implement without using ActiveSupport
112
- def sanitize_string(str, filter = /[^a-z_]/, replacement = '')
113
- str = ActiveSupport::Inflector.transliterate(str).downcase
114
- str.gsub(filter, replacement)
191
+ def humanize(str)
192
+ ActiveSupport::Inflector.humanize(str)
193
+ end
194
+
195
+ def join(master, slave, on, on2, options = {})
196
+ full_outer = options[:full_outer]
197
+
198
+ lookup = create_lookup(slave, on2)
199
+ marked_lookup = {}
200
+ results = master.reduce([]) do |a, line|
201
+ matching_values = lookup[line.values_at(*on)] || []
202
+ marked_lookup[line.values_at(*on)] = 1
203
+ if matching_values.empty?
204
+ a << line.to_hash
205
+ else
206
+ matching_values.each do |matching_value|
207
+ a << matching_value.to_hash.merge(line.to_hash)
208
+ end
209
+ end
210
+ a
211
+ end
212
+
213
+ if full_outer
214
+ (lookup.keys - marked_lookup.keys).each do |key|
215
+ puts lookup[key]
216
+ results << lookup[key].first.to_hash
217
+ end
218
+ end
219
+ results
220
+ end
221
+
222
+ def running_on_windows?
223
+ RUBY_PLATFORM =~ /mswin32|mingw32/
224
+ end
225
+
226
+ def running_on_a_mac?
227
+ RUBY_PLATFORM =~ /-darwin\d/
115
228
  end
116
229
 
117
230
  # TODO: Implement without using ActiveSupport
118
- def humanize(str)
119
- ActiveSupport::Inflector.humanize(str)
231
+ def sanitize_string(str, filter = /[^a-z_]/, replacement = '')
232
+ str = ActiveSupport::Inflector.transliterate(str).downcase
233
+ str.gsub(filter, replacement)
120
234
  end
121
235
 
122
236
  def underline(x)
@@ -3,13 +3,19 @@
3
3
  module GoodData
4
4
  module Mixin
5
5
  module Author
6
- # Gets Project Role Author
6
+ # Gets author of an object
7
7
  #
8
- # @return [GoodData::Profile] Project Role author
8
+ # @return [GoodData::Profile] object author
9
9
  def author
10
- url = meta['author']
11
- tmp = client.get url
12
- GoodData::Profile.new(tmp)
10
+ tmp = client.get(author_uri)
11
+ client.create(GoodData::Profile, tmp, project: project)
12
+ end
13
+
14
+ # Gets author URI of an object
15
+ #
16
+ # @return [String] object author URI
17
+ def author_uri
18
+ meta['author']
13
19
  end
14
20
  end
15
21
  end
@@ -0,0 +1,13 @@
1
+ # encoding: UTF-8
2
+
3
+ module GoodData
4
+ module Mixin
5
+ module IsDimension
6
+ # Returns true if the object is a dimension false otherwise
7
+ # @return [Boolean]
8
+ def dimension?
9
+ true
10
+ end
11
+ end
12
+ end
13
+ end
@@ -37,7 +37,23 @@ module GoodData
37
37
  # new(GoodData.get uri) unless uri.nil?
38
38
  if uri # rubocop:disable Style/GuardClause
39
39
  raw = client.get(uri)
40
- client.create(self, raw, client: client, project: project)
40
+ md_class = self
41
+ case raw.keys.first
42
+ when 'attribute'
43
+ md_class = GoodData::Attribute
44
+ when 'metric'
45
+ md_class = GoodData::Metric
46
+ when 'projectDashboard'
47
+ md_class = GoodData::Dashboard
48
+ when 'report'
49
+ md_class = GoodData::Report
50
+ when 'reportDefinition'
51
+ md_class = GoodData::ReportDefinition
52
+ else
53
+ md_class = self
54
+ end
55
+
56
+ client.create(md_class, raw, client: client, project: project)
41
57
  end
42
58
  end
43
59
 
@@ -45,10 +45,18 @@ module GoodData
45
45
  if key.nil?
46
46
  result
47
47
  elsif key.respond_to?(:category)
48
- result.select { |item| item['category'] == key.category }
48
+ result = result.select { |item| item['category'] == key.category }
49
49
  else
50
- result.select { |item| item['category'] == key }
50
+ result = result.select { |item| item['category'] == key }
51
51
  end
52
+
53
+ if opts[:full]
54
+ result = result.map do |res|
55
+ GoodData::MdObject[res['link'], :client => c, :project => opts[:project]]
56
+ end
57
+ end
58
+
59
+ result
52
60
  end
53
61
 
54
62
  # Checks for dependency