gooddata 0.6.0 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (208) hide show
  1. checksums.yaml +13 -5
  2. data/.rubocop.yml +23 -0
  3. data/.travis.yml +9 -4
  4. data/CLI.md +439 -0
  5. data/Gemfile +0 -1
  6. data/README.md +2 -2
  7. data/Rakefile +60 -8
  8. data/doc/templates/default/module/setup.rb +1 -1
  9. data/examples.rb +2 -0
  10. data/gooddata +2 -0
  11. data/gooddata.gemspec +12 -8
  12. data/lib/gooddata.rb +0 -2
  13. data/lib/gooddata/bricks/base_downloader.rb +52 -47
  14. data/lib/gooddata/bricks/brick.rb +20 -31
  15. data/lib/gooddata/bricks/bricks.rb +1 -1
  16. data/lib/gooddata/bricks/middleware/base_middleware.rb +9 -7
  17. data/lib/gooddata/bricks/middleware/bench_middleware.rb +12 -10
  18. data/lib/gooddata/bricks/middleware/bulk_salesforce_middleware.rb +28 -28
  19. data/lib/gooddata/bricks/middleware/fs_upload_middleware.rb +20 -16
  20. data/lib/gooddata/bricks/middleware/gooddata_middleware.rb +21 -19
  21. data/lib/gooddata/bricks/middleware/logger_middleware.rb +10 -8
  22. data/lib/gooddata/bricks/middleware/restforce_middleware.rb +36 -34
  23. data/lib/gooddata/bricks/middleware/stdout_middleware.rb +11 -9
  24. data/lib/gooddata/bricks/middleware/twitter_middleware.rb +14 -12
  25. data/lib/gooddata/bricks/pipeline.rb +28 -0
  26. data/lib/gooddata/bricks/utils.rb +10 -8
  27. data/lib/gooddata/cli/cli.rb +1 -6
  28. data/lib/gooddata/cli/commands/auth_cmd.rb +1 -1
  29. data/lib/gooddata/cli/commands/console_cmd.rb +7 -5
  30. data/lib/gooddata/cli/commands/domain_cmd.rb +45 -0
  31. data/lib/gooddata/cli/commands/process_cmd.rb +42 -5
  32. data/lib/gooddata/cli/commands/project_cmd.rb +96 -36
  33. data/lib/gooddata/cli/commands/projects_cmd.rb +21 -0
  34. data/lib/gooddata/cli/commands/role_cmd.rb +28 -0
  35. data/lib/gooddata/cli/commands/run_ruby_cmd.rb +5 -5
  36. data/lib/gooddata/cli/commands/scaffold_cmd.rb +1 -1
  37. data/lib/gooddata/cli/commands/{profile_cmd.rb → user_cmd.rb} +7 -9
  38. data/lib/gooddata/cli/shared.rb +3 -2
  39. data/lib/gooddata/client.rb +16 -304
  40. data/lib/gooddata/commands/api.rb +13 -5
  41. data/lib/gooddata/commands/auth.rb +47 -40
  42. data/lib/gooddata/commands/base.rb +4 -2
  43. data/lib/gooddata/commands/commands.rb +1 -1
  44. data/lib/gooddata/commands/datasets.rb +20 -7
  45. data/lib/gooddata/commands/domain.rb +23 -0
  46. data/lib/gooddata/commands/process.rb +23 -117
  47. data/lib/gooddata/commands/project.rb +147 -0
  48. data/lib/gooddata/commands/projects.rb +8 -102
  49. data/lib/gooddata/commands/role.rb +26 -0
  50. data/lib/gooddata/commands/runners.rb +41 -38
  51. data/lib/gooddata/commands/scaffold.rb +46 -43
  52. data/lib/gooddata/commands/user.rb +33 -0
  53. data/lib/gooddata/connection.rb +43 -353
  54. data/lib/gooddata/core/connection.rb +389 -0
  55. data/lib/gooddata/core/core.rb +5 -4
  56. data/lib/gooddata/core/logging.rb +48 -0
  57. data/lib/gooddata/core/nil_logger.rb +13 -0
  58. data/lib/gooddata/core/project.rb +70 -0
  59. data/lib/gooddata/core/rest.rb +120 -0
  60. data/lib/gooddata/core/threaded.rb +14 -0
  61. data/lib/gooddata/core/user.rb +19 -0
  62. data/lib/gooddata/data/data.rb +2 -1
  63. data/lib/gooddata/data/guesser.rb +16 -12
  64. data/lib/gooddata/exceptions/command_failed.rb +1 -1
  65. data/lib/gooddata/exceptions/exceptions.rb +2 -1
  66. data/lib/gooddata/exceptions/no_project_error.rb +11 -0
  67. data/lib/gooddata/exceptions/project_not_found.rb +1 -1
  68. data/lib/gooddata/extensions/big_decimal.rb +6 -2
  69. data/lib/gooddata/extract.rb +10 -8
  70. data/lib/gooddata/goodzilla/goodzilla.rb +61 -59
  71. data/lib/gooddata/helpers.rb +15 -9
  72. data/lib/gooddata/models/account_settings.rb +124 -0
  73. data/lib/gooddata/models/attributes/anchor.rb +37 -0
  74. data/lib/gooddata/models/attributes/attributes.rb +8 -0
  75. data/lib/gooddata/models/attributes/date_attribute.rb +25 -0
  76. data/lib/gooddata/models/attributes/time_attribute.rb +24 -0
  77. data/lib/gooddata/models/columns/attribute.rb +71 -0
  78. data/lib/gooddata/models/columns/columns.rb +8 -0
  79. data/lib/gooddata/models/columns/date_column.rb +63 -0
  80. data/lib/gooddata/models/columns/fact_model.rb +54 -0
  81. data/lib/gooddata/models/columns/label.rb +55 -0
  82. data/lib/gooddata/models/columns/reference.rb +57 -0
  83. data/lib/gooddata/models/dashboard_builder.rb +26 -0
  84. data/lib/gooddata/models/data_result.rb +10 -9
  85. data/lib/gooddata/models/domain.rb +131 -0
  86. data/lib/gooddata/models/empty_result.rb +5 -8
  87. data/lib/gooddata/models/facts/facts.rb +8 -0
  88. data/lib/gooddata/models/facts/time_fact.rb +20 -0
  89. data/lib/gooddata/models/folders/attribute_folder.rb +20 -0
  90. data/lib/gooddata/models/folders/fact_folder.rb +20 -0
  91. data/lib/gooddata/models/folders/folders.rb +8 -0
  92. data/lib/gooddata/models/invitation.rb +78 -0
  93. data/lib/gooddata/models/links.rb +6 -6
  94. data/lib/gooddata/models/md_object.rb +25 -0
  95. data/lib/gooddata/models/metadata.rb +160 -62
  96. data/lib/gooddata/models/metadata/attribute.rb +81 -0
  97. data/lib/gooddata/models/metadata/column.rb +61 -0
  98. data/lib/gooddata/models/{dashboard.rb → metadata/dashboard.rb} +12 -7
  99. data/lib/gooddata/models/{data_set.rb → metadata/data_set.rb} +5 -4
  100. data/lib/gooddata/models/metadata/date_dimension.rb +26 -0
  101. data/lib/gooddata/models/metadata/display_form.rb +61 -0
  102. data/lib/gooddata/models/metadata/fact.rb +36 -0
  103. data/lib/gooddata/models/metadata/folder.rb +24 -0
  104. data/lib/gooddata/models/metadata/metadata.rb +8 -0
  105. data/lib/gooddata/models/metadata/metric.rb +197 -0
  106. data/lib/gooddata/models/metadata/report.rb +115 -0
  107. data/lib/gooddata/models/{report_definition.rb → metadata/report_definition.rb} +16 -10
  108. data/lib/gooddata/models/metadata/schema.rb +227 -0
  109. data/lib/gooddata/models/model.rb +38 -1339
  110. data/lib/gooddata/models/models.rb +5 -2
  111. data/lib/gooddata/models/module_constants.rb +29 -0
  112. data/lib/gooddata/models/process.rb +142 -13
  113. data/lib/gooddata/models/profile.rb +4 -6
  114. data/lib/gooddata/models/project.rb +406 -136
  115. data/lib/gooddata/models/project_blueprint.rb +221 -0
  116. data/lib/gooddata/models/project_builder.rb +136 -0
  117. data/lib/gooddata/models/project_creator.rb +138 -0
  118. data/lib/gooddata/models/project_metadata.rb +11 -10
  119. data/lib/gooddata/models/project_role.rb +92 -0
  120. data/lib/gooddata/models/references/date_reference.rb +44 -0
  121. data/lib/gooddata/models/references/references.rb +8 -0
  122. data/lib/gooddata/models/references/time_reference.rb +13 -0
  123. data/lib/gooddata/models/report_data_result.rb +11 -11
  124. data/lib/gooddata/models/schedule.rb +284 -0
  125. data/lib/gooddata/models/schema_blueprint.rb +158 -0
  126. data/lib/gooddata/models/schema_builder.rb +81 -0
  127. data/lib/gooddata/models/tab_builder.rb +23 -0
  128. data/lib/gooddata/models/user.rb +165 -0
  129. data/lib/gooddata/version.rb +1 -1
  130. data/lib/templates/project/data/devs.csv +1 -1
  131. data/lib/templates/project/data/repos.csv +1 -1
  132. data/lib/templates/project/model/model.rb.erb +7 -11
  133. data/spec/bricks/bricks_spec.rb +2 -0
  134. data/spec/data/test-ci-data.csv +2 -0
  135. data/spec/data/test_project_model_spec.json +7 -27
  136. data/spec/helpers/blueprint_helper.rb +2 -0
  137. data/spec/helpers/cli_helper.rb +2 -0
  138. data/spec/helpers/connection_helper.rb +14 -1
  139. data/spec/helpers/project_helper.rb +16 -0
  140. data/spec/helpers/schema_helper.rb +16 -0
  141. data/spec/integration/command_projects_spec.rb +7 -7
  142. data/spec/integration/create_from_template_spec.rb +2 -2
  143. data/spec/integration/full_project_spec.rb +160 -7
  144. data/spec/integration/partial_md_export_import_spec.rb +3 -3
  145. data/spec/logging_in_logging_out_spec.rb +2 -1
  146. data/spec/spec_helper.rb +26 -4
  147. data/spec/unit/bricks/bricks_spec.rb +15 -7
  148. data/spec/unit/bricks/middleware/bench_middleware_spec.rb +2 -0
  149. data/spec/unit/bricks/middleware/bulk_salesforce_middleware_spec.rb +2 -0
  150. data/spec/unit/bricks/middleware/gooddata_middleware_spec.rb +2 -0
  151. data/spec/unit/bricks/middleware/logger_middleware_spec.rb +2 -0
  152. data/spec/unit/bricks/middleware/restforce_middleware_spec.rb +2 -0
  153. data/spec/unit/bricks/middleware/stdout_middleware_spec.rb +2 -0
  154. data/spec/unit/bricks/middleware/twitter_middleware_spec.rb +2 -0
  155. data/spec/unit/cli/cli_spec.rb +2 -0
  156. data/spec/unit/cli/commands/cmd_api_spec.rb +23 -15
  157. data/spec/unit/cli/commands/cmd_auth_spec.rb +8 -4
  158. data/spec/unit/cli/commands/cmd_domain_spec.rb +82 -0
  159. data/spec/unit/cli/commands/cmd_process_spec.rb +29 -13
  160. data/spec/unit/cli/commands/cmd_project_spec.rb +51 -30
  161. data/spec/unit/cli/commands/cmd_role_spec.rb +44 -0
  162. data/spec/unit/cli/commands/cmd_run_ruby_spec.rb +8 -4
  163. data/spec/unit/cli/commands/cmd_scaffold_spec.rb +48 -11
  164. data/spec/unit/cli/commands/cmd_user_spec.rb +29 -0
  165. data/spec/unit/commands/command_api_spec.rb +1 -1
  166. data/spec/unit/commands/command_auth_spec.rb +100 -18
  167. data/spec/unit/commands/command_dataset_spec.rb +4 -0
  168. data/spec/unit/commands/command_process_spec.rb +9 -4
  169. data/spec/unit/commands/command_projects_spec.rb +10 -6
  170. data/spec/unit/commands/command_scaffold_spec.rb +5 -1
  171. data/spec/unit/commands/command_user_spec.rb +22 -0
  172. data/spec/unit/core/connection_spec.rb +35 -6
  173. data/spec/unit/core/logging_spec.rb +65 -0
  174. data/spec/unit/core/nil_logger_spec.rb +9 -0
  175. data/spec/unit/core/project_spec.rb +51 -0
  176. data/spec/unit/core/rest_spec.rb +33 -0
  177. data/spec/unit/data/guesser_spec.rb +5 -0
  178. data/spec/unit/godzilla/goodzilla_spec.rb +2 -0
  179. data/spec/unit/models/account_settings_spec.rb +28 -0
  180. data/spec/unit/models/anchor_spec.rb +32 -0
  181. data/spec/unit/models/attribute_column_spec.rb +7 -0
  182. data/spec/unit/models/domain_spec.rb +45 -0
  183. data/spec/unit/models/invitation_spec.rb +13 -0
  184. data/spec/unit/models/md_object_spec.rb +47 -0
  185. data/spec/unit/models/metric.rb +92 -0
  186. data/spec/unit/{model → models}/model_spec.rb +9 -7
  187. data/spec/unit/models/project_blueprint_spec.rb +202 -0
  188. data/spec/unit/models/project_creator.rb +73 -0
  189. data/spec/unit/models/project_role_spec.rb +90 -0
  190. data/spec/unit/models/project_spec.rb +143 -0
  191. data/spec/unit/models/schedule_spec.rb +491 -0
  192. data/spec/unit/{model → models}/schema_builder_spec.rb +2 -0
  193. data/spec/unit/{model → models}/tools_spec.rb +13 -7
  194. data/spec/unit/models/user_spec.rb +16 -0
  195. data/test/test_upload.rb +2 -0
  196. metadata +189 -86
  197. data/lib/gooddata/commands/profile.rb +0 -11
  198. data/lib/gooddata/models/attribute.rb +0 -29
  199. data/lib/gooddata/models/display_form.rb +0 -9
  200. data/lib/gooddata/models/fact.rb +0 -19
  201. data/lib/gooddata/models/metric.rb +0 -99
  202. data/lib/gooddata/models/report.rb +0 -89
  203. data/spec/data/blueprint_valid.json +0 -37
  204. data/spec/unit/cli/commands/cmd_profile_spec.rb +0 -16
  205. data/spec/unit/commands/command_profile_spec.rb +0 -18
  206. data/spec/unit/core/core_spec.rb +0 -7
  207. data/spec/unit/model/blueprint_spec.rb +0 -132
  208. data/spec/unit/model/project_blueprint_spec.rb +0 -44
@@ -1,7 +1,10 @@
1
1
  # encoding: UTF-8
2
+
2
3
  require 'pathname'
3
4
 
5
+ require_relative 'attributes/attributes'
6
+
4
7
  base = Pathname(__FILE__).dirname.expand_path
5
8
  Dir.glob(base + '*.rb').each do |file|
6
- require file
7
- end
9
+ require_relative file
10
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: UTF-8
2
+
3
+ module GoodData
4
+ module Model
5
+ # GoodData REST API categories
6
+ LDM_CTG = 'ldm'
7
+ LDM_MANAGE_CTG = 'ldm-manage2'
8
+
9
+ # Model naming conventions
10
+ FIELD_PK = 'id'
11
+ FK_SUFFIX = '_id'
12
+ FACT_COLUMN_PREFIX = 'f_'
13
+ DATE_COLUMN_PREFIX = 'dt_'
14
+ TIME_COLUMN_PREFIX = 'tm_'
15
+ LABEL_COLUMN_PREFIX = 'nm_'
16
+ ATTRIBUTE_FOLDER_PREFIX = 'dim'
17
+ ATTRIBUTE_PREFIX = 'attr'
18
+ LABEL_PREFIX = 'label'
19
+ FACT_PREFIX = 'fact'
20
+ DATE_FACT_PREFIX = 'dt'
21
+ DATE_ATTRIBUTE = 'date'
22
+ DATE_ATTRIBUTE_DEFAULT_DISPLAY_FORM = 'mdyy'
23
+ TIME_FACT_PREFIX = 'tm.dt'
24
+ TIME_ATTRIBUTE_PREFIX = 'attr.time'
25
+ FACT_FOLDER_PREFIX = 'ffld'
26
+
27
+ SKIP_FIELD = false
28
+ end
29
+ end
@@ -4,49 +4,178 @@ require 'pry'
4
4
 
5
5
  module GoodData
6
6
  class Process
7
+ attr_reader :data
8
+
7
9
  class << self
8
- def [](id)
10
+ def [](id, options = {})
9
11
  if id == :all
10
12
  uri = "/gdc/projects/#{GoodData.project.pid}/dataload/processes"
11
- GoodData.get(uri)
13
+ data = GoodData.get(uri)
14
+ data['processes']['items'].map do |process_data|
15
+ Process.new(process_data)
16
+ end
12
17
  else
13
18
  uri = "/gdc/projects/#{GoodData.project.pid}/dataload/processes/#{id}"
14
- self.new(GoodData.get(uri))
19
+ new(GoodData.get(uri))
20
+ end
21
+ end
22
+
23
+ def all
24
+ Process[:all]
25
+ end
26
+
27
+ # TODO: Check the params.
28
+ def with_deploy(dir, options = {}, &block)
29
+ # verbose = options[:verbose] || false
30
+ GoodData.with_project(options[:project_id]) do |project|
31
+ params = options[:params].nil? ? [] : [options[:params]]
32
+ if block
33
+ begin
34
+ res = GoodData::Process.deploy(dir, options.merge(:files_to_exclude => params))
35
+ block.call(res)
36
+ ensure
37
+ res.delete if res
38
+ end
39
+ else
40
+ GoodData::Process.deploy(dir, options.merge(:files_to_exclude => params))
41
+ end
42
+ end
43
+ end
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)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ GoodData.upload_to_user_webdav(temp.path)
61
+ temp
15
62
  end
16
63
  end
64
+
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) }
69
+ process_id = options[:process_id]
70
+
71
+ type = options[:type] || 'GRAPH'
72
+ deploy_name = options[:name]
73
+ verbose = options[:verbose] || false
74
+ puts HighLine.color("Deploying #{dir}", HighLine::BOLD) if verbose
75
+ deployed_path = Process.upload_package(dir, files_to_exclude)
76
+ data = {
77
+ :process => {
78
+ :name => deploy_name,
79
+ :path => "/uploads/#{File.basename(deployed_path.path)}",
80
+ :type => type
81
+ }
82
+ }
83
+ res = if process_id.nil?
84
+ GoodData.post("/gdc/projects/#{GoodData.project.pid}/dataload/processes", data)
85
+ else
86
+ GoodData.put("/gdc/projects/#{GoodData.project.pid}/dataload/processes/#{process_id}", data)
87
+ end
88
+ process = Process.new(res)
89
+ puts HighLine.color("Deploy DONE #{dir}", HighLine::GREEN) if verbose
90
+ process
91
+ end
17
92
  end
18
93
 
19
94
  def initialize(data)
20
95
  @data = data
21
96
  end
22
97
 
98
+ def delete
99
+ GoodData.delete(uri)
100
+ end
101
+
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
106
+ end
107
+
108
+ def process
109
+ raw_data['process']
110
+ end
111
+
112
+ def name
113
+ process['name']
114
+ end
115
+
116
+ def type
117
+ process['type'].downcase.to_sym
118
+ end
119
+
23
120
  def links
24
- @data['process']['links']
121
+ process['links']
25
122
  end
26
123
 
27
124
  def link
28
125
  links['self']
29
126
  end
127
+ alias_method :uri, :link
128
+
129
+ def obj_id
130
+ uri.split('/').last
131
+ end
132
+
133
+ alias_method :process_id, :obj_id
30
134
 
31
135
  def executions_link
32
136
  links['executions']
33
137
  end
34
138
 
35
- def execute_process(graph, options = {})
36
- result = GoodData.post(executions_link, {
37
- :execution => {
38
- :graph => graph,
39
- :params => {}
40
- }
41
- })
139
+ def graphs
140
+ process['graphs']
141
+ end
142
+
143
+ def executables
144
+ process['executables']
145
+ end
146
+
147
+ def schedules
148
+ res = []
149
+
150
+ scheds = GoodData::Schedule[:all]
151
+ scheds['schedules']['items'].each do |item|
152
+ if item['schedule']['params']['PROCESS_ID'] == obj_id
153
+ res << GoodData::Schedule.new(item)
154
+ end
155
+ end
156
+
157
+ res
158
+ end
159
+
160
+ alias_method :raw_data, :data
161
+
162
+ def execute(executable, options = {})
163
+ params = options[:params] || {}
164
+ hidden_params = options[:hidden_params] || {}
165
+ result = GoodData.post(executions_link,
166
+ :execution => {
167
+ :graph => executable.to_s,
168
+ :params => params,
169
+ :hiddenParams => hidden_params
170
+ })
42
171
  begin
43
172
  GoodData.poll(result, 'executionTask')
44
173
  rescue RestClient::RequestFailed => e
45
-
174
+ raise(e)
46
175
  ensure
47
176
  result = GoodData.get(result['executionTask']['links']['detail'])
48
177
  if result['executionDetail']['status'] == 'ERROR'
49
- fail "Runing process failed. You can look at a log here #{result['executionDetail']['logFileName']}"
178
+ fail "Runing process failed. You can look at a log here #{result["executionDetail"]["logFileName"]}"
50
179
  end
51
180
  end
52
181
  result
@@ -3,7 +3,7 @@
3
3
  module GoodData
4
4
  class Profile
5
5
  private_class_method :new
6
- attr_reader :user
6
+ attr_reader :user, :json
7
7
 
8
8
  class << self
9
9
  def load
@@ -16,11 +16,9 @@ module GoodData
16
16
  @json['accountSetting']['links']['projects']
17
17
  end
18
18
 
19
- def to_json
20
- @json
21
- end
19
+ alias_method :to_json, :json
22
20
 
23
- def [](key)
21
+ def [](key, options = {})
24
22
  @json['accountSetting'][key]
25
23
  end
26
24
 
@@ -31,4 +29,4 @@ module GoodData
31
29
  @user = @json['accountSetting']['firstName'] + ' ' + @json['accountSetting']['lastName']
32
30
  end
33
31
  end
34
- end
32
+ end
@@ -3,17 +3,19 @@
3
3
  require 'zip'
4
4
  require 'fileutils'
5
5
 
6
- module GoodData
7
- class NoProjectError < RuntimeError;
8
- end
6
+ require_relative 'process'
7
+ require_relative '../exceptions/no_project_error'
8
+ require_relative 'project_role'
9
9
 
10
+ module GoodData
10
11
  class Project
11
12
  USERSPROJECTS_PATH = '/gdc/account/profile/%s/projects'
12
13
  PROJECTS_PATH = '/gdc/projects'
13
14
  PROJECT_PATH = '/gdc/projects/%s'
14
15
  SLIS_PATH = '/ldm/singleloadinterface'
16
+ DEFAULT_INVITE_MESSAGE = 'Join us!'
15
17
 
16
- attr_accessor :connection
18
+ attr_accessor :connection, :json
17
19
 
18
20
  class << self
19
21
  # Returns an array of all projects accessible by
@@ -31,13 +33,13 @@ module GoodData
31
33
  # - /gdc/projects/<id>
32
34
  # - <id>
33
35
  #
34
- def [](id)
35
- return id if id.respond_to?(:is_project?) && id.is_project?
36
+ def [](id, options = {})
37
+ return id if id.respond_to?(:project?) && id.project?
36
38
  if id == :all
37
39
  Project.all
38
40
  else
39
- if id.to_s !~ /^(\/gdc\/(projects|md)\/)?[a-zA-Z\d]+$/
40
- raise ArgumentError.new('wrong type of argument. Should be either project ID or path')
41
+ if id.to_s !~ %r{^(\/gdc\/(projects|md)\/)?[a-zA-Z\d]+$}
42
+ fail(ArgumentError, 'wrong type of argument. Should be either project ID or path')
41
43
  end
42
44
 
43
45
  id = id.match(/[a-zA-Z\d]+$/)[0] if id =~ /\//
@@ -57,103 +59,45 @@ module GoodData
57
59
  GoodData.logger.info "Creating project #{attributes[:title]}"
58
60
 
59
61
  auth_token = attributes[:auth_token] || GoodData.connection.auth_token
60
- fail "You have to provide your token for creating projects as :auth_token parameter" if auth_token.nil? || auth_token.empty?
61
-
62
- json = {"project" =>
63
- {
64
- 'meta' => {
65
- 'title' => attributes[:title],
66
- 'summary' => attributes[:summary] || 'No summary'
67
- },
68
- 'content' => {
69
- 'guidedNavigation' => 1,
70
- 'authorizationToken' => auth_token,
71
- 'driver' => 'Pg'
72
- }
73
- }
74
- }
75
- json["project"]['meta']['projectTemplate'] = attributes[:template] if attributes[:template] && !attributes[:template].empty?
62
+ fail 'You have to provide your token for creating projects as :auth_token parameter' if auth_token.nil? || auth_token.empty?
63
+
64
+ json = {
65
+ 'project' =>
66
+ {
67
+ 'meta' => {
68
+ 'title' => attributes[:title],
69
+ 'summary' => attributes[:summary] || 'No summary'
70
+ },
71
+ 'content' => {
72
+ 'guidedNavigation' => attributes[:guided_navigation] || 1,
73
+ 'authorizationToken' => auth_token,
74
+ 'driver' => attributes[:driver] || 'Pg'
75
+ }
76
+ }
77
+ }
78
+
79
+ json['project']['meta']['projectTemplate'] = attributes[:template] if attributes[:template] && !attributes[:template].empty?
76
80
  project = Project.new json
77
81
  project.save
78
82
 
79
83
  # until it is enabled or deleted, recur. This should still end if there is a exception thrown out from RESTClient. This sometimes happens from WebApp when request is too long
80
- while project.state.to_s != "enabled"
81
- if project.state.to_s == "deleted"
84
+ while project.state.to_s != 'enabled'
85
+ if project.state.to_s == 'deleted'
82
86
  # if project is switched to deleted state, fail. This is usually problem of creating a template which is invalid.
83
- fail "Project was marked as deleted during creation. This usually means you were trying to create from template and it failed."
87
+ fail 'Project was marked as deleted during creation. This usually means you were trying to create from template and it failed.'
84
88
  end
85
89
  sleep(3)
86
90
  project.reload!
87
91
  end
88
92
 
89
93
  if block
90
- GoodData::with_project(project) do |p|
94
+ GoodData.with_project(project) do |p|
91
95
  block.call(p)
92
96
  end
93
97
  end
98
+ sleep 3
94
99
  project
95
100
  end
96
-
97
- end
98
-
99
- def initialize(json)
100
- @json = json
101
- end
102
-
103
- def save
104
- response = GoodData.post PROJECTS_PATH, raw_data
105
- if uri == nil
106
- response = GoodData.get response['uri']
107
- @json = response
108
- end
109
- end
110
-
111
- def saved?
112
- !!uri
113
- end
114
-
115
- def reload!
116
- if saved?
117
- response = GoodData.get(uri)
118
- @json = response
119
- end
120
- self
121
- end
122
-
123
- def delete
124
- raise "Project '#{title}' with id #{uri} is already deleted" if state == :deleted
125
- GoodData.delete(uri)
126
- end
127
-
128
- def uri
129
- data['links']['self'] if data && data['links'] && data['links']['self']
130
- end
131
-
132
- def browser_uri(options={})
133
- ui = options[:ui]
134
- if ui
135
- GoodData.connection.url + '#s=' + uri
136
- else
137
- GoodData.connection.url + uri
138
- end
139
- end
140
-
141
- def obj_id
142
- uri.split('/').last
143
- end
144
-
145
- alias :pid :obj_id
146
-
147
- def title
148
- data['meta']['title'] if data['meta']
149
- end
150
-
151
- def state
152
- data['content']['state'].downcase.to_sym if data['content'] && data['content']['state']
153
- end
154
-
155
- def md
156
- @md ||= Links.new GoodData.get(data['links']['metadata'])
157
101
  end
158
102
 
159
103
  # Creates a data set within the project
@@ -167,44 +111,119 @@ module GoodData
167
111
  builder = block.call(Model::SchemaBuilder.new(schema_def))
168
112
  builder.to_schema
169
113
  else
170
- sch = {:title => schema_def, :columns => columns} if columns
114
+ sch = { :title => schema_def, :columns => columns } if columns
171
115
  sch = Model::Schema.new schema_def if schema_def.is_a? Hash
172
116
  sch = schema_def if schema_def.is_a?(Model::Schema)
173
- raise ArgumentError.new('Required either schema object or title plus columns array') unless schema_def.is_a? Model::Schema
117
+ fail(ArgumentError, 'Required either schema object or title plus columns array') unless schema_def.is_a? Model::Schema
174
118
  sch
175
119
  end
176
120
  Model.add_schema(schema, self)
177
121
  end
178
122
 
179
- def add_metric(options={})
180
- expression = options[:expression] || fail('Metric has to have its expression defined')
123
+ def add_metric(options = {})
124
+ options[:expression] || fail('Metric has to have its expression defined')
181
125
  m1 = GoodData::Metric.create(options)
182
126
  m1.save
183
127
  end
184
128
 
185
- def add_report(options={})
129
+ def add_report(options = {})
186
130
  rep = GoodData::Report.create(options)
187
131
  rep.save
188
132
  end
189
133
 
190
- def add_dashboard(options={})
191
- dash = GoodData::Dashboard.create(options)
192
- dash.save
134
+ # Gets author of project
135
+ #
136
+ # @return [String] Project author
137
+ def author
138
+ # TODO: Return object instead
139
+ @json['project']['meta']['author']
193
140
  end
194
141
 
142
+ # Adds user to project
143
+ #
144
+ # TODO: Discuss with @fluke777 if is not #invite sufficient
195
145
  def add_user(email_address, domain)
196
- raise 'Not implemented'
146
+ fail 'Not implemented'
197
147
  end
198
148
 
199
- def upload(file, schema, mode = 'FULL')
200
- schema.upload file, self, mode
149
+ # Returns web interface URI of project
150
+ #
151
+ # @return [String] Project URL
152
+ def browser_uri(options = {})
153
+ grey = options[:grey]
154
+ if grey
155
+ GoodData.connection.url + uri
156
+ else
157
+ GoodData.connection.url + '#s=' + uri
158
+ end
201
159
  end
202
160
 
203
- def slis
204
- link = "#{data['links']['metadata']}#{SLIS_PATH}"
161
+ # Clones project
162
+ #
163
+ # @return [GoodData::Project] Newly created project
164
+ def clone(options = {})
165
+ # TODO: Refactor so if export or import fails the new_project will be cleaned
166
+ with_data = options[:data] || true
167
+ with_users = options[:users] || false
168
+ title = options[:title] || "Clone of #{title}"
169
+
170
+ # Create the project first so we know that it is passing. What most likely is wrong is the tokena and the export actaully takes majoiryt of the time
171
+ new_project = GoodData::Project.create(options.merge(:title => title))
172
+
173
+ export = {
174
+ :exportProject => {
175
+ :exportUsers => with_users ? 1 : 0,
176
+ :exportData => with_data ? 1 : 0
177
+ }
178
+ }
205
179
 
206
- # TODO: Review what to do with passed extra argument
207
- Metadata.new GoodData.get(link)
180
+ result = GoodData.post("/gdc/md/#{obj_id}/maintenance/export", export)
181
+ export_token = result['exportArtifact']['token']
182
+ status_url = result['exportArtifact']['status']['uri']
183
+
184
+ state = GoodData.get(status_url)['taskState']['status']
185
+ while state == 'RUNNING'
186
+ sleep 5
187
+ result = GoodData.get(status_url)
188
+ state = result['taskState']['status']
189
+ end
190
+
191
+ import = {
192
+ :importProject => {
193
+ :token => export_token
194
+ }
195
+ }
196
+
197
+ result = GoodData.post("/gdc/md/#{new_project.obj_id}/maintenance/import", import)
198
+ status_url = result['uri']
199
+ state = GoodData.get(status_url)['taskState']['status']
200
+ while state == 'RUNNING'
201
+ sleep 5
202
+ result = GoodData.get(status_url)
203
+ state = result['taskState']['status']
204
+ end
205
+ new_project
206
+ end
207
+
208
+ # Project contributor
209
+ #
210
+ # @return [String] Project contributor
211
+ # TODO: Return as object
212
+ def contributor
213
+ # TODO: Return object instead
214
+ @json['project']['meta']['contributor']
215
+ end
216
+
217
+ # Gets the date when created
218
+ #
219
+ # @return [DateTime] Date time when created
220
+ def created
221
+ DateTime.parse(@json['meta']['created'])
222
+ end
223
+
224
+ # Gets ruby wrapped raw project JSON data
225
+ def data
226
+ raw_data['project']
208
227
  end
209
228
 
210
229
  def datasets
@@ -215,67 +234,318 @@ module GoodData
215
234
  end
216
235
  end
217
236
 
218
- def raw_data
219
- @json
237
+ # Deletes project
238
+ def delete
239
+ fail "Project '#{title}' with id #{uri} is already deleted" if state == :deleted
240
+ GoodData.delete(uri)
220
241
  end
221
242
 
222
- def data
223
- raw_data['project']
243
+ # Deletes dashboards for project
244
+ def delete_dashboards
245
+ Dashboard.all.map { |data| Dashboard[data['link']] }.each { |d| d.delete }
224
246
  end
225
247
 
248
+ # Gets project role by its identifier
249
+ #
250
+ # @param [String] role_name Title of role to look for
251
+ # @return [GoodData::ProjectRole] Project role if found
252
+ def get_role_by_identifier(role_name)
253
+ tmp = roles
254
+ tmp.each do |role|
255
+ return role if role.identifier.downcase == role_name.downcase
256
+ end
257
+ nil
258
+ end
259
+
260
+ # Gets project role byt its summary
261
+ #
262
+ # @param [String] role_summary Summary of role to look for
263
+ # @return [GoodData::ProjectRole] Project role if found
264
+ def get_role_by_summary(role_summary)
265
+ tmp = roles
266
+ tmp.each do |role|
267
+ return role if role.summary.downcase == role_summary.downcase
268
+ end
269
+ nil
270
+ end
271
+
272
+ # Gets project role by its name
273
+ #
274
+ # @param [String] role_title Title of role to look for
275
+ # @return [GoodData::ProjectRole] Project role if found
276
+ def get_role_by_title(role_title)
277
+ tmp = roles
278
+ tmp.each do |role|
279
+ return role if role.title.downcase == role_title.downcase
280
+ end
281
+ nil
282
+ end
283
+
284
+ # Initializes object instance from raw wire JSON
285
+ #
286
+ # @param json Json used for initialization
287
+ def initialize(json)
288
+ @json = json
289
+ end
290
+
291
+ # Invites new user to project
292
+ #
293
+ # @param email [String] User to be invited
294
+ # @param role [String] Role URL or Role ID to be used
295
+ # @param msg [String] Optional invite message
296
+ #
297
+ # TODO: Return invite object
298
+ def invite(email, role, msg = DEFAULT_INVITE_MESSAGE)
299
+ puts "Inviting #{email}, role: #{role}"
300
+
301
+ role_url = nil
302
+ if role.index('/gdc/') != 0
303
+ tmp = get_role_by_identifier(role)
304
+ tmp = get_role_by_title(role) if tmp.nil?
305
+ role_url = tmp['url'] if tmp
306
+ else
307
+ role_url = role if role_url.nil?
308
+ end
309
+
310
+ data = {
311
+ :invitations => [
312
+ {
313
+ :invitation => {
314
+ :content => {
315
+ :email => email,
316
+ :role => role_url,
317
+ :action => {
318
+ :setMessage => msg
319
+ }
320
+ }
321
+ }
322
+ }
323
+ ]
324
+ }
325
+
326
+ url = "/gdc/projects/#{pid}/invitations"
327
+ GoodData.post(url, data)
328
+ end
329
+
330
+ # Returns invitations to project
331
+ #
332
+ # @return [Array<GoodData::Invitation>] List of invitations
333
+ def invitations
334
+ res = []
335
+
336
+ tmp = GoodData.get @json['project']['links']['invitations']
337
+ tmp['invitations'].each do |invitation|
338
+ res << GoodData::Invitation.new(invitation)
339
+ end
340
+
341
+ res
342
+ end
343
+
344
+ # Returns project related links
345
+ #
346
+ # @return [Hash] Project related links
226
347
  def links
227
348
  data['links']
228
349
  end
229
350
 
230
- def to_json
231
- raw_data.to_json
351
+ def md
352
+ @md ||= Links.new GoodData.get(data['links']['metadata'])
232
353
  end
233
354
 
234
- def is_project?
235
- true
355
+ # Gets raw resource ID
356
+ #
357
+ # @return [String] Raw resource ID
358
+ def obj_id
359
+ uri.split('/').last
236
360
  end
237
361
 
238
- def partial_md_export(objects, options={})
362
+ alias_method :pid, :obj_id
363
+
364
+ def partial_md_export(objects, options = {})
239
365
  # TODO: refactor polling to md_polling in client
240
366
 
241
- fail "Nothing to migrate. You have to pass list of objects, ids or uris that you would like to migrate" if objects.nil? || objects.empty?
367
+ fail 'Nothing to migrate. You have to pass list of objects, ids or uris that you would like to migrate' if objects.nil? || objects.empty?
368
+ fail 'The objects to migrate has to be provided as an array' unless objects.is_a?(Array)
369
+
242
370
  target_project = options[:project]
243
- fail "You have to provide a project instance or project pid to migrate to" if target_project.nil?
371
+ fail 'You have to provide a project instance or project pid to migrate to' if target_project.nil?
244
372
  target_project = GoodData::Project[target_project]
245
- objects = objects.map {|obj| GoodData::MdObject[obj]}
246
- GoodData.logging_on
373
+ objects = objects.map { |obj| GoodData::MdObject[obj] }
247
374
  export_payload = {
248
- :partialMDExport => {
249
- :uris => objects.map {|obj| obj.uri}
250
- }
375
+ :partialMDExport => {
376
+ :uris => objects.map { |obj| obj.uri }
377
+ }
251
378
  }
252
379
  result = GoodData.post("#{GoodData.project.md['maintenance']}/partialmdexport", export_payload)
253
- polling_url = result["partialMDArtifact"]["status"]["uri"]
254
- token = result["partialMDArtifact"]["token"]
380
+ polling_url = result['partialMDArtifact']['status']['uri']
381
+ token = result['partialMDArtifact']['token']
255
382
 
256
- polling_result = GoodData.get(polling_url)
257
- while polling_result["wTaskStatus"]["poll"]["status"] == "RUNNING"
258
- polling_result = GoodData.get(polling_url)
259
- end
260
- fail "Exporting objects failed" if polling_result["wTaskStatus"]["poll"]["status"] == "ERROR"
383
+ polling_result = GoodData.wait_for_polling_result(polling_url)
384
+
385
+ fail 'Exporting objects failed' if polling_result['wTaskStatus']['status'] == 'ERROR'
261
386
 
262
387
  import_payload = {
263
- :partialMDImport => {
264
- :token => token,
265
- :overwriteNewer => "1",
266
- :updateLDMObjects => "0"
388
+ :partialMDImport => {
389
+ :token => token,
390
+ :overwriteNewer => '1',
391
+ :updateLDMObjects => '0'
267
392
  }
268
393
  }
269
394
 
270
395
  result = GoodData.post("#{target_project.md['maintenance']}/partialmdimport", import_payload)
271
- polling_uri = result["uri"]
272
- polling_result = GoodData.get(polling_url)
273
- while polling_result["wTaskStatus"]["poll"]["status"] == "RUNNING"
274
- polling_result = GoodData.get(polling_url)
396
+ polling_url = result['uri']
397
+ polling_result = GoodData.wait_for_polling_result(polling_url)
398
+
399
+ fail 'Exporting objects failed' if polling_result['wTaskStatus']['status'] == 'ERROR'
400
+ end
401
+
402
+ alias_method :transfer_objects, :partial_md_export
403
+
404
+ # Checks if this object instance is project
405
+ #
406
+ # @return [Boolean] Return true for all instances
407
+ def project?
408
+ true
409
+ end
410
+
411
+ # Forces project to reload
412
+ def reload!
413
+ if saved?
414
+ response = GoodData.get(uri)
415
+ @json = response
275
416
  end
276
- fail "Exporting objects failed" if polling_result["wTaskStatus"]["poll"]["status"] == "ERROR"
417
+ self
418
+ end
419
+
420
+ # Gets the list or project roles
421
+ #
422
+ # @return [Array<GoodData::ProjectRole>] List of roles
423
+ def roles
424
+ url = "/gdc/projects/#{pid}/roles"
277
425
 
426
+ res = []
427
+
428
+ tmp = GoodData.get(url)
429
+ tmp['projectRoles']['roles'].each do |role_url|
430
+ json = GoodData.get role_url
431
+ res << GoodData::ProjectRole.new(json)
432
+ end
433
+
434
+ res
435
+ end
436
+
437
+ # Saves project
438
+ def save
439
+ response = GoodData.post PROJECTS_PATH, raw_data
440
+ if uri.nil?
441
+ response = GoodData.get response['uri']
442
+ @json = response
443
+ end
444
+ end
445
+
446
+ # Checks if is project saved
447
+ #
448
+ # @return [Boolean] True if saved, false if not
449
+ def saved?
450
+ res = uri.nil?
451
+ !res
452
+ end
453
+
454
+ # Gets project schedules
455
+ #
456
+ # @return [Array<GoodData::Schedule>] List of schedules
457
+ def schedules
458
+ res = []
459
+ tmp = GoodData.get @json['project']['links']['schedules']
460
+ tmp['schedules']['items'].each do |schedule|
461
+ res << GoodData::Schedule.new(schedule)
462
+ end
463
+ res
464
+ end
465
+
466
+ # Gets SLIs data
467
+ #
468
+ # @return [GoodData::Metadata] SLI Metadata
469
+ def slis
470
+ link = "#{data['links']['metadata']}#{SLIS_PATH}"
471
+
472
+ # TODO: Review what to do with passed extra argument
473
+ Metadata.new GoodData.get(link)
474
+ end
475
+
476
+ # Gets project state
477
+ #
478
+ # @return [String] Project state
479
+ def state
480
+ data['content']['state'].downcase.to_sym if data['content'] && data['content']['state']
481
+ end
482
+
483
+ # Gets project summary
484
+ #
485
+ # @return [String] Project summary
486
+ def summary
487
+ data['meta']['summary'] if data['meta']
488
+ end
489
+
490
+ # Gets project title
491
+ #
492
+ # @return [String] Project title
493
+ def title
494
+ data['meta']['title'] if data['meta']
495
+ end
496
+
497
+ # Gets project update date
498
+ #
499
+ # @return [DateTime] Date time of last update
500
+ def updated
501
+ DateTime.parse(@json['meta']['updated'])
502
+ end
503
+
504
+ # Uploads file to project
505
+ #
506
+ # @param file File to be uploaded
507
+ # @param schema Schema to be used
508
+ def upload(file, schema, mode = 'FULL')
509
+ schema.upload file, self, mode
510
+ end
511
+
512
+ def uri
513
+ data['links']['self'] if data && data['links'] && data['links']['self']
514
+ end
515
+
516
+ # List of users in project
517
+ #
518
+ # @return [Array<GoodData::User>] List of users
519
+ def users
520
+ res = []
521
+
522
+ tmp = GoodData.get @json['project']['links']['users']
523
+ tmp['users'].map do |user|
524
+ res << GoodData::User.new(user)
525
+ end
526
+
527
+ res
528
+ end
529
+
530
+ # Run validation on project
531
+ # Valid settins for validation are (default all):
532
+ # ldm - Checks the consistency of LDM objects.
533
+ # pdm Checks LDM to PDM mapping consistency, also checks PDM reference integrity.
534
+ # metric_filter - Checks metadata for inconsistent metric filters.
535
+ # invalid_objects - Checks metadata for invalid/corrupted objects.
536
+ # asyncTask response
537
+ def validate(filters = %w(ldm, pdm, metric_filter, invalid_objects))
538
+ response = GoodData.post "#{GoodData.project.md['validate-project']}", 'validateProject' => filters
539
+ polling_link = response['asyncTask']['link']['poll']
540
+ polling_result = GoodData.get(polling_link)
541
+ while polling_result['wTaskStatus'] && polling_result['wTaskStatus']['status'] == 'RUNNING'
542
+ sleep(3)
543
+ polling_result = GoodData.get(polling_link)
544
+ end
545
+ polling_result
278
546
  end
279
547
 
548
+ alias_method :to_json, :json
549
+ alias_method :raw_data, :json
280
550
  end
281
551
  end