gooddata 0.6.0.pre9 → 0.6.0.pre10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. data/.gitignore +3 -0
  2. data/.rspec +3 -0
  3. data/.yardopts +19 -0
  4. data/Gemfile +5 -0
  5. data/README.md +191 -0
  6. data/Rakefile +18 -1
  7. data/bin/gooddata +74 -63
  8. data/doc/.gitignore +1 -0
  9. data/doc/css/.gitkeepme +1 -0
  10. data/doc/images/.gitkeepme +1 -0
  11. data/doc/images/background.png +0 -0
  12. data/doc/images/bg-callout-button.png +0 -0
  13. data/doc/images/header-logo.png +0 -0
  14. data/doc/images/logo-image.png +0 -0
  15. data/doc/js/.gitkeepme +1 -0
  16. data/doc/pages/GET_STARTED.md +309 -0
  17. data/doc/pages/HOMEPAGE.md +75 -0
  18. data/doc/pages/TUTORIALS.md +52 -0
  19. data/doc/pages/tutorial/BRICKS.md +257 -0
  20. data/doc/pages/tutorial/CREATING_A_MODEL.md +79 -0
  21. data/doc/pages/tutorial/CRUNCHING_NUMBERS.md +233 -0
  22. data/doc/pages/tutorial/TEST_DRIVEN_DEVELOPMENT.md +118 -0
  23. data/doc/pages/tutorial/YOUR_FIRST_PROJECT.md +52 -0
  24. data/doc/templates/default/class/dot/setup.rb +6 -0
  25. data/doc/templates/default/class/dot/superklass.erb +3 -0
  26. data/doc/templates/default/class/setup.rb +36 -0
  27. data/doc/templates/default/class/text/setup.rb +11 -0
  28. data/doc/templates/default/class/text/subclasses.erb +5 -0
  29. data/doc/templates/default/constant/text/header.erb +11 -0
  30. data/doc/templates/default/constant/text/setup.rb +3 -0
  31. data/doc/templates/default/docstring/setup.rb +51 -0
  32. data/doc/templates/default/docstring/text/abstract.erb +2 -0
  33. data/doc/templates/default/docstring/text/deprecated.erb +2 -0
  34. data/doc/templates/default/docstring/text/index.erb +2 -0
  35. data/doc/templates/default/docstring/text/note.erb +4 -0
  36. data/doc/templates/default/docstring/text/private.erb +2 -0
  37. data/doc/templates/default/docstring/text/returns_void.erb +1 -0
  38. data/doc/templates/default/docstring/text/text.erb +1 -0
  39. data/doc/templates/default/docstring/text/todo.erb +4 -0
  40. data/doc/templates/default/layout/dot/header.erb +6 -0
  41. data/doc/templates/default/layout/dot/setup.rb +14 -0
  42. data/doc/templates/default/method/setup.rb +3 -0
  43. data/doc/templates/default/method/text/header.erb +1 -0
  44. data/doc/templates/default/method_details/setup.rb +10 -0
  45. data/doc/templates/default/method_details/text/header.erb +10 -0
  46. data/doc/templates/default/method_details/text/method_signature.erb +12 -0
  47. data/doc/templates/default/method_details/text/setup.rb +10 -0
  48. data/doc/templates/default/module/dot/child.erb +1 -0
  49. data/doc/templates/default/module/dot/dependencies.erb +3 -0
  50. data/doc/templates/default/module/dot/header.erb +6 -0
  51. data/doc/templates/default/module/dot/info.erb +14 -0
  52. data/doc/templates/default/module/dot/setup.rb +14 -0
  53. data/doc/templates/default/module/setup.rb +164 -0
  54. data/doc/templates/default/module/text/children.erb +10 -0
  55. data/doc/templates/default/module/text/class_meths_list.erb +8 -0
  56. data/doc/templates/default/module/text/extends.erb +8 -0
  57. data/doc/templates/default/module/text/header.erb +7 -0
  58. data/doc/templates/default/module/text/includes.erb +8 -0
  59. data/doc/templates/default/module/text/instance_meths_list.erb +8 -0
  60. data/doc/templates/default/module/text/setup.rb +12 -0
  61. data/doc/templates/default/root/dot/child.erb +3 -0
  62. data/doc/templates/default/root/dot/setup.rb +5 -0
  63. data/doc/templates/default/tags/setup.rb +55 -0
  64. data/doc/templates/default/tags/text/example.erb +12 -0
  65. data/doc/templates/default/tags/text/index.erb +1 -0
  66. data/doc/templates/default/tags/text/option.erb +20 -0
  67. data/doc/templates/default/tags/text/overload.erb +19 -0
  68. data/doc/templates/default/tags/text/see.erb +11 -0
  69. data/doc/templates/default/tags/text/tag.erb +13 -0
  70. data/examples.rb +2 -2
  71. data/gooddata.gemspec +31 -26
  72. data/lib/gooddata/bricks/middleware/gooddata_middleware.rb +1 -1
  73. data/lib/gooddata/client.rb +65 -53
  74. data/lib/gooddata/commands/commands.rb +9 -0
  75. data/lib/gooddata/commands/process.rb +9 -8
  76. data/lib/gooddata/commands/projects.rb +29 -0
  77. data/lib/gooddata/commands/runners.rb +1 -1
  78. data/lib/gooddata/connection.rb +6 -4
  79. data/lib/gooddata/exceptions.rb +2 -1
  80. data/lib/gooddata/helpers.rb +1 -1
  81. data/lib/gooddata/model.rb +360 -189
  82. data/lib/gooddata/models/metadata.rb +1 -1
  83. data/lib/gooddata/models/metric.rb +2 -1
  84. data/lib/gooddata/models/project.rb +1 -1
  85. data/lib/gooddata/models/report.rb +0 -18
  86. data/lib/gooddata/version.rb +1 -1
  87. data/spec/blueprint_spec.rb +83 -43
  88. data/spec/data/additional_dataset_module.json +18 -0
  89. data/spec/data/blueprint_invalid.json +36 -0
  90. data/spec/data/blueprint_valid.json +37 -0
  91. data/spec/data/model_module.json +18 -0
  92. data/spec/{test_project_model_spec.json → data/test_project_model_spec.json} +4 -0
  93. data/spec/full_project_spec.rb +4 -3
  94. data/spec/helpers/blueprint_helper.rb +17 -0
  95. data/spec/merging_blueprints_spec.rb +23 -48
  96. data/spec/model_dsl_spec.rb +2 -2
  97. data/spec/model_spec.rb +44 -0
  98. data/spec/project_build_and_update_spec.rb +28 -0
  99. data/spec/spec_helper.rb +6 -0
  100. data/yard-server.sh +3 -0
  101. metadata +251 -74
  102. data/README.rdoc +0 -176
@@ -0,0 +1,118 @@
1
+ Test driven development is a holy grail for many developers. It gives you an additional sense of security and you can rely on the test suite to give you a safety net when you are refactoring and tweaking existing code. Testing reports was hard until now.
2
+
3
+ ## The model
4
+ Let's reuse the model that we have from model. Create a file called model.rb and put this inside.
5
+
6
+ GoodData::Model::ProjectBuilder.create("gooddata-ruby test #{Time.now.to_i}") do |p|
7
+ p.add_date_dimension("closed_date")
8
+
9
+ p.add_dataset("users") do |d|
10
+ d.add_anchor("id")
11
+ d.add_label("name", :reference => 'id')
12
+ end
13
+
14
+ p.add_dataset("regions") do |d|
15
+ d.add_anchor("id")
16
+ d.add_attribute("name")
17
+ end
18
+
19
+ p.add_dataset("opportunities") do |d|
20
+ d.add_fact("amount")
21
+ d.add_date("closed_date", :dataset => "closed_date")
22
+ d.add_reference("user_id", :dataset => 'users', :reference => 'id')
23
+ d.add_reference("region_id", :dataset => 'regions', :reference => 'id')
24
+ end
25
+
26
+ p.add_metric({
27
+ "title": "Sum Amount",
28
+ "expression": "SELECT SUM(#\"amount\") + 1",
29
+ "extended_notation": true
30
+ })
31
+
32
+ p.upload([["id", "name"],
33
+ ["1", "Tomas"],
34
+ ["2", "Petr"]], :dataset => 'users')
35
+
36
+ p.upload([["id", "name"],
37
+ ["1", "Tomas"],
38
+ ["2", "Petr"]], :dataset => 'regions')
39
+ end
40
+
41
+ ## The test
42
+ Let's say that you have some not so simple metric and you want to test it so you make sure it works as expected. The easiest way to do it is create a testing project and prepare some made up data and then spin it. You already know how to create a model and load data let's talk about how to define test cases.
43
+
44
+ The trick is to use assert_report helper. This means that the report will be executed and if the result will be different it will fail. The helper takes 2 parameters. First is a report definition second is the result expected. Currently it stops on the first failure but this will change soon and we will run all the tests and collect the results.
45
+
46
+ p.assert_report({:top => [{:type => :metric, :title => "Sum Amount"}]}, [["3"]])
47
+
48
+
49
+ Go ahead and test it out
50
+
51
+ gooddata --username joe@example.com --password my_secret_pass --token my_token project build model.rb
52
+
53
+
54
+ ## Production
55
+
56
+ The way we have it set up right now nothing forces us to use the same metrics to build the report. This is a problem. Ideally we wanna make sure that the same report in test project is then used in production and if somebody changes the project because the business requirements changes we want the same report to be used in the test and if something fails we want to know.
57
+
58
+ This is easiest to achieve by extracting the metrics and the model to separate file that can later be used when describing the project. You can then reference the same file form your production and testing project and make sure they are build using the same source.
59
+
60
+ The description might look something like this
61
+
62
+ GoodData::Model::ProjectBuilder.create("gooddata-ruby test #{Time.now.to_i}") do |p|
63
+ p.add_date_dimension("closed_date")
64
+
65
+ p.add_dataset("users") do |d|
66
+ d.add_anchor("id")
67
+ d.add_label("name", :reference => 'id')
68
+ end
69
+
70
+ p.add_dataset("regions") do |d|
71
+ d.add_anchor("id")
72
+ d.add_attribute("name")
73
+ end
74
+
75
+ p.add_dataset("opportunities") do |d|
76
+ d.add_fact("amount")
77
+ d.add_date("closed_date", :dataset => "closed_date")
78
+ d.add_reference("user_id", :dataset => 'users', :reference => 'id')
79
+ d.add_reference("region_id", :dataset => 'regions', :reference => 'id')
80
+ end
81
+
82
+ p.load_metrics('https://bit.ly/gd_demo_1_metrics')
83
+
84
+ p.upload([["id", "name"],
85
+ ["1", "Tomas"],
86
+ ["2", "Petr"]], :dataset => 'users')
87
+
88
+ p.upload([["id", "name"],
89
+ ["1", "Tomas"],
90
+ ["2", "Petr"]], :dataset => 'regions')
91
+
92
+ p.upload([["amount", "closed_date", "user_id", "region_id"],
93
+ ["1", "2001/01/01", "1", "1"],
94
+ ["1", "2001/01/01", "1", "2"]], :dataset => 'opportunities')
95
+
96
+ p.assert_report({:top => [{:type => :metric, :title => "Sum Amount"}]}, [["3"]])
97
+
98
+ end
99
+
100
+ Here we are externalizing only the metrics but you can hopefully see that it might make sense to do it for the model as well.
101
+
102
+ # Rinse repeat
103
+ If you have any experience with TDD you know that your tests have to run daily to have any effect. This is TBD :-)
104
+
105
+ <div class="section-nav">
106
+ <div class="left align-right">
107
+ <a href="/docs/file/doc/pages/tutorial/CRUNCHING_NUMBERS.md" class="prev">
108
+ Back
109
+ </a>
110
+ </div>
111
+ <div class="right align-left">
112
+ <a href="/docs/file/doc/pages/tutorial/BRICKS.md" class="next">
113
+ Next
114
+ </a>
115
+ </div>
116
+ <div class="clear"></div>
117
+ </div>
118
+
@@ -0,0 +1,52 @@
1
+
2
+ Ok welcome. Let's spin up an example project that we created so you can explore and see SDK in action. It is super simple. Since you are probably a developer we created simple project about developers.
3
+
4
+ ###What we want to measure
5
+ Imagine you have a small dev shop. You have couple of developers. They crank out code. You also have couple of repositories for products. You want to measure how many lines of code each of the devs create. you wanna be able to track it by time by repository and by person. You want to see how many lines of code they committed.
6
+
7
+ ###Model
8
+ This is how the model looks.
9
+
10
+ ![Model](https://dl.dropboxusercontent.com/s/1y97ziv5anmpn9s/gooddata_devs_demo_model.png?token_hash=AAENC89d8XOfCr9AnyQCrd9vwfhb-bDuYcORQ0AIRP2RQQ)
11
+
12
+ ###Spinning it up
13
+ Let's do this. I assume you have gooddata SDK installed and working. Run
14
+
15
+ gooddata scaffold project my_test_project
16
+
17
+ go to the directory
18
+
19
+ cd my_test_project
20
+
21
+ and build project
22
+
23
+ gooddata -U username -P pass -t token project build
24
+
25
+ If everything goes ok it will give you a PID also called a project_id. Open the my_test_project directory in your favorite text editor and open file called Goodfile. It should look like this
26
+
27
+ {
28
+ "model" : "./model/model.rb",
29
+ "project_id" : ""
30
+ }
31
+
32
+ Put your freshly acquired pid into an empty slot after "project_id". It should look like this.
33
+
34
+ {
35
+ "model" : "./model/model.rb",
36
+ "project_id" : "HERE_COMES_YOUR NEW_TOKEN"
37
+ }
38
+
39
+ You are done. If you go to [https://secure.gooddata.com/projects.html](https://secure.gooddata.com/projects.html) you should be able to see your new project. Also locally you are ready for other tutorials
40
+
41
+
42
+ <div class="section-nav">
43
+ <div class="left align-right">
44
+ <span class="prev disabled">Back</span>
45
+ </div>
46
+
47
+ <div class="right align-left">
48
+ <a href="/docs/file/doc/pages/tutorial/CREATING_A_MODEL.md" class="next">Next</a>
49
+ </div>
50
+
51
+ <div class="clear"></div>
52
+ </div>
@@ -0,0 +1,6 @@
1
+ include T('default/module/dot')
2
+
3
+ def init
4
+ super
5
+ sections.push :superklass
6
+ end
@@ -0,0 +1,3 @@
1
+ <% if object.superclass.path != "Object" && object.superclass.path != "BasicObject" %>
2
+ <%= format_path object %> -> <%= format_path object.superclass %>;
3
+ <% end %>
@@ -0,0 +1,36 @@
1
+ include T('default/module')
2
+
3
+ def init
4
+ super
5
+ sections.place(:subclasses).before(:children)
6
+ sections.place(:constructor_details, [T('method_details')]).before(:methodmissing)
7
+ end
8
+
9
+ def constructor_details
10
+ ctors = object.meths(:inherited => true, :included => true)
11
+ return unless @ctor = ctors.find {|o| o.constructor? }
12
+ return if prune_method_listing([@ctor]).empty?
13
+ erb(:constructor_details)
14
+ end
15
+
16
+ def subclasses
17
+ return if object.path == "Object" # don't show subclasses for Object
18
+ unless globals.subclasses
19
+ globals.subclasses = {}
20
+ list = run_verifier Registry.all(:class)
21
+ list.each do |o|
22
+ (globals.subclasses[o.superclass.path] ||= []) << o if o.superclass
23
+ end
24
+ end
25
+
26
+ @subclasses = globals.subclasses[object.path]
27
+ return if @subclasses.nil? || @subclasses.empty?
28
+ @subclasses = @subclasses.sort_by {|o| o.path }.map do |child|
29
+ name = child.path
30
+ if object.namespace
31
+ name = object.relative_path(child)
32
+ end
33
+ [name, child]
34
+ end
35
+ erb(:subclasses)
36
+ end
@@ -0,0 +1,11 @@
1
+ include T('default/module/text')
2
+
3
+ def init
4
+ super
5
+ sections.place(:subclasses).before(:children)
6
+ sections.delete(:children)
7
+ end
8
+
9
+ def format_object_title(object)
10
+ "Class: #{object.title} < #{object.superclass.title}"
11
+ end
@@ -0,0 +1,5 @@
1
+ Direct Known Subclasses:
2
+ ------------------------
3
+
4
+ <%= indent(wrap(@subclasses.map {|name, child| name }.join(", "))) %>
5
+
@@ -0,0 +1,11 @@
1
+ <%= title_align_right format_object_title(object) %>
2
+
3
+
4
+ <%= object.name %> = <%= object.value.size > 40 ? "\n\n" + indent(object.value) : object.value %>
5
+
6
+
7
+ <%= hr %>
8
+
9
+ <%= yieldall %>
10
+
11
+ <%= hr %>
@@ -0,0 +1,3 @@
1
+ def init
2
+ sections :header, [T('docstring')]
3
+ end
@@ -0,0 +1,51 @@
1
+ def init
2
+ return if object.docstring.blank? && !object.has_tag?(:api)
3
+ sections :index, [:private, :deprecated, :abstract, :todo, :note, :returns_void, :text], T('tags')
4
+ end
5
+
6
+ def private
7
+ return unless object.has_tag?(:api) && object.tag(:api).text == 'private'
8
+ erb(:private)
9
+ end
10
+
11
+ def abstract
12
+ return unless object.has_tag?(:abstract)
13
+ erb(:abstract)
14
+ end
15
+
16
+ def deprecated
17
+ return unless object.has_tag?(:deprecated)
18
+ erb(:deprecated)
19
+ end
20
+
21
+ def todo
22
+ return unless object.has_tag?(:todo)
23
+ erb(:todo)
24
+ end
25
+
26
+ def note
27
+ return unless object.has_tag?(:note)
28
+ erb(:note)
29
+ end
30
+
31
+ def returns_void
32
+ return unless object.type == :method
33
+ return if object.name == :initialize && object.scope == :instance
34
+ return unless object.tags(:return).size == 1 && object.tag(:return).types == ['void']
35
+ erb(:returns_void)
36
+ end
37
+
38
+ def docstring_text
39
+ text = ""
40
+ unless object.tags(:overload).size == 1 && object.docstring.empty?
41
+ text = object.docstring
42
+ end
43
+
44
+ if text.strip.empty? && object.tags(:return).size == 1 && object.tag(:return).text
45
+ text = object.tag(:return).text.gsub(/\A([a-z])/) {|x| x.downcase }
46
+ text = "Returns #{text}" unless text.empty? || text =~ /^\s*return/i
47
+ text = text.gsub(/\A([a-z])/) {|x| x.upcase }
48
+ end
49
+
50
+ text.strip
51
+ end
@@ -0,0 +1,2 @@
1
+ <%= indent(wrap("Abstract. #{object.tag(:abstract).text}")) %>
2
+
@@ -0,0 +1,2 @@
1
+ <%= indent(wrap("Deprecated. #{object.tag(:deprecated).text}")) %>
2
+
@@ -0,0 +1,2 @@
1
+
2
+ <%= yieldall %>
@@ -0,0 +1,4 @@
1
+ <% object.tags(:note).each do |tag| %>
2
+ <%= indent(wrap("Note: #{tag.text}")) %>
3
+
4
+ <% end %>
@@ -0,0 +1,2 @@
1
+ <%= indent(wrap("This #{object.type} is part of a private API.")) %>
2
+
@@ -0,0 +1 @@
1
+ <%= indent(wrap("This method returns an undefined value.")) %>
@@ -0,0 +1 @@
1
+ <%= indent wrap(h(docstring_text.strip), 68) %>
@@ -0,0 +1,4 @@
1
+ <% object.tags(:todo).each do |tag| %>
2
+ <%= indent(wrap("TODO: #{tag.text}")) %>
3
+
4
+ <% end %>
@@ -0,0 +1,6 @@
1
+ digraph yard {
2
+ graph [rankdir=BT rank=sink outputMode=nodesfirst packMode="graph" splines=true];
3
+ node [shape=record rank=sink rankType=sink];
4
+
5
+ <%= yieldall %>
6
+ }
@@ -0,0 +1,14 @@
1
+ attr_reader :contents
2
+
3
+ def init
4
+ if object
5
+ type = object.root? ? :module : object.type
6
+ sections :header, [T(type)]
7
+ else
8
+ sections :header, [:contents]
9
+ end
10
+ end
11
+
12
+ def header
13
+ tidy erb(:header)
14
+ end
@@ -0,0 +1,3 @@
1
+ def init
2
+ sections :header, [T('method_details')]
3
+ end
@@ -0,0 +1 @@
1
+ <%= yieldall %>
@@ -0,0 +1,10 @@
1
+ def init
2
+ sections :header, [:method_signature, T('docstring'), :source]
3
+ end
4
+
5
+ def source
6
+ return if owner != object.namespace
7
+ return if Tags::OverloadTag === object
8
+ return if object.source.nil?
9
+ erb(:source)
10
+ end
@@ -0,0 +1,10 @@
1
+ <%= title_align_right format_object_title(object) %>
2
+
3
+ <%= align_right "(Defined in: #{object.file})" %>
4
+ <% if object.aliases.size > 0 %>
5
+
6
+ <%= align_right "(Also known as: #{object.aliases.map {|o| o.name(true).to_s }.join(',')})" %>
7
+ <% end %>
8
+
9
+ <%= yieldall %>
10
+
@@ -0,0 +1,12 @@
1
+
2
+ <% if object.tags(:overload).size == 1 %>
3
+ <%= indent wrap(signature(object.tag(:overload))) %>
4
+ <% elsif object.tags(:overload).size > 1 %>
5
+ <% object.tags(:overload).each do |overload| %>
6
+ <%= indent wrap(signature(overload)) %>
7
+ <% end %>
8
+ <% else %>
9
+ <%= indent wrap(signature(object)) %>
10
+ <% end %>
11
+ <%= hr %>
12
+
@@ -0,0 +1,10 @@
1
+ def init
2
+ super
3
+ sections.last.pop
4
+ end
5
+
6
+ def format_object_title(object)
7
+ title = "Method: #{object.name(true)}"
8
+ title += " (#{object.namespace})" if !object.namespace.root?
9
+ title
10
+ end
@@ -0,0 +1 @@
1
+ <%= format_path object %> [label="{<%= yieldall.gsub("\n", '') %>}" rank=sink];
@@ -0,0 +1,3 @@
1
+ <% object.mixins(:instance).each do |obj| %>
2
+ <%= format_path object %> -> <%= format_path obj %> [style=dotted arrowType=none];
3
+ <% end %>
@@ -0,0 +1,6 @@
1
+ subgraph cluster_<%= format_path object %> {
2
+ label = "<%= h(object.name) unless object.path == "" %>"; labelloc=b;
3
+ <% for obj in @modules %>
4
+ <%= yieldall :object => obj %>
5
+ <% end %>
6
+ }
@@ -0,0 +1,14 @@
1
+ <%= object.type %> <%= h object.name %>
2
+ <% if options.full %>
3
+ |
4
+ <% object.attributes.each do |scope, list| %>
5
+ <% list.sort_by {|name, rw| name.to_s }.each do |name, rw| %>
6
+ <%= uml_visibility(rw.values.compact.first) %> <%= h (rw[:read]||rw[:write]).name(true).gsub(/=$/,'') %> [<%= 'R' if rw[:read] %><%= 'W' if rw[:write] %>]\l
7
+ <% end %>
8
+ <% end %>
9
+ |
10
+ <% method_listing.each do |obj| %>
11
+ <%= uml_visibility obj %> <%= h obj.name(true) %>
12
+ <%= h(" : #{obj.tag(:return).types.first}") if obj.has_tag?(:return) && obj.tag(:return).types && obj.tag(:return).types.size > 0 %>\l
13
+ <% end %>
14
+ <% end %>