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.
- data/Gemfile +2 -0
- data/bin/gooddata +291 -8
- data/gooddata.gemspec +14 -5
- data/lib/gooddata/client.rb +34 -5
- data/lib/gooddata/commands/api.rb +27 -30
- data/lib/gooddata/commands/process.rb +137 -0
- data/lib/gooddata/commands/profile.rb +5 -5
- data/lib/gooddata/commands/projects.rb +107 -40
- data/lib/gooddata/commands/runners.rb +37 -0
- data/lib/gooddata/commands/scaffold.rb +30 -0
- data/lib/gooddata/connection.rb +31 -19
- data/lib/gooddata/extract.rb +1 -1
- data/lib/gooddata/goodzilla/goodzilla.rb +40 -0
- data/lib/gooddata/model.rb +418 -138
- data/lib/gooddata/models/attribute.rb +24 -0
- data/lib/gooddata/models/dashboard.rb +60 -0
- data/lib/gooddata/models/data_result.rb +4 -6
- data/lib/gooddata/models/data_set.rb +20 -0
- data/lib/gooddata/models/display_form.rb +7 -0
- data/lib/gooddata/models/fact.rb +17 -0
- data/lib/gooddata/models/metadata.rb +69 -17
- data/lib/gooddata/models/metric.rb +90 -0
- data/lib/gooddata/models/process.rb +112 -0
- data/lib/gooddata/models/profile.rb +1 -1
- data/lib/gooddata/models/project.rb +85 -29
- data/lib/gooddata/models/report.rb +45 -0
- data/lib/gooddata/models/report_definition.rb +139 -0
- data/lib/gooddata/version.rb +1 -1
- data/lib/templates/bricks/brick.rb.erb +7 -0
- data/lib/templates/bricks/main.rb.erb +4 -0
- data/spec/goodzilla_spec.rb +57 -0
- data/spec/model_dsl_spec.rb +22 -0
- data/test/test_commands.rb +1 -1
- data/test/test_model.rb +6 -6
- metadata +137 -16
- data/bin/igd.rb +0 -33
- data/lib/gooddata/command.rb +0 -75
- data/lib/gooddata/commands/help.rb +0 -104
- data/lib/gooddata/commands/version.rb +0 -7
- 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,51 +1,118 @@
|
|
1
|
-
module GoodData
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
-
|
13
|
-
|
8
|
+
def self.create(options={})
|
9
|
+
title = options[:title]
|
10
|
+
summary = options[:summary]
|
11
|
+
template = options[:template]
|
12
|
+
token = options[:token]
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
template = ask "Project template", :default => ''
|
14
|
+
GoodData::Project.create(:title => title, :summary => summary, :template => template, :auth_token => token)
|
15
|
+
end
|
18
16
|
|
19
|
-
|
17
|
+
def self.show(id)
|
18
|
+
GoodData::Project[id]
|
19
|
+
end
|
20
20
|
|
21
|
-
|
22
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
data/lib/gooddata/connection.rb
CHANGED
@@ -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,
|
73
|
+
def initialize(username, password, options = {})
|
74
74
|
@status = :not_connected
|
75
75
|
@username = username
|
76
76
|
@password = password
|
77
|
-
@url =
|
77
|
+
@url = options[:server] || DEFAULT_URL
|
78
78
|
@auth_token = options.delete(:auth_token)
|
79
79
|
@options = options
|
80
80
|
|
81
|
-
@server =
|
82
|
-
|
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 + '/' +
|
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 =>
|
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
|