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,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