gooddata 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. checksums.yaml +13 -5
  2. data/.rubocop.yml +23 -0
  3. data/.travis.yml +9 -4
  4. data/CLI.md +439 -0
  5. data/Gemfile +0 -1
  6. data/README.md +2 -2
  7. data/Rakefile +60 -8
  8. data/doc/templates/default/module/setup.rb +1 -1
  9. data/examples.rb +2 -0
  10. data/gooddata +2 -0
  11. data/gooddata.gemspec +12 -8
  12. data/lib/gooddata.rb +0 -2
  13. data/lib/gooddata/bricks/base_downloader.rb +52 -47
  14. data/lib/gooddata/bricks/brick.rb +20 -31
  15. data/lib/gooddata/bricks/bricks.rb +1 -1
  16. data/lib/gooddata/bricks/middleware/base_middleware.rb +9 -7
  17. data/lib/gooddata/bricks/middleware/bench_middleware.rb +12 -10
  18. data/lib/gooddata/bricks/middleware/bulk_salesforce_middleware.rb +28 -28
  19. data/lib/gooddata/bricks/middleware/fs_upload_middleware.rb +20 -16
  20. data/lib/gooddata/bricks/middleware/gooddata_middleware.rb +21 -19
  21. data/lib/gooddata/bricks/middleware/logger_middleware.rb +10 -8
  22. data/lib/gooddata/bricks/middleware/restforce_middleware.rb +36 -34
  23. data/lib/gooddata/bricks/middleware/stdout_middleware.rb +11 -9
  24. data/lib/gooddata/bricks/middleware/twitter_middleware.rb +14 -12
  25. data/lib/gooddata/bricks/pipeline.rb +28 -0
  26. data/lib/gooddata/bricks/utils.rb +10 -8
  27. data/lib/gooddata/cli/cli.rb +1 -6
  28. data/lib/gooddata/cli/commands/auth_cmd.rb +1 -1
  29. data/lib/gooddata/cli/commands/console_cmd.rb +7 -5
  30. data/lib/gooddata/cli/commands/domain_cmd.rb +45 -0
  31. data/lib/gooddata/cli/commands/process_cmd.rb +42 -5
  32. data/lib/gooddata/cli/commands/project_cmd.rb +96 -36
  33. data/lib/gooddata/cli/commands/projects_cmd.rb +21 -0
  34. data/lib/gooddata/cli/commands/role_cmd.rb +28 -0
  35. data/lib/gooddata/cli/commands/run_ruby_cmd.rb +5 -5
  36. data/lib/gooddata/cli/commands/scaffold_cmd.rb +1 -1
  37. data/lib/gooddata/cli/commands/{profile_cmd.rb → user_cmd.rb} +7 -9
  38. data/lib/gooddata/cli/shared.rb +3 -2
  39. data/lib/gooddata/client.rb +16 -304
  40. data/lib/gooddata/commands/api.rb +13 -5
  41. data/lib/gooddata/commands/auth.rb +47 -40
  42. data/lib/gooddata/commands/base.rb +4 -2
  43. data/lib/gooddata/commands/commands.rb +1 -1
  44. data/lib/gooddata/commands/datasets.rb +20 -7
  45. data/lib/gooddata/commands/domain.rb +23 -0
  46. data/lib/gooddata/commands/process.rb +23 -117
  47. data/lib/gooddata/commands/project.rb +147 -0
  48. data/lib/gooddata/commands/projects.rb +8 -102
  49. data/lib/gooddata/commands/role.rb +26 -0
  50. data/lib/gooddata/commands/runners.rb +41 -38
  51. data/lib/gooddata/commands/scaffold.rb +46 -43
  52. data/lib/gooddata/commands/user.rb +33 -0
  53. data/lib/gooddata/connection.rb +43 -353
  54. data/lib/gooddata/core/connection.rb +389 -0
  55. data/lib/gooddata/core/core.rb +5 -4
  56. data/lib/gooddata/core/logging.rb +48 -0
  57. data/lib/gooddata/core/nil_logger.rb +13 -0
  58. data/lib/gooddata/core/project.rb +70 -0
  59. data/lib/gooddata/core/rest.rb +120 -0
  60. data/lib/gooddata/core/threaded.rb +14 -0
  61. data/lib/gooddata/core/user.rb +19 -0
  62. data/lib/gooddata/data/data.rb +2 -1
  63. data/lib/gooddata/data/guesser.rb +16 -12
  64. data/lib/gooddata/exceptions/command_failed.rb +1 -1
  65. data/lib/gooddata/exceptions/exceptions.rb +2 -1
  66. data/lib/gooddata/exceptions/no_project_error.rb +11 -0
  67. data/lib/gooddata/exceptions/project_not_found.rb +1 -1
  68. data/lib/gooddata/extensions/big_decimal.rb +6 -2
  69. data/lib/gooddata/extract.rb +10 -8
  70. data/lib/gooddata/goodzilla/goodzilla.rb +61 -59
  71. data/lib/gooddata/helpers.rb +15 -9
  72. data/lib/gooddata/models/account_settings.rb +124 -0
  73. data/lib/gooddata/models/attributes/anchor.rb +37 -0
  74. data/lib/gooddata/models/attributes/attributes.rb +8 -0
  75. data/lib/gooddata/models/attributes/date_attribute.rb +25 -0
  76. data/lib/gooddata/models/attributes/time_attribute.rb +24 -0
  77. data/lib/gooddata/models/columns/attribute.rb +71 -0
  78. data/lib/gooddata/models/columns/columns.rb +8 -0
  79. data/lib/gooddata/models/columns/date_column.rb +63 -0
  80. data/lib/gooddata/models/columns/fact_model.rb +54 -0
  81. data/lib/gooddata/models/columns/label.rb +55 -0
  82. data/lib/gooddata/models/columns/reference.rb +57 -0
  83. data/lib/gooddata/models/dashboard_builder.rb +26 -0
  84. data/lib/gooddata/models/data_result.rb +10 -9
  85. data/lib/gooddata/models/domain.rb +131 -0
  86. data/lib/gooddata/models/empty_result.rb +5 -8
  87. data/lib/gooddata/models/facts/facts.rb +8 -0
  88. data/lib/gooddata/models/facts/time_fact.rb +20 -0
  89. data/lib/gooddata/models/folders/attribute_folder.rb +20 -0
  90. data/lib/gooddata/models/folders/fact_folder.rb +20 -0
  91. data/lib/gooddata/models/folders/folders.rb +8 -0
  92. data/lib/gooddata/models/invitation.rb +78 -0
  93. data/lib/gooddata/models/links.rb +6 -6
  94. data/lib/gooddata/models/md_object.rb +25 -0
  95. data/lib/gooddata/models/metadata.rb +160 -62
  96. data/lib/gooddata/models/metadata/attribute.rb +81 -0
  97. data/lib/gooddata/models/metadata/column.rb +61 -0
  98. data/lib/gooddata/models/{dashboard.rb → metadata/dashboard.rb} +12 -7
  99. data/lib/gooddata/models/{data_set.rb → metadata/data_set.rb} +5 -4
  100. data/lib/gooddata/models/metadata/date_dimension.rb +26 -0
  101. data/lib/gooddata/models/metadata/display_form.rb +61 -0
  102. data/lib/gooddata/models/metadata/fact.rb +36 -0
  103. data/lib/gooddata/models/metadata/folder.rb +24 -0
  104. data/lib/gooddata/models/metadata/metadata.rb +8 -0
  105. data/lib/gooddata/models/metadata/metric.rb +197 -0
  106. data/lib/gooddata/models/metadata/report.rb +115 -0
  107. data/lib/gooddata/models/{report_definition.rb → metadata/report_definition.rb} +16 -10
  108. data/lib/gooddata/models/metadata/schema.rb +227 -0
  109. data/lib/gooddata/models/model.rb +38 -1339
  110. data/lib/gooddata/models/models.rb +5 -2
  111. data/lib/gooddata/models/module_constants.rb +29 -0
  112. data/lib/gooddata/models/process.rb +142 -13
  113. data/lib/gooddata/models/profile.rb +4 -6
  114. data/lib/gooddata/models/project.rb +406 -136
  115. data/lib/gooddata/models/project_blueprint.rb +221 -0
  116. data/lib/gooddata/models/project_builder.rb +136 -0
  117. data/lib/gooddata/models/project_creator.rb +138 -0
  118. data/lib/gooddata/models/project_metadata.rb +11 -10
  119. data/lib/gooddata/models/project_role.rb +92 -0
  120. data/lib/gooddata/models/references/date_reference.rb +44 -0
  121. data/lib/gooddata/models/references/references.rb +8 -0
  122. data/lib/gooddata/models/references/time_reference.rb +13 -0
  123. data/lib/gooddata/models/report_data_result.rb +11 -11
  124. data/lib/gooddata/models/schedule.rb +284 -0
  125. data/lib/gooddata/models/schema_blueprint.rb +158 -0
  126. data/lib/gooddata/models/schema_builder.rb +81 -0
  127. data/lib/gooddata/models/tab_builder.rb +23 -0
  128. data/lib/gooddata/models/user.rb +165 -0
  129. data/lib/gooddata/version.rb +1 -1
  130. data/lib/templates/project/data/devs.csv +1 -1
  131. data/lib/templates/project/data/repos.csv +1 -1
  132. data/lib/templates/project/model/model.rb.erb +7 -11
  133. data/spec/bricks/bricks_spec.rb +2 -0
  134. data/spec/data/test-ci-data.csv +2 -0
  135. data/spec/data/test_project_model_spec.json +7 -27
  136. data/spec/helpers/blueprint_helper.rb +2 -0
  137. data/spec/helpers/cli_helper.rb +2 -0
  138. data/spec/helpers/connection_helper.rb +14 -1
  139. data/spec/helpers/project_helper.rb +16 -0
  140. data/spec/helpers/schema_helper.rb +16 -0
  141. data/spec/integration/command_projects_spec.rb +7 -7
  142. data/spec/integration/create_from_template_spec.rb +2 -2
  143. data/spec/integration/full_project_spec.rb +160 -7
  144. data/spec/integration/partial_md_export_import_spec.rb +3 -3
  145. data/spec/logging_in_logging_out_spec.rb +2 -1
  146. data/spec/spec_helper.rb +26 -4
  147. data/spec/unit/bricks/bricks_spec.rb +15 -7
  148. data/spec/unit/bricks/middleware/bench_middleware_spec.rb +2 -0
  149. data/spec/unit/bricks/middleware/bulk_salesforce_middleware_spec.rb +2 -0
  150. data/spec/unit/bricks/middleware/gooddata_middleware_spec.rb +2 -0
  151. data/spec/unit/bricks/middleware/logger_middleware_spec.rb +2 -0
  152. data/spec/unit/bricks/middleware/restforce_middleware_spec.rb +2 -0
  153. data/spec/unit/bricks/middleware/stdout_middleware_spec.rb +2 -0
  154. data/spec/unit/bricks/middleware/twitter_middleware_spec.rb +2 -0
  155. data/spec/unit/cli/cli_spec.rb +2 -0
  156. data/spec/unit/cli/commands/cmd_api_spec.rb +23 -15
  157. data/spec/unit/cli/commands/cmd_auth_spec.rb +8 -4
  158. data/spec/unit/cli/commands/cmd_domain_spec.rb +82 -0
  159. data/spec/unit/cli/commands/cmd_process_spec.rb +29 -13
  160. data/spec/unit/cli/commands/cmd_project_spec.rb +51 -30
  161. data/spec/unit/cli/commands/cmd_role_spec.rb +44 -0
  162. data/spec/unit/cli/commands/cmd_run_ruby_spec.rb +8 -4
  163. data/spec/unit/cli/commands/cmd_scaffold_spec.rb +48 -11
  164. data/spec/unit/cli/commands/cmd_user_spec.rb +29 -0
  165. data/spec/unit/commands/command_api_spec.rb +1 -1
  166. data/spec/unit/commands/command_auth_spec.rb +100 -18
  167. data/spec/unit/commands/command_dataset_spec.rb +4 -0
  168. data/spec/unit/commands/command_process_spec.rb +9 -4
  169. data/spec/unit/commands/command_projects_spec.rb +10 -6
  170. data/spec/unit/commands/command_scaffold_spec.rb +5 -1
  171. data/spec/unit/commands/command_user_spec.rb +22 -0
  172. data/spec/unit/core/connection_spec.rb +35 -6
  173. data/spec/unit/core/logging_spec.rb +65 -0
  174. data/spec/unit/core/nil_logger_spec.rb +9 -0
  175. data/spec/unit/core/project_spec.rb +51 -0
  176. data/spec/unit/core/rest_spec.rb +33 -0
  177. data/spec/unit/data/guesser_spec.rb +5 -0
  178. data/spec/unit/godzilla/goodzilla_spec.rb +2 -0
  179. data/spec/unit/models/account_settings_spec.rb +28 -0
  180. data/spec/unit/models/anchor_spec.rb +32 -0
  181. data/spec/unit/models/attribute_column_spec.rb +7 -0
  182. data/spec/unit/models/domain_spec.rb +45 -0
  183. data/spec/unit/models/invitation_spec.rb +13 -0
  184. data/spec/unit/models/md_object_spec.rb +47 -0
  185. data/spec/unit/models/metric.rb +92 -0
  186. data/spec/unit/{model → models}/model_spec.rb +9 -7
  187. data/spec/unit/models/project_blueprint_spec.rb +202 -0
  188. data/spec/unit/models/project_creator.rb +73 -0
  189. data/spec/unit/models/project_role_spec.rb +90 -0
  190. data/spec/unit/models/project_spec.rb +143 -0
  191. data/spec/unit/models/schedule_spec.rb +491 -0
  192. data/spec/unit/{model → models}/schema_builder_spec.rb +2 -0
  193. data/spec/unit/{model → models}/tools_spec.rb +13 -7
  194. data/spec/unit/models/user_spec.rb +16 -0
  195. data/test/test_upload.rb +2 -0
  196. metadata +189 -86
  197. data/lib/gooddata/commands/profile.rb +0 -11
  198. data/lib/gooddata/models/attribute.rb +0 -29
  199. data/lib/gooddata/models/display_form.rb +0 -9
  200. data/lib/gooddata/models/fact.rb +0 -19
  201. data/lib/gooddata/models/metric.rb +0 -99
  202. data/lib/gooddata/models/report.rb +0 -89
  203. data/spec/data/blueprint_valid.json +0 -37
  204. data/spec/unit/cli/commands/cmd_profile_spec.rb +0 -16
  205. data/spec/unit/commands/command_profile_spec.rb +0 -18
  206. data/spec/unit/core/core_spec.rb +0 -7
  207. data/spec/unit/model/blueprint_spec.rb +0 -132
  208. data/spec/unit/model/project_blueprint_spec.rb +0 -44
@@ -0,0 +1,115 @@
1
+ # encoding: UTF-8
2
+
3
+ require_relative '../metadata'
4
+ require_relative 'metadata'
5
+
6
+ module GoodData
7
+ class Report < GoodData::MdObject
8
+ root_key :report
9
+
10
+ class << self
11
+ def [](id, options = {})
12
+ if id == :all
13
+ fail 'You have to specify a project ID' if GoodData.project.nil?
14
+ uri = GoodData.project.md['query'] + '/reports/'
15
+ GoodData.get(uri)['query']['entries']
16
+ else
17
+ super
18
+ end
19
+ end
20
+
21
+ def create(options = {})
22
+ title = options[:title]
23
+ summary = options[:summary] || ''
24
+ rd = options[:rd] || ReportDefinition.create(:top => options[:top], :left => options[:left])
25
+ rd.save
26
+
27
+ report = {
28
+ 'report' => {
29
+ 'content' => {
30
+ 'domains' => [],
31
+ 'definitions' => [rd.uri]
32
+ },
33
+ 'meta' => {
34
+ 'tags' => '',
35
+ 'deprecated' => '0',
36
+ 'summary' => summary,
37
+ 'title' => title
38
+ }
39
+ }
40
+ }
41
+ # TODO: write test for report definitions with explicit identifiers
42
+ report['report']['meta']['identifier'] = options[:identifier] if options[:identifier]
43
+ Report.new report
44
+ end
45
+ end
46
+
47
+ def results
48
+ content['results']
49
+ end
50
+
51
+ def definitions
52
+ content['definitions']
53
+ end
54
+
55
+ def latest_report_definition_uri
56
+ definitions.last
57
+ end
58
+
59
+ def latest_report_definition
60
+ GoodData::MdObject[latest_report_definition_uri]
61
+ end
62
+
63
+ def remove_definition(definition)
64
+ def_uri = is_a?(GoodData::ReportDefinition) ? definition.uri : definition
65
+ content['definitions'] = definitions.reject { |x| x == def_uri }
66
+ self
67
+ end
68
+
69
+ # TODO: Cover with test. You would probably need something that will be able to create a report easily from a definition
70
+ def remove_definition_but_latest
71
+ to_remove = definitions - [latest_report_definition_uri]
72
+ to_remove.each do |uri|
73
+ remove_definition(uri)
74
+ end
75
+ self
76
+ end
77
+
78
+ def purge_report_of_unused_definitions!
79
+ full_list = definitions
80
+ remove_definition_but_latest
81
+ purged_list = definitions
82
+ to_remove = full_list - purged_list
83
+ save
84
+ to_remove.each { |uri| GoodData.delete(uri) }
85
+ self
86
+ end
87
+
88
+ def execute
89
+ fail 'You have to save the report before executing. If you do not want to do that please use GoodData::ReportDefinition' unless saved?
90
+ result = GoodData.post '/gdc/xtab2/executor3', 'report_req' => { 'report' => uri }
91
+ data_result_uri = result['execResult']['dataResult']
92
+ result = GoodData.get data_result_uri
93
+ while result['taskState'] && result['taskState']['status'] == 'WAIT'
94
+ sleep 10
95
+ result = GoodData.get data_result_uri
96
+ end
97
+ ReportDataResult.new(GoodData.get data_result_uri)
98
+ end
99
+
100
+ def exportable?
101
+ true
102
+ end
103
+
104
+ def export(format)
105
+ result = GoodData.post('/gdc/xtab2/executor3', 'report_req' => { 'report' => uri })
106
+ result1 = GoodData.post('/gdc/exporter/executor', :result_req => { :format => format, :result => result })
107
+ png = GoodData.get(result1['uri'], :process => false)
108
+ while png.code == 202
109
+ sleep(1)
110
+ png = GoodData.get(result1['uri'], :process => false)
111
+ end
112
+ png
113
+ end
114
+ end
115
+ end
@@ -1,5 +1,6 @@
1
1
  # encoding: UTF-8
2
2
 
3
+ require_relative '../metadata'
3
4
  require_relative 'metadata'
4
5
 
5
6
  # GoodData Module
@@ -10,7 +11,7 @@ module GoodData
10
11
  root_key :reportDefinition
11
12
 
12
13
  class << self
13
- def [](id)
14
+ def [](id, options = {})
14
15
  if id == :all
15
16
  uri = GoodData.project.md['query'] + '/reportdefinition/'
16
17
  result = GoodData.get(uri)
@@ -22,7 +23,7 @@ module GoodData
22
23
 
23
24
  def create_metrics_part(left, top)
24
25
  stuff = Array(left) + Array(top)
25
- stuff.select { |item| item.respond_to?(:is_metric?) }.map do |metric|
26
+ stuff.select { |item| item.respond_to?(:metric?) && item.metric? }.map do |metric|
26
27
  create_metric_part(metric)
27
28
  end
28
29
  end
@@ -47,14 +48,14 @@ module GoodData
47
48
  def create_part(stuff)
48
49
  stuff = Array(stuff)
49
50
  parts = stuff.reduce([]) do |memo, item|
50
- if item.respond_to?(:is_metric?)
51
+ if item.respond_to?(:metric?) && item.metric?
51
52
  memo
52
53
  else
53
54
  memo << create_attribute_part(item)
54
55
  end
55
56
  memo
56
57
  end
57
- if stuff.any? { |item| item.respond_to?(:is_metric?) }
58
+ if stuff.any? { |item| item.respond_to?(:metric?) && item.metric? }
58
59
  parts << 'metricGroup'
59
60
  end
60
61
  parts
@@ -62,7 +63,7 @@ module GoodData
62
63
 
63
64
  def find(stuff)
64
65
  stuff.map do |item|
65
- if item.respond_to?(:is_attribute?)
66
+ if item.respond_to?(:attribute?) && item.attribute?
66
67
  item.display_forms.first
67
68
  elsif item.is_a?(String)
68
69
  x = GoodData::MdObject.get_by_id(item)
@@ -112,7 +113,7 @@ module GoodData
112
113
  left = Array(options[:left])
113
114
  top = Array(options[:top])
114
115
 
115
- metrics = (left + top).select { |item| item.respond_to?(:is_metric?) }
116
+ metrics = (left + top).select { |item| item.respond_to?(:metric?) && item.metric? }
116
117
 
117
118
  unsaved_metrics = metrics.reject { |i| i.saved? }
118
119
  unsaved_metrics.each { |m| m.title = 'Untitled metric' unless m.title }
@@ -120,7 +121,7 @@ module GoodData
120
121
  begin
121
122
  unsaved_metrics.each { |m| m.save }
122
123
  rd = GoodData::ReportDefinition.create(options)
123
- get_data_result(execute_inline(rd))
124
+ data_result(execute_inline(rd))
124
125
  ensure
125
126
  unsaved_metrics.each { |m| m.delete if m && m.saved? }
126
127
  end
@@ -140,7 +141,11 @@ module GoodData
140
141
  GoodData.post(uri, data)
141
142
  end
142
143
 
143
- def get_data_result(result)
144
+ # TODO: refactor the method. It should be instance method
145
+ # Method used for getting a data_result from a wire representation of
146
+ # @param result [Hash, Object] Wire data from JSON
147
+ # @return [GoodData::ReportDataResult]
148
+ def data_result(result)
144
149
  data_result_uri = result['execResult']['dataResult']
145
150
  result = GoodData.get data_result_uri
146
151
 
@@ -186,6 +191,8 @@ module GoodData
186
191
  }
187
192
  }
188
193
  }
194
+ # TODO: write test for report definitions with explicit identifiers
195
+ pars['reportDefinition']['meta']['identifier'] = options[:identifier] if options[:identifier]
189
196
 
190
197
  ReportDefinition.new(pars)
191
198
  end
@@ -204,8 +211,7 @@ module GoodData
204
211
  else
205
212
  ReportDefinition.execute_inline(self)
206
213
  end
207
-
208
- get_data_result(result)
214
+ ReportDefinition.data_result(result)
209
215
  end
210
216
  end
211
217
  end
@@ -0,0 +1,227 @@
1
+ # encoding: UTF-8
2
+
3
+ require_relative '../../helpers'
4
+
5
+ require_relative '../attributes/anchor'
6
+ require_relative '../columns/columns'
7
+ require_relative '../md_object'
8
+
9
+ module GoodData
10
+ module Model
11
+ ##
12
+ # Server-side representation of a local data set; includes connection point,
13
+ # attributes and labels, facts, folders and corresponding pieces of physical
14
+ # model abstractions.
15
+ #
16
+ class Schema < MdObject
17
+ attr_reader :fields, :attributes, :facts, :folders, :references, :labels, :name, :title, :anchor
18
+
19
+ def self.load(file)
20
+ Schema.new JSON.load(open(file))
21
+ end
22
+
23
+ def initialize(a_config, a_name = 'Default Name', a_title = 'Default Title')
24
+ super()
25
+ @fields = []
26
+ @attributes = []
27
+ @facts = []
28
+ @folders = {
29
+ :facts => {},
30
+ :attributes => {}
31
+ }
32
+ @references = []
33
+ @labels = []
34
+
35
+ a_config[:name] = a_name unless a_config[:name]
36
+ a_config[:title] = a_config[:name] unless a_config[:title]
37
+ a_config[:title] = a_title unless a_config[:title]
38
+ a_config[:title] = a_config[:title].humanize
39
+
40
+ fail 'Schema name not specified' unless a_config[:name]
41
+ @name = a_config[:name]
42
+ @title = a_config[:title]
43
+ self.config = (a_config)
44
+ end
45
+
46
+ def config=(config)
47
+ config[:columns].each do |c|
48
+ case c[:type].to_s
49
+ when 'attribute'
50
+ add_attribute c
51
+ when 'fact'
52
+ add_fact c
53
+ when 'date'
54
+ add_date c
55
+ when 'anchor'
56
+ set_anchor c
57
+ when 'label'
58
+ add_label c
59
+ when 'reference'
60
+ add_reference c
61
+ else
62
+ fail "Unexpected type #{c[:type]} in #{c.inspect}"
63
+ end
64
+ end
65
+ @anchor = Anchor.new(nil, self) unless @anchor
66
+ end
67
+
68
+ def type_prefix
69
+ 'dataset'
70
+ end
71
+
72
+ ##
73
+ # Underlying fact table name
74
+ #
75
+ def table
76
+ @table ||= FACT_COLUMN_PREFIX + name
77
+ end
78
+
79
+ ##
80
+ # Generates MAQL DDL script to drop this data set and included pieces
81
+ #
82
+ def to_maql_drop
83
+ maql = ''
84
+ [attributes, facts].each do |obj|
85
+ maql += obj.to_maql_drop
86
+ end
87
+ maql += "DROP {#{identifier}};\n"
88
+ end
89
+
90
+ ##
91
+ # Generates MAQL DDL script to create this data set and included pieces
92
+ #
93
+ def to_maql_create
94
+ # TODO: Use template (.erb)
95
+ maql = "# Create the '#{title}' data set\n"
96
+ maql += "CREATE DATASET {#{identifier}} VISUAL (TITLE \"#{title}\");\n\n"
97
+ [attributes, facts, { 1 => @anchor }].each do |objects|
98
+ objects.values.each do |obj|
99
+ maql += "# Create '#{obj.title}' and add it to the '#{title}' data set.\n"
100
+ maql += obj.to_maql_create
101
+ maql += "ALTER DATASET {#{identifier}} ADD {#{obj.identifier}};\n\n"
102
+ end
103
+ end
104
+
105
+ labels.each do |label|
106
+ maql += "# Creating Labels\n"
107
+ maql += label.to_maql_create
108
+ end
109
+
110
+ references.values.each do |ref|
111
+ maql += "# Creating references\n"
112
+ maql += ref.to_maql_create
113
+ end
114
+
115
+ folders_maql = "# Create folders\n"
116
+ (folders[:attributes].values + folders[:facts].values).each { |folder| folders_maql += folder.to_maql_create }
117
+ folders_maql + "\n" + maql + "SYNCHRONIZE {#{identifier}};\n"
118
+ end
119
+
120
+ def upload(path, project = nil, mode = 'FULL')
121
+ if path =~ URI.regexp
122
+ Tempfile.open('remote_file') do |temp|
123
+ temp << open(path).read
124
+ temp.flush
125
+ upload_data(temp, mode)
126
+ end
127
+ else
128
+ upload_data(path, mode)
129
+ end
130
+ end
131
+
132
+ def upload_data(path, mode)
133
+ GoodData::Model.upload_data(path, to_manifest(mode))
134
+ end
135
+
136
+ # Generates the SLI manifest describing the data loading
137
+ #
138
+ def to_manifest(mode = 'FULL')
139
+ {
140
+ 'dataSetSLIManifest' => {
141
+ 'parts' => fields.reduce([]) do |memo, f|
142
+ val = f.to_manifest_part(mode)
143
+ memo << val unless val.nil?
144
+ memo
145
+ end,
146
+ 'dataSet' => identifier,
147
+ 'file' => 'data.csv', # should be configurable
148
+ 'csvParams' => {
149
+ 'quoteChar' => '"',
150
+ 'escapeChar' => '"',
151
+ 'separatorChar' => ',',
152
+ 'endOfLine' => "\n"
153
+ }
154
+ }
155
+ }
156
+ end
157
+
158
+ def to_wire_model
159
+ {
160
+ 'dataset' => {
161
+ 'identifier' => identifier,
162
+ 'title' => title,
163
+ 'anchor' => @anchor.to_wire_model,
164
+ 'facts' => facts.map { |f| f.to_wire_model },
165
+ 'attributes' => attributes.map { |a| a.to_wire_model },
166
+ 'references' => references.map { |r| r.is_a?(DateReference) ? r.schema_ref : type_prefix + '.' + r.schema_ref }
167
+ } }
168
+ end
169
+
170
+ private
171
+
172
+ def add_attribute(column)
173
+ attribute = Attribute.new column, self
174
+ fields << attribute
175
+ attributes << attribute
176
+ add_attribute_folder(attribute.folder)
177
+ # folders[AttributeFolder.new(attribute.folder)] = 1 if attribute.folder
178
+ end
179
+
180
+ def add_attribute_folder(name)
181
+ return if name.nil?
182
+ return if folders[:attributes].key?(name)
183
+ folders[:attributes][name] = AttributeFolder.new(name)
184
+ end
185
+
186
+ def add_fact(column)
187
+ fact = Fact.new column, self
188
+ fields << fact
189
+ facts << fact
190
+ add_fact_folder(fact.folder)
191
+ # folders[FactFolder.new(fact.folder)] = 1 if fact.folder
192
+ end
193
+
194
+ def add_fact_folder(name)
195
+ return if name.nil?
196
+ return if folders[:facts].key?(name)
197
+ folders[:facts][name] = FactFolder.new(name)
198
+ end
199
+
200
+ def add_label(column)
201
+ label = Label.new(column, nil, self)
202
+ labels << label
203
+ fields << label
204
+ end
205
+
206
+ def add_reference(column)
207
+ reference = Reference.new(column, self)
208
+ fields << reference
209
+ references << reference
210
+ end
211
+
212
+ def add_date(column)
213
+ date = DateColumn.new column, self
214
+ @fields << date
215
+ date.parts.values.each { |p| @fields << p }
216
+ date.facts.each { |f| facts << f }
217
+ date.attributes.each { |a| attributes << a }
218
+ date.references.each { |r| references << r }
219
+ end
220
+
221
+ def set_anchor(column) # rubocop:disable AccessorMethodName
222
+ @anchor = Anchor.new column, self
223
+ @fields << @anchor
224
+ end
225
+ end
226
+ end
227
+ end
@@ -1,9 +1,23 @@
1
1
  # encoding: UTF-8
2
2
 
3
- require_relative '../helpers'
4
-
3
+ require_relative '../core/connection'
4
+ require_relative '../core/rest'
5
+
6
+ require_relative 'attributes/attributes'
7
+ require_relative 'columns/columns'
8
+ require_relative 'facts/facts'
9
+ require_relative 'folders/folders'
10
+ require_relative 'metadata/metadata'
11
+ require_relative 'references/references'
12
+
13
+ require_relative 'links'
14
+ require_relative 'module_constants'
15
+ require_relative 'metadata/schema'
16
+
17
+ require 'fileutils'
18
+ require 'multi_json'
5
19
  require 'open-uri'
6
- require 'active_support/all'
20
+ require 'zip'
7
21
 
8
22
  ##
9
23
  # Module containing classes that counter-part GoodData server-side meta-data
@@ -11,49 +25,25 @@ require 'active_support/all'
11
25
  #
12
26
  module GoodData
13
27
  module Model
14
- # GoodData REST API categories
15
- LDM_CTG = 'ldm'
16
- LDM_MANAGE_CTG = 'ldm-manage'
17
-
18
- # Model naming conventions
19
- FIELD_PK = 'id'
20
- FK_SUFFIX = '_id'
21
- FACT_COLUMN_PREFIX = 'f_'
22
- DATE_COLUMN_PREFIX = 'dt_'
23
- TIME_COLUMN_PREFIX = 'tm_'
24
- LABEL_COLUMN_PREFIX = 'nm_'
25
- ATTRIBUTE_FOLDER_PREFIX = 'dim'
26
- ATTRIBUTE_PREFIX = 'attr'
27
- LABEL_PREFIX = 'label'
28
- FACT_PREFIX = 'fact'
29
- DATE_FACT_PREFIX = 'dt'
30
- DATE_ATTRIBUTE = 'date'
31
- DATE_ATTRIBUTE_DEFAULT_DISPLAY_FORM = 'mdyy'
32
- TIME_FACT_PREFIX = 'tm.dt'
33
- TIME_ATTRIBUTE_PREFIX = 'attr.time'
34
- FACT_FOLDER_PREFIX = 'ffld'
35
-
36
- SKIP_FIELD = false
37
-
38
28
  class << self
39
- def add_dataset(name, columns, project = nil)
40
- Schema.new('columns' => columns, 'name' => name)
41
- add_schema(schema, project)
42
- end
29
+ # def add_dataset(name, columns, project = nil)
30
+ # Schema.new('columns' => columns, 'name' => name)
31
+ # add_schema(schema, project)
32
+ # end
43
33
 
44
- def add_schema(schema, project = nil)
45
- unless schema.respond_to?(:to_maql_create) || schema.is_a?(String) then
46
- raise ArgumentError.new("Schema object or schema file path expected, got '#{schema}'")
47
- end
48
- schema = Schema.load(schema) unless schema.respond_to?(:to_maql_create)
49
- project = GoodData.project unless project
50
- ldm_links = GoodData.get project.md[LDM_CTG]
51
- ldm_uri = Links.new(ldm_links)[LDM_MANAGE_CTG]
52
- GoodData.post ldm_uri, {'manage' => {'maql' => schema.to_maql_create}}
53
- end
34
+ # def add_schema(schema, project = nil)
35
+ # unless schema.respond_to?(:to_maql_create) || schema.is_a?(String)
36
+ # fail(ArgumentError, "Schema object or schema file path expected, got '#{schema}'")
37
+ # end
38
+ # schema = Schema.load(schema) unless schema.respond_to?(:to_maql_create)
39
+ # project = GoodData.project unless project
40
+ # ldm_links = GoodData.get project.md[LDM_CTG]
41
+ # ldm_uri = Links.new(ldm_links)[LDM_MANAGE_CTG]
42
+ # GoodData.post ldm_uri, 'manage' => { 'maql' => schema.to_maql_create }
43
+ # end
54
44
 
55
45
  # Load given file into a data set described by the given schema
56
- def upload_data(path, manifest, options={})
46
+ def upload_data(path, manifest, options = {})
57
47
  project = options[:project] || GoodData.project
58
48
  # mode = options[:mode] || "FULL"
59
49
  path = path.path if path.respond_to? :path
@@ -63,7 +53,7 @@ module GoodData
63
53
  dir = Dir.mktmpdir
64
54
  begin
65
55
  Zip::File.open("#{dir}/upload.zip", Zip::File::CREATE) do |zip|
66
- # TODO make sure schema columns match CSV column names
56
+ # TODO: make sure schema columns match CSV column names
67
57
  zip.get_output_stream('upload_info.json') { |f| f.puts JSON.pretty_generate(manifest) }
68
58
  if inline_data
69
59
  zip.get_output_stream('data.csv') do |f|
@@ -83,10 +73,11 @@ module GoodData
83
73
  end
84
74
 
85
75
  # kick the load
86
- pull = {'pullIntegration' => File.basename(dir)}
76
+ pull = { 'pullIntegration' => File.basename(dir) }
87
77
  link = project.md.links('etl')['pull']
88
78
  task = GoodData.post link, pull
89
- while GoodData.get(task['pullTask']['uri'])['taskStatus'] === 'RUNNING' || GoodData.get(task['pullTask']['uri'])['taskStatus'] === 'PREPARED'
79
+ # TODO: Refactor the task status out
80
+ while GoodData.get(task['pullTask']['uri'])['taskStatus'] == 'RUNNING' || GoodData.get(task['pullTask']['uri'])['taskStatus'] == 'PREPARED'
90
81
  sleep 30
91
82
  end
92
83
  if GoodData.get(task['pullTask']['uri'])['taskStatus'] == 'ERROR'
@@ -103,1302 +94,10 @@ module GoodData
103
94
  d = Marshal.load(Marshal.dump(a_schema_blueprint))
104
95
  d[:columns] = d[:columns] + b_schema_blueprint[:columns]
105
96
  d[:columns].uniq!
106
- columns_that_failed_to_merge = d[:columns].group_by { |x| x[:name] }.map { |k, v| [k, v.count] }.find_all { |x| x[1] > 1 }
97
+ columns_that_failed_to_merge = d[:columns].group_by { |x| x[:name] }.map { |k, v| [k, v.count] }.select { |x| x[1] > 1 }
107
98
  fail "Columns #{columns_that_failed_to_merge} failed to merge. When merging columns with the same name they have to be identical." unless columns_that_failed_to_merge.empty?
108
99
  d
109
100
  end
110
101
  end
111
-
112
- class ProjectBlueprint
113
- attr_accessor :data
114
-
115
- def self.from_json(spec)
116
- if spec.is_a?(String)
117
- ProjectBlueprint.new(MultiJson.load(File.read(spec), :symbolize_keys => true))
118
- else
119
- ProjectBlueprint.new(spec)
120
- end
121
- end
122
-
123
- def change(&block)
124
- builder = ProjectBuilder.create_from_data(self)
125
- block.call(builder)
126
- builder
127
- @data = builder.to_hash
128
- self
129
- end
130
-
131
- def datasets
132
- data[:datasets].map { |d| SchemaBlueprint.new(d) }
133
- end
134
-
135
- def add_dataset(a_dataset, index=nil)
136
- if index.nil? || index > datasets.length
137
- data[:datasets] << a_dataset.to_hash
138
- else
139
- data[:datasets].insert(index, a_dataset.to_hash)
140
- end
141
- end
142
-
143
- def remove_dataset(dataset_name)
144
- x = data[:datasets].find { |d| d[:name] == dataset_name }
145
- index = data[:datasets].index(x)
146
- data[:datasets].delete_at(index)
147
- end
148
-
149
- def date_dimensions
150
- data[:date_dimensions]
151
- end
152
-
153
- def get_dataset(name)
154
- ds = data[:datasets].find { |d| d[:name] == name }
155
- SchemaBlueprint.new(ds) unless ds.nil?
156
- end
157
-
158
- def initialize(init_data)
159
- @data = init_data
160
- end
161
-
162
- def model_validate
163
- if datasets.count == 1
164
- []
165
- else
166
- x = datasets.reduce([]) { |memo, schema| schema.has_anchor? ? memo << [schema.name, schema.anchor[:name]] : memo }
167
- refs = datasets.reduce([]) do |memo, dataset|
168
- memo.concat(dataset.references)
169
- end
170
- refs.reduce([]) do |memo, ref|
171
- x.include?([ref[:dataset], ref[:reference]]) ? memo : memo.concat([ref])
172
- end
173
- end
174
- end
175
-
176
- def model_valid?
177
- errors = model_validate
178
- errors.empty? ? true : false
179
- end
180
-
181
- def merge!(a_blueprint)
182
- temp_blueprint = dup
183
- a_blueprint.datasets.each do |dataset|
184
- local_dataset = temp_blueprint.get_dataset(dataset.name)
185
- if local_dataset.nil?
186
- temp_blueprint.add_dataset(dataset.dup)
187
- else
188
- index = temp_blueprint.datasets.index(local_dataset)
189
- local_dataset.merge!(dataset)
190
- temp_blueprint.remove_dataset(local_dataset.name)
191
- temp_blueprint.add_dataset(local_dataset, index)
192
- end
193
- end
194
- @data = temp_blueprint.data
195
- self
196
- end
197
-
198
- def dup
199
- deep_copy = Marshal.load(Marshal.dump(data))
200
- ProjectBlueprint.new(deep_copy)
201
- end
202
-
203
- def title
204
- data[:title]
205
- end
206
-
207
- def to_wire_model
208
- {
209
- 'diffRequest' => {
210
- 'targetModel' => {
211
- 'projectModel' => {
212
- 'datasets' => datasets.map { |d| d.to_wire_model },
213
- 'dateDimensions' => date_dimensions.map { |d|
214
- {
215
- 'dateDimension' => {
216
- 'name' => d[:name],
217
- 'title' => d[:title] || d[:name].humanize
218
- }
219
- } }
220
- }}}}
221
- end
222
-
223
- def to_hash
224
- @data
225
- end
226
- end
227
-
228
- class SchemaBlueprint
229
- attr_accessor :data
230
-
231
- def change(&block)
232
- builder = SchemaBuilder.create_from_data(self)
233
- block.call(builder)
234
- builder
235
- @data = builder.to_hash
236
- self
237
- end
238
-
239
- def initialize(init_data)
240
- @data = init_data
241
- end
242
-
243
- def upload(source, options={})
244
- project = options[:project] || GoodData.project
245
- fail 'You have to specify a project into which you want to load.' if project.nil?
246
- mode = options[:load] || 'FULL'
247
- project.upload(source, to_schema, mode)
248
- end
249
-
250
- def merge!(a_blueprint)
251
- new_blueprint = GoodData::Model.merge_dataset_columns(self, a_blueprint)
252
- @data = new_blueprint
253
- self
254
- end
255
-
256
- def name
257
- data[:name]
258
- end
259
-
260
- def title
261
- data[:title]
262
- end
263
-
264
- def to_hash
265
- data
266
- end
267
-
268
- def columns
269
- data[:columns]
270
- end
271
-
272
- def has_anchor?
273
- columns.any? { |c| c[:type].to_s == 'anchor' }
274
- end
275
-
276
- def anchor
277
- find_column_by_type(:anchor, :first)
278
- end
279
-
280
- def references
281
- find_column_by_type(:reference)
282
- end
283
-
284
- def attributes
285
- find_column_by_type(:attribute)
286
- end
287
-
288
- def facts
289
- find_column_by_type(:fact)
290
- end
291
-
292
- def find_column_by_type(type, all=:all)
293
- type = type.to_s
294
- if all == :all
295
- columns.find_all { |c| c[:type].to_s == type }
296
- else
297
- columns.find { |c| c[:type].to_s == type }
298
- end
299
- end
300
-
301
- def find_column_by_name(type, all=:all)
302
- type = type.to_s
303
- if all == :all
304
- columns.find_all { |c| c[:name].to_s == type }
305
- else
306
- columns.find { |c| c[:name].to_s == type }
307
- end
308
- end
309
-
310
- def to_schema
311
- Schema.new(to_hash)
312
- end
313
-
314
- def to_manifest
315
- to_schema.to_manifest
316
- end
317
-
318
- def pretty_print(printer)
319
- printer.text "Schema <#{object_id}>:\n"
320
- printer.text " Name: #{name}\n"
321
- printer.text " Columns: \n"
322
- printer.text columns.map { |c| " #{c[:name]}: #{c[:type]}" }.join("\n")
323
- end
324
-
325
- def dup
326
- deep_copy = Marshal.load(Marshal.dump(data))
327
- SchemaBlueprint.new(deep_copy)
328
- end
329
-
330
- def to_wire_model
331
- to_schema.to_wire_model
332
- end
333
-
334
- def ==(other)
335
- to_hash == other.to_hash
336
- end
337
- end
338
-
339
- class ProjectBuilder
340
- attr_reader :title, :datasets, :reports, :metrics, :uploads, :users, :assert_report, :date_dimensions
341
-
342
- class << self
343
- def create_from_data(blueprint, title = 'Title')
344
- pb = ProjectBuilder.new(title)
345
- pb.data = blueprint.to_hash
346
- pb
347
- end
348
-
349
- def create(title, options={}, &block)
350
- pb = ProjectBuilder.new(title)
351
- block.call(pb)
352
- pb
353
- end
354
- end
355
-
356
- def initialize(title)
357
- @title = title
358
- @datasets = []
359
- @reports = []
360
- @assert_tests = []
361
- @metrics = []
362
- @uploads = []
363
- @users = []
364
- @dashboards = []
365
- @date_dimensions = []
366
- end
367
-
368
- def add_date_dimension(name, options = {})
369
- dimension = {
370
- urn: options[:urn],
371
- name: name,
372
- title: options[:title]
373
- }
374
-
375
- @date_dimensions << dimension
376
- end
377
-
378
- def add_dataset(name, &block)
379
- builder = GoodData::Model::SchemaBuilder.new(name)
380
- block.call(builder)
381
- if @datasets.any? { |item| item[:name] == name }
382
- ds = @datasets.find { |item| item[:name] == name }
383
- index = @datasets.index(ds)
384
- stuff = GoodData::Model.merge_dataset_columns(ds, builder.to_hash)
385
- @datasets.delete_at(index)
386
- @datasets.insert(index, stuff)
387
- else
388
- @datasets << builder.to_hash
389
- end
390
- end
391
-
392
- def add_report(title, options={})
393
- @reports << {:title => title}.merge(options)
394
- end
395
-
396
- def add_metric(title, options={})
397
- @metrics << {:title => title}.merge(options)
398
- end
399
-
400
- def add_dashboard(title, &block)
401
- db = DashboardBuilder.new(title)
402
- block.call(db)
403
- @dashboards << db.to_hash
404
- end
405
-
406
- def load_metrics(file)
407
- new_metrics = MultiJson.load(open(file).read, :symbolize_keys => true)
408
- @metrics = @metrics + new_metrics
409
- end
410
-
411
- def load_datasets(file)
412
- new_metrics = MultiJson.load(open(file).read, :symbolize_keys => true)
413
- @datasets = @datasets + new_metrics
414
- end
415
-
416
- def assert_report(report, result)
417
- @assert_tests << {:report => report, :result => result}
418
- end
419
-
420
- def upload(data, options={})
421
- mode = options[:mode] || 'FULL'
422
- dataset = options[:dataset]
423
- @uploads << {
424
- :source => data,
425
- :mode => mode,
426
- :dataset => dataset
427
- }
428
- end
429
-
430
- def add_users(users)
431
- @users << users
432
- end
433
-
434
- def to_json(options={})
435
- eliminate_empty = options[:eliminate_empty] || false
436
-
437
- if eliminate_empty
438
- JSON.pretty_generate(to_hash.reject { |k, v| v.is_a?(Enumerable) && v.empty? })
439
- else
440
- JSON.pretty_generate(to_hash)
441
- end
442
- end
443
-
444
- def to_hash
445
- {
446
- :title => @title,
447
- :datasets => @datasets,
448
- :uploads => @uploads,
449
- :dashboards => @dashboards,
450
- :metrics => @metrics,
451
- :reports => @reports,
452
- :users => @users,
453
- :assert_tests => @assert_tests,
454
- :date_dimensions => @date_dimensions
455
- }
456
- end
457
-
458
- def get_dataset(name)
459
- datasets.find { |d| d.name == name }
460
- end
461
- end
462
-
463
- class DashboardBuilder
464
- def initialize(title)
465
- @title = title
466
- @tabs = []
467
- end
468
-
469
- def add_tab(tab, &block)
470
- tb = TabBuilder.new(tab)
471
- block.call(tb)
472
- @tabs << tb
473
- tb
474
- end
475
-
476
- def to_hash
477
- {
478
- :name => @name,
479
- :tabs => @tabs.map { |tab| tab.to_hash }
480
- }
481
- end
482
- end
483
-
484
- class TabBuilder
485
- def initialize(title)
486
- @title = title
487
- @stuff = []
488
- end
489
-
490
- def add_report(options={})
491
- @stuff << {:type => :report}.merge(options)
492
- end
493
-
494
- def to_hash
495
- {
496
- :title => @title,
497
- :items => @stuff
498
- }
499
- end
500
- end
501
-
502
- class SchemaBuilder
503
- attr_accessor :data
504
-
505
- class << self
506
- def create_from_data(blueprint)
507
- sc = SchemaBuilder.new
508
- sc.data = blueprint.to_hash
509
- sc
510
- end
511
- end
512
-
513
- def initialize(name=nil)
514
- @data = {
515
- :name => name,
516
- :columns => []
517
- }
518
- end
519
-
520
- def name
521
- data[:name]
522
- end
523
-
524
- def columns
525
- data[:columns]
526
- end
527
-
528
- def add_column(column_def)
529
- columns.push(column_def)
530
- self
531
- end
532
-
533
- def add_anchor(name, options={})
534
- add_column({:type => :anchor, :name => name}.merge(options))
535
- self
536
- end
537
-
538
- def add_attribute(name, options={})
539
- add_column({:type => :attribute, :name => name}.merge(options))
540
- self
541
- end
542
-
543
- def add_fact(name, options={})
544
- add_column({:type => :fact, :name => name}.merge(options))
545
- self
546
- end
547
-
548
- def add_label(name, options={})
549
- add_column({:type => :label, :name => name}.merge(options))
550
- self
551
- end
552
-
553
- def add_date(name, options={})
554
- add_column({:type => :date, :name => name}.merge(options))
555
- end
556
-
557
- def add_reference(name, options={})
558
- add_column({:type => :reference, :name => name}.merge(options))
559
- end
560
-
561
- def to_json
562
- JSON.pretty_generate(to_hash)
563
- end
564
-
565
- def to_hash
566
- data
567
- end
568
-
569
- def to_schema
570
- Schema.new(to_hash)
571
- end
572
- end
573
-
574
- class ProjectCreator
575
- class << self
576
- def migrate(options={})
577
- spec = options[:spec] || fail('You need to provide spec for migration')
578
- spec = spec.to_hash
579
-
580
- token = options[:token]
581
- project = options[:project] || GoodData::Project.create(:title => spec[:title], :auth_token => token)
582
- fail('You need to specify token for project creation') if token.nil? && project.nil?
583
-
584
- begin
585
- GoodData.with_project(project) do |p|
586
- # migrate_date_dimensions(p, spec[:date_dimensions] || [])
587
- migrate_datasets(p, spec)
588
- load(p, spec)
589
- migrate_metrics(p, spec[:metrics] || [])
590
- migrate_reports(p, spec[:reports] || [])
591
- migrate_dashboards(p, spec[:dashboards] || [])
592
- migrate_users(p, spec[:users] || [])
593
- execute_tests(p, spec[:assert_tests] || [])
594
- p
595
- end
596
- end
597
- end
598
-
599
- def migrate_date_dimensions(project, spec)
600
- spec.each do |dd|
601
- Model.add_schema(DateDimension.new(dd), project)
602
- end
603
- end
604
-
605
- def migrate_datasets(project, spec)
606
- bp = ProjectBlueprint.new(spec)
607
- # schema = Schema.load(schema) unless schema.respond_to?(:to_maql_create)
608
- # project = GoodData.project unless project
609
- uri = "/gdc/projects/#{GoodData.project.pid}/model/diff"
610
- result = GoodData.post(uri, bp.to_wire_model)
611
- link = result['asyncTask']['link']['poll']
612
- response = GoodData.get(link, :process => false)
613
- # pp response
614
- while response.code != 200
615
- sleep 1
616
- GoodData.connection.retryable(:tries => 3, :on => RestClient::InternalServerError) do
617
- sleep 1
618
- response = GoodData.get(link, :process => false)
619
- # pp response
620
- end
621
- end
622
- response = GoodData.get(link)
623
- ldm_links = GoodData.get project.md[LDM_CTG]
624
- ldm_uri = Links.new(ldm_links)[LDM_MANAGE_CTG]
625
- chunks = response['projectModelDiff']['updateScripts'].find_all { |script| script['updateScript']['preserveData'] == true && script['updateScript']['cascadeDrops'] == false }.map { |x| x['updateScript']['maqlDdlChunks'] }.flatten
626
- chunks.each do |chunk|
627
- GoodData.post ldm_uri, {'manage' => {'maql' => chunk}}
628
- end
629
-
630
- bp.datasets.each do |ds|
631
- schema = ds.to_schema
632
- GoodData::ProjectMetadata["manifest_#{schema.name}"] = schema.to_manifest.to_json
633
- end
634
- end
635
-
636
- def migrate_reports(project, spec)
637
- spec.each do |report|
638
- project.add_report(report)
639
- end
640
- end
641
-
642
- def migrate_dashboards(project, spec)
643
- spec.each do |dash|
644
- project.add_dashboard(dash)
645
- end
646
- end
647
-
648
- def migrate_metrics(project, spec)
649
- spec.each do |metric|
650
- project.add_metric(metric)
651
- end
652
- end
653
-
654
- def migrate_users(project, spec)
655
- spec.each do |user|
656
- puts "Would migrate user #{user}"
657
- # project.add_user(user)
658
- end
659
- end
660
-
661
- def load(project, spec)
662
- if spec.has_key?(:uploads)
663
- spec[:uploads].each do |load|
664
- schema = GoodData::Model::Schema.new(spec[:datasets].detect { |d| d[:name] == load[:dataset] })
665
- project.upload(load[:source], schema, load[:mode])
666
- end
667
- end
668
- end
669
-
670
- def execute_tests(project, spec)
671
- spec.each do |assert|
672
- result = GoodData::ReportDefinition.execute(assert[:report])
673
- fail "Test did not pass. Got #{result.table.inspect}, expected #{assert[:result].inspect}" if result.table != assert[:result]
674
- end
675
- end
676
- end
677
- end
678
-
679
- class MdObject
680
- attr_accessor :name, :title
681
-
682
- def visual
683
- "TITLE \"#{title_esc}\""
684
- end
685
-
686
- def title_esc
687
- title.gsub(/"/, "\\\"")
688
- end
689
-
690
- ##
691
- # Generates an identifier from the object name by transliterating
692
- # non-Latin character and then dropping non-alphanumerical characters.
693
- #
694
- def identifier
695
- @identifier ||= "#{self.type_prefix}.#{name}"
696
- end
697
- end
698
-
699
- ##
700
- # Server-side representation of a local data set; includes connection point,
701
- # attributes and labels, facts, folders and corresponding pieces of physical
702
- # model abstractions.
703
- #
704
- class Schema < MdObject
705
- attr_reader :fields, :attributes, :facts, :folders, :references, :labels, :name, :title, :anchor
706
-
707
- def self.load(file)
708
- Schema.new JSON.load(open(file))
709
- end
710
-
711
- def initialize(config, name = 'Default Name', title = 'Default Title')
712
- super()
713
- @fields = []
714
- @attributes = []
715
- @facts = []
716
- @folders = {
717
- :facts => {},
718
- :attributes => {}
719
- }
720
- @references = []
721
- @labels = []
722
-
723
- config[:name] = name unless config[:name]
724
- config[:title] = config[:name] unless config[:title]
725
- config[:title] = title unless config[:title]
726
- config[:title] = config[:title].humanize
727
-
728
- fail 'Schema name not specified' unless config[:name]
729
- self.name = config[:name]
730
- self.title = config[:title]
731
- self.config = config
732
- end
733
-
734
- def config=(config)
735
- config[:columns].each do |c|
736
- case c[:type].to_s
737
- when 'attribute'
738
- add_attribute c
739
- when 'fact'
740
- add_fact c
741
- when 'date'
742
- add_date c
743
- when 'anchor'
744
- set_anchor c
745
- when 'label'
746
- add_label c
747
- when 'reference'
748
- add_reference c
749
- else
750
- fail "Unexpected type #{c[:type]} in #{c.inspect}"
751
- end
752
- end
753
- @anchor = Anchor.new(nil, self) unless @anchor
754
- end
755
-
756
- def type_prefix
757
- 'dataset'
758
- end
759
-
760
- ##
761
- # Underlying fact table name
762
- #
763
- def table
764
- @table ||= FACT_COLUMN_PREFIX + name
765
- end
766
-
767
- ##
768
- # Generates MAQL DDL script to drop this data set and included pieces
769
- #
770
- def to_maql_drop
771
- maql = ''
772
- [attributes, facts].each do |obj|
773
- maql += obj.to_maql_drop
774
- end
775
- maql += "DROP {#{self.identifier}};\n"
776
- end
777
-
778
- ##
779
- # Generates MAQL DDL script to create this data set and included pieces
780
- #
781
- def to_maql_create
782
- # TODO: Use template (.erb)
783
- maql = "# Create the '#{self.title}' data set\n"
784
- maql += "CREATE DATASET {#{self.identifier}} VISUAL (TITLE \"#{self.title}\");\n\n"
785
- [attributes, facts, {1 => @anchor}].each do |objects|
786
- objects.values.each do |obj|
787
- maql += "# Create '#{obj.title}' and add it to the '#{self.title}' data set.\n"
788
- maql += obj.to_maql_create
789
- maql += "ALTER DATASET {#{self.identifier}} ADD {#{obj.identifier}};\n\n"
790
- end
791
- end
792
-
793
- labels.each do |label|
794
- maql += "# Creating Labels\n"
795
- maql += label.to_maql_create
796
- end
797
-
798
- references.values.each do |ref|
799
- maql += "# Creating references\n"
800
- maql += ref.to_maql_create
801
- end
802
-
803
- folders_maql = "# Create folders\n"
804
- (folders[:attributes].values + folders[:facts].values).each { |folder| folders_maql += folder.to_maql_create }
805
- folders_maql + "\n" + maql + "SYNCHRONIZE {#{identifier}};\n"
806
- end
807
-
808
- def upload(path, project = nil, mode = 'FULL')
809
- if path =~ URI::regexp
810
- Tempfile.open('remote_file') do |temp|
811
- temp << open(path).read
812
- temp.flush
813
- upload_data(temp, mode)
814
- end
815
- else
816
- upload_data(path, mode)
817
- end
818
- end
819
-
820
- def upload_data(path, mode)
821
- GoodData::Model.upload_data(path, to_manifest(mode))
822
- end
823
-
824
- # Generates the SLI manifest describing the data loading
825
- #
826
- def to_manifest(mode = 'FULL')
827
- {
828
- 'dataSetSLIManifest' => {
829
- 'parts' => fields.reduce([]) { |memo, f| val = f.to_manifest_part(mode); memo << val unless val.nil?; memo },
830
- 'dataSet' => self.identifier,
831
- 'file' => 'data.csv', # should be configurable
832
- 'csvParams' => {
833
- 'quoteChar' => '"',
834
- 'escapeChar' => '"',
835
- 'separatorChar' => ',',
836
- 'endOfLine' => "\n"
837
- }
838
- }
839
- }
840
- end
841
-
842
- def to_wire_model
843
- {
844
- 'dataset' => {
845
- 'identifier' => identifier,
846
- 'title' => title,
847
- 'anchor' => @anchor.to_wire_model,
848
- 'facts' => facts.map { |f| f.to_wire_model },
849
- 'attributes' => attributes.map { |a| a.to_wire_model },
850
- 'references' => references.map { |r| r.is_a?(DateReference) ? r.schema_ref : type_prefix + '.' + r.schema_ref }}
851
- }
852
- end
853
-
854
- private
855
-
856
- def add_attribute(column)
857
- attribute = Attribute.new column, self
858
- fields << attribute
859
- attributes << attribute
860
- add_attribute_folder(attribute.folder)
861
- # folders[AttributeFolder.new(attribute.folder)] = 1 if attribute.folder
862
- end
863
-
864
- def add_attribute_folder(name)
865
- return if name.nil?
866
- return if folders[:attributes].has_key?(name)
867
- folders[:attributes][name] = AttributeFolder.new(name)
868
- end
869
-
870
- def add_fact(column)
871
- fact = Fact.new column, self
872
- fields << fact
873
- facts << fact
874
- add_fact_folder(fact.folder)
875
- # folders[FactFolder.new(fact.folder)] = 1 if fact.folder
876
- end
877
-
878
- def add_fact_folder(name)
879
- return if name.nil?
880
- return if folders[:facts].has_key?(name)
881
- folders[:facts][name] = FactFolder.new(name)
882
- end
883
-
884
- def add_label(column)
885
- label = Label.new(column, nil, self)
886
- labels << label
887
- fields << label
888
- end
889
-
890
- def add_reference(column)
891
- reference = Reference.new(column, self)
892
- fields << reference
893
- references << reference
894
- end
895
-
896
- def add_date(column)
897
- date = DateColumn.new column, self
898
- @fields << date
899
- date.parts.values.each { |p| @fields << p }
900
- date.facts.each { |f| facts << f }
901
- date.attributes.each { |a| attributes << a }
902
- date.references.each { |r| references << r }
903
- end
904
-
905
- def set_anchor(column)
906
- @anchor = Anchor.new column, self
907
- @fields << @anchor
908
- end
909
- end
910
-
911
- ##
912
- # This is a base class for server-side LDM elements such as attributes, labels and
913
- # facts
914
- #
915
- class Column < MdObject
916
- attr_accessor :folder, :name, :title, :schema
917
-
918
- def initialize(hash, schema)
919
- super()
920
- raise ArgumentError.new("Schema must be provided, got #{schema.class}") unless schema.is_a? Schema
921
- raise('Data set fields must have their names defined') if hash[:name].nil?
922
-
923
- @name = hash[:name]
924
- @title = hash[:title] || hash[:name].humanize
925
- @folder = hash[:folder]
926
- @schema = schema
927
- end
928
-
929
- ##
930
- # Generates an identifier from the object name by transliterating
931
- # non-Latin character and then dropping non-alphanumerical characters.
932
- #
933
- def identifier
934
- @identifier ||= "#{self.type_prefix}.#{@schema.name}.#{name}"
935
- end
936
-
937
- def to_maql_drop
938
- "DROP {#{self.identifier}};\n"
939
- end
940
-
941
- def visual
942
- visual = super
943
- visual += ", FOLDER {#{folder_prefix}.#{(folder)}}" if folder
944
- visual
945
- end
946
-
947
- def to_csv_header(row)
948
- name
949
- end
950
-
951
- def to_csv_data(headers, row)
952
- row[name]
953
- end
954
-
955
-
956
- # Overriden to prevent long strings caused by the @schema attribute
957
- #
958
- def inspect
959
- to_s.sub(/>$/, " @title=#{@title.inspect}, @name=#{@name.inspect}, @folder=#{@folder.inspect}," \
960
- " @schema=#{@schema.to_s.sub(/>$/, ' @title=' + @schema.name.inspect + '>')}" \
961
- ">")
962
- end
963
- end
964
-
965
- ##
966
- # GoodData attribute abstraction
967
- #
968
- class Attribute < Column
969
- attr_reader :primary_label, :labels
970
-
971
- def type_prefix;
972
- ATTRIBUTE_PREFIX;
973
- end
974
-
975
- def folder_prefix;
976
- ATTRIBUTE_FOLDER_PREFIX;
977
- end
978
-
979
- def initialize(hash, schema)
980
- super hash, schema
981
- @labels = []
982
- @primary_label = Label.new hash, self, schema
983
- end
984
-
985
- def table
986
- @table ||= 'd_' + @schema.name + '_' + name
987
- end
988
-
989
- def key;
990
- "#{@name}#{FK_SUFFIX}";
991
- end
992
-
993
- def to_maql_create
994
- maql = "CREATE ATTRIBUTE {#{identifier}} VISUAL (#{visual})" \
995
- + " AS KEYS {#{table}.#{Model::FIELD_PK}} FULLSET;\n"
996
- maql += @primary_label.to_maql_create if @primary_label
997
- maql
998
- end
999
-
1000
- def to_manifest_part(mode)
1001
- {
1002
- 'referenceKey' => 1,
1003
- 'populates' => [@primary_label.identifier],
1004
- 'mode' => mode,
1005
- 'columnName' => name
1006
- }
1007
- end
1008
-
1009
- def to_wire_model
1010
- {
1011
- 'attribute' => {
1012
- 'identifier' => identifier,
1013
- 'title' => title,
1014
- 'labels' => labels.map do |l|
1015
- {
1016
- 'label' => {
1017
- 'identifier' => l.identifier,
1018
- 'title' => l.title,
1019
- 'type' => 'GDC.text'
1020
- }
1021
- }
1022
- end
1023
- }
1024
- }
1025
- end
1026
- end
1027
-
1028
- ##
1029
- # GoodData display form abstraction. Represents a default representation
1030
- # of an attribute column or an additional representation defined in a LABEL
1031
- # field
1032
- #
1033
- class Label < Column
1034
- attr_accessor :attribute
1035
-
1036
- def type_prefix;
1037
- 'label';
1038
- end
1039
-
1040
- # def initialize(hash, schema)
1041
- def initialize(hash, attribute, schema)
1042
- super hash, schema
1043
- attribute = attribute.nil? ? schema.fields.find { |field| field.name === hash[:reference] } : attribute
1044
- @attribute = attribute
1045
- attribute.labels << self
1046
- end
1047
-
1048
- def to_maql_create
1049
- '# LABEL FROM LABEL'
1050
- "ALTER ATTRIBUTE {#{@attribute.identifier}} ADD LABELS {#{identifier}}" \
1051
- + " VISUAL (TITLE #{title.inspect}) AS {#{column}};\n"
1052
- end
1053
-
1054
- def to_manifest_part(mode)
1055
- {
1056
- 'populates' => [identifier],
1057
- 'mode' => mode,
1058
- 'columnName' => name
1059
- }
1060
- end
1061
-
1062
- def column
1063
- "#{@attribute.table}.#{LABEL_COLUMN_PREFIX}#{name}"
1064
- end
1065
-
1066
- alias :inspect_orig :inspect
1067
-
1068
- def inspect
1069
- inspect_orig.sub(/>$/, " @attribute=#{@attribute.to_s.sub(/>$/, " @name=#{@attribute.name}")}>")
1070
- end
1071
- end
1072
-
1073
- ##
1074
- # A GoodData attribute that represents a data set's connection point or a data set
1075
- # without a connection point
1076
- #
1077
- class Anchor < Attribute
1078
- def initialize(column, schema)
1079
- if column then
1080
- super
1081
- else
1082
- super({:type => 'anchor', :name => 'id'}, schema)
1083
- @labels = []
1084
- @primary_label = nil
1085
- end
1086
- end
1087
-
1088
- def table
1089
- @table ||= 'f_' + @schema.name
1090
- end
1091
-
1092
- def to_maql_create
1093
- maql = super
1094
- maql += "\n# Connect '#{self.title}' to all attributes of this data set\n"
1095
- @schema.attributes.values.each do |c|
1096
- maql += "ALTER ATTRIBUTE {#{c.identifier}} ADD KEYS " \
1097
- + "{#{table}.#{c.key}};\n"
1098
- end
1099
- maql
1100
- end
1101
- end
1102
-
1103
- ##
1104
- # GoodData fact abstraction
1105
- #
1106
- class Fact < Column
1107
- def type_prefix;
1108
- FACT_PREFIX;
1109
- end
1110
-
1111
- def column_prefix;
1112
- FACT_COLUMN_PREFIX;
1113
- end
1114
-
1115
- def folder_prefix;
1116
- FACT_FOLDER_PREFIX;
1117
- end
1118
-
1119
- def table
1120
- @schema.table
1121
- end
1122
-
1123
- def column
1124
- @column ||= table + '.' + column_prefix + name
1125
- end
1126
-
1127
- def to_maql_create
1128
- "CREATE FACT {#{self.identifier}} VISUAL (#{visual})" \
1129
- + " AS {#{column}};\n"
1130
- end
1131
-
1132
- def to_manifest_part(mode)
1133
- {
1134
- 'populates' => [identifier],
1135
- 'mode' => mode,
1136
- 'columnName' => name
1137
- }
1138
- end
1139
-
1140
- def to_wire_model
1141
- {
1142
- 'fact' => {
1143
- 'identifier' => identifier,
1144
- 'title' => title
1145
- }
1146
- }
1147
- end
1148
- end
1149
-
1150
- ##
1151
- # Reference to another data set
1152
- #
1153
- class Reference < Column
1154
- attr_accessor :reference, :schema_ref
1155
-
1156
- def initialize(column, schema)
1157
- super column, schema
1158
- # pp column
1159
- @name = column[:name]
1160
- @reference = column[:reference]
1161
- @schema_ref = column[:dataset]
1162
- @schema = schema
1163
- end
1164
-
1165
- ##
1166
- # Generates an identifier of the referencing attribute using the
1167
- # schema name derived from schemaReference and column name derived
1168
- # from the reference key.
1169
- #
1170
- def identifier
1171
- @identifier ||= "#{ATTRIBUTE_PREFIX}.#{@schema_ref}.#{@reference}"
1172
- end
1173
-
1174
- def key;
1175
- "#{@name}_id";
1176
- end
1177
-
1178
- def label_column
1179
- "#{LABEL_PREFIX}.#{@schema_ref}.#{@reference}"
1180
- end
1181
-
1182
- def to_maql_create
1183
- "ALTER ATTRIBUTE {#{self.identifier}} ADD KEYS {#{@schema.table}.#{key}};\n"
1184
- end
1185
-
1186
- def to_maql_drop
1187
- "ALTER ATTRIBUTE {#{self.identifier} DROP KEYS {#{@schema.table}.#{key}};\n"
1188
- end
1189
-
1190
- def to_manifest_part(mode)
1191
- {
1192
- 'populates' => [label_column],
1193
- 'mode' => mode,
1194
- 'columnName' => name,
1195
- 'referenceKey' => 1
1196
- }
1197
- end
1198
- end
1199
-
1200
- ##
1201
- # Date as a reference to a date dimension
1202
- #
1203
- class DateReference < Reference
1204
- attr_accessor :format, :output_format, :urn
1205
-
1206
- def initialize(column, schema)
1207
- super column, schema
1208
- @output_format = column['format'] || 'dd/MM/yyyy'
1209
- @format = @output_format.gsub('yyyy', '%Y').gsub('MM', '%m').gsub('dd', '%d')
1210
- @urn = column[:urn] || 'URN:GOODDATA:DATE'
1211
- end
1212
-
1213
- def identifier
1214
- @identifier ||= "#{@schema_ref}.#{DATE_ATTRIBUTE}"
1215
- end
1216
-
1217
- def to_manifest_part(mode)
1218
- {
1219
- 'populates' => ["#{identifier}.#{DATE_ATTRIBUTE_DEFAULT_DISPLAY_FORM}"],
1220
- 'mode' => mode,
1221
- 'constraints' => {'date' => output_format},
1222
- 'columnName' => name,
1223
- 'referenceKey' => 1
1224
- }
1225
- end
1226
-
1227
- # def to_maql_create
1228
- # # urn:chefs_warehouse_fiscal:date
1229
- # super_maql = super
1230
- # maql = ""
1231
- # # maql = "# Include date dimensions\n"
1232
- # # maql += "INCLUDE TEMPLATE \"#{urn}\" MODIFY (IDENTIFIER \"#{name}\", TITLE \"#{title || name}\");\n"
1233
- # maql += super_maql
1234
- # end
1235
- end
1236
-
1237
- ##
1238
- # Date field that's not connected to a date dimension
1239
- #
1240
- class DateAttribute < Attribute
1241
- def key;
1242
- "#{DATE_COLUMN_PREFIX}#{super}";
1243
- end
1244
-
1245
- def to_manifest_part(mode)
1246
- {
1247
- 'populates' => ['label.stuff.mmddyy'],
1248
- 'format' => 'unknown',
1249
- 'mode' => mode,
1250
- 'referenceKey' => 1
1251
- }
1252
- end
1253
- end
1254
-
1255
- ##
1256
- # Fact representation of a time of a day
1257
- #
1258
- class TimeFact < Fact
1259
- def column_prefix;
1260
- TIME_COLUMN_PREFIX;
1261
- end
1262
-
1263
- def type_prefix;
1264
- TIME_FACT_PREFIX;
1265
- end
1266
- end
1267
-
1268
- ##
1269
- # Time as a reference to a time-of-a-day dimension
1270
- #
1271
- class TimeReference < Reference
1272
- end
1273
-
1274
- ##
1275
- # Time field that's not connected to a time-of-a-day dimension
1276
- #
1277
- class TimeAttribute < Attribute
1278
- def type_prefix;
1279
- TIME_ATTRIBUTE_PREFIX;
1280
- end
1281
-
1282
- def key;
1283
- "#{TIME_COLUMN_PREFIX}#{super}";
1284
- end
1285
-
1286
- def table;
1287
- @table ||= "#{super}_tm";
1288
- end
1289
- end
1290
-
1291
- ##
1292
- # Date column. A container holding the following
1293
- # parts: date fact, a date reference or attribute and an optional time component
1294
- # that contains a time fact and a time reference or attribute.
1295
- #
1296
- class DateColumn < Column
1297
- attr_reader :parts, :facts, :attributes, :references
1298
-
1299
- def initialize(column, schema)
1300
- super column, schema
1301
- @parts = {}; @facts = []; @attributes = []; @references = []
1302
-
1303
- # @facts << @parts[:date_fact] = DateFact.new(column, schema)
1304
- if column[:dataset] then
1305
- @parts[:date_ref] = DateReference.new column, schema
1306
- @references << @parts[:date_ref]
1307
- else
1308
- @attributes << @parts[:date_attr] = DateAttribute.new(column, schema)
1309
- end
1310
- # if column['datetime'] then
1311
- # puts "*** datetime"
1312
- # @facts << @parts[:time_fact] = TimeFact.new(column, schema)
1313
- # if column['schema_reference'] then
1314
- # @parts[:time_ref] = TimeReference.new column, schema
1315
- # else
1316
- # @attributes << @parts[:time_attr] = TimeAttribute.new(column, schema)
1317
- # end
1318
- # end
1319
- end
1320
-
1321
- def to_maql_create
1322
- @parts.values.map { |v| v.to_maql_create }.join "\n"
1323
- end
1324
-
1325
- def to_maql_drop
1326
- @parts.values.map { |v| v.to_maql_drop }.join "\n"
1327
- end
1328
-
1329
- def to_csv_header(row)
1330
- SKIP_FIELD
1331
- end
1332
-
1333
- def to_csv_data(headers, row)
1334
- SKIP_FIELD
1335
- end
1336
-
1337
- def to_manifest_part(mode)
1338
- nil
1339
- end
1340
- end
1341
-
1342
- ##
1343
- # Base class for GoodData attribute and fact folder abstractions
1344
- #
1345
- class Folder < MdObject
1346
- def initialize(title)
1347
- # TODO: should a super be here?
1348
- # how to deal with name vs title?
1349
- @title = title
1350
- @name = GoodData::Helpers.sanitize_string(title)
1351
- end
1352
-
1353
- def to_maql_create
1354
- "CREATE FOLDER {#{type_prefix}.#{name}}" \
1355
- + " VISUAL (#{visual}) TYPE #{type};\n"
1356
- end
1357
- end
1358
-
1359
- ##
1360
- # GoodData attribute folder abstraction
1361
- #
1362
- class AttributeFolder < Folder
1363
- def type;
1364
- 'ATTRIBUTE'
1365
- end
1366
-
1367
- def type_prefix;
1368
- 'dim'
1369
- end
1370
- end
1371
-
1372
- ##
1373
- # GoodData fact folder abstraction
1374
- #
1375
- class FactFolder < Folder
1376
- def type;
1377
- 'FACT'
1378
- end
1379
-
1380
- def type_prefix;
1381
- 'ffld'
1382
- end
1383
- end
1384
-
1385
- class DateDimension < MdObject
1386
- def initialize(spec={})
1387
- super()
1388
- @name = spec[:name]
1389
- @title = spec[:title] || @name
1390
- @urn = spec[:urn] || 'URN:GOODDATA:DATE'
1391
- end
1392
-
1393
- def to_maql_create
1394
- # urn = "urn:chefs_warehouse_fiscal:date"
1395
- # title = "title"
1396
- # name = "name"
1397
-
1398
- maql = ''
1399
- maql += "INCLUDE TEMPLATE \"#{@urn}\" MODIFY (IDENTIFIER \"#{@name}\", TITLE \"#{@title}\");"
1400
- maql
1401
- end
1402
- end
1403
102
  end
1404
- end
103
+ end