gooddata 0.5.16 → 0.6.0.pre2

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 (40) hide show
  1. data/Gemfile +2 -0
  2. data/bin/gooddata +291 -8
  3. data/gooddata.gemspec +14 -5
  4. data/lib/gooddata/client.rb +34 -5
  5. data/lib/gooddata/commands/api.rb +27 -30
  6. data/lib/gooddata/commands/process.rb +137 -0
  7. data/lib/gooddata/commands/profile.rb +5 -5
  8. data/lib/gooddata/commands/projects.rb +107 -40
  9. data/lib/gooddata/commands/runners.rb +37 -0
  10. data/lib/gooddata/commands/scaffold.rb +30 -0
  11. data/lib/gooddata/connection.rb +31 -19
  12. data/lib/gooddata/extract.rb +1 -1
  13. data/lib/gooddata/goodzilla/goodzilla.rb +40 -0
  14. data/lib/gooddata/model.rb +418 -138
  15. data/lib/gooddata/models/attribute.rb +24 -0
  16. data/lib/gooddata/models/dashboard.rb +60 -0
  17. data/lib/gooddata/models/data_result.rb +4 -6
  18. data/lib/gooddata/models/data_set.rb +20 -0
  19. data/lib/gooddata/models/display_form.rb +7 -0
  20. data/lib/gooddata/models/fact.rb +17 -0
  21. data/lib/gooddata/models/metadata.rb +69 -17
  22. data/lib/gooddata/models/metric.rb +90 -0
  23. data/lib/gooddata/models/process.rb +112 -0
  24. data/lib/gooddata/models/profile.rb +1 -1
  25. data/lib/gooddata/models/project.rb +85 -29
  26. data/lib/gooddata/models/report.rb +45 -0
  27. data/lib/gooddata/models/report_definition.rb +139 -0
  28. data/lib/gooddata/version.rb +1 -1
  29. data/lib/templates/bricks/brick.rb.erb +7 -0
  30. data/lib/templates/bricks/main.rb.erb +4 -0
  31. data/spec/goodzilla_spec.rb +57 -0
  32. data/spec/model_dsl_spec.rb +22 -0
  33. data/test/test_commands.rb +1 -1
  34. data/test/test_model.rb +6 -6
  35. metadata +137 -16
  36. data/bin/igd.rb +0 -33
  37. data/lib/gooddata/command.rb +0 -75
  38. data/lib/gooddata/commands/help.rb +0 -104
  39. data/lib/gooddata/commands/version.rb +0 -7
  40. data/test/helper.rb +0 -13
@@ -0,0 +1,137 @@
1
+ module GoodData::Command
2
+ class Process
3
+
4
+ def self.list(options={})
5
+ # with project usage
6
+ processes = GoodData::Process[:all]
7
+ end
8
+
9
+ def self.get(options={})
10
+ id = options[:process_id]
11
+ fail "Unspecified process id" if id.nil?
12
+ GoodData::Process[id]
13
+ end
14
+
15
+ def self.with_deploy(dir, options={}, &block)
16
+ verbose = options[:verbose] || false
17
+ if block
18
+ begin
19
+ res = deploy_graph(dir, options)
20
+ block.call(res)
21
+ ensure
22
+ # self_link = res["process"]["links"]["self"]
23
+ # GoodData.delete(self_link)
24
+ end
25
+ else
26
+ deploy_graph(dir, options)
27
+ end
28
+ end
29
+
30
+ def self.deploy_graph(dir, options={})
31
+ dir = Pathname(dir) || fail("Directory is not specified")
32
+ fail "\"#{dir}\" is not a directory" unless dir.directory?
33
+ project_id = options[:project_id] || fail("Project Id has to be specified")
34
+
35
+
36
+ type = options[:type] || fail("Type of deployment is not specified")
37
+ deploy_name = "AAAAA"
38
+ verbose = options[:verbose] || false
39
+ project_pid = options[:project_pid]
40
+ puts HighLine::color("Deploying #{dir}", HighLine::BOLD) if verbose
41
+ res = nil
42
+
43
+ Tempfile.open("deploy-graph-archive") do |temp|
44
+
45
+ Zip::OutputStream.open(temp.path) do |zio|
46
+ Dir.glob(dir + "**/*") do |item|
47
+ puts "including #{item}" if verbose
48
+ unless File.directory?(item)
49
+ zio.put_next_entry(item)
50
+ zio.print IO.read(item)
51
+ end
52
+ end
53
+ end
54
+
55
+ GoodData.connection.upload(temp.path)
56
+ process_id = options[:process]
57
+
58
+ data = {
59
+ :process => {
60
+ :name => deploy_name,
61
+ :path => "/uploads/#{File.basename(temp.path)}",
62
+ :type => type
63
+ }
64
+ }
65
+ res = if process_id.nil?
66
+ GoodData.post("/gdc/projects/#{project_pid}/dataload/processes", data)
67
+ else
68
+ GoodData.put("/gdc/projects/#{project_pid}/dataload/processes/#{process_id}", data)
69
+ end
70
+ end
71
+ puts HighLine::color("Deploy DONE #{dir}", HighLine::BOLD) if verbose
72
+ res
73
+ end
74
+
75
+ def self.execute_process(link, dir, options={})
76
+ dir = Pathname(dir)
77
+ type = :ruby
78
+ if type == :ruby
79
+ result = GoodData.post(link, {
80
+ :execution => {
81
+ :graph => dir + "main.rb",
82
+ :params => {}
83
+ }
84
+ })
85
+ begin
86
+ GoodData.poll(result, "executionTask")
87
+ rescue RestClient::RequestFailed => e
88
+
89
+ ensure
90
+ result = GoodData.get(result["executionTask"]["links"]["detail"])
91
+ if result["executionDetail"]["status"] == "ERROR"
92
+ fail "Runing process failed. You can look at a log here #{result["executionDetail"]["logFileName"]}"
93
+ end
94
+ end
95
+ result
96
+ else
97
+ result = GoodData.post(link, {
98
+ :execution => {
99
+ :graph => dir + "graphs/main.grf",
100
+ :params => {}
101
+ }
102
+ })
103
+ begin
104
+ GoodData.poll(result, "executionTask")
105
+ rescue RestClient::RequestFailed => e
106
+
107
+ ensure
108
+ result = GoodData.get(result["executionTask"]["links"]["detail"])
109
+ if result["executionDetail"]["status"] == "ERROR"
110
+ fail "Runing process failed. You can look at a log here #{result["executionDetail"]["logFileName"]}"
111
+ end
112
+ end
113
+ result
114
+ end
115
+ end
116
+
117
+ def self.run(dir, options={})
118
+ email = options[:email]
119
+ verbose = options[:v]
120
+
121
+ dir = Pathname(dir)
122
+
123
+ with_deploy(dir, options.merge(:name => "Temporary deploy[#{dir}][#{options[:project_name]}]")) do |deploy_response|
124
+ puts HighLine::color("Executing", HighLine::BOLD) if verbose
125
+ # if email.nil?
126
+ # result = execute_process(deploy_response["process"]["links"]["executions"], dir, options)
127
+ # else
128
+ # create_email_channel(options) do |channel_response|
129
+ # subscribe_on_finish(:success, channel_response, deploy_response, options)
130
+ result = execute_process(deploy_response["process"]["links"]["executions"], dir)
131
+ # end
132
+ # end
133
+ end
134
+ end
135
+
136
+ end
137
+ end
@@ -1,9 +1,9 @@
1
1
  module GoodData::Command
2
- class Profile < Base
3
- def show
4
- connect
5
- pp GoodData.profile.to_json
2
+ class Profile
3
+ class << self
4
+ def show
5
+ GoodData.profile.to_json
6
+ end
6
7
  end
7
- alias :index :show
8
8
  end
9
9
  end
@@ -1,51 +1,118 @@
1
- module GoodData
2
- module Command
3
- class Projects < Base
4
- def list
5
- connect
6
- Project.all.each do |project|
7
- puts "%s %s" % [project.uri, project.title]
8
- end
9
- end
10
- alias :index :list
1
+ module GoodData::Command
2
+ class Projects
3
+
4
+ def self.list
5
+ GoodData::Project.all
6
+ end
11
7
 
12
- def create
13
- connect
8
+ def self.create(options={})
9
+ title = options[:title]
10
+ summary = options[:summary]
11
+ template = options[:template]
12
+ token = options[:token]
14
13
 
15
- title = ask "Project name"
16
- summary = ask "Project summary"
17
- template = ask "Project template", :default => ''
14
+ GoodData::Project.create(:title => title, :summary => summary, :template => template, :auth_token => token)
15
+ end
18
16
 
19
- project = Project.create :title => title, :summary => summary, :template => template
17
+ def self.show(id)
18
+ GoodData::Project[id]
19
+ end
20
20
 
21
- puts "Project '#{project.title}' with id #{project.uri} created successfully!"
22
- end
21
+ def self.clone(project_id, options)
22
+ with_data = options[:with_data]
23
+ with_users = options[:with_users]
24
+ title = options[:title]
25
+
26
+ export = {
27
+ :exportProject => {
28
+ :exportUsers => with_users ? 1 : 0,
29
+ :exportData => with_data ? 1 : 0
30
+ }
31
+ }
32
+
33
+ result = GoodData.post("/gdc/md/#{project_id}/maintenance/export", export)
34
+ export_token = result["exportArtifact"]["token"]
35
+ status_url = result["exportArtifact"]["status"]["uri"]
23
36
 
24
- def show
25
- id = args.shift rescue nil
26
- raise(CommandFailed, "Specify the project key you wish to show.") if id.nil?
27
- connect
28
- pp Project[id].to_json
37
+ state = GoodData.get(status_url)["taskState"]["status"]
38
+ while state == "RUNNING"
39
+ sleep 5
40
+ result = GoodData.get(status_url)
41
+ state = result["taskState"]["status"]
29
42
  end
30
43
 
31
- def delete
32
- raise(CommandFailed, "Specify the project key(s) for the project(s) you wish to delete.") if args.size == 0
33
- connect
34
- while args.size > 0
35
- id = args.shift
36
- project = Project[id]
37
- ask "Do you want to delete the project '#{project.title}' with id #{project.uri}", :answers => %w(y n) do |answer|
38
- case answer
39
- when 'y' then
40
- puts "Deleting #{project.title}..."
41
- project.delete
42
- puts "Project '#{project.title}' with id #{project.uri} deleted successfully!"
43
- when 'n' then
44
- puts "Aborting..."
45
- end
46
- end
47
- end
44
+ old_project = GoodData::Project[project_id]
45
+ project_uri = self.create(options.merge({:title => "Clone of #{old_project.title}"}))
46
+ new_project = GoodData::Project[project_uri]
47
+
48
+ import = {
49
+ :importProject => {
50
+ :token => export_token
51
+ }
52
+ }
53
+ result = GoodData.post("/gdc/md/#{new_project.obj_id}/maintenance/import", import)
54
+ status_url = result["uri"]
55
+ state = GoodData.get(status_url)["taskState"]["status"]
56
+ while state == "RUNNING"
57
+ sleep 5
58
+ result = GoodData.get(status_url)
59
+ state = result["taskState"]["status"]
48
60
  end
61
+ true
62
+ end
63
+
64
+ def self.delete(project_id)
65
+ p = GoodData::Project[project_id]
66
+ p.delete
49
67
  end
68
+
50
69
  end
51
70
  end
71
+
72
+ # module GoodData
73
+ # module Command
74
+ # class Projects
75
+ # class << self
76
+ # def list
77
+ # Project.all
78
+ # end
79
+ # alias :index :list
80
+ #
81
+ # def create
82
+ # title = ask "Project name"
83
+ # summary = ask "Project summary"
84
+ # template = ask "Project template", :default => ''
85
+ #
86
+ # project = Project.create :title => title, :summary => summary, :template => template
87
+ #
88
+ # puts "Project '#{project.title}' with id #{project.uri} created successfully!"
89
+ # end
90
+ #
91
+ # def show
92
+ # id = args.shift rescue nil
93
+ # raise(CommandFailed, "Specify the project key you wish to show.") if id.nil?
94
+ # connect
95
+ # pp Project[id].to_json
96
+ # end
97
+ #
98
+ # def delete
99
+ # raise(CommandFailed, "Specify the project key(s) for the project(s) you wish to delete.") if args.size == 0
100
+ # connect
101
+ # while args.size > 0
102
+ # id = args.shift
103
+ # project = Project[id]
104
+ # ask "Do you want to delete the project '#{project.title}' with id #{project.uri}", :answers => %w(y n) do |answer|
105
+ # case answer
106
+ # when 'y' then
107
+ # puts "Deleting #{project.title}..."
108
+ # project.delete
109
+ # puts "Project '#{project.title}' with id #{project.uri} deleted successfully!"
110
+ # when 'n' then
111
+ # puts "Aborting..."
112
+ # end
113
+ # end
114
+ # end
115
+ # end
116
+ # end
117
+ # end
118
+ # end
@@ -0,0 +1,37 @@
1
+ module GoodData::Command
2
+ class Runners
3
+
4
+ def self.run_ruby(brick_dir, options={})
5
+ pid = options[:project]
6
+ fail "You have to specify a project ID" if pid.nil?
7
+ fail "You have to specify directory of the brick ran" if brick_dir.nil?
8
+
9
+ params = if options[:params]
10
+ JSON.parse(File.read(options[:params]), :symbolize_names => true)
11
+ else
12
+ {}
13
+ end
14
+
15
+ GoodData.connection.connect!
16
+ sst = GoodData.connection.cookies[:cookies]["GDCAuthSST"]
17
+
18
+ logger_stream = STDOUT
19
+ script_body = <<-script_body
20
+ require 'fileutils'
21
+ FileUtils::cd(\"#{brick_dir}\") do\
22
+ require 'bundler/setup'
23
+ eval(File.read(\"main.rb\")).call({
24
+ :gdc_sst => \"#{sst}\",
25
+ :gdc_project => \"#{pid}\"
26
+ }.merge(#{params}))
27
+ end
28
+ script_body
29
+
30
+
31
+ Bundler.with_clean_env do
32
+ system("ruby", "-e", script_body)
33
+ end
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,30 @@
1
+ module GoodData::Command
2
+ class Scaffold
3
+ class << self
4
+
5
+ def brick(name)
6
+
7
+ require 'erubis'
8
+ require 'fileutils'
9
+
10
+ templates_path = Pathname(__FILE__) + "../../../templates"
11
+
12
+ FileUtils.mkdir(name)
13
+ FileUtils.cd(name) do
14
+ input = File.read(templates_path + 'bricks/brick.rb.erb')
15
+ eruby = Erubis::Eruby.new(input)
16
+ File.open("brick.rb", 'w') do |f|
17
+ f.write(eruby.result())
18
+ end
19
+
20
+ input = File.read(templates_path + 'bricks/main.rb.erb')
21
+ eruby = Erubis::Eruby.new(input)
22
+ File.open("main.rb", 'w') do |f|
23
+ f.write(eruby.result())
24
+ end
25
+ end
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -34,7 +34,7 @@ module GoodData
34
34
  TOKEN_PATH = '/gdc/account/token'
35
35
  STAGE_PATH = '/uploads/'
36
36
 
37
- attr_reader(:auth_token)
37
+ attr_reader(:auth_token, :url)
38
38
  attr_accessor :status
39
39
 
40
40
 
@@ -70,22 +70,16 @@ module GoodData
70
70
  #
71
71
  # * +username+ - The GoodData account username
72
72
  # * +password+ - The GoodData account password
73
- def initialize(username, password, url = nil, options = {})
73
+ def initialize(username, password, options = {})
74
74
  @status = :not_connected
75
75
  @username = username
76
76
  @password = password
77
- @url = url || DEFAULT_URL
77
+ @url = options[:server] || DEFAULT_URL
78
78
  @auth_token = options.delete(:auth_token)
79
79
  @options = options
80
80
 
81
- @server = RestClient::Resource.new @url,
82
- :timeout => @options[:timeout],
83
- :headers => {
84
- :content_type => :json,
85
- :accept => [ :json, :zip ],
86
- :user_agent => GoodData.gem_version_string,
87
- }
88
-
81
+ @server = create_server_connection(@url, @options)
82
+
89
83
  end
90
84
 
91
85
  # Returns the user JSON object of the currently logged in GoodData user account.
@@ -106,7 +100,7 @@ module GoodData
106
100
  #
107
101
  # Connection.new(username, password).get '/gdc/projects'
108
102
  def get(path, options = {})
109
- GoodData.logger.debug "GET #{path}"
103
+ GoodData.logger.debug "GET #{@server}#{path}"
110
104
  ensure_connection
111
105
  b = Proc.new { @server[path].get cookies }
112
106
  process_response(options, &b)
@@ -126,7 +120,7 @@ module GoodData
126
120
  # Connection.new(username, password).post '/gdc/projects', { ... }
127
121
  def post(path, data, options = {})
128
122
  payload = data.is_a?(Hash) ? data.to_json : data
129
- GoodData.logger.debug "POST #{path}, payload: #{payload}"
123
+ GoodData.logger.debug "POST #{@server}#{path}, payload: #{payload}"
130
124
  ensure_connection
131
125
  b = Proc.new { @server[path].post payload, cookies }
132
126
  process_response(options, &b)
@@ -146,7 +140,7 @@ module GoodData
146
140
  # Connection.new(username, password).put '/gdc/projects', { ... }
147
141
  def put(path, data, options = {})
148
142
  payload = data.is_a?(Hash) ? data.to_json : data
149
- GoodData.logger.debug "PUT #{path}, payload: #{payload}"
143
+ GoodData.logger.debug "PUT #{@server}#{path}, payload: #{payload}"
150
144
  ensure_connection
151
145
  b = Proc.new { @server[path].put payload, cookies }
152
146
  process_response(options, &b)
@@ -164,7 +158,7 @@ module GoodData
164
158
  #
165
159
  # Connection.new(username, password).delete '/gdc/project/1'
166
160
  def delete(path, options = {})
167
- GoodData.logger.debug "DELETE #{path}"
161
+ GoodData.logger.debug "DELETE #{@server}#{path}"
168
162
  ensure_connection
169
163
  b = Proc.new { @server[path].delete cookies }
170
164
  process_response(options, &b)
@@ -187,6 +181,11 @@ module GoodData
187
181
  @status == :logged_in
188
182
  end
189
183
 
184
+ def url=(url=nil)
185
+ @url = url || DEFAULT_URL
186
+ @server = create_server_connection(@url, @options)
187
+ end
188
+
190
189
  # The connection will automatically be established once it's needed, which it
191
190
  # usually is when either the user, get, post or delete method is called. If you
192
191
  # want to force a connection (or a re-connect) you can use this method.
@@ -197,11 +196,11 @@ module GoodData
197
196
  # Uploads a file to GoodData server
198
197
  # /uploads/ resources are special in that they use a different
199
198
  # host and a basic authentication.
200
- def upload(file, dir = nil)
199
+ def upload(file, dir = nil, options={})
201
200
  ensure_connection
202
201
  # We should have followed a link. If it was correct.
202
+
203
203
  stage_url = @options[:webdav_server] || @url.sub(/\./, '-di.')
204
-
205
204
  # Make a directory, if needed
206
205
  if dir then
207
206
  url = stage_url + STAGE_PATH + dir + '/'
@@ -239,17 +238,20 @@ module GoodData
239
238
  dir = "."
240
239
  end
241
240
 
241
+ payload = options[:stream] ? "file" : File.read(file)
242
+ filename = options[:filename] || options[:stream] ? "randome-filename.txt" : File.basename(file)
243
+
242
244
  # Upload the file
243
245
  RestClient::Request.execute(
244
246
  :method => :put,
245
- :url => stage_url + STAGE_PATH + dir + '/' + File.basename(file),
247
+ :url => stage_url + STAGE_PATH + dir + '/' + filename,
246
248
  :user => @username,
247
249
  :password => @password,
248
250
  :timeout => @options[:timeout],
249
251
  :headers => {
250
252
  :user_agent => GoodData.gem_version_string,
251
253
  },
252
- :payload => File.read(file)
254
+ :payload => payload
253
255
  )
254
256
  end
255
257
 
@@ -271,6 +273,16 @@ module GoodData
271
273
 
272
274
  private
273
275
 
276
+ def create_server_connection(url, options)
277
+ RestClient::Resource.new url,
278
+ :timeout => options[:timeout],
279
+ :headers => {
280
+ :content_type => :json,
281
+ :accept => [ :json, :zip ],
282
+ :user_agent => GoodData.gem_version_string,
283
+ }
284
+ end
285
+
274
286
  def ensure_connection
275
287
  connect if @status == :not_connected
276
288
  end