gooddata 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -1
  3. data/CHANGELOG.markdown +6 -0
  4. data/README.md +1 -0
  5. data/gooddata.gemspec +2 -1
  6. data/lib/gooddata.rb +4 -1
  7. data/lib/gooddata/bricks/base_downloader.rb +33 -19
  8. data/lib/gooddata/bricks/middleware/bulk_salesforce_middleware.rb +49 -25
  9. data/lib/gooddata/bricks/middleware/restforce_middleware.rb +36 -33
  10. data/lib/gooddata/cli/commands/project_cmd.rb +6 -4
  11. data/lib/gooddata/client.rb +1 -1
  12. data/lib/gooddata/commands/api.rb +1 -1
  13. data/lib/gooddata/commands/auth.rb +1 -1
  14. data/lib/gooddata/connection.rb +13 -10
  15. data/lib/gooddata/core/connection.rb +1 -1
  16. data/lib/gooddata/core/user.rb +11 -3
  17. data/lib/gooddata/exceptions/validation_error.rb +12 -0
  18. data/lib/gooddata/extensions/extensions.rb +6 -0
  19. data/lib/gooddata/goodzilla/goodzilla.rb +2 -2
  20. data/lib/gooddata/helpers/csv_helper.rb +57 -0
  21. data/lib/gooddata/{helpers.rb → helpers/global_helpers.rb} +0 -0
  22. data/lib/gooddata/helpers/helpers.rb +6 -0
  23. data/lib/gooddata/models/domain.rb +134 -24
  24. data/lib/gooddata/models/membership.rb +402 -0
  25. data/lib/gooddata/models/metadata.rb +64 -7
  26. data/lib/gooddata/models/metadata/attribute.rb +27 -12
  27. data/lib/gooddata/models/metadata/column.rb +1 -1
  28. data/lib/gooddata/models/metadata/dashboard.rb +7 -6
  29. data/lib/gooddata/models/metadata/display_form.rb +17 -2
  30. data/lib/gooddata/models/metadata/fact.rb +13 -7
  31. data/lib/gooddata/models/metadata/metric.rb +9 -9
  32. data/lib/gooddata/models/metadata/report.rb +7 -8
  33. data/lib/gooddata/models/metadata/report_definition.rb +10 -11
  34. data/lib/gooddata/models/metadata/schema.rb +1 -1
  35. data/lib/gooddata/models/model.rb +1 -1
  36. data/lib/gooddata/models/process.rb +44 -25
  37. data/lib/gooddata/models/profile.rb +365 -13
  38. data/lib/gooddata/models/project.rb +245 -35
  39. data/lib/gooddata/models/project_blueprint.rb +42 -18
  40. data/lib/gooddata/models/project_creator.rb +4 -1
  41. data/lib/gooddata/models/project_role.rb +7 -7
  42. data/lib/gooddata/models/schedule.rb +17 -1
  43. data/lib/gooddata/models/schema_blueprint.rb +19 -2
  44. data/lib/gooddata/version.rb +1 -1
  45. data/out.txt +0 -0
  46. data/spec/data/users.csv +12 -0
  47. data/spec/helpers/connection_helper.rb +1 -0
  48. data/spec/helpers/csv_helper.rb +12 -0
  49. data/spec/helpers/project_helper.rb +1 -1
  50. data/spec/integration/full_project_spec.rb +136 -3
  51. data/spec/spec_helper.rb +9 -0
  52. data/spec/unit/commands/command_user_spec.rb +1 -1
  53. data/spec/unit/extensions/hash_spec.rb +19 -0
  54. data/spec/unit/godzilla/goodzilla_spec.rb +15 -0
  55. data/spec/unit/helpers/csv_helper_spec.rb +18 -0
  56. data/spec/unit/models/domain_spec.rb +47 -4
  57. data/spec/unit/models/md_object_spec.rb +8 -0
  58. data/spec/unit/models/membership_spec.rb +128 -0
  59. data/spec/unit/models/metadata_spec.rb +38 -0
  60. data/spec/unit/models/profile_spec.rb +212 -0
  61. data/spec/unit/models/project_blueprint_spec.rb +35 -8
  62. data/spec/unit/models/project_role_spec.rb +6 -6
  63. data/spec/unit/models/project_spec.rb +226 -13
  64. data/spec/unit/models/schedule_spec.rb +58 -0
  65. data/tmp/.gitkeepme +0 -0
  66. metadata +36 -11
  67. data/lib/gooddata/models/account_settings.rb +0 -124
  68. data/lib/gooddata/models/user.rb +0 -165
  69. data/spec/unit/models/account_settings_spec.rb +0 -28
  70. data/spec/unit/models/user_spec.rb +0 -16
@@ -38,7 +38,10 @@ module GoodData
38
38
  # @return [MdObject] if id is a String or number single object is returned
39
39
  # @return [Array] if :all was provided as an id, list of objects should be returned. Note that this is implemented only in the subclasses. MdObject does not support this since API has no means to return list of all types of objects
40
40
  def [](id, options = {})
41
- fail "Cannot search for nil #{self.class}" unless id
41
+ fail "You have to provide an \"id\" to be searched for." unless id
42
+ fail(NoProjectError, 'Connect to a project before searching for an object') unless GoodData.project
43
+ return all(options) if id == :all
44
+ return id if id.is_a?(MdObject)
42
45
  uri = if id.is_a?(Integer) || id =~ /^\d+$/
43
46
  "#{GoodData.project.md[MD_OBJ_CTG]}/#{id}"
44
47
  elsif id !~ /\//
@@ -51,8 +54,13 @@ module GoodData
51
54
  new(GoodData.get uri) unless uri.nil?
52
55
  end
53
56
 
57
+ # Method intended to get all objects of that type in a specified project
58
+ #
59
+ # @param options [Hash] the options hash
60
+ # @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
61
+ # @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
54
62
  def all(options = {})
55
- self[:all, options]
63
+ fail NotImplementedError, 'Method should be implemented in subclass. Currently there is no way hoe to get all metadata objects on API.'
56
64
  end
57
65
 
58
66
  def find_by_tag(tag)
@@ -92,18 +100,42 @@ module GoodData
92
100
  if response['identifiers'].empty?
93
101
  nil
94
102
  else
95
- ids = response['identifiers'].map { |x| x['uri'] }
96
- ids.count == 1 ? ids.first : ids
103
+ identifiers = response['identifiers']
104
+ ids_lookup = identifiers.reduce({}) do |a, e|
105
+ a[e['identifier']] = e['uri']
106
+ a
107
+ end
108
+ uris = ids.map { |x| ids_lookup[x] }
109
+ uris.count == 1 ? uris.first : uris
97
110
  end
98
111
  end
99
112
 
100
113
  alias_method :id_to_uri, :identifier_to_uri
101
114
 
102
115
  alias_method :get_by_id, :[]
116
+
117
+ private
118
+
119
+ # Method intended to be called by individual classes in their all
120
+ # implementations. It abstracts the way interacting with query resources.
121
+ # It either returns the array of hashes from query. If asked it also
122
+ # goes and brings the full objects. Due to performance reasons
123
+ # :full => false is the default. This will most likely change
124
+ #
125
+ # @param query_obj_type [String] string used in URI to distinguish different query resources for different objects
126
+ # @param klass [Class] A class used for instantiating the returned data
127
+ # @param options [Hash] the options hash
128
+ # @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
129
+ # @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
130
+ def query(query_obj_type, klass, options = {})
131
+ fail(NoProjectError, 'Connect to a project before searching for an object') unless GoodData.project
132
+ query_result = GoodData.get(GoodData.project.md['query'] + "/#{query_obj_type}/")['query']['entries']
133
+ options[:full] ? query_result.map { |item| klass[item['link']] } : query_result
134
+ end
103
135
  end
104
136
 
105
137
  metadata_property_reader :uri, :identifier, :title, :summary, :tags, :deprecated, :category
106
- metadata_property_writer :tags, :summary, :title
138
+ metadata_property_writer :tags, :summary, :title, :identifier
107
139
 
108
140
  def root_key
109
141
  raw_data.keys.first
@@ -237,8 +269,21 @@ module GoodData
237
269
  self
238
270
  end
239
271
 
272
+ # Saves an object with a different name
273
+ #
274
+ # @param new_title [String] New title. If not provided one is provided
275
+ # @return [GoodData::MdObject] MdObject that has been saved as
276
+ def save_as(new_title = "Clone of #{title}")
277
+ dupped = Marshal.load(Marshal.dump(raw_data))
278
+ dupped[root_key]['meta'].delete('uri')
279
+ dupped[root_key]['meta'].delete('identifier')
280
+ dupped[root_key]['meta']['title'] = new_title
281
+ x = self.class.new(dupped)
282
+ x.save
283
+ end
284
+
240
285
  def ==(other)
241
- other.uri == uri && other.respond_to?(:to_hash) && other.to_hash == to_hash
286
+ other.respond_to?(:uri) && other.uri == uri && other.respond_to?(:to_hash) && other.to_hash == to_hash
242
287
  end
243
288
 
244
289
  def validate
@@ -249,19 +294,31 @@ module GoodData
249
294
  false
250
295
  end
251
296
 
252
- # TODO: generate fill for other subtypes
297
+ # Returns true if the object is a fact false otherwise
298
+ # @return [Boolean]
253
299
  def fact?
254
300
  false
255
301
  end
256
302
 
303
+ # Returns true if the object is an attribute false otherwise
304
+ # @return [Boolean]
257
305
  def attribute?
258
306
  false
259
307
  end
260
308
 
309
+ # Returns true if the object is a metric false otherwise
310
+ # @return [Boolean]
261
311
  def metric?
262
312
  false
263
313
  end
264
314
 
315
+ # Returns true if the object is a label false otherwise
316
+ # @return [Boolean]
317
+ def label?
318
+ false
319
+ end
320
+ alias_method :display_form?, :label?
321
+
265
322
  private
266
323
 
267
324
  def dependency(uri, key = nil)
@@ -9,44 +9,56 @@ module GoodData
9
9
  ATTRIBUTE_BASE_AGGREGATIONS = [:count]
10
10
 
11
11
  class << self
12
- def [](id, options = {})
13
- if id == :all
14
- attrs = GoodData.get(GoodData.project.md['query'] + '/attributes/')['query']['entries']
15
- options[:full] ? attrs.map { |a| Attribute[a['link']] } : attrs
16
- else
17
- super
18
- end
12
+ # Method intended to get all objects of that type in a specified project
13
+ #
14
+ # @param options [Hash] the options hash
15
+ # @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
16
+ # @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
17
+ def all(options = {})
18
+ query('attributes', Attribute, options)
19
19
  end
20
20
 
21
+ # Finds the value of an atribute and gives you the textual form for the label that is acquired by calling primary_label method
22
+ #
23
+ # @param uri [String] Uri of the element. in the form of /gdc/md/PID/obj/OBJ_ID/elements?id=21
24
+ # @return [String] Textual representation of a particular attribute element
21
25
  def find_element_value(uri)
22
26
  matches = uri.match(/(.*)\/elements\?id=(\d+)$/)
23
- Attribute[matches[1]].primary_label.find_element_value(matches[2].to_i)
27
+ Attribute[matches[1]].primary_label.find_element_value(uri)
24
28
  end
25
29
  end
26
30
 
31
+ # Returns the labels of an attribute
32
+ # @return [Array<GoodData::Label>]
27
33
  def display_forms
28
- content['displayForms'].map { |df| GoodData::DisplayForm[df['meta']['uri']] }
34
+ content['displayForms'].map { |df| GoodData::Label[df['meta']['uri']] }
29
35
  end
30
36
  alias_method :labels, :display_forms
31
37
 
32
38
  # Returns the first display form which is the primary one
33
- # @return [GoodData::DisplayForm] Primary label
39
+ # @return [GoodData::Label] Primary label
34
40
  def primary_display_form
35
41
  labels.first
36
42
  end
37
43
  alias_method :primary_label, :primary_display_form
38
44
 
45
+ # Returns true if the object is an attribute false otherwise
46
+ # @return [Boolean]
39
47
  def attribute?
40
48
  true
41
49
  end
42
50
 
51
+ # Creates the basic count metric with the attribute used. If you need to compute the attribute on a different dataset you can specify that in params. The metric created is not saved.
52
+ # @param [Hash] options the options to pass to the value list
53
+ # @option options [Symbol] :type type of aggregation function.
54
+ # @option options [Symbol] :attribute Use this attribute if you need to express different dataset for performing the computation on. It basically serves for creating metrics like SELECT COUNT(User, Opportunity).
55
+ # @return [GoodData::Metric]
43
56
  def create_metric(options = {})
44
57
  an_attribute = options[:attribute]
45
58
  a_type = options[:type] || :count
46
59
  fail "Suggested aggreagtion function (#{a_type}) does not exist for base metric created out of attribute. You can use only one of #{ATTRIBUTE_BASE_AGGREGATIONS.map { |x| ":" + x.to_s }.join(',')}" unless ATTRIBUTE_BASE_AGGREGATIONS.include?(a_type)
47
60
  a_title = options[:title] || "#{a_type} of #{title}"
48
61
  if an_attribute
49
- an_attribute = Attribute[an_attribute] if an_attribute.is_a?(String)
50
62
  Metric.xcreate(:expression => "SELECT #{a_type.to_s.upcase}(![#{identifier}], ![#{an_attribute.identifier}])", :title => a_title)
51
63
  else
52
64
  Metric.xcreate(:expression => "SELECT #{a_type.to_s.upcase}(![#{identifier}])", :title => a_title)
@@ -57,7 +69,7 @@ module GoodData
57
69
  # @param [Object] element_id Element identifier either Number or a uri as a String
58
70
  # @return [Array] list of values for certain element. Returned in the same order as is the order of labels
59
71
  def values_for(element_id)
60
- element_id = element_id.is_a?(String) ? element_id.match(/\?id=(\d)/)[1] : element_id
72
+ # element_id = element_id.is_a?(String) ? element_id.match(/\?id=(\d)/)[1] : element_id
61
73
  labels.map do |label|
62
74
  label.find_element_value(element_id)
63
75
  end
@@ -74,6 +86,9 @@ module GoodData
74
86
  results.first.zip(*results[1..-1])
75
87
  end
76
88
 
89
+ # Allows to search in attribute labels by name. It uses the string as a basis for regexp and tries to match either a title or an identifier. Returns first match.
90
+ # @param name [String] name used as a basis for regular expression
91
+ # @return [GoodData::Label]
77
92
  def label_by_name(name)
78
93
  labels.find { |label| label.title.downcase =~ /#{name}/ || label.identifier.downcase =~ /#{name}/ }
79
94
  end
@@ -1,7 +1,7 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  require_relative '../md_object'
4
- require_relative '../../helpers'
4
+ require_relative '../../helpers/helpers'
5
5
 
6
6
  module GoodData
7
7
  module Model
@@ -12,12 +12,13 @@ module GoodData
12
12
  root_key :projectDashboard
13
13
 
14
14
  class << self
15
- def [](id, options = {})
16
- if id == :all
17
- GoodData.get(GoodData.project.md['query'] + '/projectdashboards/')['query']['entries']
18
- else
19
- super
20
- end
15
+ # Method intended to get all objects of that type in a specified project
16
+ #
17
+ # @param options [Hash] the options hash
18
+ # @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
19
+ # @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
20
+ def all(options = {})
21
+ query('projectdashboards', Dashboard, options)
21
22
  end
22
23
 
23
24
  def create_report_tab(tab)
@@ -4,14 +4,14 @@ require_relative '../metadata'
4
4
  require_relative 'metadata'
5
5
 
6
6
  module GoodData
7
- class DisplayForm < GoodData::MdObject
7
+ class Label < GoodData::MdObject
8
8
  root_key :attributeDisplayForm
9
9
 
10
10
  # Finds an attribute element URI for given value. This URI can be used by find_element_value to find the original value again
11
11
  # @param [String] value value of an label you are looking for
12
12
  # @return [String]
13
13
  def find_value_uri(value)
14
- value = CGI.escapeHTML(value)
14
+ value = CGI.escape(value)
15
15
  results = GoodData.post("#{uri}/validElements?limit=30&offset=0&order=asc&filter=#{value}", {})
16
16
  items = results['validElements']['items']
17
17
  if items.empty?
@@ -57,5 +57,20 @@ module GoodData
57
57
  def attribute
58
58
  GoodData::Attribute[content['formOf']]
59
59
  end
60
+
61
+ # Returns true if the object is an attribute false otherwise
62
+ # @return [Boolean]
63
+ def label?
64
+ true
65
+ end
66
+ alias_method :display_form?, :label?
67
+
68
+ # Gives an attribute url of current label. Useful for mass actions when it does not introduce HTTP call.
69
+ # @return [GoodData::Attibute]
70
+ def attribute_uri
71
+ content['formOf']
72
+ end
60
73
  end
61
74
  end
75
+
76
+ GoodData::DisplayForm = GoodData::Label
@@ -12,20 +12,26 @@ module GoodData
12
12
  FACT_BASE_AGGREGATIONS = [:sum, :min, :max, :avg, :median]
13
13
 
14
14
  class << self
15
- def [](id, options = {})
16
- if id == :all
17
- facts = GoodData.get(GoodData.project.md['query'] + '/facts/')['query']['entries']
18
- options[:full] ? facts.map { |f| Fact[f['link']] } : facts
19
- else
20
- super
21
- end
15
+ # Method intended to get all objects of that type in a specified project
16
+ #
17
+ # @param options [Hash] the options hash
18
+ # @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
19
+ # @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
20
+ def all(options = {})
21
+ query('facts', Fact, options)
22
22
  end
23
23
  end
24
24
 
25
+ # Returns true if the object is a fact false otherwise
26
+ # @return [Boolean]
25
27
  def fact?
26
28
  true
27
29
  end
28
30
 
31
+ # Creates the basic count metric with the fact used. The metric created is not saved.
32
+ # @param [Hash] options the options to pass to the value list
33
+ # @option options [Symbol] :type type of aggregation function. Default is :sum
34
+ # @return [GoodData::Metric]
29
35
  def create_metric(options = {})
30
36
  a_type = options[:type] || :sum
31
37
  fail "Suggested aggreagtion function (#{a_type}) does not exist for base metric created out of fact. You can use only one of #{FACT_BASE_AGGREGATIONS.map { |x| ":" + x.to_s }.join(',')}" unless FACT_BASE_AGGREGATIONS.include?(a_type)
@@ -12,13 +12,13 @@ module GoodData
12
12
  PARSE_MAQL_OBJECT_REGEXP = /\[([^\]]+)\]/
13
13
 
14
14
  class << self
15
- def [](id, options = {})
16
- if id == :all
17
- metrics = GoodData.get(GoodData.project.md['query'] + '/metrics/')['query']['entries']
18
- options[:full] ? metrics.map { |m| Metric[m['link']] } : metrics
19
- else
20
- super
21
- end
15
+ # Method intended to get all objects of that type in a specified project
16
+ #
17
+ # @param options [Hash] the options hash
18
+ # @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
19
+ # @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
20
+ def all(options = {})
21
+ query('metrics', Metric, options)
22
22
  end
23
23
 
24
24
  def xcreate(options)
@@ -137,7 +137,7 @@ module GoodData
137
137
  end
138
138
 
139
139
  # Checks that the expression contains certain element of an attribute. The value is looked up through given label.
140
- # @param [GoodData::DisplayForm] label Label though which the value is looked up
140
+ # @param [GoodData::Label] label Label though which the value is looked up
141
141
  # @param [String] value Value that will be looked up through the label.
142
142
  # @return [Boolean]
143
143
  def contain_value?(label, value)
@@ -157,7 +157,7 @@ module GoodData
157
157
  end
158
158
 
159
159
  # Method used for replacing attribute element values. Looks up certain value of a label in the MAQL expression and exchanges it for a different value of the same label.
160
- # @param [GoodData::DisplayForm] label Label through which the value and for_value are resolved
160
+ # @param [GoodData::Label] label Label through which the value and for_value are resolved
161
161
  # @param [String] value value that is going to be replaced
162
162
  # @param [String] for_value value that is going to be the new one
163
163
  # @return [GoodData::Metric]
@@ -8,14 +8,13 @@ module GoodData
8
8
  root_key :report
9
9
 
10
10
  class << self
11
- def [](id, options = {})
12
- if id == :all
13
- fail 'You have to specify a project ID' if GoodData.project.nil?
14
- uri = GoodData.project.md['query'] + '/reports/'
15
- GoodData.get(uri)['query']['entries']
16
- else
17
- super
18
- end
11
+ # Method intended to get all objects of that type in a specified project
12
+ #
13
+ # @param options [Hash] the options hash
14
+ # @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
15
+ # @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
16
+ def all(options = {})
17
+ query('reports', Report, options)
19
18
  end
20
19
 
21
20
  def create(options = {})
@@ -11,14 +11,13 @@ module GoodData
11
11
  root_key :reportDefinition
12
12
 
13
13
  class << self
14
- def [](id, options = {})
15
- if id == :all
16
- uri = GoodData.project.md['query'] + '/reportdefinition/'
17
- result = GoodData.get(uri)
18
- result['query']['entries']
19
- else
20
- super
21
- end
14
+ # Method intended to get all objects of that type in a specified project
15
+ #
16
+ # @param options [Hash] the options hash
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
+ # @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
19
+ def all(options = {})
20
+ query('reportdefinition', ReportDefinition, options)
22
21
  end
23
22
 
24
23
  def create_metrics_part(left, top)
@@ -72,7 +71,7 @@ module GoodData
72
71
  when 'attribute'
73
72
  GoodData::Attribute.new(x.raw_data).display_forms.first
74
73
  when 'attributeDisplayForm'
75
- GoodData::DisplayForm.new(x.raw_data)
74
+ GoodData::Label.new(x.raw_data)
76
75
  when 'metric'
77
76
  GoodData::Metric.new(x.raw_data)
78
77
  end
@@ -91,7 +90,7 @@ module GoodData
91
90
  when 'attribute'
92
91
  GoodData::Attribute.get_by_id(item[:id]).display_forms.first
93
92
  when 'label'
94
- GoodData::DisplayForm.get_by_id(item[:id])
93
+ GoodData::Label.get_by_id(item[:id])
95
94
  end
96
95
  elsif item.is_a?(Hash) && (item.keys.include?(:identifier))
97
96
  case item[:type].to_s
@@ -101,7 +100,7 @@ module GoodData
101
100
  result = GoodData::Attribute.get_by_id(item[:identifier])
102
101
  result.display_forms.first
103
102
  when 'label'
104
- GoodData::DisplayForm.get_by_id(item[:identifier])
103
+ GoodData::Label.get_by_id(item[:identifier])
105
104
  end
106
105
  else
107
106
  item
@@ -1,6 +1,6 @@
1
1
  # encoding: UTF-8
2
2
 
3
- require_relative '../../helpers'
3
+ require_relative '../../helpers/helpers'
4
4
 
5
5
  require_relative '../attributes/anchor'
6
6
  require_relative '../columns/columns'
@@ -91,7 +91,7 @@ module GoodData
91
91
  def merge_dataset_columns(a_schema_blueprint, b_schema_blueprint)
92
92
  a_schema_blueprint = a_schema_blueprint.to_hash
93
93
  b_schema_blueprint = b_schema_blueprint.to_hash
94
- d = Marshal.load(Marshal.dump(a_schema_blueprint))
94
+ d = a_schema_blueprint.deep_dup
95
95
  d[:columns] = d[:columns] + b_schema_blueprint[:columns]
96
96
  d[:columns].uniq!
97
97
  columns_that_failed_to_merge = d[:columns].group_by { |x| x[:name] }.map { |k, v| [k, v.count] }.select { |x| x[1] > 1 }
@@ -42,41 +42,54 @@ module GoodData
42
42
  end
43
43
  end
44
44
 
45
- def upload_package(dir, files_to_exclude)
46
- Tempfile.open('deploy-graph-archive') do |temp|
47
- Zip::OutputStream.open(temp.path) do |zio|
48
- FileUtils.cd(dir) do
49
-
50
- files_to_pack = Dir.glob('./**/*').reject { |f| files_to_exclude.include?(Pathname(dir) + f) }
51
- files_to_pack.each do |item|
52
- # puts "including #{item}" if verbose
53
- unless File.directory?(item)
54
- zio.put_next_entry(item)
55
- zio.print IO.read(item)
45
+ def upload_package(path, files_to_exclude)
46
+ if !path.directory?
47
+ GoodData.upload_to_user_webdav(path)
48
+ path
49
+ else
50
+ Tempfile.open('deploy-graph-archive') do |temp|
51
+ Zip::OutputStream.open(temp.path) do |zio|
52
+ FileUtils.cd(path) do
53
+
54
+ files_to_pack = Dir.glob('./**/*').reject { |f| files_to_exclude.include?(Pathname(path) + f) }
55
+ files_to_pack.each do |item|
56
+ # puts "including #{item}" if verbose
57
+ unless File.directory?(item)
58
+ zio.put_next_entry(item)
59
+ zio.print IO.read(item)
60
+ end
56
61
  end
57
62
  end
58
63
  end
64
+ GoodData.upload_to_user_webdav(temp.path)
65
+ temp.path
59
66
  end
60
- GoodData.upload_to_user_webdav(temp.path)
61
- temp
62
67
  end
63
68
  end
64
69
 
65
- def deploy(dir, options = {})
66
- dir = Pathname(dir) || fail('Directory is not specified')
67
- fail "\"#{dir}\" is not a directory" unless dir.directory?
68
- files_to_exclude = options[:files_to_exclude].map { |p| Pathname(p) }
70
+ # Deploy a new process or redeploy existing one.
71
+ #
72
+ # @param path [String] Path to ZIP archive or to a directory containing files that should be ZIPed
73
+ # @option options [String] :files_to_exclude
74
+ # @option options [String] :process_id ('nobody') From address
75
+ # @option options [String] :type ('GRAPH') Type of process - GRAPH or RUBY
76
+ # @option options [String] :name Readable name of the process
77
+ # @option options [String] :process_id ID of a process to be redeployed (do not set if you want to create a new process)
78
+ # @option options [Boolean] :verbose (false) Switch on verbose mode for detailed logging
79
+ def deploy(path, options = {})
80
+ path = Pathname(path) || fail('Path is not specified')
81
+ files_to_exclude = options[:files_to_exclude].nil? ? [] : options[:files_to_exclude].map { |p| Pathname(p) }
69
82
  process_id = options[:process_id]
70
83
 
71
84
  type = options[:type] || 'GRAPH'
72
85
  deploy_name = options[:name]
73
86
  verbose = options[:verbose] || false
74
- puts HighLine.color("Deploying #{dir}", HighLine::BOLD) if verbose
75
- deployed_path = Process.upload_package(dir, files_to_exclude)
87
+ puts HighLine.color("Deploying #{path}", HighLine::BOLD) if verbose
88
+ deployed_path = Process.upload_package(path, files_to_exclude)
76
89
  data = {
77
90
  :process => {
78
91
  :name => deploy_name,
79
- :path => "/uploads/#{File.basename(deployed_path.path)}",
92
+ :path => "/uploads/#{File.basename(deployed_path)}",
80
93
  :type => type
81
94
  }
82
95
  }
@@ -86,7 +99,7 @@ module GoodData
86
99
  GoodData.put("/gdc/projects/#{GoodData.project.pid}/dataload/processes/#{process_id}", data)
87
100
  end
88
101
  process = Process.new(res)
89
- puts HighLine.color("Deploy DONE #{dir}", HighLine::GREEN) if verbose
102
+ puts HighLine.color("Deploy DONE #{path}", HighLine::GREEN) if verbose
90
103
  process
91
104
  end
92
105
  end
@@ -99,10 +112,16 @@ module GoodData
99
112
  GoodData.delete(uri)
100
113
  end
101
114
 
102
- def deploy(dir, options = {})
103
- process = Process.upload(dir, options.merge(:process_id => process_id))
104
- puts HighLine.color("Deploy DONE #{dir}", HighLine::GREEN) if verbose
105
- process
115
+ # Redeploy existing process.
116
+ #
117
+ # @param path [String] Path to ZIP archive or to a directory containing files that should be ZIPed
118
+ # @option options [String] :files_to_exclude
119
+ # @option options [String] :process_id ('nobody') From address
120
+ # @option options [String] :type ('GRAPH') Type of process - GRAPH or RUBY
121
+ # @option options [String] :name Readable name of the process
122
+ # @option options [Boolean] :verbose (false) Switch on verbose mode for detailed logging
123
+ def deploy(path, options = {})
124
+ Process.deploy(path, options.merge(:process_id => process_id))
106
125
  end
107
126
 
108
127
  def process