gd_bam 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +169 -0
- data/bin/bam +218 -0
- data/lib/bam/version.rb +3 -0
- data/lib/bam.rb +8 -0
- data/lib/dsl/project_dsl.rb +259 -0
- data/lib/graphs/docentize.grf +47 -0
- data/lib/graphs/dummy.grf +46 -0
- data/lib/graphs/load_history.grf +579 -0
- data/lib/graphs/process_account.grf +47 -0
- data/lib/graphs/process_activity.grf +222 -0
- data/lib/graphs/process_activity_dim.grf +88 -0
- data/lib/graphs/process_activity_owner.grf +48 -0
- data/lib/graphs/process_opportunity.grf +46 -0
- data/lib/graphs/process_opportunity_line_item.grf +179 -0
- data/lib/graphs/process_opportunity_snapshot.grf +94 -0
- data/lib/graphs/process_owner.grf +48 -0
- data/lib/graphs/process_stage.grf +51 -0
- data/lib/graphs/process_stage_history.grf +184 -0
- data/lib/graphs/process_velocity_duration.grf +140 -0
- data/lib/nodes/clover_gen.rb +1283 -0
- data/lib/nodes/dependency.rb +96 -0
- data/lib/nodes/nodes.rb +371 -0
- data/lib/repo/1_config.json +8 -0
- data/lib/repository/repo.rb +21 -0
- data/lib/runtime.rb +517 -0
- data/templates/dataset.json.erb +13 -0
- data/templates/flow.rb.erb +12 -0
- data/templates/params.json.erb +7 -0
- data/templates/project.erb +18 -0
- data/templates/source.json.erb +22 -0
- data/templates/tap.json.erb +16 -0
- data/templates/update_dataset.script.erb +4 -0
- data/templates/update_dataset_dry.script.erb +3 -0
- data/templates/workspace.prm.erb +25 -0
- metadata +412 -0
data/README.md
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
#BAsh Machinery = BAM
|
2
|
+
|
3
|
+
This thing is fresh from the oven. It is 0.0.1 so there are lots of rough edges. On the other hand there is enough to make you dangerous. Play with it, break it, let me know.
|
4
|
+
|
5
|
+
###What are goals of BAM
|
6
|
+
* Be able to spin up a predefined project in hours not days
|
7
|
+
* Everything implemented using CloudConnect so we can still leverage the infrastructure and deploy to secure
|
8
|
+
* Make some specific modifications easy (new fields from sources)
|
9
|
+
|
10
|
+
###What are not goals of BAM
|
11
|
+
* Supersede Clover/CC
|
12
|
+
* Provide templates for development. The generated grapsh are meant not to be tampered with.
|
13
|
+
* define a whole processing language. This might be the next extension but right now the goal is to be able to spin up predefined projects as fast and easily as possible. I am not defining primitives for joins, reformats etc.
|
14
|
+
|
15
|
+
##Overview
|
16
|
+
|
17
|
+
BAM is consisting of two parts. The underlying layer that allows you to build ETLs from prebuild constructs. Second part should make possible to express different configurations in user comprehensible way and configure first layer for specific projects so you do not need to deal with low level stuff when you decide that you want to use Amount instead of Total Price in GoodSales project.
|
18
|
+
|
19
|
+
####1st layer
|
20
|
+
|
21
|
+
There are 3 basic pieces that you will be playing around. Let's have a look at those
|
22
|
+
|
23
|
+
1) Tap
|
24
|
+
This is a fancy name for source of data. It can be downloader from SF or CSV file. Tap configurations are source specific. Currently there is SF implemented.
|
25
|
+
|
26
|
+
2) Sink
|
27
|
+
This is the target of your data. The only sink we have currently is GD.
|
28
|
+
|
29
|
+
3) Graph
|
30
|
+
This is a clover graph. So it plays well with Ultra it needs to be created in a specific way. You can use graphs from the library or those that you provide locally (N/A yet).
|
31
|
+
|
32
|
+
|
33
|
+
4) Flow
|
34
|
+
This is something that describes how the data are flowing. The previous three pieces are the things that you can use in the flow.
|
35
|
+
|
36
|
+
|
37
|
+
###2nd layer
|
38
|
+
TBD
|
39
|
+
|
40
|
+
##Installation
|
41
|
+
|
42
|
+
create a directory `mkdir bam`
|
43
|
+
cd into it `cd bam`
|
44
|
+
|
45
|
+
clone salesforce
|
46
|
+
clone bam
|
47
|
+
create a Gemfile `touch Gemfile`
|
48
|
+
|
49
|
+
and put this inside
|
50
|
+
|
51
|
+
source "https://rubygems.org"
|
52
|
+
gem "bam", :path => "./bam"
|
53
|
+
gem "salesforce", :path => "./salesforce"
|
54
|
+
|
55
|
+
make sure you are running ruby 1.9.x
|
56
|
+
install bundler `gem install bundler`
|
57
|
+
|
58
|
+
cd bam
|
59
|
+
bundle install
|
60
|
+
|
61
|
+
cd ..
|
62
|
+
bundle install
|
63
|
+
|
64
|
+
create a project `bundle exec bam scaffold project test`
|
65
|
+
|
66
|
+
this will create a project
|
67
|
+
|
68
|
+
##Sample project
|
69
|
+
|
70
|
+
now you can go inside `cd test` and generate it
|
71
|
+
`bundle exec bam generate`
|
72
|
+
|
73
|
+
This tap will download users from sf (you have to provide credentials in params.json). It then runs graph called "process user" (this is part of the distribution). This graph concatenates first name and last name together.
|
74
|
+
|
75
|
+
GoodData::CloverGenerator::DSL::flow("user") do |f|
|
76
|
+
tap(:id => "user")
|
77
|
+
|
78
|
+
graph("process_owner")
|
79
|
+
metadata("user") do |m|
|
80
|
+
m.remove("FirstName")
|
81
|
+
m.remove("LastName")
|
82
|
+
m.add(:name => "Name")
|
83
|
+
end
|
84
|
+
|
85
|
+
sink(:id => "user")
|
86
|
+
end
|
87
|
+
|
88
|
+
Now you have to provide it the definition of tap which you can do like this.
|
89
|
+
|
90
|
+
{
|
91
|
+
"source" : "salesforce"
|
92
|
+
,"object" : "User"
|
93
|
+
,"id" : "user"
|
94
|
+
,"fields" : [
|
95
|
+
{
|
96
|
+
"name" : "Id"
|
97
|
+
},
|
98
|
+
{
|
99
|
+
"name" : "FirstName"
|
100
|
+
},
|
101
|
+
{
|
102
|
+
"name" : "LastName"
|
103
|
+
},
|
104
|
+
{
|
105
|
+
"name" : "Region"
|
106
|
+
},
|
107
|
+
{
|
108
|
+
"name" : "Department"
|
109
|
+
}
|
110
|
+
]
|
111
|
+
}
|
112
|
+
|
113
|
+
Also you need to provide a definition for sink which can look somwhow like this.
|
114
|
+
|
115
|
+
{
|
116
|
+
"type" : "dataset"
|
117
|
+
,"id" : "user"
|
118
|
+
,"gd_name" : "user"
|
119
|
+
,"fields" : [
|
120
|
+
{
|
121
|
+
"name" : "Id"
|
122
|
+
},
|
123
|
+
{
|
124
|
+
"name" : "Name"
|
125
|
+
}
|
126
|
+
]
|
127
|
+
}
|
128
|
+
|
129
|
+
For this example to work you need to provide SF and gd credentials. Provide them in params.json. You would need to provide also a project with appropriate project but this is out of scope of this "example" (I am working on tools that would make it easier).
|
130
|
+
|
131
|
+
Now run `bundle exec bam generate` and there will be a folder with the clover project generated. Open it in CC find main.grf and run it. After crunching for a while you should see data in the project.
|
132
|
+
|
133
|
+
### Runtime commands
|
134
|
+
Part of the distribution is the bam executable which lets you do several neat things.
|
135
|
+
|
136
|
+
Run `bam` to get the list of commands
|
137
|
+
Run `bam help command` to get help about the command
|
138
|
+
|
139
|
+
### deploy directory
|
140
|
+
deploys the directory to the server. You can provide the param of the process as a parameter
|
141
|
+
|
142
|
+
### generate
|
143
|
+
Generates the ETL. The default target directory is clover_project (currently cannot be changed). You can provide --only parameter to specify the name of the flow to be processed if you do not need to generate all flows. Currently you can specify only on in only param
|
144
|
+
|
145
|
+
### generate_downloaders
|
146
|
+
If you have incremental downloaders in your project it good to deploy them as a separate process. This generates only the downloaders and is meant for exacltly this purpose. If you are interested about why it is a good idea. Take a look here (TBD). The target directory is downloaders_project (currently cannot be changed).
|
147
|
+
|
148
|
+
### generate_xmls
|
149
|
+
Investigates what is changed and performs the changes in the target project. Uses CL tool behind the scenes. Needs more work
|
150
|
+
|
151
|
+
### model_sync
|
152
|
+
Syncs the model with the definition in sinks. {Todo} Add interactive addition. Sometimes the new field can actually be a typo or something like that. Possible to uncover with validate_datasets
|
153
|
+
|
154
|
+
### run
|
155
|
+
TBD
|
156
|
+
|
157
|
+
### scaffold
|
158
|
+
Takes an argument and creates a scaffold for you. It can scaffold project, flow, sink and tap.
|
159
|
+
|
160
|
+
### taps_generate_docs
|
161
|
+
In your project there should be a README.md.erb file. By running this command it will be transformed into README.md and put into the project so it can be committed to git. The interpolated params are
|
162
|
+
taps
|
163
|
+
sinks
|
164
|
+
|
165
|
+
### sinks_validate
|
166
|
+
Currently works only for SF. Validates that the target SF instance has all the fields in the objects that are specified in the taps definitions.
|
167
|
+
|
168
|
+
### validate_datasets
|
169
|
+
Vallidates the sinks (currently only GD) with the definitions in the proeject. It looks for fields that are defined inside sinks and are not in the projects missing references etc. More description needed.
|
data/bin/bam
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'gli'
|
3
|
+
# begin # XXX: Remove this begin/rescue before distributing your app
|
4
|
+
require 'bam'
|
5
|
+
# rescue LoadError
|
6
|
+
# STDERR.puts "In development, you need to use `bundle exec bin/bam` to run your app"
|
7
|
+
# STDERR.puts "At install-time, RubyGems will make sure lib, etc. are in the load path"
|
8
|
+
# STDERR.puts "Feel free to remove this message from bin/bam now"
|
9
|
+
# exit 64
|
10
|
+
# end
|
11
|
+
|
12
|
+
include GLI::App
|
13
|
+
|
14
|
+
program_desc 'Describe your application here'
|
15
|
+
|
16
|
+
version Bam::VERSION
|
17
|
+
|
18
|
+
# desc 'Describe some switch here'
|
19
|
+
# switch [:s,:switch]
|
20
|
+
#
|
21
|
+
desc 'Verbose'
|
22
|
+
default_value false
|
23
|
+
arg_name 'verbose'
|
24
|
+
switch [:v,:verbose]
|
25
|
+
|
26
|
+
|
27
|
+
desc 'Generates clover project based on information in current directory. The default ouptut is the directory ./clover_project'
|
28
|
+
# arg_name 'Describe arguments to new here'
|
29
|
+
command :generate do |c|
|
30
|
+
|
31
|
+
c.desc 'generate only specified flow'
|
32
|
+
c.arg_name 'only'
|
33
|
+
c.flag :only
|
34
|
+
|
35
|
+
c.action do |global_options,options,args|
|
36
|
+
GoodData::CloverGenerator.clobber_clover_project
|
37
|
+
GoodData::CloverGenerator.run(options)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
desc 'Generates clover project for downloaders.'
|
42
|
+
# arg_name 'Describe arguments to new here'
|
43
|
+
command :generate_downloaders do |c|
|
44
|
+
|
45
|
+
c.desc 's3 backup'
|
46
|
+
c.arg_name 'backup'
|
47
|
+
c.flag :backup
|
48
|
+
|
49
|
+
c.action do |global_options,options,args|
|
50
|
+
GoodData::CloverGenerator.clobber_downloader_project
|
51
|
+
GoodData::CloverGenerator.generate_downloaders(options)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
desc 'Validates that the tap has the fields it is claimed it should have. This is supposed to make the mitigate errors during deploy.'
|
56
|
+
# arg_name 'Describe arguments to new here'
|
57
|
+
command :taps_validate do |c|
|
58
|
+
c.action do |global_options,options,args|
|
59
|
+
GoodData::CloverGenerator.validate_taps
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
desc 'Validates that the tap has the fields it is claimed it should have. This is supposed to make the mitigate errors during deploy.'
|
64
|
+
# arg_name 'Describe arguments to new here'
|
65
|
+
command :taps_generate_docs do |c|
|
66
|
+
c.action do |global_options,options,args|
|
67
|
+
GoodData::CloverGenerator.taps_generate_docs
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
desc 'Lists processes for the project.'
|
72
|
+
# arg_name 'Describe arguments to new here'
|
73
|
+
command :procs do |c|
|
74
|
+
|
75
|
+
c.desc 'procs for all projects'
|
76
|
+
c.arg_name 'all'
|
77
|
+
c.switch :all
|
78
|
+
|
79
|
+
c.action do |global_options,options,args|
|
80
|
+
out = GoodData::CloverGenerator.procs_list(options)
|
81
|
+
out.each do |proc|
|
82
|
+
puts proc.join(',')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
desc 'Validates that the tap has the fields it is claimed it should have. This is supposed to make the mitigate errors during deploy.'
|
89
|
+
# arg_name 'Describe arguments to new here'
|
90
|
+
command :sinks_validate do |c|
|
91
|
+
c.action do |global_options,options,args|
|
92
|
+
x = GoodData::CloverGenerator.validate_datasets
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
desc 'Generates structures'
|
98
|
+
arg_name 'what you want to generate project, tap, flow, dataset'
|
99
|
+
command :scaffold do |c|
|
100
|
+
c.action do |global_options,options,args|
|
101
|
+
command = args.first
|
102
|
+
fail "You did not provide what I should scaffold. I can generate project, tap, flow, sink nothing else" unless ["project", "tap", "flow", "sink"].include?(command)
|
103
|
+
case command
|
104
|
+
when "project"
|
105
|
+
puts "project"
|
106
|
+
directory = args[1]
|
107
|
+
fail "Directory has to be provided as an argument. See help" if directory.nil?
|
108
|
+
GoodData::CloverGenerator.setup_bash_structure(directory)
|
109
|
+
when "flow"
|
110
|
+
name = args[1]
|
111
|
+
fail "Name of the flow has to be provided as an argument. See help" if name.nil?
|
112
|
+
GoodData::CloverGenerator.setup_flow(name)
|
113
|
+
when "tap"
|
114
|
+
name = args[1]
|
115
|
+
fail "Name of the tap has to be provided as an argument. See help" if name.nil?
|
116
|
+
GoodData::CloverGenerator.setup_tap(name)
|
117
|
+
when "sink"
|
118
|
+
name = args[1]
|
119
|
+
fail "Name of the sink has to be provided as an argument. See help" if name.nil?
|
120
|
+
GoodData::CloverGenerator.setup_sink(name)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
desc 'Runs the project on server'
|
126
|
+
command :run do |c|
|
127
|
+
c.action do |global_options,options,args|
|
128
|
+
puts "This would run the project. But it is not yet implemented"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
desc 'Runs the project on server'
|
133
|
+
command :model_sync do |c|
|
134
|
+
|
135
|
+
c.desc 'do not execute'
|
136
|
+
c.arg_name 'dry'
|
137
|
+
c.switch :dry
|
138
|
+
|
139
|
+
c.action do |global_options,options,args|
|
140
|
+
GoodData::CloverGenerator.model_sync(options)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
desc 'Deploys the project on server and schedules it'
|
146
|
+
command :deploy do |c|
|
147
|
+
|
148
|
+
c.desc 'existing process id under which it is going to be redeployed'
|
149
|
+
c.arg_name 'process'
|
150
|
+
c.flag :process
|
151
|
+
|
152
|
+
c.desc 'name of the process'
|
153
|
+
c.arg_name 'name'
|
154
|
+
c.flag :name
|
155
|
+
|
156
|
+
c.action do |global_options,options,args|
|
157
|
+
dir = args.first
|
158
|
+
fail "You have to specify directory to deploy as an argument" if dir.nil?
|
159
|
+
fail "Specified directory does not exist" unless File.exist?(dir)
|
160
|
+
GoodData::CloverGenerator.connect_to_gd
|
161
|
+
response = GoodData::CloverGenerator.deploy(dir, options)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
desc 'Runs the project on server'
|
166
|
+
command :run do |c|
|
167
|
+
|
168
|
+
# c.desc 'existing process id under which it is going to be redeployed'
|
169
|
+
# c.arg_name 'process'
|
170
|
+
# c.flag :process
|
171
|
+
|
172
|
+
c.action do |global_options,options,args|
|
173
|
+
|
174
|
+
dir = args.first
|
175
|
+
fail "You have to specify directory to deploy as an argument" if dir.nil?
|
176
|
+
fail "Specified directory does not exist" unless File.exist?(dir)
|
177
|
+
|
178
|
+
verbose = global_options[:v]
|
179
|
+
|
180
|
+
GoodData::CloverGenerator.connect_to_gd
|
181
|
+
GoodData::CloverGenerator.create_email_channel
|
182
|
+
|
183
|
+
GoodData::CloverGenerator.deploy(args.first, global_options.merge({:name => "temporary"})) do |deploy_response|
|
184
|
+
|
185
|
+
puts HighLine::color("Executing", HighLine::BOLD) if verbose
|
186
|
+
GoodData::CloverGenerator.create_email_channel do
|
187
|
+
GoodData::CloverGenerator.execute_process(deploy_response["cloverTransformation"]["links"]["executions"], dir)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
pre do |global,command,options,args|
|
195
|
+
# Pre logic here
|
196
|
+
# Return true to proceed; false to abort and not call the
|
197
|
+
# chosen command
|
198
|
+
# Use skips_pre before a command to skip this block
|
199
|
+
# on that command only
|
200
|
+
true
|
201
|
+
end
|
202
|
+
|
203
|
+
post do |global_options,command,options,args|
|
204
|
+
# Post logic here
|
205
|
+
# Use skips_post before a command to skip this
|
206
|
+
# block on that command only
|
207
|
+
verbose = global_options[:v]
|
208
|
+
puts HighLine::color("DONE", :green) if verbose
|
209
|
+
end
|
210
|
+
|
211
|
+
on_error do |exception|
|
212
|
+
pp exception.backtrace
|
213
|
+
# Error logic here
|
214
|
+
# return false to skip default error handling
|
215
|
+
true
|
216
|
+
end
|
217
|
+
|
218
|
+
exit run(ARGV)
|
data/lib/bam/version.rb
ADDED
data/lib/bam.rb
ADDED
@@ -0,0 +1,259 @@
|
|
1
|
+
require 'terminal-table'
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
module GoodData
|
6
|
+
module CloverGenerator
|
7
|
+
module DSL
|
8
|
+
|
9
|
+
class RemoveMetadataFieldError < RuntimeError
|
10
|
+
|
11
|
+
attr_reader :options, :metadata, :field
|
12
|
+
|
13
|
+
def initialize(message, options={})
|
14
|
+
super(message)
|
15
|
+
@options = options
|
16
|
+
@metadata = options[:metadata]
|
17
|
+
@field = options[:field]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Metadata
|
22
|
+
|
23
|
+
attr_accessor :metadata
|
24
|
+
|
25
|
+
def name
|
26
|
+
metadata[:name]
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_hash
|
30
|
+
metadata
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(metadata)
|
34
|
+
@metadata = metadata
|
35
|
+
end
|
36
|
+
|
37
|
+
def add(options={})
|
38
|
+
fail "You have to specify name at the metadata change. You specified #{what}" unless options.has_key?(:name)
|
39
|
+
position = options[:position] || 0
|
40
|
+
what = {
|
41
|
+
:name => options[:name],
|
42
|
+
:type => options[:type] || "string"
|
43
|
+
}
|
44
|
+
|
45
|
+
@metadata[:fields].insert(position - 1, what)
|
46
|
+
@metadata
|
47
|
+
end
|
48
|
+
|
49
|
+
def remove(what)
|
50
|
+
fields = metadata[:fields]
|
51
|
+
fail RemoveMetadataFieldError.new("Specified column #{what} was not found", :field => what, :metadata => self) unless fields.detect {|f| f[:name] == what}
|
52
|
+
@metadata[:fields] = fields.find_all {|f| f[:name] != what}
|
53
|
+
@metadata
|
54
|
+
end
|
55
|
+
|
56
|
+
def change
|
57
|
+
yield(self)
|
58
|
+
self
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class Flow
|
63
|
+
|
64
|
+
attr_accessor :steps, :name
|
65
|
+
|
66
|
+
def self.define(name="", &script)
|
67
|
+
puts "Reading flow #{name}"
|
68
|
+
x = self.new
|
69
|
+
x.flow_name(name)
|
70
|
+
x.instance_eval(&script)
|
71
|
+
x
|
72
|
+
end
|
73
|
+
|
74
|
+
def initialize
|
75
|
+
@steps = []
|
76
|
+
end
|
77
|
+
|
78
|
+
def flow_name(name)
|
79
|
+
@name = name
|
80
|
+
end
|
81
|
+
|
82
|
+
def tap(options={}, &bl)
|
83
|
+
step({:type => :tap, :source_name => options[:id]})
|
84
|
+
end
|
85
|
+
|
86
|
+
def sink(options={}, &bl)
|
87
|
+
step(:type => :upload, :id => options[:id], &bl)
|
88
|
+
end
|
89
|
+
|
90
|
+
def graph(graph, &bl)
|
91
|
+
step(:graph => graph, :type => :user_provided, &bl)
|
92
|
+
end
|
93
|
+
|
94
|
+
def parallel(&bl)
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
def step(options={}, &bl)
|
99
|
+
graph = options[:graph]
|
100
|
+
type = options[:type]
|
101
|
+
|
102
|
+
steps.push(options)
|
103
|
+
puts "Running step #{graph}"
|
104
|
+
end
|
105
|
+
|
106
|
+
def metadata(name=nil,options={}, &bl)
|
107
|
+
steps.last[:metadata_block] = [] if steps.last[:metadata_block].nil?
|
108
|
+
steps.last[:metadata_block] << {:name => name, :block => bl, :out_as => options[:out_as]}
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.flow(name="", &bl)
|
115
|
+
Flow.define(name, &bl)
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
class Project
|
120
|
+
|
121
|
+
attr_accessor :usecases, :name, :dims
|
122
|
+
|
123
|
+
def self.define(&script)
|
124
|
+
print self
|
125
|
+
x = self.new
|
126
|
+
x.instance_eval(&script)
|
127
|
+
x
|
128
|
+
end
|
129
|
+
|
130
|
+
def initialize
|
131
|
+
@usecases = []
|
132
|
+
end
|
133
|
+
|
134
|
+
def project_name(name)
|
135
|
+
@name = name
|
136
|
+
end
|
137
|
+
|
138
|
+
def use_dims(dims)
|
139
|
+
@dims = dims
|
140
|
+
end
|
141
|
+
|
142
|
+
def use_usecase(usecase)
|
143
|
+
@usecases << usecase
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
|
148
|
+
def get_sources
|
149
|
+
configs = []
|
150
|
+
FileUtils.cd('./taps') do
|
151
|
+
Dir.glob('*.json').each do|f|
|
152
|
+
configs << JSON.parse(File.read(f), :symbolize_names => true)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
configs
|
156
|
+
end
|
157
|
+
|
158
|
+
def print_sources(taps)
|
159
|
+
puts
|
160
|
+
puts "Printing sources"
|
161
|
+
puts "================"
|
162
|
+
puts
|
163
|
+
taps.each do |tap|
|
164
|
+
fail "Provided tap #{tap[:object]} does not seem to be tap" if tap[:type] != "tap"
|
165
|
+
if tap[:source] == "salesforce"
|
166
|
+
table = Terminal::Table.new(:title => "#{tap[:source]} => #{tap[:object]}", :style => {:width => 30}) do |t|
|
167
|
+
tap[:fields].each do |f|
|
168
|
+
t << [f[:name], f[:name]]
|
169
|
+
end
|
170
|
+
end
|
171
|
+
puts table
|
172
|
+
puts
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
def get_datasets
|
180
|
+
configs = []
|
181
|
+
FileUtils.cd('./sinks') do
|
182
|
+
Dir.glob('*.json').each do|f|
|
183
|
+
configs << JSON.parse(File.read(f), :symbolize_names => true)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
configs
|
187
|
+
end
|
188
|
+
|
189
|
+
def compare_fields(sources, datasets)
|
190
|
+
a = sources.reduce([]) do |memo, source|
|
191
|
+
x = source[:object]
|
192
|
+
memo.concat(source[:fields].map {|f| [x, f[:name]]})
|
193
|
+
memo
|
194
|
+
end
|
195
|
+
|
196
|
+
b = datasets.reduce([]) do |memo, source|
|
197
|
+
x = source[:name]
|
198
|
+
memo.concat(source[:fields].map {|f| [x, f[:name]]})
|
199
|
+
memo
|
200
|
+
end
|
201
|
+
result = (a | b) - (a & b)
|
202
|
+
if result.count > 0
|
203
|
+
puts "------------------"
|
204
|
+
puts "All fields not in"
|
205
|
+
puts "------------------"
|
206
|
+
result.each {|x| pp x}
|
207
|
+
fail "Some fields form source are not used"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def run(repo)
|
212
|
+
puts "Running"
|
213
|
+
|
214
|
+
puts "looking for dimension definitions"
|
215
|
+
dims.each do |dim|
|
216
|
+
puts "found #{dim}"
|
217
|
+
end
|
218
|
+
|
219
|
+
sources = get_sources
|
220
|
+
fail "You have no sources defined" if sources.empty?
|
221
|
+
puts "Found #{sources.count} sources"
|
222
|
+
|
223
|
+
|
224
|
+
datasets = get_datasets
|
225
|
+
fail "You have no datasets defined" if datasets.empty?
|
226
|
+
puts "Found #{datasets.count} sources"
|
227
|
+
|
228
|
+
puts "Composing the tree"
|
229
|
+
you = GoodData::CloverGenerator::Dependency::N.new({
|
230
|
+
:name => name,
|
231
|
+
:type => "project",
|
232
|
+
:provides => [],
|
233
|
+
:requires => @usecases
|
234
|
+
})
|
235
|
+
|
236
|
+
provided_dims = @dims.map do |dim_to_provide|
|
237
|
+
GoodData::CloverGenerator::Dependency::N.new({
|
238
|
+
:package => dim_to_provide.split("/").first,
|
239
|
+
:name => dim_to_provide.split("/").last,
|
240
|
+
:provides => [dim_to_provide.split("/").last],
|
241
|
+
:type => "dim",
|
242
|
+
:requires => []
|
243
|
+
})
|
244
|
+
end
|
245
|
+
|
246
|
+
provided_dims.each {|x| repo << x}
|
247
|
+
# graph = resolve(repo, you)
|
248
|
+
# to_dot(graph)
|
249
|
+
v = GoodData::CloverGenerator::Dependency::Visitor.new
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
253
|
+
|
254
|
+
def self.project(&bl)
|
255
|
+
Project.define(&bl)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<Graph author="fluke" created="Tue Feb 05 15:38:24 PST 2013" guiVersion="3.3.1" id="1360179808937" licenseType="Commercial" modified="Fri Feb 22 12:18:42 PST 2013" modifiedBy="fluke" name="process_name" revision="1.13" showComponentDetails="true">
|
3
|
+
<Global>
|
4
|
+
<Metadata fileURL="${PROJECT}/metadata/${FLOW}/${NAME}/1_in.xml" id="Metadata0"/>
|
5
|
+
<Metadata fileURL="${PROJECT}/metadata/${FLOW}/${NAME}/1_out.xml" id="Metadata1"/>
|
6
|
+
<MetadataGroup id="ComponentGroup0" name="metadata"/>
|
7
|
+
<Property fileURL="params.txt" id="GraphParameter0"/>
|
8
|
+
<Property fileURL="workspace.prm" id="GraphParameter0"/>
|
9
|
+
<Dictionary/>
|
10
|
+
</Global>
|
11
|
+
<Phase number="0">
|
12
|
+
<Node enabled="enabled" fileURL="data/1_in.csv" guiHeight="77" guiName="CSV Reader" guiWidth="128" guiX="124" guiY="169" id="DATA_READER0" quoteCharacter=""" quotedStrings="true" skipRows="1" type="DATA_READER"/>
|
13
|
+
<Node enabled="enabled" fileURL="data/out.csv" guiHeight="89" guiName="CSV Writer" guiWidth="128" guiX="609" guiY="169" id="DATA_WRITER0" outputFieldNames="true" quoteCharacter=""" quotedStrings="true" type="DATA_WRITER"/>
|
14
|
+
<Node enabled="enabled" guiHeight="65" guiName="Reformat" guiWidth="128" guiX="365" guiY="175" id="REFORMAT0" type="REFORMAT">
|
15
|
+
<attr name="transform"><![CDATA[//#CTL2
|
16
|
+
|
17
|
+
// Transforms input record into output record.
|
18
|
+
function integer transform() {
|
19
|
+
$out.0.* = $in.0.*;
|
20
|
+
$out.0.Name = "Docent " + $in.0.Name;
|
21
|
+
|
22
|
+
return OK;
|
23
|
+
}
|
24
|
+
|
25
|
+
// Called during component initialization.
|
26
|
+
// function boolean init() {}
|
27
|
+
|
28
|
+
// Called during each graph run before the transform is executed. May be used to allocate and initialize resources
|
29
|
+
// required by the transform. All resources allocated within this method should be released
|
30
|
+
// by the postExecute() method.
|
31
|
+
// function void preExecute() {}
|
32
|
+
|
33
|
+
// Called only if transform() throws an exception.
|
34
|
+
// function integer transformOnError(string errorMessage, string stackTrace) {}
|
35
|
+
|
36
|
+
// Called during each graph run after the entire transform was executed. Should be used to free any resources
|
37
|
+
// allocated within the preExecute() method.
|
38
|
+
// function void postExecute() {}
|
39
|
+
|
40
|
+
// Called to return a user-defined error message when an error occurs.
|
41
|
+
// function string getMessage() {}
|
42
|
+
]]></attr>
|
43
|
+
</Node>
|
44
|
+
<Edge fromNode="DATA_READER0:0" guiBendpoints="" guiRouter="Manhattan" id="Edge0" inPort="Port 0 (in)" metadata="Metadata0" outPort="Port 0 (output)" toNode="REFORMAT0:0"/>
|
45
|
+
<Edge fromNode="REFORMAT0:0" guiBendpoints="" guiRouter="Manhattan" id="Edge1" inPort="Port 0 (in)" metadata="Metadata1" outPort="Port 0 (out)" toNode="DATA_WRITER0:0"/>
|
46
|
+
</Phase>
|
47
|
+
</Graph>
|