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,24 @@
|
|
1
|
+
module GoodData
|
2
|
+
class Attribute < GoodData::MdObject
|
3
|
+
|
4
|
+
root_key :attribute
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def [](id)
|
8
|
+
if id == :all
|
9
|
+
GoodData.get(GoodData.project.md['query'] + '/attributes/')['query']['entries']
|
10
|
+
else
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def display_forms
|
17
|
+
content["displayForms"].map {|df| GoodData::DisplayForm[df["meta"]["uri"]]}
|
18
|
+
end
|
19
|
+
|
20
|
+
def is_attribute?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module GoodData
|
2
2
|
class Dashboard < GoodData::MdObject
|
3
3
|
|
4
|
+
root_key :projectDashboard
|
5
|
+
|
4
6
|
class << self
|
5
7
|
def [](id)
|
6
8
|
if id == :all
|
@@ -8,7 +10,65 @@ module GoodData
|
|
8
10
|
else
|
9
11
|
super
|
10
12
|
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_report_tab(tab)
|
17
|
+
title = tab[:title]
|
18
|
+
{
|
19
|
+
:title => title,
|
20
|
+
:items => tab[:items].map {|i| GoodData::Dashboard.create_report_tab_item(i)}
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_report_tab_item(options={})
|
25
|
+
title = options[:title]
|
26
|
+
|
27
|
+
report = GoodData::Report.find_first_by_title(title)
|
28
|
+
{
|
29
|
+
:reportItem => {
|
30
|
+
:obj => report.uri,
|
31
|
+
:sizeY => options[:size_y] || 200,
|
32
|
+
:sizeX => options[:size_x] || 300,
|
33
|
+
:style => {
|
34
|
+
:displayTitle => 1,
|
35
|
+
:background => {
|
36
|
+
:opacity => 0
|
37
|
+
}
|
38
|
+
},
|
39
|
+
:visualization => {
|
40
|
+
:grid => {
|
41
|
+
:columnWidths => []
|
42
|
+
},
|
43
|
+
:oneNumber => {
|
44
|
+
:labels => {}
|
45
|
+
}
|
46
|
+
},
|
47
|
+
:positionY => options[:position_y] || 0,
|
48
|
+
:filters => [],
|
49
|
+
:positionX => options[:position_x] || 0
|
50
|
+
}
|
51
|
+
}
|
11
52
|
end
|
53
|
+
|
54
|
+
def create(options={})
|
55
|
+
binding.pry
|
56
|
+
stuff = {
|
57
|
+
"projectDashboard" => {
|
58
|
+
"content" => {
|
59
|
+
"tabs" => options[:tabs].map {|t| GoodData::Dashboard.create_report_tab(t)},
|
60
|
+
"filters" => []
|
61
|
+
},
|
62
|
+
"meta" => {
|
63
|
+
"tags" => options[:tags],
|
64
|
+
"summary" => options[:summary],
|
65
|
+
"title" => options[:title]
|
66
|
+
}
|
67
|
+
}
|
68
|
+
}
|
69
|
+
Dashboard.new(stuff)
|
70
|
+
end
|
71
|
+
|
12
72
|
end
|
13
73
|
|
14
74
|
def export(format, options={})
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module GoodData
|
2
2
|
|
3
|
-
class Row <
|
3
|
+
class Row < CSV::Row
|
4
4
|
def ==(other)
|
5
5
|
len = length()
|
6
6
|
return false if len != other.length
|
@@ -72,7 +72,7 @@ module GoodData
|
|
72
72
|
end
|
73
73
|
|
74
74
|
def assemble_table
|
75
|
-
@table =
|
75
|
+
@table = CSV::Table.new([GoodData::Row.new([],[],false)])
|
76
76
|
end
|
77
77
|
|
78
78
|
def to_table
|
@@ -117,7 +117,7 @@ module GoodData
|
|
117
117
|
else
|
118
118
|
@headers = sf_data.first.keys - [:type, :Id]
|
119
119
|
end
|
120
|
-
@table =
|
120
|
+
@table = CSV::Table.new(sf_data.collect do |line|
|
121
121
|
GoodData::Row.new([], @headers.map {|h| line[h] || ' '}, false)
|
122
122
|
end)
|
123
123
|
rescue
|
@@ -179,7 +179,7 @@ module GoodData
|
|
179
179
|
end
|
180
180
|
|
181
181
|
def to_table
|
182
|
-
|
182
|
+
CSV::Table.new(table.transpose.map {|line| GoodData::Row.new([], line.map {|item| item || ' '}, false)})
|
183
183
|
end
|
184
184
|
|
185
185
|
def == (otherDataResult)
|
@@ -187,8 +187,6 @@ module GoodData
|
|
187
187
|
csv_table = to_table
|
188
188
|
len = csv_table.length
|
189
189
|
return false if len != otherDataResult.to_table.length
|
190
|
-
|
191
|
-
|
192
190
|
result
|
193
191
|
end
|
194
192
|
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module GoodData
|
2
|
+
class DataSet < MdObject
|
3
|
+
|
4
|
+
root_key :dataSet
|
5
|
+
|
6
|
+
SLI_CTG = 'singleloadinterface'
|
7
|
+
DS_SLI_CTG = 'dataset-singleloadinterface'
|
8
|
+
|
9
|
+
def sli_enabled?
|
10
|
+
content['mode'] == 'SLI'
|
11
|
+
end
|
12
|
+
|
13
|
+
def sli
|
14
|
+
raise NoProjectError.new "Connect to a project before searching for an object" unless GoodData.project
|
15
|
+
slis = GoodData.project.md.links(Model::LDM_CTG).links(SLI_CTG)[DS_SLI_CTG]
|
16
|
+
uri = slis[identifier]['link']
|
17
|
+
MdObject[uri]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module GoodData
|
2
|
+
class Fact < GoodData::MdObject
|
3
|
+
|
4
|
+
root_key :fact
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def [](id)
|
8
|
+
if id == :all
|
9
|
+
GoodData.get(GoodData.project.md['query'] + '/facts/')['query']['entries']
|
10
|
+
else
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -6,6 +6,10 @@ module GoodData
|
|
6
6
|
IDENTIFIERS_CFG = 'instance-identifiers'
|
7
7
|
|
8
8
|
class << self
|
9
|
+
def root_key(a_key)
|
10
|
+
define_method :root_key, Proc.new { a_key.to_s}
|
11
|
+
end
|
12
|
+
|
9
13
|
def [](id)
|
10
14
|
raise "Cannot search for nil #{self.class}" unless id
|
11
15
|
uri = if id.is_a? Integer or id =~ /^\d+$/
|
@@ -17,7 +21,16 @@ module GoodData
|
|
17
21
|
else
|
18
22
|
raise "Unexpected object id format: expected numeric ID, identifier with no slashes or an URI starting with a slash"
|
19
23
|
end
|
20
|
-
self.new(
|
24
|
+
self.new(GoodData.get uri) unless uri.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_by_tag(tag)
|
28
|
+
self[:all].find_all {|r| r["tags"].split(",").include?(tag)}
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_first_by_title(title)
|
32
|
+
item = self[:all].find {|r| r["title"] == title}
|
33
|
+
self[item["link"]]
|
21
34
|
end
|
22
35
|
|
23
36
|
private
|
@@ -39,8 +52,7 @@ module GoodData
|
|
39
52
|
end
|
40
53
|
|
41
54
|
def delete
|
42
|
-
|
43
|
-
GoodData.delete @json['links']['self']
|
55
|
+
GoodData.delete(uri)
|
44
56
|
end
|
45
57
|
|
46
58
|
def obj_id
|
@@ -48,13 +60,17 @@ module GoodData
|
|
48
60
|
end
|
49
61
|
|
50
62
|
def links
|
51
|
-
|
63
|
+
data['links']
|
52
64
|
end
|
53
65
|
|
54
66
|
def uri
|
55
67
|
meta['uri']
|
56
68
|
end
|
57
69
|
|
70
|
+
def browser_uri
|
71
|
+
GoodData.connection.url + meta['uri']
|
72
|
+
end
|
73
|
+
|
58
74
|
def identifier
|
59
75
|
meta['identifier']
|
60
76
|
end
|
@@ -67,12 +83,28 @@ module GoodData
|
|
67
83
|
meta['summary']
|
68
84
|
end
|
69
85
|
|
86
|
+
def title=(a_title)
|
87
|
+
data["meta"]["title"] = a_title
|
88
|
+
end
|
89
|
+
|
90
|
+
def summary=(a_summary)
|
91
|
+
data["meta"]["summary"] = a_summary
|
92
|
+
end
|
93
|
+
|
94
|
+
def tags
|
95
|
+
data["meta"]["tags"]
|
96
|
+
end
|
97
|
+
|
98
|
+
def tags=(list_of_tags)
|
99
|
+
data["meta"]["tags"] = tags
|
100
|
+
end
|
101
|
+
|
70
102
|
def meta
|
71
|
-
|
103
|
+
data['meta']
|
72
104
|
end
|
73
105
|
|
74
106
|
def content
|
75
|
-
|
107
|
+
data['content']
|
76
108
|
end
|
77
109
|
|
78
110
|
def project
|
@@ -89,21 +121,41 @@ module GoodData
|
|
89
121
|
result["entries"]
|
90
122
|
end
|
91
123
|
|
92
|
-
|
124
|
+
def to_json
|
125
|
+
@json.to_json
|
126
|
+
end
|
93
127
|
|
94
|
-
|
95
|
-
|
96
|
-
|
128
|
+
def raw_data
|
129
|
+
@json
|
130
|
+
end
|
131
|
+
|
132
|
+
def data
|
133
|
+
raw_data[root_key]
|
134
|
+
end
|
135
|
+
|
136
|
+
def saved?
|
137
|
+
!!uri
|
138
|
+
end
|
139
|
+
|
140
|
+
def save
|
141
|
+
fail("Validation failed") unless validate
|
142
|
+
|
143
|
+
if saved?
|
144
|
+
GoodData.put(uri, to_json)
|
145
|
+
else
|
146
|
+
result = GoodData.post(GoodData.project.md['obj'], to_json)
|
147
|
+
saved_object = self.class[result["uri"]]
|
148
|
+
@json = saved_object.raw_data
|
149
|
+
end
|
150
|
+
self
|
151
|
+
end
|
97
152
|
|
98
|
-
def
|
99
|
-
|
153
|
+
def ==(other)
|
154
|
+
other.uri == uri
|
100
155
|
end
|
101
156
|
|
102
|
-
def
|
103
|
-
|
104
|
-
slis = GoodData.project.md.links(Model::LDM_CTG).links(SLI_CTG)[DS_SLI_CTG]
|
105
|
-
uri = slis[identifier]['link']
|
106
|
-
MdObject[uri]
|
157
|
+
def validate
|
158
|
+
true
|
107
159
|
end
|
108
160
|
end
|
109
161
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'gooddata/goodzilla/goodzilla'
|
2
|
+
|
3
|
+
module GoodData
|
4
|
+
class Metric < GoodData::MdObject
|
5
|
+
|
6
|
+
root_key :metric
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def [](id)
|
10
|
+
if id == :all
|
11
|
+
GoodData.get(GoodData.project.md['query'] + '/metrics/')['query']['entries']
|
12
|
+
else
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def xcreate(options)
|
18
|
+
if options.is_a?(String)
|
19
|
+
create({:expression => options, :extended_notation => true})
|
20
|
+
else
|
21
|
+
create(options.merge({:extended_notation => true}))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def create(options={})
|
26
|
+
if options.is_a?(String)
|
27
|
+
expression = options
|
28
|
+
extended_notation = true
|
29
|
+
title = nil
|
30
|
+
else
|
31
|
+
title = options[:title]
|
32
|
+
summary = options[:summary]
|
33
|
+
expression = options[:expression] || fail("Metric has to have its expression defined")
|
34
|
+
extended_notation = options[:extended_notation]
|
35
|
+
end
|
36
|
+
|
37
|
+
expression = if extended_notation
|
38
|
+
dict = {
|
39
|
+
:facts => GoodData::Fact[:all].reduce({}) {|memo, item| memo[item["title"]] = item["link"]; memo},
|
40
|
+
:attributes => GoodData::Attribute[:all].reduce({}) {|memo, item| memo[item["title"]] = item["link"]; memo},
|
41
|
+
:metrics => GoodData::Metric[:all].reduce({}) {|memo, item| memo[item["title"]] = item["link"]; memo},
|
42
|
+
}
|
43
|
+
interpolated_metric = GoodData::SmallGoodZilla.interpolate_metric(expression, dict)
|
44
|
+
interpolated_metric
|
45
|
+
else
|
46
|
+
expression
|
47
|
+
end
|
48
|
+
|
49
|
+
Metric.new({
|
50
|
+
"metric" => {
|
51
|
+
"content" => {
|
52
|
+
"format" => "#,##0",
|
53
|
+
"expression" => expression
|
54
|
+
},
|
55
|
+
"meta" => {
|
56
|
+
"tags" => "",
|
57
|
+
"summary" => summary,
|
58
|
+
"title" => title,
|
59
|
+
}
|
60
|
+
}
|
61
|
+
})
|
62
|
+
end
|
63
|
+
|
64
|
+
def execute(expression, options={})
|
65
|
+
m = GoodData::Metric.create({:title => "Temporary metric to be deleted", :expression => expression}.merge(options))
|
66
|
+
m.execute
|
67
|
+
end
|
68
|
+
|
69
|
+
def xexecute(expression)
|
70
|
+
execute(expression, {:extended_notation => true})
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
def execute
|
76
|
+
res = GoodData::ReportDefinition.execute(:left => self)
|
77
|
+
res.to_table[0][0]
|
78
|
+
end
|
79
|
+
|
80
|
+
def validate
|
81
|
+
fail "Meric needs to have title" if title.nil?
|
82
|
+
true
|
83
|
+
end
|
84
|
+
|
85
|
+
def is_metric?
|
86
|
+
true
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'pry'
|
2
|
+
require 'highline'
|
3
|
+
|
4
|
+
module GoodData
|
5
|
+
class Process
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def [](id)
|
9
|
+
if id == :all
|
10
|
+
GoodData.get("/gdc/projects/hi95siviyangv53c3vptkz1eas546pnn/dataload/processes")
|
11
|
+
else
|
12
|
+
self.new(GoodData.get("/gdc/projects/hi95siviyangv53c3vptkz1eas546pnn/dataload/processes/#{id}"))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def deploy(dir, options={}, &block)
|
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 deploy_graph(dir, options={})
|
31
|
+
dir = Pathname(dir)
|
32
|
+
fail "Provided path (#{dir}) is not directory." unless dir.directory?
|
33
|
+
type = options[:type] || "ETL"
|
34
|
+
|
35
|
+
deploy_name = options[:name] || options[:project_name]
|
36
|
+
verbose = options[:verbose] || false
|
37
|
+
project_pid = 'hi95siviyangv53c3vptkz1eas546pnn'
|
38
|
+
|
39
|
+
puts HighLine::color("Deploying #{dir}", HighLine::BOLD) if verbose
|
40
|
+
res = nil
|
41
|
+
|
42
|
+
Tempfile.open("deploy-graph-archive") do |temp|
|
43
|
+
Zip::OutputStream.open(temp.path) do |zio|
|
44
|
+
Dir.glob(dir + "**/*") do |item|
|
45
|
+
puts "including #{item}" if verbose
|
46
|
+
unless File.directory?(item)
|
47
|
+
zio.put_next_entry(item)
|
48
|
+
zio.print IO.read(item)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
GoodData.connection.upload(temp.path)
|
54
|
+
process_id = options[:process]
|
55
|
+
|
56
|
+
data = {
|
57
|
+
:process => {
|
58
|
+
:name => deploy_name,
|
59
|
+
:path => "/uploads/#{File.basename(temp.path)}",
|
60
|
+
:type => type
|
61
|
+
}
|
62
|
+
}
|
63
|
+
res = if process_id.nil?
|
64
|
+
GoodData.post("/gdc/projects/#{project_pid}/dataload/processes", data)
|
65
|
+
else
|
66
|
+
GoodData.put("/gdc/projects/#{project_pid}/dataload/processes/#{process_id}", data)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
puts HighLine::color("Deploy DONE #{dir}", HighLine::BOLD) if verbose
|
70
|
+
binding.pry
|
71
|
+
res
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def initialize(data)
|
76
|
+
@data = data
|
77
|
+
end
|
78
|
+
|
79
|
+
def links
|
80
|
+
@data["process"]["links"]
|
81
|
+
end
|
82
|
+
|
83
|
+
def link
|
84
|
+
links["self"]
|
85
|
+
end
|
86
|
+
|
87
|
+
def executions_link
|
88
|
+
links["executions"]
|
89
|
+
end
|
90
|
+
|
91
|
+
def execute_process(graph, options={})
|
92
|
+
result = GoodData.post(executions_link, {
|
93
|
+
:execution => {
|
94
|
+
:graph => graph,
|
95
|
+
:params => {}
|
96
|
+
}
|
97
|
+
})
|
98
|
+
begin
|
99
|
+
GoodData.poll(result, "executionTask")
|
100
|
+
rescue RestClient::RequestFailed => e
|
101
|
+
|
102
|
+
ensure
|
103
|
+
result = GoodData.get(result["executionTask"]["links"]["detail"])
|
104
|
+
if result["executionDetail"]["status"] == "ERROR"
|
105
|
+
fail "Runing process failed. You can look at a log here #{result["executionDetail"]["logFileName"]}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
result
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|