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.
- data/.gitignore +3 -0
- data/.rspec +3 -0
- data/.yardopts +19 -0
- data/Gemfile +5 -0
- data/README.md +191 -0
- data/Rakefile +18 -1
- data/bin/gooddata +74 -63
- data/doc/.gitignore +1 -0
- data/doc/css/.gitkeepme +1 -0
- data/doc/images/.gitkeepme +1 -0
- data/doc/images/background.png +0 -0
- data/doc/images/bg-callout-button.png +0 -0
- data/doc/images/header-logo.png +0 -0
- data/doc/images/logo-image.png +0 -0
- data/doc/js/.gitkeepme +1 -0
- data/doc/pages/GET_STARTED.md +309 -0
- data/doc/pages/HOMEPAGE.md +75 -0
- data/doc/pages/TUTORIALS.md +52 -0
- data/doc/pages/tutorial/BRICKS.md +257 -0
- data/doc/pages/tutorial/CREATING_A_MODEL.md +79 -0
- data/doc/pages/tutorial/CRUNCHING_NUMBERS.md +233 -0
- data/doc/pages/tutorial/TEST_DRIVEN_DEVELOPMENT.md +118 -0
- data/doc/pages/tutorial/YOUR_FIRST_PROJECT.md +52 -0
- data/doc/templates/default/class/dot/setup.rb +6 -0
- data/doc/templates/default/class/dot/superklass.erb +3 -0
- data/doc/templates/default/class/setup.rb +36 -0
- data/doc/templates/default/class/text/setup.rb +11 -0
- data/doc/templates/default/class/text/subclasses.erb +5 -0
- data/doc/templates/default/constant/text/header.erb +11 -0
- data/doc/templates/default/constant/text/setup.rb +3 -0
- data/doc/templates/default/docstring/setup.rb +51 -0
- data/doc/templates/default/docstring/text/abstract.erb +2 -0
- data/doc/templates/default/docstring/text/deprecated.erb +2 -0
- data/doc/templates/default/docstring/text/index.erb +2 -0
- data/doc/templates/default/docstring/text/note.erb +4 -0
- data/doc/templates/default/docstring/text/private.erb +2 -0
- data/doc/templates/default/docstring/text/returns_void.erb +1 -0
- data/doc/templates/default/docstring/text/text.erb +1 -0
- data/doc/templates/default/docstring/text/todo.erb +4 -0
- data/doc/templates/default/layout/dot/header.erb +6 -0
- data/doc/templates/default/layout/dot/setup.rb +14 -0
- data/doc/templates/default/method/setup.rb +3 -0
- data/doc/templates/default/method/text/header.erb +1 -0
- data/doc/templates/default/method_details/setup.rb +10 -0
- data/doc/templates/default/method_details/text/header.erb +10 -0
- data/doc/templates/default/method_details/text/method_signature.erb +12 -0
- data/doc/templates/default/method_details/text/setup.rb +10 -0
- data/doc/templates/default/module/dot/child.erb +1 -0
- data/doc/templates/default/module/dot/dependencies.erb +3 -0
- data/doc/templates/default/module/dot/header.erb +6 -0
- data/doc/templates/default/module/dot/info.erb +14 -0
- data/doc/templates/default/module/dot/setup.rb +14 -0
- data/doc/templates/default/module/setup.rb +164 -0
- data/doc/templates/default/module/text/children.erb +10 -0
- data/doc/templates/default/module/text/class_meths_list.erb +8 -0
- data/doc/templates/default/module/text/extends.erb +8 -0
- data/doc/templates/default/module/text/header.erb +7 -0
- data/doc/templates/default/module/text/includes.erb +8 -0
- data/doc/templates/default/module/text/instance_meths_list.erb +8 -0
- data/doc/templates/default/module/text/setup.rb +12 -0
- data/doc/templates/default/root/dot/child.erb +3 -0
- data/doc/templates/default/root/dot/setup.rb +5 -0
- data/doc/templates/default/tags/setup.rb +55 -0
- data/doc/templates/default/tags/text/example.erb +12 -0
- data/doc/templates/default/tags/text/index.erb +1 -0
- data/doc/templates/default/tags/text/option.erb +20 -0
- data/doc/templates/default/tags/text/overload.erb +19 -0
- data/doc/templates/default/tags/text/see.erb +11 -0
- data/doc/templates/default/tags/text/tag.erb +13 -0
- data/examples.rb +2 -2
- data/gooddata.gemspec +31 -26
- data/lib/gooddata/bricks/middleware/gooddata_middleware.rb +1 -1
- data/lib/gooddata/client.rb +65 -53
- data/lib/gooddata/commands/commands.rb +9 -0
- data/lib/gooddata/commands/process.rb +9 -8
- data/lib/gooddata/commands/projects.rb +29 -0
- data/lib/gooddata/commands/runners.rb +1 -1
- data/lib/gooddata/connection.rb +6 -4
- data/lib/gooddata/exceptions.rb +2 -1
- data/lib/gooddata/helpers.rb +1 -1
- data/lib/gooddata/model.rb +360 -189
- data/lib/gooddata/models/metadata.rb +1 -1
- data/lib/gooddata/models/metric.rb +2 -1
- data/lib/gooddata/models/project.rb +1 -1
- data/lib/gooddata/models/report.rb +0 -18
- data/lib/gooddata/version.rb +1 -1
- data/spec/blueprint_spec.rb +83 -43
- data/spec/data/additional_dataset_module.json +18 -0
- data/spec/data/blueprint_invalid.json +36 -0
- data/spec/data/blueprint_valid.json +37 -0
- data/spec/data/model_module.json +18 -0
- data/spec/{test_project_model_spec.json → data/test_project_model_spec.json} +4 -0
- data/spec/full_project_spec.rb +4 -3
- data/spec/helpers/blueprint_helper.rb +17 -0
- data/spec/merging_blueprints_spec.rb +23 -48
- data/spec/model_dsl_spec.rb +2 -2
- data/spec/model_spec.rb +44 -0
- data/spec/project_build_and_update_spec.rb +28 -0
- data/spec/spec_helper.rb +6 -0
- data/yard-server.sh +3 -0
- metadata +251 -74
- 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,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,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 @@
|
|
1
|
+
<%= indent(wrap("This method returns an undefined value.")) %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= indent wrap(h(docstring_text.strip), 68) %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= yieldall %>
|
@@ -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 @@
|
|
1
|
+
<%= format_path object %> [label="{<%= yieldall.gsub("\n", '') %>}" rank=sink];
|
@@ -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 %>
|