gd_bam 0.0.1

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.
@@ -0,0 +1,1283 @@
1
+ module Enumerable
2
+ def uniq_by
3
+ seen = Hash.new { |h,k| h[k] = true; false }
4
+ reject { |v| seen[yield(v)] }
5
+ end
6
+ end
7
+
8
+ class Hash
9
+ #pass single or array of keys, which will be removed, returning the remaining hash
10
+ def remove!(*keys)
11
+ keys.each{|key| self.delete(key) }
12
+ self
13
+ end
14
+
15
+ #non-destructive version
16
+ def remove(*keys)
17
+ self.dup.remove!(*keys)
18
+ end
19
+ end
20
+
21
+
22
+ module GoodData
23
+ module CloverGenerator
24
+
25
+ ADD_NA_TRANSFORMATION = <<-trasnf
26
+ function integer generate() {
27
+ integer number_of_fields = length($out.0);
28
+ integer i = 0;
29
+ string type = "";
30
+ for (i; i< number_of_fields; ++i) {
31
+ type = getFieldType($out.0, i);
32
+ switch(type) {
33
+ case "string": setStringValue($out.0, i, 'N/A'); break;
34
+ case "number": setNumValue($out.0, i, 0); break;
35
+ case "date": setDateValue($out.0, i, null); break;
36
+ }
37
+ };
38
+
39
+ return OK;
40
+ }
41
+ trasnf
42
+
43
+ def self.parse_json(file)
44
+ begin
45
+ JSON.parse(File.read(file), :symbolize_names => true)
46
+ rescue RuntimeError => e
47
+ puts "Error parsing \"#{file}\": #{e.inspect}"
48
+ end
49
+ end
50
+
51
+
52
+ def self.metadata(builder, description)
53
+ builder.Record({
54
+ :fieldDelimiter => description[:fieldDelimiter],
55
+ :name => description[:name],
56
+ :recordDelimiter => description[:recordDelimiter],
57
+ :type => description[:type]
58
+ }) do |record|
59
+ description[:fields].each do |field|
60
+ builder.Field :name => field[:name], :type => field[:type], :nullable => "true"
61
+ end
62
+ end
63
+ end
64
+
65
+ def self.csv_metadata(builder, description)
66
+
67
+ sf_description = description.merge({
68
+ :fieldDelimiter => ",",
69
+ :recordDelimiter => "\\n",
70
+ :type => "delimited",
71
+ })
72
+ metadata(builder, sf_description)
73
+ end
74
+
75
+
76
+ def self.sf_connection(builder, data)
77
+ builder.Connection({
78
+ :clientId => "${SFDC_CLIENT_ID}",
79
+ :id => "SFDC",
80
+ :loginHostname => "${SFDC_LOGIN_HOSTNAME}",
81
+ :name => "${SFDC_NAME}",
82
+ :password => "${SFDC_PASSWORD}",
83
+ :passwordEncrypted => "true",
84
+ :token => "${SFDC_TOKEN}",
85
+ :type => "SFDC",
86
+ :username => "${SFDC_USERNAME}",
87
+ :passwordEncrypted => "false"
88
+ })
89
+ end
90
+
91
+ def self.external_metadata_link(builder, data)
92
+ builder.Metadata({
93
+ :fileURL => data[:fileURL],
94
+ :id => data[:id]
95
+ })
96
+ end
97
+
98
+ def self.property(builder, data)
99
+ builder.Property({
100
+ :id => data[:id],
101
+ :name => data[:name] || data[:id],
102
+ :value => data[:value]
103
+ })
104
+ end
105
+
106
+
107
+ def self.property_file(builder, data)
108
+ builder.Property({
109
+ :id => data[:id],
110
+ :fileURL => data[:fileURL]
111
+ })
112
+ end
113
+
114
+ def graph2(file, stuff)
115
+ metadata = stuff[:metadata]
116
+ nodes = stuff[:nodes]
117
+ connections = stuff[:connections]
118
+ File.open(file, "w") do |file|
119
+ builder = Builder::XmlMarkup.new(:target=>file, :indent=>2)
120
+ builder.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
121
+ builder.Graph({
122
+ :name => "Goodsales Downloader"
123
+ }) do
124
+ builder.Global do
125
+ metadata.each do |m|
126
+ build_metadata2(builder, m)
127
+ end
128
+ connections.each do |conn|
129
+ build_node2(builder, conn)
130
+ end
131
+
132
+ end
133
+ builder.Phase(:number => 0) do
134
+ nodes.each do |node|
135
+ build_node2(builder, node)
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ def self.build_node2(builder, node)
143
+ if node[:type] == GoodData::CloverGenerator::Nodes::EDGE
144
+ builder.tag!("Edge", node)
145
+ elsif node[:type] == GoodData::CloverGenerator::Nodes::SF_CONNECTION
146
+ builder.tag!("Connection", node)
147
+ elsif node[:type] == GoodData::CloverGenerator::Nodes::FILE_LIST
148
+ builder.tag!("Node", node.remove(:transformation)) do |xml|
149
+ xml.attr({:name => "outputMapping"}) do |attr|
150
+ transformation = node[:transformation]
151
+ attr.cdata! transformation
152
+ end
153
+ end
154
+ elsif node[:type] == GoodData::CloverGenerator::Nodes::REFORMAT || node[:type] == GoodData::CloverGenerator::Nodes::EXT_HASH_JOIN
155
+ builder.tag!("Node", node.remove(:transformation)) do |xml|
156
+ xml.attr({:name => "transform"}) do |attr|
157
+ transformation = node[:transformation]
158
+ attr.cdata! transformation
159
+ end
160
+ end
161
+ elsif node[:type] == GoodData::CloverGenerator::Nodes::DATA_GENERATOR
162
+ builder.tag!("Node", node.remove(:transformation)) do |xml|
163
+ xml.attr({:name => "generate"}) do |attr|
164
+ transformation = node[:generate]
165
+ attr.cdata! transformation
166
+ end
167
+ end
168
+ elsif node[:type] == GoodData::CloverGenerator::Nodes::PERSISTENT_LOOKUP || node[:type] == GoodData::CloverGenerator::Nodes::GD_LOOKUP
169
+ builder.tag!("LookupTable", node)
170
+ else
171
+ builder.tag!("Node", node)
172
+ end
173
+ end
174
+
175
+ def self.run_ctl(builder, ctl)
176
+ id1 = rand(10000000000)
177
+ id2 = rand(10000000000)
178
+ id3 = rand(10000000000)
179
+
180
+ build_node2(builder, GoodData::CloverGenerator::Nodes.data_generator2({:name => "#{id1}", :id => "#{id1}", :generate => ctl}))
181
+ build_node2(builder, GoodData::CloverGenerator::Nodes.trash2({:name => "#{id2}", :id => "#{id2}"}))
182
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "#{id2}:0", :fromNode => "#{id1}:0", :metadata => "trash_metadata", :id => "#{id3}"}))
183
+ end
184
+
185
+ def build_metadata2(builder, node)
186
+ builder.Metadata({:id => node[:id]}) do
187
+ csv_metadata(builder, {
188
+ :name => "account_sf_reformat",
189
+ :fields => translate(node[:fields])
190
+ })
191
+ end
192
+ end
193
+
194
+ def sf_field_mapping(stuff)
195
+ fields = stuff["fields"]
196
+ mapping = "{\"xmlFieldsMapping\":{\"xmlFields\":["
197
+ add = fields.map do |f|
198
+ "{\"xmlFieldMapping\":{\"name\":\"#{f[:name]}\",\"label\":\"#{f[:name]}\",\"xmlPath\":\"#{f[:name]}\",\"metadataField\":\"#{f[:name]}\"}}"
199
+ end
200
+ stuff = mapping + add.join(",") + "]}}"
201
+ stuff
202
+ end
203
+
204
+ def self.validate_gd_datasets(datasets, gd_datasets)
205
+ dataset_names = datasets.map {|d| d[:gd_name] || d[:id]}
206
+ gd_dataset_names = gd_datasets.map {|d| d["dataSet"]["meta"]["identifier"].gsub("dataset.", "")}.find_all {|d| d.index(".dt").nil?}
207
+ {
208
+ :not_in_gd => dataset_names - gd_dataset_names,
209
+ :not_loaded => gd_dataset_names - dataset_names
210
+ }
211
+ end
212
+
213
+ def self.validate_gd_datasets_metadata(datasets, gd_datasets)
214
+
215
+ init = {
216
+ :datasets => {}
217
+ }
218
+
219
+ datasets.reduce(init) do |memo, dataset|
220
+ gd_name = dataset[:gd_name] || dataset[:id]
221
+ gd_dataset = gd_datasets.find {|d| d["dataSet"]["meta"]["identifier"].gsub("dataset.", "") == gd_name}
222
+ gd_dataset_names = gd_datasets.map {|d| d["dataSet"]["meta"]["identifier"].gsub("dataset.", "")}.find_all {|d| d.index(".dt").nil?}
223
+
224
+ next(memo) if gd_dataset.nil?
225
+
226
+ refs = dataset[:fields].find_all {|f| f[:type] == "reference"}
227
+ refs.each do |ref|
228
+ (memo[:datasets][gd_name] ||= []) << ref unless gd_dataset_names.include?(ref[:schema])
229
+ ref_attr_indentifier = "attr.#{ref[:schema]}.#{ref[:ref]}"
230
+ (memo[:datasets][gd_name] ||= []) << ref if GoodData::MdObject[ref_attr_indentifier].nil?
231
+ end
232
+
233
+ labels = dataset[:fields].find_all {|f| f[:type] == "label"}
234
+ gd_labels = gd_dataset['dataSet']['content']['dataLoadingColumns'].map {|label| GoodData.get(label)}
235
+ labels.each do |label|
236
+ label_name = label[:name]
237
+ label_identifier = "label.#{gd_name}.#{label[:for]}.#{label_name}"
238
+ (memo[:datasets][gd_name] ||= []) << label if GoodData::MdObject[label_identifier].nil?
239
+ end
240
+
241
+ facts = dataset[:fields].find_all {|f| f[:type] == "fact"}
242
+ gd_facts = gd_dataset['dataSet']['content']['facts'].map {|fact| GoodData.get(fact)}
243
+ facts.each do |fact|
244
+ fact_name = fact[:name]
245
+ unless gd_facts.any? {|a| a['fact']['meta']['identifier'] == "fact.#{gd_name}.#{fact_name}"}
246
+ (memo[:datasets][gd_name] ||= []) << fact
247
+ end
248
+ end
249
+
250
+ attributes = dataset[:fields].find_all {|f| f[:type] == "attribute" || f[:type] == "connection_point"}
251
+ gd_attributes = gd_dataset['dataSet']['content']['attributes'].map {|attr| GoodData.get(attr)}
252
+ attributes.each do |attr|
253
+ attr_name = attr[:name]
254
+ unless gd_attributes.any? {|a| a['attribute']['meta']['identifier'] == "attr.#{gd_name}.#{attr_name}"}
255
+ (memo[:datasets][gd_name] ||= []) << attr
256
+ end
257
+ end
258
+ memo
259
+ end
260
+ end
261
+
262
+
263
+ def self.validate_sf_metadata(sf_client, sources)
264
+ sources.reduce({}) do |memo, source|
265
+ puts "Checking #{source[:object]}"
266
+ sf_object = source[:object]
267
+ u = sf_client.describe(sf_object)
268
+ sf_fields = u[:describeSObjectResponse][:result][:fields].map {|field| field[:name]}
269
+ fields_to_validate = source[:fields].map {|field| field[:name]}
270
+ memo[sf_object] = (fields_to_validate - sf_fields)
271
+ pp fields_to_validate - sf_fields
272
+ memo
273
+ end
274
+ end
275
+
276
+ def self.get_sf_client(params)
277
+ Salesforce::Client.new(params[:sf_login], params[:sf_password] + params[:sf_token], :server => params[:sf_server])
278
+ end
279
+
280
+ def self.download_metadata(downloaders_spec)
281
+ mods = []
282
+ c = Salesforce::Client.new(downloaders_spec["login"], downloaders_spec["password"] + downloaders_spec["token"])
283
+ downloaders_spec["input"].each do |mod|
284
+ sf_module = mod["module"]
285
+ output = mod["file"]
286
+ fields = mod["fields"]
287
+
288
+ u = c.describe(sf_module)
289
+ sf_fields = u[:describeSObjectResponse][:result][:fields]
290
+ mods << mod.merge({
291
+ "fields" => fields.map do |f|
292
+ found_field = sf_fields.find {|sf| sf[:name] == f["name"]}
293
+ # begin
294
+ if found_field.nil?
295
+ fail "Field \"#{f["name"]}\" was not found in the \"#{sf_module}\" module"
296
+ end
297
+ f.merge(found_field)
298
+ end
299
+ })
300
+ end
301
+ downloaders_spec.merge({"input" => mods})
302
+ end
303
+
304
+ def self.save_metadata(filename, data)
305
+ File.open(filename, "w") do |file|
306
+ builder = Builder::XmlMarkup.new(:target => file, :indent=>2)
307
+ builder.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
308
+ csv_metadata(builder, data)
309
+ end
310
+ end
311
+
312
+ def self.translate_field(what)
313
+ dict = {
314
+ nil => "string",
315
+ "id" => "string",
316
+ "string" => "string",
317
+ "textarea" => "string",
318
+ "email" => "string",
319
+ "phone" => "string",
320
+ "boolean" => "string",
321
+ "picklist" => "string",
322
+ "reference" => "string",
323
+ "datetime" => "date",
324
+ "date" => "date",
325
+ "currency" => "string",
326
+ "percent" => "string",
327
+ "int" => "string",
328
+ "multipicklist" => "string",
329
+ "combobox" => "string",
330
+ "attribute" => "string",
331
+ "fact" => "string"
332
+ }
333
+ fail "#{what} not included in translation dictionary from SF to Clover Types" unless dict.include?(what)
334
+ dict[what]
335
+ end
336
+
337
+ def self.translate(fields)
338
+ fields
339
+ # fields.map do |f|
340
+ # f.merge({
341
+ # :type => translate_field(f[:type])
342
+ # })
343
+ # end
344
+ end
345
+
346
+
347
+
348
+ def self.find_by_name(fields, name)
349
+ fields.find do |m|
350
+ xname = m["module"]
351
+ output = m["file"] || xname
352
+ output == name
353
+ end
354
+ end
355
+
356
+ def self.find_by_dataset(fields, name)
357
+ fields.find do |m|
358
+ md = m["module"]
359
+ file = m["file"]
360
+ dataset = m["dataset"]
361
+ (dataset || file || md) == name
362
+ end
363
+ end
364
+
365
+
366
+ def self.merge_modules(spec, base_spec)
367
+ modules = spec["input"]
368
+ resolved_modules = modules.map do |mod|
369
+ mod_name = mod["file"] || mod["module"]
370
+ fields = mod["fields"]
371
+ default = find_by_name(base_spec["input"], mod_name)
372
+ if default.nil?
373
+ mod
374
+ else
375
+ clashing_fields = ((default["fields"] || []) & (mod["fields"]))
376
+ unless clashing_fields.empty?
377
+ fail "There are fields \"#{clashing_fields.join(', ')}\" that are clashing with the default definition"
378
+ end
379
+ master_fields = default["fields"]
380
+ redefinitions = fields.find_all {|f| f.has_key?(:acts_as)}
381
+ exclusions = redefinitions.reduce([]) {|memo, e| memo.concat([*e[:acts_as]])}
382
+ mod.merge({
383
+ "fields" => (master_fields.reject {|mf| exclusions.include? mf["name"]}) + fields
384
+ })
385
+ end
386
+ end
387
+ spec.merge({
388
+ "input" => resolved_modules
389
+ })
390
+ end
391
+
392
+ def self.generate_incremental_select(spec)
393
+ if spec[:condition].nil? || spec[:condition].empty?
394
+ spec[:condition] = "SystemModstamp > ${#{spec[:id]}_START} AND SystemModstamp <= ${#{spec[:id]}_END}"
395
+ else
396
+ spec[:condition] += "AND SystemModstamp > ${#{spec[:id]}_START} AND SystemModstamp <= ${#{spec[:id]}_END}"
397
+ end
398
+ generate_select(spec)
399
+ end
400
+
401
+ def self.generate_select(spec)
402
+ fields = spec[:fields].map do |f|
403
+ f.has_key?(:multi_currency) ? "convertCurrency(#{f[:name]})" : f[:name]
404
+ end
405
+ condition = spec[:condition].nil?() ? "" : "WHERE #{spec[:condition]}"
406
+ limit = spec[:limit].nil?() ? "" : "LIMIT #{spec[:limit]}"
407
+ "SELECT #{fields.join(', ')} FROM #{spec[:object]} #{condition} #{limit}"
408
+ end
409
+
410
+ def self.normalize_module(spec)
411
+ fields = spec.has_key?("fields") ? spec["fields"].map {|f| f.kind_of?(Hash) ? f : {"name" => f} } : []
412
+ spec.merge({
413
+ "fields" => fields
414
+ })
415
+ end
416
+
417
+ def self.normalize_description(spec)
418
+ spec.merge({
419
+ "input" => spec["input"].map {|f| normalize_module(f) }
420
+ })
421
+ end
422
+
423
+ def self.sf_metadata_for_module(spec)
424
+ sf_metadata_for_fields(spec[:fields])
425
+ end
426
+
427
+ def self.sf_metadata_for_fields(spec)
428
+ spec.reduce([]) {|acc, f| acc.concat([{
429
+ :name => f[:name],
430
+ :type => f[:type] || "string"
431
+ }])}
432
+ end
433
+
434
+ def self.clover_metadata_for_module(spec)
435
+ clover_metadata_for_fields(spec[:fields])
436
+ end
437
+
438
+
439
+ def self.clover_metadata_for_fields(spec)
440
+ spec.reduce([]) do |acc, f|
441
+ transformation = f[:acts_as] ? f[:acts_as].map {|i| {:type => f[:type] || "string", :name => i}} : [{:type => f[:type] || "string", :name => f[:name]}]
442
+ acc.concat(transformation)
443
+ end
444
+ end
445
+
446
+ def self.transformation_acts_as(spec)
447
+ spec[:fields].reduce([]) do |acc, f|
448
+ transformation = f[:acts_as] ? f[:acts_as].map {|i| [f[:name], i]} : [[f[:name], f[:name]]]
449
+ acc.concat(transformation)
450
+ end
451
+ end
452
+
453
+ # def self.to_port(node, port)
454
+ # "#{to_id(node)}:#{port.to_s}"
455
+ # end
456
+
457
+ def self.to_id(node)
458
+ node[:id]
459
+ end
460
+
461
+ $id = 0
462
+ def self.get_id()
463
+ $id += 1
464
+ end
465
+
466
+ def self.create_trash_meta(builder)
467
+ builder.Metadata({:id => "trash_metadata"}) do |builder|
468
+ csv_metadata(builder, {
469
+ :name => "trash_metadata",
470
+ :fields => [{:type => "string", :name => "all"}]
471
+ })
472
+ end
473
+ end
474
+
475
+ def self.create_lookup_meta(builder)
476
+ builder.Metadata({:id => "lookup_metadata"}) do |builder|
477
+ csv_metadata(builder, {
478
+ :name => "lookup_metadata",
479
+ :fields => [{:type => "string", :name => "key"}, {:type => "string", :name => "value"}]
480
+ })
481
+ end
482
+ end
483
+
484
+
485
+ def self.create_run_graph(file, options={})
486
+ subgraphs = options[:subgraphs]
487
+
488
+ File.open(file, "w") do |file|
489
+ builder = Builder::XmlMarkup.new(:target=>file, :indent=>2)
490
+ builder.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
491
+ builder.Graph({
492
+ :name => "Run graph"
493
+ }) do
494
+ builder.Global do
495
+ property_file(builder, {:id => "workspace_params", :fileURL => "workspace.prm"})
496
+ property_file(builder, {:id => "params_params", :fileURL => "params.txt"})
497
+ create_trash_meta(builder)
498
+ create_lookup_meta(builder)
499
+
500
+ end
501
+ phase = 0
502
+ subgraphs.each do |subgraph|
503
+ builder.Phase(:number => phase+1) do
504
+ id1 = get_id
505
+ id2 = get_id
506
+ ctl = "function integer generate() {$out.0.all = \"FLOW=#{subgraph[:flow]}\";return OK;}"
507
+ build_node2(builder, GoodData::CloverGenerator::Nodes.data_generator2({:name => id1, :id => id1, :generate => ctl}))
508
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "#{id2}:0", :fromNode => "#{id1}:0", :metadata => "trash_metadata", :id => get_id()}))
509
+ build_node2(builder, GoodData::CloverGenerator::Nodes.writer2({:name => "PARAMS CSV Writer", :id => "#{id2}", :fileURL => "params.txt", :outputFieldNames => "false", :quotedStrings => "false"}))
510
+ end
511
+ builder.Phase(:number => phase+2) do
512
+
513
+ id1 = get_id
514
+ id2 = get_id
515
+ ctl = "function integer generate() {$out.0.all = \"NAME=#{subgraph[:name]}\";return OK;}"
516
+ build_node2(builder, GoodData::CloverGenerator::Nodes.data_generator2({:name => id1, :id => id1, :generate => ctl}))
517
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "#{id2}:0", :fromNode => "#{id1}:0", :metadata => "trash_metadata", :id => get_id()}))
518
+ build_node2(builder, GoodData::CloverGenerator::Nodes.writer2({:name => "PARAMS CSV Writer", :id => "#{id2}", :fileURL => "params.txt", :outputFieldNames => "false", :append => "true", :quotedStrings => "false"}))
519
+ end
520
+
521
+ builder.Phase(:number => phase+3) do
522
+ build_node2(builder, GoodData::CloverGenerator::Nodes.run_graph2({:guiName => subgraph[:name], :name => subgraph[:name], :id => subgraph[:flow], :graphName => subgraph[:file]}))
523
+ end
524
+ phase += 4
525
+ end
526
+ end
527
+ end
528
+ end
529
+
530
+ def self.create_incremental_downloader_run_graph(file, sources, options={})
531
+ # subgraphs = options[:subgraphs]
532
+
533
+ merged_sources = sources.reduce([]) do |memo, source|
534
+ merged_source = memo.find {|s| s[:object] == source[:object]}
535
+ if merged_source
536
+ merged_source[:fields] = (merged_source[:fields] + source[:fields]).uniq_by {|f| f[:name]}
537
+ else
538
+ memo.push(source)
539
+ end
540
+ memo
541
+ end
542
+
543
+ File.open(file, "w") do |file|
544
+ builder = Builder::XmlMarkup.new(:target=>file, :indent=>2)
545
+ builder.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
546
+ builder.Graph({
547
+ :name => "Run graph"
548
+ }) do
549
+ builder.Global do
550
+ property_file(builder, {:id => "workspace_params", :fileURL => "workspace.prm"})
551
+ property_file(builder, {:id => "params_params", :fileURL => "params.txt"})
552
+ create_trash_meta(builder)
553
+ create_lookup_meta(builder)
554
+ build_node2(builder, GoodData::CloverGenerator::Nodes.lookup2({:name => "gdLookup0", :id => "gdLookup0", :type => GoodData::CloverGenerator::Nodes::GD_LOOKUP, :metadata => "lookup_metadata"}))
555
+
556
+ end
557
+ phase = 0
558
+
559
+
560
+ merged_sources.each do |source|
561
+ module_name = source[:object]
562
+ file = source[:id] || module_name
563
+ dataset = file || module_name
564
+
565
+
566
+ normalize_code = <<HEREDOC
567
+ boolean done = false;
568
+ function integer count() {
569
+
570
+ if (indexOf($in.0.key, "#{dataset}_LAST_RUN") != -1) {
571
+ return 4;
572
+ }
573
+ else {
574
+ return 0;
575
+ }
576
+ }
577
+
578
+ string last_run = null;
579
+ string end_date = null;
580
+
581
+ function integer transform(integer idx) {
582
+ if (last_run == null) {
583
+ last_run = $in.0.value;
584
+ }
585
+ if (end_date == null) {
586
+ end_date = jodaDate2str(today(), "yyyy-MM-dd'T'HH:mm:ss.SSSZZ", "en_US", 'UTC');
587
+ }
588
+
589
+
590
+ if (idx == 1) {
591
+ $out.0.all = "#{dataset}_TRUNCATE_DATE=" + jodaDate2str(jodaStr2date(last_run, ["yyyy-MM-dd'T'HH:mm:ss.SSSZZ"], 'en_US', 'UTC', 'UTC'), "yyyy-MM-dd HH:mm:ss", 'en_US', 'UTC');
592
+ } else if (idx == 2) {
593
+ $out.0.all = "#{dataset}_START=" + last_run;
594
+ } else if (idx == 3) {
595
+ $out.0.all = "#{dataset}_END=" + end_date;
596
+ } else {
597
+ $out.0.all = "#{dataset}_LAST_RUN=" + end_date;
598
+ }
599
+
600
+ return OK;
601
+ }
602
+
603
+ HEREDOC
604
+
605
+
606
+
607
+
608
+ builder.Phase(:number => phase += 1) do
609
+ generate_func = <<HEREDOC
610
+ function integer generate() {
611
+ $out.0.key = "#{dataset}_LAST_RUN";
612
+ $out.0.value = "1970-01-01T00:00:00.000+00:00";
613
+ return OK;
614
+ }
615
+ HEREDOC
616
+
617
+ join_func = <<HEREDOC
618
+ function integer transform() {
619
+ $out.0.key = nvl2($in.1.value, $in.1.key, $in.0.key);
620
+ $out.0.value = nvl2($in.1.value, $in.1.value, $in.0.value);
621
+ return OK;
622
+ }
623
+ HEREDOC
624
+
625
+ build_node2(builder, GoodData::CloverGenerator::Nodes.data_generator2({:name => "generator_#{dataset}", :id => "generator_#{dataset}", :generate => generate_func}))
626
+ build_node2(builder, GoodData::CloverGenerator::Nodes.lookup_reader_writer2({:lookupTable => "gdLookup0", :id => "gd_lookup_reader_#{dataset}" }))
627
+ build_node2(builder, GoodData::CloverGenerator::Nodes.hash_join2({:id => "join_#{dataset}", :joinType => "leftOuter", :joinKey => "$key=$key", :transformation => join_func}))
628
+
629
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "join_#{dataset}:0", :fromNode => "generator_#{dataset}:0", :metadata => "lookup_metadata", :id => get_id()}))
630
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "join_#{dataset}:1", :fromNode => "gd_lookup_reader_#{dataset}:0", :metadata => "lookup_metadata", :id => get_id()}))
631
+
632
+ build_node2(builder, GoodData::CloverGenerator::Nodes.normalizer2({:name => "normalizer_#{dataset}", :id => "normalizer_#{dataset}", :normalize => normalize_code }))
633
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "normalizer_#{dataset}:0", :fromNode => "join_#{dataset}:0", :metadata => "lookup_metadata", :id => get_id()}))
634
+
635
+ build_node2(builder, GoodData::CloverGenerator::Nodes.writer2({:quotedStrings => "false", :name => "params_writer_#{dataset}", :id => "params_writer_#{dataset}", :fileURL => "params.txt", :outputFieldNames => "false", :append => "true"}))
636
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "params_writer_#{dataset}:0", :fromNode => "normalizer_#{dataset}:0", :metadata => "trash_metadata", :id => get_id()}))
637
+ end
638
+ end
639
+ builder.Phase(:number => phase += 1) do
640
+ build_node2(builder, GoodData::CloverGenerator::Nodes.run_graph2({:guiName => "incremental", :name => "incremental_downloaders", :id => "downlaoders", :graphName => "graphs/incremental.grf"}))
641
+ end
642
+
643
+ end
644
+ end
645
+ end
646
+
647
+
648
+
649
+ def self.build_attribute_df(dataset, attribute)
650
+ {
651
+ :gd_dataset_attribute_display_form => {
652
+ :id => "label.#{dataset[:gd_name] || dataset[:id]}.#{attribute[:name]}",
653
+ :title => "label",
654
+ :prettyId => "label_#{dataset[:gd_name] || dataset[:id]}_#{attribute[:name]}",
655
+ :referenceKey => true,
656
+ :assignedMetadataField => attribute[:meta] || attribute[:name]
657
+ }
658
+ }
659
+ end
660
+
661
+ def self.build_label_df(dataset, attribute, label)
662
+ {
663
+ :gd_dataset_attribute_display_form => {
664
+ :id => "label.#{dataset[:gd_name] || dataset[:id]}.#{attribute[:name]}.#{label[:name]}",
665
+ :title => "label",
666
+ :prettyId => "label_#{dataset[:gd_name] || dataset[:id]}_#{attribute[:name]}_#{label[:name]}",
667
+ :referenceKey => false,
668
+ :assignedMetadataField => label[:meta] || label[:name]
669
+ }
670
+ }
671
+ end
672
+
673
+ def self.build_gd_dataset_loader_json(dataset)
674
+ # binding.pry
675
+ {
676
+ :gd_dataset => {
677
+ :attributes => dataset[:fields].find_all {|d| d[:type] == "attribute" || d[:type] == "connection_point"}.map do |attribute|
678
+ {
679
+ :gd_dataset_attribute => {
680
+ :id => "attr.#{dataset[:gd_name] || dataset[:id]}.#{attribute[:name]}",
681
+ :title => "XXX",
682
+ :prettyId => "attr_#{dataset[:gd_name] || dataset[:id]}_#{attribute[:name]}",
683
+ :selectedDisplayForm => build_attribute_df(dataset, attribute),
684
+ :assignedMetadataField => attribute[:meta] || attribute[:name],
685
+ :displayForms => dataset[:fields].find_all {|f| f[:type] == "label" && f[:for] == attribute[:name]}.map do |label|
686
+ build_label_df(dataset, attribute, label)
687
+ end.concat([build_attribute_df(dataset, attribute)])
688
+ }
689
+ }
690
+ end,
691
+ :simpleFacts => dataset[:fields].find_all {|d| d[:type] == "fact"}.map do |fact|
692
+ {
693
+ :gd_dataset_fact => {
694
+ :id => "fact.#{dataset[:gd_name] || dataset[:id]}.#{fact[:name]}",
695
+ :title => "FFF",
696
+ :prettyId => "fact_#{dataset[:gd_name] || dataset[:id]}_#{fact[:name]}",
697
+ :type => "DECIMAL",
698
+ :assignedMetadataField => fact[:meta]
699
+ }
700
+ }
701
+ end,
702
+ :dateFacts => dataset[:fields].find_all {|d| d[:type] == "date"}.map do |date_attribute|
703
+ {
704
+ :gd_dataset_fact => {
705
+ :id => "dt.#{dataset[:gd_name] || dataset[:id]}.#{date_attribute[:fact] || date_attribute[:name]}",
706
+ :title => "TTT",
707
+ :prettyId => "dt_#{dataset[:gd_name] || dataset[:id]}_#{date_attribute[:fact] || date_attribute[:name]}",
708
+ :type => "DATE",
709
+ :assignedMetadataField => nil
710
+ }
711
+ }
712
+ end,
713
+ :dateAttributes => dataset[:fields].find_all {|d| d[:type] == "date"}.map do |date_attribute|
714
+ {
715
+ :gd_dataset_date_attribute => {
716
+ :id => "#{date_attribute[:dd]}.date",
717
+ :title => "DDD",
718
+ :prettyId => "#{date_attribute[:name]}",
719
+ :assignedMetadataField => date_attribute[:meta],
720
+ :associatedFact => {
721
+ :gd_dataset_fact => {
722
+ :id => "dt.#{dataset[:gd_name] || dataset[:id]}.#{date_attribute[:name]}",
723
+ :title => "TTT",
724
+ :prettyId => "dt_#{dataset[:gd_name] || dataset[:id]}_#{date_attribute[:name]}",
725
+ :type => "DATE",
726
+ :assignedMetadataField => nil
727
+ }
728
+ },
729
+ :displayForms => [
730
+ {
731
+ :gd_dataset_attribute_display_form => {
732
+ :id => "#{date_attribute[:dd]}.date.yyyymmdd",
733
+ :title => "yyyy-mm-dd (#{date_attribute[:dd]})",
734
+ :prettyId => "#{date_attribute[:dd]}_date_yyyymmdd",
735
+ :referenceKey => true,
736
+ :assignedMetadataField => nil
737
+ }
738
+ }
739
+ ],
740
+ :selectedDisplayForm => {
741
+ :gd_dataset_attribute_display_form => {
742
+ :id => "#{date_attribute[:dd]}.date.yyyymmdd",
743
+ :title => "yyyy-mm-dd (#{date_attribute[:dd]})",
744
+ :prettyId => "#{date_attribute[:dd]}_date_yyyymmdd",
745
+ :referenceKey => true,
746
+ :assignedMetadataField => nil
747
+ }
748
+ }
749
+ }
750
+ }
751
+ end,
752
+ :referencedAttributes => dataset[:fields].find_all {|d| d[:type] == "reference"}.map do |ref_attribute|
753
+ {
754
+ :gd_dataset_attribute => {
755
+ :id => "attr.#{ref_attribute[:schema]}.#{ref_attribute[:ref]}",
756
+ :title => "attr.#{ref_attribute[:schema]}.#{ref_attribute[:ref]}",
757
+ :prettyId => "attr_#{ref_attribute[:schema]}_#{ref_attribute[:ref]}",
758
+ :displayForms => [],
759
+ :selectedDisplayForm => {
760
+ :gd_dataset_attribute_display_form => {
761
+ :id => "label.#{ref_attribute[:schema]}.#{ref_attribute[:ref]}",
762
+ :title => "attr.#{ref_attribute[:schema]}.#{ref_attribute[:ref]}",
763
+ :prettyId => "attr_#{ref_attribute[:schema]}_#{ref_attribute[:ref]}",
764
+ :referenceKey => false,
765
+ :assignedMetadataField => nil
766
+ }
767
+ },
768
+ :assignedMetadataField => ref_attribute[:meta]
769
+ }
770
+ }
771
+ end,
772
+ :fieldsWithMetadataConflict => [],
773
+ :entitiesWithoutMetadata => [],
774
+ :entitiesWithoutValue => [],
775
+ :datesWithFactConflict => [],
776
+ :unassigneddateFacts => [],
777
+ :datasetTitle => dataset[:gd_name] || dataset[:id],
778
+ :datasetId => "dataset.#{dataset[:gd_name] || dataset[:id]}"
779
+ }
780
+ }
781
+ end
782
+
783
+ def self.create_es_write_json(spec)
784
+ {
785
+ :entityName => spec[:id] || spec[:object],
786
+ :fieldsMapping => spec[:fields].reduce({}) do |memo, f|
787
+
788
+ if f.has_key?(:acts_as)
789
+
790
+ f[:acts_as].each do |a|
791
+ type = case a
792
+ when "Id"
793
+ "recordid"
794
+ when "timestamp"
795
+ "timestamp"
796
+ else
797
+ f[:type] || "attribute"
798
+ end
799
+ memo[a] = {
800
+ :name => a,
801
+ :type => type
802
+ }
803
+ end
804
+ else
805
+ type = case f[:name]
806
+ when "Id"
807
+ "recordid"
808
+ when "timestamp"
809
+ "timestamp"
810
+ else
811
+ f[:type] || "attribute"
812
+ end
813
+
814
+ memo[f[:name]] = {
815
+ :name => f[:name],
816
+ :type => (f[:name] == "Id" ? "recordid" : f[:type] || "attribute")
817
+ }
818
+ end
819
+ memo
820
+ end
821
+
822
+
823
+ }
824
+ end
825
+
826
+
827
+ def self.create_moving_graph(file, options={})
828
+ source = options[:source]
829
+ target = options[:target]
830
+ operation = options[:operation]
831
+ force = options[:force] || false
832
+
833
+ File.open(file, "w") do |file|
834
+ builder = Builder::XmlMarkup.new(:target=>file, :indent=>2)
835
+ builder.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
836
+ builder.Graph({
837
+ :name => "File Copy"
838
+ }) do
839
+ builder.Global do
840
+ builder.Metadata({:id => "list_metadata"}) do |builder|
841
+ csv_metadata(builder, {
842
+ :name => "list_metadata",
843
+ :fields => [{:name=>"filePath", :type=>"string"}]
844
+ })
845
+ end
846
+ property_file(builder, {:id => "workspace_params", :fileURL => "workspace.prm"})
847
+ end
848
+ builder.Phase(:number => 0) do
849
+
850
+ transformation_source = "function integer transform() {\n" + ([["filePath", "filePath"]].map {|t| "$out.0.#{t.last} = $in.0.#{t.first};"}.join("\n")) + "\nreturn OK;\n}"
851
+ build_node2(builder, GoodData::CloverGenerator::Nodes.file_list2(:baseURL => target, :id => "file_list", :transformation => transformation_source))
852
+
853
+ build_node2(builder, GoodData::CloverGenerator::Nodes.file_delete2(:baseURL => "${filePath}", :id => "file_delete"))
854
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "file_delete:0", :fromNode => "file_list:0", :metadata => "list_metadata", :id => get_id()}))
855
+ end
856
+ builder.Phase(:number => 1) do
857
+ build_node2(builder, GoodData::CloverGenerator::Nodes.file_copy2({:sourcePath => source, :targetPath => target, :operation => operation, :id => "file_copy"}))
858
+ end
859
+ end
860
+ end
861
+ end
862
+
863
+ def self.create_uploading_graph(file, options={})
864
+ metadata = options[:metadata]
865
+ dataset_infos = [options[:datasets]]
866
+ input_file = options[:input_file]
867
+
868
+ File.open(file, "w") do |file|
869
+ builder = Builder::XmlMarkup.new(:target=>file, :indent=>2)
870
+ builder.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
871
+ builder.Graph({
872
+ :name => "Goodsales Downloader"
873
+ }) do
874
+ builder.Global do
875
+ property_file(builder, {:id => "workspace_params", :fileURL => "workspace.prm"})
876
+ dataset_infos.each do |dataset_info|
877
+ dataset = dataset_info[:id]
878
+ builder.Metadata({:id => "#{dataset}_load"}) do |builder|
879
+ csv_metadata(builder, metadata)
880
+ end
881
+ end
882
+ end
883
+ builder.Phase(:number => 0) do
884
+ dataset_infos.each do |dataset_info|
885
+ dataset = dataset_info[:id]
886
+ gd_dataset = dataset_info[:gd_name] || dataset_info[:id]
887
+ to_svinstvo = build_gd_dataset_loader_json(dataset_info)
888
+ build_node2(builder, GoodData::CloverGenerator::Nodes.reader2({:name => "#{dataset} CSV Loader", :id => "#{dataset}_loader", :fileURL => "${PROJECT}/data/#{dataset}.csv"}))
889
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "#{dataset}_load:0", :fromNode => "#{dataset}_loader:0", :metadata => "#{dataset}_load", :id => get_id()}))
890
+ build_node2(builder, GoodData::CloverGenerator::Nodes.gd_loader2({:name => "#{dataset} Loader", :id => "#{dataset}_load", :dataset => "dataset.#{gd_dataset}", :datasetFieldMappings => to_svinstvo.to_json}))
891
+ end
892
+ end
893
+ end
894
+ end
895
+ end
896
+
897
+ def self.create_es_uploading_graph(file, options={})
898
+ metadata = options[:metadata]
899
+ dataset_infos = [options[:datasets]]
900
+ input_file = options[:input_file]
901
+
902
+ File.open(file, "w") do |file|
903
+ builder = Builder::XmlMarkup.new(:target=>file, :indent=>2)
904
+ builder.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
905
+ builder.Graph({
906
+ :name => "Goodsales Downloader"
907
+ }) do
908
+ builder.Global do
909
+ property_file(builder, {:id => "workspace_params", :fileURL => "workspace.prm"})
910
+ dataset_infos.each do |dataset_info|
911
+ dataset = dataset_info[:id]
912
+ builder.Metadata({:id => "#{dataset}_load"}) do |builder|
913
+ csv_metadata(builder, metadata)
914
+ end
915
+ end
916
+ end
917
+ builder.Phase(:number => 0) do
918
+ dataset_infos.each do |dataset_info|
919
+ dataset = dataset_info[:id]
920
+ gd_dataset = dataset_info[:gd_name] || dataset_info[:id]
921
+ to_svinstvo = build_gd_dataset_loader_json(dataset_info)
922
+ build_node2(builder, GoodData::CloverGenerator::Nodes.reader2({:name => "#{dataset} CSV Loader", :id => "#{dataset}_loader", :fileURL => "${PROJECT}/data/#{dataset}.csv"}))
923
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "#{dataset}_load:0", :fromNode => "#{dataset}_loader:0", :metadata => "#{dataset}_load", :id => get_id()}))
924
+ build_node2(builder, GoodData::CloverGenerator::Nodes.gd_loader2({:name => "#{dataset} Loader", :id => "#{dataset}_load", :dataset => "dataset.#{gd_dataset}", :datasetFieldMappings => to_svinstvo.to_json}))
925
+ end
926
+ end
927
+ end
928
+ end
929
+ end
930
+
931
+ def self.create_metadata(mod)
932
+ module_name = mod[:object]
933
+ file = mod["file"] || module_name
934
+ # dataset = mod["dataset"] || file || module_name
935
+ {
936
+ :name => file,
937
+ :fields => translate(clover_metadata_for_module(mod))
938
+ }
939
+ end
940
+
941
+
942
+ def self.create_sf_downloading_graph(file, sources, options={})
943
+ metadata = options[:metadata]
944
+ s3_backup = true && options[:s3_backup]
945
+
946
+ File.open(file, "w") do |file|
947
+ builder = Builder::XmlMarkup.new(:target=>file, :indent=>2)
948
+ builder.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
949
+ builder.Graph({
950
+ :name => "Goodsales Downloader"
951
+ }) do
952
+ builder.Global do
953
+ sources.each do |mod|
954
+ module_name = mod[:object]
955
+ file = mod[:id] || module_name
956
+ dataset = file || module_name
957
+
958
+ builder.Metadata({:id => "#{file}_sf_metadata"}) do |builder|
959
+ csv_metadata(builder, {
960
+ :name => "#{file}_sf_metadata",
961
+ :fields => translate(sf_metadata_for_module(mod))
962
+ })
963
+ end
964
+ builder.Metadata({:id => "#{file}_clover_metadata"}) do |builder|
965
+ csv_metadata(builder, {
966
+ :name => "#{file}_clover_metadata",
967
+ :fields => translate(clover_metadata_for_module(mod))
968
+ })
969
+ end
970
+ end
971
+
972
+ sf_connection(builder, {})
973
+ property(builder, {:id => "SFDC_CLIENT_ID", :value => "gooddata/gooddata/"})
974
+ property(builder, {:id => "SFDC_LOGIN_HOSTNAME", :value => options[:sf_server] || "login.salesforce.com"})
975
+ property(builder, {:id => "SFDC_NAME", :value => "Salesforce connection"})
976
+ property(builder, {:id => "SFDC_PASSWORD", :value => options[:password]})
977
+ property(builder, {:id => "SFDC_TOKEN", :value => options[:token]})
978
+ property(builder, {:id => "SFDC_USERNAME", :value => options[:login]})
979
+ property_file(builder, {:id => "workspace_params", :fileURL => "workspace.prm"})
980
+ end
981
+
982
+ builder.Phase(:number => 0) do
983
+ sources.each do |mod|
984
+ module_name = mod[:object]
985
+ file = mod[:id] || module_name
986
+ dataset = file || module_name
987
+
988
+ fields = mod[:fields]
989
+ mapping = "{\"xmlFieldsMapping\":{\"xmlFields\":["
990
+ add = fields.map do |f|
991
+ "{\"xmlFieldMapping\":{\"name\":\"#{f[:name]}\",\"label\":\"#{f[:label]}\",\"xmlPath\":\"#{f[:name]}\",\"metadataField\":\"#{f[:name]}\"}}"
992
+ end
993
+
994
+ stuff = mapping + add.join(",") + "]}}"
995
+
996
+ build_node2(builder, GoodData::CloverGenerator::Nodes.sfdc_reader2({:name => "#{file} SF Writer", :id => "#{file}_sf", :soql => generate_select(mod), :sfdcConnection => "SFDC", :fieldsMapping => stuff}))
997
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "#{file}_reformat:0", :fromNode => "#{file}_sf:0", :metadata => "#{file}_sf_metadata", :id => get_id()}))
998
+
999
+ transformation_source = "function integer transform() {\n" + (transformation_acts_as(mod).map {|t| "$out.0.#{t.last} = $in.0.#{t.first};"}.join("\n")) + "\nreturn OK;\n}"
1000
+ build_node2(builder, GoodData::CloverGenerator::Nodes.reformat2({:name => "#{file} Reformat", :id => "#{file}_reformat", :transformation => transformation_source}))
1001
+
1002
+
1003
+ build_node2(builder, GoodData::CloverGenerator::Nodes.copy2({:name => "#{file} copy", :id => "#{file}_copy"}))
1004
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "#{file}_copy:0", :fromNode => "#{file}_reformat:0", :metadata => "#{file}_clover_metadata", :id => get_id()}))
1005
+ build_node2(builder, GoodData::CloverGenerator::Nodes.writer2({:name => "#{file} CSV Writer", :id => "#{file}_csv", :fileURL => "data/#{dataset.downcase}.csv", :outputFieldNames => "true"}))
1006
+ if s3_backup then build_node2(builder, GoodData::CloverGenerator::Nodes.writer2({:name => "#{file} s3 Writer", :id => "#{file}_s3", :fileURL => "bucket", :outputFieldNames => true, :quotedStrings => false})) end
1007
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "#{file}_csv:0", :fromNode => "#{file}_copy:0", :metadata => "#{file}_clover_metadata", :id => get_id()}))
1008
+ if s3_backup then build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "#{file}_s3:0", :fromNode => "#{file}_copy:1", :metadata => "#{file}_clover_metadata", :id => get_id()})) end
1009
+ end
1010
+ end
1011
+ end
1012
+ end
1013
+ end
1014
+
1015
+ def self.create_es_downloading_graph(file, sources, options={})
1016
+ metadata = options[:metadata]
1017
+ s3_backup = true && options[:s3_backup]
1018
+
1019
+ File.open(file, "w") do |file|
1020
+ builder = Builder::XmlMarkup.new(:target=>file, :indent=>2)
1021
+ builder.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
1022
+ builder.Graph({
1023
+ :name => "Goodsales Downloader"
1024
+ }) do
1025
+ builder.Global do
1026
+ sources.each do |mod|
1027
+ module_name = mod[:object]
1028
+ file = mod[:id] || module_name
1029
+ dataset = file || module_name
1030
+
1031
+ es_metadata = GoodData::CloverGenerator::DSL::Metadata.new({
1032
+ :name => "#{file}_es_metadata",
1033
+ :fields => clover_metadata_for_module(mod)
1034
+ })
1035
+
1036
+ builder.Metadata({:id => "#{file}_es_metadata"}) do |builder|
1037
+ csv_metadata(builder, es_metadata.change do |m|
1038
+ m.remove("timestamp")
1039
+ end.to_hash)
1040
+ end
1041
+ end
1042
+
1043
+ property_file(builder, {:id => "workspace_params", :fileURL => "workspace.prm"})
1044
+ end
1045
+
1046
+ builder.Phase(:number => 0) do
1047
+ sources.each do |mod|
1048
+ module_name = mod[:object]
1049
+ file = mod[:id] || module_name
1050
+ dataset = file || module_name
1051
+
1052
+ es_metadata = GoodData::CloverGenerator::DSL::Metadata.new({
1053
+ :name => "#{file}_es_metadata",
1054
+ :fields => clover_metadata_for_module(mod)
1055
+ })
1056
+ es_metadata = es_metadata.change do |m|
1057
+ m.remove("timestamp")
1058
+ end.to_hash
1059
+
1060
+ fields = es_metadata[:fields]
1061
+
1062
+ e = Es::Entity.new("x", {
1063
+ :file => "none",
1064
+ :fields => fields.map do |f|
1065
+ name = f[:name]
1066
+ if name == "Id"
1067
+ Es::Field.new('Id', 'recordid')
1068
+ else
1069
+ Es::Field.new(name, 'attribute')
1070
+ end
1071
+ end,
1072
+ :timeframe => Es::Timeframe::parse("latest")
1073
+ })
1074
+
1075
+
1076
+ stuff = {
1077
+ :entityName => dataset,
1078
+ :fieldsMapping => fields.inject({}) do |memo, field|
1079
+ name = field[:name]
1080
+ memo[name] = name
1081
+ memo
1082
+ end,
1083
+ :eventStoreFieldToTypeMapping => fields.inject({}) do |memo, field|
1084
+ name = field[:name]
1085
+ if name == "Id"
1086
+ memo[name] = "recordid"
1087
+ else
1088
+ memo[name] = "attribute"
1089
+ end
1090
+
1091
+ memo
1092
+ end,
1093
+ :outputMetadataName => "#{file}_es_metadata"
1094
+ }
1095
+
1096
+ readmap = {
1097
+ :columns => e.to_extract_fragment('pid')["readTask"]["readMap"].first[:columns],
1098
+ :populates => e.to_extract_fragment('pid')["readTask"]["readMap"].first[:populates]
1099
+ }
1100
+
1101
+ # binding.pry
1102
+ build_node2(builder, GoodData::CloverGenerator::Nodes.es_reader2({:name => "#{file} ES Reader", :id => "#{file}_es", :entityFieldsMapping => stuff.to_json , :readMap => readmap.to_json}))
1103
+ build_node2(builder, GoodData::CloverGenerator::Nodes.copy2({:name => "#{file} copy", :id => "#{file}_copy"}))
1104
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "#{file}_copy:0", :fromNode => "#{file}_es:0", :metadata => "#{file}_es_metadata", :id => get_id()}))
1105
+ build_node2(builder, GoodData::CloverGenerator::Nodes.writer2({:name => "#{file} CSV Writer", :id => "#{file}_csv", :fileURL => "${PROJECT}/data/#{dataset.downcase}.csv", :outputFieldNames => "true", :makeDirs => "true"}))
1106
+ if s3_backup then build_node2(builder, GoodData::CloverGenerator::Nodes.writer2({:name => "#{file} s3 Writer", :id => "#{file}_s3", :fileURL => "bucket", :outputFieldNames => true, :quotedStrings => false})) end
1107
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "#{file}_csv:0", :fromNode => "#{file}_copy:0", :metadata => "#{file}_es_metadata", :id => get_id()}))
1108
+ if s3_backup then build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "#{file}_s3:0", :fromNode => "#{file}_copy:1", :metadata => "#{file}_es_metadata", :id => get_id()})) end
1109
+ end
1110
+ end
1111
+ end
1112
+ end
1113
+ end
1114
+
1115
+ def self.create_incremental_downloading_graph(file, sources, options={})
1116
+ metadata = options[:metadata]
1117
+ store = options[:store] || "${GDC_EVENTSTORE}"
1118
+
1119
+
1120
+ merged_sources = sources.reduce([]) do |memo, source|
1121
+ merged_source = memo.find {|s| s[:object] == source[:object]}
1122
+ if merged_source
1123
+ merged_source[:fields] = (merged_source[:fields] + source[:fields]).uniq_by {|f| f[:name]}
1124
+ else
1125
+ memo.push(source)
1126
+ end
1127
+ memo
1128
+ end
1129
+
1130
+ File.open(file, "w") do |file|
1131
+ builder = Builder::XmlMarkup.new(:target=>file, :indent=>2)
1132
+ builder.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
1133
+ builder.Graph({:name => "Goodsales incremental Downloader"}) do
1134
+ builder.Global do
1135
+ property_file(builder, {:id => "params_params", :fileURL => "params.txt"})
1136
+ property_file(builder, {:id => "workspace_params", :fileURL => "workspace.prm"})
1137
+
1138
+ create_lookup_meta(builder)
1139
+ merged_sources.each do |mod|
1140
+ module_name = mod[:object]
1141
+ file = mod[:id] || module_name
1142
+
1143
+ sf_metadata = GoodData::CloverGenerator::DSL::Metadata.new({
1144
+ :name => "#{file}_sf_metadata",
1145
+ :fields => sf_metadata_for_module(mod)
1146
+ })
1147
+
1148
+ clover_metadata = GoodData::CloverGenerator::DSL::Metadata.new({
1149
+ :name => "#{file}_clover_metadata",
1150
+ :fields => clover_metadata_for_module(mod)
1151
+ })
1152
+
1153
+
1154
+ builder.Metadata({:id => "#{file}_sf_metadata"}) do |builder|
1155
+ csv_metadata(builder, sf_metadata.to_hash)
1156
+ end
1157
+
1158
+ builder.Metadata({:id => "#{file}_clover_metadata"}) do |builder|
1159
+ csv_metadata(builder, clover_metadata.to_hash)
1160
+ end
1161
+
1162
+ begin
1163
+ builder.Metadata({:id => "#{file}_es_metadata"}) do |builder|
1164
+ csv_metadata(builder, clover_metadata.change do |m|
1165
+ m.remove("timestamp")
1166
+ m.add(:name => "timestamp", :type => "date")
1167
+ end.to_hash)
1168
+ end
1169
+ rescue GoodData::CloverGenerator::DSL::RemoveMetadataFieldError => e
1170
+ exit_now!("Removing field \"#{e.field}\" failed from metadata \"#{e.metadata.name}\" for source \"#{file}\".")
1171
+ end
1172
+ # build_node2(builder, GoodData::CloverGenerator::Nodes.lookup2({:lookupTable => "params_lookup", :fileURL => "${PROJECT}/params.txt", :id => "params_lookup_id", :key => "key", :metadata => "lookup_metadata", :name => "params_lookup"}))
1173
+ build_node2(builder, GoodData::CloverGenerator::Nodes.lookup2({:name => "gdLookup0", :id => "gdLookup0", :type => GoodData::CloverGenerator::Nodes::GD_LOOKUP, :metadata => "lookup_metadata"}))
1174
+
1175
+ end
1176
+
1177
+ sf_connection(builder, {})
1178
+ property(builder, {:id => "SFDC_CLIENT_ID", :value => "gooddata/gooddata/"})
1179
+ property(builder, {:id => "SFDC_LOGIN_HOSTNAME", :value => options[:sf_server] || "login.salesforce.com"})
1180
+ property(builder, {:id => "SFDC_NAME", :value => "Salesforce connection"})
1181
+ property(builder, {:id => "SFDC_PASSWORD", :value => options[:password]})
1182
+ property(builder, {:id => "SFDC_TOKEN", :value => options[:token]})
1183
+ property(builder, {:id => "SFDC_USERNAME", :value => options[:login]})
1184
+ property_file(builder, {:id => "workspace_params", :fileURL => "workspace.prm"})
1185
+ end
1186
+
1187
+
1188
+
1189
+
1190
+ phase = 1
1191
+
1192
+ merged_sources.each do |mod|
1193
+
1194
+ module_name = mod[:object]
1195
+ file = mod[:id] || module_name
1196
+ dataset = file || module_name
1197
+ s3_backup = true
1198
+
1199
+
1200
+ builder.Phase(:number => phase += 1) do
1201
+ build_node2(builder, GoodData::CloverGenerator::Nodes.es_truncate2({:guiName => dataset, :store => store, :entity => dataset, :timestamp => "${#{dataset}_TRUNCATE_DATE}", :name => "#{module_name} es truncate", :id => "#{module_name}_es_truncate"}))
1202
+ end
1203
+
1204
+ builder.Phase(:number => phase += 1) do
1205
+
1206
+
1207
+ fields = mod[:fields]
1208
+ mapping = "{\"xmlFieldsMapping\":{\"xmlFields\":["
1209
+ add = fields.map do |f|
1210
+ "{\"xmlFieldMapping\":{\"name\":\"#{f[:name]}\",\"label\":\"#{f[:label]}\",\"xmlPath\":\"#{f[:name]}\",\"metadataField\":\"#{f[:name]}\"}}"
1211
+ end
1212
+
1213
+ stuff = mapping + add.join(",") + "]}}"
1214
+
1215
+ build_node2(builder, GoodData::CloverGenerator::Nodes.sfdc_reader2({:name => "#{file} SF Writer", :id => "#{file}_sf", :soql => generate_incremental_select(mod), :sfdcConnection => "SFDC", :fieldsMapping => stuff, :mandatoryFields => fields.map {|f| f[:name] + ";"}.join}))
1216
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "#{file}_reformat:0", :fromNode => "#{file}_sf:0", :metadata => "#{file}_sf_metadata", :id => get_id()}))
1217
+
1218
+ transformation_source = "function integer transform() {\n" + (transformation_acts_as(mod).map {|t| "$out.0.#{t.last} = $in.0.#{t.first};"}.join("\n")) + "\nreturn OK;\n}"
1219
+ es_transformation_source = "function integer transform() {\n$out.0.* = $in.0.*;\n$out.0.timestamp = str2date($in.0.timestamp,\"joda:yyyy-MM-dd'T'HH:mm:ss.SSSZZ\");;\nreturn OK;\n}"
1220
+
1221
+ build_node2(builder, GoodData::CloverGenerator::Nodes.reformat2({:name => "#{file} Reformat", :id => "#{file}_reformat", :transformation => transformation_source}))
1222
+
1223
+
1224
+ build_node2(builder, GoodData::CloverGenerator::Nodes.copy2({:name => "#{file} copy", :id => "#{file}_copy"}))
1225
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "#{file}_copy:0", :fromNode => "#{file}_reformat:0", :metadata => "#{file}_clover_metadata", :id => get_id()}))
1226
+ build_node2(builder, GoodData::CloverGenerator::Nodes.writer2({:enabled => "disabled", :name => "#{file} CSV Writer", :id => "#{file}_csv", :fileURL => "data/#{dataset.downcase}.csv", :outputFieldNames => "true"}))
1227
+
1228
+ build_node2(builder, GoodData::CloverGenerator::Nodes.reformat2({:name => "#{file} Reformat", :id => "#{file}_es_reformat", :transformation => es_transformation_source}))
1229
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "#{file}_es_reformat:0", :fromNode => "#{file}_copy:1", :metadata => "#{file}_clover_metadata", :id => get_id()}))
1230
+
1231
+ if s3_backup then
1232
+ build_node2(builder, GoodData::CloverGenerator::Nodes.writer2({:enabled => "disabled", :name => "#{file} s3 Writer", :id => "#{file}_s3", :fileURL => "https://${S3_ACCESS_KEY_ID}:\`replace(\"${S3_SECRET_ACCESS_KEY}\",\"/\",\"%2F\")\`@${S3_BUCKETNAME}.s3.amazonaws.com/${GDC_PROJECT_ID}/#{file}", :outputFieldNames => true, :quotedStrings => false}))
1233
+ end
1234
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "#{file}_csv:0", :fromNode => "#{file}_copy:0", :metadata => "#{file}_clover_metadata", :id => get_id()}))
1235
+ if s3_backup then
1236
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "#{file}_s3:0", :fromNode => "#{file}_copy:2", :metadata => "#{file}_clover_metadata", :id => get_id()}))
1237
+ end
1238
+
1239
+
1240
+ build_node2(builder, GoodData::CloverGenerator::Nodes.sort2({:sortKey => "timestamp(a)",:name => "#{file} es Sort", :id => "#{file}_es_sort"}))
1241
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "#{file}_es_sort:0", :fromNode => "#{file}_es_reformat:0", :metadata => "#{file}_es_metadata", :id => get_id()}))
1242
+
1243
+ build_node2(builder, GoodData::CloverGenerator::Nodes.es_writer2({:name => "#{file} es Writer", :id => "#{file}_es", :store => store, :entityFieldsMapping => create_es_write_json(mod).to_json}))
1244
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "#{file}_es:0", :fromNode => "#{file}_es_sort:0", :metadata => "#{file}_es_metadata", :id => get_id()}))
1245
+
1246
+
1247
+ end
1248
+
1249
+ builder.Phase(:number => phase += 1) do
1250
+ generate_func = <<HEREDOC
1251
+ function integer generate() {
1252
+ date time_end = jodaStr2date("${#{dataset}_END}",["yyyy-MM-dd'T'HH:mm:ss.SSSZZ"], 'en_US', 'UTC', 'UTC');
1253
+ $out.0.key = "#{dataset}_LAST_RUN";
1254
+ $out.0.value = jodaDate2str(time_end,"yyyy-MM-dd'T'HH:mm:ss.SSSZZ", 'en_US', 'UTC');
1255
+ return OK;
1256
+ }
1257
+ HEREDOC
1258
+
1259
+ build_node2(builder, GoodData::CloverGenerator::Nodes.data_generator2({:guiName => dataset, :name => "generator_#{phase}", :id => "generator_#{phase}", :generate => generate_func}))
1260
+ build_node2(builder, GoodData::CloverGenerator::Nodes.lookup_reader_writer2({:guiName => dataset, :lookupTable => "gdLookup0", :id => "gd_lookup_reader_#{phase}" }))
1261
+ build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "gd_lookup_reader_#{phase}:0", :fromNode => "generator_#{phase}:0", :metadata => "lookup_metadata", :id => get_id()}))
1262
+
1263
+
1264
+ end
1265
+
1266
+ end
1267
+
1268
+
1269
+
1270
+ # builder.Phase(:number => phase += 1) do
1271
+ # build_node2(builder, GoodData::CloverGenerator::Nodes.data_generator2({:guiName => "generator1", :name => "generator1", :id => "generator1", :generate => generate_func}))
1272
+ # build_node2(builder, GoodData::CloverGenerator::Nodes.lookup_reader_writer2({:lookupTable => "gdLookup0", :id => "gd_lookup_reader", :graphName => "incremental.grf"}))
1273
+ # build_node2(builder, GoodData::CloverGenerator::Nodes.edge2({:toNode => "gd_lookup_reader:0", :fromNode => "generator1:0", :metadata => "lookup_metadata", :id => get_id()}))
1274
+
1275
+ # end
1276
+
1277
+
1278
+ end
1279
+ end
1280
+ end
1281
+
1282
+ end
1283
+ end