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,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 < FasterCSV::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 = FasterCSV::Table.new([GoodData::Row.new([],[],false)])
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 = FasterCSV::Table.new(sf_data.collect do |line|
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
- FasterCSV::Table.new(table.transpose.map {|line| GoodData::Row.new([], line.map {|item| item || ' '}, false)})
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,7 @@
1
+ module GoodData
2
+ class DisplayForm < GoodData::MdObject
3
+
4
+ root_key :attributeDisplayForm
5
+
6
+ end
7
+ 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((GoodData.get uri).values[0]) unless uri.nil?
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
- raise "Project '#{title}' with id #{uri} is already deleted" if state == :deleted
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
- @json['links']
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
- @json['meta']
103
+ data['meta']
72
104
  end
73
105
 
74
106
  def content
75
- @json['content']
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
- end
124
+ def to_json
125
+ @json.to_json
126
+ end
93
127
 
94
- class DataSet < MdObject
95
- SLI_CTG = 'singleloadinterface'
96
- DS_SLI_CTG = 'dataset-singleloadinterface'
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 sli_enabled?
99
- content['mode'] == 'SLI'
153
+ def ==(other)
154
+ other.uri == uri
100
155
  end
101
156
 
102
- def sli
103
- raise NoProjectError.new "Connect to a project before searching for an object" unless GoodData.project
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