gooddata 0.5.16 → 0.6.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
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